mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
86ca102fd8
========
5b97feec90
Author: Ben Curtis <androiddev@nosolutions.com>
Bug 1046020 - Include URI port in token server audiences.
--HG--
extra : amend_source : 706eaa493e969ce27470aafd482f6b61f5f88db8
191 lines
7.6 KiB
Java
191 lines
7.6 KiB
Java
/* 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.math.BigInteger;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.security.GeneralSecurityException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import org.mozilla.gecko.background.common.log.Logger;
|
|
import org.mozilla.gecko.background.nativecode.NativeCrypto;
|
|
import org.mozilla.gecko.sync.Utils;
|
|
import org.mozilla.gecko.sync.crypto.HKDF;
|
|
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
|
import org.mozilla.gecko.sync.crypto.PBKDF2;
|
|
|
|
public class FxAccountUtils {
|
|
private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
|
|
|
|
public static final int SALT_LENGTH_BYTES = 32;
|
|
public static final int SALT_LENGTH_HEX = 2 * SALT_LENGTH_BYTES;
|
|
|
|
public static final int HASH_LENGTH_BYTES = 16;
|
|
public static final int HASH_LENGTH_HEX = 2 * HASH_LENGTH_BYTES;
|
|
|
|
public static final int CRYPTO_KEY_LENGTH_BYTES = 32;
|
|
public static final int CRYPTO_KEY_LENGTH_HEX = 2 * CRYPTO_KEY_LENGTH_BYTES;
|
|
|
|
public static final String KW_VERSION_STRING = "identity.mozilla.com/picl/v1/";
|
|
|
|
public static final int NUMBER_OF_QUICK_STRETCH_ROUNDS = 1000;
|
|
|
|
public static String bytes(String string) throws UnsupportedEncodingException {
|
|
return Utils.byte2Hex(string.getBytes("UTF-8"));
|
|
}
|
|
|
|
public static byte[] KW(String name) throws UnsupportedEncodingException {
|
|
return Utils.concatAll(
|
|
KW_VERSION_STRING.getBytes("UTF-8"),
|
|
name.getBytes("UTF-8"));
|
|
}
|
|
|
|
public static byte[] KWE(String name, byte[] emailUTF8) throws UnsupportedEncodingException {
|
|
return Utils.concatAll(
|
|
KW_VERSION_STRING.getBytes("UTF-8"),
|
|
name.getBytes("UTF-8"),
|
|
":".getBytes("UTF-8"),
|
|
emailUTF8);
|
|
}
|
|
|
|
/**
|
|
* Calculate the SRP verifier <tt>x</tt> value.
|
|
*/
|
|
public static BigInteger srpVerifierLowercaseX(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes)
|
|
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
|
byte[] inner = Utils.sha256(Utils.concatAll(emailUTF8, ":".getBytes("UTF-8"), srpPWBytes));
|
|
byte[] outer = Utils.sha256(Utils.concatAll(srpSaltBytes, inner));
|
|
return new BigInteger(1, outer);
|
|
}
|
|
|
|
/**
|
|
* Calculate the SRP verifier <tt>v</tt> value.
|
|
*/
|
|
public static BigInteger srpVerifierLowercaseV(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes, BigInteger g, BigInteger N)
|
|
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
|
BigInteger x = srpVerifierLowercaseX(emailUTF8, srpPWBytes, srpSaltBytes);
|
|
BigInteger v = g.modPow(x, N);
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* Format x modulo N in hexadecimal, using as many characters as N takes (in hexadecimal).
|
|
* @param x to format.
|
|
* @param N modulus.
|
|
* @return x modulo N in hexadecimal.
|
|
*/
|
|
public static String hexModN(BigInteger x, BigInteger N) {
|
|
int byteLength = (N.bitLength() + 7) / 8;
|
|
int hexLength = 2 * byteLength;
|
|
return Utils.byte2Hex(Utils.hex2Byte((x.mod(N)).toString(16), byteLength), hexLength);
|
|
}
|
|
|
|
/**
|
|
* The first engineering milestone of PICL (Profile-in-the-Cloud) was
|
|
* comprised of Sync 1.1 fronted by a Firefox Account. The sync key was
|
|
* generated from the Firefox Account password-derived kB value using this
|
|
* method.
|
|
*/
|
|
public static KeyBundle generateSyncKeyBundle(final byte[] kB) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
|
|
byte[] encryptionKey = new byte[32];
|
|
byte[] hmacKey = new byte[32];
|
|
byte[] derived = HKDF.derive(kB, new byte[0], FxAccountUtils.KW("oldsync"), 2*32);
|
|
System.arraycopy(derived, 0*32, encryptionKey, 0, 1*32);
|
|
System.arraycopy(derived, 1*32, hmacKey, 0, 1*32);
|
|
return new KeyBundle(encryptionKey, hmacKey);
|
|
}
|
|
|
|
/**
|
|
* Firefox Accounts are password authenticated, but clients should not store
|
|
* the plain-text password for any amount of time. Equivalent, but slightly
|
|
* more secure, is the quickly client-side stretched password.
|
|
* <p>
|
|
* We separate this since multiple login-time operations want it, and the
|
|
* PBKDF2 operation is computationally expensive.
|
|
*/
|
|
public static byte[] generateQuickStretchedPW(byte[] emailUTF8, byte[] passwordUTF8) throws GeneralSecurityException, UnsupportedEncodingException {
|
|
byte[] S = FxAccountUtils.KWE("quickStretch", emailUTF8);
|
|
try {
|
|
return NativeCrypto.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
|
|
} catch (final LinkageError e) {
|
|
// This will throw UnsatisifiedLinkError (missing mozglue) the first time it is called, and
|
|
// ClassNotDefFoundError, for the uninitialized NativeCrypto class, each subsequent time this
|
|
// is called; LinkageError is their common ancestor.
|
|
Logger.warn(LOG_TAG, "Got throwable stretching password using native pbkdf2SHA256 " +
|
|
"implementation; ignoring and using Java implementation.", e);
|
|
return PBKDF2.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The password-derived credential used to authenticate to the Firefox Account
|
|
* auth server.
|
|
*/
|
|
public static byte[] generateAuthPW(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
|
|
return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("authPW"), 32);
|
|
}
|
|
|
|
/**
|
|
* The password-derived credential used to unwrap keys managed by the Firefox
|
|
* Account auth server.
|
|
*/
|
|
public static byte[] generateUnwrapBKey(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
|
|
return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("unwrapBkey"), 32);
|
|
}
|
|
|
|
public static byte[] unwrapkB(byte[] unwrapkB, byte[] wrapkB) {
|
|
if (unwrapkB == null) {
|
|
throw new IllegalArgumentException("unwrapkB must not be null");
|
|
}
|
|
if (wrapkB == null) {
|
|
throw new IllegalArgumentException("wrapkB must not be null");
|
|
}
|
|
if (unwrapkB.length != CRYPTO_KEY_LENGTH_BYTES || wrapkB.length != CRYPTO_KEY_LENGTH_BYTES) {
|
|
throw new IllegalArgumentException("unwrapkB and wrapkB must be " + CRYPTO_KEY_LENGTH_BYTES + " bytes long");
|
|
}
|
|
byte[] kB = new byte[CRYPTO_KEY_LENGTH_BYTES];
|
|
for (int i = 0; i < wrapkB.length; i++) {
|
|
kB[i] = (byte) (wrapkB[i] ^ unwrapkB[i]);
|
|
}
|
|
return kB;
|
|
}
|
|
|
|
/**
|
|
* The token server accepts an X-Client-State header, which is the
|
|
* lowercase-hex-encoded first 16 bytes of the SHA-256 hash of the
|
|
* bytes of kB.
|
|
* @param kB a byte array, expected to be 32 bytes long.
|
|
* @return a 32-character string.
|
|
* @throws NoSuchAlgorithmException
|
|
*/
|
|
public static String computeClientState(byte[] kB) throws NoSuchAlgorithmException {
|
|
if (kB == null ||
|
|
kB.length != 32) {
|
|
throw new IllegalArgumentException("Unexpected kB.");
|
|
}
|
|
byte[] sha256 = Utils.sha256(kB);
|
|
byte[] truncated = new byte[16];
|
|
System.arraycopy(sha256, 0, truncated, 0, 16);
|
|
return Utils.byte2Hex(truncated); // This is automatically lowercase.
|
|
}
|
|
|
|
/**
|
|
* Given an endpoint, calculate the corresponding BrowserID audience.
|
|
* <p>
|
|
* This is the domain, in web parlance.
|
|
*
|
|
* @param serverURI endpoint.
|
|
* @return BrowserID audience.
|
|
* @throws URISyntaxException
|
|
*/
|
|
public static String getAudienceForURL(String serverURI) throws URISyntaxException {
|
|
URI uri = new URI(serverURI);
|
|
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), null, null, null).toString();
|
|
}
|
|
}
|