Bug 615021 - Merge fx-sync to mozilla-central. a=lotsa-blockers

This commit is contained in:
Philipp von Weitershausen 2010-11-30 20:39:13 -08:00
Родитель 15859b8f5f 07eb8a8845
Коммит 19bfb008b0
73 изменённых файлов: 2532 добавлений и 2331 удалений

Просмотреть файл

@ -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);
}