mobile/android/base/background/fxa/FxAccount10AuthDelegate.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.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.background.fxa;
michael@0 6
michael@0 7 import java.io.UnsupportedEncodingException;
michael@0 8 import java.math.BigInteger;
michael@0 9 import java.security.NoSuchAlgorithmException;
michael@0 10
michael@0 11 import org.json.simple.JSONObject;
michael@0 12 import org.mozilla.gecko.sync.ExtendedJSONObject;
michael@0 13 import org.mozilla.gecko.sync.Utils;
michael@0 14 import org.mozilla.gecko.sync.net.SRPConstants;
michael@0 15
michael@0 16 public class FxAccount10AuthDelegate implements FxAccountClient10.AuthDelegate {
michael@0 17 // Fixed by protocol.
michael@0 18 protected final BigInteger N;
michael@0 19 protected final BigInteger g;
michael@0 20 protected final int modNLengthBytes;
michael@0 21
michael@0 22 // Configured at construction time.
michael@0 23 protected final String email;
michael@0 24 protected final byte[] stretchedPWBytes;
michael@0 25
michael@0 26 // Encapsulate state.
michael@0 27 protected static class AuthState {
michael@0 28 protected String srpToken;
michael@0 29 protected String mainSalt;
michael@0 30 protected String srpSalt;
michael@0 31
michael@0 32 protected BigInteger x;
michael@0 33 protected BigInteger A;
michael@0 34 protected byte[] Kbytes;
michael@0 35 protected byte[] Mbytes;
michael@0 36 }
michael@0 37
michael@0 38 // State should be written exactly once.
michael@0 39 protected AuthState internalAuthState = null;
michael@0 40
michael@0 41 public FxAccount10AuthDelegate(String email, byte[] stretchedPWBytes) {
michael@0 42 this.email = email;
michael@0 43 this.stretchedPWBytes = stretchedPWBytes;
michael@0 44 this.N = SRPConstants._2048.N;
michael@0 45 this.g = SRPConstants._2048.g;
michael@0 46 this.modNLengthBytes = SRPConstants._2048.byteLength;
michael@0 47 }
michael@0 48
michael@0 49 protected BigInteger generateSecretValue() {
michael@0 50 return Utils.generateBigIntegerLessThan(N);
michael@0 51 }
michael@0 52
michael@0 53 public static class FxAccountClientMalformedAuthException extends FxAccountClientException {
michael@0 54 private static final long serialVersionUID = 3585262174699395505L;
michael@0 55
michael@0 56 public FxAccountClientMalformedAuthException(String detailMessage) {
michael@0 57 super(detailMessage);
michael@0 58 }
michael@0 59 }
michael@0 60
michael@0 61 @SuppressWarnings("unchecked")
michael@0 62 @Override
michael@0 63 public JSONObject getAuthStartBody() throws FxAccountClientException {
michael@0 64 try {
michael@0 65 final JSONObject body = new JSONObject();
michael@0 66 body.put("email", FxAccountUtils.bytes(email));
michael@0 67 return body;
michael@0 68 } catch (UnsupportedEncodingException e) {
michael@0 69 throw new FxAccountClientException(e);
michael@0 70 }
michael@0 71 }
michael@0 72
michael@0 73 @Override
michael@0 74 public void onAuthStartResponse(final ExtendedJSONObject body) throws FxAccountClientException {
michael@0 75 if (this.internalAuthState != null) {
michael@0 76 throw new FxAccountClientException("auth must not be written before calling onAuthStartResponse");
michael@0 77 }
michael@0 78
michael@0 79 String srpToken = null;
michael@0 80 String srpSalt = null;
michael@0 81 String srpB = null;
michael@0 82 String mainSalt = null;
michael@0 83
michael@0 84 try {
michael@0 85 srpToken = body.getString("srpToken");
michael@0 86 if (srpToken == null) {
michael@0 87 throw new FxAccountClientMalformedAuthException("srpToken must be a non-null object");
michael@0 88 }
michael@0 89 ExtendedJSONObject srp = body.getObject("srp");
michael@0 90 if (srp == null) {
michael@0 91 throw new FxAccountClientMalformedAuthException("srp must be a non-null object");
michael@0 92 }
michael@0 93 srpSalt = srp.getString("salt");
michael@0 94 if (srpSalt == null) {
michael@0 95 throw new FxAccountClientMalformedAuthException("srp.salt must not be null");
michael@0 96 }
michael@0 97 srpB = srp.getString("B");
michael@0 98 if (srpB == null) {
michael@0 99 throw new FxAccountClientMalformedAuthException("srp.B must not be null");
michael@0 100 }
michael@0 101 ExtendedJSONObject passwordStretching = body.getObject("passwordStretching");
michael@0 102 if (passwordStretching == null) {
michael@0 103 throw new FxAccountClientMalformedAuthException("passwordStretching must be a non-null object");
michael@0 104 }
michael@0 105 mainSalt = passwordStretching.getString("salt");
michael@0 106 if (mainSalt == null) {
michael@0 107 throw new FxAccountClientMalformedAuthException("srp.passwordStretching.salt must not be null");
michael@0 108 }
michael@0 109 throwIfParametersAreBad(passwordStretching);
michael@0 110
michael@0 111 this.internalAuthState = authStateFromParameters(srpToken, mainSalt, srpSalt, srpB, generateSecretValue());
michael@0 112 } catch (FxAccountClientException e) {
michael@0 113 throw e;
michael@0 114 } catch (Exception e) {
michael@0 115 throw new FxAccountClientException(e);
michael@0 116 }
michael@0 117 }
michael@0 118
michael@0 119 /**
michael@0 120 * Expect object like:
michael@0 121 * "passwordStretching": {
michael@0 122 * "type": "PBKDF2/scrypt/PBKDF2/v1",
michael@0 123 * "PBKDF2_rounds_1": 20000,
michael@0 124 * "scrypt_N": 65536,
michael@0 125 * "scrypt_r": 8,
michael@0 126 * "scrypt_p": 1,
michael@0 127 * "PBKDF2_rounds_2": 20000,
michael@0 128 * "salt": "996bc6b1aa63cd69856a2ec81cbf19d5c8a604713362df9ee15c2bf07128efab"
michael@0 129 * }
michael@0 130 * @param params to verify.
michael@0 131 * @throws FxAccountClientMalformedAuthException
michael@0 132 */
michael@0 133 protected void throwIfParametersAreBad(ExtendedJSONObject params) throws FxAccountClientMalformedAuthException {
michael@0 134 if (params == null ||
michael@0 135 params.size() != 7 ||
michael@0 136 params.getString("salt") == null ||
michael@0 137 !("PBKDF2/scrypt/PBKDF2/v1".equals(params.getString("type"))) ||
michael@0 138 20000 != params.getLong("PBKDF2_rounds_1") ||
michael@0 139 65536 != params.getLong("scrypt_N") ||
michael@0 140 8 != params.getLong("scrypt_r") ||
michael@0 141 1 != params.getLong("scrypt_p") ||
michael@0 142 20000 != params.getLong("PBKDF2_rounds_2")) {
michael@0 143 throw new FxAccountClientMalformedAuthException("malformed passwordStretching parameters: '" + params.toJSONString() + "'.");
michael@0 144 }
michael@0 145 }
michael@0 146
michael@0 147 /**
michael@0 148 * All state is written in this method.
michael@0 149 */
michael@0 150 protected AuthState authStateFromParameters(String srpToken, String mainSalt, String srpSalt, String srpB, BigInteger a) throws NoSuchAlgorithmException, UnsupportedEncodingException {
michael@0 151 AuthState authState = new AuthState();
michael@0 152 authState.srpToken = srpToken;
michael@0 153 authState.mainSalt = mainSalt;
michael@0 154 authState.srpSalt = srpSalt;
michael@0 155
michael@0 156 authState.x = FxAccountUtils.srpVerifierLowercaseX(email.getBytes("UTF-8"), this.stretchedPWBytes, Utils.hex2Byte(srpSalt, FxAccountUtils.SALT_LENGTH_BYTES));
michael@0 157
michael@0 158 authState.A = g.modPow(a, N);
michael@0 159 String srpA = FxAccountUtils.hexModN(authState.A, N);
michael@0 160 BigInteger B = new BigInteger(srpB, 16);
michael@0 161
michael@0 162 byte[] srpABytes = Utils.hex2Byte(srpA, modNLengthBytes);
michael@0 163 byte[] srpBBytes = Utils.hex2Byte(srpB, modNLengthBytes);
michael@0 164
michael@0 165 // u = H(pad(A) | pad(B))
michael@0 166 byte[] uBytes = Utils.sha256(Utils.concatAll(
michael@0 167 srpABytes,
michael@0 168 srpBBytes));
michael@0 169 BigInteger u = new BigInteger(Utils.byte2Hex(uBytes, FxAccountUtils.HASH_LENGTH_HEX), 16);
michael@0 170
michael@0 171 // S = (B - k*g^x)^(a u*x) % N
michael@0 172 // k = H(pad(N) | pad(g))
michael@0 173 int byteLength = (N.bitLength() + 7) / 8;
michael@0 174 byte[] kBytes = Utils.sha256(Utils.concatAll(
michael@0 175 Utils.hex2Byte(N.toString(16), byteLength),
michael@0 176 Utils.hex2Byte(g.toString(16), byteLength)));
michael@0 177 BigInteger k = new BigInteger(Utils.byte2Hex(kBytes, FxAccountUtils.HASH_LENGTH_HEX), 16);
michael@0 178
michael@0 179 BigInteger base = B.subtract(k.multiply(g.modPow(authState.x, N)).mod(N)).mod(N);
michael@0 180 BigInteger pow = a.add(u.multiply(authState.x));
michael@0 181 BigInteger S = base.modPow(pow, N);
michael@0 182 String srpS = FxAccountUtils.hexModN(S, N);
michael@0 183
michael@0 184 byte[] sBytes = Utils.hex2Byte(srpS, modNLengthBytes);
michael@0 185
michael@0 186 // M = H(pad(A) | pad(B) | pad(S))
michael@0 187 authState.Mbytes = Utils.sha256(Utils.concatAll(
michael@0 188 srpABytes,
michael@0 189 srpBBytes,
michael@0 190 sBytes));
michael@0 191
michael@0 192 // K = H(pad(S))
michael@0 193 authState.Kbytes = Utils.sha256(sBytes);
michael@0 194
michael@0 195 return authState;
michael@0 196 }
michael@0 197
michael@0 198 @SuppressWarnings("unchecked")
michael@0 199 @Override
michael@0 200 public JSONObject getAuthFinishBody() throws FxAccountClientException {
michael@0 201 if (internalAuthState == null) {
michael@0 202 throw new FxAccountClientException("auth must be successfully written before calling getAuthFinishBody.");
michael@0 203 }
michael@0 204 JSONObject body = new JSONObject();
michael@0 205 body.put("srpToken", internalAuthState.srpToken);
michael@0 206 body.put("A", FxAccountUtils.hexModN(internalAuthState.A, N));
michael@0 207 body.put("M", Utils.byte2Hex(internalAuthState.Mbytes, FxAccountUtils.HASH_LENGTH_HEX));
michael@0 208 return body;
michael@0 209 }
michael@0 210
michael@0 211 @Override
michael@0 212 public byte[] getSharedBytes() throws FxAccountClientException {
michael@0 213 if (internalAuthState == null) {
michael@0 214 throw new FxAccountClientException("auth must be successfully finished before calling getSharedBytes.");
michael@0 215 }
michael@0 216 return internalAuthState.Kbytes;
michael@0 217 }
michael@0 218 }

mercurial