mobile/android/base/sync/CollectionKeys.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.sync;
     7 import java.io.IOException;
     8 import java.io.UnsupportedEncodingException;
     9 import java.util.HashMap;
    10 import java.util.HashSet;
    11 import java.util.Map.Entry;
    12 import java.util.Set;
    14 import org.json.simple.JSONArray;
    15 import org.json.simple.parser.ParseException;
    16 import org.mozilla.apache.commons.codec.binary.Base64;
    17 import org.mozilla.gecko.sync.crypto.CryptoException;
    18 import org.mozilla.gecko.sync.crypto.KeyBundle;
    20 public class CollectionKeys {
    21   private KeyBundle                  defaultKeyBundle     = null;
    22   private final HashMap<String, KeyBundle> collectionKeyBundles = new HashMap<String, KeyBundle>();
    24   /**
    25    * Randomly generate a basic CollectionKeys object.
    26    * @throws CryptoException
    27    */
    28   public static CollectionKeys generateCollectionKeys() throws CryptoException {
    29     CollectionKeys ck = new CollectionKeys();
    30     ck.clear();
    31     ck.defaultKeyBundle = KeyBundle.withRandomKeys();
    32     // TODO: eventually we would like to keep per-collection keys, just generate
    33     // new ones as appropriate.
    34     return ck;
    35   }
    37   public KeyBundle defaultKeyBundle() throws NoCollectionKeysSetException {
    38     if (this.defaultKeyBundle == null) {
    39       throw new NoCollectionKeysSetException();
    40     }
    41     return this.defaultKeyBundle;
    42   }
    44   public boolean keyBundleForCollectionIsNotDefault(String collection) {
    45     return collectionKeyBundles.containsKey(collection);
    46   }
    48   public KeyBundle keyBundleForCollection(String collection)
    49       throws NoCollectionKeysSetException {
    50     if (this.defaultKeyBundle == null) {
    51       throw new NoCollectionKeysSetException();
    52     }
    53     if (keyBundleForCollectionIsNotDefault(collection)) {
    54       return collectionKeyBundles.get(collection);
    55     }
    56     return this.defaultKeyBundle;
    57   }
    59   /**
    60    * Take a pair of values in a JSON array, handing them off to KeyBundle to
    61    * produce a usable keypair.
    62    */
    63   private static KeyBundle arrayToKeyBundle(JSONArray array) throws UnsupportedEncodingException {
    64     String encKeyStr  = (String) array.get(0);
    65     String hmacKeyStr = (String) array.get(1);
    66     return KeyBundle.fromBase64EncodedKeys(encKeyStr, hmacKeyStr);
    67   }
    69   @SuppressWarnings("unchecked")
    70   private static JSONArray keyBundleToArray(KeyBundle bundle) {
    71     // Generate JSON.
    72     JSONArray keysArray = new JSONArray();
    73     keysArray.add(new String(Base64.encodeBase64(bundle.getEncryptionKey())));
    74     keysArray.add(new String(Base64.encodeBase64(bundle.getHMACKey())));
    75     return keysArray;
    76   }
    78   private ExtendedJSONObject asRecordContents() throws NoCollectionKeysSetException {
    79     ExtendedJSONObject json = new ExtendedJSONObject();
    80     json.put("id", "keys");
    81     json.put("collection", "crypto");
    82     json.put("default", keyBundleToArray(this.defaultKeyBundle()));
    83     ExtendedJSONObject colls = new ExtendedJSONObject();
    84     for (Entry<String, KeyBundle> collKey : collectionKeyBundles.entrySet()) {
    85       colls.put(collKey.getKey(), keyBundleToArray(collKey.getValue()));
    86     }
    87     json.put("collections", colls);
    88     return json;
    89   }
    91   public CryptoRecord asCryptoRecord() throws NoCollectionKeysSetException {
    92     ExtendedJSONObject payload = this.asRecordContents();
    93     CryptoRecord record = new CryptoRecord(payload);
    94     record.collection = "crypto";
    95     record.guid       = "keys";
    96     record.deleted    = false;
    97     return record;
    98   }
   100   /**
   101    * Set my key bundle and collection keys with the given key bundle and data
   102    * (possibly decrypted) from the given record.
   103    *
   104    * @param keys
   105    *          A "crypto/keys" <code>CryptoRecord</code>, encrypted with
   106    *          <code>syncKeyBundle</code> if <code>syncKeyBundle</code> is non-null.
   107    * @param syncKeyBundle
   108    *          If non-null, the sync key bundle to decrypt <code>keys</code> with.
   109    */
   110   public void setKeyPairsFromWBO(CryptoRecord keys, KeyBundle syncKeyBundle)
   111       throws CryptoException, IOException, ParseException, NonObjectJSONException {
   112     if (keys == null) {
   113       throw new IllegalArgumentException("cannot set key pairs from null record");
   114     }
   115     if (syncKeyBundle != null) {
   116       keys.keyBundle = syncKeyBundle;
   117       keys.decrypt();
   118     }
   119     ExtendedJSONObject cleartext = keys.payload;
   120     KeyBundle defaultKey = arrayToKeyBundle((JSONArray) cleartext.get("default"));
   122     ExtendedJSONObject collections = cleartext.getObject("collections");
   123     HashMap<String, KeyBundle> collectionKeys = new HashMap<String, KeyBundle>();
   124     for (Entry<String, Object> pair : collections.entrySet()) {
   125       KeyBundle bundle = arrayToKeyBundle((JSONArray) pair.getValue());
   126       collectionKeys.put(pair.getKey(), bundle);
   127     }
   129     this.collectionKeyBundles.clear();
   130     this.collectionKeyBundles.putAll(collectionKeys);
   131     this.defaultKeyBundle     = defaultKey;
   132   }
   134   public void setKeyBundleForCollection(String collection, KeyBundle keys) {
   135     this.collectionKeyBundles.put(collection, keys);
   136   }
   138   public void setDefaultKeyBundle(KeyBundle keys) {
   139     this.defaultKeyBundle = keys;
   140   }
   142   public void clear() {
   143     this.defaultKeyBundle = null;
   144     this.collectionKeyBundles.clear();
   145   }
   147   /**
   148    * Return set of collections where key is either missing from one collection
   149    * or not the same in both collections.
   150    * <p>
   151    * Does not check for different default keys.
   152    */
   153   public static Set<String> differences(CollectionKeys a, CollectionKeys b) {
   154     Set<String> differences = new HashSet<String>();
   155     Set<String> collections = new HashSet<String>(a.collectionKeyBundles.keySet());
   156     collections.addAll(b.collectionKeyBundles.keySet());
   158     // Iterate through one collection, collecting missing and differences.
   159     for (String collection : collections) {
   160       KeyBundle keyA;
   161       KeyBundle keyB;
   162       try {
   163         keyA = a.keyBundleForCollection(collection); // Will return default key as appropriate.
   164         keyB = b.keyBundleForCollection(collection); // Will return default key as appropriate.
   165       } catch (NoCollectionKeysSetException e) {
   166         differences.add(collection);
   167         continue;
   168       }
   169       // keyA and keyB are not null at this point.
   170       if (!keyA.equals(keyB)) {
   171         differences.add(collection);
   172       }
   173     }
   175     return differences;
   176   }
   178   @Override
   179   public boolean equals(Object o) {
   180     if (!(o instanceof CollectionKeys)) {
   181       return false;
   182     }
   183     CollectionKeys other = (CollectionKeys) o;
   184     try {
   185       // It would be nice to use map equality here, but there can be map entries
   186       // where the key is the default key that should compare equal to a missing
   187       // map entry. Therefore, we always compute the set of differences.
   188       return defaultKeyBundle().equals(other.defaultKeyBundle()) &&
   189              CollectionKeys.differences(this, other).isEmpty();
   190     } catch (NoCollectionKeysSetException e) {
   191       // If either default key bundle is not set, we'll say the bundles are not equal.
   192       return false;
   193     }
   194   }
   195 }

mercurial