Merge fx-sync

This commit is contained in:
Philipp von Weitershausen 2011-03-02 16:32:28 -08:00
commit 4012ec2906
12 changed files with 338 additions and 470 deletions

View File

@ -45,6 +45,10 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/ctypes.jsm");
const ALGORITHM = Ci.IWeaveCrypto.AES_256_CBC;
const KEYSIZE_AES_256 = 32;
const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000.
function WeaveCrypto() {
this.init();
}
@ -82,12 +86,38 @@ WeaveCrypto.prototype = {
this.debug = this.prefBranch.getBoolPref("cryptoDebug");
this.initNSS();
this.initAlgorithmSettings(); // Depends on NSS.
this.initIVSECItem();
} catch (e) {
this.log("init failed: " + e);
throw e;
}
},
/**
* Set a bunch of NSS values once, at init-time. These are:
* - .blockSize
* - .mechanism
* - .keygenMechanism
* - .padMechanism
* - .keySize
*
* See also the constant ALGORITHM.
*/
initAlgorithmSettings: function() {
this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM);
this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null);
this.ivLength = this.nss.PK11_GetIVLength(this.mechanism);
this.keySize = KEYSIZE_AES_256;
this.keygenMechanism = this.nss.CKM_AES_KEY_GEN; // Always the same!
// Determine which (padded) PKCS#11 mechanism to use.
// E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism);
if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM)
throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
},
log : function (message) {
if (!this.debug)
return;
@ -171,52 +201,11 @@ WeaveCrypto.prototype = {
"SECItem", [{ type: this.nss_t.SECItemType },
{ data: ctypes.unsigned_char.ptr },
{ len : ctypes.int }]);
// security/nss/lib/softoken/secmodt.h#65
// typedef struct PK11RSAGenParamsStr --> def'n on line 139
this.nss_t.PK11RSAGenParams = ctypes.StructType(
"PK11RSAGenParams", [{ keySizeInBits: ctypes.int },
{ pe : ctypes.unsigned_long }]);
// security/nss/lib/cryptohi/keythi.h#233
// typedef struct SECKEYPrivateKeyStr SECKEYPrivateKey; --> def'n right above it
this.nss_t.SECKEYPrivateKey = ctypes.StructType(
"SECKEYPrivateKey", [{ arena: this.nss_t.PLArenaPool.ptr },
{ keyType: this.nss_t.KeyType },
{ pkcs11Slot: this.nss_t.PK11SlotInfo.ptr },
{ pkcs11ID: this.nss_t.CK_OBJECT_HANDLE },
{ pkcs11IsTemp: this.nss_t.PRBool },
{ wincx: ctypes.voidptr_t },
{ staticflags: ctypes.unsigned_int }]);
// security/nss/lib/cryptohi/keythi.h#78
// typedef struct SECKEYRSAPublicKeyStr --> def'n right above it
this.nss_t.SECKEYRSAPublicKey = ctypes.StructType(
"SECKEYRSAPublicKey", [{ arena: this.nss_t.PLArenaPool.ptr },
{ modulus: this.nss_t.SECItem },
{ publicExponent: this.nss_t.SECItem }]);
// security/nss/lib/cryptohi/keythi.h#189
// typedef struct SECKEYPublicKeyStr SECKEYPublicKey; --> def'n right above it
this.nss_t.SECKEYPublicKey = ctypes.StructType(
"SECKEYPublicKey", [{ arena: this.nss_t.PLArenaPool.ptr },
{ keyType: this.nss_t.KeyType },
{ pkcs11Slot: this.nss_t.PK11SlotInfo.ptr },
{ pkcs11ID: this.nss_t.CK_OBJECT_HANDLE },
{ rsa: this.nss_t.SECKEYRSAPublicKey } ]);
// XXX: "rsa" et al into a union here!
// { dsa: SECKEYDSAPublicKey },
// { dh: SECKEYDHPublicKey },
// { kea: SECKEYKEAPublicKey },
// { fortezza: SECKEYFortezzaPublicKey },
// { ec: SECKEYECPublicKey } ]);
// security/nss/lib/util/secoidt.h#52
// typedef struct SECAlgorithmIDStr --> def'n right below it
this.nss_t.SECAlgorithmID = ctypes.StructType(
"SECAlgorithmID", [{ algorithm: this.nss_t.SECItem },
{ parameters: this.nss_t.SECItem }]);
// security/nss/lib/certdb/certt.h#98
// typedef struct CERTSubjectPublicKeyInfoStrA --> def'n on line 160
this.nss_t.CERTSubjectPublicKeyInfo = ctypes.StructType(
"CERTSubjectPublicKeyInfo", [{ arena: this.nss_t.PLArenaPool.ptr },
{ algorithm: this.nss_t.SECAlgorithmID },
{ subjectPublicKey: this.nss_t.SECItem }]);
// security/nss/lib/util/pkcs11t.h
@ -225,7 +214,6 @@ WeaveCrypto.prototype = {
this.nss.CKM_AES_KEY_GEN = 0x1080;
this.nss.CKA_ENCRYPT = 0x104;
this.nss.CKA_DECRYPT = 0x105;
this.nss.CKA_UNWRAP = 0x107;
// security/nss/lib/softoken/secmodt.h
this.nss.PK11_ATTR_SESSION = 0x02;
@ -320,19 +308,6 @@ WeaveCrypto.prototype = {
ctypes.default_abi, this.nss_t.SECStatus,
this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
ctypes.unsigned_int.ptr, ctypes.unsigned_int);
// security/nss/lib/pk11wrap/pk11pub.h#507
// SECKEYPrivateKey *PK11_GenerateKeyPairWithFlags(PK11SlotInfo *slot,
// CK_MECHANISM_TYPE type, void *param, SECKEYPublicKey **pubk,
// PK11AttrFlags attrFlags, void *wincx);
this.nss.PK11_GenerateKeyPairWithFlags = nsslib.declare("PK11_GenerateKeyPairWithFlags",
ctypes.default_abi, this.nss_t.SECKEYPrivateKey.ptr,
this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, ctypes.voidptr_t,
this.nss_t.SECKEYPublicKey.ptr.ptr, this.nss_t.PK11AttrFlags, ctypes.voidptr_t);
// security/nss/lib/pk11wrap/pk11pub.h#466
// SECStatus PK11_SetPrivateKeyNickname(SECKEYPrivateKey *privKey, const char *nickname);
this.nss.PK11_SetPrivateKeyNickname = nsslib.declare("PK11_SetPrivateKeyNickname",
ctypes.default_abi, this.nss_t.SECStatus,
this.nss_t.SECKEYPrivateKey.ptr, ctypes.char.ptr);
// security/nss/lib/pk11wrap/pk11pub.h#731
// SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag,
// SECOidTag prfAlgTag, int keyLength, int iteration,
@ -347,60 +322,6 @@ WeaveCrypto.prototype = {
ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr,
this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t);
// security/nss/lib/pk11wrap/pk11pub.h#574
// SECStatus PK11_WrapPrivKey(PK11SlotInfo *slot, PK11SymKey *wrappingKey,
// SECKEYPrivateKey *privKey, CK_MECHANISM_TYPE wrapType,
// SECItem *param, SECItem *wrappedKey, void *wincx);
this.nss.PK11_WrapPrivKey = nsslib.declare("PK11_WrapPrivKey",
ctypes.default_abi, this.nss_t.SECStatus,
this.nss_t.PK11SlotInfo.ptr, this.nss_t.PK11SymKey.ptr,
this.nss_t.SECKEYPrivateKey.ptr, this.nss_t.CK_MECHANISM_TYPE,
this.nss_t.SECItem.ptr, this.nss_t.SECItem.ptr, ctypes.voidptr_t);
// security/nss/lib/cryptohi/keyhi.h#159
// SECItem* SECKEY_EncodeDERSubjectPublicKeyInfo(SECKEYPublicKey *pubk);
this.nss.SECKEY_EncodeDERSubjectPublicKeyInfo = nsslib.declare("SECKEY_EncodeDERSubjectPublicKeyInfo",
ctypes.default_abi, this.nss_t.SECItem.ptr,
this.nss_t.SECKEYPublicKey.ptr);
// security/nss/lib/cryptohi/keyhi.h#165
// CERTSubjectPublicKeyInfo * SECKEY_DecodeDERSubjectPublicKeyInfo(SECItem *spkider);
this.nss.SECKEY_DecodeDERSubjectPublicKeyInfo = nsslib.declare("SECKEY_DecodeDERSubjectPublicKeyInfo",
ctypes.default_abi, this.nss_t.CERTSubjectPublicKeyInfo.ptr,
this.nss_t.SECItem.ptr);
// security/nss/lib/cryptohi/keyhi.h#179
// SECKEYPublicKey * SECKEY_ExtractPublicKey(CERTSubjectPublicKeyInfo *);
this.nss.SECKEY_ExtractPublicKey = nsslib.declare("SECKEY_ExtractPublicKey",
ctypes.default_abi, this.nss_t.SECKEYPublicKey.ptr,
this.nss_t.CERTSubjectPublicKeyInfo.ptr);
// security/nss/lib/pk11wrap/pk11pub.h#377
// SECStatus PK11_PubWrapSymKey(CK_MECHANISM_TYPE type, SECKEYPublicKey *pubKey,
// PK11SymKey *symKey, SECItem *wrappedKey);
this.nss.PK11_PubWrapSymKey = nsslib.declare("PK11_PubWrapSymKey",
ctypes.default_abi, this.nss_t.SECStatus,
this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECKEYPublicKey.ptr,
this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr);
// security/nss/lib/pk11wrap/pk11pub.h#568
// SECKEYPrivateKey *PK11_UnwrapPrivKey(PK11SlotInfo *slot,
// PK11SymKey *wrappingKey, CK_MECHANISM_TYPE wrapType,
// SECItem *param, SECItem *wrappedKey, SECItem *label,
// SECItem *publicValue, PRBool token, PRBool sensitive,
// CK_KEY_TYPE keyType, CK_ATTRIBUTE_TYPE *usage, int usageCount,
// void *wincx);
this.nss.PK11_UnwrapPrivKey = nsslib.declare("PK11_UnwrapPrivKey",
ctypes.default_abi, this.nss_t.SECKEYPrivateKey.ptr,
this.nss_t.PK11SlotInfo.ptr, this.nss_t.PK11SymKey.ptr,
this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr,
this.nss_t.SECItem.ptr, this.nss_t.SECItem.ptr,
this.nss_t.SECItem.ptr, this.nss_t.PRBool,
this.nss_t.PRBool, this.nss_t.CK_KEY_TYPE,
this.nss_t.CK_ATTRIBUTE_TYPE.ptr, ctypes.int,
ctypes.voidptr_t);
// security/nss/lib/pk11wrap/pk11pub.h#447
// PK11SymKey *PK11_PubUnwrapSymKey(SECKEYPrivateKey *key, SECItem *wrapppedKey,
// CK_MECHANISM_TYPE target, CK_ATTRIBUTE_TYPE operation, int keySize);
this.nss.PK11_PubUnwrapSymKey = nsslib.declare("PK11_PubUnwrapSymKey",
ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
this.nss_t.SECKEYPrivateKey.ptr, this.nss_t.SECItem.ptr,
this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE, ctypes.int);
// security/nss/lib/pk11wrap/pk11pub.h#675
// void PK11_DestroyContext(PK11Context *context, PRBool freeit);
this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext",
@ -432,26 +353,11 @@ WeaveCrypto.prototype = {
this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem",
ctypes.default_abi, ctypes.void_t,
this.nss_t.SECItem.ptr, this.nss_t.PRBool);
// security/nss/lib/cryptohi/keyhi.h#193
// extern void SECKEY_DestroyPublicKey(SECKEYPublicKey *key);
this.nss.SECKEY_DestroyPublicKey = nsslib.declare("SECKEY_DestroyPublicKey",
ctypes.default_abi, ctypes.void_t,
this.nss_t.SECKEYPublicKey.ptr);
// security/nss/lib/cryptohi/keyhi.h#186
// extern void SECKEY_DestroyPrivateKey(SECKEYPrivateKey *key);
this.nss.SECKEY_DestroyPrivateKey = nsslib.declare("SECKEY_DestroyPrivateKey",
ctypes.default_abi, ctypes.void_t,
this.nss_t.SECKEYPrivateKey.ptr);
// security/nss/lib/util/secoid.h#103
// extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit);
this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID",
ctypes.default_abi, ctypes.void_t,
this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool);
// security/nss/lib/cryptohi/keyhi.h#58
// extern void SECKEY_DestroySubjectPublicKeyInfo(CERTSubjectPublicKeyInfo *spki);
this.nss.SECKEY_DestroySubjectPublicKeyInfo = nsslib.declare("SECKEY_DestroySubjectPublicKeyInfo",
ctypes.default_abi, ctypes.void_t,
this.nss_t.CERTSubjectPublicKeyInfo.ptr);
},
@ -459,9 +365,6 @@ WeaveCrypto.prototype = {
// IWeaveCrypto interfaces
//
algorithm : Ci.IWeaveCrypto.AES_256_CBC,
encrypt : function(clearTextUCS2, symmetricKey, iv) {
this.log("encrypt() called");
@ -473,9 +376,7 @@ WeaveCrypto.prototype = {
// When using CBC padding, the output size is the input size rounded
// up to the nearest block. If the input size is exactly on a block
// boundary, the output is 1 extra block long.
let mech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
let blockSize = this.nss.PK11_GetBlockSize(mech, null);
let outputBufferSize = inputBuffer.length + blockSize;
let outputBufferSize = inputBuffer.length + this.blockSize;
let outputBuffer = new ctypes.ArrayType(ctypes.unsigned_char, outputBufferSize)();
outputBuffer = this._commonCrypt(inputBuffer, outputBuffer, symmetricKey, iv, this.nss.CKA_ENCRYPT);
@ -494,8 +395,12 @@ WeaveCrypto.prototype = {
// We can't have js-ctypes create the buffer directly from the string
// (as in encrypt()), because we do _not_ want it to do UTF8
// conversion... We've got random binary data in the input's low byte.
let input = new ctypes.ArrayType(ctypes.unsigned_char, inputUCS2.length)();
this.byteCompress(inputUCS2, input);
//
// Compress a JS string (2-byte chars) into a normal C string (1-byte chars).
let len = inputUCS2.length;
let input = new ctypes.ArrayType(ctypes.unsigned_char, len)();
let ints = ctypes.cast(input, ctypes.uint8_t.array(len));
this.byteCompressInts(inputUCS2, ints, len);
let outputBuffer = new ctypes.ArrayType(ctypes.unsigned_char, input.length)();
@ -510,32 +415,24 @@ WeaveCrypto.prototype = {
_commonCrypt : function (input, output, symmetricKey, iv, operation) {
this.log("_commonCrypt() called");
// Get rid of the base64 encoding and convert to SECItems.
let keyItem = this.makeSECItem(symmetricKey, true);
let ivItem = this.makeSECItem(iv, true);
iv = atob(iv);
// Determine which (padded) PKCS#11 mechanism to use.
// EG: AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
let mechanism = this.nss.PK11_AlgtagToMechanism(this.algorithm);
mechanism = this.nss.PK11_GetPadMechanism(mechanism);
if (mechanism == this.nss.CKM_INVALID_MECHANISM)
throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
// We never want an IV longer than the block size, which is 16 bytes
// for AES.
if (iv.length > this.blockSize)
iv = iv.slice(0, this.blockSize);
let ctx, symKey, slot, ivParam;
// We use a single IV SECItem for the sake of efficiency. Fill it here.
this.byteCompressInts(iv, this._ivSECItemContents, iv.length);
let ctx, symKey, ivParam;
try {
ivParam = this.nss.PK11_ParamFromIV(mechanism, ivItem);
ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem);
if (ivParam.isNull())
throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE);
slot = this.nss.PK11_GetInternalKeySlot();
if (slot.isNull())
throw Components.Exception("can't get internal key slot", Cr.NS_ERROR_FAILURE);
symKey = this.nss.PK11_ImportSymKey(slot, mechanism, this.nss.PK11_OriginUnwrap, operation, keyItem, null);
if (symKey.isNull())
throw Components.Exception("symkey import failed", Cr.NS_ERROR_FAILURE);
ctx = this.nss.PK11_CreateContextBySymKey(mechanism, operation, symKey, ivParam);
symKey = this.importSymKey(symmetricKey, operation);
ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam);
if (ctx.isNull())
throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE);
@ -565,50 +462,24 @@ WeaveCrypto.prototype = {
} finally {
if (ctx && !ctx.isNull())
this.nss.PK11_DestroyContext(ctx, true);
if (symKey && !symKey.isNull())
this.nss.PK11_FreeSymKey(symKey);
if (slot && !slot.isNull())
this.nss.PK11_FreeSlot(slot);
if (ivParam && !ivParam.isNull())
this.nss.SECITEM_FreeItem(ivParam, true);
this.freeSECItem(keyItem);
this.freeSECItem(ivItem);
// Note that we do not free the IV SECItem; we reuse it.
// Neither do we free the symKey, because that's memoized.
}
},
generateRandomKey : function() {
this.log("generateRandomKey() called");
let encodedKey, keygenMech, keySize;
// Doesn't NSS have a lookup function to do this?
switch(this.algorithm) {
case Ci.IWeaveCrypto.AES_128_CBC:
keygenMech = this.nss.CKM_AES_KEY_GEN;
keySize = 16;
break;
case Ci.IWeaveCrypto.AES_192_CBC:
keygenMech = this.nss.CKM_AES_KEY_GEN;
keySize = 24;
break;
case Ci.IWeaveCrypto.AES_256_CBC:
keygenMech = this.nss.CKM_AES_KEY_GEN;
keySize = 32;
break;
default:
throw Components.Exception("unknown algorithm", Cr.NS_ERROR_FAILURE);
}
let slot, randKey, keydata;
try {
slot = this.nss.PK11_GetInternalSlot();
if (slot.isNull())
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
randKey = this.nss.PK11_KeyGen(slot, keygenMech, null, keySize, null);
randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null);
if (randKey.isNull())
throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE);
@ -633,16 +504,7 @@ WeaveCrypto.prototype = {
}
},
generateRandomIV : function() {
this.log("generateRandomIV() called");
let mech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
let size = this.nss.PK11_GetIVLength(mech);
return this.generateRandomBytes(size);
},
generateRandomIV : function() this.generateRandomBytes(this.ivLength),
generateRandomBytes : function(byteCount) {
this.log("generateRandomBytes() called");
@ -655,18 +517,78 @@ WeaveCrypto.prototype = {
return this.encodeBase64(scratch.address(), scratch.length);
},
//
// PK11SymKey memoization.
//
// Memoize the lookup of symmetric keys. We do this by using the base64
// string itself as a key -- the overhead of SECItem creation during the
// initial population is negligible, so that phase is not memoized.
_encryptionSymKeyMemo: {},
_decryptionSymKeyMemo: {},
importSymKey: function importSymKey(encodedKeyString, operation) {
let memo;
// We use two separate memos for thoroughness: operation is an input to
// key import.
switch (operation) {
case this.nss.CKA_ENCRYPT:
memo = this._encryptionSymKeyMemo;
break;
case this.nss.CKA_DECRYPT:
memo = this._decryptionSymKeyMemo;
break;
default:
throw "Unsupported operation in importSymKey.";
}
if (encodedKeyString in memo)
return memo[encodedKeyString];
let keyItem, slot;
try {
keyItem = this.makeSECItem(encodedKeyString, true);
slot = this.nss.PK11_GetInternalKeySlot();
if (slot.isNull())
throw Components.Exception("can't get internal key slot",
Cr.NS_ERROR_FAILURE);
let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism,
this.nss.PK11_OriginUnwrap,
operation, keyItem, null);
if (!symKey || symKey.isNull())
throw Components.Exception("symkey import failed",
Cr.NS_ERROR_FAILURE);
return memo[encodedKeyString] = symKey;
} finally {
if (slot && !slot.isNull())
this.nss.PK11_FreeSlot(slot);
this.freeSECItem(keyItem);
}
},
//
// Utility functions
//
/**
* Compress a JS string into a C uint8 array. count is the number of
* elements in the destination array. If the array is smaller than the
* string, the string is effectively truncated. If the string is smaller
* than the array, the array is 0-padded.
*/
byteCompressInts : function byteCompressInts (jsString, intArray, count) {
let len = jsString.length;
let end = Math.min(len, count);
// Compress a JS string (2-byte chars) into a normal C string (1-byte chars)
// EG, for "ABC", 0x0041, 0x0042, 0x0043 --> 0x41, 0x42, 0x43
byteCompress : function (jsString, charArray) {
let intArray = ctypes.cast(charArray, ctypes.uint8_t.array(charArray.length));
for (let i = 0; i < jsString.length; i++)
for (let i = 0; i < end; i++)
intArray[i] = jsString.charCodeAt(i) % 256; // convert to bytes
// Must zero-pad.
for (let i = len; i < count; i++)
intArray[i] = 0;
},
// Expand a normal C string (1-byte chars) into a JS string (2-byte chars)
@ -695,28 +617,54 @@ WeaveCrypto.prototype = {
},
// Returns a filled SECItem *, as returned by SECITEM_AllocItem.
//
//
// Note that this must be released with freeSECItem, which will also
// deallocate the internal buffer.
makeSECItem : function(input, isEncoded) {
if (isEncoded)
input = atob(input);
let len = input.length;
let item = this.nss.SECITEM_AllocItem(null, null, len);
if (item.isNull())
throw "SECITEM_AllocItem failed.";
let dest = ctypes.cast(item.contents.data, ctypes.unsigned_char.array(len).ptr);
this.byteCompress(input, dest.contents);
throw "SECITEM_AllocItem failed.";
let ptr = ctypes.cast(item.contents.data,
ctypes.unsigned_char.array(len).ptr);
let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len));
this.byteCompressInts(input, dest, len);
return item;
},
freeSECItem : function(zap) {
if (zap && !zap.isNull())
this.nss.SECITEM_ZfreeItem(zap, true);
},
// We only ever handle one IV at a time, and they're always different.
// Consequently, we maintain a single SECItem, and a handy pointer into its
// contents to avoid repetitive and expensive casts.
_ivSECItem: null,
_ivSECItemContents: null,
initIVSECItem: function initIVSECItem() {
if (this._ivSECItem) {
this._ivSECItemContents = null;
this.freeSECItem(this._ivSECItem);
}
let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize);
if (item.isNull())
throw "SECITEM_AllocItem failed.";
let ptr = ctypes.cast(item.contents.data,
ctypes.unsigned_char.array(this.blockSize).ptr);
let contents = ctypes.cast(ptr.contents,
ctypes.uint8_t.array(this.blockSize));
this._ivSECItem = item;
this._ivSECItemContents = contents;
},
/**
* Returns the expanded data string for the derived key.
*/
@ -725,17 +673,19 @@ WeaveCrypto.prototype = {
let passItem = this.makeSECItem(passphrase, false);
let saltItem = this.makeSECItem(salt, true);
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 pbeAlg = ALGORITHM;
let cipherAlg = ALGORITHM; // Ignored by callee when pbeAlg != a pkcs5 mech.
// Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported.
let prfAlg = this.nss.SEC_OID_HMAC_SHA1;
let keyLength = keyLength || 0; // 0 = Callee will pick.
let iterations = 4096; // PKCS#5 recommends at least 1000.
let iterations = KEY_DERIVATION_ITERATIONS;
let algid, slot, symKey, keyData;
try {
algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
keyLength, iterations,
keyLength, iterations,
saltItem);
if (algid.isNull())
throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE);
@ -772,7 +722,7 @@ WeaveCrypto.prototype = {
this.nss.PK11_FreeSlot(slot);
if (symKey && !symKey.isNull())
this.nss.PK11_FreeSymKey(symKey);
this.freeSECItem(passItem);
this.freeSECItem(saltItem);
}

View File

@ -19,15 +19,49 @@ function run_test() {
}
test_bug_617650();
test_encrypt_decrypt();
test_SECItem_byteCompressInts();
test_key_memoization();
if (this.gczeal)
gczeal(0);
}
function test_key_memoization() {
let oldImport = cryptoSvc.nss && cryptoSvc.nss.PK11_ImportSymKey;
if (!oldImport) {
_("Couldn't swizzle PK11_ImportSymKey; returning.");
return;
}
let iv = cryptoSvc.generateRandomIV();
let key = cryptoSvc.generateRandomKey();
let c = 0;
cryptoSvc.nss.PK11_ImportSymKey = function(slot, type, origin, operation, key, wincx) {
c++;
return oldImport(slot, type, origin, operation, key, wincx);
}
// Encryption should cause a single counter increment.
do_check_eq(c, 0);
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
do_check_eq(c, 1);
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
do_check_eq(c, 1);
// ... as should decryption.
cryptoSvc.decrypt(cipherText, key, iv);
cryptoSvc.decrypt(cipherText, key, iv);
cryptoSvc.decrypt(cipherText, key, iv);
do_check_eq(c, 2);
// Un-swizzle.
cryptoSvc.nss.PK11_ImportSymKey = oldImport;
}
function multiple_decrypts(iterations) {
let iv = cryptoSvc.generateRandomIV();
let key = cryptoSvc.generateRandomKey();
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
for (let i = 0; i < iterations; ++i) {
let clearText = cryptoSvc.decrypt(cipherText, key, iv);
do_check_eq(clearText + " " + i, "Hello, world. " + i);
@ -52,7 +86,7 @@ function test_bug_617650() {
// Just verify that it gets populated with the correct bytes.
function test_makeSECItem() {
Components.utils.import("resource://gre/modules/ctypes.jsm");
let item1 = cryptoSvc.makeSECItem("abcdefghi", false);
do_check_true(!item1.isNull());
let intData = ctypes.cast(item1.contents.data, ctypes.uint8_t.array(8).ptr).contents;
@ -60,6 +94,24 @@ function test_makeSECItem() {
do_check_eq(intData[i], "abcdefghi".charCodeAt(i));
}
function test_SECItem_byteCompressInts() {
Components.utils.import("resource://gre/modules/ctypes.jsm");
let item1 = cryptoSvc.makeSECItem("abcdefghi", false);
do_check_true(!item1.isNull());
let intData = ctypes.cast(item1.contents.data, ctypes.uint8_t.array(8).ptr).contents;
// Fill it too short.
cryptoSvc.byteCompressInts("MMM", intData, 8);
for (let i = 0; i < 8; ++i)
do_check_eq(intData[i], [77, 77, 77, 0, 0, 0, 0, 0, 0][i]);
// Fill it too much. Doesn't buffer overrun.
cryptoSvc.byteCompressInts("NNNNNNNNNNNNNNNN", intData, 8);
for (let i = 0; i < 8; ++i)
do_check_eq(intData[i], "NNNNNNNNNNNNNNNN".charCodeAt(i));
}
function test_encrypt_decrypt() {
// First, do a normal run with expected usage... Generate a random key and
@ -83,7 +135,6 @@ function test_encrypt_decrypt() {
// Do some more tests with a fixed key/iv, to check for reproducable results.
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
key = "St1tFCor7vQEJNug/465dQ==";
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
@ -146,8 +197,6 @@ function test_encrypt_decrypt() {
do_check_eq(clearText, mySecret);
// Test with 192 bit key.
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
key = "iz35tuIMq4/H+IYw2KTgow==";
iv = "TJYrvva2KxvkM8hvOIvWp3xgjTXgq5Ss";
mySecret = "i like pie";
@ -157,8 +206,6 @@ function test_encrypt_decrypt() {
do_check_eq(cipherText, "DLGx8BWqSCLGG7i/xwvvxg==");
do_check_eq(clearText, mySecret);
// Test with 256 bit key.
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_256_CBC;
key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI=";
iv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk=";
mySecret = "i like pie";
@ -168,9 +215,6 @@ function test_encrypt_decrypt() {
do_check_eq(cipherText, "o+ADtdMd8ubzNWurS6jt0Q==");
do_check_eq(clearText, mySecret);
// Test with bogus inputs
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
key = "St1tFCor7vQEJNug/465dQ==";
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
mySecret = "does thunder read testcases?";
@ -213,5 +257,4 @@ function test_encrypt_decrypt() {
failure = true;
}
do_check_true(failure);
}

View File

@ -59,22 +59,6 @@ function run_test() {
iv = cryptoSvc.generateRandomIV();
do_check_eq(iv.length, 24);
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
keydata = cryptoSvc.generateRandomKey();
do_check_eq(keydata.length, 32);
keydata2 = cryptoSvc.generateRandomKey();
do_check_neq(keydata, keydata2); // sanity check for randomness
iv = cryptoSvc.generateRandomIV();
do_check_eq(iv.length, 24);
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
keydata = cryptoSvc.generateRandomKey();
do_check_eq(keydata.length, 24);
keydata2 = cryptoSvc.generateRandomKey();
do_check_neq(keydata, keydata2); // sanity check for randomness
iv = cryptoSvc.generateRandomIV();
do_check_eq(iv.length, 24);
if (this.gczeal)
gczeal(0);
}

View File

@ -374,86 +374,7 @@ Engine.prototype = {
if (!this._sync)
throw "engine does not implement _sync method";
let times = {};
let wrapped = {};
// Find functions in any point of the prototype chain
for (let _name in this) {
let name = _name;
// Ignore certain constructors/functions
if (name.search(/^_(.+Obj|notify)$/) == 0)
continue;
// Only track functions but skip the constructors
if (typeof this[name] == "function") {
times[name] = [];
wrapped[name] = this[name];
// Wrap the original function with a start/stop timer
this[name] = function() {
let start = Date.now();
try {
return wrapped[name].apply(this, arguments);
}
finally {
times[name].push(Date.now() - start);
}
};
}
}
try {
this._notify("sync", this.name, this._sync)();
}
finally {
// Restore original unwrapped functionality
for (let [name, func] in Iterator(wrapped))
this[name] = func;
let stats = {};
for (let [name, time] in Iterator(times)) {
// Figure out stats on the times unless there's nothing
let num = time.length;
if (num == 0)
continue;
// Track the min/max/sum of the values
let stat = {
num: num,
sum: 0
};
time.forEach(function(val) {
if (stat.min == null || val < stat.min)
stat.min = val;
if (stat.max == null || val > stat.max)
stat.max = val;
stat.sum += val;
});
stat.avg = Number((stat.sum / num).toFixed(2));
stats[name] = stat;
}
stats.toString = function() {
let sums = [];
for (let [name, stat] in Iterator(this))
if (stat.sum != null)
sums.push(name.replace(/^_/, "") + " " + stat.sum);
// Order certain functions first before any other random ones
let nameOrder = ["sync", "processIncoming", "uploadOutgoing",
"syncStartup", "syncFinish"];
let getPos = function(str) {
let pos = nameOrder.indexOf(str.split(" ")[0]);
return pos != -1 ? pos : Infinity;
};
let order = function(a, b) getPos(a) > getPos(b);
return "Total (ms): " + sums.sort(order).join(", ");
};
this._log.debug(stats);
}
this._notify("sync", this.name, this._sync)();
},
/**
@ -702,13 +623,16 @@ SyncEngine.prototype = {
}
}
newitems.recordHandler = Utils.bind2(this, function(item) {
// Not binding this method to 'this' for performance reasons. It gets
// called for every incoming record.
let self = this;
newitems.recordHandler = function(item) {
// Grab a later last modified if possible
if (this.lastModified == null || item.modified > this.lastModified)
this.lastModified = item.modified;
if (self.lastModified == null || item.modified > self.lastModified)
self.lastModified = item.modified;
// Track the collection for the WBO.
item.collection = this.name;
item.collection = self.name;
// Remember which records were processed
handled.push(item.id);
@ -717,25 +641,25 @@ SyncEngine.prototype = {
try {
item.decrypt();
} catch (ex if (Utils.isHMACMismatch(ex) &&
this.handleHMACMismatch(item))) {
self.handleHMACMismatch(item))) {
// Let's try handling it.
// If the callback returns true, try decrypting again, because
// we've got new keys.
this._log.info("Trying decrypt again...");
self._log.info("Trying decrypt again...");
item.decrypt();
}
} catch (ex) {
this._log.warn("Error decrypting record: " + Utils.exceptionStr(ex));
self._log.warn("Error decrypting record: " + Utils.exceptionStr(ex));
failed.push(item.id);
return;
}
let shouldApply;
try {
shouldApply = this._reconcile(item);
shouldApply = self._reconcile(item);
} catch (ex) {
this._log.warn("Failed to reconcile incoming record " + item.id);
this._log.warn("Encountered exception: " + Utils.exceptionStr(ex));
self._log.warn("Failed to reconcile incoming record " + item.id);
self._log.warn("Encountered exception: " + Utils.exceptionStr(ex));
failed.push(item.id);
return;
}
@ -745,14 +669,14 @@ SyncEngine.prototype = {
applyBatch.push(item);
} else {
count.reconciled++;
this._log.trace("Skipping reconciled incoming item " + item.id);
self._log.trace("Skipping reconciled incoming item " + item.id);
}
if (applyBatch.length == this.applyIncomingBatchSize) {
doApplyBatch.call(this);
if (applyBatch.length == self.applyIncomingBatchSize) {
doApplyBatch.call(self);
}
this._sleep(0);
});
self._sleep(0);
};
// Only bother getting data from the server if there's new things
if (this.lastModified == null || this.lastModified > this.lastSync) {
@ -929,6 +853,7 @@ SyncEngine.prototype = {
// Upload outgoing records
_uploadOutgoing: function SyncEngine__uploadOutgoing() {
this._log.trace("Uploading local changes to server.");
if (this._modifiedIDs.length) {
this._log.trace("Preparing " + this._modifiedIDs.length +
" outgoing records");

View File

@ -47,11 +47,23 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const GUID_ANNO = "sync/guid";
const MOBILE_ANNO = "mobile/bookmarksRoot";
const PARENT_ANNO = "sync/parent";
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
const ALLBOOKMARKS_ANNO = "AllBookmarks";
const DESCRIPTION_ANNO = "bookmarkProperties/description";
const SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
const STATICTITLE_ANNO = "bookmarks/staticTitle";
const FEEDURI_ANNO = "livemark/feedURI";
const SITEURI_ANNO = "livemark/siteURI";
const GENERATORURI_ANNO = "microsummary/generatorURI";
const MOBILEROOT_ANNO = "mobile/bookmarksRoot";
const MOBILE_ANNO = "MobileBookmarks";
const EXCLUDEBACKUP_ANNO = "places/excludeFromBackup";
const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
const GUID_ANNO = "sync/guid";
const PARENT_ANNO = "sync/parent";
const ANNOS_TO_TRACK = [DESCRIPTION_ANNO, SIDEBAR_ANNO, STATICTITLE_ANNO,
FEEDURI_ANNO, SITEURI_ANNO, GENERATORURI_ANNO];
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
const FOLDER_SORTINDEX = 1000000;
try {
@ -72,9 +84,9 @@ function PlacesItem(collection, id, type) {
this.type = type || "item";
}
PlacesItem.prototype = {
decrypt: function PlacesItem_decrypt() {
decrypt: function PlacesItem_decrypt(keyBundle) {
// Do the normal CryptoWrapper decrypt, but change types before returning
let clear = CryptoWrapper.prototype.decrypt.apply(this, arguments);
let clear = CryptoWrapper.prototype.decrypt.call(this, keyBundle);
// Convert the abstract places item to the actual object type
if (!this.deleted)
@ -187,20 +199,20 @@ let kSpecialIds = {
// Special IDs. Note that mobile can attempt to create a record on
// dereference; special accessors are provided to prevent recursion within
// observers.
get guids()
["menu", "places", "tags", "toolbar", "unfiled", "mobile"],
guids: ["menu", "places", "tags", "toolbar", "unfiled", "mobile"],
// Create the special mobile folder to store mobile bookmarks.
createMobileRoot: function createMobileRoot() {
let root = Svc.Bookmark.placesRoot;
let mRoot = Svc.Bookmark.createFolder(root, "mobile", -1);
Utils.anno(mRoot, MOBILE_ANNO, 1);
Svc.Annos.setItemAnnotation(mRoot, MOBILEROOT_ANNO, 1, 0,
Svc.Annos.EXPIRE_NEVER);
return mRoot;
},
findMobileRoot: function findMobileRoot(create) {
// Use the (one) mobile root if it already exists.
let root = Svc.Annos.getItemsWithAnnotation(MOBILE_ANNO, {});
let root = Svc.Annos.getItemsWithAnnotation(MOBILEROOT_ANNO, {});
if (root.length != 0)
return root[0];
@ -272,7 +284,7 @@ BookmarksEngine.prototype = {
// Smart bookmarks map to their annotation value.
let queryId;
try {
queryId = Utils.anno(id, SMART_BOOKMARKS_ANNO);
queryId = Svc.Annos.getItemAnnotation(id, SMART_BOOKMARKS_ANNO);
} catch(ex) {}
if (queryId)
@ -544,7 +556,7 @@ BookmarksStore.prototype = {
applyIncoming: function BStore_applyIncoming(record) {
// Don't bother with pre and post-processing for deletions.
if (record.deleted) {
Store.prototype.applyIncoming.apply(this, arguments);
Store.prototype.applyIncoming.call(this, record);
return;
}
@ -577,7 +589,7 @@ BookmarksStore.prototype = {
}
// Do the normal processing of incoming records
Store.prototype.applyIncoming.apply(this, arguments);
Store.prototype.applyIncoming.call(this, record);
// Do some post-processing if we have an item
let itemId = this.idForGUID(record.id);
@ -591,8 +603,10 @@ BookmarksStore.prototype = {
}
// Create an annotation to remember that it needs reparenting.
if (record._orphan)
Utils.anno(itemId, PARENT_ANNO, parentGUID);
if (record._orphan) {
Svc.Annos.setItemAnnotation(itemId, PARENT_ANNO, parentGUID, 0,
Svc.Annos.EXPIRE_NEVER);
}
}
},
@ -601,7 +615,7 @@ BookmarksStore.prototype = {
*/
_findAnnoItems: function BStore__findAnnoItems(anno, val) {
return Svc.Annos.getItemsWithAnnotation(anno, {}).filter(function(id)
Utils.anno(id, anno) == val);
Svc.Annos.getItemAnnotation(id, anno) == val);
},
/**
@ -683,27 +697,35 @@ BookmarksStore.prototype = {
let uri = Utils.makeURI(record.bmkUri);
newId = this._bms.insertBookmark(record._parent, uri,
Svc.Bookmark.DEFAULT_INDEX, record.title);
this._log.debug(["created bookmark", newId, "under", record._parent,
"as", record.title, record.bmkUri].join(" "));
this._log.debug("created bookmark " + newId + " under " + record._parent
+ " as " + record.title + " " + record.bmkUri);
// Smart bookmark annotations are strings.
if (record.queryId) {
Utils.anno(newId, SMART_BOOKMARKS_ANNO, record.queryId);
Svc.Annos.setItemAnnotation(newId, SMART_BOOKMARKS_ANNO, record.queryId,
0, Svc.Annos.EXPIRE_NEVER);
}
if (Utils.isArray(record.tags)) {
this._tagURI(uri, record.tags);
}
this._bms.setKeywordForBookmark(newId, record.keyword);
if (record.description)
Utils.anno(newId, "bookmarkProperties/description", record.description);
if (record.description) {
Svc.Annos.setItemAnnotation(newId, DESCRIPTION_ANNO,
record.description, 0,
Svc.Annos.EXPIRE_NEVER);
}
if (record.loadInSidebar)
Utils.anno(newId, "bookmarkProperties/loadInSidebar", true);
if (record.loadInSidebar) {
Svc.Annos.setItemAnnotation(newId, SIDEBAR_ANNO, true, 0,
Svc.Annos.EXPIRE_NEVER);
}
if (record.type == "microsummary") {
this._log.debug(" \-> is a microsummary");
Utils.anno(newId, "bookmarks/staticTitle", record.staticTitle || "");
Svc.Annos.setItemAnnotation(newId, STATICTITLE_ANNO,
record.staticTitle || "", 0,
Svc.Annos.EXPIRE_NEVER);
let genURI = Utils.makeURI(record.generatorUri);
if (this._ms) {
try {
@ -719,11 +741,13 @@ BookmarksStore.prototype = {
case "folder":
newId = this._bms.createFolder(record._parent, record.title,
Svc.Bookmark.DEFAULT_INDEX);
this._log.debug(["created folder", newId, "under", record._parent,
"as", record.title].join(" "));
this._log.debug("created folder " + newId + " under " + record._parent
+ " as " + record.title);
if (record.description)
Utils.anno(newId, "bookmarkProperties/description", record.description);
if (record.description) {
Svc.Annos.setItemAnnotation(newId, DESCRIPTION_ANNO, record.description,
0, Svc.Annos.EXPIRE_NEVER);
}
// record.children will be dealt with in _orderChildren.
break;
@ -754,8 +778,7 @@ BookmarksStore.prototype = {
case "separator":
newId = this._bms.insertSeparator(record._parent,
Svc.Bookmark.DEFAULT_INDEX);
this._log.debug(["created separator", newId, "under", record._parent]
.join(" "));
this._log.debug("created separator " + newId + " under " + record._parent);
break;
case "item":
this._log.debug(" -> got a generic places item.. do nothing?");
@ -857,13 +880,16 @@ BookmarksStore.prototype = {
break;
case "description":
val = val || "";
Utils.anno(itemId, "bookmarkProperties/description", val);
Svc.Annos.setItemAnnotation(itemId, DESCRIPTION_ANNO, val, 0,
Svc.Annos.EXPIRE_NEVER);
break;
case "loadInSidebar":
if (val)
Utils.anno(itemId, "bookmarkProperties/loadInSidebar", true);
else
Svc.Annos.removeItemAnnotation(itemId, "bookmarkProperties/loadInSidebar");
if (val) {
Svc.Annos.setItemAnnotation(itemId, SIDEBAR_ANNO, true, 0,
Svc.Annos.EXPIRE_NEVER);
} else {
Svc.Annos.removeItemAnnotation(itemId, SIDEBAR_ANNO);
}
break;
case "generatorUri": {
try {
@ -880,7 +906,8 @@ BookmarksStore.prototype = {
}
} break;
case "queryId":
Utils.anno(itemId, SMART_BOOKMARKS_ANNO, val);
Svc.Annos.setItemAnnotation(itemId, SMART_BOOKMARKS_ANNO, val, 0,
Svc.Annos.EXPIRE_NEVER);
break;
case "siteUri":
this._ls.setSiteURI(itemId, Utils.makeURI(val));
@ -948,19 +975,19 @@ BookmarksStore.prototype = {
_getDescription: function BStore__getDescription(id) {
try {
return Utils.anno(id, "bookmarkProperties/description");
return Svc.Annos.getItemAnnotation(id, DESCRIPTION_ANNO);
} catch (e) {
return null;
}
},
_isLoadInSidebar: function BStore__isLoadInSidebar(id) {
return Svc.Annos.itemHasAnnotation(id, "bookmarkProperties/loadInSidebar");
return Svc.Annos.itemHasAnnotation(id, SIDEBAR_ANNO);
},
_getStaticTitle: function BStore__getStaticTitle(id) {
try {
return Utils.anno(id, "bookmarks/staticTitle");
return Svc.Annos.getItemAnnotation(id, STATICTITLE_ANNO);
} catch (e) {
return "";
}
@ -1045,7 +1072,7 @@ BookmarksStore.prototype = {
// Persist the Smart Bookmark anno, if found.
try {
let anno = Utils.anno(placeId, SMART_BOOKMARKS_ANNO);
let anno = Svc.Annos.getItemAnnotation(placeId, SMART_BOOKMARKS_ANNO);
if (anno != null) {
this._log.trace("query anno: " + SMART_BOOKMARKS_ANNO +
" = " + anno);
@ -1199,7 +1226,7 @@ BookmarksStore.prototype = {
// Some helper functions to handle GUIDs
_setGUID: function _setGUID(id, guid) {
if (arguments.length == 1)
if (!guid)
guid = Utils.makeGUID();
// If we can, set the GUID on moz_bookmarks and do not do any other work.
@ -1334,8 +1361,8 @@ BookmarksStore.prototype = {
stmt.params.guid = guid.toString();
let results = Utils.queryAsync(stmt, this._idForGUIDCols);
this._log.trace("Rows matching GUID " + guid + ": " +
results.map(function(x) x.item_id));
this._log.trace("Number of rows matching GUID " + guid + ": "
+ results.length);
// Here's the one we care about: the first.
let result = results[0];
@ -1570,7 +1597,7 @@ BookmarksTracker.prototype = {
this._ensureMobileQuery();
// Make sure to remove items that have the exclude annotation
if (Svc.Annos.itemHasAnnotation(itemId, "places/excludeFromBackup")) {
if (Svc.Annos.itemHasAnnotation(itemId, EXCLUDEBACKUP_ANNO)) {
this.removeChangedID(this._GUIDForId(itemId));
return true;
}
@ -1614,17 +1641,17 @@ BookmarksTracker.prototype = {
_ensureMobileQuery: function _ensureMobileQuery() {
let anno = "PlacesOrganizer/OrganizerQuery";
let find = function(val) Svc.Annos.getItemsWithAnnotation(anno, {}).filter(
function(id) Utils.anno(id, anno) == val);
function(id) Svc.Annos.getItemAnnotation(id, anno) == val);
// Don't continue if the Library isn't ready
let all = find("AllBookmarks");
let all = find(ALLBOOKMARKS_ANNO);
if (all.length == 0)
return;
// Disable handling of notifications while changing the mobile query
this.ignoreAll = true;
let mobile = find("MobileBookmarks");
let mobile = find(MOBILE_ANNO);
let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile);
let title = Str.sync.get("mobile.label");
@ -1636,8 +1663,10 @@ BookmarksTracker.prototype = {
// Add the mobile bookmarks query if it doesn't exist
else if (mobile.length == 0) {
let query = Svc.Bookmark.insertBookmark(all[0], queryURI, -1, title);
Utils.anno(query, anno, "MobileBookmarks");
Utils.anno(query, "places/excludeFromBackup", 1);
Svc.Annos.setItemAnnotation(query, anno, MOBILE_ANNO, 0,
Svc.Annos.EXPIRE_NEVER);
Svc.Annos.setItemAnnotation(query, EXCLUDEBACKUP_ANNO, 1, 0,
Svc.Annos.EXPIRE_NEVER);
}
// Make sure the existing title is correct
else if (Svc.Bookmark.getItemTitle(mobile[0]) != title)
@ -1661,10 +1690,7 @@ BookmarksTracker.prototype = {
}
// ignore annotations except for the ones that we sync
let annos = ["bookmarkProperties/description",
"bookmarkProperties/loadInSidebar", "bookmarks/staticTitle",
"livemark/feedURI", "livemark/siteURI", "microsummary/generatorURI"];
if (isAnno && annos.indexOf(property) == -1)
if (isAnno && ANNOS_TO_TRACK.indexOf(property) == -1)
return;
// Ignore favicon changes to avoid unnecessary churn

View File

@ -223,7 +223,7 @@ HistoryStore.prototype = {
setGUID: function setGUID(uri, guid) {
uri = uri.spec ? uri.spec : uri;
if (arguments.length == 1)
if (!guid)
guid = Utils.makeGUID();
// If we can, set the GUID on moz_places and do not do any other work.

View File

@ -310,33 +310,6 @@ let Utils = {
return !!guid && this._base64url_regex.test(guid);
},
anno: function anno(id, anno, val, expire) {
// Figure out if we have a bookmark or page
let annoFunc = (typeof id == "number" ? "Item" : "Page") + "Annotation";
// Convert to a nsIURI if necessary
if (typeof id == "string")
id = Utils.makeURI(id);
if (id == null)
throw "Null id for anno! (invalid uri)";
switch (arguments.length) {
case 2:
// Get the annotation with 2 args
return Svc.Annos["get" + annoFunc](id, anno);
case 3:
expire = "NEVER";
// Fallthrough!
case 4:
// Convert to actual EXPIRE value
expire = Svc.Annos["EXPIRE_" + expire];
// Set the annotation with 3 or 4 args
return Svc.Annos["set" + annoFunc](id, anno, val, 0, expire);
}
},
ensureOneOpen: let (windows = {}) function ensureOneOpen(window) {
// Close the other window if it exists
let url = window.location.href;

View File

@ -88,7 +88,8 @@ function test_livemark_descriptions(next) {
// Attempt to provoke an error by adding a bad description anno.
let id = store.idForGUID(record.id);
Utils.anno(id, DESCRIPTION_ANNO, "");
Svc.Annos.setItemAnnotation(id, DESCRIPTION_ANNO, "", 0,
Svc.Annos.EXPIRE_NEVER);
next();
}

View File

@ -83,7 +83,8 @@ function test_annotation_uploaded() {
_("New item ID: " + mostVisitedID);
do_check_true(!!mostVisitedID);
let annoValue = Utils.anno(mostVisitedID, SMART_BOOKMARKS_ANNO);
let annoValue = Svc.Annos.getItemAnnotation(mostVisitedID,
SMART_BOOKMARKS_ANNO);
_("Anno: " + annoValue);
do_check_eq("MostVisited", annoValue);
@ -157,7 +158,7 @@ function test_annotation_uploaded() {
_("Find by GUID and verify that it's annotated.");
let newID = store.idForGUID(serverGUID);
let newAnnoValue = Utils.anno(newID, SMART_BOOKMARKS_ANNO);
let newAnnoValue = Svc.Annos.getItemAnnotation(newID, SMART_BOOKMARKS_ANNO);
do_check_eq(newAnnoValue, "MostVisited");
do_check_eq(Svc.Bookmark.getBookmarkURI(newID).spec, uri.spec);
@ -166,7 +167,8 @@ function test_annotation_uploaded() {
do_check_eq(newRecord.queryId, newAnnoValue);
newRecord.queryId = "LeastVisited";
store.update(newRecord);
do_check_eq("LeastVisited", Utils.anno(newID, SMART_BOOKMARKS_ANNO));
do_check_eq("LeastVisited",
Svc.Annos.getItemAnnotation(newID, SMART_BOOKMARKS_ANNO));
} finally {

View File

@ -35,7 +35,7 @@ function test_bookmark_create() {
do_check_eq(Svc.Bookmark.getItemType(id), Svc.Bookmark.TYPE_BOOKMARK);
do_check_true(Svc.Bookmark.getBookmarkURI(id).equals(fxuri));
do_check_eq(Svc.Bookmark.getItemTitle(id), fxrecord.title);
do_check_eq(Utils.anno(id, "bookmarkProperties/description"),
do_check_eq(Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description"),
fxrecord.description);
do_check_eq(Svc.Bookmark.getFolderIdForItem(id),
Svc.Bookmark.toolbarFolder);
@ -69,7 +69,7 @@ function test_bookmark_create() {
do_check_eq(Svc.Bookmark.getItemTitle(id), null);
let error;
try {
Utils.anno(id, "bookmarkProperties/description");
Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description");
} catch(ex) {
error = ex;
}
@ -89,7 +89,8 @@ function test_bookmark_update() {
let bmk1_id = Svc.Bookmark.insertBookmark(
Svc.Bookmark.toolbarFolder, fxuri, Svc.Bookmark.DEFAULT_INDEX,
"Get Firefox!");
Utils.anno(bmk1_id, "bookmarkProperties/description", "Firefox is awesome.");
Svc.Annos.setItemAnnotation(bmk1_id, "bookmarkProperties/description",
"Firefox is awesome.", 0, Svc.Annos.EXPIRE_NEVER);
Svc.Bookmark.setKeywordForBookmark(bmk1_id, "firefox");
let bmk1_guid = store.GUIDForId(bmk1_id);
@ -102,7 +103,7 @@ function test_bookmark_update() {
store.applyIncoming(record);
_("Verify that the values have been cleared.");
do_check_eq(Utils.anno(bmk1_id, "bookmarkProperties/description"), "");
do_check_eq(Svc.Annos.getItemAnnotation(bmk1_id, "bookmarkProperties/description"), "");
do_check_eq(Svc.Bookmark.getItemTitle(bmk1_id), "");
do_check_eq(Svc.Bookmark.getKeywordForBookmark(bmk1_id), null);
} finally {
@ -306,7 +307,7 @@ function test_orphan() {
do_check_eq(Svc.Bookmark.getFolderIdForItem(bmk1_id), Svc.Bookmark.toolbarFolder);
let error;
try {
Utils.anno(bmk1_id, PARENT_ANNO);
Svc.Annos.getItemAnnotation(bmk1_id, PARENT_ANNO);
} catch(ex) {
error = ex;
}
@ -319,7 +320,7 @@ function test_orphan() {
_("Verify that bookmark has been flagged as orphan, has not moved.");
do_check_eq(Svc.Bookmark.getFolderIdForItem(bmk1_id), Svc.Bookmark.toolbarFolder);
do_check_eq(Utils.anno(bmk1_id, PARENT_ANNO), "non-existent");
do_check_eq(Svc.Annos.getItemAnnotation(bmk1_id, PARENT_ANNO), "non-existent");
} finally {
_("Clean up.");
@ -334,14 +335,16 @@ function test_reparentOrphans() {
let folder1_guid = store.GUIDForId(folder1_id);
_("Create a bogus orphan record and write the record back to the store to trigger _reparentOrphans.");
Utils.anno(folder1_id, PARENT_ANNO, folder1_guid);
Svc.Annos.setItemAnnotation(folder1_id, PARENT_ANNO, folder1_guid, 0,
Svc.Annos.EXPIRE_NEVER);
let record = store.createRecord(folder1_guid);
record.title = "New title for Folder 1";
store._childrenToOrder = {};
store.applyIncoming(record);
_("Verify that is has been marked as an orphan even though it couldn't be moved into itself.");
do_check_eq(Utils.anno(folder1_id, PARENT_ANNO), folder1_guid);
do_check_eq(Svc.Annos.getItemAnnotation(folder1_id, PARENT_ANNO),
folder1_guid);
} finally {
_("Clean up.");
@ -378,7 +381,7 @@ function test_copying_avoid_duplicate_guids() {
let id = store.idForGUID(fxrecord.id);
do_check_eq(store.GUIDForId(id), fxrecord.id);
do_check_true(Svc.Bookmark.getBookmarkURI(id).equals(fxuri));
do_check_eq(Utils.anno(id, "bookmarkProperties/description"),
do_check_eq(Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description"),
fxrecord.description);
_("Copy the record as happens in the UI: with the same GUID.");
@ -397,8 +400,9 @@ function test_copying_avoid_duplicate_guids() {
do_check_neq(store.GUIDForId(copy), store.GUIDForId(id));
_("Verify that the anno itself has changed.");
do_check_neq(Utils.anno(copy, "sync/guid"), fxrecord.id);
do_check_eq(Utils.anno(copy, "sync/guid"), store.GUIDForId(copy));
do_check_neq(Svc.Annos.getItemAnnotation(copy, "sync/guid"), fxrecord.id);
do_check_eq(Svc.Annos.getItemAnnotation(copy, "sync/guid"),
store.GUIDForId(copy));
} finally {
_("Clean up.");

View File

@ -283,7 +283,8 @@ function test_guid_stripping() {
let victimGUID = store.GUIDForId(victim);
_("Set the GUID on one entry to be the same as another.");
do_check_neq(suspectGUID, victimGUID);
Utils.anno(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim));
Svc.Annos.setItemAnnotation(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim),
0, Svc.Annos.EXPIRE_NEVER);
_("Tracker changed it to something else.");
let newGUID = store.GUIDForId(suspect);

View File

@ -1,41 +0,0 @@
_("Make sure various combinations of anno arguments do the right get/set for pages/items");
Cu.import("resource://services-sync/util.js");
function run_test() {
_("create a bookmark to a url so it exists");
let url = "about:";
let bmkid = Svc.Bookmark.insertBookmark(Svc.Bookmark.unfiledBookmarksFolder,
Utils.makeURI(url), -1, "");
_("set an anno on the bookmark ");
Utils.anno(bmkid, "anno", "hi");
do_check_eq(Utils.anno(bmkid, "anno"), "hi");
_("set an anno on a url");
Utils.anno(url, "tation", "hello");
do_check_eq(Utils.anno(url, "tation"), "hello");
_("make sure getting it also works with a nsIURI");
let uri = Utils.makeURI(url);
do_check_eq(Utils.anno(uri, "tation"), "hello");
_("make sure annotations get updated");
Utils.anno(uri, "tation", "bye!");
do_check_eq(Utils.anno(url, "tation"), "bye!");
_("sanity check that the item anno is still there");
do_check_eq(Utils.anno(bmkid, "anno"), "hi");
_("invalid uris don't get annos");
let didThrow = false;
try {
Utils.anno("foo/bar/baz", "bad");
}
catch(ex) {
didThrow = true;
}
do_check_true(didThrow);
_("cleaning up the bookmark we created");
Svc.Bookmark.removeItem(bmkid);
}