mobile/android/base/fxa/login/StateFactory.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.fxa.login;
     7 import java.security.NoSuchAlgorithmException;
     8 import java.security.spec.InvalidKeySpecException;
    10 import org.mozilla.gecko.background.common.log.Logger;
    11 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
    12 import org.mozilla.gecko.browserid.DSACryptoImplementation;
    13 import org.mozilla.gecko.browserid.RSACryptoImplementation;
    14 import org.mozilla.gecko.fxa.FxAccountConstants;
    15 import org.mozilla.gecko.fxa.login.State.StateLabel;
    16 import org.mozilla.gecko.sync.ExtendedJSONObject;
    17 import org.mozilla.gecko.sync.NonObjectJSONException;
    18 import org.mozilla.gecko.sync.Utils;
    20 /**
    21  * Create {@link State} instances from serialized representations.
    22  * <p>
    23  * Version 1 recognizes 5 state labels (Engaged, Cohabiting, Married, Separated,
    24  * Doghouse). In the Cohabiting and Married states, the associated key pairs are
    25  * always RSA key pairs.
    26  * <p>
    27  * Version 2 is identical to version 1, except that in the Cohabiting and
    28  * Married states, the associated keypairs are always DSA key pairs.
    29  */
    30 public class StateFactory {
    31   private static final String LOG_TAG = StateFactory.class.getSimpleName();
    33   private static final int KEY_PAIR_SIZE_IN_BITS_V1 = 1024;
    35   public static BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
    36     // New key pairs are always DSA.
    37     return DSACryptoImplementation.generateKeyPair(KEY_PAIR_SIZE_IN_BITS_V1);
    38   }
    40   protected static BrowserIDKeyPair keyPairFromJSONObjectV1(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
    41     // V1 key pairs are RSA.
    42     return RSACryptoImplementation.fromJSONObject(o);
    43   }
    45   protected static BrowserIDKeyPair keyPairFromJSONObjectV2(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
    46     // V2 key pairs are DSA.
    47     return DSACryptoImplementation.fromJSONObject(o);
    48   }
    50   public static State fromJSONObject(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
    51     Long version = o.getLong("version");
    52     if (version == null) {
    53       throw new IllegalStateException("version must not be null");
    54     }
    56     final int v = version.intValue();
    57     if (v == 2) {
    58       // The most common case is the most recent version.
    59       return fromJSONObjectV2(stateLabel, o);
    60     }
    61     if (v == 1) {
    62       final State state = fromJSONObjectV1(stateLabel, o);
    63       return migrateV1toV2(stateLabel, state);
    64     }
    65     throw new IllegalStateException("version must be in {1, 2}");
    66   }
    68   protected static State fromJSONObjectV1(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
    69     switch (stateLabel) {
    70     case Engaged:
    71       return new Engaged(
    72           o.getString("email"),
    73           o.getString("uid"),
    74           o.getBoolean("verified"),
    75           Utils.hex2Byte(o.getString("unwrapkB")),
    76           Utils.hex2Byte(o.getString("sessionToken")),
    77           Utils.hex2Byte(o.getString("keyFetchToken")));
    78     case Cohabiting:
    79       return new Cohabiting(
    80           o.getString("email"),
    81           o.getString("uid"),
    82           Utils.hex2Byte(o.getString("sessionToken")),
    83           Utils.hex2Byte(o.getString("kA")),
    84           Utils.hex2Byte(o.getString("kB")),
    85           keyPairFromJSONObjectV1(o.getObject("keyPair")));
    86     case Married:
    87       return new Married(
    88           o.getString("email"),
    89           o.getString("uid"),
    90           Utils.hex2Byte(o.getString("sessionToken")),
    91           Utils.hex2Byte(o.getString("kA")),
    92           Utils.hex2Byte(o.getString("kB")),
    93           keyPairFromJSONObjectV1(o.getObject("keyPair")),
    94           o.getString("certificate"));
    95     case Separated:
    96       return new Separated(
    97           o.getString("email"),
    98           o.getString("uid"),
    99           o.getBoolean("verified"));
   100     case Doghouse:
   101       return new Doghouse(
   102           o.getString("email"),
   103           o.getString("uid"),
   104           o.getBoolean("verified"));
   105     default:
   106       throw new IllegalStateException("unrecognized state label: " + stateLabel);
   107     }
   108   }
   110   /**
   111    * Exactly the same as {@link fromJSONObjectV1}, except that all key pairs are DSA key pairs.
   112    */
   113   protected static State fromJSONObjectV2(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
   114     switch (stateLabel) {
   115     case Cohabiting:
   116       return new Cohabiting(
   117           o.getString("email"),
   118           o.getString("uid"),
   119           Utils.hex2Byte(o.getString("sessionToken")),
   120           Utils.hex2Byte(o.getString("kA")),
   121           Utils.hex2Byte(o.getString("kB")),
   122           keyPairFromJSONObjectV2(o.getObject("keyPair")));
   123     case Married:
   124       return new Married(
   125           o.getString("email"),
   126           o.getString("uid"),
   127           Utils.hex2Byte(o.getString("sessionToken")),
   128           Utils.hex2Byte(o.getString("kA")),
   129           Utils.hex2Byte(o.getString("kB")),
   130           keyPairFromJSONObjectV2(o.getObject("keyPair")),
   131           o.getString("certificate"));
   132     default:
   133       return fromJSONObjectV1(stateLabel, o);
   134     }
   135   }
   137   protected static void logMigration(State from, State to) {
   138     if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
   139       return;
   140     }
   141     try {
   142       FxAccountConstants.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
   143     } catch (Exception e) {
   144       Logger.warn(LOG_TAG, "Error producing JSON representation of V1 state.", e);
   145     }
   146     FxAccountConstants.pii(LOG_TAG, "Generated new V2 state: " + to.toJSONObject().toJSONString());
   147   }
   149   protected static State migrateV1toV2(StateLabel stateLabel, State state) throws NoSuchAlgorithmException {
   150     if (state == null) {
   151       // This should never happen, but let's be careful.
   152       Logger.error(LOG_TAG, "Got null state in migrateV1toV2; returning null.");
   153       return state;
   154     }
   156     Logger.info(LOG_TAG, "Migrating V1 persisted State to V2; stateLabel: " + stateLabel);
   158     // In V1, we use an RSA keyPair. In V2, we use a DSA keyPair. Only
   159     // Cohabiting and Married states have a persisted keyPair at all; all
   160     // other states need no conversion at all.
   161     switch (stateLabel) {
   162     case Cohabiting: {
   163       // In the Cohabiting state, we can just generate a new key pair and move on.
   164       final Cohabiting cohabiting = (Cohabiting) state;
   165       final BrowserIDKeyPair keyPair = generateKeyPair();
   166       final State migrated = new Cohabiting(cohabiting.email, cohabiting.uid, cohabiting.sessionToken, cohabiting.kA, cohabiting.kB, keyPair);
   167       logMigration(cohabiting, migrated);
   168       return migrated;
   169     }
   170     case Married: {
   171       // In the Married state, we cannot only change the key pair: the stored
   172       // certificate signs the public key of the now obsolete key pair. We
   173       // regress to the Cohabiting state; the next time we sync, we should
   174       // advance back to Married.
   175       final Married married = (Married) state;
   176       final BrowserIDKeyPair keyPair = generateKeyPair();
   177       final State migrated = new Cohabiting(married.email, married.uid, married.sessionToken, married.kA, married.kB, keyPair);
   178       logMigration(married, migrated);
   179       return migrated;
   180     }
   181     default:
   182       // Otherwise, V1 and V2 states are identical.
   183       return state;
   184     }
   185   }
   186 }

mercurial