mobile/android/base/background/fxa/FxAccountClient20.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.background.fxa;
     7 import java.net.URI;
     8 import java.util.concurrent.Executor;
    10 import org.json.simple.JSONObject;
    11 import org.mozilla.gecko.background.common.log.Logger;
    12 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
    13 import org.mozilla.gecko.fxa.FxAccountConstants;
    14 import org.mozilla.gecko.sync.ExtendedJSONObject;
    15 import org.mozilla.gecko.sync.Utils;
    16 import org.mozilla.gecko.sync.net.BaseResource;
    18 import ch.boye.httpclientandroidlib.HttpResponse;
    20 public class FxAccountClient20 extends FxAccountClient10 implements FxAccountClient {
    21   protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN };
    22   protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN, JSON_KEY_KEYFETCHTOKEN, };
    23   protected static final String[] LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS = new String[] { JSON_KEY_VERIFIED };
    25   public FxAccountClient20(String serverURI, Executor executor) {
    26     super(serverURI, executor);
    27   }
    29   /**
    30    * Thin container for login response.
    31    * <p>
    32    * The <code>remoteEmail</code> field is the email address as normalized by the
    33    * server, and is <b>not necessarily</b> the email address delivered to the
    34    * <code>login</code> or <code>create</code> call.
    35    */
    36   public static class LoginResponse {
    37     public final String remoteEmail;
    38     public final String uid;
    39     public final byte[] sessionToken;
    40     public final boolean verified;
    41     public final byte[] keyFetchToken;
    43     public LoginResponse(String remoteEmail, String uid, boolean verified, byte[] sessionToken, byte[] keyFetchToken) {
    44       this.remoteEmail = remoteEmail;
    45       this.uid = uid;
    46       this.verified = verified;
    47       this.sessionToken = sessionToken;
    48       this.keyFetchToken = keyFetchToken;
    49     }
    50   }
    52   // Public for testing only; prefer login and loginAndGetKeys (without boolean parameter).
    53   public void login(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys,
    54       final RequestDelegate<LoginResponse> delegate) {
    55     BaseResource resource;
    56     JSONObject body;
    57     final String path = getKeys ? "account/login?keys=true" : "account/login";
    58     try {
    59       resource = new BaseResource(new URI(serverURI + path));
    60       body = new FxAccount20LoginDelegate(emailUTF8, quickStretchedPW).getCreateBody();
    61     } catch (Exception e) {
    62       invokeHandleError(delegate, e);
    63       return;
    64     }
    66     resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
    67       @Override
    68       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
    69         try {
    70           final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
    71           body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
    73           final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
    74           body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
    76           String uid = body.getString(JSON_KEY_UID);
    77           boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
    78           byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
    79           byte[] keyFetchToken = null;
    80           if (getKeys) {
    81             keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
    82           }
    83           LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
    85           delegate.handleSuccess(loginResponse);
    86           return;
    87         } catch (Exception e) {
    88           delegate.handleError(e);
    89           return;
    90         }
    91       }
    92     };
    94     post(resource, body, delegate);
    95   }
    97   public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys, final boolean preVerified,
    98       final RequestDelegate<LoginResponse> delegate) {
    99     BaseResource resource;
   100     JSONObject body;
   101     final String path = getKeys ? "account/create?keys=true" : "account/create";
   102     try {
   103       resource = new BaseResource(new URI(serverURI + path));
   104       body = new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified).getCreateBody();
   105     } catch (Exception e) {
   106       invokeHandleError(delegate, e);
   107       return;
   108     }
   110     // This is very similar to login, except verified is not required.
   111     resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
   112       @Override
   113       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
   114         try {
   115           final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
   116           body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
   118           String uid = body.getString(JSON_KEY_UID);
   119           boolean verified = false; // In production, we're definitely not verified immediately upon creation.
   120           Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
   121           if (tempVerified != null) {
   122             verified = tempVerified.booleanValue();
   123           }
   124           byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
   125           byte[] keyFetchToken = null;
   126           if (getKeys) {
   127             keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
   128           }
   129           LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
   131           delegate.handleSuccess(loginResponse);
   132           return;
   133         } catch (Exception e) {
   134           delegate.handleError(e);
   135           return;
   136         }
   137       }
   138     };
   140     post(resource, body, delegate);
   141   }
   143   @Override
   144   public void createAccountAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, RequestDelegate<LoginResponse> delegate) {
   145     try {
   146       byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(emailUTF8);
   147       createAccount(emailUTF8, quickStretchedPW, true, false, delegate);
   148     } catch (Exception e) {
   149       invokeHandleError(delegate, e);
   150       return;
   151     }
   152   }
   154   @Override
   155   public void loginAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, RequestDelegate<LoginResponse> delegate) {
   156     login(emailUTF8, passwordStretcher, true, delegate);
   157   }
   159   /**
   160    * We want users to be able to enter their email address case-insensitively.
   161    * We stretch the password locally using the email address as a salt, to make
   162    * dictionary attacks more expensive. This means that a client with a
   163    * case-differing email address is unable to produce the correct
   164    * authorization, even though it knows the password. In this case, the server
   165    * returns the email that the account was created with, so that the client can
   166    * re-stretch the password locally with the correct email salt. This version
   167    * of <code>login</code> retries at most one time with a server provided email
   168    * address.
   169    * <p>
   170    * Be aware that consumers will not see the initial error response from the
   171    * server providing an alternate email (if there is one).
   172    *
   173    * @param emailUTF8
   174    *          user entered email address.
   175    * @param stretcher
   176    *          delegate to stretch and re-stretch password.
   177    * @param getKeys
   178    *          true if a <code>keyFetchToken</code> should be returned (in
   179    *          addition to the standard <code>sessionToken</code>).
   180    * @param delegate
   181    *          to invoke callbacks.
   182    */
   183   public void login(final byte[] emailUTF8, final PasswordStretcher stretcher, final boolean getKeys,
   184       final RequestDelegate<LoginResponse> delegate) {
   185     byte[] quickStretchedPW;
   186     try {
   187       FxAccountConstants.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" );
   188       quickStretchedPW = stretcher.getQuickStretchedPW(emailUTF8);
   189     } catch (Exception e) {
   190       delegate.handleError(e);
   191       return;
   192     }
   194     this.login(emailUTF8, quickStretchedPW, getKeys, new RequestDelegate<LoginResponse>() {
   195       @Override
   196       public void handleSuccess(LoginResponse result) {
   197         delegate.handleSuccess(result);
   198       }
   200       @Override
   201       public void handleError(Exception e) {
   202         delegate.handleError(e);
   203       }
   205       @Override
   206       public void handleFailure(FxAccountClientRemoteException e) {
   207         String alternateEmail = e.body.getString(JSON_KEY_EMAIL);
   208         if (!e.isBadEmailCase() || alternateEmail == null) {
   209           delegate.handleFailure(e);
   210           return;
   211         };
   213         Logger.info(LOG_TAG, "Server returned alternate email; retrying login with provided email.");
   214         FxAccountConstants.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" );
   216         try {
   217           // Nota bene: this is not recursive, since we call the fixed password
   218           // signature here, which invokes a non-retrying version.
   219           byte[] alternateEmailUTF8 = alternateEmail.getBytes("UTF-8");
   220           byte[] alternateQuickStretchedPW = stretcher.getQuickStretchedPW(alternateEmailUTF8);
   221           login(alternateEmailUTF8, alternateQuickStretchedPW, getKeys, delegate);
   222         } catch (Exception innerException) {
   223           delegate.handleError(innerException);
   224           return;
   225         }
   226       }
   227     });
   228   }
   229 }

mercurial