Bug 955808 - Follow-up: Fix bustage. r=me
authorNick Alexander <nalexander@mozilla.com>
Tue, 07 Jan 2014 20:29:18 -0800
changeset 162515 ec04925b2e819979fdfc09f01fb2f275a5b77cf2
parent 162514 6fbf42e15bb1803e21f92de66ab63a3fc6b3892b
child 162516 a30925f7f28e8dbdead44247dcd6417ee15f9237
push id25956
push usernalexander@mozilla.com
push dateWed, 08 Jan 2014 18:09:17 +0000
treeherdermozilla-central@a30925f7f28e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersme
bugs955808
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 955808 - Follow-up: Fix bustage. r=me
mobile/android/base/background/fxa/FxAccount20CreateDelegate.java
mobile/android/base/background/fxa/FxAccount20LoginDelegate.java
mobile/android/base/background/fxa/FxAccountClient20.java
mobile/android/base/sync/BadRequiredFieldJSONException.java
mobile/android/base/sync/crypto/PBKDF2.java
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/fxa/FxAccount20CreateDelegate.java
@@ -0,0 +1,40 @@
+/* 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 org.json.simple.JSONObject;
+
+public class FxAccount20CreateDelegate extends FxAccount20LoginDelegate {
+  protected final boolean preVerified;
+
+  /**
+   * Make a new "create account" delegate.
+   *
+   * @param emailUTF8
+   *          email as UTF-8 bytes.
+   * @param passwordUTF8
+   *          password as UTF-8 bytes.
+   * @param preVerified
+   *          true if account should be marked already verified; only effective
+   *          for non-production auth servers.
+   * @throws UnsupportedEncodingException
+   * @throws GeneralSecurityException
+   */
+  public FxAccount20CreateDelegate(byte[] emailUTF8, byte[] passwordUTF8, boolean preVerified) throws UnsupportedEncodingException, GeneralSecurityException {
+    super(emailUTF8, passwordUTF8);
+    this.preVerified = preVerified;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public JSONObject getCreateBody() throws FxAccountClientException {
+    final JSONObject body = super.getCreateBody();
+    body.put("preVerified", preVerified);
+    return body;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/fxa/FxAccount20LoginDelegate.java
@@ -0,0 +1,41 @@
+/* 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 org.json.simple.JSONObject;
+import org.mozilla.gecko.background.fxa.FxAccountClient.CreateDelegate;
+import org.mozilla.gecko.sync.Utils;
+
+/**
+ * An abstraction around providing an email and authorization token to the auth
+ * server.
+ */
+public class FxAccount20LoginDelegate implements CreateDelegate {
+  protected final byte[] emailUTF8;
+  protected final byte[] passwordUTF8;
+  protected final byte[] authPW;
+
+  public FxAccount20LoginDelegate(byte[] emailUTF8, byte[] passwordUTF8) throws UnsupportedEncodingException, GeneralSecurityException {
+    this.emailUTF8 = emailUTF8;
+    this.passwordUTF8 = passwordUTF8;
+    this.authPW = FxAccountUtils.generateAuthPW(FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public JSONObject getCreateBody() throws FxAccountClientException {
+    final JSONObject body = new JSONObject();
+    try {
+      body.put("email", new String(emailUTF8, "UTF-8"));
+      body.put("authPW", Utils.byte2Hex(authPW));
+      return body;
+    } catch (UnsupportedEncodingException e) {
+      throw new FxAccountClientException(e);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/fxa/FxAccountClient20.java
@@ -0,0 +1,118 @@
+/* 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.net.URI;
+import java.util.concurrent.Executor;
+
+import org.json.simple.JSONObject;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.net.BaseResource;
+
+import ch.boye.httpclientandroidlib.HttpResponse;
+
+public class FxAccountClient20 extends FxAccountClient {
+  protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN };
+  protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN, JSON_KEY_KEYFETCHTOKEN, };
+  protected static final String[] LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS = new String[] { JSON_KEY_VERIFIED };
+
+  public FxAccountClient20(String serverURI, Executor executor) {
+    super(serverURI, executor);
+  }
+
+  public void createAccount(final byte[] emailUTF8, final byte[] passwordUTF8, final boolean preVerified,
+      final RequestDelegate<String> delegate) {
+    try {
+      createAccount(new FxAccount20CreateDelegate(emailUTF8, passwordUTF8, preVerified), delegate);
+    } catch (final Exception e) {
+      invokeHandleError(delegate, e);
+      return;
+    }
+  }
+
+  /**
+   * Thin container for login response.
+   */
+  public static class LoginResponse {
+    public final String serverURI;
+    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;
+      this.uid = uid;
+      this.verified = verified;
+      this.sessionToken = sessionToken;
+      this.keyFetchToken = keyFetchToken;
+    }
+  }
+
+  public void login(final byte[] emailUTF8, final byte[] passwordUTF8,
+      final RequestDelegate<LoginResponse> delegate) {
+    login(emailUTF8, passwordUTF8, false, delegate);
+  }
+
+  public void loginAndGetKeys(final byte[] emailUTF8, final byte[] passwordUTF8,
+      final RequestDelegate<LoginResponse> delegate) {
+    login(emailUTF8, passwordUTF8, true, delegate);
+  }
+
+  // Public for testing only; prefer login and loginAndGetKeys (without boolean parameter).
+  public void login(final byte[] emailUTF8, final byte[] passwordUTF8, final boolean getKeys,
+      final RequestDelegate<LoginResponse> delegate) {
+    BaseResource resource;
+    JSONObject body;
+    final String path = getKeys ? "account/login?keys=true" : "account/login";
+    try {
+      resource = new BaseResource(new URI(serverURI + path));
+      body = new FxAccount20LoginDelegate(emailUTF8, passwordUTF8).getCreateBody();
+    } catch (Exception e) {
+      invokeHandleError(delegate, e);
+      return;
+    }
+
+    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
+      @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;
+
+          body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
+          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));
+          byte[] keyFetchToken = null;
+          if (getKeys) {
+            keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
+          }
+          loginResponse = new LoginResponse(serverURI, uid, verified, sessionToken, keyFetchToken);
+
+          delegate.handleSuccess(loginResponse);
+          return;
+        } catch (Exception e) {
+          delegate.handleError(e);
+          return;
+        }
+      }
+    };
+
+    post(resource, body, delegate);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/BadRequiredFieldJSONException.java
@@ -0,0 +1,5 @@
+/* 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.sync;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/crypto/PBKDF2.java
@@ -0,0 +1,87 @@
+/* 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.sync.crypto;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PBKDF2 {
+  public static byte[] pbkdf2SHA1(byte[] password, byte[] salt, int c, int dkLen)
+      throws GeneralSecurityException {
+    // Won't work on API level 8, but this is trivial.
+    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+    PBEKeySpec keySpec;
+    try {
+      keySpec = new PBEKeySpec(new String(password, "UTF-8").toCharArray(), salt, c, dkLen * 8);
+    } catch (UnsupportedEncodingException e) {
+      throw new GeneralSecurityException(e);
+    }
+    SecretKey key = factory.generateSecret(keySpec);
+    return key.getEncoded();
+  }
+
+  public static byte[] pbkdf2SHA256(byte[] password, byte[] salt, int c, int dkLen)
+      throws GeneralSecurityException {
+    final String algorithm = "HmacSHA256";
+    SecretKeySpec keyspec = new SecretKeySpec(password, algorithm);
+    Mac prf = Mac.getInstance(algorithm);
+    prf.init(keyspec);
+
+    int hLen = prf.getMacLength();
+    int l = Math.max(dkLen, hLen);
+    int r = dkLen - (l - 1) * hLen;
+    byte T[] = new byte[l * hLen];
+    int ti_offset = 0;
+    for (int i = 1; i <= l; i++) {
+      F(T, ti_offset, prf, salt, c, i);
+      ti_offset += hLen;
+    }
+
+    if (r < hLen) {
+      // Incomplete last block.
+      byte DK[] = new byte[dkLen];
+      System.arraycopy(T, 0, DK, 0, dkLen);
+      return DK;
+    }
+
+    return T;
+  }
+
+  private static void F(byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex) {
+    final int hLen = prf.getMacLength();
+    byte U_r[] = new byte[hLen];
+
+    // U0 = S || INT (i);
+    byte U_i[] = new byte[S.length + 4];
+    System.arraycopy(S, 0, U_i, 0, S.length);
+    INT(U_i, S.length, blockIndex);
+
+    for (int i = 0; i < c; i++) {
+      U_i = prf.doFinal(U_i);
+      xor(U_r, U_i);
+    }
+
+    System.arraycopy(U_r, 0, dest, offset, hLen);
+  }
+
+  private static void xor(byte[] dest, byte[] src) {
+    for (int i = 0; i < dest.length; i++) {
+      dest[i] ^= src[i];
+    }
+  }
+
+  private static void INT(byte[] dest, int offset, int i) {
+    dest[offset + 0] = (byte) (i / (256 * 256 * 256));
+    dest[offset + 1] = (byte) (i / (256 * 256));
+    dest[offset + 2] = (byte) (i / (256));
+    dest[offset + 3] = (byte) (i);
+  }
+}