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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/background/fxa/FxAccount10AuthDelegate.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,218 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.background.fxa;
     1.9 +
    1.10 +import java.io.UnsupportedEncodingException;
    1.11 +import java.math.BigInteger;
    1.12 +import java.security.NoSuchAlgorithmException;
    1.13 +
    1.14 +import org.json.simple.JSONObject;
    1.15 +import org.mozilla.gecko.sync.ExtendedJSONObject;
    1.16 +import org.mozilla.gecko.sync.Utils;
    1.17 +import org.mozilla.gecko.sync.net.SRPConstants;
    1.18 +
    1.19 +public class FxAccount10AuthDelegate implements FxAccountClient10.AuthDelegate {
    1.20 +  // Fixed by protocol.
    1.21 +  protected final BigInteger N;
    1.22 +  protected final BigInteger g;
    1.23 +  protected final int modNLengthBytes;
    1.24 +
    1.25 +  // Configured at construction time.
    1.26 +  protected final String email;
    1.27 +  protected final byte[] stretchedPWBytes;
    1.28 +
    1.29 +  // Encapsulate state.
    1.30 +  protected static class AuthState {
    1.31 +    protected String srpToken;
    1.32 +    protected String mainSalt;
    1.33 +    protected String srpSalt;
    1.34 +
    1.35 +    protected BigInteger x;
    1.36 +    protected BigInteger A;
    1.37 +    protected byte[] Kbytes;
    1.38 +    protected byte[] Mbytes;
    1.39 +  }
    1.40 +
    1.41 +  // State should be written exactly once.
    1.42 +  protected AuthState internalAuthState = null;
    1.43 +
    1.44 +  public FxAccount10AuthDelegate(String email, byte[] stretchedPWBytes) {
    1.45 +    this.email = email;
    1.46 +    this.stretchedPWBytes = stretchedPWBytes;
    1.47 +    this.N = SRPConstants._2048.N;
    1.48 +    this.g = SRPConstants._2048.g;
    1.49 +    this.modNLengthBytes = SRPConstants._2048.byteLength;
    1.50 +  }
    1.51 +
    1.52 +  protected BigInteger generateSecretValue() {
    1.53 +    return Utils.generateBigIntegerLessThan(N);
    1.54 +  }
    1.55 +
    1.56 +  public static class FxAccountClientMalformedAuthException extends FxAccountClientException {
    1.57 +    private static final long serialVersionUID = 3585262174699395505L;
    1.58 +
    1.59 +    public FxAccountClientMalformedAuthException(String detailMessage) {
    1.60 +      super(detailMessage);
    1.61 +    }
    1.62 +  }
    1.63 +
    1.64 +  @SuppressWarnings("unchecked")
    1.65 +  @Override
    1.66 +  public JSONObject getAuthStartBody() throws FxAccountClientException {
    1.67 +    try {
    1.68 +      final JSONObject body = new JSONObject();
    1.69 +      body.put("email", FxAccountUtils.bytes(email));
    1.70 +      return body;
    1.71 +    } catch (UnsupportedEncodingException e) {
    1.72 +      throw new FxAccountClientException(e);
    1.73 +    }
    1.74 +  }
    1.75 +
    1.76 +  @Override
    1.77 +  public void onAuthStartResponse(final ExtendedJSONObject body) throws FxAccountClientException {
    1.78 +    if (this.internalAuthState != null) {
    1.79 +      throw new FxAccountClientException("auth must not be written before calling onAuthStartResponse");
    1.80 +    }
    1.81 +
    1.82 +    String srpToken = null;
    1.83 +    String srpSalt = null;
    1.84 +    String srpB = null;
    1.85 +    String mainSalt = null;
    1.86 +
    1.87 +    try {
    1.88 +      srpToken = body.getString("srpToken");
    1.89 +      if (srpToken == null) {
    1.90 +        throw new FxAccountClientMalformedAuthException("srpToken must be a non-null object");
    1.91 +      }
    1.92 +      ExtendedJSONObject srp = body.getObject("srp");
    1.93 +      if (srp == null) {
    1.94 +        throw new FxAccountClientMalformedAuthException("srp must be a non-null object");
    1.95 +      }
    1.96 +      srpSalt = srp.getString("salt");
    1.97 +      if (srpSalt == null) {
    1.98 +        throw new FxAccountClientMalformedAuthException("srp.salt must not be null");
    1.99 +      }
   1.100 +      srpB = srp.getString("B");
   1.101 +      if (srpB == null) {
   1.102 +        throw new FxAccountClientMalformedAuthException("srp.B must not be null");
   1.103 +      }
   1.104 +      ExtendedJSONObject passwordStretching = body.getObject("passwordStretching");
   1.105 +      if (passwordStretching == null) {
   1.106 +        throw new FxAccountClientMalformedAuthException("passwordStretching must be a non-null object");
   1.107 +      }
   1.108 +      mainSalt = passwordStretching.getString("salt");
   1.109 +      if (mainSalt == null) {
   1.110 +        throw new FxAccountClientMalformedAuthException("srp.passwordStretching.salt must not be null");
   1.111 +      }
   1.112 +      throwIfParametersAreBad(passwordStretching);
   1.113 +
   1.114 +      this.internalAuthState = authStateFromParameters(srpToken, mainSalt, srpSalt, srpB, generateSecretValue());
   1.115 +    } catch (FxAccountClientException e) {
   1.116 +      throw e;
   1.117 +    } catch (Exception e) {
   1.118 +      throw new FxAccountClientException(e);
   1.119 +    }
   1.120 +  }
   1.121 +
   1.122 +  /**
   1.123 +   * Expect object like:
   1.124 +   * "passwordStretching": {
   1.125 +   *   "type": "PBKDF2/scrypt/PBKDF2/v1",
   1.126 +   *   "PBKDF2_rounds_1": 20000,
   1.127 +   *   "scrypt_N": 65536,
   1.128 +   *   "scrypt_r": 8,
   1.129 +   *   "scrypt_p": 1,
   1.130 +   *   "PBKDF2_rounds_2": 20000,
   1.131 +   *   "salt": "996bc6b1aa63cd69856a2ec81cbf19d5c8a604713362df9ee15c2bf07128efab"
   1.132 +   * }
   1.133 +   * @param params to verify.
   1.134 +   * @throws FxAccountClientMalformedAuthException
   1.135 +   */
   1.136 +  protected void throwIfParametersAreBad(ExtendedJSONObject params) throws FxAccountClientMalformedAuthException {
   1.137 +    if (params == null ||
   1.138 +        params.size() != 7 ||
   1.139 +        params.getString("salt") == null ||
   1.140 +        !("PBKDF2/scrypt/PBKDF2/v1".equals(params.getString("type"))) ||
   1.141 +        20000 != params.getLong("PBKDF2_rounds_1") ||
   1.142 +        65536 != params.getLong("scrypt_N") ||
   1.143 +        8 != params.getLong("scrypt_r") ||
   1.144 +        1 != params.getLong("scrypt_p") ||
   1.145 +        20000 != params.getLong("PBKDF2_rounds_2")) {
   1.146 +      throw new FxAccountClientMalformedAuthException("malformed passwordStretching parameters: '" + params.toJSONString() + "'.");
   1.147 +    }
   1.148 +  }
   1.149 +
   1.150 +  /**
   1.151 +   * All state is written in this method.
   1.152 +   */
   1.153 +  protected AuthState authStateFromParameters(String srpToken, String mainSalt, String srpSalt, String srpB, BigInteger a) throws NoSuchAlgorithmException, UnsupportedEncodingException {
   1.154 +    AuthState authState = new AuthState();
   1.155 +    authState.srpToken = srpToken;
   1.156 +    authState.mainSalt = mainSalt;
   1.157 +    authState.srpSalt = srpSalt;
   1.158 +
   1.159 +    authState.x = FxAccountUtils.srpVerifierLowercaseX(email.getBytes("UTF-8"), this.stretchedPWBytes, Utils.hex2Byte(srpSalt, FxAccountUtils.SALT_LENGTH_BYTES));
   1.160 +
   1.161 +    authState.A = g.modPow(a, N);
   1.162 +    String srpA = FxAccountUtils.hexModN(authState.A, N);
   1.163 +    BigInteger B = new BigInteger(srpB, 16);
   1.164 +
   1.165 +    byte[] srpABytes = Utils.hex2Byte(srpA, modNLengthBytes);
   1.166 +    byte[] srpBBytes = Utils.hex2Byte(srpB, modNLengthBytes);
   1.167 +
   1.168 +    // u = H(pad(A) | pad(B))
   1.169 +    byte[] uBytes = Utils.sha256(Utils.concatAll(
   1.170 +        srpABytes,
   1.171 +        srpBBytes));
   1.172 +    BigInteger u = new BigInteger(Utils.byte2Hex(uBytes, FxAccountUtils.HASH_LENGTH_HEX), 16);
   1.173 +
   1.174 +    // S = (B - k*g^x)^(a  u*x) % N
   1.175 +    // k = H(pad(N) | pad(g))
   1.176 +    int byteLength = (N.bitLength() + 7) / 8;
   1.177 +    byte[] kBytes = Utils.sha256(Utils.concatAll(
   1.178 +        Utils.hex2Byte(N.toString(16), byteLength),
   1.179 +        Utils.hex2Byte(g.toString(16), byteLength)));
   1.180 +    BigInteger k = new BigInteger(Utils.byte2Hex(kBytes, FxAccountUtils.HASH_LENGTH_HEX), 16);
   1.181 +
   1.182 +    BigInteger base = B.subtract(k.multiply(g.modPow(authState.x, N)).mod(N)).mod(N);
   1.183 +    BigInteger pow = a.add(u.multiply(authState.x));
   1.184 +    BigInteger S = base.modPow(pow, N);
   1.185 +    String srpS = FxAccountUtils.hexModN(S, N);
   1.186 +
   1.187 +    byte[] sBytes = Utils.hex2Byte(srpS, modNLengthBytes);
   1.188 +
   1.189 +    // M = H(pad(A) | pad(B) | pad(S))
   1.190 +    authState.Mbytes = Utils.sha256(Utils.concatAll(
   1.191 +        srpABytes,
   1.192 +        srpBBytes,
   1.193 +        sBytes));
   1.194 +
   1.195 +    // K = H(pad(S))
   1.196 +    authState.Kbytes = Utils.sha256(sBytes);
   1.197 +
   1.198 +    return authState;
   1.199 +  }
   1.200 +
   1.201 +  @SuppressWarnings("unchecked")
   1.202 +  @Override
   1.203 +  public JSONObject getAuthFinishBody() throws FxAccountClientException {
   1.204 +    if (internalAuthState == null) {
   1.205 +      throw new FxAccountClientException("auth must be successfully written before calling getAuthFinishBody.");
   1.206 +    }
   1.207 +    JSONObject body = new JSONObject();
   1.208 +    body.put("srpToken", internalAuthState.srpToken);
   1.209 +    body.put("A", FxAccountUtils.hexModN(internalAuthState.A, N));
   1.210 +    body.put("M", Utils.byte2Hex(internalAuthState.Mbytes, FxAccountUtils.HASH_LENGTH_HEX));
   1.211 +    return body;
   1.212 +  }
   1.213 +
   1.214 +  @Override
   1.215 +  public byte[] getSharedBytes() throws FxAccountClientException {
   1.216 +    if (internalAuthState == null) {
   1.217 +      throw new FxAccountClientException("auth must be successfully finished before calling getSharedBytes.");
   1.218 +    }
   1.219 +    return internalAuthState.Kbytes;
   1.220 +  }
   1.221 +}

mercurial