Bug 618496: remove algorithm adaptability in WeaveCrypto. r=philiKON
authorRichard Newman <rnewman@mozilla.com>
Wed, 02 Mar 2011 10:08:43 -0800
changeset 63304 ab322645172b
parent 63303 aa2115807bde
child 63305 0bea44f90d6d
push idunknown
push userunknown
push dateunknown
reviewersphiliKON
bugs618496
Bug 618496: remove algorithm adaptability in WeaveCrypto. r=philiKON
services/crypto/modules/WeaveCrypto.js
services/crypto/tests/unit/test_crypto_crypt.js
services/crypto/tests/unit/test_crypto_random.js
--- a/services/crypto/modules/WeaveCrypto.js
+++ b/services/crypto/modules/WeaveCrypto.js
@@ -40,16 +40,20 @@ const EXPORTED_SYMBOLS = ["WeaveCrypto"]
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 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();
 }
 
 WeaveCrypto.prototype = {
     QueryInterface: XPCOMUtils.generateQI([Ci.IWeaveCrypto]),
 
     prefBranch : null,
@@ -77,22 +81,47 @@ WeaveCrypto.prototype = {
             // Preferences. Add observer so we get notified of changes.
             this.prefBranch = Services.prefs.getBranch("services.sync.log.");
             this.prefBranch.QueryInterface(Ci.nsIPrefBranch2);
             this.prefBranch.addObserver("cryptoDebug", this.observer, false);
             this.observer._self = this;
             this.debug = this.prefBranch.getBoolPref("cryptoDebug");
 
             this.initNSS();
+            this.initAlgorithmSettings();   // Depends on NSS.
         } 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;
         dump("WeaveCrypto: " + message + "\n");
         Services.console.logStringMessage("WeaveCrypto: " + message);
     },
 
     initNSS : function() {
@@ -331,33 +360,28 @@ WeaveCrypto.prototype = {
                                                             this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool);
     },
 
 
     //
     // IWeaveCrypto interfaces
     //
 
-
-    algorithm : Ci.IWeaveCrypto.AES_256_CBC,
-
     encrypt : function(clearTextUCS2, symmetricKey, iv) {
         this.log("encrypt() called");
 
         // js-ctypes autoconverts to a UTF8 buffer, but also includes a null
         // at the end which we don't want. Cast to make the length 1 byte shorter.
         let inputBuffer = new ctypes.ArrayType(ctypes.unsigned_char)(clearTextUCS2);
         inputBuffer = ctypes.cast(inputBuffer, ctypes.unsigned_char.array(inputBuffer.length - 1));
 
         // 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);
 
         return this.encodeBase64(outputBuffer.address(), outputBuffer.length);
     },
 
 
@@ -386,38 +410,31 @@ 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);
 
-        // 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);
-
         let ctx, symKey, slot, ivParam;
         try {
-            ivParam = this.nss.PK11_ParamFromIV(mechanism, ivItem);
+            ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, ivItem);
             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);
+            symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism, 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);
+            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);
 
             let maxOutputSize = output.length;
             let tmpOutputSize = new ctypes.int(); // Note 1: NSS uses a signed int here...
 
             if (this.nss.PK11_CipherOp(ctx, output, tmpOutputSize.address(), maxOutputSize, input, input.length))
                 throw Components.Exception("cipher operation failed", Cr.NS_ERROR_FAILURE);
@@ -451,46 +468,23 @@ WeaveCrypto.prototype = {
             this.freeSECItem(keyItem);
             this.freeSECItem(ivItem);
         }
     },
 
 
     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);
 
             // Slightly odd API, this call just prepares the key value for
             // extraction, we get the actual bits from the call to PK11_GetKeyData().
             if (this.nss.PK11_ExtractKeyValue(randKey))
                 throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
 
@@ -505,26 +499,17 @@ WeaveCrypto.prototype = {
         } finally {
             if (randKey && !randKey.isNull())
                 this.nss.PK11_FreeSymKey(randKey);
             if (slot && !slot.isNull())
                 this.nss.PK11_FreeSlot(slot);
         }
     },
 
-
-    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");
 
         // Temporary buffer to hold the generated data.
         let scratch = new ctypes.ArrayType(ctypes.unsigned_char, byteCount)();
         if (this.nss.PK11_GenerateRandom(scratch, byteCount))
             throw Components.Exception("PK11_GenrateRandom failed", Cr.NS_ERROR_FAILURE);
@@ -597,22 +582,24 @@ WeaveCrypto.prototype = {
     /**
      * 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);
 
-        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, 
                                                          saltItem);
             if (algid.isNull())
                 throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE);
--- a/services/crypto/tests/unit/test_crypto_crypt.js
+++ b/services/crypto/tests/unit/test_crypto_crypt.js
@@ -78,17 +78,16 @@ function test_encrypt_decrypt() {
   do_check_eq(clearText.length, 20);
   
   // Did the text survive the encryption round-trip?
   do_check_eq(clearText, mySecret);
   do_check_neq(cipherText, mySecret); // just to be explicit
 
 
   // 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==";
 
   // Test small input sizes
   mySecret = "";
   cipherText = cryptoSvc.encrypt(mySecret, key, iv);
   clearText = cryptoSvc.decrypt(cipherText, key, iv);
   do_check_eq(cipherText, "OGQjp6mK1a3fs9k9Ml4L3w==");
@@ -141,41 +140,34 @@ function test_encrypt_decrypt() {
 
   mySecret = "12345678901234567";
   cipherText = cryptoSvc.encrypt(mySecret, key, iv);
   clearText = cryptoSvc.decrypt(cipherText, key, iv);
   do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP5GvxWJ9+GIAS6lXw+5fHTI=");
   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";
 
   cipherText = cryptoSvc.encrypt(mySecret, key, iv);
   clearText = cryptoSvc.decrypt(cipherText, key, iv);
   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";
 
   cipherText = cryptoSvc.encrypt(mySecret, key, iv);
   clearText = cryptoSvc.decrypt(cipherText, key, iv);
   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?";
   cipherText = cryptoSvc.encrypt(mySecret, key, iv);
   do_check_eq(cipherText, "T6fik9Ros+DB2ablH9zZ8FWZ0xm/szSwJjIHZu7sjPs=");
 
   var badkey    = "badkeybadkeybadkeybadk==";
   var badiv     = "badivbadivbadivbadivbad=";
--- a/services/crypto/tests/unit/test_crypto_random.js
+++ b/services/crypto/tests/unit/test_crypto_random.js
@@ -54,27 +54,11 @@ function run_test() {
   cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_256_CBC;
   keydata  = cryptoSvc.generateRandomKey();
   do_check_eq(keydata.length, 44);
   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_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);
 }