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.

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

mercurial