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

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.background.fxa;
     7 import java.io.UnsupportedEncodingException;
     8 import java.math.BigInteger;
     9 import java.net.URI;
    10 import java.net.URISyntaxException;
    11 import java.security.GeneralSecurityException;
    12 import java.security.InvalidKeyException;
    13 import java.security.NoSuchAlgorithmException;
    15 import org.mozilla.gecko.background.common.log.Logger;
    16 import org.mozilla.gecko.background.nativecode.NativeCrypto;
    17 import org.mozilla.gecko.sync.Utils;
    18 import org.mozilla.gecko.sync.crypto.HKDF;
    19 import org.mozilla.gecko.sync.crypto.KeyBundle;
    20 import org.mozilla.gecko.sync.crypto.PBKDF2;
    22 public class FxAccountUtils {
    23   private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
    25   public static final int SALT_LENGTH_BYTES = 32;
    26   public static final int SALT_LENGTH_HEX = 2 * SALT_LENGTH_BYTES;
    28   public static final int HASH_LENGTH_BYTES = 16;
    29   public static final int HASH_LENGTH_HEX = 2 * HASH_LENGTH_BYTES;
    31   public static final int CRYPTO_KEY_LENGTH_BYTES = 32;
    32   public static final int CRYPTO_KEY_LENGTH_HEX = 2 * CRYPTO_KEY_LENGTH_BYTES;
    34   public static final String KW_VERSION_STRING = "identity.mozilla.com/picl/v1/";
    36   public static final int NUMBER_OF_QUICK_STRETCH_ROUNDS = 1000;
    38   public static String bytes(String string) throws UnsupportedEncodingException {
    39     return Utils.byte2Hex(string.getBytes("UTF-8"));
    40   }
    42   public static byte[] KW(String name) throws UnsupportedEncodingException {
    43     return Utils.concatAll(
    44         KW_VERSION_STRING.getBytes("UTF-8"),
    45         name.getBytes("UTF-8"));
    46   }
    48   public static byte[] KWE(String name, byte[] emailUTF8) throws UnsupportedEncodingException {
    49     return Utils.concatAll(
    50         KW_VERSION_STRING.getBytes("UTF-8"),
    51         name.getBytes("UTF-8"),
    52         ":".getBytes("UTF-8"),
    53         emailUTF8);
    54   }
    56   /**
    57    * Calculate the SRP verifier <tt>x</tt> value.
    58    */
    59   public static BigInteger srpVerifierLowercaseX(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes)
    60       throws NoSuchAlgorithmException, UnsupportedEncodingException {
    61     byte[] inner = Utils.sha256(Utils.concatAll(emailUTF8, ":".getBytes("UTF-8"), srpPWBytes));
    62     byte[] outer = Utils.sha256(Utils.concatAll(srpSaltBytes, inner));
    63     return new BigInteger(1, outer);
    64   }
    66   /**
    67    * Calculate the SRP verifier <tt>v</tt> value.
    68    */
    69   public static BigInteger srpVerifierLowercaseV(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes, BigInteger g, BigInteger N)
    70       throws NoSuchAlgorithmException, UnsupportedEncodingException {
    71     BigInteger x = srpVerifierLowercaseX(emailUTF8, srpPWBytes, srpSaltBytes);
    72     BigInteger v = g.modPow(x, N);
    73     return v;
    74   }
    76   /**
    77    * Format x modulo N in hexadecimal, using as many characters as N takes (in hexadecimal).
    78    * @param x to format.
    79    * @param N modulus.
    80    * @return x modulo N in hexadecimal.
    81    */
    82   public static String hexModN(BigInteger x, BigInteger N) {
    83     int byteLength = (N.bitLength() + 7) / 8;
    84     int hexLength = 2 * byteLength;
    85     return Utils.byte2Hex(Utils.hex2Byte((x.mod(N)).toString(16), byteLength), hexLength);
    86   }
    88   /**
    89    * The first engineering milestone of PICL (Profile-in-the-Cloud) was
    90    * comprised of Sync 1.1 fronted by a Firefox Account. The sync key was
    91    * generated from the Firefox Account password-derived kB value using this
    92    * method.
    93    */
    94   public static KeyBundle generateSyncKeyBundle(final byte[] kB) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
    95     byte[] encryptionKey = new byte[32];
    96     byte[] hmacKey = new byte[32];
    97     byte[] derived = HKDF.derive(kB, new byte[0], FxAccountUtils.KW("oldsync"), 2*32);
    98     System.arraycopy(derived, 0*32, encryptionKey, 0, 1*32);
    99     System.arraycopy(derived, 1*32, hmacKey, 0, 1*32);
   100     return new KeyBundle(encryptionKey, hmacKey);
   101   }
   103   /**
   104    * Firefox Accounts are password authenticated, but clients should not store
   105    * the plain-text password for any amount of time. Equivalent, but slightly
   106    * more secure, is the quickly client-side stretched password.
   107    * <p>
   108    * We separate this since multiple login-time operations want it, and the
   109    * PBKDF2 operation is computationally expensive.
   110    */
   111   public static byte[] generateQuickStretchedPW(byte[] emailUTF8, byte[] passwordUTF8) throws GeneralSecurityException, UnsupportedEncodingException {
   112     byte[] S = FxAccountUtils.KWE("quickStretch", emailUTF8);
   113     try {
   114       return NativeCrypto.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
   115     } catch (final LinkageError e) {
   116       // This will throw UnsatisifiedLinkError (missing mozglue) the first time it is called, and
   117       // ClassNotDefFoundError, for the uninitialized NativeCrypto class, each subsequent time this
   118       // is called; LinkageError is their common ancestor.
   119       Logger.warn(LOG_TAG, "Got throwable stretching password using native pbkdf2SHA256 " +
   120           "implementation; ignoring and using Java implementation.", e);
   121       return PBKDF2.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
   122     }
   123   }
   125   /**
   126    * The password-derived credential used to authenticate to the Firefox Account
   127    * auth server.
   128    */
   129   public static byte[] generateAuthPW(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
   130     return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("authPW"), 32);
   131   }
   133   /**
   134    * The password-derived credential used to unwrap keys managed by the Firefox
   135    * Account auth server.
   136    */
   137   public static byte[] generateUnwrapBKey(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
   138     return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("unwrapBkey"), 32);
   139   }
   141   public static byte[] unwrapkB(byte[] unwrapkB, byte[] wrapkB) {
   142     if (unwrapkB == null) {
   143       throw new IllegalArgumentException("unwrapkB must not be null");
   144     }
   145     if (wrapkB == null) {
   146       throw new IllegalArgumentException("wrapkB must not be null");
   147     }
   148     if (unwrapkB.length != CRYPTO_KEY_LENGTH_BYTES || wrapkB.length != CRYPTO_KEY_LENGTH_BYTES) {
   149       throw new IllegalArgumentException("unwrapkB and wrapkB must be " + CRYPTO_KEY_LENGTH_BYTES + " bytes long");
   150     }
   151     byte[] kB = new byte[CRYPTO_KEY_LENGTH_BYTES];
   152     for (int i = 0; i < wrapkB.length; i++) {
   153       kB[i] = (byte) (wrapkB[i] ^ unwrapkB[i]);
   154     }
   155     return kB;
   156   }
   158   /**
   159    * The token server accepts an X-Client-State header, which is the
   160    * lowercase-hex-encoded first 16 bytes of the SHA-256 hash of the
   161    * bytes of kB.
   162    * @param kB a byte array, expected to be 32 bytes long.
   163    * @return a 32-character string.
   164    * @throws NoSuchAlgorithmException
   165    */
   166   public static String computeClientState(byte[] kB) throws NoSuchAlgorithmException {
   167     if (kB == null ||
   168         kB.length != 32) {
   169       throw new IllegalArgumentException("Unexpected kB.");
   170     }
   171     byte[] sha256 = Utils.sha256(kB);
   172     byte[] truncated = new byte[16];
   173     System.arraycopy(sha256, 0, truncated, 0, 16);
   174     return Utils.byte2Hex(truncated);    // This is automatically lowercase.
   175   }
   177   /**
   178    * Given an endpoint, calculate the corresponding BrowserID audience.
   179    * <p>
   180    * This is the domain, in web parlance.
   181    *
   182    * @param serverURI endpoint.
   183    * @return BrowserID audience.
   184    * @throws URISyntaxException
   185    */
   186   public static String getAudienceForURL(String serverURI) throws URISyntaxException {
   187     URI uri = new URI(serverURI);
   188     return new URI(uri.getScheme(), uri.getHost(), null, null).toString();
   189   }
   190 }

mercurial