mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 709323 - Handle key changes in info/collections. r=rnewman, a=blocking-fennec
This commit is contained in:
parent
328b5ebbe6
commit
bb1e8744d8
@ -7,7 +7,9 @@ package org.mozilla.gecko.sync;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.parser.ParseException;
|
||||
@ -16,21 +18,9 @@ import org.mozilla.gecko.sync.crypto.CryptoException;
|
||||
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
||||
|
||||
public class CollectionKeys {
|
||||
private static final String LOG_TAG = "CollectionKeys";
|
||||
private KeyBundle defaultKeyBundle = null;
|
||||
private HashMap<String, KeyBundle> collectionKeyBundles = new HashMap<String, KeyBundle>();
|
||||
|
||||
public static CryptoRecord generateCollectionKeysRecord() throws CryptoException {
|
||||
CollectionKeys ck = generateCollectionKeys();
|
||||
try {
|
||||
return ck.asCryptoRecord();
|
||||
} catch (NoCollectionKeysSetException e) {
|
||||
// Cannot occur.
|
||||
Logger.error(LOG_TAG, "generateCollectionKeys returned a value with no default key.", e);
|
||||
throw new IllegalStateException("CollectionKeys should not have null default key.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly generate a basic CollectionKeys object.
|
||||
* @throws CryptoException
|
||||
@ -51,12 +41,16 @@ public class CollectionKeys {
|
||||
return this.defaultKeyBundle;
|
||||
}
|
||||
|
||||
public boolean keyBundleForCollectionIsNotDefault(String collection) {
|
||||
return collectionKeyBundles.containsKey(collection);
|
||||
}
|
||||
|
||||
public KeyBundle keyBundleForCollection(String collection)
|
||||
throws NoCollectionKeysSetException {
|
||||
throws NoCollectionKeysSetException {
|
||||
if (this.defaultKeyBundle == null) {
|
||||
throw new NoCollectionKeysSetException();
|
||||
}
|
||||
if (collectionKeyBundles.containsKey(collection)) {
|
||||
if (keyBundleForCollectionIsNotDefault(collection)) {
|
||||
return collectionKeyBundles.get(collection);
|
||||
}
|
||||
return this.defaultKeyBundle;
|
||||
@ -145,4 +139,35 @@ public class CollectionKeys {
|
||||
this.defaultKeyBundle = null;
|
||||
this.collectionKeyBundles = new HashMap<String, KeyBundle>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return set of collections where key is either missing from one collection
|
||||
* or not the same in both collections.
|
||||
* <p>
|
||||
* Does not check for different default keys.
|
||||
*/
|
||||
public static Set<String> differences(CollectionKeys a, CollectionKeys b) {
|
||||
Set<String> differences = new HashSet<String>();
|
||||
|
||||
// Iterate through one collection, collecting missing and differences.
|
||||
for (String collection : a.collectionKeyBundles.keySet()) {
|
||||
KeyBundle key = b.collectionKeyBundles.get(collection);
|
||||
if (key == null) {
|
||||
differences.add(collection);
|
||||
continue;
|
||||
}
|
||||
if (!key.equals(a.collectionKeyBundles.get(collection))) {
|
||||
differences.add(collection);
|
||||
}
|
||||
}
|
||||
|
||||
// Now iterate through the other collection, collecting just the missing.
|
||||
for (String collection : b.collectionKeyBundles.keySet()) {
|
||||
if (!a.collectionKeyBundles.containsKey(collection)) {
|
||||
differences.add(collection);
|
||||
}
|
||||
}
|
||||
|
||||
return differences;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.gecko.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
@ -22,20 +23,27 @@ public class InfoCollections implements SyncStorageRequestDelegate {
|
||||
protected String infoURL;
|
||||
protected String credentials;
|
||||
|
||||
// Fields.
|
||||
// Rather than storing decimal/double timestamps, as provided by the
|
||||
// server, we convert immediately to milliseconds since epoch.
|
||||
private HashMap<String, Long> timestamps;
|
||||
|
||||
public HashMap<String, Long> getTimestamps() {
|
||||
if (this.timestamps == null) {
|
||||
throw new IllegalStateException("No record fetched.");
|
||||
}
|
||||
return this.timestamps;
|
||||
}
|
||||
/**
|
||||
* Fields fetched from the server, or <code>null</code> if not yet fetched.
|
||||
* <p>
|
||||
* Rather than storing decimal/double timestamps, as provided by the server,
|
||||
* we convert immediately to milliseconds since epoch.
|
||||
*/
|
||||
private Map<String, Long> timestamps = null;
|
||||
|
||||
/**
|
||||
* Return the timestamp for the given collection, or null if the timestamps
|
||||
* have not been fetched or the given collection does not have a timestamp.
|
||||
*
|
||||
* @param collection
|
||||
* The collection to inspect.
|
||||
* @return the timestamp in milliseconds since epoch.
|
||||
*/
|
||||
public Long getTimestamp(String collection) {
|
||||
return this.getTimestamps().get(collection);
|
||||
if (timestamps == null) {
|
||||
return null;
|
||||
}
|
||||
return timestamps.get(collection);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,12 +82,8 @@ public class InfoCollections implements SyncStorageRequestDelegate {
|
||||
}
|
||||
|
||||
public void fetch(InfoCollectionsDelegate callback) {
|
||||
if (this.timestamps == null) {
|
||||
this.callback = callback;
|
||||
this.doFetch();
|
||||
return;
|
||||
}
|
||||
callback.handleSuccess(this);
|
||||
this.callback = callback;
|
||||
this.doFetch();
|
||||
}
|
||||
|
||||
private void doFetch() {
|
||||
|
@ -1,40 +1,6 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Android Sync Client.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jason Voll <jvoll@mozilla.com>
|
||||
* Richard Newman <rnewman@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
/* 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.sync.crypto;
|
||||
|
||||
@ -47,6 +13,7 @@ import javax.crypto.Mac;
|
||||
import org.mozilla.apache.commons.codec.binary.Base64;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public class KeyBundle {
|
||||
@ -178,4 +145,14 @@ public class KeyBundle {
|
||||
public void setHMACKey(byte[] hmacKey) {
|
||||
this.hmacKey = hmacKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof KeyBundle)) {
|
||||
return false;
|
||||
}
|
||||
KeyBundle other = (KeyBundle) o;
|
||||
return Arrays.equals(other.encryptionKey, this.encryptionKey) &&
|
||||
Arrays.equals(other.hmacKey, this.hmacKey);
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,10 @@ public class PersistedCrypto5Keys {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean persistedKeysExist() {
|
||||
return lastModified() > 0;
|
||||
}
|
||||
|
||||
public long lastModified() {
|
||||
return prefs.getLong(CRYPTO5_KEYS_LAST_MODIFIED, -1);
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ package org.mozilla.gecko.sync.stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.mozilla.gecko.sync.CollectionKeys;
|
||||
@ -14,8 +16,10 @@ import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.InfoCollections;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
|
||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
import org.mozilla.gecko.sync.crypto.CryptoException;
|
||||
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
||||
import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
|
||||
import org.mozilla.gecko.sync.delegates.KeyUploadDelegate;
|
||||
import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
|
||||
@ -44,7 +48,7 @@ implements SyncStorageRequestDelegate, KeyUploadDelegate {
|
||||
|
||||
PersistedCrypto5Keys pck = session.config.persistedCryptoKeys();
|
||||
long lastModified = pck.lastModified();
|
||||
if (!infoCollections.updateNeeded(CRYPTO_COLLECTION, lastModified)) {
|
||||
if (retrying || !infoCollections.updateNeeded(CRYPTO_COLLECTION, lastModified)) {
|
||||
// Try to use our local collection keys for this session.
|
||||
Logger.info(LOG_TAG, "Trying to use persisted collection keys for this session.");
|
||||
CollectionKeys keys = pck.keys();
|
||||
@ -79,16 +83,65 @@ implements SyncStorageRequestDelegate, KeyUploadDelegate {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void setAndPersist(PersistedCrypto5Keys pck, CollectionKeys keys, long timestamp) {
|
||||
session.config.setCollectionKeys(keys);
|
||||
pck.persistKeys(keys);
|
||||
pck.persistLastModified(timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return collections where either the individual key has changed, or if the
|
||||
* new default key is not the same as the old default key, where the
|
||||
* collection is using the default key.
|
||||
*/
|
||||
protected Set<String> collectionsToUpdate(CollectionKeys oldKeys, CollectionKeys newKeys) {
|
||||
// These keys have explicitly changed; they definitely need updating.
|
||||
Set<String> changedKeys = new HashSet<String>(CollectionKeys.differences(oldKeys, newKeys));
|
||||
|
||||
boolean defaultKeyChanged = true; // Most pessimistic is to assume default key has changed.
|
||||
KeyBundle newDefaultKeyBundle = null;
|
||||
try {
|
||||
KeyBundle oldDefaultKeyBundle = oldKeys.defaultKeyBundle();
|
||||
newDefaultKeyBundle = newKeys.defaultKeyBundle();
|
||||
defaultKeyChanged = !oldDefaultKeyBundle.equals(newDefaultKeyBundle);
|
||||
} catch (NoCollectionKeysSetException e) {
|
||||
Logger.warn(LOG_TAG, "NoCollectionKeysSetException in EnsureCrypto5KeysStage.", e);
|
||||
}
|
||||
|
||||
if (newDefaultKeyBundle == null) {
|
||||
Logger.info(LOG_TAG, "New default key not provided; returning changed individual keys.");
|
||||
return changedKeys;
|
||||
}
|
||||
|
||||
if (!defaultKeyChanged) {
|
||||
Logger.info(LOG_TAG, "New default key is the same as old default key; returning changed individual keys.");
|
||||
return changedKeys;
|
||||
}
|
||||
|
||||
// New keys have a different default/sync key; check known collections against the default key.
|
||||
Logger.info(LOG_TAG, "New default key is not the same as old default key.");
|
||||
for (Stage stage : Stage.getNamedStages()) {
|
||||
String name = stage.getRepositoryName();
|
||||
if (!newKeys.keyBundleForCollectionIsNotDefault(name)) {
|
||||
// Default key has changed, so this collection has changed.
|
||||
changedKeys.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return changedKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequestSuccess(SyncStorageResponse response) {
|
||||
CollectionKeys k = new CollectionKeys();
|
||||
// Take the timestamp from the response since it is later than the timestamp from info/collections.
|
||||
long responseTimestamp = response.normalizedWeaveTimestamp();
|
||||
CollectionKeys keys = new CollectionKeys();
|
||||
try {
|
||||
ExtendedJSONObject body = response.jsonObjectBody();
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.pii(LOG_TAG, "Fetched keys: " + body.toJSONString());
|
||||
}
|
||||
k.setKeyPairsFromWBO(CryptoRecord.fromJSONRecord(body), session.config.syncKeyBundle);
|
||||
|
||||
keys.setKeyPairsFromWBO(CryptoRecord.fromJSONRecord(body), session.config.syncKeyBundle);
|
||||
} catch (IllegalStateException e) {
|
||||
session.abort(e, "Invalid keys WBO.");
|
||||
return;
|
||||
@ -107,15 +160,32 @@ implements SyncStorageRequestDelegate, KeyUploadDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
// New keys! Persist keys and server timestamp.
|
||||
Logger.info(LOG_TAG, "Setting fetched keys for this session.");
|
||||
session.config.setCollectionKeys(k);
|
||||
Logger.trace(LOG_TAG, "Persisting fetched keys and last modified.");
|
||||
PersistedCrypto5Keys pck = session.config.persistedCryptoKeys();
|
||||
pck.persistKeys(k);
|
||||
// Take the timestamp from the response since it is later than the timestamp from info/collections.
|
||||
pck.persistLastModified(response.normalizedWeaveTimestamp());
|
||||
if (!pck.persistedKeysExist()) {
|
||||
// New keys, and no old keys! Persist keys and server timestamp.
|
||||
Logger.info(LOG_TAG, "Setting fetched keys for this session; persisting fetched keys and last modified.");
|
||||
setAndPersist(pck, keys, responseTimestamp);
|
||||
session.advance();
|
||||
return;
|
||||
}
|
||||
|
||||
// New keys, but we had old keys. Check for differences.
|
||||
CollectionKeys oldKeys = pck.keys();
|
||||
Set<String> changedCollections = collectionsToUpdate(oldKeys, keys);
|
||||
if (!changedCollections.isEmpty()) {
|
||||
// New keys, different from old keys.
|
||||
Logger.info(LOG_TAG, "Fetched keys are not the same as persisted keys; " +
|
||||
"setting fetched keys for this session before resetting changed engines.");
|
||||
setAndPersist(pck, keys, responseTimestamp);
|
||||
session.resetStagesByName(changedCollections);
|
||||
session.abort(null, "crypto/keys changed on server.");
|
||||
return;
|
||||
}
|
||||
|
||||
// New keys don't differ from old keys; persist timestamp and move on.
|
||||
Logger.trace(LOG_TAG, "Fetched keys are the same as persisted keys; persisting only last modified.");
|
||||
session.config.setCollectionKeys(oldKeys);
|
||||
pck.persistLastModified(response.normalizedWeaveTimestamp());
|
||||
session.advance();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user