diff --git a/mobile/android/base/android-services.mozbuild b/mobile/android/base/android-services.mozbuild index 8bc5f8ab96a..f1a024a16c5 100644 --- a/mobile/android/base/android-services.mozbuild +++ b/mobile/android/base/android-services.mozbuild @@ -510,6 +510,8 @@ sync_java_files = [ 'background/fxa/FxAccountClientException.java', 'background/fxa/FxAccountRemoteError.java', 'background/fxa/FxAccountUtils.java', + 'background/fxa/PasswordStretcher.java', + 'background/fxa/QuickPasswordStretcher.java', 'background/fxa/SkewHandler.java', 'background/healthreport/Environment.java', 'background/healthreport/EnvironmentBuilder.java', @@ -568,7 +570,6 @@ sync_java_files = [ 'fxa/login/FxAccountLoginStateMachine.java', 'fxa/login/FxAccountLoginTransition.java', 'fxa/login/Married.java', - 'fxa/login/Promised.java', 'fxa/login/Separated.java', 'fxa/login/State.java', 'fxa/login/StateFactory.java', diff --git a/mobile/android/base/background/fxa/FxAccountClient.java b/mobile/android/base/background/fxa/FxAccountClient.java index 635b6a9d611..98c699e4151 100644 --- a/mobile/android/base/background/fxa/FxAccountClient.java +++ b/mobile/android/base/background/fxa/FxAccountClient.java @@ -11,8 +11,10 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.sync.ExtendedJSONObject; public interface FxAccountClient { - public void loginAndGetKeys(final byte[] emailUTF8, final byte[] quickStretchedPW, final RequestDelegate requestDelegate); + public void createAccountAndGetKeys(final byte[] emailUTF8, final PasswordStretcher passwordStretcher, final RequestDelegate delegate); + public void loginAndGetKeys(final byte[] emailUTF8, final PasswordStretcher passwordStretcher, final RequestDelegate requestDelegate); public void status(byte[] sessionToken, RequestDelegate requestDelegate); public void keys(byte[] keyFetchToken, RequestDelegate requestDelegate); public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate requestDelegate); + public void resendCode(byte[] sessionToken, RequestDelegate delegate); } diff --git a/mobile/android/base/background/fxa/FxAccountClient10.java b/mobile/android/base/background/fxa/FxAccountClient10.java index de553ab66fa..8bfe8e9cd06 100644 --- a/mobile/android/base/background/fxa/FxAccountClient10.java +++ b/mobile/android/base/background/fxa/FxAccountClient10.java @@ -70,7 +70,14 @@ public class FxAccountClient10 { protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE, JSON_KEY_INFO }; protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO }; + /** + * The server's URI. + *

+ * We assume throughout that this ends with a trailing slash (and guarantee as + * much in the constructor). + */ protected final String serverURI; + protected final Executor executor; public FxAccountClient10(String serverURI, Executor executor) { @@ -81,6 +88,9 @@ public class FxAccountClient10 { throw new IllegalArgumentException("Must provide a non-null executor."); } this.serverURI = (serverURI.endsWith("/") ? serverURI : serverURI + "/") + VERSION_FRAGMENT; + if (!this.serverURI.endsWith("/")) { + throw new IllegalArgumentException("Constructed serverURI must end with a trailing slash: " + this.serverURI); + } this.executor = executor; } @@ -282,8 +292,9 @@ public class FxAccountClient10 { String error; String message; String info; + ExtendedJSONObject body; try { - ExtendedJSONObject body = new SyncStorageResponse(response).jsonObjectBody(); + body = new SyncStorageResponse(response).jsonObjectBody(); body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class); body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class); code = body.getLong(JSON_KEY_CODE).intValue(); @@ -294,7 +305,7 @@ public class FxAccountClient10 { } catch (Exception e) { throw new FxAccountClientMalformedResponseException(response); } - throw new FxAccountClientRemoteException(response, code, errno, error, message, info); + throw new FxAccountClientRemoteException(response, code, errno, error, message, info, body); } public void createAccount(final String email, final byte[] stretchedPWBytes, diff --git a/mobile/android/base/background/fxa/FxAccountClient20.java b/mobile/android/base/background/fxa/FxAccountClient20.java index c805fbfa616..477fce1d83a 100644 --- a/mobile/android/base/background/fxa/FxAccountClient20.java +++ b/mobile/android/base/background/fxa/FxAccountClient20.java @@ -8,6 +8,9 @@ import java.net.URI; import java.util.concurrent.Executor; import org.json.simple.JSONObject; +import org.mozilla.gecko.background.common.log.Logger; +import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; +import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.net.BaseResource; @@ -23,31 +26,22 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli super(serverURI, executor); } - public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean preVerified, - final RequestDelegate delegate) { - try { - createAccount(new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified), delegate); - } catch (final Exception e) { - invokeHandleError(delegate, e); - return; - } - } - /** * Thin container for login response. + *

+ * The remoteEmail field is the email address as normalized by the + * server, and is not necessarily the email address delivered to the + * login or create call. */ public static class LoginResponse { - public final String serverURI; + public final String remoteEmail; public final String uid; public final byte[] sessionToken; public final boolean verified; public final byte[] keyFetchToken; - public LoginResponse(String serverURI, String uid, boolean verified, byte[] sessionToken, byte[] keyFetchToken) { - // This is pretty awful. - this.serverURI = serverURI.endsWith(VERSION_FRAGMENT) ? - serverURI.substring(0, serverURI.length() - VERSION_FRAGMENT.length()) : - serverURI; + public LoginResponse(String remoteEmail, String uid, boolean verified, byte[] sessionToken, byte[] keyFetchToken) { + this.remoteEmail = remoteEmail; this.uid = uid; this.verified = verified; this.sessionToken = sessionToken; @@ -55,16 +49,6 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli } } - public void login(final byte[] emailUTF8, final byte[] quickStretchedPW, - final RequestDelegate delegate) { - login(emailUTF8, quickStretchedPW, false, delegate); - } - - public void loginAndGetKeys(final byte[] emailUTF8, final byte[] quickStretchedPW, - final RequestDelegate delegate) { - login(emailUTF8, quickStretchedPW, true, delegate); - } - // Public for testing only; prefer login and loginAndGetKeys (without boolean parameter). public void login(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys, final RequestDelegate delegate) { @@ -83,18 +67,12 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli @Override public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) { try { - String[] requiredStringFields; - if (!getKeys) { - requiredStringFields = LOGIN_RESPONSE_REQUIRED_STRING_FIELDS; - } else { - requiredStringFields = LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS; - } - String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS; - + final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS; body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class); + + final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS; body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class); - LoginResponse loginResponse; String uid = body.getString(JSON_KEY_UID); boolean verified = body.getBoolean(JSON_KEY_VERIFIED); byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN)); @@ -102,7 +80,7 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli if (getKeys) { keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN)); } - loginResponse = new LoginResponse(serverURI, uid, verified, sessionToken, keyFetchToken); + LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken); delegate.handleSuccess(loginResponse); return; @@ -115,4 +93,137 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli post(resource, body, delegate); } + + public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys, final boolean preVerified, + final RequestDelegate delegate) { + BaseResource resource; + JSONObject body; + final String path = getKeys ? "account/create?keys=true" : "account/create"; + try { + resource = new BaseResource(new URI(serverURI + path)); + body = new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified).getCreateBody(); + } catch (Exception e) { + invokeHandleError(delegate, e); + return; + } + + // This is very similar to login, except verified is not required. + resource.delegate = new ResourceDelegate(resource, delegate) { + @Override + public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) { + try { + final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS; + body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class); + + String uid = body.getString(JSON_KEY_UID); + boolean verified = false; // In production, we're definitely not verified immediately upon creation. + Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED); + if (tempVerified != null) { + verified = tempVerified.booleanValue(); + } + byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN)); + byte[] keyFetchToken = null; + if (getKeys) { + keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN)); + } + LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken); + + delegate.handleSuccess(loginResponse); + return; + } catch (Exception e) { + delegate.handleError(e); + return; + } + } + }; + + post(resource, body, delegate); + } + + @Override + public void createAccountAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, RequestDelegate delegate) { + try { + byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(emailUTF8); + createAccount(emailUTF8, quickStretchedPW, true, false, delegate); + } catch (Exception e) { + invokeHandleError(delegate, e); + return; + } + } + + @Override + public void loginAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, RequestDelegate delegate) { + login(emailUTF8, passwordStretcher, true, delegate); + } + + /** + * We want users to be able to enter their email address case-insensitively. + * We stretch the password locally using the email address as a salt, to make + * dictionary attacks more expensive. This means that a client with a + * case-differing email address is unable to produce the correct + * authorization, even though it knows the password. In this case, the server + * returns the email that the account was created with, so that the client can + * re-stretch the password locally with the correct email salt. This version + * of login retries at most one time with a server provided email + * address. + *

+ * Be aware that consumers will not see the initial error response from the + * server providing an alternate email (if there is one). + * + * @param emailUTF8 + * user entered email address. + * @param stretcher + * delegate to stretch and re-stretch password. + * @param getKeys + * true if a keyFetchToken should be returned (in + * addition to the standard sessionToken). + * @param delegate + * to invoke callbacks. + */ + public void login(final byte[] emailUTF8, final PasswordStretcher stretcher, final boolean getKeys, + final RequestDelegate delegate) { + byte[] quickStretchedPW; + try { + FxAccountConstants.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" ); + quickStretchedPW = stretcher.getQuickStretchedPW(emailUTF8); + } catch (Exception e) { + delegate.handleError(e); + return; + } + + this.login(emailUTF8, quickStretchedPW, getKeys, new RequestDelegate() { + @Override + public void handleSuccess(LoginResponse result) { + delegate.handleSuccess(result); + } + + @Override + public void handleError(Exception e) { + delegate.handleError(e); + } + + @Override + public void handleFailure(FxAccountClientRemoteException e) { + String alternateEmail = e.body.getString(JSON_KEY_EMAIL); + if (!e.isBadEmailCase() || alternateEmail == null) { + delegate.handleFailure(e); + return; + }; + + Logger.info(LOG_TAG, "Server returned alternate email; retrying login with provided email."); + FxAccountConstants.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" ); + + try { + // Nota bene: this is not recursive, since we call the fixed password + // signature here, which invokes a non-retrying version. + byte[] alternateEmailUTF8 = alternateEmail.getBytes("UTF-8"); + byte[] alternateQuickStretchedPW = stretcher.getQuickStretchedPW(alternateEmailUTF8); + login(alternateEmailUTF8, alternateQuickStretchedPW, getKeys, delegate); + } catch (Exception innerException) { + delegate.handleError(innerException); + return; + } + } + }); + } } diff --git a/mobile/android/base/background/fxa/FxAccountClientException.java b/mobile/android/base/background/fxa/FxAccountClientException.java index d069c682791..913a77f9597 100644 --- a/mobile/android/base/background/fxa/FxAccountClientException.java +++ b/mobile/android/base/background/fxa/FxAccountClientException.java @@ -5,6 +5,7 @@ package org.mozilla.gecko.background.fxa; import org.mozilla.gecko.R; +import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.HTTPFailureException; import org.mozilla.gecko.sync.net.SyncStorageResponse; @@ -34,15 +35,20 @@ public class FxAccountClientException extends Exception { public final String error; public final String message; public final String info; + public final ExtendedJSONObject body; - public FxAccountClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, String info) { + public FxAccountClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, String info, ExtendedJSONObject body) { super(new HTTPFailureException(new SyncStorageResponse(response))); + if (body == null) { + throw new IllegalArgumentException("body must not be null"); + } this.response = response; this.httpStatusCode = httpStatusCode; this.apiErrorNumber = apiErrorNumber; this.error = error; this.message = message; this.info = info; + this.body = body; } @Override @@ -55,6 +61,10 @@ public class FxAccountClientException extends Exception { } public boolean isAccountAlreadyExists() { + return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS; + } + + public boolean isAccountDoesNotExist() { return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST; } @@ -74,35 +84,44 @@ public class FxAccountClientException extends Exception { apiErrorNumber == FxAccountRemoteError.INCORRECT_API_VERSION_FOR_THIS_ACCOUNT; } + public boolean isTooManyRequests() { + return apiErrorNumber == FxAccountRemoteError.CLIENT_HAS_SENT_TOO_MANY_REQUESTS; + } + + public boolean isServerUnavailable() { + return apiErrorNumber == FxAccountRemoteError.SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD; + } + + public boolean isBadEmailCase() { + return apiErrorNumber == FxAccountRemoteError.INCORRECT_EMAIL_CASE; + } + public int getErrorMessageStringResource() { if (isUpgradeRequired()) { return R.string.fxaccount_remote_error_UPGRADE_REQUIRED; - } - switch ((int) apiErrorNumber) { - case FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS: + } else if (isAccountAlreadyExists()) { return R.string.fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS; - case FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST: + } else if (isAccountDoesNotExist()) { return R.string.fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST; - case FxAccountRemoteError.INCORRECT_PASSWORD: + } else if (isBadPassword()) { return R.string.fxaccount_remote_error_INCORRECT_PASSWORD; - case FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT: + } else if (isUnverified()) { return R.string.fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT; - case FxAccountRemoteError.CLIENT_HAS_SENT_TOO_MANY_REQUESTS: + } else if (isTooManyRequests()) { return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS; - case FxAccountRemoteError.SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD: + } else if (isServerUnavailable()) { return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD; - default: + } else { return R.string.fxaccount_remote_error_UNKNOWN_ERROR; } } } - public static class FxAccountClientMalformedResponseException extends FxAccountClientRemoteException { private static final long serialVersionUID = 2209313149952001098L; public FxAccountClientMalformedResponseException(HttpResponse response) { - super(response, 0, FxAccountRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", "Response malformed"); + super(response, 0, FxAccountRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", "Response malformed", new ExtendedJSONObject()); } } } diff --git a/mobile/android/base/background/fxa/FxAccountRemoteError.java b/mobile/android/base/background/fxa/FxAccountRemoteError.java index c486c585e04..cb0894353ac 100644 --- a/mobile/android/base/background/fxa/FxAccountRemoteError.java +++ b/mobile/android/base/background/fxa/FxAccountRemoteError.java @@ -25,6 +25,7 @@ public interface FxAccountRemoteError { public static final int INCORRECT_LOGIN_METHOD_FOR_THIS_ACCOUNT = 117; public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118; public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119; + public static final int INCORRECT_EMAIL_CASE = 120; public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201; public static final int UNKNOWN_ERROR = 999; } diff --git a/mobile/android/base/background/fxa/PasswordStretcher.java b/mobile/android/base/background/fxa/PasswordStretcher.java new file mode 100644 index 00000000000..2debf3c77ef --- /dev/null +++ b/mobile/android/base/background/fxa/PasswordStretcher.java @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.background.fxa; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; + +public interface PasswordStretcher { + public byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException; +} diff --git a/mobile/android/base/background/fxa/QuickPasswordStretcher.java b/mobile/android/base/background/fxa/QuickPasswordStretcher.java new file mode 100644 index 00000000000..9e50f2ba980 --- /dev/null +++ b/mobile/android/base/background/fxa/QuickPasswordStretcher.java @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.background.fxa; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; + +import org.mozilla.gecko.sync.Utils; + +public class QuickPasswordStretcher implements PasswordStretcher { + protected final String password; + protected final Map cache = new HashMap(); + + public QuickPasswordStretcher(String password) { + this.password = password; + } + + public synchronized byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException { + if (emailUTF8 == null) { + throw new IllegalArgumentException("emailUTF8 must not be null"); + } + String key = Utils.byte2Hex(emailUTF8); + if (!cache.containsKey(key)) { + byte[] value = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes("UTF-8")); + cache.put(key, Utils.byte2Hex(value)); + return value; + } + return Utils.hex2Byte(cache.get(key)); + } +} diff --git a/mobile/android/base/fxa/FxAccountConstants.java.in b/mobile/android/base/fxa/FxAccountConstants.java.in index a8bac31769e..c44d46433d0 100644 --- a/mobile/android/base/fxa/FxAccountConstants.java.in +++ b/mobile/android/base/fxa/FxAccountConstants.java.in @@ -11,7 +11,7 @@ public class FxAccountConstants { public static final String GLOBAL_LOG_TAG = "FxAccounts"; public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"; - public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts.dev.lcip.org"; + public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts-latest.dev.lcip.org"; public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.dev.lcip.org"; public static final String DEFAULT_TOKEN_SERVER_URI = DEFAULT_TOKEN_SERVER_ENDPOINT + (DEFAULT_TOKEN_SERVER_ENDPOINT.endsWith("/") ? "" : "/") + "1.0/sync/1.5"; diff --git a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java index 7f0a2584fc0..005da85684a 100644 --- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java @@ -8,9 +8,20 @@ import java.io.IOException; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; +import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; +import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; +import org.mozilla.gecko.background.fxa.FxAccountUtils; +import org.mozilla.gecko.background.fxa.PasswordStretcher; +import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay; +import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; +import org.mozilla.gecko.fxa.login.Engaged; +import org.mozilla.gecko.fxa.login.State; +import org.mozilla.gecko.sync.setup.Constants; +import android.accounts.AccountManager; +import android.content.Intent; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; @@ -164,4 +175,83 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc progressBar.setVisibility(View.INVISIBLE); button.setVisibility(View.VISIBLE); } + + public Intent makeSuccessIntent(String email, LoginResponse result) { + Intent successIntent; + if (result.verified) { + successIntent = new Intent(this, FxAccountVerifiedAccountActivity.class); + } else { + successIntent = new Intent(this, FxAccountConfirmAccountActivity.class); + successIntent.putExtra("sessionToken", result.sessionToken); + } + successIntent.putExtra("email", email); + // Per http://stackoverflow.com/a/8992365, this triggers a known bug with + // the soft keyboard not being shown for the started activity. Why, Android, why? + successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + return successIntent; + } + + protected abstract class AddAccountDelegate implements RequestDelegate { + public final String email; + public final PasswordStretcher passwordStretcher; + public final String serverURI; + + public AddAccountDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) { + this.email = email; + this.passwordStretcher = passwordStretcher; + this.serverURI = serverURI; + } + + @Override + public void handleSuccess(LoginResponse result) { + Logger.info(LOG_TAG, "Got success response; adding Android account."); + + // We're on the UI thread, but it's okay to create the account here. + AndroidFxAccount fxAccount; + try { + final String profile = Constants.DEFAULT_PROFILE; + final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI; + // It is crucial that we use the email address provided by the server + // (rather than whatever the user entered), because the user's keys are + // wrapped and salted with the initial email they provided to + // /create/account. Of course, we want to pass through what the user + // entered locally as much as possible, so we create the Android account + // with their entered email address, etc. + // The passwordStretcher should have seen this email address before, so + // we shouldn't be calculating the expensive stretch twice. + byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8")); + byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); + State state = new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken); + fxAccount = AndroidFxAccount.addAndroidAccount(getApplicationContext(), + email, + profile, + serverURI, + tokenServerURI, + state); + if (fxAccount == null) { + throw new RuntimeException("Could not add Android account."); + } + } catch (Exception e) { + handleError(e); + return; + } + + // For great debugging. + if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { + fxAccount.dump(); + } + + // The GetStarted activity has called us and needs to return a result to the authenticator. + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE); + // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType); + setResult(RESULT_OK, intent); + + // Show success activity depending on verification status. + Intent successIntent = makeSuccessIntent(email, result); + startActivity(successIntent); + finish(); + } + } } diff --git a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java index 6631f949065..c3b04c26fe4 100644 --- a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java @@ -9,6 +9,7 @@ import java.util.concurrent.Executors; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; +import org.mozilla.gecko.background.fxa.FxAccountClient; import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.gecko.background.fxa.FxAccountClient20; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; @@ -85,7 +86,7 @@ public class FxAccountConfirmAccountActivity extends Activity implements OnClick protected final byte[] sessionToken; - public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient20 client, RequestDelegate delegate) { + public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { super(context, null, client, delegate); this.sessionToken = sessionToken; } @@ -126,7 +127,7 @@ public class FxAccountConfirmAccountActivity extends Activity implements OnClick String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT; RequestDelegate delegate = new ResendCodeDelegate(); Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient20 client = new FxAccountClient20(serverURI, executor); + FxAccountClient client = new FxAccountClient20(serverURI, executor); new FxAccountResendCodeTask(this, sessionToken, client, delegate).execute(); } diff --git a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java index d40da9c6249..496e5f8e94a 100644 --- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java @@ -10,19 +10,16 @@ import java.util.concurrent.Executors; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; +import org.mozilla.gecko.background.fxa.FxAccountClient; import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.gecko.background.fxa.FxAccountClient20; +import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.FxAccountUtils; +import org.mozilla.gecko.background.fxa.PasswordStretcher; +import org.mozilla.gecko.background.fxa.QuickPasswordStretcher; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Promised; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.sync.setup.Constants; -import android.accounts.AccountManager; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; @@ -134,85 +131,28 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi }); } - protected class CreateAccountDelegate implements RequestDelegate { - public final String email; - public final String password; - public final String serverURI; - - public CreateAccountDelegate(String email, String password, String serverURI) { - this.email = email; - this.password = password; - this.serverURI = serverURI; - } - - @Override - public void handleError(Exception e) { - showRemoteError(e, R.string.fxaccount_create_account_unknown_error); - } - - @Override - public void handleFailure(final FxAccountClientRemoteException e) { - showRemoteError(e, R.string.fxaccount_create_account_unknown_error); - } - - @Override - public void handleSuccess(String uid) { - Activity activity = FxAccountCreateAccountActivity.this; - Logger.info(LOG_TAG, "Got success creating account."); - - // We're on the UI thread, but it's okay to create the account here. - AndroidFxAccount fxAccount; - try { - final String profile = Constants.DEFAULT_PROFILE; - final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI; - // TODO: This is wasteful. We should be able to thread these through so they don't get recomputed. - byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8")); - byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); - State state = new Promised(email, uid, false, unwrapkB, quickStretchedPW); - fxAccount = AndroidFxAccount.addAndroidAccount(activity, email, password, - profile, - serverURI, - tokenServerURI, - state); - if (fxAccount == null) { - throw new RuntimeException("Could not add Android account."); - } - } catch (Exception e) { - handleError(e); - return; - } - - // For great debugging. - if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { - fxAccount.dump(); - } - - // The GetStarted activity has called us and needs to return a result to the authenticator. - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE); - // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType); - setResult(RESULT_OK, intent); - finish(); - - // Show success activity. - Intent successIntent = new Intent(FxAccountCreateAccountActivity.this, FxAccountConfirmAccountActivity.class); - successIntent.putExtra("email", email); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(successIntent); - } - } - public void createAccount(String email, String password) { String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT; - RequestDelegate delegate = new CreateAccountDelegate(email, password, serverURI); + PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password); + // This delegate creates a new Android account on success, opens the + // appropriate "success!" activity, and finishes this activity. + RequestDelegate delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) { + @Override + public void handleError(Exception e) { + showRemoteError(e, R.string.fxaccount_create_account_unknown_error); + } + + @Override + public void handleFailure(FxAccountClientRemoteException e) { + showRemoteError(e, R.string.fxaccount_create_account_unknown_error); + } + }; + Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient20 client = new FxAccountClient20(serverURI, executor); + FxAccountClient client = new FxAccountClient20(serverURI, executor); try { hideRemoteError(); - new FxAccountCreateAccountTask(this, this, email, password, client, delegate).execute(); + new FxAccountCreateAccountTask(this, this, email, passwordStretcher, client, delegate).execute(); } catch (Exception e) { showRemoteError(e, R.string.fxaccount_create_account_unknown_error); } diff --git a/mobile/android/base/fxa/activities/FxAccountSetupTask.java b/mobile/android/base/fxa/activities/FxAccountSetupTask.java index 98129a487fe..1cb63fa1b6b 100644 --- a/mobile/android/base/fxa/activities/FxAccountSetupTask.java +++ b/mobile/android/base/fxa/activities/FxAccountSetupTask.java @@ -5,15 +5,14 @@ package org.mozilla.gecko.fxa.activities; import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; import java.util.concurrent.CountDownLatch; import org.mozilla.gecko.background.common.log.Logger; +import org.mozilla.gecko.background.fxa.FxAccountClient; import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20; import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.FxAccountUtils; +import org.mozilla.gecko.background.fxa.PasswordStretcher; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate; import android.content.Context; @@ -37,7 +36,7 @@ abstract class FxAccountSetupTask extends AsyncTask extends AsyncTask delegate; - public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient20 client, RequestDelegate delegate) { + public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { this.context = context; this.client = client; this.delegate = delegate; @@ -119,36 +118,22 @@ abstract class FxAccountSetupTask extends AsyncTask { + public static class FxAccountCreateAccountTask extends FxAccountSetupTask { private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); protected final byte[] emailUTF8; - protected final byte[] passwordUTF8; + protected final PasswordStretcher passwordStretcher; - public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, String password, FxAccountClient20 client, RequestDelegate delegate) throws UnsupportedEncodingException { + public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { super(context, progressDisplay, client, delegate); this.emailUTF8 = email.getBytes("UTF-8"); - this.passwordUTF8 = password.getBytes("UTF-8"); - } - - /** - * Stretching the password is expensive, so we compute the stretched value lazily. - * - * @return stretched password. - * @throws GeneralSecurityException - * @throws UnsupportedEncodingException - */ - public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException { - if (this.quickStretchedPW == null) { - this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8); - } - return this.quickStretchedPW; + this.passwordStretcher = passwordStretcher; } @Override - protected InnerRequestDelegate doInBackground(Void... arg0) { + protected InnerRequestDelegate doInBackground(Void... arg0) { try { - client.createAccount(emailUTF8, generateQuickStretchedPW(), false, innerDelegate); + client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); latch.await(); return innerDelegate; } catch (Exception e) { @@ -163,32 +148,18 @@ abstract class FxAccountSetupTask extends AsyncTask delegate) throws UnsupportedEncodingException { + public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { super(context, progressDisplay, client, delegate); this.emailUTF8 = email.getBytes("UTF-8"); - this.passwordUTF8 = password.getBytes("UTF-8"); - } - - /** - * Stretching the password is expensive, so we compute the stretched value lazily. - * - * @return stretched password. - * @throws GeneralSecurityException - * @throws UnsupportedEncodingException - */ - public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException { - if (this.quickStretchedPW == null) { - this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8); - } - return this.quickStretchedPW; + this.passwordStretcher = passwordStretcher; } @Override protected InnerRequestDelegate doInBackground(Void... arg0) { try { - client.loginAndGetKeys(emailUTF8, generateQuickStretchedPW(), innerDelegate); + client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); latch.await(); return innerDelegate; } catch (Exception e) { diff --git a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java index 1c81f3aadbe..57d15128bd7 100644 --- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java @@ -9,20 +9,16 @@ import java.util.concurrent.Executors; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; +import org.mozilla.gecko.background.fxa.FxAccountClient; import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.gecko.background.fxa.FxAccountClient20; import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.FxAccountUtils; +import org.mozilla.gecko.background.fxa.PasswordStretcher; +import org.mozilla.gecko.background.fxa.QuickPasswordStretcher; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Engaged; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.sync.setup.Constants; -import android.accounts.AccountManager; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; @@ -103,91 +99,28 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity { this.finish(); } - protected class SignInDelegate implements RequestDelegate { - public final String email; - public final String password; - public final String serverURI; - - public SignInDelegate(String email, String password, String serverURI) { - this.email = email; - this.password = password; - this.serverURI = serverURI; - } - - @Override - public void handleError(Exception e) { - showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); - } - - @Override - public void handleSuccess(LoginResponse result) { - Activity activity = FxAccountSignInActivity.this; - Logger.info(LOG_TAG, "Got success signing in."); - - // We're on the UI thread, but it's okay to create the account here. - AndroidFxAccount fxAccount; - try { - final String profile = Constants.DEFAULT_PROFILE; - final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI; - // TODO: This is wasteful. We should be able to thread these through so they don't get recomputed. - byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8")); - byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); - State state = new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken); - fxAccount = AndroidFxAccount.addAndroidAccount(activity, email, password, - profile, - serverURI, - tokenServerURI, - state); - if (fxAccount == null) { - throw new RuntimeException("Could not add Android account."); - } - } catch (Exception e) { - handleError(e); - return; - } - - // For great debugging. - if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { - fxAccount.dump(); - } - - // The GetStarted activity has called us and needs to return a result to the authenticator. - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE); - // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType); - setResult(RESULT_OK, intent); - finish(); - - // Show success activity depending on verification status. - Intent successIntent; - if (result.verified) { - successIntent = new Intent(FxAccountSignInActivity.this, FxAccountVerifiedAccountActivity.class); - } else { - successIntent = new Intent(FxAccountSignInActivity.this, FxAccountConfirmAccountActivity.class); - successIntent.putExtra("sessionToken", result.sessionToken); - } - successIntent.putExtra("email", email); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(successIntent); - } - } - public void signIn(String email, String password) { String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT; - RequestDelegate delegate = new SignInDelegate(email, password, serverURI); + PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password); + // This delegate creates a new Android account on success, opens the + // appropriate "success!" activity, and finishes this activity. + RequestDelegate delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) { + @Override + public void handleError(Exception e) { + showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); + } + + @Override + public void handleFailure(FxAccountClientRemoteException e) { + showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); + } + }; + Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient20 client = new FxAccountClient20(serverURI, executor); + FxAccountClient client = new FxAccountClient20(serverURI, executor); try { hideRemoteError(); - new FxAccountSignInTask(this, this, email, password, client, delegate).execute(); + new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute(); } catch (Exception e) { showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); } diff --git a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java index 9308393f7a8..57059789e76 100644 --- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java @@ -4,18 +4,19 @@ package org.mozilla.gecko.fxa.activities; -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.mozilla.gecko.R; import org.mozilla.gecko.background.common.log.Logger; +import org.mozilla.gecko.background.fxa.FxAccountClient; import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.gecko.background.fxa.FxAccountClient20; import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; import org.mozilla.gecko.background.fxa.FxAccountUtils; +import org.mozilla.gecko.background.fxa.PasswordStretcher; +import org.mozilla.gecko.background.fxa.QuickPasswordStretcher; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; @@ -110,16 +111,13 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc protected class UpdateCredentialsDelegate implements RequestDelegate { public final String email; - public final String password; public final String serverURI; - public final byte[] quickStretchedPW; + public final PasswordStretcher passwordStretcher; - public UpdateCredentialsDelegate(String email, String password, String serverURI) throws UnsupportedEncodingException, GeneralSecurityException { + public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) { this.email = email; - this.password = password; this.serverURI = serverURI; - // XXX This needs to be calculated lazily. - this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8")); + this.passwordStretcher = passwordStretcher; } @Override @@ -144,6 +142,12 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc byte[] unwrapkB; try { + // It is crucial that we use the email address provided by the server + // (rather than whatever the user entered), because the user's keys are + // wrapped and salted with the initial email they provided to + // /create/account. Of course, we want to pass through what the user + // entered locally as much as possible. + byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8")); unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); } catch (Exception e) { this.handleError(e); @@ -163,11 +167,12 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc public void updateCredentials(String email, String password) { String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT; Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient20 client = new FxAccountClient20(serverURI, executor); + FxAccountClient client = new FxAccountClient20(serverURI, executor); + PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password); try { hideRemoteError(); - RequestDelegate delegate = new UpdateCredentialsDelegate(email, password, serverURI); - new FxAccountSignInTask(this, this, email, password, client, delegate).execute(); + RequestDelegate delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI); + new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute(); } catch (Exception e) { Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e); showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error); diff --git a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java index f69b917d8bd..2be56c095ef 100644 --- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java +++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java @@ -262,7 +262,6 @@ public class AndroidFxAccount { public static AndroidFxAccount addAndroidAccount( Context context, String email, - String password, String profile, String idpServerURI, String tokenServerURI, @@ -271,9 +270,6 @@ public class AndroidFxAccount { if (email == null) { throw new IllegalArgumentException("email must not be null"); } - if (password == null) { - throw new IllegalArgumentException("password must not be null"); - } if (idpServerURI == null) { throw new IllegalArgumentException("idpServerURI must not be null"); } @@ -303,7 +299,10 @@ public class AndroidFxAccount { Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE); AccountManager accountManager = AccountManager.get(context); - boolean added = accountManager.addAccountExplicitly(account, null, userdata); // XXX what should the password be? + // We don't set an Android password, because we don't want to persist the + // password (or anything else as powerful as the password). Instead, we + // internally manage a sessionToken with a remotely owned lifecycle. + boolean added = accountManager.addAccountExplicitly(account, null, userdata); if (!added) { return null; } diff --git a/mobile/android/base/fxa/login/State.java b/mobile/android/base/fxa/login/State.java index 91f044c57b8..5974ed24d2d 100644 --- a/mobile/android/base/fxa/login/State.java +++ b/mobile/android/base/fxa/login/State.java @@ -12,7 +12,6 @@ public abstract class State { public static final long CURRENT_VERSION = 1L; public enum StateLabel { - Promised, Engaged, Cohabiting, Married, diff --git a/mobile/android/base/fxa/login/StateFactory.java b/mobile/android/base/fxa/login/StateFactory.java index e81d8c18d27..d604f95d2b6 100644 --- a/mobile/android/base/fxa/login/StateFactory.java +++ b/mobile/android/base/fxa/login/StateFactory.java @@ -20,13 +20,6 @@ public class StateFactory { throw new IllegalStateException("version must be 1"); } switch (stateLabel) { - case Promised: - return new Promised( - o.getString("email"), - o.getString("uid"), - o.getBoolean("verified"), - Utils.hex2Byte(o.getString("unwrapkB")), - Utils.hex2Byte(o.getString("quickStretchedPW"))); case Engaged: return new Engaged( o.getString("email"),