Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 this.EXPORTED_SYMBOLS = ["WeaveCrypto"];
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cr = Components.results;
11 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
12 Components.utils.import("resource://gre/modules/Services.jsm");
13 Components.utils.import("resource://gre/modules/ctypes.jsm");
15 /**
16 * Shortcuts for some algorithm SEC OIDs. Full list available here:
17 * http://lxr.mozilla.org/seamonkey/source/security/nss/lib/util/secoidt.h
18 */
19 const DES_EDE3_CBC = 156;
20 const AES_128_CBC = 184;
21 const AES_192_CBC = 186;
22 const AES_256_CBC = 188;
24 const ALGORITHM = AES_256_CBC;
25 const KEYSIZE_AES_256 = 32;
26 const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000.
27 const INITIAL_BUFFER_SIZE = 1024;
29 this.WeaveCrypto = function WeaveCrypto() {
30 this.init();
31 }
33 WeaveCrypto.prototype = {
34 prefBranch : null,
35 debug : true, // services.sync.log.cryptoDebug
36 nss : null,
37 nss_t : null,
39 observer : {
40 _self : null,
42 QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
43 Ci.nsISupportsWeakReference]),
45 observe : function (subject, topic, data) {
46 let self = this._self;
47 self.log("Observed " + topic + " topic.");
48 if (topic == "nsPref:changed") {
49 self.debug = self.prefBranch.getBoolPref("cryptoDebug");
50 }
51 }
52 },
54 init : function() {
55 try {
56 // Preferences. Add observer so we get notified of changes.
57 this.prefBranch = Services.prefs.getBranch("services.sync.log.");
58 this.prefBranch.addObserver("cryptoDebug", this.observer, false);
59 this.observer._self = this;
60 try {
61 this.debug = this.prefBranch.getBoolPref("cryptoDebug");
62 } catch (x) {
63 this.debug = false;
64 }
66 this.initNSS();
67 this.initAlgorithmSettings(); // Depends on NSS.
68 this.initIVSECItem();
69 this.initSharedInts();
70 this.initBuffers(INITIAL_BUFFER_SIZE);
71 } catch (e) {
72 this.log("init failed: " + e);
73 throw e;
74 }
75 },
77 // Avoid allocating new temporary ints on every run of _commonCrypt.
78 _commonCryptSignedOutputSize: null,
79 _commonCryptSignedOutputSizeAddr: null,
80 _commonCryptUnsignedOutputSize: null,
81 _commonCryptUnsignedOutputSizeAddr: null,
83 initSharedInts: function initSharedInts() {
84 let signed = new ctypes.int();
85 let unsigned = new ctypes.unsigned_int();
86 this._commonCryptSignedOutputSize = signed;
87 this._commonCryptUnsignedOutputSize = unsigned;
88 this._commonCryptSignedOutputSizeAddr = signed.address();
89 this._commonCryptUnsignedOutputSizeAddr = unsigned.address();
90 },
92 /**
93 * Set a bunch of NSS values once, at init-time. These are:
94 * - .blockSize
95 * - .mechanism
96 * - .keygenMechanism
97 * - .padMechanism
98 * - .keySize
99 *
100 * See also the constant ALGORITHM.
101 */
102 initAlgorithmSettings: function() {
103 this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM);
104 this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null);
105 this.ivLength = this.nss.PK11_GetIVLength(this.mechanism);
106 this.keySize = KEYSIZE_AES_256;
107 this.keygenMechanism = this.nss.CKM_AES_KEY_GEN; // Always the same!
109 // Determine which (padded) PKCS#11 mechanism to use.
110 // E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
111 this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism);
112 if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM)
113 throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
114 },
116 log : function (message) {
117 if (!this.debug)
118 return;
119 dump("WeaveCrypto: " + message + "\n");
120 Services.console.logStringMessage("WeaveCrypto: " + message);
121 },
123 initNSS : function() {
124 // We use NSS for the crypto ops, which needs to be initialized before
125 // use. By convention, PSM is required to be the module that
126 // initializes NSS. So, make sure PSM is initialized in order to
127 // implicitly initialize NSS.
128 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
130 // Open the NSS library.
131 let path = ctypes.libraryName("nss3");
133 // XXX really want to be able to pass specific dlopen flags here.
134 var nsslib;
135 try {
136 this.log("Trying NSS library without path");
137 nsslib = ctypes.open(path);
138 } catch(e) {
139 // In case opening the library without a full path fails,
140 // try again with a full path.
141 let file = Services.dirsvc.get("GreD", Ci.nsILocalFile);
142 file.append(path);
143 this.log("Trying again with path " + file.path);
144 nsslib = ctypes.open(file.path);
145 }
147 this.log("Initializing NSS types and function declarations...");
149 this.nss = {};
150 this.nss_t = {};
152 // nsprpub/pr/include/prtypes.h#435
153 // typedef PRIntn PRBool; --> int
154 this.nss_t.PRBool = ctypes.int;
155 // security/nss/lib/util/seccomon.h#91
156 // typedef enum
157 this.nss_t.SECStatus = ctypes.int;
158 // security/nss/lib/softoken/secmodt.h#59
159 // typedef struct PK11SlotInfoStr PK11SlotInfo; (defined in secmodti.h)
160 this.nss_t.PK11SlotInfo = ctypes.void_t;
161 // security/nss/lib/util/pkcs11t.h
162 this.nss_t.CK_MECHANISM_TYPE = ctypes.unsigned_long;
163 this.nss_t.CK_ATTRIBUTE_TYPE = ctypes.unsigned_long;
164 this.nss_t.CK_KEY_TYPE = ctypes.unsigned_long;
165 this.nss_t.CK_OBJECT_HANDLE = ctypes.unsigned_long;
166 // security/nss/lib/softoken/secmodt.h#359
167 // typedef enum PK11Origin
168 this.nss_t.PK11Origin = ctypes.int;
169 // PK11Origin enum values...
170 this.nss.PK11_OriginUnwrap = 4;
171 // security/nss/lib/softoken/secmodt.h#61
172 // typedef struct PK11SymKeyStr PK11SymKey; (defined in secmodti.h)
173 this.nss_t.PK11SymKey = ctypes.void_t;
174 // security/nss/lib/util/secoidt.h#454
175 // typedef enum
176 this.nss_t.SECOidTag = ctypes.int;
177 // security/nss/lib/util/seccomon.h#64
178 // typedef enum
179 this.nss_t.SECItemType = ctypes.int;
180 // SECItemType enum values...
181 this.nss.SIBUFFER = 0;
182 // security/nss/lib/softoken/secmodt.h#62 (defined in secmodti.h)
183 // typedef struct PK11ContextStr PK11Context;
184 this.nss_t.PK11Context = ctypes.void_t;
185 // Needed for SECKEYPrivateKey struct def'n, but I don't think we need to actually access it.
186 this.nss_t.PLArenaPool = ctypes.void_t;
187 // security/nss/lib/cryptohi/keythi.h#45
188 // typedef enum
189 this.nss_t.KeyType = ctypes.int;
190 // security/nss/lib/softoken/secmodt.h#201
191 // typedef PRUint32 PK11AttrFlags;
192 this.nss_t.PK11AttrFlags = ctypes.unsigned_int;
193 // security/nss/lib/util/seccomon.h#83
194 // typedef struct SECItemStr SECItem; --> SECItemStr defined right below it
195 this.nss_t.SECItem = ctypes.StructType(
196 "SECItem", [{ type: this.nss_t.SECItemType },
197 { data: ctypes.unsigned_char.ptr },
198 { len : ctypes.int }]);
199 // security/nss/lib/util/secoidt.h#52
200 // typedef struct SECAlgorithmIDStr --> def'n right below it
201 this.nss_t.SECAlgorithmID = ctypes.StructType(
202 "SECAlgorithmID", [{ algorithm: this.nss_t.SECItem },
203 { parameters: this.nss_t.SECItem }]);
206 // security/nss/lib/util/pkcs11t.h
207 this.nss.CKK_RSA = 0x0;
208 this.nss.CKM_RSA_PKCS_KEY_PAIR_GEN = 0x0000;
209 this.nss.CKM_AES_KEY_GEN = 0x1080;
210 this.nss.CKA_ENCRYPT = 0x104;
211 this.nss.CKA_DECRYPT = 0x105;
213 // security/nss/lib/softoken/secmodt.h
214 this.nss.PK11_ATTR_SESSION = 0x02;
215 this.nss.PK11_ATTR_PUBLIC = 0x08;
216 this.nss.PK11_ATTR_SENSITIVE = 0x40;
218 // security/nss/lib/util/secoidt.h
219 this.nss.SEC_OID_PKCS5_PBKDF2 = 291;
220 this.nss.SEC_OID_HMAC_SHA1 = 294;
221 this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION = 16;
224 // security/nss/lib/pk11wrap/pk11pub.h#286
225 // SECStatus PK11_GenerateRandom(unsigned char *data,int len);
226 this.nss.PK11_GenerateRandom = nsslib.declare("PK11_GenerateRandom",
227 ctypes.default_abi, this.nss_t.SECStatus,
228 ctypes.unsigned_char.ptr, ctypes.int);
229 // security/nss/lib/pk11wrap/pk11pub.h#74
230 // PK11SlotInfo *PK11_GetInternalSlot(void);
231 this.nss.PK11_GetInternalSlot = nsslib.declare("PK11_GetInternalSlot",
232 ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr);
233 // security/nss/lib/pk11wrap/pk11pub.h#73
234 // PK11SlotInfo *PK11_GetInternalKeySlot(void);
235 this.nss.PK11_GetInternalKeySlot = nsslib.declare("PK11_GetInternalKeySlot",
236 ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr);
237 // security/nss/lib/pk11wrap/pk11pub.h#328
238 // PK11SymKey *PK11_KeyGen(PK11SlotInfo *slot,CK_MECHANISM_TYPE type, SECItem *param, int keySize,void *wincx);
239 this.nss.PK11_KeyGen = nsslib.declare("PK11_KeyGen",
240 ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
241 this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE,
242 this.nss_t.SECItem.ptr, ctypes.int, ctypes.voidptr_t);
243 // security/nss/lib/pk11wrap/pk11pub.h#477
244 // SECStatus PK11_ExtractKeyValue(PK11SymKey *symKey);
245 this.nss.PK11_ExtractKeyValue = nsslib.declare("PK11_ExtractKeyValue",
246 ctypes.default_abi, this.nss_t.SECStatus,
247 this.nss_t.PK11SymKey.ptr);
248 // security/nss/lib/pk11wrap/pk11pub.h#478
249 // SECItem * PK11_GetKeyData(PK11SymKey *symKey);
250 this.nss.PK11_GetKeyData = nsslib.declare("PK11_GetKeyData",
251 ctypes.default_abi, this.nss_t.SECItem.ptr,
252 this.nss_t.PK11SymKey.ptr);
253 // security/nss/lib/pk11wrap/pk11pub.h#278
254 // CK_MECHANISM_TYPE PK11_AlgtagToMechanism(SECOidTag algTag);
255 this.nss.PK11_AlgtagToMechanism = nsslib.declare("PK11_AlgtagToMechanism",
256 ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE,
257 this.nss_t.SECOidTag);
258 // security/nss/lib/pk11wrap/pk11pub.h#270
259 // int PK11_GetIVLength(CK_MECHANISM_TYPE type);
260 this.nss.PK11_GetIVLength = nsslib.declare("PK11_GetIVLength",
261 ctypes.default_abi, ctypes.int,
262 this.nss_t.CK_MECHANISM_TYPE);
263 // security/nss/lib/pk11wrap/pk11pub.h#269
264 // int PK11_GetBlockSize(CK_MECHANISM_TYPE type,SECItem *params);
265 this.nss.PK11_GetBlockSize = nsslib.declare("PK11_GetBlockSize",
266 ctypes.default_abi, ctypes.int,
267 this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr);
268 // security/nss/lib/pk11wrap/pk11pub.h#293
269 // CK_MECHANISM_TYPE PK11_GetPadMechanism(CK_MECHANISM_TYPE);
270 this.nss.PK11_GetPadMechanism = nsslib.declare("PK11_GetPadMechanism",
271 ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE,
272 this.nss_t.CK_MECHANISM_TYPE);
273 // security/nss/lib/pk11wrap/pk11pub.h#271
274 // SECItem *PK11_ParamFromIV(CK_MECHANISM_TYPE type,SECItem *iv);
275 this.nss.PK11_ParamFromIV = nsslib.declare("PK11_ParamFromIV",
276 ctypes.default_abi, this.nss_t.SECItem.ptr,
277 this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr);
278 // security/nss/lib/pk11wrap/pk11pub.h#301
279 // PK11SymKey *PK11_ImportSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PK11Origin origin,
280 // CK_ATTRIBUTE_TYPE operation, SECItem *key, void *wincx);
281 this.nss.PK11_ImportSymKey = nsslib.declare("PK11_ImportSymKey",
282 ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
283 this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, this.nss_t.PK11Origin,
284 this.nss_t.CK_ATTRIBUTE_TYPE, this.nss_t.SECItem.ptr, ctypes.voidptr_t);
285 // security/nss/lib/pk11wrap/pk11pub.h#672
286 // PK11Context *PK11_CreateContextBySymKey(CK_MECHANISM_TYPE type, CK_ATTRIBUTE_TYPE operation,
287 // PK11SymKey *symKey, SECItem *param);
288 this.nss.PK11_CreateContextBySymKey = nsslib.declare("PK11_CreateContextBySymKey",
289 ctypes.default_abi, this.nss_t.PK11Context.ptr,
290 this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE,
291 this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr);
292 // security/nss/lib/pk11wrap/pk11pub.h#685
293 // SECStatus PK11_CipherOp(PK11Context *context, unsigned char *out
294 // int *outlen, int maxout, unsigned char *in, int inlen);
295 this.nss.PK11_CipherOp = nsslib.declare("PK11_CipherOp",
296 ctypes.default_abi, this.nss_t.SECStatus,
297 this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
298 ctypes.int.ptr, ctypes.int, ctypes.unsigned_char.ptr, ctypes.int);
299 // security/nss/lib/pk11wrap/pk11pub.h#688
300 // SECStatus PK11_DigestFinal(PK11Context *context, unsigned char *data,
301 // unsigned int *outLen, unsigned int length);
302 this.nss.PK11_DigestFinal = nsslib.declare("PK11_DigestFinal",
303 ctypes.default_abi, this.nss_t.SECStatus,
304 this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
305 ctypes.unsigned_int.ptr, ctypes.unsigned_int);
306 // security/nss/lib/pk11wrap/pk11pub.h#731
307 // SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag,
308 // SECOidTag prfAlgTag, int keyLength, int iteration,
309 // SECItem *salt);
310 this.nss.PK11_CreatePBEV2AlgorithmID = nsslib.declare("PK11_CreatePBEV2AlgorithmID",
311 ctypes.default_abi, this.nss_t.SECAlgorithmID.ptr,
312 this.nss_t.SECOidTag, this.nss_t.SECOidTag, this.nss_t.SECOidTag,
313 ctypes.int, ctypes.int, this.nss_t.SECItem.ptr);
314 // security/nss/lib/pk11wrap/pk11pub.h#736
315 // PK11SymKey * PK11_PBEKeyGen(PK11SlotInfo *slot, SECAlgorithmID *algid, SECItem *pwitem, PRBool faulty3DES, void *wincx);
316 this.nss.PK11_PBEKeyGen = nsslib.declare("PK11_PBEKeyGen",
317 ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
318 this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr,
319 this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t);
320 // security/nss/lib/pk11wrap/pk11pub.h#675
321 // void PK11_DestroyContext(PK11Context *context, PRBool freeit);
322 this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext",
323 ctypes.default_abi, ctypes.void_t,
324 this.nss_t.PK11Context.ptr, this.nss_t.PRBool);
325 // security/nss/lib/pk11wrap/pk11pub.h#299
326 // void PK11_FreeSymKey(PK11SymKey *key);
327 this.nss.PK11_FreeSymKey = nsslib.declare("PK11_FreeSymKey",
328 ctypes.default_abi, ctypes.void_t,
329 this.nss_t.PK11SymKey.ptr);
330 // security/nss/lib/pk11wrap/pk11pub.h#70
331 // void PK11_FreeSlot(PK11SlotInfo *slot);
332 this.nss.PK11_FreeSlot = nsslib.declare("PK11_FreeSlot",
333 ctypes.default_abi, ctypes.void_t,
334 this.nss_t.PK11SlotInfo.ptr);
335 // security/nss/lib/util/secitem.h#49
336 // extern SECItem *SECITEM_AllocItem(PRArenaPool *arena, SECItem *item, unsigned int len);
337 this.nss.SECITEM_AllocItem = nsslib.declare("SECITEM_AllocItem",
338 ctypes.default_abi, this.nss_t.SECItem.ptr,
339 this.nss_t.PLArenaPool.ptr, // Not used.
340 this.nss_t.SECItem.ptr, ctypes.unsigned_int);
341 // security/nss/lib/util/secitem.h#274
342 // extern void SECITEM_ZfreeItem(SECItem *zap, PRBool freeit);
343 this.nss.SECITEM_ZfreeItem = nsslib.declare("SECITEM_ZfreeItem",
344 ctypes.default_abi, ctypes.void_t,
345 this.nss_t.SECItem.ptr, this.nss_t.PRBool);
346 // security/nss/lib/util/secitem.h#114
347 // extern void SECITEM_FreeItem(SECItem *zap, PRBool freeit);
348 this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem",
349 ctypes.default_abi, ctypes.void_t,
350 this.nss_t.SECItem.ptr, this.nss_t.PRBool);
351 // security/nss/lib/util/secoid.h#103
352 // extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit);
353 this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID",
354 ctypes.default_abi, ctypes.void_t,
355 this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool);
356 },
359 _sharedInputBuffer: null,
360 _sharedInputBufferInts: null,
361 _sharedInputBufferSize: 0,
362 _sharedOutputBuffer: null,
363 _sharedOutputBufferSize: 0,
364 _randomByteBuffer: null,
365 _randomByteBufferAddr: null,
366 _randomByteBufferSize: 0,
368 _getInputBuffer: function _getInputBuffer(size) {
369 if (size > this._sharedInputBufferSize) {
370 let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
371 this._sharedInputBuffer = b;
372 this._sharedInputBufferInts = ctypes.cast(b, ctypes.uint8_t.array(size));
373 this._sharedInputBufferSize = size;
374 }
375 return this._sharedInputBuffer;
376 },
378 _getOutputBuffer: function _getOutputBuffer(size) {
379 if (size > this._sharedOutputBufferSize) {
380 let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
381 this._sharedOutputBuffer = b;
382 this._sharedOutputBufferSize = size;
383 }
384 return this._sharedOutputBuffer;
385 },
387 _getRandomByteBuffer: function _getRandomByteBuffer(size) {
388 if (size > this._randomByteBufferSize) {
389 let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
390 this._randomByteBuffer = b;
391 this._randomByteBufferAddr = b.address();
392 this._randomByteBufferSize = size;
393 }
394 return this._randomByteBuffer;
395 },
397 initBuffers: function initBuffers(initialSize) {
398 this._getInputBuffer(initialSize);
399 this._getOutputBuffer(initialSize);
401 this._getRandomByteBuffer(this.ivLength);
402 },
404 encrypt : function(clearTextUCS2, symmetricKey, iv) {
405 this.log("encrypt() called");
407 // js-ctypes autoconverts to a UTF8 buffer, but also includes a null
408 // at the end which we don't want. Decrement length to skip it.
409 let inputBuffer = new ctypes.ArrayType(ctypes.unsigned_char)(clearTextUCS2);
410 let inputBufferSize = inputBuffer.length - 1;
412 // When using CBC padding, the output size is the input size rounded
413 // up to the nearest block. If the input size is exactly on a block
414 // boundary, the output is 1 extra block long.
415 let outputBufferSize = inputBufferSize + this.blockSize;
416 let outputBuffer = this._getOutputBuffer(outputBufferSize);
418 outputBuffer = this._commonCrypt(inputBuffer, inputBufferSize,
419 outputBuffer, outputBufferSize,
420 symmetricKey, iv, this.nss.CKA_ENCRYPT);
422 return this.encodeBase64(outputBuffer.address(), outputBuffer.length);
423 },
426 decrypt : function(cipherText, symmetricKey, iv) {
427 this.log("decrypt() called");
429 let inputUCS2 = "";
430 if (cipherText.length)
431 inputUCS2 = atob(cipherText);
433 // We can't have js-ctypes create the buffer directly from the string
434 // (as in encrypt()), because we do _not_ want it to do UTF8
435 // conversion... We've got random binary data in the input's low byte.
436 //
437 // Compress a JS string (2-byte chars) into a normal C string (1-byte chars).
438 let len = inputUCS2.length;
439 let input = this._getInputBuffer(len);
440 this.byteCompressInts(inputUCS2, this._sharedInputBufferInts, len);
442 let outputBuffer = this._commonCrypt(input, len,
443 this._getOutputBuffer(len), len,
444 symmetricKey, iv, this.nss.CKA_DECRYPT);
446 // outputBuffer contains UTF-8 data, let js-ctypes autoconvert that to a JS string.
447 // XXX Bug 573842: wrap the string from ctypes to get a new string, so
448 // we don't hit bug 573841.
449 return "" + outputBuffer.readString() + "";
450 },
452 _commonCrypt : function (input, inputLength, output, outputLength, symmetricKey, iv, operation) {
453 this.log("_commonCrypt() called");
454 iv = atob(iv);
456 // We never want an IV longer than the block size, which is 16 bytes
457 // for AES. Neither do we want one smaller; throw in that case.
458 if (iv.length < this.blockSize)
459 throw "IV too short; must be " + this.blockSize + " bytes.";
460 if (iv.length > this.blockSize)
461 iv = iv.slice(0, this.blockSize);
463 // We use a single IV SECItem for the sake of efficiency. Fill it here.
464 this.byteCompressInts(iv, this._ivSECItemContents, iv.length);
466 let ctx, symKey, ivParam;
467 try {
468 ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem);
469 if (ivParam.isNull())
470 throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE);
472 symKey = this.importSymKey(symmetricKey, operation);
473 ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam);
474 if (ctx.isNull())
475 throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE);
477 let maxOutputSize = outputLength;
478 if (this.nss.PK11_CipherOp(ctx, output, this._commonCryptSignedOutputSize.address(), maxOutputSize, input, inputLength))
479 throw Components.Exception("cipher operation failed", Cr.NS_ERROR_FAILURE);
481 let actualOutputSize = this._commonCryptSignedOutputSize.value;
482 let finalOutput = output.addressOfElement(actualOutputSize);
483 maxOutputSize -= actualOutputSize;
485 // PK11_DigestFinal sure sounds like the last step for *hashing*, but it
486 // just seems to be an odd name -- NSS uses this to finish the current
487 // cipher operation. You'd think it would be called PK11_CipherOpFinal...
488 if (this.nss.PK11_DigestFinal(ctx, finalOutput, this._commonCryptUnsignedOutputSizeAddr, maxOutputSize))
489 throw Components.Exception("cipher finalize failed", Cr.NS_ERROR_FAILURE);
491 actualOutputSize += this._commonCryptUnsignedOutputSize.value;
492 let newOutput = ctypes.cast(output, ctypes.unsigned_char.array(actualOutputSize));
493 return newOutput;
494 } catch (e) {
495 this.log("_commonCrypt: failed: " + e);
496 throw e;
497 } finally {
498 if (ctx && !ctx.isNull())
499 this.nss.PK11_DestroyContext(ctx, true);
500 if (ivParam && !ivParam.isNull())
501 this.nss.SECITEM_FreeItem(ivParam, true);
503 // Note that we do not free the IV SECItem; we reuse it.
504 // Neither do we free the symKey, because that's memoized.
505 }
506 },
509 generateRandomKey : function() {
510 this.log("generateRandomKey() called");
511 let slot, randKey, keydata;
512 try {
513 slot = this.nss.PK11_GetInternalSlot();
514 if (slot.isNull())
515 throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
517 randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null);
518 if (randKey.isNull())
519 throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE);
521 // Slightly odd API, this call just prepares the key value for
522 // extraction, we get the actual bits from the call to PK11_GetKeyData().
523 if (this.nss.PK11_ExtractKeyValue(randKey))
524 throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
526 keydata = this.nss.PK11_GetKeyData(randKey);
527 if (keydata.isNull())
528 throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE);
530 return this.encodeBase64(keydata.contents.data, keydata.contents.len);
531 } catch (e) {
532 this.log("generateRandomKey: failed: " + e);
533 throw e;
534 } finally {
535 if (randKey && !randKey.isNull())
536 this.nss.PK11_FreeSymKey(randKey);
537 if (slot && !slot.isNull())
538 this.nss.PK11_FreeSlot(slot);
539 }
540 },
542 generateRandomIV : function() this.generateRandomBytes(this.ivLength),
544 generateRandomBytes : function(byteCount) {
545 this.log("generateRandomBytes() called");
547 // Temporary buffer to hold the generated data.
548 let scratch = this._getRandomByteBuffer(byteCount);
549 if (this.nss.PK11_GenerateRandom(scratch, byteCount))
550 throw Components.Exception("PK11_GenrateRandom failed", Cr.NS_ERROR_FAILURE);
552 return this.encodeBase64(this._randomByteBufferAddr, byteCount);
553 },
555 //
556 // PK11SymKey memoization.
557 //
559 // Memoize the lookup of symmetric keys. We do this by using the base64
560 // string itself as a key -- the overhead of SECItem creation during the
561 // initial population is negligible, so that phase is not memoized.
562 _encryptionSymKeyMemo: {},
563 _decryptionSymKeyMemo: {},
564 importSymKey: function importSymKey(encodedKeyString, operation) {
565 let memo;
567 // We use two separate memos for thoroughness: operation is an input to
568 // key import.
569 switch (operation) {
570 case this.nss.CKA_ENCRYPT:
571 memo = this._encryptionSymKeyMemo;
572 break;
573 case this.nss.CKA_DECRYPT:
574 memo = this._decryptionSymKeyMemo;
575 break;
576 default:
577 throw "Unsupported operation in importSymKey.";
578 }
580 if (encodedKeyString in memo)
581 return memo[encodedKeyString];
583 let keyItem, slot;
584 try {
585 keyItem = this.makeSECItem(encodedKeyString, true);
586 slot = this.nss.PK11_GetInternalKeySlot();
587 if (slot.isNull())
588 throw Components.Exception("can't get internal key slot",
589 Cr.NS_ERROR_FAILURE);
591 let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism,
592 this.nss.PK11_OriginUnwrap,
593 operation, keyItem, null);
594 if (!symKey || symKey.isNull())
595 throw Components.Exception("symkey import failed",
596 Cr.NS_ERROR_FAILURE);
598 return memo[encodedKeyString] = symKey;
599 } finally {
600 if (slot && !slot.isNull())
601 this.nss.PK11_FreeSlot(slot);
602 this.freeSECItem(keyItem);
603 }
604 },
607 //
608 // Utility functions
609 //
611 /**
612 * Compress a JS string into a C uint8 array. count is the number of
613 * elements in the destination array. If the array is smaller than the
614 * string, the string is effectively truncated. If the string is smaller
615 * than the array, the array is not 0-padded.
616 */
617 byteCompressInts : function byteCompressInts (jsString, intArray, count) {
618 let len = jsString.length;
619 let end = Math.min(len, count);
620 for (let i = 0; i < end; i++)
621 intArray[i] = jsString.charCodeAt(i) & 0xFF; // convert to bytes.
622 },
624 // Expand a normal C string (1-byte chars) into a JS string (2-byte chars)
625 // EG, for "ABC", 0x41, 0x42, 0x43 --> 0x0041, 0x0042, 0x0043
626 byteExpand : function (charArray) {
627 let expanded = "";
628 let len = charArray.length;
629 let intData = ctypes.cast(charArray, ctypes.uint8_t.array(len));
630 for (let i = 0; i < len; i++)
631 expanded += String.fromCharCode(intData[i]);
632 return expanded;
633 },
635 expandData : function expandData(data, len) {
636 // Byte-expand the buffer, so we can treat it as a UCS-2 string
637 // consisting of u0000 - u00FF.
638 let expanded = "";
639 let intData = ctypes.cast(data, ctypes.uint8_t.array(len).ptr).contents;
640 for (let i = 0; i < len; i++)
641 expanded += String.fromCharCode(intData[i]);
642 return expanded;
643 },
645 encodeBase64 : function (data, len) {
646 return btoa(this.expandData(data, len));
647 },
649 // Returns a filled SECItem *, as returned by SECITEM_AllocItem.
650 //
651 // Note that this must be released with freeSECItem, which will also
652 // deallocate the internal buffer.
653 makeSECItem : function(input, isEncoded) {
654 if (isEncoded)
655 input = atob(input);
657 let len = input.length;
658 let item = this.nss.SECITEM_AllocItem(null, null, len);
659 if (item.isNull())
660 throw "SECITEM_AllocItem failed.";
662 let ptr = ctypes.cast(item.contents.data,
663 ctypes.unsigned_char.array(len).ptr);
664 let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len));
665 this.byteCompressInts(input, dest, len);
666 return item;
667 },
669 freeSECItem : function(zap) {
670 if (zap && !zap.isNull())
671 this.nss.SECITEM_ZfreeItem(zap, true);
672 },
674 // We only ever handle one IV at a time, and they're always different.
675 // Consequently, we maintain a single SECItem, and a handy pointer into its
676 // contents to avoid repetitive and expensive casts.
677 _ivSECItem: null,
678 _ivSECItemContents: null,
680 initIVSECItem: function initIVSECItem() {
681 if (this._ivSECItem) {
682 this._ivSECItemContents = null;
683 this.freeSECItem(this._ivSECItem);
684 }
686 let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize);
687 if (item.isNull())
688 throw "SECITEM_AllocItem failed.";
690 let ptr = ctypes.cast(item.contents.data,
691 ctypes.unsigned_char.array(this.blockSize).ptr);
692 let contents = ctypes.cast(ptr.contents,
693 ctypes.uint8_t.array(this.blockSize));
694 this._ivSECItem = item;
695 this._ivSECItemContents = contents;
696 },
698 /**
699 * Returns the expanded data string for the derived key.
700 */
701 deriveKeyFromPassphrase : function deriveKeyFromPassphrase(passphrase, salt, keyLength) {
702 this.log("deriveKeyFromPassphrase() called.");
703 let passItem = this.makeSECItem(passphrase, false);
704 let saltItem = this.makeSECItem(salt, true);
706 let pbeAlg = ALGORITHM;
707 let cipherAlg = ALGORITHM; // Ignored by callee when pbeAlg != a pkcs5 mech.
709 // Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported.
710 let prfAlg = this.nss.SEC_OID_HMAC_SHA1;
712 let keyLength = keyLength || 0; // 0 = Callee will pick.
713 let iterations = KEY_DERIVATION_ITERATIONS;
715 let algid, slot, symKey, keyData;
716 try {
717 algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
718 keyLength, iterations,
719 saltItem);
720 if (algid.isNull())
721 throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE);
723 slot = this.nss.PK11_GetInternalSlot();
724 if (slot.isNull())
725 throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
727 symKey = this.nss.PK11_PBEKeyGen(slot, algid, passItem, false, null);
728 if (symKey.isNull())
729 throw Components.Exception("PK11_PBEKeyGen failed", Cr.NS_ERROR_FAILURE);
731 // Take the PK11SymKeyStr, returning the extracted key data.
732 if (this.nss.PK11_ExtractKeyValue(symKey)) {
733 throw this.makeException("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
734 }
736 keyData = this.nss.PK11_GetKeyData(symKey);
738 if (keyData.isNull())
739 throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE);
741 // This copies the key contents into a JS string, so we don't leak.
742 // The `finally` block below will clean up.
743 return this.expandData(keyData.contents.data, keyData.contents.len);
745 } catch (e) {
746 this.log("deriveKeyFromPassphrase: failed: " + e);
747 throw e;
748 } finally {
749 if (algid && !algid.isNull())
750 this.nss.SECOID_DestroyAlgorithmID(algid, true);
751 if (slot && !slot.isNull())
752 this.nss.PK11_FreeSlot(slot);
753 if (symKey && !symKey.isNull())
754 this.nss.PK11_FreeSymKey(symKey);
756 this.freeSECItem(passItem);
757 this.freeSECItem(saltItem);
758 }
759 },
760 };