зеркало из https://github.com/mozilla/pjs.git
Bug 615021 - Merge fx-sync to mozilla-central. a=lotsa-blockers
This commit is contained in:
Коммит
19bfb008b0
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Justin Dolske <dolske@mozilla.com> (original author)
|
||||
* 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
|
||||
|
@ -235,6 +236,7 @@ WeaveCrypto.prototype = {
|
|||
this.nss.PK11_ATTR_SENSITIVE = 0x40;
|
||||
|
||||
// security/nss/lib/util/secoidt.h
|
||||
this.nss.SEC_OID_PKCS5_PBKDF2 = 291;
|
||||
this.nss.SEC_OID_HMAC_SHA1 = 294;
|
||||
this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION = 16;
|
||||
|
||||
|
@ -452,8 +454,6 @@ WeaveCrypto.prototype = {
|
|||
|
||||
algorithm : Ci.IWeaveCrypto.AES_256_CBC,
|
||||
|
||||
keypairBits : 2048,
|
||||
|
||||
encrypt : function(clearTextUCS2, symmetricKey, iv) {
|
||||
this.log("encrypt() called");
|
||||
|
||||
|
@ -567,62 +567,6 @@ WeaveCrypto.prototype = {
|
|||
},
|
||||
|
||||
|
||||
generateKeypair : function(passphrase, salt, iv, out_encodedPublicKey, out_wrappedPrivateKey) {
|
||||
this.log("generateKeypair() called.");
|
||||
|
||||
let pubKey, privKey, slot;
|
||||
try {
|
||||
// Attributes for the private key. We're just going to wrap and extract the
|
||||
// value, so they're not critical. The _PUBLIC attribute just indicates the
|
||||
// object can be accessed without being logged into the token.
|
||||
let attrFlags = (this.nss.PK11_ATTR_SESSION | this.nss.PK11_ATTR_PUBLIC | this.nss.PK11_ATTR_SENSITIVE);
|
||||
|
||||
pubKey = new this.nss_t.SECKEYPublicKey.ptr();
|
||||
|
||||
let rsaParams = new this.nss_t.PK11RSAGenParams();
|
||||
rsaParams.keySizeInBits = this.keypairBits; // 1024, 2048, etc.
|
||||
rsaParams.pe = 65537; // public exponent.
|
||||
|
||||
slot = this.nss.PK11_GetInternalSlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Generate the keypair.
|
||||
privKey = this.nss.PK11_GenerateKeyPairWithFlags(slot,
|
||||
this.nss.CKM_RSA_PKCS_KEY_PAIR_GEN,
|
||||
rsaParams.address(),
|
||||
pubKey.address(),
|
||||
attrFlags, null);
|
||||
if (privKey.isNull())
|
||||
throw Components.Exception("keypair generation failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let s = this.nss.PK11_SetPrivateKeyNickname(privKey, "Weave User PrivKey");
|
||||
if (s)
|
||||
throw Components.Exception("key nickname failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let wrappedPrivateKey = this._wrapPrivateKey(privKey, passphrase, salt, iv);
|
||||
out_wrappedPrivateKey.value = wrappedPrivateKey; // outparam
|
||||
|
||||
let derKey = this.nss.SECKEY_EncodeDERSubjectPublicKeyInfo(pubKey);
|
||||
if (derKey.isNull())
|
||||
throw Components.Exception("SECKEY_EncodeDERSubjectPublicKeyInfo failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let encodedPublicKey = this.encodeBase64(derKey.contents.data, derKey.contents.len);
|
||||
out_encodedPublicKey.value = encodedPublicKey; // outparam
|
||||
} catch (e) {
|
||||
this.log("generateKeypair: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (pubKey && !pubKey.isNull())
|
||||
this.nss.SECKEY_DestroyPublicKey(pubKey);
|
||||
if (privKey && !privKey.isNull())
|
||||
this.nss.SECKEY_DestroyPrivateKey(privKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
generateRandomKey : function() {
|
||||
this.log("generateRandomKey() called");
|
||||
let encodedKey, keygenMech, keySize;
|
||||
|
@ -702,280 +646,6 @@ WeaveCrypto.prototype = {
|
|||
},
|
||||
|
||||
|
||||
wrapSymmetricKey : function(symmetricKey, encodedPublicKey) {
|
||||
this.log("wrapSymmetricKey() called");
|
||||
|
||||
// Step 1. Get rid of the base64 encoding on the inputs.
|
||||
|
||||
let pubKeyData = this.makeSECItem(encodedPublicKey, true);
|
||||
let symKeyData = this.makeSECItem(symmetricKey, true);
|
||||
|
||||
// This buffer is much larger than needed, but that's ok.
|
||||
let keyData = new ctypes.ArrayType(ctypes.unsigned_char, 4096)();
|
||||
let wrappedKey = new this.nss_t.SECItem(this.nss.SIBUFFER, keyData, keyData.length);
|
||||
|
||||
// Step 2. Put the symmetric key bits into a P11 key object.
|
||||
let slot, symKey, pubKeyInfo, pubKey;
|
||||
try {
|
||||
slot = this.nss.PK11_GetInternalSlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// ImportSymKey wants a mechanism, from which it derives the key type.
|
||||
let keyMech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
|
||||
// This imports a key with the usage set for encryption, but that doesn't
|
||||
// really matter because we're just going to wrap it up and not use it.
|
||||
symKey = this.nss.PK11_ImportSymKey(slot, keyMech, this.nss.PK11_OriginUnwrap, this.nss.CKA_ENCRYPT, symKeyData.address(), null);
|
||||
if (symKey.isNull())
|
||||
throw Components.Exception("symkey import failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 3. Put the public key bits into a P11 key object.
|
||||
|
||||
// Can't just do this directly, it's expecting a minimal ASN1 blob
|
||||
// pubKey = SECKEY_ImportDERPublicKey(&pubKeyData, CKK_RSA);
|
||||
pubKeyInfo = this.nss.SECKEY_DecodeDERSubjectPublicKeyInfo(pubKeyData.address());
|
||||
if (pubKeyInfo.isNull())
|
||||
throw Components.Exception("SECKEY_DecodeDERSubjectPublicKeyInfo failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
pubKey = this.nss.SECKEY_ExtractPublicKey(pubKeyInfo);
|
||||
if (pubKey.isNull())
|
||||
throw Components.Exception("SECKEY_ExtractPublicKey failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 4. Wrap the symmetric key with the public key.
|
||||
|
||||
let wrapMech = this.nss.PK11_AlgtagToMechanism(this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION);
|
||||
|
||||
let s = this.nss.PK11_PubWrapSymKey(wrapMech, pubKey, symKey, wrappedKey.address());
|
||||
if (s)
|
||||
throw Components.Exception("PK11_PubWrapSymKey failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 5. Base64 encode the wrapped key, cleanup, and return to caller.
|
||||
return this.encodeBase64(wrappedKey.data, wrappedKey.len);
|
||||
} catch (e) {
|
||||
this.log("wrapSymmetricKey: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (pubKey && !pubKey.isNull())
|
||||
this.nss.SECKEY_DestroyPublicKey(pubKey);
|
||||
if (pubKeyInfo && !pubKeyInfo.isNull())
|
||||
this.nss.SECKEY_DestroySubjectPublicKeyInfo(pubKeyInfo);
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
unwrapSymmetricKey : function(wrappedSymmetricKey, wrappedPrivateKey, passphrase, salt, iv) {
|
||||
this.log("unwrapSymmetricKey() called");
|
||||
let privKeyUsageLength = 1;
|
||||
let privKeyUsage = new ctypes.ArrayType(this.nss_t.CK_ATTRIBUTE_TYPE, privKeyUsageLength)();
|
||||
privKeyUsage[0] = this.nss.CKA_UNWRAP;
|
||||
|
||||
// Step 1. Get rid of the base64 encoding on the inputs.
|
||||
let wrappedPrivKey = this.makeSECItem(wrappedPrivateKey, true);
|
||||
let wrappedSymKey = this.makeSECItem(wrappedSymmetricKey, true);
|
||||
|
||||
let ivParam, slot, pbeKey, symKey, privKey, symKeyData;
|
||||
try {
|
||||
// Step 2. Convert the passphrase to a symmetric key and get the IV in the proper form.
|
||||
pbeKey = this._deriveKeyFromPassphrase(passphrase, salt);
|
||||
let ivItem = this.makeSECItem(iv, true);
|
||||
|
||||
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
|
||||
let wrapMech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
wrapMech = this.nss.PK11_GetPadMechanism(wrapMech);
|
||||
if (wrapMech == this.nss.CKM_INVALID_MECHANISM)
|
||||
throw Components.Exception("unwrapSymKey: unknown key mech", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
ivParam = this.nss.PK11_ParamFromIV(wrapMech, ivItem.address());
|
||||
if (ivParam.isNull())
|
||||
throw Components.Exception("unwrapSymKey: PK11_ParamFromIV failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 3. Unwrap the private key with the key from the passphrase.
|
||||
slot = this.nss.PK11_GetInternalSlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Normally, one wants to associate a private key with a public key.
|
||||
// P11_UnwrapPrivKey() passes its keyID arg to PK11_MakeIDFromPubKey(),
|
||||
// which hashes the public key to create an ID (or, for small inputs,
|
||||
// assumes it's already hashed and does nothing).
|
||||
// We don't really care about this, because our unwrapped private key will
|
||||
// just live long enough to unwrap the bulk data key. So, we'll just jam in
|
||||
// a random value... We have an IV handy, so that will suffice.
|
||||
let keyID = ivItem.address();
|
||||
|
||||
privKey = this.nss.PK11_UnwrapPrivKey(slot,
|
||||
pbeKey, wrapMech, ivParam, wrappedPrivKey.address(),
|
||||
null, // label
|
||||
keyID,
|
||||
false, // isPerm (token object)
|
||||
true, // isSensitive
|
||||
this.nss.CKK_RSA,
|
||||
privKeyUsage.addressOfElement(0), privKeyUsageLength,
|
||||
null); // wincx
|
||||
if (privKey.isNull())
|
||||
throw Components.Exception("PK11_UnwrapPrivKey failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 4. Unwrap the symmetric key with the user's private key.
|
||||
|
||||
// XXX also have PK11_PubUnwrapSymKeyWithFlags() if more control is needed.
|
||||
// (last arg is keySize, 0 seems to work)
|
||||
symKey = this.nss.PK11_PubUnwrapSymKey(privKey, wrappedSymKey.address(), wrapMech,
|
||||
this.nss.CKA_DECRYPT, 0);
|
||||
if (symKey.isNull())
|
||||
throw Components.Exception("PK11_PubUnwrapSymKey failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 5. Base64 encode the unwrapped key, cleanup, and return to caller.
|
||||
if (this.nss.PK11_ExtractKeyValue(symKey))
|
||||
throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
symKeyData = this.nss.PK11_GetKeyData(symKey);
|
||||
if (symKeyData.isNull())
|
||||
throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
return this.encodeBase64(symKeyData.contents.data, symKeyData.contents.len);
|
||||
} catch (e) {
|
||||
this.log("unwrapSymmetricKey: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (privKey && !privKey.isNull())
|
||||
this.nss.SECKEY_DestroyPrivateKey(privKey);
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
if (pbeKey && !pbeKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(pbeKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
if (ivParam && !ivParam.isNull())
|
||||
this.nss.SECITEM_FreeItem(ivParam, true);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
rewrapPrivateKey : function(wrappedPrivateKey, oldPassphrase, salt, iv, newPassphrase) {
|
||||
this.log("rewrapPrivateKey() called");
|
||||
let privKeyUsageLength = 1;
|
||||
let privKeyUsage = new ctypes.ArrayType(this.nss_t.CK_ATTRIBUTE_TYPE, privKeyUsageLength)();
|
||||
privKeyUsage[0] = this.nss.CKA_UNWRAP;
|
||||
|
||||
// Step 1. Get rid of the base64 encoding on the inputs.
|
||||
let wrappedPrivKey = this.makeSECItem(wrappedPrivateKey, true);
|
||||
|
||||
let pbeKey, ivParam, slot, privKey;
|
||||
try {
|
||||
// Step 2. Convert the passphrase to a symmetric key and get the IV in the proper form.
|
||||
let pbeKey = this._deriveKeyFromPassphrase(oldPassphrase, salt);
|
||||
let ivItem = this.makeSECItem(iv, true);
|
||||
|
||||
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
|
||||
let wrapMech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
wrapMech = this.nss.PK11_GetPadMechanism(wrapMech);
|
||||
if (wrapMech == this.nss.CKM_INVALID_MECHANISM)
|
||||
throw Components.Exception("rewrapSymKey: unknown key mech", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
ivParam = this.nss.PK11_ParamFromIV(wrapMech, ivItem.address());
|
||||
if (ivParam.isNull())
|
||||
throw Components.Exception("rewrapSymKey: PK11_ParamFromIV failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 3. Unwrap the private key with the key from the passphrase.
|
||||
slot = this.nss.PK11_GetInternalSlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let keyID = ivItem.address();
|
||||
|
||||
privKey = this.nss.PK11_UnwrapPrivKey(slot,
|
||||
pbeKey, wrapMech, ivParam, wrappedPrivKey.address(),
|
||||
null, // label
|
||||
keyID,
|
||||
false, // isPerm (token object)
|
||||
true, // isSensitive
|
||||
this.nss.CKK_RSA,
|
||||
privKeyUsage.addressOfElement(0), privKeyUsageLength,
|
||||
null); // wincx
|
||||
if (privKey.isNull())
|
||||
throw Components.Exception("PK11_UnwrapPrivKey failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 4. Rewrap the private key with the new passphrase.
|
||||
return this._wrapPrivateKey(privKey, newPassphrase, salt, iv);
|
||||
} catch (e) {
|
||||
this.log("rewrapPrivateKey: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (privKey && !privKey.isNull())
|
||||
this.nss.SECKEY_DestroyPrivateKey(privKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
if (ivParam && !ivParam.isNull())
|
||||
this.nss.SECITEM_FreeItem(ivParam, true);
|
||||
if (pbeKey && !pbeKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(pbeKey);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
verifyPassphrase : function(wrappedPrivateKey, passphrase, salt, iv) {
|
||||
this.log("verifyPassphrase() called");
|
||||
let privKeyUsageLength = 1;
|
||||
let privKeyUsage = new ctypes.ArrayType(this.nss_t.CK_ATTRIBUTE_TYPE, privKeyUsageLength)();
|
||||
privKeyUsage[0] = this.nss.CKA_UNWRAP;
|
||||
|
||||
// Step 1. Get rid of the base64 encoding on the inputs.
|
||||
let wrappedPrivKey = this.makeSECItem(wrappedPrivateKey, true);
|
||||
|
||||
let pbeKey, ivParam, slot, privKey;
|
||||
try {
|
||||
// Step 2. Convert the passphrase to a symmetric key and get the IV in the proper form.
|
||||
pbeKey = this._deriveKeyFromPassphrase(passphrase, salt);
|
||||
let ivItem = this.makeSECItem(iv, true);
|
||||
|
||||
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
|
||||
let wrapMech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
wrapMech = this.nss.PK11_GetPadMechanism(wrapMech);
|
||||
if (wrapMech == this.nss.CKM_INVALID_MECHANISM)
|
||||
throw Components.Exception("rewrapSymKey: unknown key mech", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
ivParam = this.nss.PK11_ParamFromIV(wrapMech, ivItem.address());
|
||||
if (ivParam.isNull())
|
||||
throw Components.Exception("rewrapSymKey: PK11_ParamFromIV failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Step 3. Unwrap the private key with the key from the passphrase.
|
||||
slot = this.nss.PK11_GetInternalSlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let keyID = ivItem.address();
|
||||
|
||||
privKey = this.nss.PK11_UnwrapPrivKey(slot,
|
||||
pbeKey, wrapMech, ivParam, wrappedPrivKey.address(),
|
||||
null, // label
|
||||
keyID,
|
||||
false, // isPerm (token object)
|
||||
true, // isSensitive
|
||||
this.nss.CKK_RSA,
|
||||
privKeyUsage.addressOfElement(0), privKeyUsageLength,
|
||||
null); // wincx
|
||||
return (!privKey.isNull());
|
||||
} catch (e) {
|
||||
this.log("verifyPassphrase: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (privKey && !privKey.isNull())
|
||||
this.nss.SECKEY_DestroyPrivateKey(privKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
if (ivParam && !ivParam.isNull())
|
||||
this.nss.SECITEM_FreeItem(ivParam, true);
|
||||
if (pbeKey && !pbeKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(pbeKey);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
@ -1000,16 +670,19 @@ WeaveCrypto.prototype = {
|
|||
return expanded;
|
||||
},
|
||||
|
||||
encodeBase64 : function (data, len) {
|
||||
expandData : function expandData(data, len) {
|
||||
// Byte-expand the buffer, so we can treat it as a UCS-2 string
|
||||
// consisting of u0000 - u00FF.
|
||||
let expanded = "";
|
||||
let intData = ctypes.cast(data, ctypes.uint8_t.array(len).ptr).contents;
|
||||
for (let i = 0; i < len; i++)
|
||||
expanded += String.fromCharCode(intData[i]);
|
||||
return btoa(expanded);
|
||||
return expanded;
|
||||
},
|
||||
|
||||
encodeBase64 : function (data, len) {
|
||||
return btoa(this.expandData(data, len));
|
||||
},
|
||||
|
||||
makeSECItem : function(input, isEncoded) {
|
||||
if (isEncoded)
|
||||
|
@ -1020,22 +693,23 @@ WeaveCrypto.prototype = {
|
|||
return new this.nss_t.SECItem(this.nss.SIBUFFER, outputData, outputData.length);
|
||||
},
|
||||
|
||||
_deriveKeyFromPassphrase : function (passphrase, salt) {
|
||||
this.log("_deriveKeyFromPassphrase() called.");
|
||||
|
||||
/**
|
||||
* Returns the expanded data string for the derived key.
|
||||
*/
|
||||
deriveKeyFromPassphrase : function deriveKeyFromPassphrase(passphrase, salt, keyLength) {
|
||||
this.log("deriveKeyFromPassphrase() called.");
|
||||
let passItem = this.makeSECItem(passphrase, false);
|
||||
let saltItem = this.makeSECItem(salt, true);
|
||||
|
||||
// http://mxr.mozilla.org/seamonkey/source/security/nss/lib/pk11wrap/pk11pbe.c#1261
|
||||
|
||||
// Bug 436577 prevents us from just using SEC_OID_PKCS5_PBKDF2 here
|
||||
let pbeAlg = this.algorithm;
|
||||
let cipherAlg = this.algorithm; // ignored by callee when pbeAlg != a pkcs5 mech.
|
||||
let prfAlg = this.nss.SEC_OID_HMAC_SHA1; // callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported
|
||||
|
||||
let keyLength = 0; // Callee will pick.
|
||||
let keyLength = keyLength || 0; // 0 = Callee will pick.
|
||||
let iterations = 4096; // PKCS#5 recommends at least 1000.
|
||||
|
||||
let algid, slot, symKey;
|
||||
let algid, slot, symKey, keyData;
|
||||
try {
|
||||
algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
|
||||
keyLength, iterations, saltItem.address());
|
||||
|
@ -1049,60 +723,31 @@ WeaveCrypto.prototype = {
|
|||
symKey = this.nss.PK11_PBEKeyGen(slot, algid, passItem.address(), false, null);
|
||||
if (symKey.isNull())
|
||||
throw Components.Exception("PK11_PBEKeyGen failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Take the PK11SymKeyStr, returning the extracted key data.
|
||||
if (this.nss.PK11_ExtractKeyValue(symKey)) {
|
||||
throw this.makeException("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
keyData = this.nss.PK11_GetKeyData(symKey);
|
||||
|
||||
if (keyData.isNull())
|
||||
throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// This copies the key contents into a JS string, so we don't leak.
|
||||
// The `finally` block below will clean up.
|
||||
return this.expandData(keyData.contents.data, keyData.contents.len);
|
||||
|
||||
} catch (e) {
|
||||
this.log("_deriveKeyFromPassphrase: failed: " + e);
|
||||
this.log("deriveKeyFromPassphrase: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (algid && !algid.isNull())
|
||||
this.nss.SECOID_DestroyAlgorithmID(algid, true);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
}
|
||||
|
||||
return symKey;
|
||||
},
|
||||
|
||||
|
||||
_wrapPrivateKey : function(privKey, passphrase, salt, iv) {
|
||||
this.log("_wrapPrivateKey() called.");
|
||||
let ivParam, pbeKey, wrappedKey;
|
||||
try {
|
||||
// Convert our passphrase to a symkey and get the IV in the form we want.
|
||||
pbeKey = this._deriveKeyFromPassphrase(passphrase, salt);
|
||||
|
||||
let ivItem = this.makeSECItem(iv, true);
|
||||
|
||||
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
|
||||
let wrapMech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
wrapMech = this.nss.PK11_GetPadMechanism(wrapMech);
|
||||
if (wrapMech == this.nss.CKM_INVALID_MECHANISM)
|
||||
throw Components.Exception("wrapPrivKey: unknown key mech", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let ivParam = this.nss.PK11_ParamFromIV(wrapMech, ivItem.address());
|
||||
if (ivParam.isNull())
|
||||
throw Components.Exception("wrapPrivKey: PK11_ParamFromIV failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
// Use a buffer to hold the wrapped key. NSS says about 1200 bytes for
|
||||
// a 2048-bit RSA key, so a 4096 byte buffer should be plenty.
|
||||
let keyData = new ctypes.ArrayType(ctypes.unsigned_char, 4096)();
|
||||
wrappedKey = new this.nss_t.SECItem(this.nss.SIBUFFER, keyData, keyData.length);
|
||||
|
||||
let s = this.nss.PK11_WrapPrivKey(privKey.contents.pkcs11Slot,
|
||||
pbeKey, privKey,
|
||||
wrapMech, ivParam,
|
||||
wrappedKey.address(), null);
|
||||
if (s)
|
||||
throw Components.Exception("wrapPrivKey: PK11_WrapPrivKey failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
return this.encodeBase64(wrappedKey.data, wrappedKey.len);
|
||||
} catch (e) {
|
||||
this.log("_wrapPrivateKey: failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (ivParam && !ivParam.isNull())
|
||||
this.nss.SECITEM_FreeItem(ivParam, true);
|
||||
if (pbeKey && !pbeKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(pbeKey);
|
||||
}
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
var btoa;
|
||||
|
||||
function test_derive(cryptoSvc) {
|
||||
// Extracted from test_utils_deriveKey.
|
||||
let pp = "secret phrase";
|
||||
let salt = "RE5YUHpQcGl3bg=="; // btoa("DNXPzPpiwn")
|
||||
|
||||
// 16-byte, extract key data.
|
||||
let k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, 16);
|
||||
do_check_eq(16, k.length);
|
||||
do_check_eq(btoa(k), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
|
||||
// Test different key lengths.
|
||||
k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, 32);
|
||||
do_check_eq(32, k.length);
|
||||
let encKey = btoa(k);
|
||||
|
||||
// Test via encryption.
|
||||
let iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(cryptoSvc.decrypt(cryptoSvc.encrypt("bacon", encKey, iv), encKey, iv), "bacon");
|
||||
|
||||
// Test default length (32).
|
||||
k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, null);
|
||||
do_check_eq(32, k.length);
|
||||
do_check_eq(encKey, btoa(k));
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc;
|
||||
try {
|
||||
let backstagePass = Components.utils.import("resource://services-crypto/WeaveCrypto.js");
|
||||
btoa = backstagePass.btoa;
|
||||
} catch (ex) {
|
||||
_("Aborting test: no WeaveCrypto.js.");
|
||||
return;
|
||||
}
|
||||
test_derive(new WeaveCrypto());
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
let cryptoSvc;
|
||||
try {
|
||||
Components.utils.import("resource://services-crypto/WeaveCrypto.js");
|
||||
cryptoSvc = new WeaveCrypto();
|
||||
} catch (ex) {
|
||||
// Fallback to binary WeaveCrypto
|
||||
cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"]
|
||||
.getService(Ci.IWeaveCrypto);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
var salt = cryptoSvc.generateRandomBytes(16);
|
||||
do_check_eq(salt.length, 24);
|
||||
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
var symKey = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(symKey.length, 44);
|
||||
|
||||
|
||||
// Tests with a 2048 bit key (the default)
|
||||
do_check_eq(cryptoSvc.keypairBits, 2048)
|
||||
|
||||
var pubOut = {};
|
||||
var privOut = {};
|
||||
cryptoSvc.generateKeypair("my passphrase", salt, iv, pubOut, privOut);
|
||||
var pubKey = pubOut.value;
|
||||
var privKey = privOut.value;
|
||||
do_check_true(!!pubKey);
|
||||
do_check_true(!!privKey);
|
||||
do_check_eq(pubKey.length, 392);
|
||||
do_check_true(privKey.length == 1624 || privKey.length == 1644);
|
||||
|
||||
// do some key wrapping
|
||||
var wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
|
||||
do_check_eq(wrappedKey.length, 344);
|
||||
|
||||
var unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
|
||||
"my passphrase", salt, iv);
|
||||
do_check_eq(unwrappedKey.length, 44);
|
||||
|
||||
// The acid test... Is our unwrapped key the same thing we started with?
|
||||
do_check_eq(unwrappedKey, symKey);
|
||||
|
||||
|
||||
// Tests with a 1024 bit key
|
||||
cryptoSvc.keypairBits = 1024;
|
||||
do_check_eq(cryptoSvc.keypairBits, 1024)
|
||||
|
||||
cryptoSvc.generateKeypair("my passphrase", salt, iv, pubOut, privOut);
|
||||
var pubKey = pubOut.value;
|
||||
var privKey = privOut.value;
|
||||
do_check_true(!!pubKey);
|
||||
do_check_true(!!privKey);
|
||||
do_check_eq(pubKey.length, 216);
|
||||
do_check_eq(privKey.length, 856);
|
||||
|
||||
// do some key wrapping
|
||||
wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
|
||||
do_check_eq(wrappedKey.length, 172);
|
||||
unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
|
||||
"my passphrase", salt, iv);
|
||||
do_check_eq(unwrappedKey.length, 44);
|
||||
|
||||
// The acid test... Is our unwrapped key the same thing we started with?
|
||||
do_check_eq(unwrappedKey, symKey);
|
||||
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
let cryptoSvc;
|
||||
try {
|
||||
Components.utils.import("resource://services-crypto/WeaveCrypto.js");
|
||||
cryptoSvc = new WeaveCrypto();
|
||||
} catch (ex) {
|
||||
// Fallback to binary WeaveCrypto
|
||||
cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"]
|
||||
.getService(Ci.IWeaveCrypto);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
var salt = cryptoSvc.generateRandomBytes(16);
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
var symKey = cryptoSvc.generateRandomKey();
|
||||
|
||||
// Tests with a 2048 bit key (the default)
|
||||
do_check_eq(cryptoSvc.keypairBits, 2048)
|
||||
var pubOut = {};
|
||||
var privOut = {};
|
||||
cryptoSvc.generateKeypair("old passphrase", salt, iv, pubOut, privOut);
|
||||
var pubKey = pubOut.value;
|
||||
var privKey = privOut.value;
|
||||
|
||||
// do some key wrapping
|
||||
var wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
|
||||
var unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
|
||||
"old passphrase", salt, iv);
|
||||
|
||||
// Is our unwrapped key the same thing we started with?
|
||||
do_check_eq(unwrappedKey, symKey);
|
||||
|
||||
// Rewrap key with a new passphrase
|
||||
var newPrivKey = cryptoSvc.rewrapPrivateKey(privKey, "old passphrase",
|
||||
salt, iv, "new passphrase");
|
||||
|
||||
// Unwrap symkey with new symkey
|
||||
var newUnwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, newPrivKey,
|
||||
"new passphrase", salt, iv);
|
||||
|
||||
// The acid test... Is this unwrapped symkey the same as before?
|
||||
do_check_eq(newUnwrappedKey, unwrappedKey);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
let cryptoSvc;
|
||||
try {
|
||||
Components.utils.import("resource://services-crypto/WeaveCrypto.js");
|
||||
cryptoSvc = new WeaveCrypto();
|
||||
} catch (ex) {
|
||||
// Fallback to binary WeaveCrypto
|
||||
cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"]
|
||||
.getService(Ci.IWeaveCrypto);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
var salt = cryptoSvc.generateRandomBytes(16);
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
|
||||
// Tests with a 2048 bit key (the default)
|
||||
do_check_eq(cryptoSvc.keypairBits, 2048)
|
||||
var privOut = {};
|
||||
cryptoSvc.generateKeypair("passphrase", salt, iv, {}, privOut);
|
||||
var privKey = privOut.value;
|
||||
|
||||
// Check with correct passphrase
|
||||
var shouldBeTrue = cryptoSvc.verifyPassphrase(privKey, "passphrase",
|
||||
salt, iv);
|
||||
do_check_eq(shouldBeTrue, true);
|
||||
|
||||
// Check with incorrect passphrase
|
||||
var shouldBeFalse = cryptoSvc.verifyPassphrase(privKey, "NotPassphrase",
|
||||
salt, iv);
|
||||
do_check_eq(shouldBeFalse, false);
|
||||
}
|
|
@ -138,12 +138,6 @@ Collection.prototype = {
|
|||
// Save this because onProgress is called with this as the ChannelListener
|
||||
let coll = this;
|
||||
|
||||
// Prepare a dummyUri so that records can generate the correct
|
||||
// relative URLs. The last bit will be replaced with record.id.
|
||||
let dummyUri = this.uri.clone().QueryInterface(Ci.nsIURL);
|
||||
dummyUri.filePath += "/replaceme";
|
||||
dummyUri.query = "";
|
||||
|
||||
// Switch to newline separated records for incremental parsing
|
||||
coll.setHeader("Accept", "application/newlines");
|
||||
|
||||
|
@ -155,7 +149,7 @@ Collection.prototype = {
|
|||
this._data = this._data.slice(newline + 1);
|
||||
|
||||
// Deserialize a record from json and give it to the callback
|
||||
let record = new coll._recordObj(dummyUri);
|
||||
let record = new coll._recordObj();
|
||||
record.deserialize(json);
|
||||
onRecord(record);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@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
|
||||
|
@ -34,64 +35,90 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['CryptoWrapper', 'CryptoMeta', 'CryptoMetas'];
|
||||
const EXPORTED_SYMBOLS = ["CryptoWrapper", "CollectionKeys", "BulkKeyBundle", "SyncKeyBundle"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
|
||||
function CryptoWrapper(uri) {
|
||||
function CryptoWrapper(collection, id) {
|
||||
this.cleartext = {};
|
||||
WBORecord.call(this, uri);
|
||||
WBORecord.call(this, collection, id);
|
||||
this.ciphertext = null;
|
||||
this.id = id;
|
||||
}
|
||||
CryptoWrapper.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.CryptoWrapper",
|
||||
|
||||
get encryption() {
|
||||
return this.uri.resolve(this.payload.encryption);
|
||||
},
|
||||
set encryption(value) {
|
||||
this.payload.encryption = this.uri.getRelativeSpec(Utils.makeURI(value));
|
||||
ciphertextHMAC: function ciphertextHMAC(keyBundle) {
|
||||
let hmacKey = keyBundle.hmacKeyObject;
|
||||
if (!hmacKey)
|
||||
throw "Cannot compute HMAC with null key.";
|
||||
|
||||
return Utils.sha256HMAC(this.ciphertext, hmacKey);
|
||||
},
|
||||
|
||||
encrypt: function CryptoWrapper_encrypt(passphrase) {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
/*
|
||||
* Don't directly use the sync key. Instead, grab a key for this
|
||||
* collection, which is decrypted with the sync key.
|
||||
*
|
||||
* Cache those keys; invalidate the cache if the time on the keys collection
|
||||
* changes, or other auth events occur.
|
||||
*
|
||||
* Optional key bundle overrides the collection key lookup.
|
||||
*/
|
||||
encrypt: function encrypt(keyBundle) {
|
||||
|
||||
let meta = CryptoMetas.get(this.encryption);
|
||||
let symkey = meta.getKey(privkey, passphrase);
|
||||
keyBundle = keyBundle || CollectionKeys.keyForCollection(this.collection);
|
||||
if (!keyBundle)
|
||||
throw new Error("Key bundle is null for " + this.uri.spec);
|
||||
|
||||
this.IV = Svc.Crypto.generateRandomIV();
|
||||
this.ciphertext = Svc.Crypto.encrypt(JSON.stringify(this.cleartext),
|
||||
symkey, this.IV);
|
||||
this.hmac = Utils.sha256HMAC(this.ciphertext, symkey.hmacKey);
|
||||
keyBundle.encryptionKey, this.IV);
|
||||
this.hmac = this.ciphertextHMAC(keyBundle);
|
||||
this.cleartext = null;
|
||||
},
|
||||
|
||||
decrypt: function CryptoWrapper_decrypt(passphrase, keyUri) {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
// Optional key bundle.
|
||||
decrypt: function decrypt(keyBundle) {
|
||||
|
||||
if (!this.ciphertext) {
|
||||
throw "No ciphertext: nothing to decrypt?";
|
||||
}
|
||||
|
||||
let meta = CryptoMetas.get(keyUri);
|
||||
let symkey = meta.getKey(privkey, passphrase);
|
||||
keyBundle = keyBundle || CollectionKeys.keyForCollection(this.collection);
|
||||
if (!keyBundle)
|
||||
throw new Error("Key bundle is null for " + this.collection + "/" + this.id);
|
||||
|
||||
// Authenticate the encrypted blob with the expected HMAC
|
||||
if (Utils.sha256HMAC(this.ciphertext, symkey.hmacKey) != this.hmac)
|
||||
throw "Record SHA256 HMAC mismatch: " + this.hmac;
|
||||
let computedHMAC = this.ciphertextHMAC(keyBundle);
|
||||
|
||||
this.cleartext = JSON.parse(Svc.Crypto.decrypt(this.ciphertext, symkey,
|
||||
this.IV));
|
||||
if (computedHMAC != this.hmac) {
|
||||
throw "Record SHA256 HMAC mismatch: " + this.hmac + ", not " + computedHMAC;
|
||||
}
|
||||
|
||||
// Handle invalid data here. Elsewhere we assume that cleartext is an object.
|
||||
let json_result = JSON.parse(Svc.Crypto.decrypt(this.ciphertext,
|
||||
keyBundle.encryptionKey, this.IV));
|
||||
|
||||
if (json_result && (json_result instanceof Object)) {
|
||||
this.cleartext = json_result;
|
||||
this.ciphertext = null;
|
||||
}
|
||||
else {
|
||||
throw "Decryption failed: result is <" + json_result + ">, not an object.";
|
||||
}
|
||||
|
||||
// Verify that the encrypted id matches the requested record's id
|
||||
// Verify that the encrypted id matches the requested record's id.
|
||||
if (this.cleartext.id != this.id)
|
||||
throw "Record id mismatch: " + [this.cleartext.id, this.id];
|
||||
|
||||
|
@ -102,7 +129,8 @@ CryptoWrapper.prototype = {
|
|||
"id: " + this.id,
|
||||
"index: " + this.sortindex,
|
||||
"modified: " + this.modified,
|
||||
"payload: " + (this.deleted ? "DELETED" : JSON.stringify(this.cleartext))
|
||||
"payload: " + (this.deleted ? "DELETED" : JSON.stringify(this.cleartext)),
|
||||
"collection: " + (this.collection || "undefined")
|
||||
].join("\n ") + " }",
|
||||
|
||||
// The custom setter below masks the parent's getter, so explicitly call it :(
|
||||
|
@ -118,93 +146,321 @@ CryptoWrapper.prototype = {
|
|||
Utils.deferGetSet(CryptoWrapper, "payload", ["ciphertext", "IV", "hmac"]);
|
||||
Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted");
|
||||
|
||||
function CryptoMeta(uri) {
|
||||
WBORecord.call(this, uri);
|
||||
this.keyring = {};
|
||||
Utils.lazy(this, "CollectionKeys", CollectionKeyManager);
|
||||
|
||||
|
||||
/**
|
||||
* Keeps track of mappings between collection names ('tabs') and
|
||||
* keyStrs, which you can feed into KeyBundle to get encryption tokens.
|
||||
*
|
||||
* You can update this thing simply by giving it /info/collections. It'll
|
||||
* use the last modified time to bring itself up to date.
|
||||
*/
|
||||
function CollectionKeyManager() {
|
||||
this._lastModified = 0;
|
||||
this._collections = {};
|
||||
this._default = null;
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("CollectionKeys");
|
||||
}
|
||||
CryptoMeta.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.CryptoMeta",
|
||||
|
||||
getWrappedKey: function _getWrappedKey(privkey) {
|
||||
// get the uri to our public key
|
||||
let pubkeyUri = privkey.publicKeyUri.spec;
|
||||
// TODO: persist this locally as an Identity. Bug 610913.
|
||||
// Note that the last modified time needs to be preserved.
|
||||
CollectionKeyManager.prototype = {
|
||||
|
||||
keyForCollection: function(collection) {
|
||||
|
||||
// Moderately temporary debugging code.
|
||||
this._log.trace("keyForCollection: " + collection + ". Default is " + (this._default ? "not null." : "null."));
|
||||
|
||||
if (collection && this._collections[collection])
|
||||
return this._collections[collection];
|
||||
|
||||
return this._default;
|
||||
},
|
||||
|
||||
// each hash key is a relative uri, resolve those and match against ours
|
||||
for (let relUri in this.keyring) {
|
||||
if (pubkeyUri == this.baseUri.resolve(relUri))
|
||||
return this.keyring[relUri];
|
||||
/**
|
||||
* If `collections` (an array of strings) is provided, iterate
|
||||
* over it and generate random keys for each collection.
|
||||
*/
|
||||
generateNewKeys: function(collections) {
|
||||
let newDefaultKey = new BulkKeyBundle(null, DEFAULT_KEYBUNDLE_NAME);
|
||||
newDefaultKey.generateRandom();
|
||||
|
||||
let newColls = {};
|
||||
if (collections) {
|
||||
collections.forEach(function (c) {
|
||||
let b = new BulkKeyBundle(null, c);
|
||||
b.generateRandom();
|
||||
newColls[c] = b;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
this._default = newDefaultKey;
|
||||
this._collections = newColls;
|
||||
this._lastModified = (Math.round(Date.now()/10)/100);
|
||||
},
|
||||
|
||||
getKey: function CryptoMeta_getKey(privkey, passphrase) {
|
||||
let wrapped_key = this.getWrappedKey(privkey);
|
||||
if (!wrapped_key)
|
||||
throw "keyring doesn't contain a key for " + privkey.publicKeyUri.spec;
|
||||
|
||||
// Make sure the wrapped key hasn't been tampered with
|
||||
let localHMAC = Utils.sha256HMAC(wrapped_key.wrapped, this.hmacKey);
|
||||
if (localHMAC != wrapped_key.hmac)
|
||||
throw "Key SHA256 HMAC mismatch: " + wrapped_key.hmac;
|
||||
|
||||
// Decrypt the symmetric key and make it a String object to add properties
|
||||
let unwrappedKey = new String(
|
||||
Svc.Crypto.unwrapSymmetricKey(
|
||||
wrapped_key.wrapped,
|
||||
privkey.keyData,
|
||||
passphrase.passwordUTF8,
|
||||
privkey.salt,
|
||||
privkey.iv
|
||||
)
|
||||
);
|
||||
|
||||
unwrappedKey.hmacKey = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC,
|
||||
unwrappedKey);
|
||||
|
||||
// Cache the result after the first get and just return it
|
||||
return (this.getKey = function() unwrappedKey)();
|
||||
},
|
||||
|
||||
addKey: function CryptoMeta_addKey(new_pubkey, privkey, passphrase) {
|
||||
let symkey = this.getKey(privkey, passphrase);
|
||||
this.addUnwrappedKey(new_pubkey, symkey);
|
||||
},
|
||||
|
||||
addUnwrappedKey: function CryptoMeta_addUnwrappedKey(new_pubkey, symkey) {
|
||||
// get the new public key
|
||||
if (typeof new_pubkey == "string")
|
||||
new_pubkey = PubKeys.get(new_pubkey);
|
||||
|
||||
// each hash key is a relative uri, resolve those and
|
||||
// if we find the one we're about to add, remove it
|
||||
for (let relUri in this.keyring) {
|
||||
if (new_pubkey.uri.spec == this.uri.resolve(relUri))
|
||||
delete this.keyring[relUri];
|
||||
asWBO: function(collection, id) {
|
||||
let wbo = new CryptoWrapper(collection || "crypto", id || "keys");
|
||||
let c = {};
|
||||
for (let k in this._collections) {
|
||||
c[k] = this._collections[k].keyPair;
|
||||
}
|
||||
|
||||
// Wrap the symmetric key and generate a HMAC for it
|
||||
let wrapped = Svc.Crypto.wrapSymmetricKey(symkey, new_pubkey.keyData);
|
||||
this.keyring[this.uri.getRelativeSpec(new_pubkey.uri)] = {
|
||||
wrapped: wrapped,
|
||||
hmac: Utils.sha256HMAC(wrapped, this.hmacKey)
|
||||
wbo.cleartext = {
|
||||
"default": this._default ? this._default.keyPair : null,
|
||||
"collections": c,
|
||||
"id": id,
|
||||
"collection": collection
|
||||
};
|
||||
wbo.modified = this._lastModified;
|
||||
return wbo;
|
||||
},
|
||||
|
||||
// Take the fetched info/collections WBO, checking the change
|
||||
// time of the crypto collection.
|
||||
updateNeeded: function(info_collections) {
|
||||
|
||||
this._log.info("Testing for updateNeeded. Last modified: " + this._lastModified);
|
||||
|
||||
// No local record of modification time? Need an update.
|
||||
if (!this._lastModified)
|
||||
return true;
|
||||
|
||||
// No keys on the server? We need an update, though our
|
||||
// update handling will be a little more drastic...
|
||||
if (!("crypto" in info_collections))
|
||||
return true;
|
||||
|
||||
// Otherwise, we need an update if our modification time is stale.
|
||||
return (info_collections["crypto"] > this._lastModified);
|
||||
},
|
||||
|
||||
setContents: function setContents(payload, modified) {
|
||||
if ("collections" in payload) {
|
||||
let out_coll = {};
|
||||
let colls = payload["collections"];
|
||||
for (let k in colls) {
|
||||
let v = colls[k];
|
||||
if (v) {
|
||||
let keyObj = new BulkKeyBundle(null, k);
|
||||
keyObj.keyPair = v;
|
||||
if (keyObj) {
|
||||
out_coll[k] = keyObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._collections = out_coll;
|
||||
}
|
||||
if ("default" in payload) {
|
||||
if (payload.default) {
|
||||
let b = new BulkKeyBundle(null, DEFAULT_KEYBUNDLE_NAME);
|
||||
b.keyPair = payload.default;
|
||||
this._default = b;
|
||||
}
|
||||
else {
|
||||
this._default = null;
|
||||
}
|
||||
}
|
||||
|
||||
// The server will round the time, which can lead to us having spurious
|
||||
// key refreshes. Do the best we can to get an accurate timestamp, but
|
||||
// rounded to 2 decimal places.
|
||||
// We could use .toFixed(2), but that's a little more multiplication and
|
||||
// division...
|
||||
this._lastModified = modified || (Math.round(Date.now()/10)/100);
|
||||
return payload;
|
||||
},
|
||||
|
||||
updateContents: function updateContents(syncKeyBundle, storage_keys) {
|
||||
let log = this._log;
|
||||
log.info("Updating collection keys...");
|
||||
|
||||
// storage_keys is a WBO, fetched from storage/crypto/keys.
|
||||
// Its payload is the default key, and a map of collections to keys.
|
||||
// We lazily compute the key objects from the strings we're given.
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = storage_keys.decrypt(syncKeyBundle);
|
||||
} catch (ex) {
|
||||
log.warn("Got exception \"" + ex + "\" decrypting storage keys with sync key.");
|
||||
log.info("Aborting updateContents. Rethrowing.");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
let r = this.setContents(payload, storage_keys.modified);
|
||||
log.info("Collection keys updated.");
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abuse Identity: store the collection name (or default) in the
|
||||
* username field, and the keyStr in the password field.
|
||||
*
|
||||
* We very rarely want to override the realm, so pass null and
|
||||
* it'll default to PWDMGR_KEYBUNDLE_REALM.
|
||||
*
|
||||
* KeyBundle is the base class for two similar classes:
|
||||
*
|
||||
* SyncKeyBundle:
|
||||
*
|
||||
* A key string is provided, and it must be hashed to derive two different
|
||||
* keys (one HMAC, one AES).
|
||||
*
|
||||
* BulkKeyBundle:
|
||||
*
|
||||
* Two independent keys are provided, or randomly generated on request.
|
||||
*
|
||||
*/
|
||||
function KeyBundle(realm, collectionName, keyStr) {
|
||||
let realm = realm || PWDMGR_KEYBUNDLE_REALM;
|
||||
|
||||
if (keyStr && !keyStr.charAt)
|
||||
// Ensure it's valid.
|
||||
throw "KeyBundle given non-string key.";
|
||||
|
||||
Identity.call(this, realm, collectionName, keyStr);
|
||||
this._hmac = null;
|
||||
this._encrypt = null;
|
||||
}
|
||||
|
||||
KeyBundle.prototype = {
|
||||
__proto__: Identity.prototype,
|
||||
|
||||
/*
|
||||
* Accessors for the two keys.
|
||||
*/
|
||||
get encryptionKey() {
|
||||
return this._encrypt;
|
||||
},
|
||||
|
||||
set encryptionKey(value) {
|
||||
this._encrypt = value;
|
||||
},
|
||||
|
||||
get hmacKey() {
|
||||
let passphrase = ID.get("WeaveCryptoID").passwordUTF8;
|
||||
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, passphrase);
|
||||
return this._hmac;
|
||||
},
|
||||
|
||||
set hmacKey(value) {
|
||||
this._hmac = value;
|
||||
},
|
||||
|
||||
get hmacKeyObject() {
|
||||
if (this.hmacKey)
|
||||
return Utils.makeHMACKey(this.hmacKey);
|
||||
},
|
||||
}
|
||||
|
||||
function BulkKeyBundle(realm, collectionName) {
|
||||
let log = Log4Moz.repository.getLogger("BulkKeyBundle");
|
||||
log.info("BulkKeyBundle being created for " + collectionName);
|
||||
KeyBundle.call(this, realm, collectionName);
|
||||
}
|
||||
|
||||
BulkKeyBundle.prototype = {
|
||||
__proto__: KeyBundle.prototype,
|
||||
|
||||
generateRandom: function generateRandom() {
|
||||
let generatedHMAC = Svc.Crypto.generateRandomKey();
|
||||
let generatedEncr = Svc.Crypto.generateRandomKey();
|
||||
this.keyPair = [generatedEncr, generatedHMAC];
|
||||
},
|
||||
|
||||
get keyPair() {
|
||||
return [this._encrypt, btoa(this._hmac)];
|
||||
},
|
||||
|
||||
/*
|
||||
* Use keyPair = [enc, hmac], or generateRandom(), when
|
||||
* you want to manage the two individual keys.
|
||||
*/
|
||||
set keyPair(value) {
|
||||
if (value.length && (value.length == 2)) {
|
||||
let json = JSON.stringify(value);
|
||||
let en = value[0];
|
||||
let hm = value[1];
|
||||
|
||||
this.password = json;
|
||||
this._hmac = Utils.safeAtoB(hm);
|
||||
this._encrypt = en; // Store in base64.
|
||||
}
|
||||
else {
|
||||
throw "Invalid keypair";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function SyncKeyBundle(realm, collectionName, syncKey) {
|
||||
let log = Log4Moz.repository.getLogger("SyncKeyBundle");
|
||||
log.info("SyncKeyBundle being created for " + collectionName);
|
||||
KeyBundle.call(this, realm, collectionName, syncKey);
|
||||
if (syncKey)
|
||||
this.keyStr = syncKey; // Accessor sets up keys.
|
||||
}
|
||||
|
||||
SyncKeyBundle.prototype = {
|
||||
__proto__: KeyBundle.prototype,
|
||||
|
||||
/*
|
||||
* Use keyStr when you want to work with a key string that's
|
||||
* hashed into individual keys.
|
||||
*/
|
||||
get keyStr() {
|
||||
return this.password;
|
||||
},
|
||||
|
||||
set keyStr(value) {
|
||||
this.password = value;
|
||||
this._hmac = null;
|
||||
this._encrypt = null;
|
||||
this.generateEntry();
|
||||
},
|
||||
|
||||
/*
|
||||
* Can't rely on password being set through any of our setters:
|
||||
* Identity does work under the hood.
|
||||
*
|
||||
* Consequently, make sure we derive keys if that work hasn't already been
|
||||
* done.
|
||||
*/
|
||||
get encryptionKey() {
|
||||
if (!this._encrypt)
|
||||
this.generateEntry();
|
||||
return this._encrypt;
|
||||
},
|
||||
|
||||
get hmacKey() {
|
||||
if (!this._hmac)
|
||||
this.generateEntry();
|
||||
return this._hmac;
|
||||
},
|
||||
|
||||
/*
|
||||
* If we've got a string, hash it into keys and store them.
|
||||
*/
|
||||
generateEntry: function generateEntry() {
|
||||
let m = this.keyStr;
|
||||
if (m) {
|
||||
// Decode into a 16-byte string before we go any further.
|
||||
m = Utils.decodeKeyBase32(m);
|
||||
|
||||
// Reuse the hasher.
|
||||
let h = Utils.makeHMACHasher();
|
||||
|
||||
// First key.
|
||||
let u = this.username;
|
||||
let k1 = Utils.makeHMACKey("" + HMAC_INPUT + u + "\x01");
|
||||
let enc = Utils.sha256HMACBytes(m, k1, h);
|
||||
|
||||
// Second key: depends on the output of the first run.
|
||||
let k2 = Utils.makeHMACKey(enc + HMAC_INPUT + u + "\x02");
|
||||
let hmac = Utils.sha256HMACBytes(m, k2, h);
|
||||
|
||||
// Save them.
|
||||
this._encrypt = btoa(enc);
|
||||
this._hmac = hmac;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Utils.deferGetSet(CryptoMeta, "payload", "keyring");
|
||||
|
||||
Utils.lazy(this, 'CryptoMetas', CryptoRecordManager);
|
||||
|
||||
function CryptoRecordManager() {
|
||||
RecordManager.call(this);
|
||||
}
|
||||
CryptoRecordManager.prototype = {
|
||||
__proto__: RecordManager.prototype,
|
||||
_recordType: CryptoMeta
|
||||
};
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
/* ***** 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 Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@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 ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['PubKey', 'PrivKey',
|
||||
'PubKeys', 'PrivKeys'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PubKey(uri) {
|
||||
WBORecord.call(this, uri);
|
||||
this.type = "pubkey";
|
||||
this.keyData = null;
|
||||
}
|
||||
PubKey.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.PubKey",
|
||||
|
||||
get privateKeyUri() {
|
||||
if (!this.data)
|
||||
return null;
|
||||
|
||||
// Use the uri if it resolves, otherwise return raw (uri type unresolvable)
|
||||
let key = this.payload.privateKeyUri;
|
||||
return Utils.makeURI(this.uri.resolve(key) || key);
|
||||
},
|
||||
set privateKeyUri(value) {
|
||||
this.payload.privateKeyUri = this.uri.getRelativeSpec(Utils.makeURI(value));
|
||||
},
|
||||
|
||||
get publicKeyUri() {
|
||||
throw "attempted to get public key url from a public key!";
|
||||
}
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PubKey, "payload", ["keyData", "type"]);
|
||||
|
||||
function PrivKey(uri) {
|
||||
WBORecord.call(this, uri);
|
||||
this.type = "privkey";
|
||||
this.salt = null;
|
||||
this.iv = null;
|
||||
this.keyData = null;
|
||||
}
|
||||
PrivKey.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.PrivKey",
|
||||
|
||||
get publicKeyUri() {
|
||||
if (!this.data)
|
||||
return null;
|
||||
|
||||
// Use the uri if it resolves, otherwise return raw (uri type unresolvable)
|
||||
let key = this.payload.publicKeyUri;
|
||||
return Utils.makeURI(this.uri.resolve(key) || key);
|
||||
},
|
||||
set publicKeyUri(value) {
|
||||
this.payload.publicKeyUri = this.uri.getRelativeSpec(Utils.makeURI(value));
|
||||
},
|
||||
|
||||
get privateKeyUri() {
|
||||
throw "attempted to get private key url from a private key!";
|
||||
}
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PrivKey, "payload", ["salt", "iv", "keyData", "type"]);
|
||||
|
||||
// XXX unused/unfinished
|
||||
function SymKey(keyData, wrapped) {
|
||||
this._data = keyData;
|
||||
this._wrapped = wrapped;
|
||||
}
|
||||
SymKey.prototype = {
|
||||
get wrapped() {
|
||||
return this._wrapped;
|
||||
},
|
||||
|
||||
unwrap: function SymKey_unwrap(privkey, passphrase, meta_record) {
|
||||
this._data =
|
||||
Svc.Crypto.unwrapSymmetricKey(this._data, privkey.keyData, passphrase,
|
||||
privkey.salt, privkey.iv);
|
||||
}
|
||||
};
|
||||
|
||||
Utils.lazy(this, 'PubKeys', PubKeyManager);
|
||||
|
||||
function PubKeyManager() {
|
||||
RecordManager.call(this);
|
||||
}
|
||||
PubKeyManager.prototype = {
|
||||
__proto__: RecordManager.prototype,
|
||||
_recordType: PubKey,
|
||||
_logName: "PubKeyManager",
|
||||
|
||||
get defaultKeyUri() this._defaultKeyUri,
|
||||
set defaultKeyUri(value) { this._defaultKeyUri = value; },
|
||||
|
||||
getDefaultKey: function PubKeyManager_getDefaultKey() {
|
||||
return this.get(this.defaultKeyUri);
|
||||
},
|
||||
|
||||
createKeypair: function KeyMgr_createKeypair(passphrase, pubkeyUri, privkeyUri) {
|
||||
if (!pubkeyUri)
|
||||
throw "Missing or null parameter 'pubkeyUri'.";
|
||||
if (!privkeyUri)
|
||||
throw "Missing or null parameter 'privkeyUri'.";
|
||||
|
||||
this._log.debug("Generating RSA keypair");
|
||||
let pubkey = new PubKey(pubkeyUri);
|
||||
let privkey = new PrivKey(privkeyUri);
|
||||
privkey.salt = Svc.Crypto.generateRandomBytes(16);
|
||||
privkey.iv = Svc.Crypto.generateRandomIV();
|
||||
|
||||
let pub = {}, priv = {};
|
||||
Svc.Crypto.generateKeypair(passphrase.passwordUTF8, privkey.salt,
|
||||
privkey.iv, pub, priv);
|
||||
[pubkey.keyData, privkey.keyData] = [pub.value, priv.value];
|
||||
|
||||
pubkey.privateKeyUri = privkeyUri;
|
||||
privkey.publicKeyUri = pubkeyUri;
|
||||
|
||||
this._log.debug("Generating RSA keypair... done");
|
||||
return {pubkey: pubkey, privkey: privkey};
|
||||
},
|
||||
|
||||
uploadKeypair: function PubKeyManager_uploadKeypair(keys) {
|
||||
for each (let key in keys)
|
||||
new Resource(key.uri).put(key);
|
||||
}
|
||||
};
|
||||
|
||||
Utils.lazy(this, 'PrivKeys', PrivKeyManager);
|
||||
|
||||
function PrivKeyManager() {
|
||||
PubKeyManager.call(this);
|
||||
}
|
||||
PrivKeyManager.prototype = {
|
||||
__proto__: PubKeyManager.prototype,
|
||||
_recordType: PrivKey,
|
||||
_logName: "PrivKeyManager"
|
||||
};
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@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
|
||||
|
@ -45,44 +46,51 @@ Cu.import("resource://services-sync/log4moz.js");
|
|||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function WBORecord(uri) {
|
||||
if (uri == null)
|
||||
throw "WBOs must have a URI!";
|
||||
|
||||
function WBORecord(collection, id) {
|
||||
this.data = {};
|
||||
this.payload = {};
|
||||
this.uri = uri;
|
||||
this.collection = collection; // Optional.
|
||||
this.id = id; // Optional.
|
||||
}
|
||||
WBORecord.prototype = {
|
||||
_logName: "Record.WBO",
|
||||
|
||||
// NOTE: baseUri must have a trailing slash, or baseUri.resolve() will omit
|
||||
// the collection name
|
||||
get uri() {
|
||||
return Utils.makeURL(this.baseUri.resolve(encodeURI(this.id)));
|
||||
},
|
||||
set uri(value) {
|
||||
if (typeof(value) != "string")
|
||||
value = value.spec;
|
||||
let parts = value.split('/');
|
||||
this.id = parts.pop();
|
||||
this.baseUri = Utils.makeURI(parts.join('/') + '/');
|
||||
},
|
||||
|
||||
get sortindex() {
|
||||
if (this.data.sortindex)
|
||||
return this.data.sortindex;
|
||||
return 0;
|
||||
},
|
||||
|
||||
// Get thyself from your URI, then deserialize.
|
||||
// Set thine 'response' field.
|
||||
fetch: function fetch(uri) {
|
||||
let r = new Resource(uri).get();
|
||||
if (r.success) {
|
||||
this.deserialize(r); // Warning! Muffles exceptions!
|
||||
}
|
||||
this.response = r;
|
||||
return this;
|
||||
},
|
||||
|
||||
upload: function upload(uri) {
|
||||
return new Resource(uri).put(this);
|
||||
},
|
||||
|
||||
// Take a base URI string, with trailing slash, and return the URI of this
|
||||
// WBO based on collection and ID.
|
||||
uri: function(base) {
|
||||
if (this.collection && this.id)
|
||||
return Utils.makeURL(base + this.collection + "/" + this.id);
|
||||
return null;
|
||||
},
|
||||
|
||||
deserialize: function deserialize(json) {
|
||||
this.data = json.constructor.toString() == String ? JSON.parse(json) : json;
|
||||
|
||||
try {
|
||||
// The payload is likely to be JSON, but if not, keep it as a string
|
||||
this.payload = JSON.parse(this.payload);
|
||||
}
|
||||
catch(ex) {}
|
||||
} catch(ex) {}
|
||||
},
|
||||
|
||||
toJSON: function toJSON() {
|
||||
|
@ -128,8 +136,7 @@ RecordManager.prototype = {
|
|||
record.deserialize(this.response);
|
||||
|
||||
return this.set(url, record);
|
||||
}
|
||||
catch(ex) {
|
||||
} catch(ex) {
|
||||
this._log.debug("Failed to import record: " + Utils.exceptionStr(ex));
|
||||
return null;
|
||||
}
|
||||
|
@ -143,12 +150,12 @@ RecordManager.prototype = {
|
|||
return this.import(url);
|
||||
},
|
||||
|
||||
set: function RegordMgr_set(url, record) {
|
||||
set: function RecordMgr_set(url, record) {
|
||||
let spec = url.spec ? url.spec : url;
|
||||
return this._records[spec] = record;
|
||||
},
|
||||
|
||||
contains: function RegordMgr_contains(url) {
|
||||
contains: function RecordMgr_contains(url) {
|
||||
if ((url.spec || url) in this._records)
|
||||
return true;
|
||||
return false;
|
||||
|
@ -158,7 +165,7 @@ RecordManager.prototype = {
|
|||
this._records = {};
|
||||
},
|
||||
|
||||
del: function RegordMgr_del(url) {
|
||||
del: function RecordMgr_del(url) {
|
||||
delete this._records[url];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@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
|
||||
|
@ -44,7 +45,7 @@ WEAVE_ID: "@weave_id@",
|
|||
// Version of the data format this client supports. The data format describes
|
||||
// how records are packaged; this is separate from the Server API version and
|
||||
// the per-engine cleartext formats.
|
||||
STORAGE_VERSION: 3,
|
||||
STORAGE_VERSION: 4,
|
||||
|
||||
UPDATED_DEV_URL: "https://services.mozilla.com/sync/updated/?version=@weave_version@&channel=@xpi_type@",
|
||||
UPDATED_REL_URL: "http://www.mozilla.com/firefox/sync/updated.html",
|
||||
|
@ -55,6 +56,19 @@ PREFS_BRANCH: "services.sync.",
|
|||
PWDMGR_HOST: "chrome://weave",
|
||||
PWDMGR_PASSWORD_REALM: "Mozilla Services Password",
|
||||
PWDMGR_PASSPHRASE_REALM: "Mozilla Services Encryption Passphrase",
|
||||
PWDMGR_KEYBUNDLE_REALM: "Mozilla Services Key Bundles",
|
||||
|
||||
// Put in [] because those aren't allowed in a collection name.
|
||||
DEFAULT_KEYBUNDLE_NAME: "[default]",
|
||||
|
||||
// Our extra input to SHA256-HMAC in generateEntry.
|
||||
// This includes the full crypto spec; change this when our algo changes.
|
||||
HMAC_INPUT: "Sync-AES_256_CBC-HMAC256",
|
||||
|
||||
// Key dimensions.
|
||||
SYNC_KEY_ENCODED_LENGTH: 26,
|
||||
SYNC_KEY_DECODED_LENGTH: 16,
|
||||
SYNC_KEY_HYPHENATED_LENGTH: 31, // 26 chars, 5 hyphens.
|
||||
|
||||
// Sync intervals for various clients configurations
|
||||
SINGLE_USER_SYNC: 24 * 60 * 60 * 1000, // 1 day
|
||||
|
@ -114,9 +128,6 @@ LOGIN_FAILED_LOGIN_REJECTED: "error.login.reason.account",
|
|||
METARECORD_DOWNLOAD_FAIL: "error.sync.reason.metarecord_download_fail",
|
||||
VERSION_OUT_OF_DATE: "error.sync.reason.version_out_of_date",
|
||||
DESKTOP_VERSION_OUT_OF_DATE: "error.sync.reason.desktop_version_out_of_date",
|
||||
KEYS_DOWNLOAD_FAIL: "error.sync.reason.keys_download_fail",
|
||||
NO_KEYS_NO_KEYGEN: "error.sync.reason.no_keys_no_keygen",
|
||||
KEYS_UPLOAD_FAIL: "error.sync.reason.keys_upload_fail",
|
||||
SETUP_FAILED_NO_PASSPHRASE: "error.sync.reason.setup_failed_no_passphrase",
|
||||
CREDENTIALS_CHANGED: "error.sync.reason.credentials_changed",
|
||||
ABORT_SYNC_COMMAND: "aborting sync, process commands said so",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* Dan Mills <thunder@mozilla.com>
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
* Philipp von Weitershausen <philipp@weitershausen.de>
|
||||
* 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
|
||||
|
@ -45,7 +46,6 @@ const Cu = Components.utils;
|
|||
|
||||
Cu.import("resource://services-sync/base_records/collection.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
|
@ -81,8 +81,11 @@ EngineManagerSvc.prototype = {
|
|||
}
|
||||
|
||||
let engine = this._engines[name];
|
||||
if (!engine)
|
||||
if (!engine) {
|
||||
this._log.debug("Could not get engine: " + name);
|
||||
if (Object.keys)
|
||||
this._log.debug("Engines are: " + JSON.stringify(Object.keys(this._engines)));
|
||||
}
|
||||
return engine;
|
||||
},
|
||||
getAll: function EngMgr_getAll() {
|
||||
|
@ -294,7 +297,7 @@ SyncEngine.prototype = {
|
|||
|
||||
get engineURL() this.storageURL + this.name,
|
||||
|
||||
get cryptoMetaURL() this.storageURL + "crypto/" + this.name,
|
||||
get cryptoKeysURL() this.storageURL + "crypto/keys",
|
||||
|
||||
get metaURL() this.storageURL + "meta/global",
|
||||
|
||||
|
@ -348,49 +351,28 @@ SyncEngine.prototype = {
|
|||
|
||||
// Create a new record using the store and add in crypto fields
|
||||
_createRecord: function SyncEngine__createRecord(id) {
|
||||
let record = this._store.createRecord(id, this.engineURL + "/" + id);
|
||||
let record = this._store.createRecord(id, this.name);
|
||||
record.id = id;
|
||||
record.encryption = this.cryptoMetaURL;
|
||||
record.collection = this.name;
|
||||
return record;
|
||||
},
|
||||
|
||||
// Any setup that needs to happen at the beginning of each sync.
|
||||
// Makes sure crypto records and keys are all set-up
|
||||
_syncStartup: function SyncEngine__syncStartup() {
|
||||
this._log.trace("Ensuring server crypto records are there");
|
||||
|
||||
// Try getting/unwrapping the crypto record
|
||||
let meta = CryptoMetas.get(this.cryptoMetaURL);
|
||||
if (meta) {
|
||||
try {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
meta.getKey(privkey, ID.get("WeaveCryptoID"));
|
||||
}
|
||||
catch(ex) {
|
||||
// Indicate that we don't have a cryptometa to delete and reupload
|
||||
this._log.debug("Purging bad data after failed unwrap crypto: " + ex);
|
||||
meta = null;
|
||||
}
|
||||
}
|
||||
// Don't proceed if we failed to get the crypto meta for reasons not 404
|
||||
else if (CryptoMetas.response.status != 404) {
|
||||
let resp = CryptoMetas.response;
|
||||
resp.failureCode = ENGINE_METARECORD_DOWNLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
|
||||
// Determine if we need to wipe on outdated versions
|
||||
let metaGlobal = Records.get(this.metaURL);
|
||||
let engines = metaGlobal.payload.engines || {};
|
||||
let engineData = engines[this.name] || {};
|
||||
|
||||
let needsWipe = false;
|
||||
|
||||
// Assume missing versions are 0 and wipe the server
|
||||
if ((engineData.version || 0) < this.version) {
|
||||
this._log.debug("Old engine data: " + [engineData.version, this.version]);
|
||||
|
||||
// Prepare to clear the server and upload everything
|
||||
meta = null;
|
||||
needsWipe = true;
|
||||
this.syncID = "";
|
||||
|
||||
// Set the newer version and newly generated syncID
|
||||
|
@ -415,34 +397,21 @@ SyncEngine.prototype = {
|
|||
this._resetClient();
|
||||
};
|
||||
|
||||
// Delete any existing data and reupload on bad version or missing meta
|
||||
if (meta == null) {
|
||||
// Delete any existing data and reupload on bad version or missing meta.
|
||||
// No crypto component here...? We could regenerate per-collection keys...
|
||||
if (needsWipe) {
|
||||
this.wipeServer(true);
|
||||
|
||||
// Generate a new crypto record
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
meta = new CryptoMeta(this.cryptoMetaURL);
|
||||
meta.addUnwrappedKey(pubkey, symkey);
|
||||
let res = new Resource(meta.uri);
|
||||
let resp = res.put(meta);
|
||||
if (!resp.success) {
|
||||
this._log.debug("Metarecord upload fail:" + resp);
|
||||
resp.failureCode = ENGINE_METARECORD_UPLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
|
||||
// Cache the cryto meta that we just put on the server
|
||||
CryptoMetas.set(meta.uri, meta);
|
||||
}
|
||||
|
||||
this._maybeLastSyncLocal = Date.now();
|
||||
// Save objects that need to be uploaded in this._modified. We also save
|
||||
// the timestamp of this fetch in this.lastSyncLocal. As we successfully
|
||||
// upload objects we remove them from this._modified. If an error occurs
|
||||
// or any objects fail to upload, they will remain in this._modified. At
|
||||
// the end of a sync, or after an error, we add all objects remaining in
|
||||
// this._modified to the tracker.
|
||||
this.lastSyncLocal = Date.now();
|
||||
if (this.lastSync) {
|
||||
this._modified = this.getChangedIDs();
|
||||
// Clear the tracker now but remember the changed IDs in case we
|
||||
// need to roll back.
|
||||
this._backupChangedIDs = this._tracker.changedIDs;
|
||||
this._tracker.clearChangedIDs();
|
||||
} else {
|
||||
// Mark all items to be uploaded, but treat them as changed from long ago
|
||||
this._log.debug("First sync, uploading all items");
|
||||
|
@ -450,9 +419,15 @@ SyncEngine.prototype = {
|
|||
for (let id in this._store.getAllIDs())
|
||||
this._modified[id] = 0;
|
||||
}
|
||||
|
||||
let outnum = [i for (i in this._modified)].length;
|
||||
this._log.info(outnum + " outgoing items pre-reconciliation");
|
||||
// Clear the tracker now. If the sync fails we'll add the ones we failed
|
||||
// to upload back.
|
||||
this._tracker.clearChangedIDs();
|
||||
|
||||
// Array of just the IDs from this._modified. This is what we iterate over
|
||||
// so we can modify this._modified during the iteration.
|
||||
this._modifiedIDs = [id for (id in this._modified)];
|
||||
this._log.info(this._modifiedIDs.length +
|
||||
" outgoing items pre-reconciliation");
|
||||
|
||||
// Keep track of what to delete at the end of sync
|
||||
this._delete = {};
|
||||
|
@ -479,17 +454,14 @@ SyncEngine.prototype = {
|
|||
if (this.lastModified == null || item.modified > this.lastModified)
|
||||
this.lastModified = item.modified;
|
||||
|
||||
// Track the collection for the WBO.
|
||||
item.collection = this.name;
|
||||
|
||||
// Remember which records were processed
|
||||
handled.push(item.id);
|
||||
|
||||
try {
|
||||
// Short-circuit the key URI to the engine's one in case the WBO's
|
||||
// might be wrong due to relative URI confusions (bug 600995).
|
||||
try {
|
||||
item.decrypt(ID.get("WeaveCryptoID"), this.cryptoMetaURL);
|
||||
} catch (ex) {
|
||||
item.decrypt(ID.get("WeaveCryptoID"), item.encryption);
|
||||
}
|
||||
item.decrypt();
|
||||
if (this._reconcile(item)) {
|
||||
count.applied++;
|
||||
this._tracker.ignoreAll = true;
|
||||
|
@ -622,7 +594,7 @@ SyncEngine.prototype = {
|
|||
if (item.id in this._modified) {
|
||||
// If the incoming and local changes are the same, skip
|
||||
if (this._isEqual(item)) {
|
||||
this._tracker.removeChangedID(item.id);
|
||||
delete this._modified[item.id];
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -654,10 +626,9 @@ SyncEngine.prototype = {
|
|||
|
||||
// Upload outgoing records
|
||||
_uploadOutgoing: function SyncEngine__uploadOutgoing() {
|
||||
let failed = {};
|
||||
let outnum = [i for (i in this._modified)].length;
|
||||
if (outnum) {
|
||||
this._log.trace("Preparing " + outnum + " outgoing records");
|
||||
if (this._modifiedIDs.length) {
|
||||
this._log.trace("Preparing " + this._modifiedIDs.length +
|
||||
" outgoing records");
|
||||
|
||||
// collection we'll upload
|
||||
let up = new Collection(this.engineURL);
|
||||
|
@ -665,7 +636,8 @@ SyncEngine.prototype = {
|
|||
|
||||
// Upload what we've got so far in the collection
|
||||
let doUpload = Utils.bind2(this, function(desc) {
|
||||
this._log.info("Uploading " + desc + " of " + outnum + " records");
|
||||
this._log.info("Uploading " + desc + " of " +
|
||||
this._modifiedIDs.length + " records");
|
||||
let resp = up.post();
|
||||
if (!resp.success) {
|
||||
this._log.debug("Uploading records failed: " + resp);
|
||||
|
@ -678,28 +650,27 @@ SyncEngine.prototype = {
|
|||
if (modified > this.lastSync)
|
||||
this.lastSync = modified;
|
||||
|
||||
// Remember changed IDs and timestamp of failed items so we
|
||||
// can mark them changed again.
|
||||
let failed_ids = [];
|
||||
for (let id in resp.obj.failed) {
|
||||
failed[id] = this._modified[id];
|
||||
failed_ids.push(id);
|
||||
}
|
||||
let failed_ids = [id for (id in resp.obj.failed)];
|
||||
if (failed_ids.length)
|
||||
this._log.debug("Records that will be uploaded again because "
|
||||
+ "the server couldn't store them: "
|
||||
+ failed_ids.join(", "));
|
||||
|
||||
// Clear successfully uploaded objects.
|
||||
for each (let id in resp.obj.success) {
|
||||
delete this._modified[id];
|
||||
}
|
||||
|
||||
up.clearRecords();
|
||||
});
|
||||
|
||||
for (let id in this._modified) {
|
||||
for each (let id in this._modifiedIDs) {
|
||||
try {
|
||||
let out = this._createRecord(id);
|
||||
if (this._log.level <= Log4Moz.Level.Trace)
|
||||
this._log.trace("Outgoing: " + out);
|
||||
|
||||
out.encrypt(ID.get("WeaveCryptoID"));
|
||||
out.encrypt();
|
||||
up.pushData(out);
|
||||
}
|
||||
catch(ex) {
|
||||
|
@ -717,16 +688,6 @@ SyncEngine.prototype = {
|
|||
if (count % MAX_UPLOAD_RECORDS > 0)
|
||||
doUpload(count >= MAX_UPLOAD_RECORDS ? "last batch" : "all");
|
||||
}
|
||||
|
||||
// Update local timestamp.
|
||||
this.lastSyncLocal = this._maybeLastSyncLocal;
|
||||
delete this._modified;
|
||||
delete this._backupChangedIDs;
|
||||
|
||||
// Mark failed WBOs as changed again so they are reuploaded next time.
|
||||
for (let id in failed) {
|
||||
this._tracker.addChangedID(id, failed[id]);
|
||||
}
|
||||
},
|
||||
|
||||
// Any cleanup necessary.
|
||||
|
@ -758,13 +719,16 @@ SyncEngine.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_rollback: function _rollback() {
|
||||
if (!this._backupChangedIDs)
|
||||
_syncCleanup: function _syncCleanup() {
|
||||
if (!this._modified)
|
||||
return;
|
||||
|
||||
for (let [id, when] in Iterator(this._backupChangedIDs)) {
|
||||
// Mark failed WBOs as changed again so they are reuploaded next time.
|
||||
for (let [id, when] in Iterator(this._modified)) {
|
||||
this._tracker.addChangedID(id, when);
|
||||
}
|
||||
delete this._modified;
|
||||
delete this._modifiedIDs;
|
||||
},
|
||||
|
||||
_sync: function SyncEngine__sync() {
|
||||
|
@ -775,11 +739,8 @@ SyncEngine.prototype = {
|
|||
Observers.notify("weave:engine:sync:status", "upload-outgoing");
|
||||
this._uploadOutgoing();
|
||||
this._syncFinish();
|
||||
}
|
||||
catch (e) {
|
||||
this._rollback();
|
||||
this._log.warn("Sync failed");
|
||||
throw e;
|
||||
} finally {
|
||||
this._syncCleanup();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -792,9 +753,8 @@ SyncEngine.prototype = {
|
|||
test.limit = 1;
|
||||
test.sort = "newest";
|
||||
test.full = true;
|
||||
let self = this;
|
||||
test.recordHandler = function(record) {
|
||||
record.decrypt(ID.get("WeaveCryptoID"), self.cryptoMetaURL);
|
||||
record.decrypt();
|
||||
canDecrypt = true;
|
||||
};
|
||||
|
||||
|
@ -814,10 +774,8 @@ SyncEngine.prototype = {
|
|||
this.resetLastSync();
|
||||
},
|
||||
|
||||
wipeServer: function wipeServer(ignoreCrypto) {
|
||||
wipeServer: function wipeServer() {
|
||||
new Resource(this.engineURL).delete();
|
||||
if (!ignoreCrypto)
|
||||
new Resource(this.cryptoMetaURL).delete();
|
||||
this._resetClient();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -757,11 +757,11 @@ BookmarksStore.prototype = {
|
|||
},
|
||||
|
||||
// Create a record starting from the weave id (places guid)
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let placeId = idForGUID(guid);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let placeId = idForGUID(id);
|
||||
let record;
|
||||
if (placeId <= 0) { // deleted item
|
||||
record = new PlacesItem(uri);
|
||||
record = new PlacesItem(collection, id);
|
||||
record.deleted = true;
|
||||
return record;
|
||||
}
|
||||
|
@ -771,14 +771,14 @@ BookmarksStore.prototype = {
|
|||
case this._bms.TYPE_BOOKMARK:
|
||||
let bmkUri = this._bms.getBookmarkURI(placeId).spec;
|
||||
if (this._ms && this._ms.hasMicrosummary(placeId)) {
|
||||
record = new BookmarkMicsum(uri);
|
||||
record = new BookmarkMicsum(collection, id);
|
||||
let micsum = this._ms.getMicrosummary(placeId);
|
||||
record.generatorUri = micsum.generator.uri.spec; // breaks local generators
|
||||
record.staticTitle = this._getStaticTitle(placeId);
|
||||
}
|
||||
else {
|
||||
if (bmkUri.search(/^place:/) == 0) {
|
||||
record = new BookmarkQuery(uri);
|
||||
record = new BookmarkQuery(collection, id);
|
||||
|
||||
// Get the actual tag name instead of the local itemId
|
||||
let folder = bmkUri.match(/[:&]folder=(\d+)/);
|
||||
|
@ -793,7 +793,7 @@ BookmarksStore.prototype = {
|
|||
catch(ex) {}
|
||||
}
|
||||
else
|
||||
record = new Bookmark(uri);
|
||||
record = new Bookmark(collection, id);
|
||||
record.title = this._bms.getItemTitle(placeId);
|
||||
}
|
||||
|
||||
|
@ -807,7 +807,7 @@ BookmarksStore.prototype = {
|
|||
|
||||
case this._bms.TYPE_FOLDER:
|
||||
if (this._ls.isLivemark(placeId)) {
|
||||
record = new Livemark(uri);
|
||||
record = new Livemark(collection, id);
|
||||
|
||||
let siteURI = this._ls.getSiteURI(placeId);
|
||||
if (siteURI != null)
|
||||
|
@ -815,7 +815,7 @@ BookmarksStore.prototype = {
|
|||
record.feedUri = this._ls.getFeedURI(placeId).spec;
|
||||
|
||||
} else {
|
||||
record = new BookmarkFolder(uri);
|
||||
record = new BookmarkFolder(collection, id);
|
||||
}
|
||||
|
||||
record.parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
|
@ -824,19 +824,19 @@ BookmarksStore.prototype = {
|
|||
break;
|
||||
|
||||
case this._bms.TYPE_SEPARATOR:
|
||||
record = new BookmarkSeparator(uri);
|
||||
record = new BookmarkSeparator(collection, id);
|
||||
// Create a positioning identifier for the separator
|
||||
record.parentName = Svc.Bookmark.getItemTitle(parent);
|
||||
record.pos = Svc.Bookmark.getItemIndex(placeId);
|
||||
break;
|
||||
|
||||
case this._bms.TYPE_DYNAMIC_CONTAINER:
|
||||
record = new PlacesItem(uri);
|
||||
record = new PlacesItem(collection, id);
|
||||
this._log.warn("Don't know how to serialize dynamic containers yet");
|
||||
break;
|
||||
|
||||
default:
|
||||
record = new PlacesItem(uri);
|
||||
record = new PlacesItem(collection, id);
|
||||
this._log.warn("Unknown item type, cannot serialize: " +
|
||||
this._bms.getItemType(placeId));
|
||||
}
|
||||
|
|
|
@ -180,17 +180,17 @@ ClientStore.prototype = {
|
|||
this._remoteClients[record.id] = record.cleartext;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let record = new ClientsRec(uri);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let record = new ClientsRec(collection, id);
|
||||
|
||||
// Package the individual components into a record for the local client
|
||||
if (guid == Clients.localID) {
|
||||
if (id == Clients.localID) {
|
||||
record.name = Clients.localName;
|
||||
record.type = Clients.localType;
|
||||
record.commands = Clients.localCommands;
|
||||
}
|
||||
else
|
||||
record.cleartext = this._remoteClients[guid];
|
||||
record.cleartext = this._remoteClients[id];
|
||||
|
||||
return record;
|
||||
},
|
||||
|
|
|
@ -163,9 +163,9 @@ FormStore.prototype = {
|
|||
return FormWrapper.hasGUID(id);
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let record = new FormRec(uri);
|
||||
let entry = FormWrapper.getEntry(guid);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let record = new FormRec(collection, id);
|
||||
let entry = FormWrapper.getEntry(id);
|
||||
if (entry != null) {
|
||||
record.name = entry.name;
|
||||
record.value = entry.value
|
||||
|
|
|
@ -40,7 +40,7 @@ const Cc = Components.classes;
|
|||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const GUID_ANNO = "weave/guid";
|
||||
const GUID_ANNO = "sync/guid";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
|
@ -383,9 +383,9 @@ HistoryStore.prototype = {
|
|||
return url ? this._hsvc.isVisited(url) : false;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let foo = this._findURLByGUID(guid);
|
||||
let record = new HistoryRec(uri);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let foo = this._findURLByGUID(id);
|
||||
let record = new HistoryRec(collection, id);
|
||||
if (foo) {
|
||||
record.histUri = foo.url;
|
||||
record.title = foo.title;
|
||||
|
|
|
@ -169,9 +169,9 @@ PasswordStore.prototype = {
|
|||
return false;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let record = new LoginRec(uri);
|
||||
let login = this._getLoginFromGUID(guid);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let record = new LoginRec(collection, id);
|
||||
let login = this._getLoginFromGUID(id);
|
||||
|
||||
if (login) {
|
||||
record.hostname = login.hostname;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
* Philipp von Weitershausen <philipp@weitershausen.de>
|
||||
*
|
||||
* 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
|
||||
|
@ -41,13 +42,15 @@ const Ci = Components.interfaces;
|
|||
const Cu = Components.utils;
|
||||
|
||||
const WEAVE_SYNC_PREFS = "services.sync.prefs.sync.";
|
||||
const WEAVE_PREFS_GUID = "preferences";
|
||||
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/type_records/prefs.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/ext/Preferences.js");
|
||||
|
||||
const PREFS_GUID = Utils.encodeBase64url(Svc.AppInfo.ID);
|
||||
|
||||
function PrefsEngine() {
|
||||
SyncEngine.call(this, "Prefs");
|
||||
|
@ -57,6 +60,15 @@ PrefsEngine.prototype = {
|
|||
_storeObj: PrefStore,
|
||||
_trackerObj: PrefTracker,
|
||||
_recordObj: PrefRec,
|
||||
version: 2,
|
||||
|
||||
getChangedIDs: function getChangedIDs() {
|
||||
// No need for a proper timestamp (no conflict resolution needed).
|
||||
let changedIDs = {};
|
||||
if (this._tracker.modified)
|
||||
changedIDs[PREFS_GUID] = 0;
|
||||
return changedIDs;
|
||||
},
|
||||
|
||||
_wipeClient: function _wipeClient() {
|
||||
SyncEngine.prototype._wipeClient.call(this);
|
||||
|
@ -78,7 +90,6 @@ function PrefStore(name) {
|
|||
Store.call(this, name);
|
||||
Svc.Obs.add("profile-before-change", function() {
|
||||
this.__prefs = null;
|
||||
this.__syncPrefs = null;
|
||||
}, this);
|
||||
}
|
||||
PrefStore.prototype = {
|
||||
|
@ -87,53 +98,33 @@ PrefStore.prototype = {
|
|||
__prefs: null,
|
||||
get _prefs() {
|
||||
if (!this.__prefs)
|
||||
this.__prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch2);
|
||||
this.__prefs = new Preferences();
|
||||
return this.__prefs;
|
||||
},
|
||||
|
||||
__syncPrefs: null,
|
||||
get _syncPrefs() {
|
||||
if (!this.__syncPrefs)
|
||||
this.__syncPrefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefService).
|
||||
getBranch(WEAVE_SYNC_PREFS).
|
||||
getChildList("", {});
|
||||
return this.__syncPrefs;
|
||||
_getSyncPrefs: function _getSyncPrefs() {
|
||||
let syncPrefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService)
|
||||
.getBranch(WEAVE_SYNC_PREFS)
|
||||
.getChildList("", {});
|
||||
// Also sync preferences that determine which prefs get synced.
|
||||
return syncPrefs.concat(
|
||||
syncPrefs.map(function (pref) { return WEAVE_SYNC_PREFS + pref; }));
|
||||
},
|
||||
|
||||
_getAllPrefs: function PrefStore__getAllPrefs() {
|
||||
let values = [];
|
||||
let toSync = this._syncPrefs;
|
||||
_isSynced: function _isSyncedPref(pref) {
|
||||
return (pref.indexOf(WEAVE_SYNC_PREFS) == 0)
|
||||
|| this._prefs.get(WEAVE_SYNC_PREFS + pref, false);
|
||||
},
|
||||
|
||||
let pref;
|
||||
for (let i = 0; i < toSync.length; i++) {
|
||||
if (!this._prefs.getBoolPref(WEAVE_SYNC_PREFS + toSync[i]))
|
||||
continue;
|
||||
|
||||
pref = {};
|
||||
pref["name"] = toSync[i];
|
||||
|
||||
switch (this._prefs.getPrefType(toSync[i])) {
|
||||
case Ci.nsIPrefBranch.PREF_INT:
|
||||
pref["type"] = "int";
|
||||
pref["value"] = this._prefs.getIntPref(toSync[i]);
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_STRING:
|
||||
pref["type"] = "string";
|
||||
pref["value"] = this._prefs.getCharPref(toSync[i]);
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
||||
pref["type"] = "boolean";
|
||||
pref["value"] = this._prefs.getBoolPref(toSync[i]);
|
||||
break;
|
||||
default:
|
||||
this._log.trace("Unsupported pref type for " + toSync[i]);
|
||||
_getAllPrefs: function () {
|
||||
let values = {};
|
||||
for each (let pref in this._getSyncPrefs()) {
|
||||
if (this._isSynced(pref)) {
|
||||
// Missing prefs get the null value.
|
||||
values[pref] = this._prefs.get(pref, null);
|
||||
}
|
||||
if ("value" in pref)
|
||||
values[values.length] = pref;
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
|
@ -155,21 +146,22 @@ PrefStore.prototype = {
|
|||
} catch(ex) {
|
||||
ltmExists = false;
|
||||
} // LightweightThemeManager only exists in Firefox 3.6+
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
switch (values[i]["type"]) {
|
||||
case "int":
|
||||
this._prefs.setIntPref(values[i]["name"], values[i]["value"]);
|
||||
break;
|
||||
case "string":
|
||||
this._prefs.setCharPref(values[i]["name"], values[i]["value"]);
|
||||
break;
|
||||
case "boolean":
|
||||
this._prefs.setBoolPref(values[i]["name"], values[i]["value"]);
|
||||
break;
|
||||
default:
|
||||
this._log.trace("Unexpected preference type: " + values[i]["type"]);
|
||||
|
||||
for (let [pref, value] in Iterator(values)) {
|
||||
if (!this._isSynced(pref))
|
||||
continue;
|
||||
|
||||
// Pref has gone missing, best we can do is reset it.
|
||||
if (value == null) {
|
||||
this._prefs.reset(pref);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
this._prefs.set(pref, value);
|
||||
} catch(ex) {
|
||||
this._log.trace("Failed to set pref: " + pref + ": " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the lightweight theme manager of all the new values
|
||||
|
@ -187,7 +179,7 @@ PrefStore.prototype = {
|
|||
getAllIDs: function PrefStore_getAllIDs() {
|
||||
/* We store all prefs in just one WBO, with just one GUID */
|
||||
let allprefs = {};
|
||||
allprefs[WEAVE_PREFS_GUID] = this._getAllPrefs();
|
||||
allprefs[PREFS_GUID] = true;
|
||||
return allprefs;
|
||||
},
|
||||
|
||||
|
@ -196,13 +188,13 @@ PrefStore.prototype = {
|
|||
},
|
||||
|
||||
itemExists: function FormStore_itemExists(id) {
|
||||
return (id === WEAVE_PREFS_GUID);
|
||||
return (id === PREFS_GUID);
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let record = new PrefRec(uri);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let record = new PrefRec(collection, id);
|
||||
|
||||
if (guid == WEAVE_PREFS_GUID) {
|
||||
if (id == PREFS_GUID) {
|
||||
record.value = this._getAllPrefs();
|
||||
} else {
|
||||
record.deleted = true;
|
||||
|
@ -220,6 +212,10 @@ PrefStore.prototype = {
|
|||
},
|
||||
|
||||
update: function PrefStore_update(record) {
|
||||
// Silently ignore pref updates that are for other apps.
|
||||
if (record.id != PREFS_GUID)
|
||||
return;
|
||||
|
||||
this._log.trace("Received pref updates, applying...");
|
||||
this._setAllPrefs(record.value);
|
||||
},
|
||||
|
@ -238,49 +234,59 @@ function PrefTracker(name) {
|
|||
PrefTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
get modified() {
|
||||
return Svc.Prefs.get("engine.prefs.modified", false);
|
||||
},
|
||||
set modified(value) {
|
||||
Svc.Prefs.set("engine.prefs.modified", value);
|
||||
},
|
||||
|
||||
loadChangedIDs: function loadChangedIDs() {
|
||||
// Don't read changed IDs from disk at start up.
|
||||
},
|
||||
|
||||
clearChangedIDs: function clearChangedIDs() {
|
||||
this.modified = false;
|
||||
},
|
||||
|
||||
__prefs: null,
|
||||
get _prefs() {
|
||||
if (!this.__prefs)
|
||||
this.__prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch2);
|
||||
this.__prefs = new Preferences();
|
||||
return this.__prefs;
|
||||
},
|
||||
|
||||
__syncPrefs: null,
|
||||
get _syncPrefs() {
|
||||
if (!this.__syncPrefs)
|
||||
this.__syncPrefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefService).
|
||||
getBranch(WEAVE_SYNC_PREFS).
|
||||
getChildList("", {});
|
||||
return this.__syncPrefs;
|
||||
},
|
||||
|
||||
_enabled: false,
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "weave:engine:start-tracking":
|
||||
if (!this._enabled) {
|
||||
this._prefs.addObserver("", this, false);
|
||||
Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch2).addObserver("", this, false);
|
||||
this._enabled = true;
|
||||
}
|
||||
break;
|
||||
case "weave:engine:stop-tracking":
|
||||
if (this._enabled) {
|
||||
this._prefs.removeObserver("", this);
|
||||
if (this._enabled)
|
||||
this._enabled = false;
|
||||
}
|
||||
// Fall through to clean up.
|
||||
case "profile-before-change":
|
||||
this.__prefs = null;
|
||||
this.__syncPrefs = null;
|
||||
this._prefs.removeObserver("", this);
|
||||
Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch2).removeObserver("", this);
|
||||
break;
|
||||
case "nsPref:changed":
|
||||
// 25 points per pref change
|
||||
if (this._syncPrefs.indexOf(aData) != -1) {
|
||||
this.score += 1;
|
||||
this.addChangedID(WEAVE_PREFS_GUID);
|
||||
// 100 points for a change that determines which prefs are synced,
|
||||
// 25 points per regular pref change.
|
||||
let up;
|
||||
if (aData.indexOf(WEAVE_SYNC_PREFS) == 0)
|
||||
up = 100;
|
||||
else if (this._prefs.get(WEAVE_SYNC_PREFS + aData, false))
|
||||
up = 25;
|
||||
|
||||
if (up) {
|
||||
this.score += up;
|
||||
this.modified = true;
|
||||
this._log.trace("Preference " + aData + " changed");
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -156,8 +156,8 @@ TabStore.prototype = {
|
|||
return allTabs;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid, uri) {
|
||||
let record = new TabSetRecord(uri);
|
||||
createRecord: function createRecord(id, collection) {
|
||||
let record = new TabSetRecord(collection, id);
|
||||
record.clientName = Clients.localName;
|
||||
|
||||
// Don't provide any tabs to compare against and ignore the update later.
|
||||
|
|
|
@ -46,6 +46,13 @@ Cu.import("resource://services-sync/ext/Sync.js");
|
|||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// Avoid circular import.
|
||||
__defineGetter__("Service", function() {
|
||||
delete this.Service;
|
||||
Cu.import("resource://services-sync/service.js", this);
|
||||
return this.Service;
|
||||
});
|
||||
|
||||
Utils.lazy(this, 'ID', IDManager);
|
||||
|
||||
// For storing identities we'll use throughout Weave
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Philipp von Weitershausen <philipp@weitershausen.de>
|
||||
* 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
|
||||
|
@ -41,7 +42,8 @@ Components.utils.import("resource://services-sync/constants.js", Weave);
|
|||
let lazies = {
|
||||
"auth.js": ['Auth', 'BrokenBasicAuthenticator',
|
||||
'BasicAuthenticator', 'NoOpAuthenticator'],
|
||||
"base_records/keys.js": ['PubKey', 'PrivKey', 'PubKeys', 'PrivKeys'],
|
||||
"base_records/crypto.js":
|
||||
["CollectionKeys", "BulkKeyBundle", "SyncKeyBundle"],
|
||||
"engines.js": ['Engines', 'Engine', 'SyncEngine'],
|
||||
"engines/bookmarks.js": ['BookmarksEngine', 'BookmarksSharingManager'],
|
||||
"engines/clients.js": ["Clients"],
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* Dan Mills <thunder@mozilla.com>
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
* 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
|
||||
|
@ -50,10 +51,12 @@ const IDLE_TIME = 5; // xxxmpc: in seconds, should be preffable
|
|||
// How long before refreshing the cluster
|
||||
const CLUSTER_BACKOFF = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
// How long a key to generate from an old passphrase.
|
||||
const PBKDF2_KEY_BYTES = 16;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
|
@ -83,7 +86,6 @@ WeaveSvc.prototype = {
|
|||
_catch: Utils.catch,
|
||||
_locked: false,
|
||||
_loggedIn: false,
|
||||
keyGenEnabled: true,
|
||||
|
||||
get account() Svc.Prefs.get("account", this.username),
|
||||
set account(value) {
|
||||
|
@ -127,9 +129,10 @@ WeaveSvc.prototype = {
|
|||
get password() ID.get("WeaveID").password,
|
||||
set password(value) ID.get("WeaveID").password = value,
|
||||
|
||||
get passphrase() ID.get("WeaveCryptoID").password,
|
||||
set passphrase(value) ID.get("WeaveCryptoID").password = value,
|
||||
get passphraseUTF8() ID.get("WeaveCryptoID").passwordUTF8,
|
||||
get passphrase() ID.get("WeaveCryptoID").keyStr,
|
||||
set passphrase(value) ID.get("WeaveCryptoID").keyStr = value,
|
||||
|
||||
get syncKeyBundle() ID.get("WeaveCryptoID"),
|
||||
|
||||
get serverURL() Svc.Prefs.get("serverURL"),
|
||||
set serverURL(value) {
|
||||
|
@ -230,8 +233,7 @@ WeaveSvc.prototype = {
|
|||
this.infoURL = this.userBaseURL + "info/collections";
|
||||
this.storageURL = this.userBaseURL + "storage/";
|
||||
this.metaURL = this.storageURL + "meta/global";
|
||||
PubKeys.defaultKeyUri = this.storageURL + "keys/pubkey";
|
||||
PrivKeys.defaultKeyUri = this.storageURL + "keys/privkey";
|
||||
this.cryptoKeysURL = this.storageURL + "crypto/keys";
|
||||
},
|
||||
|
||||
_checkCrypto: function WeaveSvc__checkCrypto() {
|
||||
|
@ -291,7 +293,7 @@ WeaveSvc.prototype = {
|
|||
|
||||
if (!ID.get("WeaveCryptoID"))
|
||||
ID.set("WeaveCryptoID",
|
||||
new Identity(PWDMGR_PASSPHRASE_REALM, this.username));
|
||||
new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, this.username));
|
||||
|
||||
this._updateCachedURLs();
|
||||
|
||||
|
@ -554,6 +556,136 @@ WeaveSvc.prototype = {
|
|||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform the info fetch as part of a login or key fetch.
|
||||
*/
|
||||
_fetchInfo: function _fetchInfo(url, logout) {
|
||||
let infoURL = url || this.infoURL;
|
||||
|
||||
let info = new Resource(infoURL).get();
|
||||
if (!info.success) {
|
||||
if (info.status == 401) {
|
||||
if (logout) {
|
||||
this.logout();
|
||||
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
|
||||
}
|
||||
}
|
||||
throw "aborting sync, failed to get collections";
|
||||
}
|
||||
return info;
|
||||
},
|
||||
|
||||
verifyAndFetchSymmetricKeys: function verifyAndFetchSymmetricKeys(infoResponse) {
|
||||
|
||||
this._log.debug("Fetching and verifying -- or generating -- symmetric keys.");
|
||||
|
||||
// Don't allow empty/missing passphrase.
|
||||
// Furthermore, we assume that our sync key is already upgraded,
|
||||
// and fail if that assumption is invalidated.
|
||||
|
||||
let syncKey = this.syncKeyBundle;
|
||||
if (!syncKey) {
|
||||
this._log.error("No sync key: cannot fetch symmetric keys.");
|
||||
Status.login = LOGIN_FAILED_NO_PASSPHRASE;
|
||||
Status.sync = CREDENTIALS_CHANGED; // For want of a better option.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not sure this validation is necessary now.
|
||||
if (!Utils.isPassphrase(syncKey.keyStr)) {
|
||||
this._log.warn("Sync key input is invalid: cannot fetch symmetric keys.");
|
||||
Status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
|
||||
Status.sync = CREDENTIALS_CHANGED;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!infoResponse)
|
||||
infoResponse = this._fetchInfo(); // Will throw an exception on failure.
|
||||
|
||||
// This only applies when the server is already at version 4.
|
||||
if (infoResponse.status != 200) {
|
||||
this._log.warn("info/collections returned non-200 response. Failing key fetch.");
|
||||
Status.login = LOGIN_FAILED_SERVER_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
let infoCollections = infoResponse.obj;
|
||||
|
||||
this._log.info("Testing info/collections: " + JSON.stringify(infoCollections));
|
||||
|
||||
if (CollectionKeys.updateNeeded(infoCollections)) {
|
||||
this._log.info("CollectionKeys reports that a key update is needed.");
|
||||
|
||||
// Don't always set to CREDENTIALS_CHANGED -- we will probably take care of this.
|
||||
|
||||
// Fetch storage/crypto/keys.
|
||||
let cryptoKeys;
|
||||
|
||||
if (infoCollections && ('crypto' in infoCollections)) {
|
||||
try {
|
||||
cryptoKeys = new CryptoWrapper("crypto", "keys");
|
||||
let cryptoResp = cryptoKeys.fetch(this.cryptoKeysURL).response;
|
||||
|
||||
if (cryptoResp.success) {
|
||||
// On success, pass to CollectionKeys.
|
||||
CollectionKeys.updateContents(syncKey, cryptoKeys);
|
||||
return true;
|
||||
}
|
||||
else if (cryptoResp.status == 404) {
|
||||
// On failure, ask CollectionKeys to generate new keys and upload them.
|
||||
// Fall through to the behavior below.
|
||||
this._log.warn("Got 404 for crypto/keys, but 'crypto' in info/collections. Regenerating.");
|
||||
cryptoKeys = null;
|
||||
}
|
||||
else {
|
||||
// Some other problem.
|
||||
this._log.warn("Got status " + cryptoResp.status + " fetching crypto keys.");
|
||||
Status.login = LOGIN_FAILED_SERVER_ERROR;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
this._log.warn("Got exception \"" + ex + "\" fetching cryptoKeys.");
|
||||
// TODO: Um, what exceptions might we get here? Should we re-throw any?
|
||||
|
||||
// One kind of exception: HMAC failure.
|
||||
let hmacFail = "Record SHA256 HMAC mismatch: ";
|
||||
if (ex && ex.substr && (ex.substr(0, hmacFail.length) == hmacFail)) {
|
||||
Status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
|
||||
Status.sync = CREDENTIALS_CHANGED;
|
||||
}
|
||||
else
|
||||
// Assume that every other failure is network-related.
|
||||
Status.login = LOGIN_FAILED_NETWORK_ERROR;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._log.info("... 'crypto' is not a reported collection. Generating new keys.");
|
||||
}
|
||||
|
||||
if (!cryptoKeys) {
|
||||
// Must have got a 404, or no reported collection.
|
||||
// Better make some and upload them.
|
||||
this.generateNewSymmetricKeys();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Last-ditch case.
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// No update needed: we're good!
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
// This means no keys are present, or there's a network error.
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
verifyLogin: function verifyLogin()
|
||||
this._notify("verify-login", "", function() {
|
||||
// Make sure we have a cluster to verify against
|
||||
|
@ -566,31 +698,42 @@ WeaveSvc.prototype = {
|
|||
}
|
||||
|
||||
if (!this.username) {
|
||||
this._log.warn("No username in verifyLogin.");
|
||||
Status.login = LOGIN_FAILED_NO_USERNAME;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch collection info on every startup.
|
||||
let test = new Resource(this.infoURL).get();
|
||||
switch (test.status) {
|
||||
case 200:
|
||||
// The user is authenticated.
|
||||
|
||||
// We have no way of verifying the passphrase right now,
|
||||
// so wait until remoteSetup to do so.
|
||||
// Just make the most trivial checks.
|
||||
if (!this.passphrase) {
|
||||
this._log.warn("No passphrase in verifyLogin.");
|
||||
Status.login = LOGIN_FAILED_NO_PASSPHRASE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We also have a passphrase, so check it now.
|
||||
if (!this._verifyPassphrase()) {
|
||||
Status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
|
||||
return false;
|
||||
}
|
||||
// Go ahead and do remote setup, so that we can determine
|
||||
// conclusively that our passphrase is correct.
|
||||
if (this._remoteSetup()) {
|
||||
|
||||
// Username/password and passphrase all verified
|
||||
// Username/password verified.
|
||||
Status.login = LOGIN_SUCCEEDED;
|
||||
return true;
|
||||
}
|
||||
|
||||
this._log.warn("Remote setup failed.");
|
||||
// Remote setup must have failed.
|
||||
return false;
|
||||
|
||||
case 401:
|
||||
this._log.warn("401: login failed.");
|
||||
// Login failed. If the password contains non-ASCII characters,
|
||||
// perhaps the server password is an old low-byte only one?
|
||||
let id = ID.get('WeaveID');
|
||||
|
@ -635,36 +778,22 @@ WeaveSvc.prototype = {
|
|||
}
|
||||
})(),
|
||||
|
||||
_verifyPassphrase: function _verifyPassphrase()
|
||||
this._catch(this._notify("verify-passphrase", "", function() {
|
||||
// Don't allow empty/missing passphrase
|
||||
if (!this.passphrase)
|
||||
return false;
|
||||
|
||||
try {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
let result = Svc.Crypto.verifyPassphrase(
|
||||
privkey.payload.keyData, this.passphraseUTF8,
|
||||
privkey.payload.salt, privkey.payload.iv
|
||||
);
|
||||
if (result)
|
||||
return true;
|
||||
|
||||
// Passphrase validation failed. Perhaps because the keys are
|
||||
// based on an old low-byte only passphrase?
|
||||
result = Svc.Crypto.verifyPassphrase(
|
||||
privkey.payload.keyData, this.passphrase,
|
||||
privkey.payload.salt, privkey.payload.iv
|
||||
);
|
||||
if (result)
|
||||
this._needUpdatedKeys = true;
|
||||
return result;
|
||||
} catch (e) {
|
||||
// this means no keys are present (or there's a network error)
|
||||
return true;
|
||||
}
|
||||
}))(),
|
||||
generateNewSymmetricKeys:
|
||||
function WeaveSvc_generateNewSymmetricKeys() {
|
||||
this._log.info("Generating new keys....");
|
||||
CollectionKeys.generateNewKeys();
|
||||
let wbo = CollectionKeys.asWBO("crypto", "keys");
|
||||
this._log.info("Encrypting new key bundle. Modified time is " + wbo.modified);
|
||||
wbo.encrypt(this.syncKeyBundle);
|
||||
|
||||
this._log.info("Uploading...");
|
||||
let uploadRes = wbo.upload(this.cryptoKeysURL);
|
||||
if (uploadRes.status >= 400) {
|
||||
this._log.warn("Got status " + uploadRes.status + " uploading new keys. What to do? Throw!");
|
||||
throw new Error("Unable to upload symmetric keys.");
|
||||
}
|
||||
this._log.info("Got status " + uploadRes.status);
|
||||
},
|
||||
|
||||
changePassword: function WeaveSvc_changePassword(newpass)
|
||||
this._notify("changepwd", "", function() {
|
||||
|
@ -682,7 +811,7 @@ WeaveSvc.prototype = {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Save the new password for requests and login manager
|
||||
// Save the new password for requests and login manager.
|
||||
this.password = newpass;
|
||||
this.persistLogin();
|
||||
return true;
|
||||
|
@ -690,18 +819,16 @@ WeaveSvc.prototype = {
|
|||
|
||||
changePassphrase: function WeaveSvc_changePassphrase(newphrase)
|
||||
this._catch(this._notify("changepph", "", function() {
|
||||
/* Wipe */
|
||||
/* Wipe. */
|
||||
this.wipeServer();
|
||||
PubKeys.clearCache();
|
||||
PrivKeys.clearCache();
|
||||
|
||||
this.logout();
|
||||
|
||||
/* Set this so UI is updated on next run */
|
||||
/* Set this so UI is updated on next run. */
|
||||
this.passphrase = newphrase;
|
||||
this.persistLogin();
|
||||
|
||||
/* Login in sync: this also generates new keys */
|
||||
/* Login and sync. This also generates new keys. */
|
||||
this.login();
|
||||
this.sync(true);
|
||||
return true;
|
||||
|
@ -711,13 +838,13 @@ WeaveSvc.prototype = {
|
|||
// Set a username error so the status message shows "set up..."
|
||||
Status.login = LOGIN_FAILED_NO_USERNAME;
|
||||
this.logout();
|
||||
// Reset all engines
|
||||
// Reset all engines.
|
||||
this.resetClient();
|
||||
// Reset Weave prefs
|
||||
// Reset Weave prefs.
|
||||
this._ignorePrefObserver = true;
|
||||
Svc.Prefs.resetBranch("");
|
||||
this._ignorePrefObserver = false;
|
||||
// set lastversion pref
|
||||
|
||||
Svc.Prefs.set("lastversion", WEAVE_VERSION);
|
||||
// Find weave logins and remove them.
|
||||
this.password = "";
|
||||
|
@ -743,17 +870,17 @@ WeaveSvc.prototype = {
|
|||
Utils.mpLocked() ? "master password still locked"
|
||||
: this._checkSync([kSyncNotLoggedIn, kFirstSyncChoiceNotMade]);
|
||||
|
||||
// Can't autoconnect if we're missing these values
|
||||
// Can't autoconnect if we're missing these values.
|
||||
if (!reason) {
|
||||
if (!this.username || !this.password || !this.passphrase)
|
||||
return;
|
||||
|
||||
// Nothing more to do on a successful login
|
||||
// Nothing more to do on a successful login.
|
||||
if (this.login())
|
||||
return;
|
||||
}
|
||||
|
||||
// Something failed, so try again some time later
|
||||
// Something failed, so try again some time later.
|
||||
let interval = this._calculateBackoff(++attempts, 60 * 1000);
|
||||
this._log.debug("Autoconnect failed: " + (reason || Status.login) +
|
||||
"; retry in " + Math.ceil(interval / 1000) + " sec.");
|
||||
|
@ -761,7 +888,7 @@ WeaveSvc.prototype = {
|
|||
},
|
||||
|
||||
persistLogin: function persistLogin() {
|
||||
// Canceled master password prompt can prevent these from succeeding
|
||||
// Canceled master password prompt can prevent these from succeeding.
|
||||
try {
|
||||
ID.get("WeaveID").persist();
|
||||
ID.get("WeaveCryptoID").persist();
|
||||
|
@ -770,7 +897,8 @@ WeaveSvc.prototype = {
|
|||
},
|
||||
|
||||
login: function WeaveSvc_login(username, password, passphrase)
|
||||
this._catch(this._lock(this._notify("login", "", function() {
|
||||
this._catch(this._lock("service.js: login",
|
||||
this._notify("login", "", function() {
|
||||
this._loggedIn = false;
|
||||
if (Svc.IO.offline)
|
||||
throw "Application is offline, login should not be called";
|
||||
|
@ -799,12 +927,12 @@ WeaveSvc.prototype = {
|
|||
throw "Login failed: " + Status.login;
|
||||
}
|
||||
|
||||
// No need to try automatically connecting after a successful login
|
||||
// No need to try automatically connecting after a successful login.
|
||||
if (this._autoTimer)
|
||||
this._autoTimer.clear();
|
||||
|
||||
this._loggedIn = true;
|
||||
// Try starting the sync timer now that we're logged in
|
||||
// Try starting the sync timer now that we're logged in.
|
||||
this._checkSyncStatus();
|
||||
Svc.Prefs.set("autoconnect", true);
|
||||
|
||||
|
@ -812,14 +940,14 @@ WeaveSvc.prototype = {
|
|||
})))(),
|
||||
|
||||
logout: function WeaveSvc_logout() {
|
||||
// No need to do anything if we're already logged out
|
||||
// No need to do anything if we're already logged out.
|
||||
if (!this._loggedIn)
|
||||
return;
|
||||
|
||||
this._log.info("Logging out");
|
||||
this._loggedIn = false;
|
||||
|
||||
// Cancel the sync timer now that we're logged out
|
||||
// Cancel the sync timer now that we're logged out.
|
||||
this._checkSyncStatus();
|
||||
Svc.Prefs.set("autoconnect", false);
|
||||
|
||||
|
@ -876,7 +1004,7 @@ WeaveSvc.prototype = {
|
|||
}
|
||||
catch(ex) {}
|
||||
|
||||
// Convert to the error string, or default to generic on exception
|
||||
// Convert to the error string, or default to generic on exception.
|
||||
return this._errorStr(data);
|
||||
},
|
||||
|
||||
|
@ -927,12 +1055,12 @@ WeaveSvc.prototype = {
|
|||
return error;
|
||||
},
|
||||
|
||||
// stuff we need to to after login, before we can really do
|
||||
// anything (e.g. key setup)
|
||||
_remoteSetup: function WeaveSvc__remoteSetup() {
|
||||
// Stuff we need to do after login, before we can really do
|
||||
// anything (e.g. key setup).
|
||||
_remoteSetup: function WeaveSvc__remoteSetup(infoResponse) {
|
||||
let reset = false;
|
||||
|
||||
this._log.trace("Fetching global metadata record");
|
||||
this._log.debug("Fetching global metadata record");
|
||||
let meta = Records.get(this.metaURL);
|
||||
|
||||
let remoteVersion = (meta && meta.payload.storageVersion)?
|
||||
|
@ -961,22 +1089,18 @@ WeaveSvc.prototype = {
|
|||
if (meta && !meta.payload.syncID)
|
||||
this._log.warn("No sync id, server wipe needed");
|
||||
|
||||
if (!this.keyGenEnabled) {
|
||||
this._log.info("...and key generation is disabled. Not wiping. " +
|
||||
"Aborting sync.");
|
||||
Status.sync = DESKTOP_VERSION_OUT_OF_DATE;
|
||||
return false;
|
||||
}
|
||||
reset = true;
|
||||
|
||||
this._log.info("Wiping server data");
|
||||
this._freshStart();
|
||||
|
||||
if (status == 404)
|
||||
this._log.info("Metadata record not found, server wiped to ensure " +
|
||||
this._log.info("Metadata record not found, server was wiped to ensure " +
|
||||
"consistency.");
|
||||
else // 200
|
||||
this._log.info("Wiped server; incompatible metadata: " + remoteVersion);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (remoteVersion > STORAGE_VERSION) {
|
||||
Status.sync = VERSION_OUT_OF_DATE;
|
||||
|
@ -988,9 +1112,15 @@ WeaveSvc.prototype = {
|
|||
this.syncID = meta.payload.syncID;
|
||||
this._log.debug("Clear cached values and take syncId: " + this.syncID);
|
||||
|
||||
// XXX Bug 531005 Wait long enough to allow potentially another concurrent
|
||||
// sync to finish generating the keypair and uploading them
|
||||
Sync.sleep(15000);
|
||||
if (!this.upgradeSyncKey(meta.payload.syncID)) {
|
||||
this._log.warn("Failed to upgrade sync key. Failing remote setup.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.verifyAndFetchSymmetricKeys(infoResponse)) {
|
||||
this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// bug 545725 - re-verify creds and fail sanely
|
||||
if (!this.verifyLogin()) {
|
||||
|
@ -998,70 +1128,22 @@ WeaveSvc.prototype = {
|
|||
this._log.info("Credentials have changed, aborting sync and forcing re-login.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let needKeys = true;
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
if (!pubkey)
|
||||
this._log.debug("Could not get public key");
|
||||
else if (pubkey.keyData == null)
|
||||
this._log.debug("Public key has no key data");
|
||||
else {
|
||||
// make sure we have a matching privkey
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
if (!privkey)
|
||||
this._log.debug("Could not get private key");
|
||||
else if (privkey.keyData == null)
|
||||
this._log.debug("Private key has no key data");
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
if (needKeys) {
|
||||
if (PubKeys.response.status != 404 && PrivKeys.response.status != 404) {
|
||||
this._log.warn("Couldn't download keys from server, aborting sync");
|
||||
this._log.debug("PubKey HTTP status: " + PubKeys.response.status);
|
||||
this._log.debug("PrivKey HTTP status: " + PrivKeys.response.status);
|
||||
this._checkServerError(PubKeys.response);
|
||||
this._checkServerError(PrivKeys.response);
|
||||
Status.sync = KEYS_DOWNLOAD_FAIL;
|
||||
else {
|
||||
if (!this.upgradeSyncKey(meta.payload.syncID)) {
|
||||
this._log.warn("Failed to upgrade sync key. Failing remote setup.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.keyGenEnabled) {
|
||||
this._log.warn("Couldn't download keys from server, and key generation" +
|
||||
"is disabled. Aborting sync");
|
||||
Status.sync = NO_KEYS_NO_KEYGEN;
|
||||
if (!this.verifyAndFetchSymmetricKeys(infoResponse)) {
|
||||
this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!reset) {
|
||||
this._log.warn("Calling freshStart from !reset case.");
|
||||
this._freshStart();
|
||||
this._log.info("Server data wiped to ensure consistency due to missing keys");
|
||||
}
|
||||
|
||||
let passphrase = ID.get("WeaveCryptoID");
|
||||
if (passphrase.password) {
|
||||
let keys = PubKeys.createKeypair(passphrase, PubKeys.defaultKeyUri,
|
||||
PrivKeys.defaultKeyUri);
|
||||
try {
|
||||
// Upload and cache the keypair
|
||||
PubKeys.uploadKeypair(keys);
|
||||
PubKeys.set(keys.pubkey.uri, keys.pubkey);
|
||||
PrivKeys.set(keys.privkey.uri, keys.privkey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Status.sync = KEYS_UPLOAD_FAIL;
|
||||
this._log.error("Could not upload keys: " + Utils.exceptionStr(e));
|
||||
}
|
||||
} else {
|
||||
Status.sync = SETUP_FAILED_NO_PASSPHRASE;
|
||||
this._log.warn("Could not get encryption passphrase");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1120,9 +1202,6 @@ WeaveSvc.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._needUpdatedKeys)
|
||||
this._updateKeysToUTF8Passphrase();
|
||||
|
||||
// Only set the wait time to 0 if we need to sync right away
|
||||
let wait;
|
||||
if (this.globalScore > this.syncThreshold) {
|
||||
|
@ -1132,62 +1211,6 @@ WeaveSvc.prototype = {
|
|||
this._scheduleNextSync(wait);
|
||||
},
|
||||
|
||||
_updateKeysToUTF8Passphrase: function _updateKeysToUTF8Passphrase() {
|
||||
// Rewrap private key in UTF-8 encoded passphrase.
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
|
||||
this._log.debug("Rewrapping private key with UTF-8 encoded passphrase.");
|
||||
let oldPrivKeyData = privkey.payload.keyData;
|
||||
privkey.payload.keyData = Svc.Crypto.rewrapPrivateKey(
|
||||
oldPrivKeyData, this.passphrase,
|
||||
privkey.payload.salt, privkey.payload.iv, this.passphraseUTF8
|
||||
);
|
||||
let response = new Resource(privkey.uri).put(privkey);
|
||||
if (!response.success) {
|
||||
this._log("Uploading rewrapped private key failed!");
|
||||
this._needUpdatedKeys = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Recompute HMAC for symmetric bulk keys based on UTF-8 encoded passphrase.
|
||||
let oldHmacKey = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC,
|
||||
this.passphrase);
|
||||
let enginesToWipe = [];
|
||||
|
||||
for each (let engine in Engines.getAll()) {
|
||||
let meta = CryptoMetas.get(engine.cryptoMetaURL);
|
||||
if (!meta)
|
||||
continue;
|
||||
|
||||
this._log.debug("Recomputing HMAC for key at " + engine.cryptoMetaURL
|
||||
+ " with UTF-8 encoded passphrase.");
|
||||
for each (key in meta.keyring) {
|
||||
if (key.hmac != Utils.sha256HMAC(key.wrapped, oldHmacKey)) {
|
||||
this._log.debug("Key SHA256 HMAC mismatch! Wiping server.");
|
||||
enginesToWipe.push(engine.name);
|
||||
meta = null;
|
||||
break;
|
||||
}
|
||||
key.hmac = Utils.sha256HMAC(key.wrapped, meta.hmacKey);
|
||||
}
|
||||
|
||||
if (!meta)
|
||||
continue;
|
||||
|
||||
response = new Resource(meta.uri).put(meta);
|
||||
if (!response.success) {
|
||||
this._log.debug("Key upload failed: " + response);
|
||||
}
|
||||
}
|
||||
|
||||
if (enginesToWipe.length) {
|
||||
this._log.debug("Wiping engines " + enginesToWipe.join(", "));
|
||||
this.wipeRemote(enginesToWipe);
|
||||
}
|
||||
this._needUpdatedKeys = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Call sync() on an idle timer
|
||||
*
|
||||
|
@ -1220,6 +1243,7 @@ WeaveSvc.prototype = {
|
|||
|
||||
// Start the sync right away if we're already late
|
||||
if (interval <= 0) {
|
||||
this._log.debug("Syncing as soon as we're idle.");
|
||||
this.syncOnIdle();
|
||||
return;
|
||||
}
|
||||
|
@ -1333,7 +1357,10 @@ WeaveSvc.prototype = {
|
|||
* Sync up engines with the server.
|
||||
*/
|
||||
sync: function sync()
|
||||
this._catch(this._lock(this._notify("sync", "", function() {
|
||||
this._catch(this._lock("service.js: sync",
|
||||
this._notify("sync", "", function() {
|
||||
|
||||
this._log.info("In sync().");
|
||||
|
||||
let syncStartTime = Date.now();
|
||||
|
||||
|
@ -1364,7 +1391,7 @@ WeaveSvc.prototype = {
|
|||
// we'll handle that later
|
||||
Status.resetBackoff();
|
||||
|
||||
// Ping the server with a special info request once a day
|
||||
// Ping the server with a special info request once a day.
|
||||
let infoURL = this.infoURL;
|
||||
let now = Math.floor(Date.now() / 1000);
|
||||
let lastPing = Svc.Prefs.get("lastPing", 0);
|
||||
|
@ -1374,44 +1401,24 @@ WeaveSvc.prototype = {
|
|||
}
|
||||
|
||||
// Figure out what the last modified time is for each collection
|
||||
let info = new Resource(infoURL).get();
|
||||
if (!info.success) {
|
||||
if (info.status == 401) {
|
||||
this.logout();
|
||||
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
|
||||
}
|
||||
throw "aborting sync, failed to get collections";
|
||||
}
|
||||
|
||||
let info = this._fetchInfo(infoURL, true);
|
||||
this.globalScore = 0;
|
||||
|
||||
// Convert the response to an object and read out the modified times
|
||||
for each (let engine in [Clients].concat(Engines.getAll()))
|
||||
engine.lastModified = info.obj[engine.name] || 0;
|
||||
|
||||
// If the modified time of crypto records ever changes, clear the cache
|
||||
if (info.obj.crypto != this.cryptoModified) {
|
||||
this._log.debug("Clearing cached crypto records");
|
||||
CryptoMetas.clearCache();
|
||||
this.cryptoModified = info.obj.crypto;
|
||||
}
|
||||
|
||||
// If the modified time of keys records ever changes, clear the cache
|
||||
if (info.obj.keys != this.keysModified) {
|
||||
this._log.debug("Clearing cached keys records");
|
||||
PubKeys.clearCache();
|
||||
PrivKeys.clearCache();
|
||||
this.keysModified = info.obj.keys;
|
||||
}
|
||||
|
||||
// If the modified time of the meta record ever changes, clear the cache.
|
||||
if (info.obj.meta != this.metaModified) {
|
||||
this._log.debug("Clearing cached meta record.");
|
||||
// ... unless meta is marked as new.
|
||||
if ((info.obj.meta != this.metaModified) && !Records.get(this.metaURL).isNew) {
|
||||
this._log.debug("Clearing cached meta record. metaModified is " +
|
||||
JSON.stringify(this.metaModified) + ", setting to " +
|
||||
JSON.stringify(info.obj.meta));
|
||||
Records.del(this.metaURL);
|
||||
this.metaModified = info.obj.meta;
|
||||
}
|
||||
|
||||
if (!(this._remoteSetup()))
|
||||
if (!(this._remoteSetup(info)))
|
||||
throw "aborting sync, remote setup failed";
|
||||
|
||||
// Make sure we have an up-to-date list of clients before sending commands
|
||||
|
@ -1440,7 +1447,7 @@ WeaveSvc.prototype = {
|
|||
}
|
||||
|
||||
// Repeat remoteSetup in-case the commands forced us to reset
|
||||
if (!(this._remoteSetup()))
|
||||
if (!(this._remoteSetup(info)))
|
||||
throw "aborting sync, remote setup failed after processing commands";
|
||||
}
|
||||
finally {
|
||||
|
@ -1465,7 +1472,7 @@ WeaveSvc.prototype = {
|
|||
// Upload meta/global if any engines changed anything
|
||||
let meta = Records.get(this.metaURL);
|
||||
if (meta.isNew || meta.changed) {
|
||||
new Resource(meta.uri).put(meta);
|
||||
new Resource(this.metaURL).put(meta);
|
||||
delete meta.isNew;
|
||||
delete meta.changed;
|
||||
}
|
||||
|
@ -1573,7 +1580,7 @@ WeaveSvc.prototype = {
|
|||
Status.engines = [engine.name, e.failureCode || ENGINE_UNKNOWN_FAIL];
|
||||
|
||||
this._syncError = true;
|
||||
this._log.debug(Utils.exceptionStr(e));
|
||||
this._log.debug(engine.name + " failed: " + Utils.exceptionStr(e));
|
||||
return true;
|
||||
}
|
||||
finally {
|
||||
|
@ -1583,25 +1590,81 @@ WeaveSvc.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_freshStart: function WeaveSvc__freshStart() {
|
||||
this.resetClient();
|
||||
/**
|
||||
* Silently fixes case issues.
|
||||
*/
|
||||
syncKeyNeedsUpgrade: function syncKeyNeedsUpgrade() {
|
||||
let p = this.passphrase;
|
||||
|
||||
// Check whether it's already a key that we generated.
|
||||
if (Utils.isPassphrase(p)) {
|
||||
this._log.info("Sync key is up-to-date: no need to upgrade.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* If we have a passphrase, rather than a 25-alphadigit sync key,
|
||||
* use the provided sync ID to bootstrap it using PBKDF2.
|
||||
*
|
||||
* Store the new 'passphrase' back into the identity manager.
|
||||
*
|
||||
* We can check this as often as we want, because once it's done the
|
||||
* check will no longer succeed. It only matters that it happens after
|
||||
* we decide to bump the server storage version.
|
||||
*/
|
||||
upgradeSyncKey: function upgradeSyncKey(syncID) {
|
||||
let p = this.passphrase;
|
||||
|
||||
// Check whether it's already a key that we generated.
|
||||
if (!this.syncKeyNeedsUpgrade(p))
|
||||
return true;
|
||||
|
||||
// Otherwise, let's upgrade it.
|
||||
// N.B., we persist the sync key without testing it first...
|
||||
|
||||
let s = btoa(syncID); // It's what WeaveCrypto expects. *sigh*
|
||||
let k = Utils.derivePresentableKeyFromPassphrase(p, s, PBKDF2_KEY_BYTES); // Base 32.
|
||||
|
||||
if (!k) {
|
||||
this._log.error("No key resulted from derivePresentableKeyFromPassphrase. Failing upgrade.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this._log.info("Upgrading sync key...");
|
||||
this.passphrase = k;
|
||||
this._log.info("Saving upgraded sync key...");
|
||||
this.persistLogin();
|
||||
this._log.info("Done saving.");
|
||||
return true;
|
||||
},
|
||||
|
||||
let meta = new WBORecord(this.metaURL);
|
||||
_freshStart: function WeaveSvc__freshStart() {
|
||||
this._log.info("Fresh start. Resetting client and considering key upgrade.");
|
||||
this.resetClient();
|
||||
this.upgradeSyncKey(this.syncID);
|
||||
|
||||
let meta = new WBORecord("meta", "global");
|
||||
meta.payload.syncID = this.syncID;
|
||||
meta.payload.storageVersion = STORAGE_VERSION;
|
||||
meta.isNew = true;
|
||||
|
||||
this._log.debug("New metadata record: " + JSON.stringify(meta.payload));
|
||||
let resp = new Resource(meta.uri).put(meta);
|
||||
let resp = new Resource(this.metaURL).put(meta);
|
||||
if (!resp.success)
|
||||
throw resp;
|
||||
Records.set(meta.uri, meta);
|
||||
Records.set(this.metaURL, meta);
|
||||
|
||||
// Wipe everything we know about except meta because we just uploaded it
|
||||
let collections = [Clients].concat(Engines.getAll()).map(function(engine) {
|
||||
return engine.name;
|
||||
});
|
||||
this.wipeServer(["crypto", "keys"].concat(collections));
|
||||
this.wipeServer(collections);
|
||||
|
||||
// Generate and upload new keys. Do this last so we don't wipe them...
|
||||
this.generateNewSymmetricKeys();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1636,14 +1699,26 @@ WeaveSvc.prototype = {
|
|||
*
|
||||
* @param collections [optional]
|
||||
* Array of collections to wipe. If not given, all collections are wiped.
|
||||
*
|
||||
* @param includeKeys [optional]
|
||||
* If true, keys/pubkey and keys/privkey are deleted from the server.
|
||||
* This is false by default, which will cause the usual upgrade paths
|
||||
* to leave those keys on the server. This is to solve Bug 614737: old
|
||||
* clients check for keys *before* checking storage versions.
|
||||
*
|
||||
* Note that this parameter only has an effect if `collections` is not
|
||||
* passed. If you explicitly pass a list of collections, they will be
|
||||
* processed regardless of the value of `includeKeys`.
|
||||
*/
|
||||
wipeServer: function WeaveSvc_wipeServer(collections)
|
||||
wipeServer: function wipeServer(collections, includeKeyPairs)
|
||||
this._notify("wipe-server", "", function() {
|
||||
if (!collections) {
|
||||
collections = [];
|
||||
let info = new Resource(this.infoURL).get();
|
||||
for (let name in info.obj)
|
||||
collections.push(name);
|
||||
for (let name in info.obj) {
|
||||
if (includeKeyPairs || (name != "keys"))
|
||||
collections.push(name);
|
||||
}
|
||||
}
|
||||
for each (let name in collections) {
|
||||
let url = this.storageURL + name;
|
||||
|
@ -1652,15 +1727,6 @@ WeaveSvc.prototype = {
|
|||
throw "Aborting wipeServer. Server responded with "
|
||||
+ response.status + " response for " + url;
|
||||
}
|
||||
|
||||
// Remove the crypto record from the server and local cache
|
||||
let crypto = this.storageURL + "crypto/" + name;
|
||||
response = new Resource(crypto).delete();
|
||||
CryptoMetas.del(crypto);
|
||||
if (response.status != 200 && response.status != 404) {
|
||||
throw "Aborting wipeServer. Server responded with "
|
||||
+ response.status + " response for " + crypto;
|
||||
}
|
||||
}
|
||||
})(),
|
||||
|
||||
|
@ -1730,8 +1796,7 @@ WeaveSvc.prototype = {
|
|||
// Pretend we've never synced to the server and drop cached data
|
||||
this.syncID = "";
|
||||
Svc.Prefs.reset("lastSync");
|
||||
for each (let cache in [PubKeys, PrivKeys, CryptoMetas, Records])
|
||||
cache.clearCache();
|
||||
Records.clearCache();
|
||||
}))(),
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,6 +79,7 @@ let Status = {
|
|||
try {
|
||||
username = prefs.getCharPref("username");
|
||||
} catch(ex) {}
|
||||
|
||||
if (!username) {
|
||||
Status.login = LOGIN_FAILED_NO_USERNAME;
|
||||
return Status.service;
|
||||
|
@ -86,6 +87,7 @@ let Status = {
|
|||
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
if (!Utils.mpLocked()) {
|
||||
let id = ID.get("WeaveID");
|
||||
if (!id)
|
||||
|
@ -99,9 +101,9 @@ let Status = {
|
|||
id = ID.get("WeaveCryptoID");
|
||||
if (!id)
|
||||
id = ID.set("WeaveCryptoID",
|
||||
new Identity(PWDMGR_PASSPHRASE_REALM, username));
|
||||
new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, username));
|
||||
|
||||
if (!id.password) {
|
||||
if (!id.keyStr) {
|
||||
Status.login = LOGIN_FAILED_NO_PASSPHRASE;
|
||||
return Status.service;
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ Store.prototype = {
|
|||
throw "override itemExists in a subclass";
|
||||
},
|
||||
|
||||
createRecord: function Store_createRecord(id, uri) {
|
||||
createRecord: function Store_createRecord(id, collection) {
|
||||
throw "override createRecord in a subclass";
|
||||
},
|
||||
|
||||
|
|
|
@ -45,12 +45,12 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PlacesItem(uri, type) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function PlacesItem(collection, id, type) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
this.type = type || "item";
|
||||
}
|
||||
PlacesItem.prototype = {
|
||||
decrypt: function PlacesItem_decrypt(passphrase, keyUri) {
|
||||
decrypt: function PlacesItem_decrypt() {
|
||||
// Do the normal CryptoWrapper decrypt, but change types before returning
|
||||
let clear = CryptoWrapper.prototype.decrypt.apply(this, arguments);
|
||||
|
||||
|
@ -88,8 +88,8 @@ PlacesItem.prototype = {
|
|||
Utils.deferGetSet(PlacesItem, "cleartext", ["hasDupe", "parentid", "parentName",
|
||||
"predecessorid", "type"]);
|
||||
|
||||
function Bookmark(uri, type) {
|
||||
PlacesItem.call(this, uri, type || "bookmark");
|
||||
function Bookmark(collection, id, type) {
|
||||
PlacesItem.call(this, collection, id, type || "bookmark");
|
||||
}
|
||||
Bookmark.prototype = {
|
||||
__proto__: PlacesItem.prototype,
|
||||
|
@ -99,8 +99,8 @@ Bookmark.prototype = {
|
|||
Utils.deferGetSet(Bookmark, "cleartext", ["title", "bmkUri", "description",
|
||||
"loadInSidebar", "tags", "keyword"]);
|
||||
|
||||
function BookmarkMicsum(uri) {
|
||||
Bookmark.call(this, uri, "microsummary");
|
||||
function BookmarkMicsum(collection, id) {
|
||||
Bookmark.call(this, collection, id, "microsummary");
|
||||
}
|
||||
BookmarkMicsum.prototype = {
|
||||
__proto__: Bookmark.prototype,
|
||||
|
@ -109,8 +109,8 @@ BookmarkMicsum.prototype = {
|
|||
|
||||
Utils.deferGetSet(BookmarkMicsum, "cleartext", ["generatorUri", "staticTitle"]);
|
||||
|
||||
function BookmarkQuery(uri) {
|
||||
Bookmark.call(this, uri, "query");
|
||||
function BookmarkQuery(collection, id) {
|
||||
Bookmark.call(this, collection, id, "query");
|
||||
}
|
||||
BookmarkQuery.prototype = {
|
||||
__proto__: Bookmark.prototype,
|
||||
|
@ -119,8 +119,8 @@ BookmarkQuery.prototype = {
|
|||
|
||||
Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName"]);
|
||||
|
||||
function BookmarkFolder(uri, type) {
|
||||
PlacesItem.call(this, uri, type || "folder");
|
||||
function BookmarkFolder(collection, id, type) {
|
||||
PlacesItem.call(this, collection, id, type || "folder");
|
||||
}
|
||||
BookmarkFolder.prototype = {
|
||||
__proto__: PlacesItem.prototype,
|
||||
|
@ -129,8 +129,8 @@ BookmarkFolder.prototype = {
|
|||
|
||||
Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title"]);
|
||||
|
||||
function Livemark(uri) {
|
||||
BookmarkFolder.call(this, uri, "livemark");
|
||||
function Livemark(collection, id) {
|
||||
BookmarkFolder.call(this, collection, id, "livemark");
|
||||
}
|
||||
Livemark.prototype = {
|
||||
__proto__: BookmarkFolder.prototype,
|
||||
|
@ -139,8 +139,8 @@ Livemark.prototype = {
|
|||
|
||||
Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);
|
||||
|
||||
function BookmarkSeparator(uri) {
|
||||
PlacesItem.call(this, uri, "separator");
|
||||
function BookmarkSeparator(collection, id) {
|
||||
PlacesItem.call(this, collection, id, "separator");
|
||||
}
|
||||
BookmarkSeparator.prototype = {
|
||||
__proto__: PlacesItem.prototype,
|
||||
|
|
|
@ -44,8 +44,8 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function ClientsRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function ClientsRec(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
ClientsRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
|
|
|
@ -44,8 +44,8 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function FormRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function FormRec(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
FormRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
|
|
|
@ -44,8 +44,8 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function HistoryRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function HistoryRec(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
HistoryRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
|
|
|
@ -44,8 +44,8 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function LoginRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function LoginRec(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
LoginRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
|
|
|
@ -44,12 +44,12 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PrefRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function PrefRec(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
PrefRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Pref",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PrefRec, "cleartext", ["type", "value"]);
|
||||
Utils.deferGetSet(PrefRec, "cleartext", ["value"]);
|
||||
|
|
|
@ -44,8 +44,8 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function TabSetRecord(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function TabSetRecord(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
TabSetRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@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
|
||||
|
@ -107,11 +108,12 @@ let Utils = {
|
|||
* @usage MyObj._lock = Utils.lock;
|
||||
* MyObj.foo = function() { this._lock(func)(); }
|
||||
*/
|
||||
lock: function Utils_lock(func) {
|
||||
lock: function lock(label, func) {
|
||||
let thisArg = this;
|
||||
return function WrappedLock() {
|
||||
if (!thisArg.lock())
|
||||
throw "Could not acquire lock";
|
||||
if (!thisArg.lock()) {
|
||||
throw "Could not acquire lock. Label: \"" + label + "\".";
|
||||
}
|
||||
|
||||
try {
|
||||
return func.call(thisArg);
|
||||
|
@ -213,30 +215,33 @@ let Utils = {
|
|||
});
|
||||
},
|
||||
|
||||
// Generates a brand-new globally unique identifier (GUID).
|
||||
byteArrayToString: function byteArrayToString(bytes) {
|
||||
return [String.fromCharCode(byte) for each (byte in bytes)].join("");
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a string of random bytes.
|
||||
*/
|
||||
generateRandomBytes: function generateRandomBytes(length) {
|
||||
let rng = Cc["@mozilla.org/security/random-generator;1"]
|
||||
.createInstance(Ci.nsIRandomGenerator);
|
||||
let bytes = rng.generateRandomBytes(length);
|
||||
return Utils.byteArrayToString(bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encode byte string as base64url (RFC 4648).
|
||||
*/
|
||||
encodeBase64url: function encodeBase64url(bytes) {
|
||||
return btoa(bytes).replace('+', '-', 'g').replace('/', '_', 'g');
|
||||
},
|
||||
|
||||
/**
|
||||
* GUIDs are 9 random bytes encoded with base64url (RFC 4648).
|
||||
* That makes them 12 characters long with 72 bits of entropy.
|
||||
*/
|
||||
makeGUID: function makeGUID() {
|
||||
// 70 characters that are not-escaped URL-friendly
|
||||
const code =
|
||||
"!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
|
||||
|
||||
let guid = "";
|
||||
let num = 0;
|
||||
let val;
|
||||
|
||||
// Generate ten 70-value characters for a 70^10 (~61.29-bit) GUID
|
||||
for (let i = 0; i < 10; i++) {
|
||||
// Refresh the number source after using it a few times
|
||||
if (i == 0 || i == 5)
|
||||
num = Math.random();
|
||||
|
||||
// Figure out which code to use for the next GUID character
|
||||
num *= 70;
|
||||
val = Math.floor(num);
|
||||
guid += code[val];
|
||||
num -= val;
|
||||
}
|
||||
|
||||
return guid;
|
||||
return Utils.encodeBase64url(Utils.generateRandomBytes(9));
|
||||
},
|
||||
|
||||
anno: function anno(id, anno, val, expire) {
|
||||
|
@ -330,8 +335,21 @@ let Utils = {
|
|||
let prot = obj.prototype;
|
||||
|
||||
// Create a getter if it doesn't exist yet
|
||||
if (!prot.__lookupGetter__(prop))
|
||||
prot.__defineGetter__(prop, function() deref(this)[prop]);
|
||||
if (!prot.__lookupGetter__(prop)) {
|
||||
// Yes, this should be a one-liner, but there are errors if it's not
|
||||
// broken out. *sigh*
|
||||
// Errors are these:
|
||||
// JavaScript strict warning: resource://services-sync/util.js, line 304: reference to undefined property deref(this)[prop]
|
||||
// JavaScript strict warning: resource://services-sync/util.js, line 304: reference to undefined property deref(this)[prop]
|
||||
let f = function() {
|
||||
let d = deref(this);
|
||||
if (!d)
|
||||
return undefined;
|
||||
let out = d[prop];
|
||||
return out;
|
||||
}
|
||||
prot.__defineGetter__(prop, f);
|
||||
}
|
||||
|
||||
// Create a setter if it doesn't exist yet
|
||||
if (!prot.__lookupSetter__(prop))
|
||||
|
@ -481,7 +499,7 @@ let Utils = {
|
|||
exceptionStr: function Weave_exceptionStr(e) {
|
||||
let message = e.message ? e.message : e;
|
||||
return message + " " + Utils.stackTrace(e);
|
||||
},
|
||||
},
|
||||
|
||||
stackTraceFromFrame: function Weave_stackTraceFromFrame(frame) {
|
||||
let output = [];
|
||||
|
@ -549,6 +567,21 @@ let Utils = {
|
|||
for each (byte in bytes)].join("");
|
||||
},
|
||||
|
||||
_sha256: function _sha256(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
return Utils.digest(message, hasher);
|
||||
},
|
||||
|
||||
sha256: function sha256(message) {
|
||||
return Utils.bytesAsHex(Utils._sha256(message));
|
||||
},
|
||||
|
||||
sha256Base64: function (message) {
|
||||
return btoa(Utils._sha256(message));
|
||||
},
|
||||
|
||||
_sha1: function _sha1(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
|
@ -563,15 +596,249 @@ let Utils = {
|
|||
sha1Base32: function sha1Base32(message) {
|
||||
return Utils.encodeBase32(Utils._sha1(message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC key object from a key string.
|
||||
*/
|
||||
makeHMACKey: function makeHMACKey(str) {
|
||||
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC hasher.
|
||||
*/
|
||||
makeHMACHasher: function makeHMACHasher() {
|
||||
return Cc["@mozilla.org/security/hmac;1"]
|
||||
.createInstance(Ci.nsICryptoHMAC);
|
||||
},
|
||||
|
||||
sha1Base64: function (message) {
|
||||
return btoa(Utils._sha1(message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a sha256 HMAC for a string message and a given nsIKeyObject
|
||||
* Generate a sha1 HMAC for a message, not UTF-8 encoded,
|
||||
* and a given nsIKeyObject.
|
||||
* Optionally provide an existing hasher, which will be
|
||||
* initialized and reused.
|
||||
*/
|
||||
sha256HMAC: function sha256HMAC(message, key) {
|
||||
let hasher = Cc["@mozilla.org/security/hmac;1"].
|
||||
createInstance(Ci.nsICryptoHMAC);
|
||||
hasher.init(hasher.SHA256, key);
|
||||
return Utils.bytesAsHex(Utils.digest(message, hasher));
|
||||
sha1HMACBytes: function sha1HMACBytes(message, key, hasher) {
|
||||
let h = hasher || this.makeHMACHasher();
|
||||
h.init(h.SHA1, key);
|
||||
|
||||
// No UTF-8 encoding for you, sunshine.
|
||||
let bytes = [b.charCodeAt() for each (b in message)];
|
||||
h.update(bytes, bytes.length);
|
||||
return h.finish(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a sha256 HMAC for a string message and a given nsIKeyObject.
|
||||
* Optionally provide an existing hasher, which will be
|
||||
* initialized and reused.
|
||||
*
|
||||
* Returns hex output.
|
||||
*/
|
||||
sha256HMAC: function sha256HMAC(message, key, hasher) {
|
||||
let h = hasher || this.makeHMACHasher();
|
||||
h.init(h.SHA256, key);
|
||||
return Utils.bytesAsHex(Utils.digest(message, h));
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generate a sha256 HMAC for a string message, not UTF-8 encoded,
|
||||
* and a given nsIKeyObject.
|
||||
* Optionally provide an existing hasher, which will be
|
||||
* initialized and reused.
|
||||
*/
|
||||
sha256HMACBytes: function sha256HMACBytes(message, key, hasher) {
|
||||
let h = hasher || this.makeHMACHasher();
|
||||
h.init(h.SHA256, key);
|
||||
|
||||
// No UTF-8 encoding for you, sunshine.
|
||||
let bytes = [b.charCodeAt() for each (b in message)];
|
||||
h.update(bytes, bytes.length);
|
||||
return h.finish(false);
|
||||
},
|
||||
|
||||
byteArrayToString: function byteArrayToString(bytes) {
|
||||
return [String.fromCharCode(byte) for each (byte in bytes)].join("");
|
||||
},
|
||||
|
||||
/**
|
||||
* PBKDF2 implementation in Javascript.
|
||||
*
|
||||
* The arguments to this function correspond to items in
|
||||
* PKCS #5, v2.0 pp. 9-10
|
||||
*
|
||||
* P: the passphrase, an octet string: e.g., "secret phrase"
|
||||
* S: the salt, an octet string: e.g., "DNXPzPpiwn"
|
||||
* c: the number of iterations, a positive integer: e.g., 4096
|
||||
* dkLen: the length in octets of the destination
|
||||
* key, a positive integer: e.g., 16
|
||||
*
|
||||
* The output is an octet string of length dkLen, which you
|
||||
* can encode as you wish.
|
||||
*/
|
||||
pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen) {
|
||||
|
||||
// We don't have a default in the algo itself, as NSS does.
|
||||
// Use the constant.
|
||||
if (!dkLen)
|
||||
dkLen = SYNC_KEY_DECODED_LENGTH;
|
||||
|
||||
/* For HMAC-SHA-1 */
|
||||
const HLEN = 20;
|
||||
|
||||
function F(PK, S, c, i, h) {
|
||||
|
||||
function XOR(a, b, isA) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let val = [];
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (isA) {
|
||||
val[i] = a[i] ^ b[i];
|
||||
} else {
|
||||
val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
let ret;
|
||||
let U = [];
|
||||
|
||||
/* Encode i into 4 octets: _INT */
|
||||
let I = [];
|
||||
I[0] = String.fromCharCode((i >> 24) & 0xff);
|
||||
I[1] = String.fromCharCode((i >> 16) & 0xff);
|
||||
I[2] = String.fromCharCode((i >> 8) & 0xff);
|
||||
I[3] = String.fromCharCode(i & 0xff);
|
||||
|
||||
U[0] = Utils.sha1HMACBytes(S + I.join(''), PK, h);
|
||||
for (let j = 1; j < c; j++) {
|
||||
U[j] = Utils.sha1HMACBytes(U[j - 1], PK, h);
|
||||
}
|
||||
|
||||
ret = U[0];
|
||||
for (j = 1; j < c; j++) {
|
||||
ret = Utils.byteArrayToString(XOR(ret, U[j]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let l = Math.ceil(dkLen / HLEN);
|
||||
let r = dkLen - ((l - 1) * HLEN);
|
||||
|
||||
// Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
|
||||
let PK = Utils.makeHMACKey(P);
|
||||
let h = Utils.makeHMACHasher();
|
||||
|
||||
T = [];
|
||||
for (let i = 0; i < l;) {
|
||||
T[i] = F(PK, S, c, ++i, h);
|
||||
}
|
||||
|
||||
let ret = '';
|
||||
for (i = 0; i < l-1;) {
|
||||
ret += T[i++];
|
||||
}
|
||||
ret += T[l - 1].substr(0, r);
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Base32 decode (RFC 4648) a string.
|
||||
*/
|
||||
decodeBase32: function decodeBase32(str) {
|
||||
const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
let padChar = str.indexOf("=");
|
||||
let chars = (padChar == -1) ? str.length : padChar;
|
||||
let bytes = Math.floor(chars * 5 / 8);
|
||||
let blocks = Math.ceil(chars / 8);
|
||||
|
||||
// Process a chunk of 5 bytes / 8 characters.
|
||||
// The processing of this is known in advance,
|
||||
// so avoid arithmetic!
|
||||
function processBlock(ret, cOffset, rOffset) {
|
||||
let c, val;
|
||||
|
||||
// N.B., this relies on
|
||||
// undefined | foo == foo.
|
||||
function accumulate(val) {
|
||||
ret[rOffset] |= val;
|
||||
}
|
||||
|
||||
function advance() {
|
||||
c = str[cOffset++];
|
||||
if (!c || c == "" || c == "=") // Easier than range checking.
|
||||
throw "Done"; // Will be caught far away.
|
||||
val = key.indexOf(c);
|
||||
if (val == -1)
|
||||
throw "Unknown character in base32: " + c;
|
||||
}
|
||||
|
||||
// Handle a left shift, restricted to bytes.
|
||||
function left(octet, shift)
|
||||
(octet << shift) & 0xff;
|
||||
|
||||
advance();
|
||||
accumulate(left(val, 3));
|
||||
advance();
|
||||
accumulate(val >> 2);
|
||||
++rOffset;
|
||||
accumulate(left(val, 6));
|
||||
advance();
|
||||
accumulate(left(val, 1));
|
||||
advance();
|
||||
accumulate(val >> 4);
|
||||
++rOffset;
|
||||
accumulate(left(val, 4));
|
||||
advance();
|
||||
accumulate(val >> 1);
|
||||
++rOffset;
|
||||
accumulate(left(val, 7));
|
||||
advance();
|
||||
accumulate(left(val, 2));
|
||||
advance();
|
||||
accumulate(val >> 3);
|
||||
++rOffset;
|
||||
accumulate(left(val, 5));
|
||||
advance();
|
||||
accumulate(val);
|
||||
++rOffset;
|
||||
}
|
||||
|
||||
// Our output. Define to be explicit (and maybe the compiler will be smart).
|
||||
let ret = new Array(bytes);
|
||||
let i = 0;
|
||||
let cOff = 0;
|
||||
let rOff = 0;
|
||||
|
||||
for (; i < blocks; ++i) {
|
||||
try {
|
||||
processBlock(ret, cOff, rOff);
|
||||
} catch (ex) {
|
||||
// Handle the detection of padding.
|
||||
if (ex == "Done")
|
||||
break;
|
||||
throw ex;
|
||||
}
|
||||
cOff += 8;
|
||||
rOff += 5;
|
||||
}
|
||||
|
||||
// Slice in case our shift overflowed to the right.
|
||||
return Utils.byteArrayToString(ret.slice(0, bytes));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -618,6 +885,85 @@ let Utils = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Turn RFC 4648 base32 into our own user-friendly version.
|
||||
* ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
|
||||
* becomes
|
||||
* abcdefghijk8mn9pqrstuvwxyz234567
|
||||
*/
|
||||
base32ToFriendly: function base32ToFriendly(input) {
|
||||
return input.toLowerCase()
|
||||
.replace("l", '8', "g")
|
||||
.replace("o", '9', "g");
|
||||
},
|
||||
|
||||
base32FromFriendly: function base32FromFriendly(input) {
|
||||
return input.toUpperCase()
|
||||
.replace("8", 'L', "g")
|
||||
.replace("9", 'O', "g");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Key manipulation.
|
||||
*/
|
||||
|
||||
// Return an octet string in friendly base32 *with no trailing =*.
|
||||
encodeKeyBase32: function encodeKeyBase32(keyData) {
|
||||
return Utils.base32ToFriendly(
|
||||
Utils.encodeBase32(keyData))
|
||||
.slice(0, SYNC_KEY_ENCODED_LENGTH);
|
||||
},
|
||||
|
||||
decodeKeyBase32: function decodeKeyBase32(encoded) {
|
||||
return Utils.decodeBase32(
|
||||
Utils.base32FromFriendly(
|
||||
Utils.normalizePassphrase(encoded)))
|
||||
.slice(0, SYNC_KEY_DECODED_LENGTH);
|
||||
},
|
||||
|
||||
base64Key: function base64Key(keyData) {
|
||||
return btoa(keyData);
|
||||
},
|
||||
|
||||
deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
||||
if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
|
||||
return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
|
||||
}
|
||||
else {
|
||||
// Fall back to JS implementation.
|
||||
// 4096 is hardcoded in WeaveCrypto, so do so here.
|
||||
return Utils.pbkdf2Generate(passphrase, atob(salt), 4096, keyLength);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* N.B., salt should be base64 encoded, even though we have to decode
|
||||
* it later!
|
||||
*/
|
||||
derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
||||
let k = Utils.deriveKeyFromPassphrase(passphrase, salt, keyLength, forceJS);
|
||||
return Utils.encodeKeyBase32(k);
|
||||
},
|
||||
|
||||
/**
|
||||
* N.B., salt should be base64 encoded, even though we have to decode
|
||||
* it later!
|
||||
*/
|
||||
deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
||||
let k = Utils.deriveKeyFromPassphrase(passphrase, salt, keyLength, forceJS);
|
||||
return Utils.base64Key(k);
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a base64-encoded 128-bit AES key, returning it as five groups of five
|
||||
* uppercase alphanumeric characters, separated by hyphens.
|
||||
* A.K.A. base64-to-base32 encoding.
|
||||
*/
|
||||
presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
|
||||
return Utils.encodeKeyBase32(atob(encodedKey));
|
||||
},
|
||||
|
||||
makeURI: function Weave_makeURI(URIString) {
|
||||
if (!URIString)
|
||||
return null;
|
||||
|
@ -861,36 +1207,92 @@ let Utils = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Generate 20 random characters a-z
|
||||
* Generate 26 characters.
|
||||
*/
|
||||
generatePassphrase: function() {
|
||||
let rng = Cc["@mozilla.org/security/random-generator;1"]
|
||||
.createInstance(Ci.nsIRandomGenerator);
|
||||
let bytes = rng.generateRandomBytes(20);
|
||||
return [String.fromCharCode(97 + Math.floor(byte * 26 / 256))
|
||||
for each (byte in bytes)].join("");
|
||||
generatePassphrase: function generatePassphrase() {
|
||||
// Note that this is a different base32 alphabet to the one we use for
|
||||
// other tasks. It's lowercase, uses different letters, and needs to be
|
||||
// decoded with decodeKeyBase32, not just decodeBase32.
|
||||
return Utils.encodeKeyBase32(Utils.generateRandomBytes(16));
|
||||
},
|
||||
|
||||
/**
|
||||
* Hyphenate a 20 character passphrase in 4 groups of 5.
|
||||
* The following are the methods supported for UI use:
|
||||
*
|
||||
* * isPassphrase:
|
||||
* determines whether a string is either a normalized or presentable
|
||||
* passphrase.
|
||||
* * hyphenatePassphrase:
|
||||
* present a normalized passphrase for display. This might actually
|
||||
* perform work beyond just hyphenation; sorry.
|
||||
* * hyphenatePartialPassphrase:
|
||||
* present a fragment of a normalized passphrase for display.
|
||||
* * normalizePassphrase:
|
||||
* take a presentable passphrase and reduce it to a normalized
|
||||
* representation for storage. normalizePassphrase can safely be called
|
||||
* on normalized input.
|
||||
*/
|
||||
hyphenatePassphrase: function(passphrase) {
|
||||
return passphrase.slice(0, 5) + '-'
|
||||
+ passphrase.slice(5, 10) + '-'
|
||||
+ passphrase.slice(10, 15) + '-'
|
||||
+ passphrase.slice(15, 20);
|
||||
|
||||
isPassphrase: function(s) {
|
||||
if (s) {
|
||||
return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove hyphens as inserted by hyphenatePassphrase().
|
||||
* Hyphenate a passphrase (26 characters) into groups.
|
||||
* abbbbccccddddeeeeffffggggh
|
||||
* =>
|
||||
* a-bbbbc-cccdd-ddeee-effff-ggggh
|
||||
*/
|
||||
normalizePassphrase: function(pp) {
|
||||
if (pp.length == 23 && pp[5] == '-' && pp[11] == '-' && pp[17] == '-')
|
||||
return pp.slice(0, 5) + pp.slice(6, 11)
|
||||
+ pp.slice(12, 17) + pp.slice(18, 23);
|
||||
hyphenatePassphrase: function hyphenatePassphrase(passphrase) {
|
||||
// For now, these are the same.
|
||||
return Utils.hyphenatePartialPassphrase(passphrase, true);
|
||||
},
|
||||
|
||||
hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) {
|
||||
if (!passphrase)
|
||||
return null;
|
||||
|
||||
// Get the raw data input. Just base32.
|
||||
let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, "");
|
||||
|
||||
// This is the neatest way to do this.
|
||||
if ((data.length == 1) && !omitTrailingDash)
|
||||
return data + "-";
|
||||
|
||||
// Hyphenate it.
|
||||
let y = data.substr(0,1);
|
||||
let z = data.substr(1).replace(/(.{1,5})/g, "-$1");
|
||||
|
||||
// Correct length? We're done.
|
||||
if ((z.length == 30) || omitTrailingDash)
|
||||
return y + z;
|
||||
|
||||
// Add a trailing dash if appropriate.
|
||||
return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH);
|
||||
},
|
||||
|
||||
normalizePassphrase: function normalizePassphrase(pp) {
|
||||
// Short var name... have you seen the lines below?!
|
||||
pp = pp.toLowerCase();
|
||||
if (pp.length == 31 && [1, 7, 13, 19, 25].every(function(i) pp[i] == '-'))
|
||||
return pp.slice(0, 1) + pp.slice(2, 7)
|
||||
+ pp.slice(8, 13) + pp.slice(14, 19)
|
||||
+ pp.slice(20, 25) + pp.slice(26, 31);
|
||||
return pp;
|
||||
},
|
||||
|
||||
// WeaveCrypto returns bad base64 strings. Truncate excess padding
|
||||
// and decode.
|
||||
// See Bug 562431, comment 4.
|
||||
safeAtoB: function safeAtoB(b64) {
|
||||
let len = b64.length;
|
||||
let over = len % 4;
|
||||
return over ? atob(b64.substr(0, len - over)) : atob(b64);
|
||||
},
|
||||
|
||||
/*
|
||||
* Calculate the strength of a passphrase provided by the user
|
||||
* according to the NIST algorithm (NIST 800-63 Appendix A.1).
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
var btoa;
|
||||
|
||||
// initialize nss
|
||||
let ch = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
|
@ -72,7 +74,7 @@ function FakeTimerService() {
|
|||
Utils.makeTimerForCall = self.makeTimerForCall;
|
||||
};
|
||||
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
btoa = Cu.import("resource://services-sync/log4moz.js").btoa;
|
||||
function getTestLogger(component) {
|
||||
return Log4Moz.repository.getLogger("Testing");
|
||||
}
|
||||
|
@ -236,54 +238,33 @@ FakeCryptoService.prototype = {
|
|||
return aCipherText;
|
||||
},
|
||||
|
||||
generateKeypair: function(aPassphrase, aSalt, aIV,
|
||||
aEncodedPublicKey, aWrappedPrivateKey) {
|
||||
aEncodedPublicKey.value = aPassphrase;
|
||||
aWrappedPrivateKey.value = aPassphrase;
|
||||
},
|
||||
|
||||
generateRandomKey: function() {
|
||||
return "fake-symmetric-key-" + this.counter++;
|
||||
return btoa("fake-symmetric-key-" + this.counter++);
|
||||
},
|
||||
|
||||
generateRandomIV: function() {
|
||||
// A base64-encoded IV is 24 characters long
|
||||
return "fake-fake-fake-random-iv";
|
||||
return btoa("fake-fake-fake-random-iv");
|
||||
},
|
||||
|
||||
expandData : function expandData(data, len) {
|
||||
return data;
|
||||
},
|
||||
|
||||
deriveKeyFromPassphrase : function (passphrase, salt, keyLength) {
|
||||
return "some derived key string composed of bytes";
|
||||
},
|
||||
|
||||
generateRandomBytes: function(aByteCount) {
|
||||
return "not-so-random-now-are-we-HA-HA-HA! >:)".slice(aByteCount);
|
||||
},
|
||||
|
||||
wrapSymmetricKey: function(aSymmetricKey, aEncodedPublicKey) {
|
||||
return aSymmetricKey;
|
||||
},
|
||||
|
||||
unwrapSymmetricKey: function(aWrappedSymmetricKey, aWrappedPrivateKey,
|
||||
aPassphrase, aSalt, aIV) {
|
||||
if (!this.verifyPassphrase(aWrappedPrivateKey, aPassphrase)) {
|
||||
throw Components.Exception("Unwrapping the private key failed",
|
||||
Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
return aWrappedSymmetricKey;
|
||||
},
|
||||
|
||||
rewrapPrivateKey: function(aWrappedPrivateKey, aPassphrase, aSalt, aIV,
|
||||
aNewPassphrase) {
|
||||
return aNewPassphrase;
|
||||
},
|
||||
|
||||
verifyPassphrase: function(aWrappedPrivateKey, aPassphrase, aSalt, aIV) {
|
||||
return aWrappedPrivateKey == aPassphrase;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
function SyncTestingInfrastructure(engineFactory) {
|
||||
let __fakePasswords = {
|
||||
'Mozilla Services Password': {foo: "bar"},
|
||||
'Mozilla Services Encryption Passphrase': {foo: "passphrase"}
|
||||
'Mozilla Services Encryption Passphrase': {foo: "a-abcde-abcde-abcde-abcde-abcde"}
|
||||
};
|
||||
|
||||
let __fakePrefs = {
|
||||
|
|
|
@ -23,11 +23,11 @@ function run_test() {
|
|||
let second = insert(10);
|
||||
|
||||
_("Making sure the record created for the first has no predecessor");
|
||||
let pos5 = store.createRecord("pos5", baseuri + "pos5");
|
||||
let pos5 = store.createRecord("pos5");
|
||||
do_check_eq(pos5.predecessorid, undefined);
|
||||
|
||||
_("Making sure the second record has the first as its predecessor");
|
||||
let pos10 = store.createRecord("pos10", baseuri + "pos10");
|
||||
let pos10 = store.createRecord("pos10");
|
||||
do_check_eq(pos10.predecessorid, "pos5");
|
||||
|
||||
_("Make sure the index of item gets fixed");
|
||||
|
@ -37,8 +37,8 @@ function run_test() {
|
|||
_("Make sure things that are in unsorted don't set the predecessor");
|
||||
insert(0, Svc.Bookmark.unfiledBookmarksFolder);
|
||||
insert(1, Svc.Bookmark.unfiledBookmarksFolder);
|
||||
do_check_eq(store.createRecord("pos0", baseuri + "pos0").predecessorid,
|
||||
do_check_eq(store.createRecord("pos0").predecessorid,
|
||||
undefined);
|
||||
do_check_eq(store.createRecord("pos1", baseuri + "pos1").predecessorid,
|
||||
do_check_eq(store.createRecord("pos1").predecessorid,
|
||||
undefined);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/type_records/bookmark.js");
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function prepareBookmarkItem(collection, id) {
|
||||
let b = new Bookmark(collection, id);
|
||||
b.cleartext.stuff = "my payload here";
|
||||
return b;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let keyBundle = ID.set("WeaveCryptoID", new SyncKeyBundle(null, "john@example.com"));
|
||||
keyBundle.keyStr = "abcdeabcdeabcdeabcdeabcdea";
|
||||
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
let log = Log4Moz.repository.getLogger("Test");
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
log.info("Creating a record");
|
||||
|
||||
let u = "http://localhost:8080/storage/bookmarks/foo";
|
||||
let placesItem = new PlacesItem("bookmarks", "foo", "bookmark");
|
||||
let bookmarkItem = prepareBookmarkItem("bookmarks", "foo");
|
||||
|
||||
log.info("Checking getTypeObject");
|
||||
do_check_eq(placesItem.getTypeObject(placesItem.type), Bookmark);
|
||||
do_check_eq(bookmarkItem.getTypeObject(bookmarkItem.type), Bookmark);
|
||||
|
||||
bookmarkItem.encrypt(keyBundle);
|
||||
log.info("Ciphertext is " + bookmarkItem.ciphertext);
|
||||
do_check_true(bookmarkItem.ciphertext != null);
|
||||
|
||||
log.info("Decrypting the record");
|
||||
|
||||
let payload = bookmarkItem.decrypt(keyBundle);
|
||||
do_check_eq(payload.stuff, "my payload here");
|
||||
do_check_eq(bookmarkItem.getTypeObject(bookmarkItem.type), Bookmark);
|
||||
do_check_neq(payload, bookmarkItem.payload); // wrap.data.payload is the encrypted one
|
||||
}
|
|
@ -35,7 +35,7 @@ function run_test() {
|
|||
do_check_eq(Svc.Bookmark.getKeywordForBookmark(id), fxrecord.keyword);
|
||||
|
||||
_("Have the store create a new record object. Verify that it has the same data.");
|
||||
let newrecord = store.createRecord(fxrecord.id, "http://fake/uri");
|
||||
let newrecord = store.createRecord(fxrecord.id);
|
||||
for each (let property in ["type", "bmkUri", "title", "keyword",
|
||||
"parentName", "parentid"])
|
||||
do_check_eq(newrecord[property], fxrecord[property]);
|
||||
|
@ -47,7 +47,7 @@ function run_test() {
|
|||
let folder_id = Svc.Bookmark.createFolder(Svc.Bookmark.toolbarFolder,
|
||||
"Test Folder", 0);
|
||||
let folder_guid = Svc.Bookmark.getItemGUID(folder_id);
|
||||
let folder_record = store.createRecord(folder_guid, "http://fake/uri");
|
||||
let folder_record = store.createRecord(folder_guid);
|
||||
do_check_eq(folder_record.sortindex, 1000000);
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/engines/clients.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
@ -13,18 +12,8 @@ function run_test() {
|
|||
let pubUri = baseUri + "keys/pubkey";
|
||||
let privUri = baseUri + "keys/privkey";
|
||||
|
||||
let passphrase = ID.set("WeaveCryptoID", new Identity());
|
||||
passphrase.password = "passphrase";
|
||||
|
||||
_("Setting up fake pub/priv keypair and symkey for encrypt/decrypt");
|
||||
PubKeys.defaultKeyUri = baseUri + "keys/pubkey";
|
||||
let {pubkey, privkey} = PubKeys.createKeypair(passphrase, pubUri, privUri);
|
||||
PubKeys.set(pubUri, pubkey);
|
||||
PrivKeys.set(privUri, privkey);
|
||||
|
||||
let cryptoMeta = new CryptoMeta(Clients.cryptoMetaURL);
|
||||
cryptoMeta.addUnwrappedKey(pubkey, Svc.Crypto.generateRandomKey());
|
||||
CryptoMetas.set(Clients.cryptoMetaURL, cryptoMeta);
|
||||
let keyBundle = ID.set("WeaveCryptoID",
|
||||
new SyncKeyBundle(null, "john@example.com", "abcdeabcdeabcdeabcdeabcdea"));
|
||||
|
||||
try {
|
||||
_("Test that serializing client records results in uploadable ascii");
|
||||
|
@ -36,7 +25,10 @@ function run_test() {
|
|||
do_check_eq(record.id, "ascii");
|
||||
do_check_eq(record.name, "wéävê");
|
||||
|
||||
record.encrypt(passphrase);
|
||||
_("Encrypting record...");
|
||||
record.encrypt(keyBundle);
|
||||
_("Encrypted.");
|
||||
|
||||
let serialized = JSON.stringify(record);
|
||||
let checkCount = 0;
|
||||
_("Checking for all ASCII:", serialized);
|
||||
|
@ -51,7 +43,7 @@ function run_test() {
|
|||
do_check_eq(checkCount, serialized.length);
|
||||
|
||||
_("Making sure the record still looks like it did before");
|
||||
record.decrypt(passphrase, Clients.cryptoMetaURL);
|
||||
record.decrypt(keyBundle);
|
||||
do_check_eq(record.id, "ascii");
|
||||
do_check_eq(record.name, "wéävê");
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ Cu.import("resource://services-sync/base_records/collection.js");
|
|||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
|
||||
function run_test() {
|
||||
let coll = new Collection("http://fake/uri", WBORecord);
|
||||
let base = "http://fake/";
|
||||
let coll = new Collection("http://fake/uri/", WBORecord);
|
||||
let stream = { _data: "" };
|
||||
let called, recCount, sum;
|
||||
|
||||
|
@ -13,8 +14,10 @@ function run_test() {
|
|||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
_("Got record:", JSON.stringify(rec));
|
||||
rec.collection = "uri"; // This would be done by an engine, so do it here.
|
||||
do_check_eq(rec.collection, "uri");
|
||||
do_check_eq(rec.id, "hello");
|
||||
do_check_eq(rec.uri.spec, "http://fake/uri/hello");
|
||||
do_check_eq(rec.uri(base).spec, "http://fake/uri/hello");
|
||||
do_check_eq(rec.payload, "world");
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
|
@ -48,22 +51,23 @@ function run_test() {
|
|||
recCount++;
|
||||
sum += rec.payload.value;
|
||||
_("Incremental status: count", recCount, "sum", sum);
|
||||
rec.collection = "uri";
|
||||
switch (recCount) {
|
||||
case 1:
|
||||
do_check_eq(rec.id, "hundred");
|
||||
do_check_eq(rec.uri.spec, "http://fake/uri/hundred");
|
||||
do_check_eq(rec.uri(base).spec, "http://fake/uri/hundred");
|
||||
do_check_eq(rec.payload.value, 100);
|
||||
do_check_eq(sum, 100);
|
||||
break;
|
||||
case 2:
|
||||
do_check_eq(rec.id, "ten");
|
||||
do_check_eq(rec.uri.spec, "http://fake/uri/ten");
|
||||
do_check_eq(rec.uri(base).spec, "http://fake/uri/ten");
|
||||
do_check_eq(rec.payload.value, 10);
|
||||
do_check_eq(sum, 110);
|
||||
break;
|
||||
case 3:
|
||||
do_check_eq(rec.id, "one");
|
||||
do_check_eq(rec.uri.spec, "http://fake/uri/one");
|
||||
do_check_eq(rec.uri(base).spec, "http://fake/uri/one");
|
||||
do_check_eq(rec.payload.value, 1);
|
||||
do_check_eq(sum, 111);
|
||||
break;
|
||||
|
|
|
@ -28,13 +28,13 @@ function run_test() {
|
|||
}
|
||||
do_check_true(store.itemExists(id));
|
||||
|
||||
let rec = store.createRecord(id, baseuri + id);
|
||||
let rec = store.createRecord(id);
|
||||
_("Got record for id", id, rec);
|
||||
do_check_eq(rec.name, "name!!");
|
||||
do_check_eq(rec.value, "value??");
|
||||
|
||||
_("Create a non-existent id for delete");
|
||||
do_check_true(store.createRecord("deleted!!", baseuri + "deleted!!").deleted);
|
||||
do_check_true(store.createRecord("deleted!!").deleted);
|
||||
|
||||
_("Try updating.. doesn't do anything yet");
|
||||
store.update({});
|
||||
|
|
|
@ -83,11 +83,11 @@ function run_test() {
|
|||
do_check_true(store.itemExists(fxguid));
|
||||
|
||||
_("If we query a non-existent record, it's marked as deleted.");
|
||||
let record = store.createRecord("non-existent", "http://fake/uri");
|
||||
let record = store.createRecord("non-existent");
|
||||
do_check_true(record.deleted);
|
||||
|
||||
_("Verify createRecord() returns a complete record.");
|
||||
record = store.createRecord(fxguid, "http://fake/urk");
|
||||
record = store.createRecord(fxguid);
|
||||
do_check_eq(record.histUri, fxuri.spec);
|
||||
do_check_eq(record.title, "Get Firefox!");
|
||||
do_check_eq(record.visits.length, 1);
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
var btoa;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
btoa = Cu.import("resource://services-sync/util.js").btoa;
|
||||
|
||||
function test_keymanager() {
|
||||
let testKey = "ababcdefabcdefabcdefabcdef";
|
||||
|
||||
let username = "john@example.com";
|
||||
|
||||
// Decode the key here to mirror what generateEntry will do,
|
||||
// but pass it encoded into the KeyBundle call below.
|
||||
|
||||
let sha256inputE = Utils.makeHMACKey("" + HMAC_INPUT + username + "\x01");
|
||||
let encryptKey = Utils.sha256HMACBytes(Utils.decodeKeyBase32(testKey), sha256inputE);
|
||||
|
||||
let sha256inputH = Utils.makeHMACKey(encryptKey + HMAC_INPUT + username + "\x02");
|
||||
let hmacKey = Utils.sha256HMACBytes(Utils.decodeKeyBase32(testKey), sha256inputH);
|
||||
|
||||
// Encryption key is stored in base64 for WeaveCrypto convenience.
|
||||
do_check_eq(btoa(encryptKey), new SyncKeyBundle(null, username, testKey).encryptionKey);
|
||||
do_check_eq(hmacKey, new SyncKeyBundle(null, username, testKey).hmacKey);
|
||||
|
||||
// Test with the same KeyBundle for both.
|
||||
let obj = new SyncKeyBundle(null, username, testKey);
|
||||
do_check_eq(hmacKey, obj.hmacKey);
|
||||
do_check_eq(btoa(encryptKey), obj.encryptionKey);
|
||||
}
|
||||
|
||||
function do_check_keypair_eq(a, b) {
|
||||
do_check_eq(2, a.length);
|
||||
do_check_eq(2, b.length);
|
||||
do_check_eq(a[0], b[0]);
|
||||
do_check_eq(a[1], b[1]);
|
||||
}
|
||||
|
||||
function test_collections_manager() {
|
||||
let log = Log4Moz.repository.getLogger("Test");
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let keyBundle = ID.set("WeaveCryptoID",
|
||||
new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, "john@example.com", "a-bbbbb-ccccc-ddddd-eeeee-fffff"));
|
||||
|
||||
/*
|
||||
* Build a test version of storage/crypto/keys.
|
||||
* Encrypt it with the sync key.
|
||||
* Pass it into the CollectionKeyManager.
|
||||
*/
|
||||
|
||||
log.info("Building storage keys...");
|
||||
let storage_keys = new CryptoWrapper("crypto", "keys");
|
||||
let default_key64 = Svc.Crypto.generateRandomKey();
|
||||
let default_hmac64 = Svc.Crypto.generateRandomKey();
|
||||
let bookmarks_key64 = Svc.Crypto.generateRandomKey();
|
||||
let bookmarks_hmac64 = Svc.Crypto.generateRandomKey();
|
||||
|
||||
storage_keys.cleartext = {
|
||||
"default": [default_key64, default_hmac64],
|
||||
"collections": {"bookmarks": [bookmarks_key64, bookmarks_hmac64]},
|
||||
};
|
||||
storage_keys.modified = Date.now()/1000;
|
||||
storage_keys.id = "keys";
|
||||
|
||||
log.info("Encrypting storage keys...");
|
||||
|
||||
// Use passphrase (sync key) itself to encrypt the key bundle.
|
||||
storage_keys.encrypt(keyBundle);
|
||||
|
||||
// Sanity checking.
|
||||
do_check_true(null == storage_keys.cleartext);
|
||||
do_check_true(null != storage_keys.ciphertext);
|
||||
|
||||
log.info("Updating CollectionKeys.");
|
||||
|
||||
// updateContents decrypts the object, but it also returns the payload
|
||||
// for us to use.
|
||||
let payload = CollectionKeys.updateContents(keyBundle, storage_keys);
|
||||
|
||||
_("CK: " + JSON.stringify(CollectionKeys._collections));
|
||||
|
||||
// Test that the CollectionKeyManager returns a similar WBO.
|
||||
let wbo = CollectionKeys.asWBO("crypto", "keys");
|
||||
|
||||
_("WBO: " + JSON.stringify(wbo));
|
||||
|
||||
// Check the individual contents.
|
||||
do_check_eq(wbo.collection, "crypto");
|
||||
do_check_eq(wbo.id, "keys");
|
||||
do_check_eq(storage_keys.modified, wbo.modified);
|
||||
do_check_true(!!wbo.cleartext.default);
|
||||
do_check_keypair_eq(payload.default, wbo.cleartext.default);
|
||||
do_check_keypair_eq(payload.collections.bookmarks, wbo.cleartext.collections.bookmarks);
|
||||
|
||||
do_check_true('bookmarks' in CollectionKeys._collections);
|
||||
do_check_false('tabs' in CollectionKeys._collections);
|
||||
|
||||
/*
|
||||
* Test that we get the right keys out when we ask for
|
||||
* a collection's tokens.
|
||||
*/
|
||||
let b1 = new BulkKeyBundle(null, "bookmarks");
|
||||
b1.keyPair = [bookmarks_key64, bookmarks_hmac64];
|
||||
let b2 = CollectionKeys.keyForCollection("bookmarks");
|
||||
do_check_keypair_eq(b1.keyPair, b2.keyPair);
|
||||
|
||||
b1 = new BulkKeyBundle(null, "[default]");
|
||||
b1.keyPair = [default_key64, default_hmac64];
|
||||
b2 = CollectionKeys.keyForCollection(null);
|
||||
do_check_keypair_eq(b1.keyPair, b2.keyPair);
|
||||
|
||||
/*
|
||||
* Checking for update times.
|
||||
*/
|
||||
let info_collections = {};
|
||||
do_check_true(CollectionKeys.updateNeeded(info_collections));
|
||||
info_collections["crypto"] = 5000;
|
||||
do_check_false(CollectionKeys.updateNeeded(info_collections));
|
||||
info_collections["crypto"] = 1 + (Date.now()/1000); // Add one in case computers are fast!
|
||||
do_check_true(CollectionKeys.updateNeeded(info_collections));
|
||||
|
||||
CollectionKeys._lastModified = null;
|
||||
do_check_true(CollectionKeys.updateNeeded({}));
|
||||
}
|
||||
|
||||
// Make sure that KeyBundles work when persisted through Identity.
|
||||
function test_key_persistence() {
|
||||
_("Testing key persistence.");
|
||||
|
||||
// Create our sync key bundle and persist it.
|
||||
let k = new SyncKeyBundle(null, null, "abcdeabcdeabcdeabcdeabcdea");
|
||||
k.username = "john@example.com";
|
||||
ID.set("WeaveCryptoID", k);
|
||||
let id = ID.get("WeaveCryptoID");
|
||||
do_check_eq(k, id);
|
||||
id.persist();
|
||||
|
||||
// Now erase any memory of it.
|
||||
ID.del("WeaveCryptoID");
|
||||
k = id = null;
|
||||
|
||||
// Now recreate via the persisted value.
|
||||
id = new SyncKeyBundle();
|
||||
id.username = "john@example.com";
|
||||
|
||||
// The password should have been fetched from storage...
|
||||
do_check_eq(id.password, "abcdeabcdeabcdeabcdeabcdea");
|
||||
|
||||
// ... and we should be able to grab these by derivation.
|
||||
do_check_true(!!id.hmacKeyObject);
|
||||
do_check_true(!!id.hmacKey);
|
||||
do_check_true(!!id.encryptionKey);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_keymanager();
|
||||
test_collections_manager();
|
||||
test_key_persistence();
|
||||
}
|
|
@ -2,7 +2,6 @@ const modules = [
|
|||
"auth.js",
|
||||
"base_records/collection.js",
|
||||
"base_records/crypto.js",
|
||||
"base_records/keys.js",
|
||||
"base_records/wbo.js",
|
||||
"constants.js",
|
||||
"engines/bookmarks.js",
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
Cu.import("resource://services-sync/engines/prefs.js");
|
||||
Cu.import("resource://services-sync/type_records/prefs.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/ext/Preferences.js");
|
||||
|
||||
const PREFS_GUID = Utils.encodeBase64url(Svc.AppInfo.ID);
|
||||
|
||||
function run_test() {
|
||||
let store = new PrefsEngine()._store;
|
||||
let prefs = new Preferences();
|
||||
try {
|
||||
|
||||
_("Test fixtures.");
|
||||
Svc.Prefs.set("prefs.sync.testing.int", true);
|
||||
Svc.Prefs.set("prefs.sync.testing.string", true);
|
||||
Svc.Prefs.set("prefs.sync.testing.bool", true);
|
||||
Svc.Prefs.set("prefs.sync.testing.dont.change", true);
|
||||
Svc.Prefs.set("prefs.sync.testing.turned.off", false);
|
||||
Svc.Prefs.set("prefs.sync.testing.nonexistent", true);
|
||||
|
||||
prefs.set("testing.int", 123);
|
||||
prefs.set("testing.string", "ohai");
|
||||
prefs.set("testing.bool", true);
|
||||
prefs.set("testing.dont.change", "Please don't change me.");
|
||||
prefs.set("testing.turned.off", "I won't get synced.");
|
||||
prefs.set("testing.not.turned.on", "I won't get synced either!");
|
||||
|
||||
_("The GUID corresponds to XUL App ID.");
|
||||
let allIDs = store.getAllIDs();
|
||||
let ids = [id for (id in allIDs)];
|
||||
do_check_eq(ids.length, 1);
|
||||
do_check_eq(ids[0], PREFS_GUID);
|
||||
do_check_true(allIDs[PREFS_GUID], true);
|
||||
|
||||
do_check_true(store.itemExists(PREFS_GUID));
|
||||
do_check_false(store.itemExists("random-gibberish"));
|
||||
|
||||
_("Unknown prefs record is created as deleted.");
|
||||
let record = store.createRecord("random-gibberish", "prefs");
|
||||
do_check_true(record.deleted);
|
||||
|
||||
_("Prefs record contains only prefs that should be synced.");
|
||||
record = store.createRecord(PREFS_GUID, "prefs");
|
||||
do_check_eq(record.value["testing.int"], 123);
|
||||
do_check_eq(record.value["testing.string"], "ohai");
|
||||
do_check_eq(record.value["testing.bool"], true);
|
||||
do_check_eq(record.value["testing.nonexistent"], null);
|
||||
do_check_false("testing.turned.off" in record.value);
|
||||
do_check_false("testing.not.turned.on" in record.value);
|
||||
|
||||
_("Prefs record contains pref sync prefs too.");
|
||||
do_check_eq(record.value["services.sync.prefs.sync.testing.int"], true);
|
||||
do_check_eq(record.value["services.sync.prefs.sync.testing.string"], true);
|
||||
do_check_eq(record.value["services.sync.prefs.sync.testing.bool"], true);
|
||||
do_check_eq(record.value["services.sync.prefs.sync.testing.dont.change"], true);
|
||||
do_check_eq(record.value["services.sync.prefs.sync.testing.turned.off"], false);
|
||||
do_check_eq(record.value["services.sync.prefs.sync.testing.nonexistent"], true);
|
||||
|
||||
_("Update some prefs, including one that's to be reset/deleted.");
|
||||
Svc.Prefs.set("testing.deleteme", "I'm going to be deleted!");
|
||||
record = new PrefRec("prefs", PREFS_GUID);
|
||||
record.value = {
|
||||
"testing.int": 42,
|
||||
"testing.string": "im in ur prefs",
|
||||
"testing.bool": false,
|
||||
"testing.deleteme": null,
|
||||
"services.sync.prefs.sync.testing.somepref": true
|
||||
};
|
||||
store.update(record);
|
||||
do_check_eq(prefs.get("testing.int"), 42);
|
||||
do_check_eq(prefs.get("testing.string"), "im in ur prefs");
|
||||
do_check_eq(prefs.get("testing.bool"), false);
|
||||
do_check_eq(prefs.get("testing.deleteme"), undefined);
|
||||
do_check_eq(prefs.get("testing.dont.change"), "Please don't change me.");
|
||||
do_check_eq(Svc.Prefs.get("prefs.sync.testing.somepref"), true);
|
||||
|
||||
_("Only the current app's preferences are applied.");
|
||||
record = new PrefRec("prefs", "some-fake-app");
|
||||
record.value = {
|
||||
"testing.int": 98
|
||||
};
|
||||
store.update(record);
|
||||
do_check_eq(prefs.get("testing.int"), 42);
|
||||
|
||||
} finally {
|
||||
prefs.resetBranch("");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
Cu.import("resource://services-sync/engines/prefs.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/ext/Preferences.js");
|
||||
|
||||
function run_test() {
|
||||
let engine = new PrefsEngine();
|
||||
let tracker = engine._tracker;
|
||||
let prefs = new Preferences();
|
||||
|
||||
try {
|
||||
|
||||
_("tracker.modified corresponds to preference.");
|
||||
do_check_eq(Svc.Prefs.get("engine.prefs.modified"), undefined);
|
||||
do_check_false(tracker.modified);
|
||||
|
||||
tracker.modified = true;
|
||||
do_check_eq(Svc.Prefs.get("engine.prefs.modified"), true);
|
||||
do_check_true(tracker.modified);
|
||||
|
||||
_("Engine's getChangedID() just returns the one GUID we have.");
|
||||
let changedIDs = engine.getChangedIDs();
|
||||
let ids = [id for (id in changedIDs)];
|
||||
do_check_eq(ids.length, 1);
|
||||
do_check_eq(ids[0], Utils.encodeBase64url(Svc.AppInfo.ID));
|
||||
|
||||
Svc.Prefs.set("engine.prefs.modified", false);
|
||||
do_check_false(tracker.modified);
|
||||
|
||||
_("No modified state, so no changed IDs.");
|
||||
do_check_eq([id for (id in engine.getChangedIDs())].length, 0);
|
||||
|
||||
_("Initial score is 0");
|
||||
do_check_eq(tracker.score, 0);
|
||||
|
||||
_("Test fixtures.");
|
||||
Svc.Prefs.set("prefs.sync.testing.int", true);
|
||||
|
||||
_("Test fixtures haven't upped the tracker score yet because it hasn't started tracking yet.");
|
||||
do_check_eq(tracker.score, 0);
|
||||
|
||||
_("Tell the tracker to start tracking changes.");
|
||||
Svc.Obs.notify("weave:engine:start-tracking");
|
||||
prefs.set("testing.int", 23);
|
||||
do_check_eq(tracker.score, 25);
|
||||
do_check_eq(tracker.modified, true);
|
||||
|
||||
_("Clearing changed IDs reset modified status.");
|
||||
tracker.clearChangedIDs();
|
||||
do_check_eq(tracker.modified, false);
|
||||
|
||||
_("Resetting a pref ups the score, too.");
|
||||
prefs.reset("testing.int");
|
||||
do_check_eq(tracker.score, 50);
|
||||
do_check_eq(tracker.modified, true);
|
||||
tracker.clearChangedIDs();
|
||||
|
||||
_("So does changing a pref sync pref.");
|
||||
Svc.Prefs.set("prefs.sync.testing.int", false);
|
||||
do_check_eq(tracker.score, 150);
|
||||
do_check_eq(tracker.modified, true);
|
||||
tracker.clearChangedIDs();
|
||||
|
||||
_("Now that the pref sync pref has been flipped, changes to it won't be picked up.");
|
||||
prefs.set("testing.int", 42);
|
||||
do_check_eq(tracker.score, 150);
|
||||
do_check_eq(tracker.modified, false);
|
||||
tracker.clearChangedIDs();
|
||||
|
||||
_("Changing some other random pref won't do anything.");
|
||||
prefs.set("testing.other", "blergh");
|
||||
do_check_eq(tracker.score, 150);
|
||||
do_check_eq(tracker.modified, false);
|
||||
|
||||
} finally {
|
||||
Svc.Obs.notify("weave:engine:stop-tracking");
|
||||
prefs.resetBranch("");
|
||||
}
|
||||
}
|
|
@ -1,25 +1,11 @@
|
|||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
let keys, cryptoMeta, cryptoWrap;
|
||||
|
||||
function pubkey_handler(metadata, response) {
|
||||
let obj = {id: "pubkey",
|
||||
modified: keys.pubkey.modified,
|
||||
payload: JSON.stringify(keys.pubkey.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function privkey_handler(metadata, response) {
|
||||
let obj = {id: "privkey",
|
||||
modified: keys.privkey.modified,
|
||||
payload: JSON.stringify(keys.privkey.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
let cryptoWrap;
|
||||
|
||||
function crypted_resource_handler(metadata, response) {
|
||||
let obj = {id: "resource",
|
||||
|
@ -28,19 +14,20 @@ function crypted_resource_handler(metadata, response) {
|
|||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function crypto_meta_handler(metadata, response) {
|
||||
let obj = {id: "steam",
|
||||
modified: cryptoMeta.modified,
|
||||
payload: JSON.stringify(cryptoMeta.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
function prepareCryptoWrap(collection, id) {
|
||||
let w = new CryptoWrapper();
|
||||
w.cleartext.stuff = "my payload here";
|
||||
w.collection = collection;
|
||||
w.id = id;
|
||||
return w;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let server;
|
||||
do_test_pending();
|
||||
|
||||
let passphrase = ID.set("WeaveCryptoID", new Identity());
|
||||
passphrase.password = "passphrase";
|
||||
let keyBundle = ID.set("WeaveCryptoID", new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, "john@example.com"));
|
||||
keyBundle.keyStr = "a-abcde-abcde-abcde-abcde-abcde";
|
||||
|
||||
try {
|
||||
let log = Log4Moz.repository.getLogger("Test");
|
||||
|
@ -48,67 +35,59 @@ function run_test() {
|
|||
|
||||
log.info("Setting up server and authenticator");
|
||||
|
||||
server = httpd_setup({"/keys/pubkey": pubkey_handler,
|
||||
"/keys/privkey": privkey_handler,
|
||||
"/steam/resource": crypted_resource_handler,
|
||||
"/crypto/steam": crypto_meta_handler});
|
||||
server = httpd_setup({"/steam/resource": crypted_resource_handler});
|
||||
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
Auth.defaultAuthenticator = auth;
|
||||
|
||||
log.info("Generating keypair + symmetric key");
|
||||
|
||||
PubKeys.defaultKeyUri = "http://localhost:8080/keys/pubkey";
|
||||
keys = PubKeys.createKeypair(passphrase,
|
||||
"http://localhost:8080/keys/pubkey",
|
||||
"http://localhost:8080/keys/privkey");
|
||||
let crypto = Svc.Crypto;
|
||||
keys.symkey = crypto.generateRandomKey();
|
||||
keys.wrappedkey = crypto.wrapSymmetricKey(keys.symkey, keys.pubkey.keyData);
|
||||
|
||||
log.info("Setting up keyring");
|
||||
|
||||
cryptoMeta = new CryptoMeta("http://localhost:8080/crypto/steam", auth);
|
||||
cryptoMeta.addUnwrappedKey(keys.pubkey, keys.symkey);
|
||||
CryptoMetas.set(cryptoMeta.uri, cryptoMeta);
|
||||
|
||||
log.info("Creating a record");
|
||||
|
||||
let cryptoUri = "http://localhost:8080/crypto/steam";
|
||||
cryptoWrap = new CryptoWrapper("http://localhost:8080/steam/resource");
|
||||
cryptoWrap.encryption = cryptoUri;
|
||||
do_check_eq(cryptoWrap.encryption, cryptoUri);
|
||||
do_check_eq(cryptoWrap.payload.encryption, "../crypto/steam");
|
||||
cryptoWrap = prepareCryptoWrap("steam", "resource");
|
||||
|
||||
log.info("cryptoWrap: " + cryptoWrap.toString());
|
||||
|
||||
log.info("Encrypting a record");
|
||||
|
||||
cryptoWrap.cleartext.stuff = "my payload here";
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
cryptoWrap.encrypt(keyBundle);
|
||||
log.info("Ciphertext is " + cryptoWrap.ciphertext);
|
||||
do_check_true(cryptoWrap.ciphertext != null);
|
||||
|
||||
let firstIV = cryptoWrap.IV;
|
||||
|
||||
log.info("Decrypting the record");
|
||||
|
||||
let payload = cryptoWrap.decrypt(passphrase, cryptoUri);
|
||||
let payload = cryptoWrap.decrypt(keyBundle);
|
||||
do_check_eq(payload.stuff, "my payload here");
|
||||
do_check_neq(payload, cryptoWrap.payload); // wrap.data.payload is the encrypted one
|
||||
|
||||
log.info("Make sure multiple decrypts cause failures");
|
||||
let error = "";
|
||||
try {
|
||||
payload = cryptoWrap.decrypt(keyBundle);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "No ciphertext: nothing to decrypt?");
|
||||
|
||||
log.info("Re-encrypting the record with alternate payload");
|
||||
|
||||
cryptoWrap.cleartext.stuff = "another payload";
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
cryptoWrap.encrypt(keyBundle);
|
||||
let secondIV = cryptoWrap.IV;
|
||||
payload = cryptoWrap.decrypt(passphrase, cryptoUri);
|
||||
payload = cryptoWrap.decrypt(keyBundle);
|
||||
do_check_eq(payload.stuff, "another payload");
|
||||
|
||||
log.info("Make sure multiple encrypts use different IVs");
|
||||
do_check_neq(firstIV, secondIV);
|
||||
|
||||
log.info("Make sure differing ids cause failures");
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
cryptoWrap.encrypt(keyBundle);
|
||||
cryptoWrap.data.id = "other";
|
||||
let error = "";
|
||||
error = "";
|
||||
try {
|
||||
cryptoWrap.decrypt(passphrase, cryptoUri);
|
||||
cryptoWrap.decrypt(keyBundle);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
|
@ -116,16 +95,58 @@ function run_test() {
|
|||
do_check_eq(error, "Record id mismatch: resource,other");
|
||||
|
||||
log.info("Make sure wrong hmacs cause failures");
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
cryptoWrap.encrypt(keyBundle);
|
||||
cryptoWrap.hmac = "foo";
|
||||
error = "";
|
||||
try {
|
||||
cryptoWrap.decrypt(passphrase, cryptoUri);
|
||||
cryptoWrap.decrypt(keyBundle);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Record SHA256 HMAC mismatch: foo");
|
||||
do_check_eq(error.substr(0, 32), "Record SHA256 HMAC mismatch: foo");
|
||||
|
||||
// Checking per-collection keys and default key handling.
|
||||
|
||||
CollectionKeys.generateNewKeys();
|
||||
let bu = "http://localhost:8080/storage/bookmarks/foo";
|
||||
let bookmarkItem = prepareCryptoWrap("bookmarks", "foo");
|
||||
bookmarkItem.encrypt();
|
||||
log.info("Ciphertext is " + bookmarkItem.ciphertext);
|
||||
do_check_true(bookmarkItem.ciphertext != null);
|
||||
log.info("Decrypting the record explicitly with the default key.");
|
||||
do_check_eq(bookmarkItem.decrypt(CollectionKeys._default).stuff, "my payload here");
|
||||
|
||||
// Per-collection keys.
|
||||
// Generate a key for "bookmarks".
|
||||
CollectionKeys.generateNewKeys(["bookmarks"]);
|
||||
bookmarkItem = prepareCryptoWrap("bookmarks", "foo");
|
||||
do_check_eq(bookmarkItem.collection, "bookmarks");
|
||||
|
||||
// Encrypt. This'll use the "bookmarks" encryption key, because we have a
|
||||
// special key for it. The same key will need to be used for decryption.
|
||||
bookmarkItem.encrypt();
|
||||
do_check_true(bookmarkItem.ciphertext != null);
|
||||
|
||||
_("Default key is " + CollectionKeys._default.keyStr);
|
||||
_("Bookmarks key is " + CollectionKeys.keyForCollection("bookmarks").keyStr);
|
||||
_("Bookmarks key is " + CollectionKeys._collections["bookmarks"].keyStr);
|
||||
|
||||
// Attempt to use the default key, because this is a collision that could
|
||||
// conceivably occur in the real world. Decryption will error, because
|
||||
// it's not the bookmarks key.
|
||||
let err;
|
||||
try {
|
||||
bookmarkItem.decrypt(CollectionKeys._default);
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_eq("Record SHA256 HMAC mismatch", err.substr(0, 27));
|
||||
|
||||
// Explicitly check that it's using the bookmarks key.
|
||||
// This should succeed.
|
||||
do_check_eq(bookmarkItem.decrypt(CollectionKeys.keyForCollection("bookmarks")).stuff,
|
||||
"my payload here");
|
||||
|
||||
log.info("Done!");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
let atob = Cu.import("resource://services-sync/util.js").atob;
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
|
||||
/**
|
||||
* Testing the SHA256-HMAC key derivation process against st3fan's implementation
|
||||
* in Firefox Home.
|
||||
*/
|
||||
function run_test() {
|
||||
|
||||
// Test the production of keys from a sync key.
|
||||
let bundle = new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, "st3fan", "q7ynpwq7vsc9m34hankbyi3s3i");
|
||||
|
||||
// These should be compared to the results from Home, as they once were.
|
||||
let e = "3fe2d3743fe03d4f460ce2405ec189e68dfd7e42c97d50fab9bda3761263cc87";
|
||||
let h = "bf05f720423d297e8fd55faee7cdeaf32aa15cfb6e56115268c9c326b999795a";
|
||||
|
||||
// The encryption key is stored as base64 for handing off to WeaveCrypto.
|
||||
let realE = Utils.bytesAsHex(atob(bundle.encryptionKey));
|
||||
let realH = Utils.bytesAsHex(bundle.hmacKey);
|
||||
|
||||
_("Real E: " + realE);
|
||||
_("Real H: " + realH);
|
||||
do_check_eq(realH, h);
|
||||
do_check_eq(realE, e);
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
|
||||
function run_test() {
|
||||
let passphrase = ID.set("WeaveCryptoID", new Identity());
|
||||
passphrase.password = "passphrase";
|
||||
|
||||
_("Generating keypair to encrypt/decrypt symkeys");
|
||||
let {pubkey, privkey} = PubKeys.createKeypair(
|
||||
passphrase,
|
||||
"http://site/pubkey",
|
||||
"http://site/privkey"
|
||||
);
|
||||
PubKeys.set(pubkey.uri, pubkey);
|
||||
PrivKeys.set(privkey.uri, privkey);
|
||||
|
||||
_("Generating a crypto meta with a random key");
|
||||
let crypto = new CryptoMeta("http://site/crypto");
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
crypto.addUnwrappedKey(pubkey, symkey);
|
||||
|
||||
_("Verifying correct HMAC by getting the key");
|
||||
crypto.getKey(privkey, passphrase);
|
||||
|
||||
_("Generating a new crypto meta as the previous caches the unwrapped key");
|
||||
let crypto = new CryptoMeta("http://site/crypto");
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
crypto.addUnwrappedKey(pubkey, symkey);
|
||||
|
||||
_("Changing the HMAC to force a mismatch");
|
||||
let relUri = crypto.uri.getRelativeSpec(pubkey.uri);
|
||||
let goodHMAC = crypto.keyring[relUri].hmac;
|
||||
crypto.keyring[relUri].hmac = "failme!";
|
||||
let error = "";
|
||||
try {
|
||||
crypto.getKey(privkey, passphrase);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Key SHA256 HMAC mismatch: failme!");
|
||||
|
||||
_("Switching back to the correct HMAC and trying again");
|
||||
crypto.keyring[relUri].hmac = goodHMAC;
|
||||
crypto.getKey(privkey, passphrase);
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function pubkey_handler(metadata, response) {
|
||||
let obj = {id: "asdf-1234-asdf-1234",
|
||||
modified: "2454725.98283",
|
||||
payload: JSON.stringify({type: "pubkey",
|
||||
privateKeyUri: "http://localhost:8080/privkey",
|
||||
keyData: "asdfasdfasf..."})};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function privkey_handler(metadata, response) {
|
||||
let obj = {id: "asdf-1234-asdf-1234-2",
|
||||
modified: "2454725.98283",
|
||||
payload: JSON.stringify({type: "privkey",
|
||||
publicKeyUri: "http://localhost:8080/pubkey",
|
||||
keyData: "asdfasdfasf..."})};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function test_get() {
|
||||
let log = Log4Moz.repository.getLogger("Test");
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
log.info("Setting up authenticator");
|
||||
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
Auth.defaultAuthenticator = auth;
|
||||
|
||||
log.info("Getting a public key");
|
||||
|
||||
let pubkey = PubKeys.get("http://localhost:8080/pubkey");
|
||||
do_check_eq(pubkey.data.payload.type, "pubkey");
|
||||
do_check_eq(PubKeys.response.status, 200);
|
||||
|
||||
log.info("Getting matching private key");
|
||||
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
do_check_eq(privkey.data.payload.type, "privkey");
|
||||
do_check_eq(PrivKeys.response.status, 200);
|
||||
|
||||
log.info("Done!");
|
||||
}
|
||||
|
||||
|
||||
function test_createKeypair() {
|
||||
let passphrase = "moneyislike$\u20ac\u00a5\u5143";
|
||||
let id = ID.set('foo', new Identity('foo', 'luser'));
|
||||
id.password = passphrase;
|
||||
|
||||
_("Key pair requires URIs for both keys.");
|
||||
let error;
|
||||
try {
|
||||
let result = PubKeys.createKeypair(id);
|
||||
} catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Missing or null parameter 'pubkeyUri'.");
|
||||
|
||||
error = undefined;
|
||||
try {
|
||||
let result = PubKeys.createKeypair(id, "http://host/pub/key");
|
||||
} catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Missing or null parameter 'privkeyUri'.");
|
||||
|
||||
_("Generate a key pair.");
|
||||
let result = PubKeys.createKeypair(id, "http://host/pub/key", "http://host/priv/key");
|
||||
|
||||
_("Check that salt and IV are of correct length.");
|
||||
// 16 bytes = 24 base64 encoded characters
|
||||
do_check_eq(result.privkey.salt.length, 24);
|
||||
do_check_eq(result.privkey.iv.length, 24);
|
||||
|
||||
_("URIs are set.");
|
||||
do_check_eq(result.pubkey.uri.spec, "http://host/pub/key");
|
||||
do_check_eq(result.pubkey.privateKeyUri.spec, "http://host/priv/key");
|
||||
do_check_eq(result.pubkey.payload.privateKeyUri, "../priv/key");
|
||||
|
||||
do_check_eq(result.privkey.uri.spec, "http://host/priv/key");
|
||||
do_check_eq(result.privkey.publicKeyUri.spec, "http://host/pub/key");
|
||||
do_check_eq(result.privkey.payload.publicKeyUri, "../pub/key");
|
||||
|
||||
_("UTF8 encoded passphrase was used.");
|
||||
do_check_true(Svc.Crypto.verifyPassphrase(result.privkey.keyData,
|
||||
Utils.encodeUTF8(passphrase),
|
||||
result.privkey.salt,
|
||||
result.privkey.payload.iv));
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
do_test_pending();
|
||||
let server;
|
||||
try {
|
||||
server = httpd_setup({"/pubkey": pubkey_handler,
|
||||
"/privkey": privkey_handler});
|
||||
|
||||
test_get();
|
||||
test_createKeypair();
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
}
|
||||
}
|
|
@ -51,13 +51,10 @@ function run_test() {
|
|||
let res = new Resource("http://localhost:8080/record");
|
||||
let resp = res.get();
|
||||
|
||||
let rec = new WBORecord("http://localhost:8080/record");
|
||||
let rec = new WBORecord("coll", "record");
|
||||
rec.deserialize(res.data);
|
||||
do_check_eq(rec.id, "asdf-1234-asdf-1234"); // NOT "record"!
|
||||
|
||||
rec.uri = res.uri;
|
||||
do_check_eq(rec.id, "record"); // NOT "asdf-1234-asdf-1234"!
|
||||
|
||||
do_check_eq(rec.modified, 2454725.98283);
|
||||
do_check_eq(typeof(rec.payload), "object");
|
||||
do_check_eq(rec.payload.cheese, "roquefort");
|
||||
|
@ -72,6 +69,10 @@ function run_test() {
|
|||
do_check_eq(rec2.payload.cheese, "gruyere");
|
||||
do_check_eq(Records.response.status, 200);
|
||||
|
||||
// Testing collection extraction.
|
||||
log.info("Extracting collection.");
|
||||
let rec3 = new WBORecord("tabs", "foo"); // Create through constructor.
|
||||
do_check_eq(rec3.collection, "tabs");
|
||||
log.info("Done!");
|
||||
}
|
||||
catch (e) { do_throw(e); }
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
|
@ -62,8 +61,6 @@ function test_identities() {
|
|||
function test_urls() {
|
||||
_("URL related Service properties corresopnd to preference settings.");
|
||||
try {
|
||||
do_check_eq(PubKeys.defaultKeyUri, undefined);
|
||||
do_check_eq(PrivKeys.defaultKeyUri, undefined);
|
||||
do_check_true(!!Service.serverURL); // actual value may change
|
||||
do_check_eq(Service.clusterURL, "");
|
||||
do_check_eq(Service.userBaseURL, undefined);
|
||||
|
@ -79,8 +76,6 @@ function test_urls() {
|
|||
do_check_eq(Service.userBaseURL, undefined);
|
||||
do_check_eq(Service.storageURL, undefined);
|
||||
do_check_eq(Service.metaURL, undefined);
|
||||
do_check_eq(PubKeys.defaultKeyUri, undefined);
|
||||
do_check_eq(PrivKeys.defaultKeyUri, undefined);
|
||||
|
||||
Service.serverURL = "http://weave.server/";
|
||||
Service.clusterURL = "http://weave.cluster/";
|
||||
|
@ -93,10 +88,6 @@ function test_urls() {
|
|||
"http://weave.cluster/1.0/johndoe/storage/");
|
||||
do_check_eq(Service.metaURL,
|
||||
"http://weave.cluster/1.0/johndoe/storage/meta/global");
|
||||
do_check_eq(PubKeys.defaultKeyUri,
|
||||
"http://weave.cluster/1.0/johndoe/storage/keys/pubkey");
|
||||
do_check_eq(PrivKeys.defaultKeyUri,
|
||||
"http://weave.cluster/1.0/johndoe/storage/keys/privkey");
|
||||
|
||||
_("The 'miscURL' and 'userURL' attributes can be relative to 'serverURL' or absolute.");
|
||||
Svc.Prefs.set("miscURL", "relative/misc/");
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
Cu.import("resource://services-sync/main.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js"); // For Records.
|
||||
Cu.import("resource://services-sync/base_records/crypto.js"); // For CollectionKeys.
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
|
||||
function run_test() {
|
||||
let passphrase = "abcdeabcdeabcdeabcdeabcdea";
|
||||
let logger = Log4Moz.repository.rootLogger;
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let clients = new ServerCollection();
|
||||
let meta_global = new ServerWBO('global');
|
||||
|
||||
let collections = {};
|
||||
function info_collections(request, response) {
|
||||
let body = JSON.stringify(collections);
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/1.0/johndoe/storage/clients": clients.handler(),
|
||||
"/1.0/johndoe/storage/meta/global": meta_global.handler(),
|
||||
"/1.0/johndoe/info/collections": info_collections
|
||||
});
|
||||
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.clusterURL = "http://localhost:8080/";
|
||||
|
||||
Weave.Service.login("johndoe", "ilovejane", passphrase);
|
||||
do_check_true(Weave.Service.isLoggedIn);
|
||||
Weave.Service.verifyAndFetchSymmetricKeys();
|
||||
do_check_true(Weave.Service._remoteSetup());
|
||||
|
||||
function test_out_of_date() {
|
||||
_("meta_global: " + JSON.stringify(meta_global));
|
||||
meta_global.payload = {"syncID": "foooooooooooooooooooooooooo",
|
||||
"storageVersion": STORAGE_VERSION + 1};
|
||||
_("meta_global: " + JSON.stringify(meta_global));
|
||||
Records.set(Weave.Service.metaURL, meta_global);
|
||||
try {
|
||||
Weave.Service.sync();
|
||||
}
|
||||
catch (ex) {
|
||||
}
|
||||
do_check_eq(Status.sync, VERSION_OUT_OF_DATE);
|
||||
}
|
||||
|
||||
// See what happens when we bump the storage version.
|
||||
_("Syncing after server has been upgraded.");
|
||||
test_out_of_date();
|
||||
|
||||
// Same should happen after a wipe.
|
||||
_("Syncing after server has been upgraded and wiped.");
|
||||
Weave.Service.wipeServer();
|
||||
test_out_of_date();
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
server.stop(do_test_finished);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,15 @@ function run_test() {
|
|||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/info/collections": login_handler,
|
||||
"/1.0/janedoe/info/collections": login_handler
|
||||
"/1.0/janedoe/info/collections": login_handler,
|
||||
|
||||
// We need these handlers because we test login, and login
|
||||
// is where keys are generated or fetched.
|
||||
// TODO: have Jane fetch her keys, not generate them...
|
||||
"/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
|
||||
"/1.0/janedoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/1.0/janedoe/storage/meta/global": new ServerWBO().handler()
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -39,7 +47,7 @@ function run_test() {
|
|||
Status.service = STATUS_OK;
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
|
||||
_("Try logging in. It wont' work because we're not configured yet.");
|
||||
_("Try logging in. It won't work because we're not configured yet.");
|
||||
Service.login();
|
||||
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/main.js");
|
||||
|
||||
function run_test() {
|
||||
initTestLogging("Debug");
|
||||
|
||||
// Test fixtures:
|
||||
|
||||
// A private key with a unicode passphrase generated with Sync 1.4
|
||||
// and the corresponding public key.
|
||||
let pubkeyUri = "http://localhost:8080/1.0/foo/storage/keys/pubkey";
|
||||
let privkeyUri = "http://localhost:8080/1.0/foo/storage/keys/privkey";
|
||||
|
||||
// a bunch of currency symbols
|
||||
let passphrase = "moneyislike$\u20ac\u00a5\u5143";
|
||||
let lowbyteonly = "moneyislike$\u00ac\u00a5\u0043";
|
||||
|
||||
let sync14privkey = {
|
||||
salt: "Huk0JwJoJBXeKNioTZBQZA==",
|
||||
iv: "bHPlRmSFfkUe5IcfjnoNTA==",
|
||||
keyData: "MxAragjVoBL7erecOAaEgq/qSyLgVBFTHdEXpnP1tlEseobFsM+4z5Xhnxbows5+aL1nu50BNwr4Aao9A6Gky/Eeoo4vmWPGlQTaJgX4DDI3IrW6aXkoNzzJqvdYF2EbttnMz/x6JcXhX3KOmBgXl4bz2AgOwwyH1KCYTYhQ8PLB/BKh8vSR7gycwAqOC7OgaH1iAs4TMqy53DIlYGOdQKMxgsbQoRNGEIycBboMc35/e8PROVrp1LIUHniGtPj13J2BEi7sPGFswZbxOSnS+Zu1B8X1HVf3xlpKLGPsrD5ZWrtX/6hZS0tKR2M+ec1vIRYTGevAmICp+f6vdlp27bT9nAlSFwwHKodysNj3SyNQ2Dj6JuBkqSrjk9nV/73/8af8TF6NXZkCH9dRuI+gKrbzK1u6ikrg3ayQRzChLhqt4UvvkufMs2Pu8dIaZBgo1XSQiE+spMdsj/KEinyFeD3AnWi2WwesMSFrs0zWLHxHMgVJw0L6XXpTWPohf/uZ+O5fjULFZoyJ/Jr2AB67shm1wxkIaNIPACg9tjYstF2oyA6LsLUt1ctEXhoj/GjvnZqPXl9GGR1ElKbncKBOl9hxV4l1SEu70w/IVcluDhBIT3Fu1jX96TL+4UeRf3Qev84aY+7xSX2+OnWAFTMb6BsOcJmQXNh6PQ59eiv+AfwlxPedrWnP69+m/JfT5YvvzcEF6eiQKswOLid+O0aiHvqlhH/yxqHh5PjeGnNIWwtHO56d4tdfQsPGGF+dWGPIIw5hrG+GbdgA6nleIrZIEmxkyhVFikqSJLeFxB11+pF7Vk1wANgYPoxAh5T3CiCkX+k0KiJodqt+DThmmcgevdW3yOilmOPRkeYSlkfH9wFt+LnrJ5/LAglyOmudwc+amT6LYMUEp++NzPwk1Lx18hwXe7CQDvFaqvGB/XLQIPXqbCSdNVdRc3yADk5cA4caiOG37i2Qb/q6laJ3kjawSvSI43nkk4akGd1Gt533L1Ip1r70Tm1iV4nyeAO84CPgRPnDLR1KHxRNJZejVE4ouSIR/94veHRUDEFPrsae4JJlXVG1urmyPHXDdrWmquMkyjzbs+YB8JT1eFtT2Wqu9W8NZyMP0R57btNuXrw1qZm5QK5MmrCfqjGzx2dlv0Zito6CtQePlVjMq+gdprqB71Pl98WzvRNXZtK0uxVZjMJSLPqI1Zc9ju/lHgZgarsIFlKTGPcoqGnlUex2n8JBn9/sdiit1YLIFUMsrd7myyC6KaBFCoaFqOyFQjqD1phPBIM8fn5qp8vdsOwBHxIwfGet5UUa9jtrhu0f8ZT1FgYF2Add9bHxh8mlzsrr0dBMYC6/87hfHkcj08hRZgfG4glDlgzjVHGa4Z3KoyhdBmbFH8GRHp6BxjwdJNn9eUkT3bbelphU/wsIplwMDWbAT2fdphiwCYxH87sAiBR44PsQqOnHuzC36IE7Hes+t4f6iUzHF0yDX7txB0b069dHXMW9crXIAGzKMPcBaMn9ojeX6Y5rRfMLCN8nMQa3Bd1xU5/bgpxbsSnOgybIuHPWxMHksbQlj5x+PsLFQLYwAF5/PLjHqrDPcnybh6cJYjMI46vYJXY+A20L14YjRbqsDkO5nlJxBmV2n4s8RPpmLGo="
|
||||
};
|
||||
let sync14pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuTplSewMZI+iEuW2tQsEI06ySCht2c7oS5GHQkmVjFW3EWZwBtV+XFhq4T/XM/bhVKHYEQ8h74V14i3zFG/1+g8oQprhls59LMX0W6Q4YZqJQhw4egoS74ZkEQWSCZpQQlpCPfO339OdSUAEHl6Gn63CO06aBrsSWZm4Ue6PtoBdUMjl9Wf6ig0DKAlBxTB3XOXeGzsgnWOMcjyPloSY+LH7gf7LT+79FDCvAX0K85Qyii2kN2ScEzAgGMrGFlK8f7CBBNnjR9RKiOZT0+EzX5DgH8FfBBbbZDgGYcU0bKS262XrmnSbMzhgEl2/bH2w4xRnNdRaE0Dk+Ggi37/SwIDAQAB";
|
||||
|
||||
// A symmetric key that was wrapped with the above public key
|
||||
let symkeyData = "Vyrj8pzynsZ6+9LzLJx+myDogLMOboaBMrOi1I3Xa9tO/x5mkoGs7owQbsK1eTbfW3WUAsTpGaozzSmOR27QbVFxksSS38OZsVkT5SAmkwQ/MDPB4A6kJ7fedyTru8o/2O/XGMEQM3Qp5ApE/DJkITWPAx1U3K1f21vV7Mo4dkKkgHCNjsTMplbcA77+EyRLBaQtMaBuJhUi91RLSwKzqpVU0MdmWxgjhv+7+hd76idlaxNDMsAk1n13rYI6Sv6aQ1jqTBBsD7YEa1sShJadB9NxOnl2sugPuEqqnQN6Emb8R2Ok59NzoY+Zhk4n3QkoDeWgJSa96sQbzVa3M88ZVg==";
|
||||
let symkey = {keyring:{}};
|
||||
symkey.keyring[pubkeyUri] = {
|
||||
wrapped: symkeyData,
|
||||
hmac: "f0f5f4e4ee08dfed196455496e67af97fcf80cbc425ce8e753591023d3194635"
|
||||
};
|
||||
|
||||
// Hook these keys into the Weave API.
|
||||
let privkey = new PrivKey(privkeyUri);
|
||||
privkey.salt = sync14privkey.salt;
|
||||
privkey.iv = sync14privkey.iv;
|
||||
privkey.keyData = sync14privkey.keyData;
|
||||
privkey.publicKeyUri = pubkeyUri;
|
||||
PrivKeys.set(privkeyUri, privkey);
|
||||
|
||||
let pubkey = new PubKey(pubkeyUri);
|
||||
pubkey.keyData = sync14pubkey;
|
||||
pubkey.privateKeyUri = privkeyUri;
|
||||
PubKeys.defaultKeyUri = pubkeyUri;
|
||||
PubKeys.set(pubkeyUri, pubkey);
|
||||
|
||||
Weave.Service.username = "foo";
|
||||
Weave.Service.clusterURL = "http://localhost:8080/";
|
||||
|
||||
// Set up an engine whose bulk key we need to reupload.
|
||||
function SteamEngine() {
|
||||
Weave.SyncEngine.call(this, "Steam");
|
||||
}
|
||||
SteamEngine.prototype = Weave.SyncEngine.prototype;
|
||||
Weave.Engines.register(SteamEngine);
|
||||
|
||||
// Set up an engine whose bulk key won't have the right HMAC, so we
|
||||
// need to wipe it.
|
||||
function PetrolEngine() {
|
||||
Weave.SyncEngine.call(this, "Petrol");
|
||||
}
|
||||
PetrolEngine.prototype = Weave.SyncEngine.prototype;
|
||||
Weave.Engines.register(PetrolEngine);
|
||||
let petrol_symkey = Weave.Utils.deepCopy(symkey);
|
||||
petrol_symkey.keyring[pubkeyUri].hmac = "definitely-not-the-right-HMAC";
|
||||
|
||||
// Set up the server
|
||||
let server_privkey = new ServerWBO('privkey');
|
||||
let server_steam_key = new ServerWBO('steam', symkey);
|
||||
let server_petrol_key = new ServerWBO('petrol', petrol_symkey);
|
||||
let server_petrol_coll = new ServerCollection({
|
||||
'obj': new ServerWBO('obj', {somedata: "that's going", toget: "wiped"})
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
// Need these to make Weave.Service.wipeRemote() etc. happy
|
||||
"/1.0/foo/storage/meta/global": new ServerWBO('global', {}).handler(),
|
||||
"/1.0/foo/storage/crypto/clients": new ServerWBO('clients', {}).handler(),
|
||||
"/1.0/foo/storage/clients": new ServerCollection().handler(),
|
||||
|
||||
// Records to reupload
|
||||
"/1.0/foo/storage/keys/privkey": server_privkey.handler(),
|
||||
"/1.0/foo/storage/crypto/steam": server_steam_key.handler(),
|
||||
|
||||
// Records that are going to be wiped
|
||||
"/1.0/foo/storage/crypto/petrol": server_petrol_key.handler(),
|
||||
"/1.0/foo/storage/petrol": server_petrol_coll.handler()
|
||||
});
|
||||
|
||||
try {
|
||||
_("The original key can be decoded with both the low byte only and the full unicode passphrase.");
|
||||
do_check_true(Weave.Svc.Crypto.verifyPassphrase(sync14privkey.keyData,
|
||||
passphrase,
|
||||
sync14privkey.salt,
|
||||
sync14privkey.iv));
|
||||
do_check_true(Weave.Svc.Crypto.verifyPassphrase(sync14privkey.keyData,
|
||||
lowbyteonly,
|
||||
sync14privkey.salt,
|
||||
sync14privkey.iv));
|
||||
|
||||
_("An obviously different passphrase will not work.");
|
||||
Weave.Service.passphrase = "something completely different";
|
||||
do_check_false(Weave.Service._verifyPassphrase());
|
||||
do_check_eq(server_privkey.payload, undefined);
|
||||
|
||||
_("The right unicode passphrase will work, even though the key was generated with a low-byte only passphrase.");
|
||||
Weave.Service.passphrase = passphrase;
|
||||
do_check_true(Weave.Service._verifyPassphrase());
|
||||
|
||||
_("The _needUpdatedKeys flag is set.");
|
||||
do_check_true(Weave.Service._needUpdatedKeys);
|
||||
|
||||
_("We can now call updateKeysToUTF8Passphrase to trigger an upload of a new privkey.");
|
||||
Weave.Service._updateKeysToUTF8Passphrase();
|
||||
do_check_true(!!server_privkey.payload);
|
||||
do_check_eq(server_privkey.data.keyData, privkey.keyData);
|
||||
|
||||
_("The new key can't be decoded with the raw passphrase but the UTF8 encoded one.");
|
||||
do_check_false(Weave.Svc.Crypto.verifyPassphrase(
|
||||
server_privkey.data.keyData, passphrase,
|
||||
server_privkey.data.salt, server_privkey.data.iv)
|
||||
);
|
||||
|
||||
do_check_true(Weave.Svc.Crypto.verifyPassphrase(
|
||||
server_privkey.data.keyData, Weave.Utils.encodeUTF8(passphrase),
|
||||
server_privkey.data.salt, server_privkey.data.iv)
|
||||
);
|
||||
|
||||
_("The 'steam' bulk key has been reuploaded (though only HMAC changes).");
|
||||
let server_wrapped_key = server_steam_key.data.keyring[pubkeyUri];
|
||||
do_check_eq(server_wrapped_key.wrapped, symkeyData);
|
||||
let hmacKey = Weave.Svc.KeyFactory.keyFromString(
|
||||
Ci.nsIKeyObject.HMAC, Weave.Utils.encodeUTF8(passphrase));
|
||||
let hmac = Weave.Utils.sha256HMAC(symkeyData, hmacKey);
|
||||
do_check_eq(server_wrapped_key.hmac, hmac);
|
||||
|
||||
_("The 'petrol' bulk key had an incorrect HMAC to begin with, so it and all the data from that engine has been wiped.");
|
||||
do_check_eq(server_petrol_key.payload, undefined);
|
||||
do_check_eq(server_petrol_coll.wbos.obj.payload, undefined);
|
||||
|
||||
_("The _needUpdatedKeys flag is no longer set.");
|
||||
do_check_false(Weave.Service._needUpdatedKeys);
|
||||
|
||||
} finally {
|
||||
if (server)
|
||||
server.stop(do_test_finished);
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
|
@ -52,15 +52,19 @@ function change_password(request, response) {
|
|||
}
|
||||
|
||||
function run_test() {
|
||||
initTestLogging("Trace");
|
||||
|
||||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/info/collections": info_collections,
|
||||
"/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
|
||||
"/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/user/1.0/johndoe/password": change_password
|
||||
});
|
||||
|
||||
Service.username = "johndoe";
|
||||
Service.password = JAPANESE;
|
||||
Service.passphrase = "Must exist, but contents irrelevant.";
|
||||
Service.passphrase = "cantentsveryrelevantabbbb";
|
||||
Service.serverURL = "http://localhost:8080/";
|
||||
|
||||
try {
|
||||
|
|
|
@ -3,9 +3,12 @@ Cu.import("resource://services-sync/constants.js");
|
|||
|
||||
function run_test() {
|
||||
try {
|
||||
// Ensure we have a blank slate to start.
|
||||
Weave.Svc.Login.removeAllLogins();
|
||||
|
||||
Weave.Service.username = "johndoe";
|
||||
Weave.Service.password = "ilovejane";
|
||||
Weave.Service.passphrase = "my preciousss";
|
||||
Weave.Service.passphrase = "abbbbbcccccdddddeeeeefffff";
|
||||
|
||||
_("Confirm initial environment is empty.");
|
||||
let logins = Weave.Svc.Login.findLogins({}, PWDMGR_HOST, null,
|
||||
|
@ -30,7 +33,7 @@ function run_test() {
|
|||
PWDMGR_PASSPHRASE_REALM);
|
||||
do_check_eq(logins.length, 1);
|
||||
do_check_eq(logins[0].username, "johndoe");
|
||||
do_check_eq(logins[0].password, "my preciousss");
|
||||
do_check_eq(logins[0].password, "abbbbbcccccdddddeeeeefffff");
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
|
|
|
@ -20,6 +20,8 @@ function run_test() {
|
|||
|
||||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
|
||||
"/1.0/johndoe/info/collections": login_handler
|
||||
});
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Svc.DefaultPrefs.set("registerEngines", "");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
|
||||
initTestLogging();
|
||||
|
||||
|
@ -23,14 +24,12 @@ function sync_httpd_setup() {
|
|||
let handlers = {};
|
||||
handlers["/1.0/johndoe/info/collections"]
|
||||
= (new ServerWBO("collections", {})).handler(),
|
||||
handlers["/1.0/johndoe/storage/keys/pubkey"]
|
||||
= (new ServerWBO("pubkey")).handler();
|
||||
handlers["/1.0/johndoe/storage/keys/privkey"]
|
||||
= (new ServerWBO("privkey")).handler();
|
||||
handlers["/1.0/johndoe/storage/clients"]
|
||||
= (new ServerCollection()).handler();
|
||||
handlers["/1.0/johndoe/storage/crypto"]
|
||||
= (new ServerCollection()).handler();
|
||||
handlers["/1.0/johndoe/storage/crypto/keys"]
|
||||
= (new ServerWBO("keys", {})).handler();
|
||||
handlers["/1.0/johndoe/storage/crypto/clients"]
|
||||
= (new ServerWBO("clients", {})).handler();
|
||||
handlers["/1.0/johndoe/storage/meta/global"]
|
||||
|
@ -41,7 +40,7 @@ function sync_httpd_setup() {
|
|||
function setUp() {
|
||||
Service.username = "johndoe";
|
||||
Service.password = "ilovejane";
|
||||
Service.passphrase = "sekrit";
|
||||
Service.passphrase = "aabcdeabcdeabcdeabcdeabcde";
|
||||
Service.clusterURL = "http://localhost:8080/";
|
||||
new FakeCryptoService();
|
||||
}
|
||||
|
@ -59,6 +58,11 @@ function test_backoff500() {
|
|||
|
||||
try {
|
||||
do_check_false(Status.enforceBackoff);
|
||||
|
||||
// Forcibly create and upload keys here -- otherwise we don't get to the 500!
|
||||
CollectionKeys.generateNewKeys();
|
||||
do_check_true(CollectionKeys.asWBO().upload("http://localhost:8080/1.0/johndoe/storage/crypto/keys").success);
|
||||
|
||||
Service.login();
|
||||
Service.sync();
|
||||
do_check_true(Status.enforceBackoff);
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
Cu.import("resource://services-sync/main.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js"); // For Records.
|
||||
Cu.import("resource://services-sync/base_records/crypto.js"); // For CollectionKeys.
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
|
||||
function run_test() {
|
||||
|
@ -10,12 +14,6 @@ function run_test() {
|
|||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let guidSvc = new FakeGUIDService();
|
||||
let cryptoSvc = new FakeCryptoService();
|
||||
|
||||
let keys = new ServerCollection({privkey: new ServerWBO('privkey'),
|
||||
pubkey: new ServerWBO('pubkey')});
|
||||
let crypto = new ServerCollection({keys: new ServerWBO('keys'),
|
||||
clients: new ServerWBO('clients')});
|
||||
let clients = new ServerCollection();
|
||||
let meta_global = new ServerWBO('global');
|
||||
|
||||
|
@ -36,14 +34,7 @@ function run_test() {
|
|||
|
||||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/storage/keys": keys.handler(),
|
||||
"/1.0/johndoe/storage/keys/pubkey": wasCalledHandler(keys.wbos.pubkey),
|
||||
"/1.0/johndoe/storage/keys/privkey": wasCalledHandler(keys.wbos.privkey),
|
||||
|
||||
"/1.0/johndoe/storage/crypto": crypto.handler(),
|
||||
"/1.0/johndoe/storage/crypto/keys": crypto.wbos.keys.handler(),
|
||||
"/1.0/johndoe/storage/crypto/clients": crypto.wbos.clients.handler(),
|
||||
|
||||
"/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/1.0/johndoe/storage/clients": clients.handler(),
|
||||
"/1.0/johndoe/storage/meta/global": wasCalledHandler(meta_global),
|
||||
"/1.0/johndoe/info/collections": info_collections
|
||||
|
@ -53,55 +44,74 @@ function run_test() {
|
|||
_("Log in.");
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.clusterURL = "http://localhost:8080/";
|
||||
|
||||
_("Checking Status.sync with no credentials.");
|
||||
Weave.Service.verifyAndFetchSymmetricKeys();
|
||||
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_INVALID_PASSPHRASE);
|
||||
|
||||
Weave.Service.login("johndoe", "ilovejane", "foo");
|
||||
do_check_true(Weave.Service.isLoggedIn);
|
||||
|
||||
_("Checking that remoteSetup returns true when credentials have changed.");
|
||||
Records.get(Weave.Service.metaURL).payload.syncID = "foobar";
|
||||
do_check_true(Weave.Service._remoteSetup());
|
||||
|
||||
_("Do an initial sync.");
|
||||
let beforeSync = Date.now()/1000;
|
||||
Weave.Service.sync();
|
||||
|
||||
_("Verify that the meta record and keys was uploaded.");
|
||||
_("Checking that remoteSetup returns true.");
|
||||
do_check_true(Weave.Service._remoteSetup());
|
||||
|
||||
_("Verify that the meta record was uploaded.");
|
||||
do_check_eq(meta_global.data.syncID, Weave.Service.syncID);
|
||||
do_check_eq(meta_global.data.storageVersion, Weave.STORAGE_VERSION);
|
||||
do_check_eq(meta_global.data.engines.clients.version, Weave.Clients.version);
|
||||
do_check_eq(meta_global.data.engines.clients.syncID, Weave.Clients.syncID);
|
||||
do_check_true(!!keys.wbos.privkey.payload);
|
||||
do_check_true(!!keys.wbos.pubkey.payload);
|
||||
|
||||
_("Set the collection info hash so that sync() will remember the modified times for future runs.");
|
||||
collections = {meta: Weave.Clients.lastSync,
|
||||
keys: Weave.Clients.lastSync,
|
||||
clients: Weave.Clients.lastSync,
|
||||
crypto: Weave.Clients.lastSync};
|
||||
clients: Weave.Clients.lastSync};
|
||||
Weave.Service.sync();
|
||||
|
||||
_("Sync again and verify that meta/global and keys weren't downloaded again");
|
||||
_("Sync again and verify that meta/global wasn't downloaded again");
|
||||
meta_global.wasCalled = false;
|
||||
keys.wbos.pubkey.wasCalled = false;
|
||||
keys.wbos.privkey.wasCalled = false;
|
||||
Weave.Service.sync();
|
||||
do_check_false(meta_global.wasCalled);
|
||||
do_check_false(keys.wbos.pubkey.wasCalled);
|
||||
do_check_false(keys.wbos.privkey.wasCalled);
|
||||
|
||||
_("Fake modified records. This will cause a redownload, but not reupload since it hasn't changed.");
|
||||
collections.meta += 42;
|
||||
collections.keys += 23;
|
||||
meta_global.wasCalled = false;
|
||||
keys.wbos.pubkey.wasCalled = false;
|
||||
keys.wbos.privkey.wasCalled = false;
|
||||
|
||||
let metaModified = meta_global.modified;
|
||||
let pubkeyModified = keys.wbos.pubkey.modified;
|
||||
let privkeyModified = keys.wbos.privkey.modified;
|
||||
|
||||
Weave.Service.sync();
|
||||
do_check_true(meta_global.wasCalled);
|
||||
do_check_true(keys.wbos.privkey.wasCalled);
|
||||
do_check_true(keys.wbos.pubkey.wasCalled);
|
||||
do_check_eq(metaModified, meta_global.modified);
|
||||
do_check_eq(privkeyModified, keys.wbos.privkey.modified);
|
||||
do_check_eq(pubkeyModified, keys.wbos.pubkey.modified);
|
||||
|
||||
_("Checking bad passphrases.");
|
||||
let pp = Weave.Service.passphrase;
|
||||
Weave.Service.passphrase = "notvalid";
|
||||
do_check_false(Weave.Service.verifyAndFetchSymmetricKeys());
|
||||
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_INVALID_PASSPHRASE);
|
||||
Weave.Service.passphrase = pp;
|
||||
do_check_true(Weave.Service.verifyAndFetchSymmetricKeys());
|
||||
|
||||
// Try to screw up HMAC calculation.
|
||||
// Re-encrypt keys with a new random keybundle, and upload them to the
|
||||
// server, just as might happen with a second client.
|
||||
_("Attempting to screw up HMAC by re-encrypting keys.");
|
||||
let keys = CollectionKeys.asWBO();
|
||||
let b = new BulkKeyBundle();
|
||||
b.generateRandom();
|
||||
collections.crypto = keys.modified = 100 + (Date.now()/1000); // Future modification time.
|
||||
keys.encrypt(b);
|
||||
keys.upload(Weave.Service.cryptoKeysURL);
|
||||
|
||||
do_check_false(Weave.Service.verifyAndFetchSymmetricKeys());
|
||||
do_check_eq(Status.login, LOGIN_FAILED_INVALID_PASSPHRASE);
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
|
|
|
@ -2,7 +2,6 @@ Cu.import("resource://services-sync/engines.js");
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
|
||||
Svc.DefaultPrefs.set("registerEngines", "");
|
||||
|
@ -34,18 +33,12 @@ Engines.register(StirlingEngine);
|
|||
|
||||
|
||||
function sync_httpd_setup(handlers) {
|
||||
let collections = {};
|
||||
handlers["/1.0/johndoe/storage/crypto/keys"] = new ServerWBO().handler(),
|
||||
handlers["/1.0/johndoe/info/collections"]
|
||||
= (new ServerWBO("collections", {})).handler(),
|
||||
handlers["/1.0/johndoe/storage/keys/pubkey"]
|
||||
= (new ServerWBO("pubkey")).handler();
|
||||
handlers["/1.0/johndoe/storage/keys/privkey"]
|
||||
= (new ServerWBO("privkey")).handler();
|
||||
= (new ServerWBO("collections", collections)).handler(),
|
||||
handlers["/1.0/johndoe/storage/clients"]
|
||||
= (new ServerCollection()).handler();
|
||||
handlers["/1.0/johndoe/storage/crypto"]
|
||||
= (new ServerCollection()).handler();
|
||||
handlers["/1.0/johndoe/storage/crypto/clients"]
|
||||
= (new ServerWBO("clients", {})).handler();
|
||||
return httpd_setup(handlers);
|
||||
}
|
||||
|
||||
|
@ -55,7 +48,6 @@ function setUp() {
|
|||
Service.passphrase = "sekrit";
|
||||
Service.clusterURL = "http://localhost:8080/";
|
||||
new FakeCryptoService();
|
||||
createAndUploadKeypair();
|
||||
}
|
||||
|
||||
const PAYLOAD = 42;
|
||||
|
@ -65,7 +57,6 @@ function test_newAccount() {
|
|||
let engine = Engines.get("steam");
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": new ServerWBO("steam", {}).handler(),
|
||||
"/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -98,7 +89,6 @@ function test_enabledLocally() {
|
|||
engines: {}});
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": metaWBO.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": new ServerWBO("steam", {}).handler(),
|
||||
"/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -133,11 +123,9 @@ function test_disabledLocally() {
|
|||
engines: {steam: {syncID: engine.syncID,
|
||||
version: engine.version}}
|
||||
});
|
||||
let steamCrypto = new ServerWBO("steam", PAYLOAD);
|
||||
let steamCollection = new ServerWBO("steam", PAYLOAD);
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": metaWBO.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": steamCrypto.handler(),
|
||||
"/1.0/johndoe/storage/steam": steamCollection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -159,7 +147,6 @@ function test_disabledLocally() {
|
|||
|
||||
_("Server records are wiped.");
|
||||
do_check_eq(steamCollection.payload, undefined);
|
||||
do_check_eq(steamCrypto.payload, undefined);
|
||||
|
||||
_("Engine continues to be disabled.");
|
||||
do_check_false(engine.enabled);
|
||||
|
@ -181,7 +168,6 @@ function test_enabledRemotely() {
|
|||
});
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": metaWBO.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": new ServerWBO("steam", {}).handler(),
|
||||
"/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -215,7 +201,6 @@ function test_disabledRemotely() {
|
|||
engines: {}});
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": metaWBO.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": new ServerWBO("steam", {}).handler(),
|
||||
"/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -252,9 +237,7 @@ function test_dependentEnginesEnabledLocally() {
|
|||
engines: {}});
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": metaWBO.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": new ServerWBO("steam", {}).handler(),
|
||||
"/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler(),
|
||||
"/1.0/johndoe/storage/crypto/stirling": new ServerWBO("stirling", {}).handler(),
|
||||
"/1.0/johndoe/storage/stirling": new ServerWBO("stirling", {}).handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -295,15 +278,11 @@ function test_dependentEnginesDisabledLocally() {
|
|||
version: stirlingEngine.version}}
|
||||
});
|
||||
|
||||
let steamCrypto = new ServerWBO("steam", PAYLOAD);
|
||||
let steamCollection = new ServerWBO("steam", PAYLOAD);
|
||||
let stirlingCrypto = new ServerWBO("stirling", PAYLOAD);
|
||||
let stirlingCollection = new ServerWBO("stirling", PAYLOAD);
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/johndoe/storage/meta/global": metaWBO.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": steamCrypto.handler(),
|
||||
"/1.0/johndoe/storage/steam": steamCollection.handler(),
|
||||
"/1.0/johndoe/storage/crypto/stirling": stirlingCrypto.handler(),
|
||||
"/1.0/johndoe/storage/stirling": stirlingCollection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -328,9 +307,7 @@ function test_dependentEnginesDisabledLocally() {
|
|||
|
||||
_("Server records are wiped.");
|
||||
do_check_eq(steamCollection.payload, undefined);
|
||||
do_check_eq(steamCrypto.payload, undefined);
|
||||
do_check_eq(stirlingCollection.payload, undefined);
|
||||
do_check_eq(stirlingCrypto.payload, undefined);
|
||||
|
||||
_("Engines continue to be disabled.");
|
||||
do_check_false(steamEngine.enabled);
|
||||
|
|
|
@ -36,10 +36,15 @@ function run_test() {
|
|||
let logger = Log4Moz.repository.rootLogger;
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
// This test expects a clean slate -- no saved passphrase.
|
||||
Weave.Svc.Login.removeAllLogins();
|
||||
|
||||
do_test_pending();
|
||||
let server = httpd_setup({
|
||||
"/api/1.0/johndoe/info/collections": login_handler,
|
||||
"/api/1.0/janedoe/info/collections": service_unavailable,
|
||||
"/api/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
|
||||
"/api/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
|
||||
"/user/1.0/johndoe/node/weave": send(200, "OK", "http://localhost:8080/api/")
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
|
||||
Svc.DefaultPrefs.set("registerEngines", "");
|
||||
|
@ -30,119 +29,24 @@ function serviceUnavailable(request, response) {
|
|||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function createAndUploadKeypair() {
|
||||
let keys = PubKeys.createKeypair(ID.get("WeaveCryptoID"),
|
||||
PubKeys.defaultKeyUri,
|
||||
PrivKeys.defaultKeyUri);
|
||||
PubKeys.uploadKeypair(keys);
|
||||
}
|
||||
|
||||
function createAndUploadSymKey(url) {
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let meta = new CryptoMeta(url);
|
||||
meta.addUnwrappedKey(pubkey, symkey);
|
||||
let res = new Resource(meta.uri);
|
||||
res.put(meta);
|
||||
CryptoMetas.set(url, meta);
|
||||
}
|
||||
|
||||
function setUpTestFixtures() {
|
||||
let cryptoService = new FakeCryptoService();
|
||||
|
||||
Service.clusterURL = "http://localhost:8080/";
|
||||
Service.username = "johndoe";
|
||||
Service.passphrase = "secret";
|
||||
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/johndoe/storage/crypto/steam");
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/johndoe/storage/crypto/petrol");
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/johndoe/storage/crypto/diesel");
|
||||
Service.passphrase = "aabcdeabcdeabcdeabcdeabcde";
|
||||
}
|
||||
|
||||
function test_withCollectionList_failOnCrypto() {
|
||||
_("Service.wipeServer() deletes collections given as argument and aborts if a collection delete fails.");
|
||||
|
||||
let steam_coll = new FakeCollection();
|
||||
let petrol_coll = new FakeCollection();
|
||||
let diesel_coll = new FakeCollection();
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let crypto_diesel = new ServerWBO('diesel');
|
||||
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/storage/keys/pubkey": (new ServerWBO('pubkey')).handler(),
|
||||
"/1.0/johndoe/storage/keys/privkey": (new ServerWBO('privkey')).handler(),
|
||||
"/1.0/johndoe/storage/steam": steam_coll.handler(),
|
||||
"/1.0/johndoe/storage/petrol": petrol_coll.handler(),
|
||||
"/1.0/johndoe/storage/diesel": diesel_coll.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/johndoe/storage/crypto/petrol": serviceUnavailable,
|
||||
"/1.0/johndoe/storage/crypto/diesel": crypto_diesel.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
||||
try {
|
||||
setUpTestFixtures();
|
||||
|
||||
_("Confirm initial environment.");
|
||||
do_check_false(steam_coll.deleted);
|
||||
do_check_false(petrol_coll.deleted);
|
||||
do_check_false(diesel_coll.deleted);
|
||||
|
||||
do_check_true(crypto_steam.payload != undefined);
|
||||
do_check_true(crypto_diesel.payload != undefined);
|
||||
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/steam"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/petrol"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/diesel"));
|
||||
|
||||
_("wipeServer() will happily ignore the non-existent collection, delete the 'steam' collection and abort after an receiving an error on the 'petrol' collection's symkey.");
|
||||
let error;
|
||||
try {
|
||||
Service.wipeServer(["non-existent", "steam", "petrol", "diesel"]);
|
||||
} catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
_("wipeServer() threw this exception: " + error);
|
||||
do_check_true(error != undefined);
|
||||
|
||||
_("wipeServer stopped deleting after encountering an error with the 'petrol' collection's symkey, thus only 'steam' and 'petrol' have been deleted.");
|
||||
do_check_true(steam_coll.deleted);
|
||||
do_check_true(petrol_coll.deleted);
|
||||
do_check_false(diesel_coll.deleted);
|
||||
|
||||
do_check_true(crypto_steam.payload == undefined);
|
||||
do_check_true(crypto_diesel.payload != undefined);
|
||||
|
||||
do_check_false(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/steam"));
|
||||
do_check_false(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/petrol"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/diesel"));
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
CryptoMetas.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
function test_withCollectionList_failOnCollection() {
|
||||
function test_withCollectionList_fail() {
|
||||
_("Service.wipeServer() deletes collections given as argument.");
|
||||
|
||||
let steam_coll = new FakeCollection();
|
||||
let diesel_coll = new FakeCollection();
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let crypto_petrol = new ServerWBO('petrol');
|
||||
let crypto_diesel = new ServerWBO('diesel');
|
||||
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/storage/keys/pubkey": (new ServerWBO('pubkey')).handler(),
|
||||
"/1.0/johndoe/storage/keys/privkey": (new ServerWBO('privkey')).handler(),
|
||||
"/1.0/johndoe/storage/steam": steam_coll.handler(),
|
||||
"/1.0/johndoe/storage/petrol": serviceUnavailable,
|
||||
"/1.0/johndoe/storage/diesel": diesel_coll.handler(),
|
||||
"/1.0/johndoe/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/johndoe/storage/crypto/petrol": crypto_petrol.handler(),
|
||||
"/1.0/johndoe/storage/crypto/diesel": crypto_diesel.handler()
|
||||
"/1.0/johndoe/storage/diesel": diesel_coll.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
||||
|
@ -153,14 +57,6 @@ function test_withCollectionList_failOnCollection() {
|
|||
do_check_false(steam_coll.deleted);
|
||||
do_check_false(diesel_coll.deleted);
|
||||
|
||||
do_check_true(crypto_steam.payload != undefined);
|
||||
do_check_true(crypto_petrol.payload != undefined);
|
||||
do_check_true(crypto_diesel.payload != undefined);
|
||||
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/steam"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/petrol"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/diesel"));
|
||||
|
||||
_("wipeServer() will happily ignore the non-existent collection, delete the 'steam' collection and abort after an receiving an error on the 'petrol' collection.");
|
||||
let error;
|
||||
try {
|
||||
|
@ -175,22 +71,74 @@ function test_withCollectionList_failOnCollection() {
|
|||
do_check_true(steam_coll.deleted);
|
||||
do_check_false(diesel_coll.deleted);
|
||||
|
||||
do_check_true(crypto_steam.payload == undefined);
|
||||
do_check_true(crypto_petrol.payload != undefined);
|
||||
do_check_true(crypto_diesel.payload != undefined);
|
||||
|
||||
do_check_false(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/steam"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/petrol"));
|
||||
do_check_true(CryptoMetas.contains("http://localhost:8080/1.0/johndoe/storage/crypto/diesel"));
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
CryptoMetas.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
function test_wipeServer_leaves_collections() {
|
||||
_("Service.wipeServer() deletes everything but keys.");
|
||||
|
||||
let steam_coll = new FakeCollection();
|
||||
let diesel_coll = new FakeCollection();
|
||||
let keys_coll = new FakeCollection();
|
||||
|
||||
function info_collections(request, response) {
|
||||
let collections = {};
|
||||
let timestamp = Date.now() / 1000;
|
||||
if (!steam_coll.deleted)
|
||||
collections.steam = timestamp
|
||||
if (!diesel_coll.deleted)
|
||||
collections.diesel = timestamp;
|
||||
if (!keys_coll.deleted)
|
||||
collections.keys = timestamp;
|
||||
let body = JSON.stringify(collections);
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/storage/steam": steam_coll.handler(),
|
||||
"/1.0/johndoe/storage/diesel": diesel_coll.handler(),
|
||||
"/1.0/johndoe/storage/keys": keys_coll.handler(),
|
||||
"/1.0/johndoe/info/collections": info_collections
|
||||
});
|
||||
do_test_pending();
|
||||
|
||||
try {
|
||||
setUpTestFixtures();
|
||||
_("Info URL: " + Service.infoURL);
|
||||
|
||||
_("Confirm initial environment.");
|
||||
do_check_false(steam_coll.deleted);
|
||||
do_check_false(diesel_coll.deleted);
|
||||
do_check_false(keys_coll.deleted);
|
||||
|
||||
_("Collections: " + new Resource(Service.infoURL).get());
|
||||
_("Try deletion.");
|
||||
Service.wipeServer();
|
||||
_("Collections: " + new Resource(Service.infoURL).get());
|
||||
|
||||
_("Make sure keys is still present.");
|
||||
do_check_true(steam_coll.deleted);
|
||||
do_check_true(diesel_coll.deleted);
|
||||
do_check_false(keys_coll.deleted);
|
||||
|
||||
_("Delete everything.");
|
||||
Service.wipeServer(null, true);
|
||||
do_check_true(steam_coll.deleted);
|
||||
do_check_true(diesel_coll.deleted);
|
||||
do_check_true(keys_coll.deleted);
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_withCollectionList_failOnCollection();
|
||||
test_withCollectionList_failOnCrypto();
|
||||
initTestLogging("Trace");
|
||||
test_withCollectionList_fail();
|
||||
test_wipeServer_leaves_collections();
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ function run_test() {
|
|||
do_check_true(!!id);
|
||||
|
||||
_("Let's provide a passphrase");
|
||||
id.password = "chickeninacan";
|
||||
id.keyStr = "a-bcdef-abcde-acbde-acbde-acbde";
|
||||
do_check_eq(Status.checkSetup(), STATUS_OK);
|
||||
Status.resetSync();
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ function test_url_attributes() {
|
|||
try {
|
||||
do_check_eq(engine.storageURL, "https://cluster/1.0/foo/storage/");
|
||||
do_check_eq(engine.engineURL, "https://cluster/1.0/foo/storage/steam");
|
||||
do_check_eq(engine.cryptoMetaURL,
|
||||
"https://cluster/1.0/foo/storage/crypto/steam");
|
||||
do_check_eq(engine.metaURL, "https://cluster/1.0/foo/storage/meta/global");
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
|
@ -97,10 +95,8 @@ function test_wipeServer() {
|
|||
let engine = makeSteamEngine();
|
||||
|
||||
const PAYLOAD = 42;
|
||||
let steamCrypto = new ServerWBO("steam", PAYLOAD);
|
||||
let steamCollection = new ServerWBO("steam", PAYLOAD);
|
||||
let server = httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": steamCrypto.handler(),
|
||||
"/1.0/foo/storage/steam": steamCollection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
|
@ -110,15 +106,10 @@ function test_wipeServer() {
|
|||
engine.lastSync = 123.45;
|
||||
|
||||
_("Wipe server data and reset client.");
|
||||
engine.wipeServer(true);
|
||||
engine.wipeServer();
|
||||
do_check_eq(steamCollection.payload, undefined);
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
|
||||
_("We passed a truthy arg earlier in which case it doesn't wipe the crypto collection.");
|
||||
do_check_eq(steamCrypto.payload, PAYLOAD);
|
||||
engine.wipeServer();
|
||||
do_check_eq(steamCrypto.payload, undefined);
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
|
@ -15,8 +14,8 @@ Cu.import("resource://services-sync/util.js");
|
|||
* Complete with record, store, and tracker implementations.
|
||||
*/
|
||||
|
||||
function SteamRecord(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
function SteamRecord(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
SteamRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype
|
||||
|
@ -46,8 +45,8 @@ SteamStore.prototype = {
|
|||
return (id in this.items);
|
||||
},
|
||||
|
||||
createRecord: function(id, uri) {
|
||||
var record = new SteamRecord(uri);
|
||||
createRecord: function(id, collection) {
|
||||
var record = new SteamRecord(collection, id);
|
||||
record.denomination = this.items[id] || "Data for new record: " + id;
|
||||
return record;
|
||||
},
|
||||
|
@ -111,10 +110,6 @@ var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
|||
function sync_httpd_setup(handlers) {
|
||||
handlers["/1.0/foo/storage/meta/global"]
|
||||
= (new ServerWBO('global', {})).handler();
|
||||
handlers["/1.0/foo/storage/keys/pubkey"]
|
||||
= (new ServerWBO('pubkey')).handler();
|
||||
handlers["/1.0/foo/storage/keys/privkey"]
|
||||
= (new ServerWBO('privkey')).handler();
|
||||
return httpd_setup(handlers);
|
||||
}
|
||||
|
||||
|
@ -146,11 +141,10 @@ function encryptPayload(cleartext) {
|
|||
*/
|
||||
|
||||
function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() {
|
||||
_("SyncEngine._syncStartup resets sync and wipes server data if there's no or an oudated global record");
|
||||
_("SyncEngine._syncStartup resets sync and wipes server data if there's no or an outdated global record");
|
||||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
|
||||
// Some server side data that's going to be wiped
|
||||
let collection = new ServerCollection();
|
||||
|
@ -162,18 +156,15 @@ function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() {
|
|||
denomination: "Flying Scotsman"}));
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
engine._store.items = {rekolok: "Rekonstruktionslokomotive"};
|
||||
try {
|
||||
|
||||
// Confirm initial environment
|
||||
do_check_eq(crypto_steam.payload, undefined);
|
||||
do_check_eq(engine._tracker.changedIDs["rekolok"], undefined);
|
||||
let metaGlobal = Records.get(engine.metaURL);
|
||||
do_check_eq(metaGlobal.payload.engines, undefined);
|
||||
|
@ -182,6 +173,9 @@ function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() {
|
|||
|
||||
engine.lastSync = Date.now() / 1000;
|
||||
engine.lastSyncLocal = Date.now();
|
||||
|
||||
// Trying to prompt a wipe -- we no longer track CryptoMeta per engine,
|
||||
// so it has nothing to check.
|
||||
engine._syncStartup();
|
||||
|
||||
// The meta/global WBO has been filled with data about the engine
|
||||
|
@ -191,110 +185,9 @@ function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() {
|
|||
|
||||
// Sync was reset and server data was wiped
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
do_check_eq(engine.lastSyncLocal, 0);
|
||||
do_check_eq(collection.wbos.flying.payload, undefined);
|
||||
do_check_eq(collection.wbos.scotsman.payload, undefined);
|
||||
|
||||
// Bulk key was uploaded
|
||||
do_check_true(!!crypto_steam.payload);
|
||||
do_check_true(!!crypto_steam.data.keyring);
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
||||
function test_syncStartup_metaGet404() {
|
||||
_("SyncEngine._syncStartup resets sync and wipes server data if the symmetric key is missing 404");
|
||||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
|
||||
// A symmetric key with an incorrect HMAC
|
||||
let crypto_steam = new ServerWBO("steam");
|
||||
|
||||
// A proper global record with matching version and syncID
|
||||
let engine = makeSteamEngine();
|
||||
let global = new ServerWBO("global",
|
||||
{engines: {steam: {version: engine.version,
|
||||
syncID: engine.syncID}}});
|
||||
|
||||
// Some server side data that's going to be wiped
|
||||
let collection = new ServerCollection();
|
||||
collection.wbos.flying = new ServerWBO(
|
||||
"flying", encryptPayload({id: "flying",
|
||||
denomination: "LNER Class A3 4472"}));
|
||||
collection.wbos.scotsman = new ServerWBO(
|
||||
"scotsman", encryptPayload({id: "scotsman",
|
||||
denomination: "Flying Scotsman"}));
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
|
||||
try {
|
||||
|
||||
_("Confirm initial environment");
|
||||
do_check_false(!!crypto_steam.payload);
|
||||
do_check_true(!!collection.wbos.flying.payload);
|
||||
do_check_true(!!collection.wbos.scotsman.payload);
|
||||
|
||||
engine.lastSync = Date.now() / 1000;
|
||||
engine.lastSyncLocal = Date.now();
|
||||
engine._syncStartup();
|
||||
|
||||
_("Sync was reset and server data was wiped");
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
do_check_eq(engine.lastSyncLocal, 0);
|
||||
do_check_eq(collection.wbos.flying.payload, undefined);
|
||||
do_check_eq(collection.wbos.scotsman.payload, undefined);
|
||||
|
||||
_("New bulk key was uploaded");
|
||||
let key = crypto_steam.data.keyring["../keys/pubkey"];
|
||||
do_check_eq(key.wrapped, "fake-symmetric-key-0");
|
||||
do_check_eq(key.hmac, "fake-symmetric-key-0 ");
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
||||
function test_syncStartup_failedMetaGet() {
|
||||
_("SyncEngine._syncStartup non-404 failures for getting cryptometa should stop sync");
|
||||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let server = httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": function(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
|
||||
response.bodyOutputStream.write("Fail!", 5);
|
||||
}
|
||||
});
|
||||
do_test_pending();
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
|
||||
_("Getting the cryptometa will fail and should set the appropriate failure");
|
||||
let error;
|
||||
try {
|
||||
engine._syncStartup();
|
||||
} catch (ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error.failureCode, ENGINE_METARECORD_DOWNLOAD_FAIL);
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
|
@ -341,10 +234,7 @@ function test_syncStartup_syncIDMismatchResetsClient() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler()
|
||||
});
|
||||
let server = sync_httpd_setup({});
|
||||
do_test_pending();
|
||||
|
||||
// global record with a different syncID than our engine has
|
||||
|
@ -354,13 +244,10 @@ function test_syncStartup_syncIDMismatchResetsClient() {
|
|||
syncID: 'foobar'}}});
|
||||
server.registerPathHandler("/1.0/foo/storage/meta/global", global.handler());
|
||||
|
||||
createAndUploadKeypair();
|
||||
|
||||
try {
|
||||
|
||||
// Confirm initial environment
|
||||
do_check_eq(engine.syncID, 'fake-guid-0');
|
||||
do_check_eq(crypto_steam.payload, undefined);
|
||||
do_check_eq(engine._tracker.changedIDs["rekolok"], undefined);
|
||||
|
||||
engine.lastSync = Date.now() / 1000;
|
||||
|
@ -372,86 +259,11 @@ function test_syncStartup_syncIDMismatchResetsClient() {
|
|||
|
||||
// Sync was reset
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
do_check_eq(engine.lastSyncLocal, 0);
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test_syncStartup_badKeyWipesServerData() {
|
||||
_("SyncEngine._syncStartup resets sync and wipes server data if there's something wrong with the symmetric key");
|
||||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
|
||||
// A symmetric key with an incorrect HMAC
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
crypto_steam.payload = JSON.stringify({
|
||||
keyring: {
|
||||
"http://localhost:8080/1.0/foo/storage/keys/pubkey": {
|
||||
wrapped: Svc.Crypto.generateRandomKey(),
|
||||
hmac: "this-hmac-is-incorrect"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// A proper global record with matching version and syncID
|
||||
let engine = makeSteamEngine();
|
||||
let global = new ServerWBO('global',
|
||||
{engines: {steam: {version: engine.version,
|
||||
syncID: engine.syncID}}});
|
||||
|
||||
// Some server side data that's going to be wiped
|
||||
let collection = new ServerCollection();
|
||||
collection.wbos.flying = new ServerWBO(
|
||||
'flying', encryptPayload({id: 'flying',
|
||||
denomination: "LNER Class A3 4472"}));
|
||||
collection.wbos.scotsman = new ServerWBO(
|
||||
'scotsman', encryptPayload({id: 'scotsman',
|
||||
denomination: "Flying Scotsman"}));
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
|
||||
try {
|
||||
|
||||
// Confirm initial environment
|
||||
let key = crypto_steam.data.keyring["http://localhost:8080/1.0/foo/storage/keys/pubkey"];
|
||||
do_check_eq(key.wrapped, "fake-symmetric-key-0");
|
||||
do_check_eq(key.hmac, "this-hmac-is-incorrect");
|
||||
do_check_true(!!collection.wbos.flying.payload);
|
||||
do_check_true(!!collection.wbos.scotsman.payload);
|
||||
|
||||
engine.lastSync = Date.now() / 1000;
|
||||
engine.lastSyncLocal = Date.now();
|
||||
engine._syncStartup();
|
||||
|
||||
// Sync was reset and server data was wiped
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
do_check_eq(engine.lastSyncLocal, 0);
|
||||
do_check_eq(collection.wbos.flying.payload, undefined);
|
||||
do_check_eq(collection.wbos.scotsman.payload, undefined);
|
||||
|
||||
// New bulk key was uploaded
|
||||
key = crypto_steam.data.keyring["../keys/pubkey"];
|
||||
do_check_eq(key.wrapped, "fake-symmetric-key-1");
|
||||
do_check_eq(key.hmac, "fake-symmetric-key-1 ");
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -462,15 +274,12 @@ function test_processIncoming_emptyServer() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let collection = new ServerCollection();
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
|
@ -483,7 +292,6 @@ function test_processIncoming_emptyServer() {
|
|||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +302,8 @@ function test_processIncoming_createFromServer() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
// Some server records that will be downloaded
|
||||
let collection = new ServerCollection();
|
||||
|
@ -509,20 +318,13 @@ function test_processIncoming_createFromServer() {
|
|||
collection.wbos['../pathological'] = new ServerWBO(
|
||||
'../pathological', encryptPayload({id: '../pathological',
|
||||
denomination: "Pathological Case"}));
|
||||
let wrong_keyuri = encryptPayload({id: "wrong_keyuri",
|
||||
denomination: "Wrong Key URI"});
|
||||
wrong_keyuri.encryption = "../../crypto/steam";
|
||||
collection.wbos["wrong_keyuri"] = new ServerWBO("wrong_keyuri", wrong_keyuri);
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler(),
|
||||
"/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(),
|
||||
"/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
|
||||
|
@ -537,7 +339,6 @@ function test_processIncoming_createFromServer() {
|
|||
do_check_eq(engine._store.items.flying, undefined);
|
||||
do_check_eq(engine._store.items.scotsman, undefined);
|
||||
do_check_eq(engine._store.items['../pathological'], undefined);
|
||||
do_check_eq(engine._store.items.wrong_keyuri, undefined);
|
||||
|
||||
engine._syncStartup();
|
||||
engine._processIncoming();
|
||||
|
@ -550,13 +351,11 @@ function test_processIncoming_createFromServer() {
|
|||
do_check_eq(engine._store.items.flying, "LNER Class A3 4472");
|
||||
do_check_eq(engine._store.items.scotsman, "Flying Scotsman");
|
||||
do_check_eq(engine._store.items['../pathological'], "Pathological Case");
|
||||
do_check_eq(engine._store.items.wrong_keyuri, "Wrong Key URI");
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -567,7 +366,6 @@ function test_processIncoming_reconcile() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let collection = new ServerCollection();
|
||||
|
||||
// This server record is newer than the corresponding client one,
|
||||
|
@ -616,12 +414,9 @@ function test_processIncoming_reconcile() {
|
|||
deleted: true}));
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
engine._store.items = {newerserver: "New data, but not as new as server!",
|
||||
|
@ -683,7 +478,6 @@ function test_processIncoming_reconcile() {
|
|||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -717,12 +511,9 @@ function test_processIncoming_mobile_batchSize() {
|
|||
}
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
|
||||
|
@ -761,7 +552,6 @@ function test_processIncoming_mobile_batchSize() {
|
|||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -772,20 +562,17 @@ function test_uploadOutgoing_toEmptyServer() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let collection = new ServerCollection();
|
||||
collection.wbos.flying = new ServerWBO('flying');
|
||||
collection.wbos.scotsman = new ServerWBO('scotsman');
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler(),
|
||||
"/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(),
|
||||
"/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
engine.lastSync = 123; // needs to be non-zero so that tracker is queried
|
||||
|
@ -826,7 +613,6 @@ function test_uploadOutgoing_toEmptyServer() {
|
|||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -837,19 +623,15 @@ function test_uploadOutgoing_failed() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let collection = new ServerCollection();
|
||||
// We only define the "flying" WBO on the server, not the "scotsman"
|
||||
// and "peppercorn" ones.
|
||||
collection.wbos.flying = new ServerWBO('flying');
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
engine.lastSync = 123; // needs to be non-zero so that tracker is queried
|
||||
|
@ -877,8 +659,8 @@ function test_uploadOutgoing_failed() {
|
|||
do_check_eq(engine._tracker.changedIDs['scotsman'], SCOTSMAN_CHANGED);
|
||||
do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
|
||||
|
||||
engine._syncStartup();
|
||||
engine._uploadOutgoing();
|
||||
engine.enabled = true;
|
||||
engine.sync();
|
||||
|
||||
// Local timestamp has been set.
|
||||
do_check_true(engine.lastSyncLocal > 0);
|
||||
|
@ -896,7 +678,6 @@ function test_uploadOutgoing_failed() {
|
|||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -907,7 +688,6 @@ function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
|
|||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let collection = new ServerCollection();
|
||||
|
||||
// Let's count how many times the client posts to the server
|
||||
|
@ -933,12 +713,9 @@ function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
|
|||
syncID: engine.syncID}};
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
|
||||
try {
|
||||
|
||||
|
@ -960,7 +737,6 @@ function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
|
|||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
@ -1095,48 +871,53 @@ function test_syncFinish_deleteLotsInBatches() {
|
|||
}
|
||||
}
|
||||
|
||||
function test_sync_rollback() {
|
||||
_("SyncEngine.sync() rolls back tracker's changedIDs when syncing fails.");
|
||||
|
||||
function test_sync_partialUpload() {
|
||||
_("SyncEngine.sync() keeps changedIDs that couldn't be uploaded.");
|
||||
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
|
||||
// Set up a server without a "steam" collection handler, so sync will fail.
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
let collection = new ServerCollection();
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler()
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
engine.lastSync = 123; // needs to be non-zero so that tracker is queried
|
||||
engine.lastSyncLocal = 456;
|
||||
engine._store.items = {flying: "LNER Class A3 4472",
|
||||
scotsman: "Flying Scotsman",
|
||||
peppercorn: "Peppercorn Class"};
|
||||
|
||||
// Mark these records as changed
|
||||
const FLYING_CHANGED = 12345;
|
||||
const SCOTSMAN_CHANGED = 23456;
|
||||
const PEPPERCORN_CHANGED = 34567;
|
||||
engine._tracker.addChangedID('flying', FLYING_CHANGED);
|
||||
engine._tracker.addChangedID('scotsman', SCOTSMAN_CHANGED);
|
||||
engine._tracker.addChangedID('peppercorn', PEPPERCORN_CHANGED);
|
||||
// Let the third upload fail completely
|
||||
var noOfUploads = 0;
|
||||
collection.post = (function(orig) {
|
||||
return function() {
|
||||
if (noOfUploads == 2)
|
||||
throw "FAIL!";
|
||||
noOfUploads++;
|
||||
return orig.apply(this, arguments);
|
||||
};
|
||||
}(collection.post));
|
||||
|
||||
// Create a bunch of records (and server side handlers)
|
||||
for (let i = 0; i < 234; i++) {
|
||||
let id = 'record-no-' + i;
|
||||
engine._store.items[id] = "Record No. " + i;
|
||||
engine._tracker.addChangedID(id, i);
|
||||
// Let two items in the first upload batch fail.
|
||||
if ((i != 23) && (i != 42))
|
||||
collection.wbos[id] = new ServerWBO(id);
|
||||
}
|
||||
|
||||
let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
|
||||
meta_global.payload.engines = {steam: {version: engine.version,
|
||||
syncID: engine.syncID}};
|
||||
|
||||
|
||||
try {
|
||||
|
||||
// Confirm initial environment
|
||||
do_check_eq(engine.lastSyncLocal, 456);
|
||||
do_check_eq(engine._tracker.changedIDs['flying'], FLYING_CHANGED);
|
||||
do_check_eq(engine._tracker.changedIDs['scotsman'], SCOTSMAN_CHANGED);
|
||||
do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
|
||||
|
||||
engine.enabled = true;
|
||||
let error;
|
||||
try {
|
||||
|
@ -1146,26 +927,37 @@ function test_sync_rollback() {
|
|||
}
|
||||
do_check_true(!!error);
|
||||
|
||||
// Verify that the tracker state and local timestamp has been rolled back.
|
||||
do_check_eq(engine.lastSyncLocal, 456);
|
||||
do_check_eq(engine._tracker.changedIDs['flying'], FLYING_CHANGED);
|
||||
do_check_eq(engine._tracker.changedIDs['scotsman'], SCOTSMAN_CHANGED);
|
||||
do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
|
||||
// The timestamp has been updated.
|
||||
do_check_true(engine.lastSyncLocal > 456);
|
||||
|
||||
for (let i = 0; i < 234; i++) {
|
||||
let id = 'record-no-' + i;
|
||||
// Ensure failed records are back in the tracker:
|
||||
// * records no. 23 and 42 were rejected by the server,
|
||||
// * records no. 200 and higher couldn't be uploaded because we failed
|
||||
// hard on the 3rd upload.
|
||||
if ((i == 23) || (i == 42) || (i >= 200))
|
||||
do_check_eq(engine._tracker.changedIDs[id], i);
|
||||
else
|
||||
do_check_false(id in engine._tracker.changedIDs);
|
||||
}
|
||||
|
||||
} finally {
|
||||
server.stop(do_test_finished);
|
||||
Svc.Prefs.resetBranch("");
|
||||
Records.clearCache();
|
||||
CryptoMetas.clearCache();
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
||||
function test_canDecrypt_noCryptoMeta() {
|
||||
_("SyncEngine.canDecrypt returns false if the engine fails to decrypt items on the server, e.g. due to a missing crypto key.");
|
||||
function test_canDecrypt_noCryptoKeys() {
|
||||
_("SyncEngine.canDecrypt returns false if the engine fails to decrypt items on the server, e.g. due to a missing crypto key collection.");
|
||||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
|
||||
// Wipe CollectionKeys so we can test the desired scenario.
|
||||
CollectionKeys.setContents({"collections": {}, "default": null});
|
||||
|
||||
let collection = new ServerCollection();
|
||||
collection.wbos.flying = new ServerWBO(
|
||||
'flying', encryptPayload({id: 'flying',
|
||||
|
@ -1175,7 +967,6 @@ function test_canDecrypt_noCryptoMeta() {
|
|||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
|
@ -1195,19 +986,18 @@ function test_canDecrypt_true() {
|
|||
Svc.Prefs.set("clusterURL", "http://localhost:8080/");
|
||||
Svc.Prefs.set("username", "foo");
|
||||
|
||||
let crypto_steam = new ServerWBO('steam');
|
||||
// Set up CollectionKeys, as service.js does.
|
||||
CollectionKeys.generateNewKeys();
|
||||
|
||||
let collection = new ServerCollection();
|
||||
collection.wbos.flying = new ServerWBO(
|
||||
'flying', encryptPayload({id: 'flying',
|
||||
denomination: "LNER Class A3 4472"}));
|
||||
|
||||
let server = sync_httpd_setup({
|
||||
"/1.0/foo/storage/crypto/steam": crypto_steam.handler(),
|
||||
"/1.0/foo/storage/steam": collection.handler()
|
||||
});
|
||||
do_test_pending();
|
||||
createAndUploadKeypair();
|
||||
createAndUploadSymKey("http://localhost:8080/1.0/foo/storage/crypto/steam");
|
||||
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
|
@ -1228,11 +1018,8 @@ function run_test() {
|
|||
return;
|
||||
|
||||
test_syncStartup_emptyOrOutdatedGlobalsResetsSync();
|
||||
test_syncStartup_metaGet404();
|
||||
test_syncStartup_failedMetaGet();
|
||||
test_syncStartup_serverHasNewerVersion();
|
||||
test_syncStartup_syncIDMismatchResetsClient();
|
||||
test_syncStartup_badKeyWipesServerData();
|
||||
test_processIncoming_emptyServer();
|
||||
test_processIncoming_createFromServer();
|
||||
test_processIncoming_reconcile();
|
||||
|
@ -1243,7 +1030,7 @@ function run_test() {
|
|||
test_syncFinish_noDelete();
|
||||
test_syncFinish_deleteByIds();
|
||||
test_syncFinish_deleteLotsInBatches();
|
||||
test_sync_rollback();
|
||||
test_canDecrypt_noCryptoMeta();
|
||||
test_sync_partialUpload();
|
||||
test_canDecrypt_noCryptoKeys();
|
||||
test_canDecrypt_true();
|
||||
}
|
||||
|
|
|
@ -101,13 +101,13 @@ function test_createRecord() {
|
|||
|
||||
_("create a record");
|
||||
fakeSessionSvc("http://foo.com");
|
||||
record = store.createRecord("fake-guid", "http://fake.uri/");
|
||||
record = store.createRecord("fake-guid");
|
||||
do_check_true(record instanceof TabSetRecord);
|
||||
do_check_eq(record.tabs.length, 1);
|
||||
|
||||
_("create a big record");
|
||||
fakeSessionSvc("http://foo.com", numtabs);
|
||||
record = store.createRecord("fake-guid", "http://fake.uri/");
|
||||
record = store.createRecord("fake-guid");
|
||||
do_check_true(record instanceof TabSetRecord);
|
||||
do_check_eq(record.tabs.length, 256);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let data = ["Zm9vYmE=", "Zm9vYmE==", "Zm9vYmE==="];
|
||||
for (let d in data) {
|
||||
do_check_eq(Utils.safeAtoB(data[d]), "fooba");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
let cryptoSvc;
|
||||
try {
|
||||
Components.utils.import("resource://services-crypto/WeaveCrypto.js");
|
||||
cryptoSvc = new WeaveCrypto();
|
||||
} catch (ex) {
|
||||
// Fallback to binary WeaveCrypto
|
||||
cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"]
|
||||
.getService(Ci.IWeaveCrypto);
|
||||
}
|
||||
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
var der_passphrase = "secret phrase";
|
||||
var der_salt = "RE5YUHpQcGl3bg=="; // btoa("DNXPzPpiwn")
|
||||
|
||||
_("Testing deriveKeyFromPassphrase. Input is \"" + der_passphrase + "\", \"" + der_salt + "\" (base64-encoded).");
|
||||
|
||||
// Test friendly-ing.
|
||||
do_check_eq("abcdefghijk8mn9pqrstuvwxyz234567",
|
||||
Utils.base32ToFriendly("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"));
|
||||
do_check_eq("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
|
||||
Utils.base32FromFriendly(
|
||||
Utils.base32ToFriendly("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")));
|
||||
|
||||
// Test translation.
|
||||
do_check_false(Utils.isPassphrase("o-5wmnu-o5tqc-7lz2h-amkbw-izqzi")); // Wrong charset.
|
||||
do_check_false(Utils.isPassphrase("O-5WMNU-O5TQC-7LZ2H-AMKBW-IZQZI")); // Wrong charset.
|
||||
do_check_true(Utils.isPassphrase("9-5wmnu-95tqc-78z2h-amkbw-izqzi"));
|
||||
do_check_true(Utils.isPassphrase("9-5WMNU-95TQC-78Z2H-AMKBW-IZQZI")); // isPassphrase normalizes.
|
||||
do_check_true(Utils.isPassphrase(
|
||||
Utils.normalizePassphrase("9-5WMNU-95TQC-78Z2H-AMKBW-IZQZI")));
|
||||
|
||||
// Base64. We don't actually use this in anger, particularly not with a 32-byte key.
|
||||
var der_key = Utils.deriveEncodedKeyFromPassphrase(der_passphrase, der_salt);
|
||||
_("Derived key in base64: " + der_key);
|
||||
do_check_eq(cryptoSvc.decrypt(cryptoSvc.encrypt("bacon", der_key, iv), der_key, iv), "bacon");
|
||||
|
||||
// Test the equivalence of our NSS and JS versions.
|
||||
// Will only work on FF4, of course.
|
||||
do_check_eq(
|
||||
Utils.deriveEncodedKeyFromPassphrase(der_passphrase, der_salt, 16, false),
|
||||
Utils.deriveEncodedKeyFromPassphrase(der_passphrase, der_salt, 16, true));
|
||||
|
||||
// Base64, 16-byte output.
|
||||
var der_key = Utils.deriveEncodedKeyFromPassphrase(der_passphrase, der_salt, 16);
|
||||
_("Derived key in base64: " + der_key);
|
||||
do_check_eq("d2zG0d2cBfXnRwMUGyMwyg==", der_key);
|
||||
do_check_eq(cryptoSvc.decrypt(cryptoSvc.encrypt("bacon", der_key, iv), der_key, iv), "bacon");
|
||||
|
||||
// Base32. Again, specify '16' to avoid it generating a 256-bit key string.
|
||||
var b32key = Utils.derivePresentableKeyFromPassphrase(der_passphrase, der_salt, 16);
|
||||
var hyphenated = Utils.hyphenatePassphrase(b32key);
|
||||
do_check_true(Utils.isPassphrase(b32key));
|
||||
|
||||
_("Derived key in base32: " + b32key);
|
||||
do_check_eq(b32key.length, 26);
|
||||
do_check_eq(hyphenated.length, 31); // 1 char, plus 5 groups of 5, hyphenated = 5 + (5*5) + 1 = 31.
|
||||
do_check_eq(hyphenated, "9-5wmnu-95tqc-78z2h-amkbw-izqzi");
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
// Testing byte array manipulation.
|
||||
do_check_eq("FOOBAR", Utils.byteArrayToString([70, 79, 79, 66, 65, 82]));
|
||||
do_check_eq("", Utils.byteArrayToString([]));
|
||||
|
||||
_("Testing encoding...");
|
||||
// Test vectors from RFC 4648
|
||||
do_check_eq(Utils.encodeBase32(""), "");
|
||||
do_check_eq(Utils.encodeBase32("f"), "MY======");
|
||||
|
@ -12,4 +17,42 @@ function run_test() {
|
|||
|
||||
do_check_eq(Utils.encodeBase32("Bacon is a vegetable."),
|
||||
"IJQWG33OEBUXGIDBEB3GKZ3FORQWE3DFFY======");
|
||||
|
||||
_("Checking assumptions...");
|
||||
for (let i = 0; i <= 255; ++i)
|
||||
do_check_eq(undefined | i, i);
|
||||
|
||||
_("Testing decoding...");
|
||||
do_check_eq(Utils.decodeBase32(""), "");
|
||||
do_check_eq(Utils.decodeBase32("MY======"), "f");
|
||||
do_check_eq(Utils.decodeBase32("MZXQ===="), "fo");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTB"), "fooba");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI======"), "foobar");
|
||||
|
||||
// Same with incorrect or missing padding.
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI=="), "foobar");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI"), "foobar");
|
||||
|
||||
let encoded = Utils.encodeBase32("Bacon is a vegetable.");
|
||||
_("Encoded to " + JSON.stringify(encoded));
|
||||
do_check_eq(Utils.decodeBase32(encoded), "Bacon is a vegetable.");
|
||||
|
||||
// Test failure.
|
||||
let err;
|
||||
try {
|
||||
Utils.decodeBase32("000");
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_eq(err, "Unknown character in base32: 0");
|
||||
|
||||
// Testing our own variant.
|
||||
do_check_eq(Utils.encodeKeyBase32("foobarbafoobarba"), "mzxw6ytb9jrgcztpn5rgc4tcme");
|
||||
do_check_eq(Utils.decodeKeyBase32("mzxw6ytb9jrgcztpn5rgc4tcme"), "foobarbafoobarba");
|
||||
do_check_eq(
|
||||
Utils.encodeKeyBase32("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"),
|
||||
"aeaqcaibaeaqcaibaeaqcaibae");
|
||||
do_check_eq(
|
||||
Utils.decodeKeyBase32("aeaqcaibaeaqcaibaeaqcaibae"),
|
||||
"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
_("Make sure lock prevents calling with a shared lock");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// Utility that we only use here.
|
||||
|
||||
function do_check_begins(thing, startsWith) {
|
||||
if (!(thing && thing.indexOf && (thing.indexOf(startsWith) == 0)))
|
||||
do_throw(thing + " doesn't begin with " + startsWith);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let ret, rightThis, didCall;
|
||||
let state, lockState, lockedState, unlockState;
|
||||
|
@ -20,13 +27,15 @@ function run_test() {
|
|||
this._locked = false;
|
||||
},
|
||||
|
||||
func: function() this._lock(function() {
|
||||
func: function() this._lock("Test utils lock",
|
||||
function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
return 5;
|
||||
})(),
|
||||
|
||||
throwy: function() this._lock(function() {
|
||||
throwy: function() this._lock("Test utils lock throwy",
|
||||
function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
this.throwy();
|
||||
|
@ -52,7 +61,8 @@ function run_test() {
|
|||
do_throw("throwy internal call should have thrown!");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_eq(ex, "Could not acquire lock");
|
||||
// Should throw an Error, not a string.
|
||||
do_check_begins(ex, "Could not acquire lock");
|
||||
}
|
||||
do_check_eq(ret, null);
|
||||
do_check_true(rightThis);
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
_("Make sure makeGUID makes guids of the right length/characters");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
const base64url =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
||||
|
||||
function run_test() {
|
||||
// XXX this could cause random failures as guids arent always unique...
|
||||
_("Create a bunch of guids to make sure they don't conflict");
|
||||
let guids = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
let newGuid = Utils.makeGUID();
|
||||
_("Making sure guid has the right length without special characters:", newGuid);
|
||||
do_check_eq(encodeURIComponent(newGuid).length, 10);
|
||||
do_check_true(guids.every(function(g) g != newGuid));
|
||||
_("Generated " + newGuid);
|
||||
|
||||
// Verify that the GUID's length is correct, even when it's URL encoded.
|
||||
do_check_eq(newGuid.length, 12);
|
||||
do_check_eq(encodeURIComponent(newGuid).length, 12);
|
||||
|
||||
// Verify that the GUID only contains base64url characters
|
||||
do_check_true(Array.every(newGuid, function(chr) {
|
||||
return base64url.indexOf(chr) != -1;
|
||||
}));
|
||||
|
||||
// Verify uniqueness within our sample of 1000. This could cause random
|
||||
// failures, but they should be extremely rare. Otherwise we'd have a
|
||||
// problem with GUID collisions.
|
||||
do_check_true(guids.every(function(g) { return g != newGuid; }));
|
||||
guids.push(newGuid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,54 @@
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Generated passphrase has length 20.");
|
||||
_("Generated passphrase has length 26.");
|
||||
let pp = Utils.generatePassphrase();
|
||||
do_check_eq(pp.length, 20);
|
||||
do_check_eq(pp.length, 26);
|
||||
|
||||
_("Passphrase only contains a-z.");
|
||||
let bytes = [chr.charCodeAt() for each (chr in pp)];
|
||||
do_check_true(Math.min.apply(null, bytes) >= 97);
|
||||
do_check_true(Math.max.apply(null, bytes) <= 122);
|
||||
const key = "abcdefghijkmnpqrstuvwxyz23456789";
|
||||
_("Passphrase only contains [" + key + "].");
|
||||
do_check_true(pp.split('').every(function(chr) key.indexOf(chr) != -1));
|
||||
|
||||
_("Hyphenated passphrase has 3 hyphens.");
|
||||
_("Hyphenated passphrase has 5 hyphens.");
|
||||
let hyphenated = Utils.hyphenatePassphrase(pp);
|
||||
do_check_eq(hyphenated.length, 23);
|
||||
do_check_eq(hyphenated[5], '-');
|
||||
do_check_eq(hyphenated[11], '-');
|
||||
do_check_eq(hyphenated[17], '-');
|
||||
do_check_eq(hyphenated.slice(0, 5) + hyphenated.slice(6, 11)
|
||||
+ hyphenated.slice(12, 17) + hyphenated.slice(18, 23), pp);
|
||||
_("H: " + hyphenated);
|
||||
do_check_eq(hyphenated.length, 31);
|
||||
do_check_eq(hyphenated[1], '-');
|
||||
do_check_eq(hyphenated[7], '-');
|
||||
do_check_eq(hyphenated[13], '-');
|
||||
do_check_eq(hyphenated[19], '-');
|
||||
do_check_eq(hyphenated[25], '-');
|
||||
do_check_eq(pp,
|
||||
hyphenated.slice(0, 1) + hyphenated.slice(2, 7)
|
||||
+ hyphenated.slice(8, 13) + hyphenated.slice(14, 19)
|
||||
+ hyphenated.slice(20, 25) + hyphenated.slice(26, 31));
|
||||
|
||||
_("Arbitrary hyphenation.");
|
||||
// We don't allow invalid characters for our base32 character set.
|
||||
do_check_eq(Utils.hyphenatePassphrase("1234567"), "2-34567"); // Not partial, so no trailing dash.
|
||||
do_check_eq(Utils.hyphenatePassphrase("1234567890"), "2-34567-89");
|
||||
do_check_eq(Utils.hyphenatePassphrase("abcdeabcdeabcdeabcdeabcde"), "a-bcdea-bcdea-bcdea-bcdea-bcde");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("1234567"), "2-34567-");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("1234567890"), "2-34567-89");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("abcdeabcdeabcdeabcdeabcde"), "a-bcdea-bcdea-bcdea-bcdea-bcde");
|
||||
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("a"), "a-");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("1234567"), "2-34567-");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("a-bcdef-g"),
|
||||
"a-bcdef-g");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("abcdefghijklmnop"),
|
||||
"a-bcdef-ghijk-mnp");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("abcdefghijklmnopabcde"),
|
||||
"a-bcdef-ghijk-mnpab-cde");
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("a-bcdef-ghijk-LMNOP-ABCDE-Fg"),
|
||||
"a-bcdef-ghijk-mnpab-cdefg-");
|
||||
// Cuts off.
|
||||
do_check_eq(Utils.hyphenatePartialPassphrase("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").length, 31);
|
||||
|
||||
|
||||
|
||||
_("Normalize passphrase recognizes hyphens.");
|
||||
do_check_eq(Utils.normalizePassphrase(hyphenated), pp);
|
||||
do_check_eq(pp, pp);
|
||||
|
||||
_("Passphrase strength calculated according to the NIST algorithm.");
|
||||
do_check_eq(Utils.passphraseStrength(""), 0);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Evil.
|
||||
let btoa = Cu.import("resource://services-sync/util.js").btoa;
|
||||
|
||||
function run_test() {
|
||||
let symmKey16 = Utils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
||||
do_check_eq(symmKey16.length, 16);
|
||||
do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
do_check_eq(Utils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
||||
let symmKey32 = Utils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
||||
do_check_eq(symmKey32.length, 32);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function _hexToString(hex) {
|
||||
var ret = '';
|
||||
if (hex.length % 2 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < hex.length; i += 2) {
|
||||
var cur = hex[i] + hex[i + 1];
|
||||
ret += String.fromCharCode(parseInt(cur, 16));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let test_data =
|
||||
[{test_case: 1,
|
||||
key: _hexToString("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
key_len: 20,
|
||||
data: "Hi There",
|
||||
data_len: 8,
|
||||
digest: "b617318655057264e28bc0b6fb378c8ef146be00"},
|
||||
|
||||
{test_case: 2,
|
||||
key: "Jefe",
|
||||
key_len: 4,
|
||||
data: "what do ya want for nothing?",
|
||||
data_len: 28,
|
||||
digest: "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"}];
|
||||
|
||||
let d;
|
||||
// Testing repeated hashing.
|
||||
d = test_data[0];
|
||||
do_check_eq(Utils.bytesAsHex(Utils.sha1HMACBytes(d.data, Utils.makeHMACKey(d.key))), d.digest);
|
||||
d = test_data[1];
|
||||
do_check_eq(Utils.bytesAsHex(Utils.sha1HMACBytes(d.data, Utils.makeHMACKey(d.key))), d.digest);
|
||||
d = test_data[0];
|
||||
do_check_eq(Utils.bytesAsHex(Utils.sha1HMACBytes(d.data, Utils.makeHMACKey(d.key))), d.digest);
|
||||
d = test_data[1];
|
||||
do_check_eq(Utils.bytesAsHex(Utils.sha1HMACBytes(d.data, Utils.makeHMACKey(d.key))), d.digest);
|
||||
let kk = Utils.makeHMACKey(d.key);
|
||||
do_check_eq(
|
||||
Utils.bytesAsHex(
|
||||
Utils.sha1HMACBytes(
|
||||
Utils.sha1HMACBytes(
|
||||
Utils.sha1HMACBytes(d.data, kk),
|
||||
kk),
|
||||
kk)),
|
||||
Utils.bytesAsHex(
|
||||
Utils.sha1HMACBytes(
|
||||
Utils.sha1HMACBytes(
|
||||
Utils.sha1HMACBytes(d.data, kk),
|
||||
kk),
|
||||
kk)));
|
||||
|
||||
d = test_data[0];
|
||||
kk = Utils.makeHMACKey(d.key);
|
||||
do_check_eq(Utils.bytesAsHex(Utils.sha1HMACBytes(d.data, kk)), d.digest);
|
||||
do_check_eq(Utils.bytesAsHex(Utils.sha1HMACBytes(d.data, kk)), d.digest);
|
||||
}
|
|
@ -23,4 +23,17 @@ function run_test() {
|
|||
do_check_eq(Utils.sha256HMAC(mes1, key2), hmac12);
|
||||
do_check_eq(Utils.sha256HMAC(mes2, key1), hmac21);
|
||||
do_check_eq(Utils.sha256HMAC(mes2, key2), hmac22);
|
||||
|
||||
// RFC tests for SHA256-HMAC.
|
||||
let k1 = "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
|
||||
let d1 = "\x48\x69\x20\x54\x68\x65\x72\x65";
|
||||
let o1 = "\xb0\x34\x4c\x61\xd8\xdb\x38\x53\x5c\xa8\xaf\xce\xaf\x0b\xf1\x2b\x88\x1d\xc2\x00\xc9\x83\x3d\xa7\x26\xe9\x37\x6c\x2e\x32\xcf\xf7";
|
||||
|
||||
// Sample data for Marcus Wohlschon.
|
||||
let k2 = "78xR6gnAnBja7nYl6vkECo5bTlax5iHelP/jenj+dhQ=";
|
||||
let d2 = "4F5T+hK0VFBx880sYlMtfkBD2ZGe337tgbbQRTFxndFEgCC1fRrJkvzZ6Wytr+DySw3rxJ05O4Lqfn9F8Kxlvc4pcnAX//TK6MvRLs1NmcZr6HTo3NPurNB1VRTnJCE6";
|
||||
let o2 = "3dd17eb5091e0f2400a733f9e2cf8264d59206b6351078c2ce88499f1971f9b0";
|
||||
|
||||
do_check_eq(Utils.sha256HMACBytes(d1, Utils.makeHMACKey(k1)), o1);
|
||||
do_check_eq(Utils.sha256HMAC(d2, Utils.makeHMACKey(k2)), o2);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче