michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ["WeaveCrypto"]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/ctypes.jsm"); michael@0: michael@0: /** michael@0: * Shortcuts for some algorithm SEC OIDs. Full list available here: michael@0: * http://lxr.mozilla.org/seamonkey/source/security/nss/lib/util/secoidt.h michael@0: */ michael@0: const DES_EDE3_CBC = 156; michael@0: const AES_128_CBC = 184; michael@0: const AES_192_CBC = 186; michael@0: const AES_256_CBC = 188; michael@0: michael@0: const ALGORITHM = AES_256_CBC; michael@0: const KEYSIZE_AES_256 = 32; michael@0: const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000. michael@0: const INITIAL_BUFFER_SIZE = 1024; michael@0: michael@0: this.WeaveCrypto = function WeaveCrypto() { michael@0: this.init(); michael@0: } michael@0: michael@0: WeaveCrypto.prototype = { michael@0: prefBranch : null, michael@0: debug : true, // services.sync.log.cryptoDebug michael@0: nss : null, michael@0: nss_t : null, michael@0: michael@0: observer : { michael@0: _self : null, michael@0: michael@0: QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]), michael@0: michael@0: observe : function (subject, topic, data) { michael@0: let self = this._self; michael@0: self.log("Observed " + topic + " topic."); michael@0: if (topic == "nsPref:changed") { michael@0: self.debug = self.prefBranch.getBoolPref("cryptoDebug"); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: init : function() { michael@0: try { michael@0: // Preferences. Add observer so we get notified of changes. michael@0: this.prefBranch = Services.prefs.getBranch("services.sync.log."); michael@0: this.prefBranch.addObserver("cryptoDebug", this.observer, false); michael@0: this.observer._self = this; michael@0: try { michael@0: this.debug = this.prefBranch.getBoolPref("cryptoDebug"); michael@0: } catch (x) { michael@0: this.debug = false; michael@0: } michael@0: michael@0: this.initNSS(); michael@0: this.initAlgorithmSettings(); // Depends on NSS. michael@0: this.initIVSECItem(); michael@0: this.initSharedInts(); michael@0: this.initBuffers(INITIAL_BUFFER_SIZE); michael@0: } catch (e) { michael@0: this.log("init failed: " + e); michael@0: throw e; michael@0: } michael@0: }, michael@0: michael@0: // Avoid allocating new temporary ints on every run of _commonCrypt. michael@0: _commonCryptSignedOutputSize: null, michael@0: _commonCryptSignedOutputSizeAddr: null, michael@0: _commonCryptUnsignedOutputSize: null, michael@0: _commonCryptUnsignedOutputSizeAddr: null, michael@0: michael@0: initSharedInts: function initSharedInts() { michael@0: let signed = new ctypes.int(); michael@0: let unsigned = new ctypes.unsigned_int(); michael@0: this._commonCryptSignedOutputSize = signed; michael@0: this._commonCryptUnsignedOutputSize = unsigned; michael@0: this._commonCryptSignedOutputSizeAddr = signed.address(); michael@0: this._commonCryptUnsignedOutputSizeAddr = unsigned.address(); michael@0: }, michael@0: michael@0: /** michael@0: * Set a bunch of NSS values once, at init-time. These are: michael@0: * - .blockSize michael@0: * - .mechanism michael@0: * - .keygenMechanism michael@0: * - .padMechanism michael@0: * - .keySize michael@0: * michael@0: * See also the constant ALGORITHM. michael@0: */ michael@0: initAlgorithmSettings: function() { michael@0: this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM); michael@0: this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null); michael@0: this.ivLength = this.nss.PK11_GetIVLength(this.mechanism); michael@0: this.keySize = KEYSIZE_AES_256; michael@0: this.keygenMechanism = this.nss.CKM_AES_KEY_GEN; // Always the same! michael@0: michael@0: // Determine which (padded) PKCS#11 mechanism to use. michael@0: // E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD michael@0: this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism); michael@0: if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM) michael@0: throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE); michael@0: }, michael@0: michael@0: log : function (message) { michael@0: if (!this.debug) michael@0: return; michael@0: dump("WeaveCrypto: " + message + "\n"); michael@0: Services.console.logStringMessage("WeaveCrypto: " + message); michael@0: }, michael@0: michael@0: initNSS : function() { michael@0: // We use NSS for the crypto ops, which needs to be initialized before michael@0: // use. By convention, PSM is required to be the module that michael@0: // initializes NSS. So, make sure PSM is initialized in order to michael@0: // implicitly initialize NSS. michael@0: Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); michael@0: michael@0: // Open the NSS library. michael@0: let path = ctypes.libraryName("nss3"); michael@0: michael@0: // XXX really want to be able to pass specific dlopen flags here. michael@0: var nsslib; michael@0: try { michael@0: this.log("Trying NSS library without path"); michael@0: nsslib = ctypes.open(path); michael@0: } catch(e) { michael@0: // In case opening the library without a full path fails, michael@0: // try again with a full path. michael@0: let file = Services.dirsvc.get("GreD", Ci.nsILocalFile); michael@0: file.append(path); michael@0: this.log("Trying again with path " + file.path); michael@0: nsslib = ctypes.open(file.path); michael@0: } michael@0: michael@0: this.log("Initializing NSS types and function declarations..."); michael@0: michael@0: this.nss = {}; michael@0: this.nss_t = {}; michael@0: michael@0: // nsprpub/pr/include/prtypes.h#435 michael@0: // typedef PRIntn PRBool; --> int michael@0: this.nss_t.PRBool = ctypes.int; michael@0: // security/nss/lib/util/seccomon.h#91 michael@0: // typedef enum michael@0: this.nss_t.SECStatus = ctypes.int; michael@0: // security/nss/lib/softoken/secmodt.h#59 michael@0: // typedef struct PK11SlotInfoStr PK11SlotInfo; (defined in secmodti.h) michael@0: this.nss_t.PK11SlotInfo = ctypes.void_t; michael@0: // security/nss/lib/util/pkcs11t.h michael@0: this.nss_t.CK_MECHANISM_TYPE = ctypes.unsigned_long; michael@0: this.nss_t.CK_ATTRIBUTE_TYPE = ctypes.unsigned_long; michael@0: this.nss_t.CK_KEY_TYPE = ctypes.unsigned_long; michael@0: this.nss_t.CK_OBJECT_HANDLE = ctypes.unsigned_long; michael@0: // security/nss/lib/softoken/secmodt.h#359 michael@0: // typedef enum PK11Origin michael@0: this.nss_t.PK11Origin = ctypes.int; michael@0: // PK11Origin enum values... michael@0: this.nss.PK11_OriginUnwrap = 4; michael@0: // security/nss/lib/softoken/secmodt.h#61 michael@0: // typedef struct PK11SymKeyStr PK11SymKey; (defined in secmodti.h) michael@0: this.nss_t.PK11SymKey = ctypes.void_t; michael@0: // security/nss/lib/util/secoidt.h#454 michael@0: // typedef enum michael@0: this.nss_t.SECOidTag = ctypes.int; michael@0: // security/nss/lib/util/seccomon.h#64 michael@0: // typedef enum michael@0: this.nss_t.SECItemType = ctypes.int; michael@0: // SECItemType enum values... michael@0: this.nss.SIBUFFER = 0; michael@0: // security/nss/lib/softoken/secmodt.h#62 (defined in secmodti.h) michael@0: // typedef struct PK11ContextStr PK11Context; michael@0: this.nss_t.PK11Context = ctypes.void_t; michael@0: // Needed for SECKEYPrivateKey struct def'n, but I don't think we need to actually access it. michael@0: this.nss_t.PLArenaPool = ctypes.void_t; michael@0: // security/nss/lib/cryptohi/keythi.h#45 michael@0: // typedef enum michael@0: this.nss_t.KeyType = ctypes.int; michael@0: // security/nss/lib/softoken/secmodt.h#201 michael@0: // typedef PRUint32 PK11AttrFlags; michael@0: this.nss_t.PK11AttrFlags = ctypes.unsigned_int; michael@0: // security/nss/lib/util/seccomon.h#83 michael@0: // typedef struct SECItemStr SECItem; --> SECItemStr defined right below it michael@0: this.nss_t.SECItem = ctypes.StructType( michael@0: "SECItem", [{ type: this.nss_t.SECItemType }, michael@0: { data: ctypes.unsigned_char.ptr }, michael@0: { len : ctypes.int }]); michael@0: // security/nss/lib/util/secoidt.h#52 michael@0: // typedef struct SECAlgorithmIDStr --> def'n right below it michael@0: this.nss_t.SECAlgorithmID = ctypes.StructType( michael@0: "SECAlgorithmID", [{ algorithm: this.nss_t.SECItem }, michael@0: { parameters: this.nss_t.SECItem }]); michael@0: michael@0: michael@0: // security/nss/lib/util/pkcs11t.h michael@0: this.nss.CKK_RSA = 0x0; michael@0: this.nss.CKM_RSA_PKCS_KEY_PAIR_GEN = 0x0000; michael@0: this.nss.CKM_AES_KEY_GEN = 0x1080; michael@0: this.nss.CKA_ENCRYPT = 0x104; michael@0: this.nss.CKA_DECRYPT = 0x105; michael@0: michael@0: // security/nss/lib/softoken/secmodt.h michael@0: this.nss.PK11_ATTR_SESSION = 0x02; michael@0: this.nss.PK11_ATTR_PUBLIC = 0x08; michael@0: this.nss.PK11_ATTR_SENSITIVE = 0x40; michael@0: michael@0: // security/nss/lib/util/secoidt.h michael@0: this.nss.SEC_OID_PKCS5_PBKDF2 = 291; michael@0: this.nss.SEC_OID_HMAC_SHA1 = 294; michael@0: this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION = 16; michael@0: michael@0: michael@0: // security/nss/lib/pk11wrap/pk11pub.h#286 michael@0: // SECStatus PK11_GenerateRandom(unsigned char *data,int len); michael@0: this.nss.PK11_GenerateRandom = nsslib.declare("PK11_GenerateRandom", michael@0: ctypes.default_abi, this.nss_t.SECStatus, michael@0: ctypes.unsigned_char.ptr, ctypes.int); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#74 michael@0: // PK11SlotInfo *PK11_GetInternalSlot(void); michael@0: this.nss.PK11_GetInternalSlot = nsslib.declare("PK11_GetInternalSlot", michael@0: ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#73 michael@0: // PK11SlotInfo *PK11_GetInternalKeySlot(void); michael@0: this.nss.PK11_GetInternalKeySlot = nsslib.declare("PK11_GetInternalKeySlot", michael@0: ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#328 michael@0: // PK11SymKey *PK11_KeyGen(PK11SlotInfo *slot,CK_MECHANISM_TYPE type, SECItem *param, int keySize,void *wincx); michael@0: this.nss.PK11_KeyGen = nsslib.declare("PK11_KeyGen", michael@0: ctypes.default_abi, this.nss_t.PK11SymKey.ptr, michael@0: this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, michael@0: this.nss_t.SECItem.ptr, ctypes.int, ctypes.voidptr_t); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#477 michael@0: // SECStatus PK11_ExtractKeyValue(PK11SymKey *symKey); michael@0: this.nss.PK11_ExtractKeyValue = nsslib.declare("PK11_ExtractKeyValue", michael@0: ctypes.default_abi, this.nss_t.SECStatus, michael@0: this.nss_t.PK11SymKey.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#478 michael@0: // SECItem * PK11_GetKeyData(PK11SymKey *symKey); michael@0: this.nss.PK11_GetKeyData = nsslib.declare("PK11_GetKeyData", michael@0: ctypes.default_abi, this.nss_t.SECItem.ptr, michael@0: this.nss_t.PK11SymKey.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#278 michael@0: // CK_MECHANISM_TYPE PK11_AlgtagToMechanism(SECOidTag algTag); michael@0: this.nss.PK11_AlgtagToMechanism = nsslib.declare("PK11_AlgtagToMechanism", michael@0: ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE, michael@0: this.nss_t.SECOidTag); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#270 michael@0: // int PK11_GetIVLength(CK_MECHANISM_TYPE type); michael@0: this.nss.PK11_GetIVLength = nsslib.declare("PK11_GetIVLength", michael@0: ctypes.default_abi, ctypes.int, michael@0: this.nss_t.CK_MECHANISM_TYPE); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#269 michael@0: // int PK11_GetBlockSize(CK_MECHANISM_TYPE type,SECItem *params); michael@0: this.nss.PK11_GetBlockSize = nsslib.declare("PK11_GetBlockSize", michael@0: ctypes.default_abi, ctypes.int, michael@0: this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#293 michael@0: // CK_MECHANISM_TYPE PK11_GetPadMechanism(CK_MECHANISM_TYPE); michael@0: this.nss.PK11_GetPadMechanism = nsslib.declare("PK11_GetPadMechanism", michael@0: ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE, michael@0: this.nss_t.CK_MECHANISM_TYPE); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#271 michael@0: // SECItem *PK11_ParamFromIV(CK_MECHANISM_TYPE type,SECItem *iv); michael@0: this.nss.PK11_ParamFromIV = nsslib.declare("PK11_ParamFromIV", michael@0: ctypes.default_abi, this.nss_t.SECItem.ptr, michael@0: this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#301 michael@0: // PK11SymKey *PK11_ImportSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PK11Origin origin, michael@0: // CK_ATTRIBUTE_TYPE operation, SECItem *key, void *wincx); michael@0: this.nss.PK11_ImportSymKey = nsslib.declare("PK11_ImportSymKey", michael@0: ctypes.default_abi, this.nss_t.PK11SymKey.ptr, michael@0: this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, this.nss_t.PK11Origin, michael@0: this.nss_t.CK_ATTRIBUTE_TYPE, this.nss_t.SECItem.ptr, ctypes.voidptr_t); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#672 michael@0: // PK11Context *PK11_CreateContextBySymKey(CK_MECHANISM_TYPE type, CK_ATTRIBUTE_TYPE operation, michael@0: // PK11SymKey *symKey, SECItem *param); michael@0: this.nss.PK11_CreateContextBySymKey = nsslib.declare("PK11_CreateContextBySymKey", michael@0: ctypes.default_abi, this.nss_t.PK11Context.ptr, michael@0: this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE, michael@0: this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#685 michael@0: // SECStatus PK11_CipherOp(PK11Context *context, unsigned char *out michael@0: // int *outlen, int maxout, unsigned char *in, int inlen); michael@0: this.nss.PK11_CipherOp = nsslib.declare("PK11_CipherOp", michael@0: ctypes.default_abi, this.nss_t.SECStatus, michael@0: this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr, michael@0: ctypes.int.ptr, ctypes.int, ctypes.unsigned_char.ptr, ctypes.int); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#688 michael@0: // SECStatus PK11_DigestFinal(PK11Context *context, unsigned char *data, michael@0: // unsigned int *outLen, unsigned int length); michael@0: this.nss.PK11_DigestFinal = nsslib.declare("PK11_DigestFinal", michael@0: ctypes.default_abi, this.nss_t.SECStatus, michael@0: this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr, michael@0: ctypes.unsigned_int.ptr, ctypes.unsigned_int); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#731 michael@0: // SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag, michael@0: // SECOidTag prfAlgTag, int keyLength, int iteration, michael@0: // SECItem *salt); michael@0: this.nss.PK11_CreatePBEV2AlgorithmID = nsslib.declare("PK11_CreatePBEV2AlgorithmID", michael@0: ctypes.default_abi, this.nss_t.SECAlgorithmID.ptr, michael@0: this.nss_t.SECOidTag, this.nss_t.SECOidTag, this.nss_t.SECOidTag, michael@0: ctypes.int, ctypes.int, this.nss_t.SECItem.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#736 michael@0: // PK11SymKey * PK11_PBEKeyGen(PK11SlotInfo *slot, SECAlgorithmID *algid, SECItem *pwitem, PRBool faulty3DES, void *wincx); michael@0: this.nss.PK11_PBEKeyGen = nsslib.declare("PK11_PBEKeyGen", michael@0: ctypes.default_abi, this.nss_t.PK11SymKey.ptr, michael@0: this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr, michael@0: this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#675 michael@0: // void PK11_DestroyContext(PK11Context *context, PRBool freeit); michael@0: this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext", michael@0: ctypes.default_abi, ctypes.void_t, michael@0: this.nss_t.PK11Context.ptr, this.nss_t.PRBool); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#299 michael@0: // void PK11_FreeSymKey(PK11SymKey *key); michael@0: this.nss.PK11_FreeSymKey = nsslib.declare("PK11_FreeSymKey", michael@0: ctypes.default_abi, ctypes.void_t, michael@0: this.nss_t.PK11SymKey.ptr); michael@0: // security/nss/lib/pk11wrap/pk11pub.h#70 michael@0: // void PK11_FreeSlot(PK11SlotInfo *slot); michael@0: this.nss.PK11_FreeSlot = nsslib.declare("PK11_FreeSlot", michael@0: ctypes.default_abi, ctypes.void_t, michael@0: this.nss_t.PK11SlotInfo.ptr); michael@0: // security/nss/lib/util/secitem.h#49 michael@0: // extern SECItem *SECITEM_AllocItem(PRArenaPool *arena, SECItem *item, unsigned int len); michael@0: this.nss.SECITEM_AllocItem = nsslib.declare("SECITEM_AllocItem", michael@0: ctypes.default_abi, this.nss_t.SECItem.ptr, michael@0: this.nss_t.PLArenaPool.ptr, // Not used. michael@0: this.nss_t.SECItem.ptr, ctypes.unsigned_int); michael@0: // security/nss/lib/util/secitem.h#274 michael@0: // extern void SECITEM_ZfreeItem(SECItem *zap, PRBool freeit); michael@0: this.nss.SECITEM_ZfreeItem = nsslib.declare("SECITEM_ZfreeItem", michael@0: ctypes.default_abi, ctypes.void_t, michael@0: this.nss_t.SECItem.ptr, this.nss_t.PRBool); michael@0: // security/nss/lib/util/secitem.h#114 michael@0: // extern void SECITEM_FreeItem(SECItem *zap, PRBool freeit); michael@0: this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem", michael@0: ctypes.default_abi, ctypes.void_t, michael@0: this.nss_t.SECItem.ptr, this.nss_t.PRBool); michael@0: // security/nss/lib/util/secoid.h#103 michael@0: // extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit); michael@0: this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID", michael@0: ctypes.default_abi, ctypes.void_t, michael@0: this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool); michael@0: }, michael@0: michael@0: michael@0: _sharedInputBuffer: null, michael@0: _sharedInputBufferInts: null, michael@0: _sharedInputBufferSize: 0, michael@0: _sharedOutputBuffer: null, michael@0: _sharedOutputBufferSize: 0, michael@0: _randomByteBuffer: null, michael@0: _randomByteBufferAddr: null, michael@0: _randomByteBufferSize: 0, michael@0: michael@0: _getInputBuffer: function _getInputBuffer(size) { michael@0: if (size > this._sharedInputBufferSize) { michael@0: let b = new ctypes.ArrayType(ctypes.unsigned_char, size)(); michael@0: this._sharedInputBuffer = b; michael@0: this._sharedInputBufferInts = ctypes.cast(b, ctypes.uint8_t.array(size)); michael@0: this._sharedInputBufferSize = size; michael@0: } michael@0: return this._sharedInputBuffer; michael@0: }, michael@0: michael@0: _getOutputBuffer: function _getOutputBuffer(size) { michael@0: if (size > this._sharedOutputBufferSize) { michael@0: let b = new ctypes.ArrayType(ctypes.unsigned_char, size)(); michael@0: this._sharedOutputBuffer = b; michael@0: this._sharedOutputBufferSize = size; michael@0: } michael@0: return this._sharedOutputBuffer; michael@0: }, michael@0: michael@0: _getRandomByteBuffer: function _getRandomByteBuffer(size) { michael@0: if (size > this._randomByteBufferSize) { michael@0: let b = new ctypes.ArrayType(ctypes.unsigned_char, size)(); michael@0: this._randomByteBuffer = b; michael@0: this._randomByteBufferAddr = b.address(); michael@0: this._randomByteBufferSize = size; michael@0: } michael@0: return this._randomByteBuffer; michael@0: }, michael@0: michael@0: initBuffers: function initBuffers(initialSize) { michael@0: this._getInputBuffer(initialSize); michael@0: this._getOutputBuffer(initialSize); michael@0: michael@0: this._getRandomByteBuffer(this.ivLength); michael@0: }, michael@0: michael@0: encrypt : function(clearTextUCS2, symmetricKey, iv) { michael@0: this.log("encrypt() called"); michael@0: michael@0: // js-ctypes autoconverts to a UTF8 buffer, but also includes a null michael@0: // at the end which we don't want. Decrement length to skip it. michael@0: let inputBuffer = new ctypes.ArrayType(ctypes.unsigned_char)(clearTextUCS2); michael@0: let inputBufferSize = inputBuffer.length - 1; michael@0: michael@0: // When using CBC padding, the output size is the input size rounded michael@0: // up to the nearest block. If the input size is exactly on a block michael@0: // boundary, the output is 1 extra block long. michael@0: let outputBufferSize = inputBufferSize + this.blockSize; michael@0: let outputBuffer = this._getOutputBuffer(outputBufferSize); michael@0: michael@0: outputBuffer = this._commonCrypt(inputBuffer, inputBufferSize, michael@0: outputBuffer, outputBufferSize, michael@0: symmetricKey, iv, this.nss.CKA_ENCRYPT); michael@0: michael@0: return this.encodeBase64(outputBuffer.address(), outputBuffer.length); michael@0: }, michael@0: michael@0: michael@0: decrypt : function(cipherText, symmetricKey, iv) { michael@0: this.log("decrypt() called"); michael@0: michael@0: let inputUCS2 = ""; michael@0: if (cipherText.length) michael@0: inputUCS2 = atob(cipherText); michael@0: michael@0: // We can't have js-ctypes create the buffer directly from the string michael@0: // (as in encrypt()), because we do _not_ want it to do UTF8 michael@0: // conversion... We've got random binary data in the input's low byte. michael@0: // michael@0: // Compress a JS string (2-byte chars) into a normal C string (1-byte chars). michael@0: let len = inputUCS2.length; michael@0: let input = this._getInputBuffer(len); michael@0: this.byteCompressInts(inputUCS2, this._sharedInputBufferInts, len); michael@0: michael@0: let outputBuffer = this._commonCrypt(input, len, michael@0: this._getOutputBuffer(len), len, michael@0: symmetricKey, iv, this.nss.CKA_DECRYPT); michael@0: michael@0: // outputBuffer contains UTF-8 data, let js-ctypes autoconvert that to a JS string. michael@0: // XXX Bug 573842: wrap the string from ctypes to get a new string, so michael@0: // we don't hit bug 573841. michael@0: return "" + outputBuffer.readString() + ""; michael@0: }, michael@0: michael@0: _commonCrypt : function (input, inputLength, output, outputLength, symmetricKey, iv, operation) { michael@0: this.log("_commonCrypt() called"); michael@0: iv = atob(iv); michael@0: michael@0: // We never want an IV longer than the block size, which is 16 bytes michael@0: // for AES. Neither do we want one smaller; throw in that case. michael@0: if (iv.length < this.blockSize) michael@0: throw "IV too short; must be " + this.blockSize + " bytes."; michael@0: if (iv.length > this.blockSize) michael@0: iv = iv.slice(0, this.blockSize); michael@0: michael@0: // We use a single IV SECItem for the sake of efficiency. Fill it here. michael@0: this.byteCompressInts(iv, this._ivSECItemContents, iv.length); michael@0: michael@0: let ctx, symKey, ivParam; michael@0: try { michael@0: ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem); michael@0: if (ivParam.isNull()) michael@0: throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE); michael@0: michael@0: symKey = this.importSymKey(symmetricKey, operation); michael@0: ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam); michael@0: if (ctx.isNull()) michael@0: throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE); michael@0: michael@0: let maxOutputSize = outputLength; michael@0: if (this.nss.PK11_CipherOp(ctx, output, this._commonCryptSignedOutputSize.address(), maxOutputSize, input, inputLength)) michael@0: throw Components.Exception("cipher operation failed", Cr.NS_ERROR_FAILURE); michael@0: michael@0: let actualOutputSize = this._commonCryptSignedOutputSize.value; michael@0: let finalOutput = output.addressOfElement(actualOutputSize); michael@0: maxOutputSize -= actualOutputSize; michael@0: michael@0: // PK11_DigestFinal sure sounds like the last step for *hashing*, but it michael@0: // just seems to be an odd name -- NSS uses this to finish the current michael@0: // cipher operation. You'd think it would be called PK11_CipherOpFinal... michael@0: if (this.nss.PK11_DigestFinal(ctx, finalOutput, this._commonCryptUnsignedOutputSizeAddr, maxOutputSize)) michael@0: throw Components.Exception("cipher finalize failed", Cr.NS_ERROR_FAILURE); michael@0: michael@0: actualOutputSize += this._commonCryptUnsignedOutputSize.value; michael@0: let newOutput = ctypes.cast(output, ctypes.unsigned_char.array(actualOutputSize)); michael@0: return newOutput; michael@0: } catch (e) { michael@0: this.log("_commonCrypt: failed: " + e); michael@0: throw e; michael@0: } finally { michael@0: if (ctx && !ctx.isNull()) michael@0: this.nss.PK11_DestroyContext(ctx, true); michael@0: if (ivParam && !ivParam.isNull()) michael@0: this.nss.SECITEM_FreeItem(ivParam, true); michael@0: michael@0: // Note that we do not free the IV SECItem; we reuse it. michael@0: // Neither do we free the symKey, because that's memoized. michael@0: } michael@0: }, michael@0: michael@0: michael@0: generateRandomKey : function() { michael@0: this.log("generateRandomKey() called"); michael@0: let slot, randKey, keydata; michael@0: try { michael@0: slot = this.nss.PK11_GetInternalSlot(); michael@0: if (slot.isNull()) michael@0: throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE); michael@0: michael@0: randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null); michael@0: if (randKey.isNull()) michael@0: throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE); michael@0: michael@0: // Slightly odd API, this call just prepares the key value for michael@0: // extraction, we get the actual bits from the call to PK11_GetKeyData(). michael@0: if (this.nss.PK11_ExtractKeyValue(randKey)) michael@0: throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE); michael@0: michael@0: keydata = this.nss.PK11_GetKeyData(randKey); michael@0: if (keydata.isNull()) michael@0: throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE); michael@0: michael@0: return this.encodeBase64(keydata.contents.data, keydata.contents.len); michael@0: } catch (e) { michael@0: this.log("generateRandomKey: failed: " + e); michael@0: throw e; michael@0: } finally { michael@0: if (randKey && !randKey.isNull()) michael@0: this.nss.PK11_FreeSymKey(randKey); michael@0: if (slot && !slot.isNull()) michael@0: this.nss.PK11_FreeSlot(slot); michael@0: } michael@0: }, michael@0: michael@0: generateRandomIV : function() this.generateRandomBytes(this.ivLength), michael@0: michael@0: generateRandomBytes : function(byteCount) { michael@0: this.log("generateRandomBytes() called"); michael@0: michael@0: // Temporary buffer to hold the generated data. michael@0: let scratch = this._getRandomByteBuffer(byteCount); michael@0: if (this.nss.PK11_GenerateRandom(scratch, byteCount)) michael@0: throw Components.Exception("PK11_GenrateRandom failed", Cr.NS_ERROR_FAILURE); michael@0: michael@0: return this.encodeBase64(this._randomByteBufferAddr, byteCount); michael@0: }, michael@0: michael@0: // michael@0: // PK11SymKey memoization. michael@0: // michael@0: michael@0: // Memoize the lookup of symmetric keys. We do this by using the base64 michael@0: // string itself as a key -- the overhead of SECItem creation during the michael@0: // initial population is negligible, so that phase is not memoized. michael@0: _encryptionSymKeyMemo: {}, michael@0: _decryptionSymKeyMemo: {}, michael@0: importSymKey: function importSymKey(encodedKeyString, operation) { michael@0: let memo; michael@0: michael@0: // We use two separate memos for thoroughness: operation is an input to michael@0: // key import. michael@0: switch (operation) { michael@0: case this.nss.CKA_ENCRYPT: michael@0: memo = this._encryptionSymKeyMemo; michael@0: break; michael@0: case this.nss.CKA_DECRYPT: michael@0: memo = this._decryptionSymKeyMemo; michael@0: break; michael@0: default: michael@0: throw "Unsupported operation in importSymKey."; michael@0: } michael@0: michael@0: if (encodedKeyString in memo) michael@0: return memo[encodedKeyString]; michael@0: michael@0: let keyItem, slot; michael@0: try { michael@0: keyItem = this.makeSECItem(encodedKeyString, true); michael@0: slot = this.nss.PK11_GetInternalKeySlot(); michael@0: if (slot.isNull()) michael@0: throw Components.Exception("can't get internal key slot", michael@0: Cr.NS_ERROR_FAILURE); michael@0: michael@0: let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism, michael@0: this.nss.PK11_OriginUnwrap, michael@0: operation, keyItem, null); michael@0: if (!symKey || symKey.isNull()) michael@0: throw Components.Exception("symkey import failed", michael@0: Cr.NS_ERROR_FAILURE); michael@0: michael@0: return memo[encodedKeyString] = symKey; michael@0: } finally { michael@0: if (slot && !slot.isNull()) michael@0: this.nss.PK11_FreeSlot(slot); michael@0: this.freeSECItem(keyItem); michael@0: } michael@0: }, michael@0: michael@0: michael@0: // michael@0: // Utility functions michael@0: // michael@0: michael@0: /** michael@0: * Compress a JS string into a C uint8 array. count is the number of michael@0: * elements in the destination array. If the array is smaller than the michael@0: * string, the string is effectively truncated. If the string is smaller michael@0: * than the array, the array is not 0-padded. michael@0: */ michael@0: byteCompressInts : function byteCompressInts (jsString, intArray, count) { michael@0: let len = jsString.length; michael@0: let end = Math.min(len, count); michael@0: for (let i = 0; i < end; i++) michael@0: intArray[i] = jsString.charCodeAt(i) & 0xFF; // convert to bytes. michael@0: }, michael@0: michael@0: // Expand a normal C string (1-byte chars) into a JS string (2-byte chars) michael@0: // EG, for "ABC", 0x41, 0x42, 0x43 --> 0x0041, 0x0042, 0x0043 michael@0: byteExpand : function (charArray) { michael@0: let expanded = ""; michael@0: let len = charArray.length; michael@0: let intData = ctypes.cast(charArray, ctypes.uint8_t.array(len)); michael@0: for (let i = 0; i < len; i++) michael@0: expanded += String.fromCharCode(intData[i]); michael@0: return expanded; michael@0: }, michael@0: michael@0: expandData : function expandData(data, len) { michael@0: // Byte-expand the buffer, so we can treat it as a UCS-2 string michael@0: // consisting of u0000 - u00FF. michael@0: let expanded = ""; michael@0: let intData = ctypes.cast(data, ctypes.uint8_t.array(len).ptr).contents; michael@0: for (let i = 0; i < len; i++) michael@0: expanded += String.fromCharCode(intData[i]); michael@0: return expanded; michael@0: }, michael@0: michael@0: encodeBase64 : function (data, len) { michael@0: return btoa(this.expandData(data, len)); michael@0: }, michael@0: michael@0: // Returns a filled SECItem *, as returned by SECITEM_AllocItem. michael@0: // michael@0: // Note that this must be released with freeSECItem, which will also michael@0: // deallocate the internal buffer. michael@0: makeSECItem : function(input, isEncoded) { michael@0: if (isEncoded) michael@0: input = atob(input); michael@0: michael@0: let len = input.length; michael@0: let item = this.nss.SECITEM_AllocItem(null, null, len); michael@0: if (item.isNull()) michael@0: throw "SECITEM_AllocItem failed."; michael@0: michael@0: let ptr = ctypes.cast(item.contents.data, michael@0: ctypes.unsigned_char.array(len).ptr); michael@0: let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len)); michael@0: this.byteCompressInts(input, dest, len); michael@0: return item; michael@0: }, michael@0: michael@0: freeSECItem : function(zap) { michael@0: if (zap && !zap.isNull()) michael@0: this.nss.SECITEM_ZfreeItem(zap, true); michael@0: }, michael@0: michael@0: // We only ever handle one IV at a time, and they're always different. michael@0: // Consequently, we maintain a single SECItem, and a handy pointer into its michael@0: // contents to avoid repetitive and expensive casts. michael@0: _ivSECItem: null, michael@0: _ivSECItemContents: null, michael@0: michael@0: initIVSECItem: function initIVSECItem() { michael@0: if (this._ivSECItem) { michael@0: this._ivSECItemContents = null; michael@0: this.freeSECItem(this._ivSECItem); michael@0: } michael@0: michael@0: let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize); michael@0: if (item.isNull()) michael@0: throw "SECITEM_AllocItem failed."; michael@0: michael@0: let ptr = ctypes.cast(item.contents.data, michael@0: ctypes.unsigned_char.array(this.blockSize).ptr); michael@0: let contents = ctypes.cast(ptr.contents, michael@0: ctypes.uint8_t.array(this.blockSize)); michael@0: this._ivSECItem = item; michael@0: this._ivSECItemContents = contents; michael@0: }, michael@0: michael@0: /** michael@0: * Returns the expanded data string for the derived key. michael@0: */ michael@0: deriveKeyFromPassphrase : function deriveKeyFromPassphrase(passphrase, salt, keyLength) { michael@0: this.log("deriveKeyFromPassphrase() called."); michael@0: let passItem = this.makeSECItem(passphrase, false); michael@0: let saltItem = this.makeSECItem(salt, true); michael@0: michael@0: let pbeAlg = ALGORITHM; michael@0: let cipherAlg = ALGORITHM; // Ignored by callee when pbeAlg != a pkcs5 mech. michael@0: michael@0: // Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported. michael@0: let prfAlg = this.nss.SEC_OID_HMAC_SHA1; michael@0: michael@0: let keyLength = keyLength || 0; // 0 = Callee will pick. michael@0: let iterations = KEY_DERIVATION_ITERATIONS; michael@0: michael@0: let algid, slot, symKey, keyData; michael@0: try { michael@0: algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg, michael@0: keyLength, iterations, michael@0: saltItem); michael@0: if (algid.isNull()) michael@0: throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE); michael@0: michael@0: slot = this.nss.PK11_GetInternalSlot(); michael@0: if (slot.isNull()) michael@0: throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE); michael@0: michael@0: symKey = this.nss.PK11_PBEKeyGen(slot, algid, passItem, false, null); michael@0: if (symKey.isNull()) michael@0: throw Components.Exception("PK11_PBEKeyGen failed", Cr.NS_ERROR_FAILURE); michael@0: michael@0: // Take the PK11SymKeyStr, returning the extracted key data. michael@0: if (this.nss.PK11_ExtractKeyValue(symKey)) { michael@0: throw this.makeException("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: keyData = this.nss.PK11_GetKeyData(symKey); michael@0: michael@0: if (keyData.isNull()) michael@0: throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE); michael@0: michael@0: // This copies the key contents into a JS string, so we don't leak. michael@0: // The `finally` block below will clean up. michael@0: return this.expandData(keyData.contents.data, keyData.contents.len); michael@0: michael@0: } catch (e) { michael@0: this.log("deriveKeyFromPassphrase: failed: " + e); michael@0: throw e; michael@0: } finally { michael@0: if (algid && !algid.isNull()) michael@0: this.nss.SECOID_DestroyAlgorithmID(algid, true); michael@0: if (slot && !slot.isNull()) michael@0: this.nss.PK11_FreeSlot(slot); michael@0: if (symKey && !symKey.isNull()) michael@0: this.nss.PK11_FreeSymKey(symKey); michael@0: michael@0: this.freeSECItem(passItem); michael@0: this.freeSECItem(saltItem); michael@0: } michael@0: }, michael@0: };