1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/crypto/modules/WeaveCrypto.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,760 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = ["WeaveCrypto"]; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cr = Components.results; 1.13 + 1.14 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.16 +Components.utils.import("resource://gre/modules/ctypes.jsm"); 1.17 + 1.18 +/** 1.19 + * Shortcuts for some algorithm SEC OIDs. Full list available here: 1.20 + * http://lxr.mozilla.org/seamonkey/source/security/nss/lib/util/secoidt.h 1.21 + */ 1.22 +const DES_EDE3_CBC = 156; 1.23 +const AES_128_CBC = 184; 1.24 +const AES_192_CBC = 186; 1.25 +const AES_256_CBC = 188; 1.26 + 1.27 +const ALGORITHM = AES_256_CBC; 1.28 +const KEYSIZE_AES_256 = 32; 1.29 +const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000. 1.30 +const INITIAL_BUFFER_SIZE = 1024; 1.31 + 1.32 +this.WeaveCrypto = function WeaveCrypto() { 1.33 + this.init(); 1.34 +} 1.35 + 1.36 +WeaveCrypto.prototype = { 1.37 + prefBranch : null, 1.38 + debug : true, // services.sync.log.cryptoDebug 1.39 + nss : null, 1.40 + nss_t : null, 1.41 + 1.42 + observer : { 1.43 + _self : null, 1.44 + 1.45 + QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, 1.46 + Ci.nsISupportsWeakReference]), 1.47 + 1.48 + observe : function (subject, topic, data) { 1.49 + let self = this._self; 1.50 + self.log("Observed " + topic + " topic."); 1.51 + if (topic == "nsPref:changed") { 1.52 + self.debug = self.prefBranch.getBoolPref("cryptoDebug"); 1.53 + } 1.54 + } 1.55 + }, 1.56 + 1.57 + init : function() { 1.58 + try { 1.59 + // Preferences. Add observer so we get notified of changes. 1.60 + this.prefBranch = Services.prefs.getBranch("services.sync.log."); 1.61 + this.prefBranch.addObserver("cryptoDebug", this.observer, false); 1.62 + this.observer._self = this; 1.63 + try { 1.64 + this.debug = this.prefBranch.getBoolPref("cryptoDebug"); 1.65 + } catch (x) { 1.66 + this.debug = false; 1.67 + } 1.68 + 1.69 + this.initNSS(); 1.70 + this.initAlgorithmSettings(); // Depends on NSS. 1.71 + this.initIVSECItem(); 1.72 + this.initSharedInts(); 1.73 + this.initBuffers(INITIAL_BUFFER_SIZE); 1.74 + } catch (e) { 1.75 + this.log("init failed: " + e); 1.76 + throw e; 1.77 + } 1.78 + }, 1.79 + 1.80 + // Avoid allocating new temporary ints on every run of _commonCrypt. 1.81 + _commonCryptSignedOutputSize: null, 1.82 + _commonCryptSignedOutputSizeAddr: null, 1.83 + _commonCryptUnsignedOutputSize: null, 1.84 + _commonCryptUnsignedOutputSizeAddr: null, 1.85 + 1.86 + initSharedInts: function initSharedInts() { 1.87 + let signed = new ctypes.int(); 1.88 + let unsigned = new ctypes.unsigned_int(); 1.89 + this._commonCryptSignedOutputSize = signed; 1.90 + this._commonCryptUnsignedOutputSize = unsigned; 1.91 + this._commonCryptSignedOutputSizeAddr = signed.address(); 1.92 + this._commonCryptUnsignedOutputSizeAddr = unsigned.address(); 1.93 + }, 1.94 + 1.95 + /** 1.96 + * Set a bunch of NSS values once, at init-time. These are: 1.97 + * - .blockSize 1.98 + * - .mechanism 1.99 + * - .keygenMechanism 1.100 + * - .padMechanism 1.101 + * - .keySize 1.102 + * 1.103 + * See also the constant ALGORITHM. 1.104 + */ 1.105 + initAlgorithmSettings: function() { 1.106 + this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM); 1.107 + this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null); 1.108 + this.ivLength = this.nss.PK11_GetIVLength(this.mechanism); 1.109 + this.keySize = KEYSIZE_AES_256; 1.110 + this.keygenMechanism = this.nss.CKM_AES_KEY_GEN; // Always the same! 1.111 + 1.112 + // Determine which (padded) PKCS#11 mechanism to use. 1.113 + // E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD 1.114 + this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism); 1.115 + if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM) 1.116 + throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE); 1.117 + }, 1.118 + 1.119 + log : function (message) { 1.120 + if (!this.debug) 1.121 + return; 1.122 + dump("WeaveCrypto: " + message + "\n"); 1.123 + Services.console.logStringMessage("WeaveCrypto: " + message); 1.124 + }, 1.125 + 1.126 + initNSS : function() { 1.127 + // We use NSS for the crypto ops, which needs to be initialized before 1.128 + // use. By convention, PSM is required to be the module that 1.129 + // initializes NSS. So, make sure PSM is initialized in order to 1.130 + // implicitly initialize NSS. 1.131 + Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); 1.132 + 1.133 + // Open the NSS library. 1.134 + let path = ctypes.libraryName("nss3"); 1.135 + 1.136 + // XXX really want to be able to pass specific dlopen flags here. 1.137 + var nsslib; 1.138 + try { 1.139 + this.log("Trying NSS library without path"); 1.140 + nsslib = ctypes.open(path); 1.141 + } catch(e) { 1.142 + // In case opening the library without a full path fails, 1.143 + // try again with a full path. 1.144 + let file = Services.dirsvc.get("GreD", Ci.nsILocalFile); 1.145 + file.append(path); 1.146 + this.log("Trying again with path " + file.path); 1.147 + nsslib = ctypes.open(file.path); 1.148 + } 1.149 + 1.150 + this.log("Initializing NSS types and function declarations..."); 1.151 + 1.152 + this.nss = {}; 1.153 + this.nss_t = {}; 1.154 + 1.155 + // nsprpub/pr/include/prtypes.h#435 1.156 + // typedef PRIntn PRBool; --> int 1.157 + this.nss_t.PRBool = ctypes.int; 1.158 + // security/nss/lib/util/seccomon.h#91 1.159 + // typedef enum 1.160 + this.nss_t.SECStatus = ctypes.int; 1.161 + // security/nss/lib/softoken/secmodt.h#59 1.162 + // typedef struct PK11SlotInfoStr PK11SlotInfo; (defined in secmodti.h) 1.163 + this.nss_t.PK11SlotInfo = ctypes.void_t; 1.164 + // security/nss/lib/util/pkcs11t.h 1.165 + this.nss_t.CK_MECHANISM_TYPE = ctypes.unsigned_long; 1.166 + this.nss_t.CK_ATTRIBUTE_TYPE = ctypes.unsigned_long; 1.167 + this.nss_t.CK_KEY_TYPE = ctypes.unsigned_long; 1.168 + this.nss_t.CK_OBJECT_HANDLE = ctypes.unsigned_long; 1.169 + // security/nss/lib/softoken/secmodt.h#359 1.170 + // typedef enum PK11Origin 1.171 + this.nss_t.PK11Origin = ctypes.int; 1.172 + // PK11Origin enum values... 1.173 + this.nss.PK11_OriginUnwrap = 4; 1.174 + // security/nss/lib/softoken/secmodt.h#61 1.175 + // typedef struct PK11SymKeyStr PK11SymKey; (defined in secmodti.h) 1.176 + this.nss_t.PK11SymKey = ctypes.void_t; 1.177 + // security/nss/lib/util/secoidt.h#454 1.178 + // typedef enum 1.179 + this.nss_t.SECOidTag = ctypes.int; 1.180 + // security/nss/lib/util/seccomon.h#64 1.181 + // typedef enum 1.182 + this.nss_t.SECItemType = ctypes.int; 1.183 + // SECItemType enum values... 1.184 + this.nss.SIBUFFER = 0; 1.185 + // security/nss/lib/softoken/secmodt.h#62 (defined in secmodti.h) 1.186 + // typedef struct PK11ContextStr PK11Context; 1.187 + this.nss_t.PK11Context = ctypes.void_t; 1.188 + // Needed for SECKEYPrivateKey struct def'n, but I don't think we need to actually access it. 1.189 + this.nss_t.PLArenaPool = ctypes.void_t; 1.190 + // security/nss/lib/cryptohi/keythi.h#45 1.191 + // typedef enum 1.192 + this.nss_t.KeyType = ctypes.int; 1.193 + // security/nss/lib/softoken/secmodt.h#201 1.194 + // typedef PRUint32 PK11AttrFlags; 1.195 + this.nss_t.PK11AttrFlags = ctypes.unsigned_int; 1.196 + // security/nss/lib/util/seccomon.h#83 1.197 + // typedef struct SECItemStr SECItem; --> SECItemStr defined right below it 1.198 + this.nss_t.SECItem = ctypes.StructType( 1.199 + "SECItem", [{ type: this.nss_t.SECItemType }, 1.200 + { data: ctypes.unsigned_char.ptr }, 1.201 + { len : ctypes.int }]); 1.202 + // security/nss/lib/util/secoidt.h#52 1.203 + // typedef struct SECAlgorithmIDStr --> def'n right below it 1.204 + this.nss_t.SECAlgorithmID = ctypes.StructType( 1.205 + "SECAlgorithmID", [{ algorithm: this.nss_t.SECItem }, 1.206 + { parameters: this.nss_t.SECItem }]); 1.207 + 1.208 + 1.209 + // security/nss/lib/util/pkcs11t.h 1.210 + this.nss.CKK_RSA = 0x0; 1.211 + this.nss.CKM_RSA_PKCS_KEY_PAIR_GEN = 0x0000; 1.212 + this.nss.CKM_AES_KEY_GEN = 0x1080; 1.213 + this.nss.CKA_ENCRYPT = 0x104; 1.214 + this.nss.CKA_DECRYPT = 0x105; 1.215 + 1.216 + // security/nss/lib/softoken/secmodt.h 1.217 + this.nss.PK11_ATTR_SESSION = 0x02; 1.218 + this.nss.PK11_ATTR_PUBLIC = 0x08; 1.219 + this.nss.PK11_ATTR_SENSITIVE = 0x40; 1.220 + 1.221 + // security/nss/lib/util/secoidt.h 1.222 + this.nss.SEC_OID_PKCS5_PBKDF2 = 291; 1.223 + this.nss.SEC_OID_HMAC_SHA1 = 294; 1.224 + this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION = 16; 1.225 + 1.226 + 1.227 + // security/nss/lib/pk11wrap/pk11pub.h#286 1.228 + // SECStatus PK11_GenerateRandom(unsigned char *data,int len); 1.229 + this.nss.PK11_GenerateRandom = nsslib.declare("PK11_GenerateRandom", 1.230 + ctypes.default_abi, this.nss_t.SECStatus, 1.231 + ctypes.unsigned_char.ptr, ctypes.int); 1.232 + // security/nss/lib/pk11wrap/pk11pub.h#74 1.233 + // PK11SlotInfo *PK11_GetInternalSlot(void); 1.234 + this.nss.PK11_GetInternalSlot = nsslib.declare("PK11_GetInternalSlot", 1.235 + ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr); 1.236 + // security/nss/lib/pk11wrap/pk11pub.h#73 1.237 + // PK11SlotInfo *PK11_GetInternalKeySlot(void); 1.238 + this.nss.PK11_GetInternalKeySlot = nsslib.declare("PK11_GetInternalKeySlot", 1.239 + ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr); 1.240 + // security/nss/lib/pk11wrap/pk11pub.h#328 1.241 + // PK11SymKey *PK11_KeyGen(PK11SlotInfo *slot,CK_MECHANISM_TYPE type, SECItem *param, int keySize,void *wincx); 1.242 + this.nss.PK11_KeyGen = nsslib.declare("PK11_KeyGen", 1.243 + ctypes.default_abi, this.nss_t.PK11SymKey.ptr, 1.244 + this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, 1.245 + this.nss_t.SECItem.ptr, ctypes.int, ctypes.voidptr_t); 1.246 + // security/nss/lib/pk11wrap/pk11pub.h#477 1.247 + // SECStatus PK11_ExtractKeyValue(PK11SymKey *symKey); 1.248 + this.nss.PK11_ExtractKeyValue = nsslib.declare("PK11_ExtractKeyValue", 1.249 + ctypes.default_abi, this.nss_t.SECStatus, 1.250 + this.nss_t.PK11SymKey.ptr); 1.251 + // security/nss/lib/pk11wrap/pk11pub.h#478 1.252 + // SECItem * PK11_GetKeyData(PK11SymKey *symKey); 1.253 + this.nss.PK11_GetKeyData = nsslib.declare("PK11_GetKeyData", 1.254 + ctypes.default_abi, this.nss_t.SECItem.ptr, 1.255 + this.nss_t.PK11SymKey.ptr); 1.256 + // security/nss/lib/pk11wrap/pk11pub.h#278 1.257 + // CK_MECHANISM_TYPE PK11_AlgtagToMechanism(SECOidTag algTag); 1.258 + this.nss.PK11_AlgtagToMechanism = nsslib.declare("PK11_AlgtagToMechanism", 1.259 + ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE, 1.260 + this.nss_t.SECOidTag); 1.261 + // security/nss/lib/pk11wrap/pk11pub.h#270 1.262 + // int PK11_GetIVLength(CK_MECHANISM_TYPE type); 1.263 + this.nss.PK11_GetIVLength = nsslib.declare("PK11_GetIVLength", 1.264 + ctypes.default_abi, ctypes.int, 1.265 + this.nss_t.CK_MECHANISM_TYPE); 1.266 + // security/nss/lib/pk11wrap/pk11pub.h#269 1.267 + // int PK11_GetBlockSize(CK_MECHANISM_TYPE type,SECItem *params); 1.268 + this.nss.PK11_GetBlockSize = nsslib.declare("PK11_GetBlockSize", 1.269 + ctypes.default_abi, ctypes.int, 1.270 + this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr); 1.271 + // security/nss/lib/pk11wrap/pk11pub.h#293 1.272 + // CK_MECHANISM_TYPE PK11_GetPadMechanism(CK_MECHANISM_TYPE); 1.273 + this.nss.PK11_GetPadMechanism = nsslib.declare("PK11_GetPadMechanism", 1.274 + ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE, 1.275 + this.nss_t.CK_MECHANISM_TYPE); 1.276 + // security/nss/lib/pk11wrap/pk11pub.h#271 1.277 + // SECItem *PK11_ParamFromIV(CK_MECHANISM_TYPE type,SECItem *iv); 1.278 + this.nss.PK11_ParamFromIV = nsslib.declare("PK11_ParamFromIV", 1.279 + ctypes.default_abi, this.nss_t.SECItem.ptr, 1.280 + this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr); 1.281 + // security/nss/lib/pk11wrap/pk11pub.h#301 1.282 + // PK11SymKey *PK11_ImportSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PK11Origin origin, 1.283 + // CK_ATTRIBUTE_TYPE operation, SECItem *key, void *wincx); 1.284 + this.nss.PK11_ImportSymKey = nsslib.declare("PK11_ImportSymKey", 1.285 + ctypes.default_abi, this.nss_t.PK11SymKey.ptr, 1.286 + this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, this.nss_t.PK11Origin, 1.287 + this.nss_t.CK_ATTRIBUTE_TYPE, this.nss_t.SECItem.ptr, ctypes.voidptr_t); 1.288 + // security/nss/lib/pk11wrap/pk11pub.h#672 1.289 + // PK11Context *PK11_CreateContextBySymKey(CK_MECHANISM_TYPE type, CK_ATTRIBUTE_TYPE operation, 1.290 + // PK11SymKey *symKey, SECItem *param); 1.291 + this.nss.PK11_CreateContextBySymKey = nsslib.declare("PK11_CreateContextBySymKey", 1.292 + ctypes.default_abi, this.nss_t.PK11Context.ptr, 1.293 + this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE, 1.294 + this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr); 1.295 + // security/nss/lib/pk11wrap/pk11pub.h#685 1.296 + // SECStatus PK11_CipherOp(PK11Context *context, unsigned char *out 1.297 + // int *outlen, int maxout, unsigned char *in, int inlen); 1.298 + this.nss.PK11_CipherOp = nsslib.declare("PK11_CipherOp", 1.299 + ctypes.default_abi, this.nss_t.SECStatus, 1.300 + this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr, 1.301 + ctypes.int.ptr, ctypes.int, ctypes.unsigned_char.ptr, ctypes.int); 1.302 + // security/nss/lib/pk11wrap/pk11pub.h#688 1.303 + // SECStatus PK11_DigestFinal(PK11Context *context, unsigned char *data, 1.304 + // unsigned int *outLen, unsigned int length); 1.305 + this.nss.PK11_DigestFinal = nsslib.declare("PK11_DigestFinal", 1.306 + ctypes.default_abi, this.nss_t.SECStatus, 1.307 + this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr, 1.308 + ctypes.unsigned_int.ptr, ctypes.unsigned_int); 1.309 + // security/nss/lib/pk11wrap/pk11pub.h#731 1.310 + // SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag, 1.311 + // SECOidTag prfAlgTag, int keyLength, int iteration, 1.312 + // SECItem *salt); 1.313 + this.nss.PK11_CreatePBEV2AlgorithmID = nsslib.declare("PK11_CreatePBEV2AlgorithmID", 1.314 + ctypes.default_abi, this.nss_t.SECAlgorithmID.ptr, 1.315 + this.nss_t.SECOidTag, this.nss_t.SECOidTag, this.nss_t.SECOidTag, 1.316 + ctypes.int, ctypes.int, this.nss_t.SECItem.ptr); 1.317 + // security/nss/lib/pk11wrap/pk11pub.h#736 1.318 + // PK11SymKey * PK11_PBEKeyGen(PK11SlotInfo *slot, SECAlgorithmID *algid, SECItem *pwitem, PRBool faulty3DES, void *wincx); 1.319 + this.nss.PK11_PBEKeyGen = nsslib.declare("PK11_PBEKeyGen", 1.320 + ctypes.default_abi, this.nss_t.PK11SymKey.ptr, 1.321 + this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr, 1.322 + this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t); 1.323 + // security/nss/lib/pk11wrap/pk11pub.h#675 1.324 + // void PK11_DestroyContext(PK11Context *context, PRBool freeit); 1.325 + this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext", 1.326 + ctypes.default_abi, ctypes.void_t, 1.327 + this.nss_t.PK11Context.ptr, this.nss_t.PRBool); 1.328 + // security/nss/lib/pk11wrap/pk11pub.h#299 1.329 + // void PK11_FreeSymKey(PK11SymKey *key); 1.330 + this.nss.PK11_FreeSymKey = nsslib.declare("PK11_FreeSymKey", 1.331 + ctypes.default_abi, ctypes.void_t, 1.332 + this.nss_t.PK11SymKey.ptr); 1.333 + // security/nss/lib/pk11wrap/pk11pub.h#70 1.334 + // void PK11_FreeSlot(PK11SlotInfo *slot); 1.335 + this.nss.PK11_FreeSlot = nsslib.declare("PK11_FreeSlot", 1.336 + ctypes.default_abi, ctypes.void_t, 1.337 + this.nss_t.PK11SlotInfo.ptr); 1.338 + // security/nss/lib/util/secitem.h#49 1.339 + // extern SECItem *SECITEM_AllocItem(PRArenaPool *arena, SECItem *item, unsigned int len); 1.340 + this.nss.SECITEM_AllocItem = nsslib.declare("SECITEM_AllocItem", 1.341 + ctypes.default_abi, this.nss_t.SECItem.ptr, 1.342 + this.nss_t.PLArenaPool.ptr, // Not used. 1.343 + this.nss_t.SECItem.ptr, ctypes.unsigned_int); 1.344 + // security/nss/lib/util/secitem.h#274 1.345 + // extern void SECITEM_ZfreeItem(SECItem *zap, PRBool freeit); 1.346 + this.nss.SECITEM_ZfreeItem = nsslib.declare("SECITEM_ZfreeItem", 1.347 + ctypes.default_abi, ctypes.void_t, 1.348 + this.nss_t.SECItem.ptr, this.nss_t.PRBool); 1.349 + // security/nss/lib/util/secitem.h#114 1.350 + // extern void SECITEM_FreeItem(SECItem *zap, PRBool freeit); 1.351 + this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem", 1.352 + ctypes.default_abi, ctypes.void_t, 1.353 + this.nss_t.SECItem.ptr, this.nss_t.PRBool); 1.354 + // security/nss/lib/util/secoid.h#103 1.355 + // extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit); 1.356 + this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID", 1.357 + ctypes.default_abi, ctypes.void_t, 1.358 + this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool); 1.359 + }, 1.360 + 1.361 + 1.362 + _sharedInputBuffer: null, 1.363 + _sharedInputBufferInts: null, 1.364 + _sharedInputBufferSize: 0, 1.365 + _sharedOutputBuffer: null, 1.366 + _sharedOutputBufferSize: 0, 1.367 + _randomByteBuffer: null, 1.368 + _randomByteBufferAddr: null, 1.369 + _randomByteBufferSize: 0, 1.370 + 1.371 + _getInputBuffer: function _getInputBuffer(size) { 1.372 + if (size > this._sharedInputBufferSize) { 1.373 + let b = new ctypes.ArrayType(ctypes.unsigned_char, size)(); 1.374 + this._sharedInputBuffer = b; 1.375 + this._sharedInputBufferInts = ctypes.cast(b, ctypes.uint8_t.array(size)); 1.376 + this._sharedInputBufferSize = size; 1.377 + } 1.378 + return this._sharedInputBuffer; 1.379 + }, 1.380 + 1.381 + _getOutputBuffer: function _getOutputBuffer(size) { 1.382 + if (size > this._sharedOutputBufferSize) { 1.383 + let b = new ctypes.ArrayType(ctypes.unsigned_char, size)(); 1.384 + this._sharedOutputBuffer = b; 1.385 + this._sharedOutputBufferSize = size; 1.386 + } 1.387 + return this._sharedOutputBuffer; 1.388 + }, 1.389 + 1.390 + _getRandomByteBuffer: function _getRandomByteBuffer(size) { 1.391 + if (size > this._randomByteBufferSize) { 1.392 + let b = new ctypes.ArrayType(ctypes.unsigned_char, size)(); 1.393 + this._randomByteBuffer = b; 1.394 + this._randomByteBufferAddr = b.address(); 1.395 + this._randomByteBufferSize = size; 1.396 + } 1.397 + return this._randomByteBuffer; 1.398 + }, 1.399 + 1.400 + initBuffers: function initBuffers(initialSize) { 1.401 + this._getInputBuffer(initialSize); 1.402 + this._getOutputBuffer(initialSize); 1.403 + 1.404 + this._getRandomByteBuffer(this.ivLength); 1.405 + }, 1.406 + 1.407 + encrypt : function(clearTextUCS2, symmetricKey, iv) { 1.408 + this.log("encrypt() called"); 1.409 + 1.410 + // js-ctypes autoconverts to a UTF8 buffer, but also includes a null 1.411 + // at the end which we don't want. Decrement length to skip it. 1.412 + let inputBuffer = new ctypes.ArrayType(ctypes.unsigned_char)(clearTextUCS2); 1.413 + let inputBufferSize = inputBuffer.length - 1; 1.414 + 1.415 + // When using CBC padding, the output size is the input size rounded 1.416 + // up to the nearest block. If the input size is exactly on a block 1.417 + // boundary, the output is 1 extra block long. 1.418 + let outputBufferSize = inputBufferSize + this.blockSize; 1.419 + let outputBuffer = this._getOutputBuffer(outputBufferSize); 1.420 + 1.421 + outputBuffer = this._commonCrypt(inputBuffer, inputBufferSize, 1.422 + outputBuffer, outputBufferSize, 1.423 + symmetricKey, iv, this.nss.CKA_ENCRYPT); 1.424 + 1.425 + return this.encodeBase64(outputBuffer.address(), outputBuffer.length); 1.426 + }, 1.427 + 1.428 + 1.429 + decrypt : function(cipherText, symmetricKey, iv) { 1.430 + this.log("decrypt() called"); 1.431 + 1.432 + let inputUCS2 = ""; 1.433 + if (cipherText.length) 1.434 + inputUCS2 = atob(cipherText); 1.435 + 1.436 + // We can't have js-ctypes create the buffer directly from the string 1.437 + // (as in encrypt()), because we do _not_ want it to do UTF8 1.438 + // conversion... We've got random binary data in the input's low byte. 1.439 + // 1.440 + // Compress a JS string (2-byte chars) into a normal C string (1-byte chars). 1.441 + let len = inputUCS2.length; 1.442 + let input = this._getInputBuffer(len); 1.443 + this.byteCompressInts(inputUCS2, this._sharedInputBufferInts, len); 1.444 + 1.445 + let outputBuffer = this._commonCrypt(input, len, 1.446 + this._getOutputBuffer(len), len, 1.447 + symmetricKey, iv, this.nss.CKA_DECRYPT); 1.448 + 1.449 + // outputBuffer contains UTF-8 data, let js-ctypes autoconvert that to a JS string. 1.450 + // XXX Bug 573842: wrap the string from ctypes to get a new string, so 1.451 + // we don't hit bug 573841. 1.452 + return "" + outputBuffer.readString() + ""; 1.453 + }, 1.454 + 1.455 + _commonCrypt : function (input, inputLength, output, outputLength, symmetricKey, iv, operation) { 1.456 + this.log("_commonCrypt() called"); 1.457 + iv = atob(iv); 1.458 + 1.459 + // We never want an IV longer than the block size, which is 16 bytes 1.460 + // for AES. Neither do we want one smaller; throw in that case. 1.461 + if (iv.length < this.blockSize) 1.462 + throw "IV too short; must be " + this.blockSize + " bytes."; 1.463 + if (iv.length > this.blockSize) 1.464 + iv = iv.slice(0, this.blockSize); 1.465 + 1.466 + // We use a single IV SECItem for the sake of efficiency. Fill it here. 1.467 + this.byteCompressInts(iv, this._ivSECItemContents, iv.length); 1.468 + 1.469 + let ctx, symKey, ivParam; 1.470 + try { 1.471 + ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem); 1.472 + if (ivParam.isNull()) 1.473 + throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE); 1.474 + 1.475 + symKey = this.importSymKey(symmetricKey, operation); 1.476 + ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam); 1.477 + if (ctx.isNull()) 1.478 + throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE); 1.479 + 1.480 + let maxOutputSize = outputLength; 1.481 + if (this.nss.PK11_CipherOp(ctx, output, this._commonCryptSignedOutputSize.address(), maxOutputSize, input, inputLength)) 1.482 + throw Components.Exception("cipher operation failed", Cr.NS_ERROR_FAILURE); 1.483 + 1.484 + let actualOutputSize = this._commonCryptSignedOutputSize.value; 1.485 + let finalOutput = output.addressOfElement(actualOutputSize); 1.486 + maxOutputSize -= actualOutputSize; 1.487 + 1.488 + // PK11_DigestFinal sure sounds like the last step for *hashing*, but it 1.489 + // just seems to be an odd name -- NSS uses this to finish the current 1.490 + // cipher operation. You'd think it would be called PK11_CipherOpFinal... 1.491 + if (this.nss.PK11_DigestFinal(ctx, finalOutput, this._commonCryptUnsignedOutputSizeAddr, maxOutputSize)) 1.492 + throw Components.Exception("cipher finalize failed", Cr.NS_ERROR_FAILURE); 1.493 + 1.494 + actualOutputSize += this._commonCryptUnsignedOutputSize.value; 1.495 + let newOutput = ctypes.cast(output, ctypes.unsigned_char.array(actualOutputSize)); 1.496 + return newOutput; 1.497 + } catch (e) { 1.498 + this.log("_commonCrypt: failed: " + e); 1.499 + throw e; 1.500 + } finally { 1.501 + if (ctx && !ctx.isNull()) 1.502 + this.nss.PK11_DestroyContext(ctx, true); 1.503 + if (ivParam && !ivParam.isNull()) 1.504 + this.nss.SECITEM_FreeItem(ivParam, true); 1.505 + 1.506 + // Note that we do not free the IV SECItem; we reuse it. 1.507 + // Neither do we free the symKey, because that's memoized. 1.508 + } 1.509 + }, 1.510 + 1.511 + 1.512 + generateRandomKey : function() { 1.513 + this.log("generateRandomKey() called"); 1.514 + let slot, randKey, keydata; 1.515 + try { 1.516 + slot = this.nss.PK11_GetInternalSlot(); 1.517 + if (slot.isNull()) 1.518 + throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE); 1.519 + 1.520 + randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null); 1.521 + if (randKey.isNull()) 1.522 + throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE); 1.523 + 1.524 + // Slightly odd API, this call just prepares the key value for 1.525 + // extraction, we get the actual bits from the call to PK11_GetKeyData(). 1.526 + if (this.nss.PK11_ExtractKeyValue(randKey)) 1.527 + throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE); 1.528 + 1.529 + keydata = this.nss.PK11_GetKeyData(randKey); 1.530 + if (keydata.isNull()) 1.531 + throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE); 1.532 + 1.533 + return this.encodeBase64(keydata.contents.data, keydata.contents.len); 1.534 + } catch (e) { 1.535 + this.log("generateRandomKey: failed: " + e); 1.536 + throw e; 1.537 + } finally { 1.538 + if (randKey && !randKey.isNull()) 1.539 + this.nss.PK11_FreeSymKey(randKey); 1.540 + if (slot && !slot.isNull()) 1.541 + this.nss.PK11_FreeSlot(slot); 1.542 + } 1.543 + }, 1.544 + 1.545 + generateRandomIV : function() this.generateRandomBytes(this.ivLength), 1.546 + 1.547 + generateRandomBytes : function(byteCount) { 1.548 + this.log("generateRandomBytes() called"); 1.549 + 1.550 + // Temporary buffer to hold the generated data. 1.551 + let scratch = this._getRandomByteBuffer(byteCount); 1.552 + if (this.nss.PK11_GenerateRandom(scratch, byteCount)) 1.553 + throw Components.Exception("PK11_GenrateRandom failed", Cr.NS_ERROR_FAILURE); 1.554 + 1.555 + return this.encodeBase64(this._randomByteBufferAddr, byteCount); 1.556 + }, 1.557 + 1.558 + // 1.559 + // PK11SymKey memoization. 1.560 + // 1.561 + 1.562 + // Memoize the lookup of symmetric keys. We do this by using the base64 1.563 + // string itself as a key -- the overhead of SECItem creation during the 1.564 + // initial population is negligible, so that phase is not memoized. 1.565 + _encryptionSymKeyMemo: {}, 1.566 + _decryptionSymKeyMemo: {}, 1.567 + importSymKey: function importSymKey(encodedKeyString, operation) { 1.568 + let memo; 1.569 + 1.570 + // We use two separate memos for thoroughness: operation is an input to 1.571 + // key import. 1.572 + switch (operation) { 1.573 + case this.nss.CKA_ENCRYPT: 1.574 + memo = this._encryptionSymKeyMemo; 1.575 + break; 1.576 + case this.nss.CKA_DECRYPT: 1.577 + memo = this._decryptionSymKeyMemo; 1.578 + break; 1.579 + default: 1.580 + throw "Unsupported operation in importSymKey."; 1.581 + } 1.582 + 1.583 + if (encodedKeyString in memo) 1.584 + return memo[encodedKeyString]; 1.585 + 1.586 + let keyItem, slot; 1.587 + try { 1.588 + keyItem = this.makeSECItem(encodedKeyString, true); 1.589 + slot = this.nss.PK11_GetInternalKeySlot(); 1.590 + if (slot.isNull()) 1.591 + throw Components.Exception("can't get internal key slot", 1.592 + Cr.NS_ERROR_FAILURE); 1.593 + 1.594 + let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism, 1.595 + this.nss.PK11_OriginUnwrap, 1.596 + operation, keyItem, null); 1.597 + if (!symKey || symKey.isNull()) 1.598 + throw Components.Exception("symkey import failed", 1.599 + Cr.NS_ERROR_FAILURE); 1.600 + 1.601 + return memo[encodedKeyString] = symKey; 1.602 + } finally { 1.603 + if (slot && !slot.isNull()) 1.604 + this.nss.PK11_FreeSlot(slot); 1.605 + this.freeSECItem(keyItem); 1.606 + } 1.607 + }, 1.608 + 1.609 + 1.610 + // 1.611 + // Utility functions 1.612 + // 1.613 + 1.614 + /** 1.615 + * Compress a JS string into a C uint8 array. count is the number of 1.616 + * elements in the destination array. If the array is smaller than the 1.617 + * string, the string is effectively truncated. If the string is smaller 1.618 + * than the array, the array is not 0-padded. 1.619 + */ 1.620 + byteCompressInts : function byteCompressInts (jsString, intArray, count) { 1.621 + let len = jsString.length; 1.622 + let end = Math.min(len, count); 1.623 + for (let i = 0; i < end; i++) 1.624 + intArray[i] = jsString.charCodeAt(i) & 0xFF; // convert to bytes. 1.625 + }, 1.626 + 1.627 + // Expand a normal C string (1-byte chars) into a JS string (2-byte chars) 1.628 + // EG, for "ABC", 0x41, 0x42, 0x43 --> 0x0041, 0x0042, 0x0043 1.629 + byteExpand : function (charArray) { 1.630 + let expanded = ""; 1.631 + let len = charArray.length; 1.632 + let intData = ctypes.cast(charArray, ctypes.uint8_t.array(len)); 1.633 + for (let i = 0; i < len; i++) 1.634 + expanded += String.fromCharCode(intData[i]); 1.635 + return expanded; 1.636 + }, 1.637 + 1.638 + expandData : function expandData(data, len) { 1.639 + // Byte-expand the buffer, so we can treat it as a UCS-2 string 1.640 + // consisting of u0000 - u00FF. 1.641 + let expanded = ""; 1.642 + let intData = ctypes.cast(data, ctypes.uint8_t.array(len).ptr).contents; 1.643 + for (let i = 0; i < len; i++) 1.644 + expanded += String.fromCharCode(intData[i]); 1.645 + return expanded; 1.646 + }, 1.647 + 1.648 + encodeBase64 : function (data, len) { 1.649 + return btoa(this.expandData(data, len)); 1.650 + }, 1.651 + 1.652 + // Returns a filled SECItem *, as returned by SECITEM_AllocItem. 1.653 + // 1.654 + // Note that this must be released with freeSECItem, which will also 1.655 + // deallocate the internal buffer. 1.656 + makeSECItem : function(input, isEncoded) { 1.657 + if (isEncoded) 1.658 + input = atob(input); 1.659 + 1.660 + let len = input.length; 1.661 + let item = this.nss.SECITEM_AllocItem(null, null, len); 1.662 + if (item.isNull()) 1.663 + throw "SECITEM_AllocItem failed."; 1.664 + 1.665 + let ptr = ctypes.cast(item.contents.data, 1.666 + ctypes.unsigned_char.array(len).ptr); 1.667 + let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len)); 1.668 + this.byteCompressInts(input, dest, len); 1.669 + return item; 1.670 + }, 1.671 + 1.672 + freeSECItem : function(zap) { 1.673 + if (zap && !zap.isNull()) 1.674 + this.nss.SECITEM_ZfreeItem(zap, true); 1.675 + }, 1.676 + 1.677 + // We only ever handle one IV at a time, and they're always different. 1.678 + // Consequently, we maintain a single SECItem, and a handy pointer into its 1.679 + // contents to avoid repetitive and expensive casts. 1.680 + _ivSECItem: null, 1.681 + _ivSECItemContents: null, 1.682 + 1.683 + initIVSECItem: function initIVSECItem() { 1.684 + if (this._ivSECItem) { 1.685 + this._ivSECItemContents = null; 1.686 + this.freeSECItem(this._ivSECItem); 1.687 + } 1.688 + 1.689 + let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize); 1.690 + if (item.isNull()) 1.691 + throw "SECITEM_AllocItem failed."; 1.692 + 1.693 + let ptr = ctypes.cast(item.contents.data, 1.694 + ctypes.unsigned_char.array(this.blockSize).ptr); 1.695 + let contents = ctypes.cast(ptr.contents, 1.696 + ctypes.uint8_t.array(this.blockSize)); 1.697 + this._ivSECItem = item; 1.698 + this._ivSECItemContents = contents; 1.699 + }, 1.700 + 1.701 + /** 1.702 + * Returns the expanded data string for the derived key. 1.703 + */ 1.704 + deriveKeyFromPassphrase : function deriveKeyFromPassphrase(passphrase, salt, keyLength) { 1.705 + this.log("deriveKeyFromPassphrase() called."); 1.706 + let passItem = this.makeSECItem(passphrase, false); 1.707 + let saltItem = this.makeSECItem(salt, true); 1.708 + 1.709 + let pbeAlg = ALGORITHM; 1.710 + let cipherAlg = ALGORITHM; // Ignored by callee when pbeAlg != a pkcs5 mech. 1.711 + 1.712 + // Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported. 1.713 + let prfAlg = this.nss.SEC_OID_HMAC_SHA1; 1.714 + 1.715 + let keyLength = keyLength || 0; // 0 = Callee will pick. 1.716 + let iterations = KEY_DERIVATION_ITERATIONS; 1.717 + 1.718 + let algid, slot, symKey, keyData; 1.719 + try { 1.720 + algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg, 1.721 + keyLength, iterations, 1.722 + saltItem); 1.723 + if (algid.isNull()) 1.724 + throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE); 1.725 + 1.726 + slot = this.nss.PK11_GetInternalSlot(); 1.727 + if (slot.isNull()) 1.728 + throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE); 1.729 + 1.730 + symKey = this.nss.PK11_PBEKeyGen(slot, algid, passItem, false, null); 1.731 + if (symKey.isNull()) 1.732 + throw Components.Exception("PK11_PBEKeyGen failed", Cr.NS_ERROR_FAILURE); 1.733 + 1.734 + // Take the PK11SymKeyStr, returning the extracted key data. 1.735 + if (this.nss.PK11_ExtractKeyValue(symKey)) { 1.736 + throw this.makeException("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE); 1.737 + } 1.738 + 1.739 + keyData = this.nss.PK11_GetKeyData(symKey); 1.740 + 1.741 + if (keyData.isNull()) 1.742 + throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE); 1.743 + 1.744 + // This copies the key contents into a JS string, so we don't leak. 1.745 + // The `finally` block below will clean up. 1.746 + return this.expandData(keyData.contents.data, keyData.contents.len); 1.747 + 1.748 + } catch (e) { 1.749 + this.log("deriveKeyFromPassphrase: failed: " + e); 1.750 + throw e; 1.751 + } finally { 1.752 + if (algid && !algid.isNull()) 1.753 + this.nss.SECOID_DestroyAlgorithmID(algid, true); 1.754 + if (slot && !slot.isNull()) 1.755 + this.nss.PK11_FreeSlot(slot); 1.756 + if (symKey && !symKey.isNull()) 1.757 + this.nss.PK11_FreeSymKey(symKey); 1.758 + 1.759 + this.freeSECItem(passItem); 1.760 + this.freeSECItem(saltItem); 1.761 + } 1.762 + }, 1.763 +};