Bug 955808 - Follow-up: Fix bustage. r=me

This commit is contained in:
Nick Alexander 2014-01-07 20:29:18 -08:00
parent a1851de7a1
commit 5400cefddd
5 changed files with 291 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}