|
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/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ["WeaveCrypto"]; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cr = Components.results; |
|
10 |
|
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"); |
|
14 |
|
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; |
|
23 |
|
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; |
|
28 |
|
29 this.WeaveCrypto = function WeaveCrypto() { |
|
30 this.init(); |
|
31 } |
|
32 |
|
33 WeaveCrypto.prototype = { |
|
34 prefBranch : null, |
|
35 debug : true, // services.sync.log.cryptoDebug |
|
36 nss : null, |
|
37 nss_t : null, |
|
38 |
|
39 observer : { |
|
40 _self : null, |
|
41 |
|
42 QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, |
|
43 Ci.nsISupportsWeakReference]), |
|
44 |
|
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 }, |
|
53 |
|
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 } |
|
65 |
|
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 }, |
|
76 |
|
77 // Avoid allocating new temporary ints on every run of _commonCrypt. |
|
78 _commonCryptSignedOutputSize: null, |
|
79 _commonCryptSignedOutputSizeAddr: null, |
|
80 _commonCryptUnsignedOutputSize: null, |
|
81 _commonCryptUnsignedOutputSizeAddr: null, |
|
82 |
|
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 }, |
|
91 |
|
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! |
|
108 |
|
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 }, |
|
115 |
|
116 log : function (message) { |
|
117 if (!this.debug) |
|
118 return; |
|
119 dump("WeaveCrypto: " + message + "\n"); |
|
120 Services.console.logStringMessage("WeaveCrypto: " + message); |
|
121 }, |
|
122 |
|
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); |
|
129 |
|
130 // Open the NSS library. |
|
131 let path = ctypes.libraryName("nss3"); |
|
132 |
|
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 } |
|
146 |
|
147 this.log("Initializing NSS types and function declarations..."); |
|
148 |
|
149 this.nss = {}; |
|
150 this.nss_t = {}; |
|
151 |
|
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 }]); |
|
204 |
|
205 |
|
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; |
|
212 |
|
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; |
|
217 |
|
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; |
|
222 |
|
223 |
|
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 }, |
|
357 |
|
358 |
|
359 _sharedInputBuffer: null, |
|
360 _sharedInputBufferInts: null, |
|
361 _sharedInputBufferSize: 0, |
|
362 _sharedOutputBuffer: null, |
|
363 _sharedOutputBufferSize: 0, |
|
364 _randomByteBuffer: null, |
|
365 _randomByteBufferAddr: null, |
|
366 _randomByteBufferSize: 0, |
|
367 |
|
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 }, |
|
377 |
|
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 }, |
|
386 |
|
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 }, |
|
396 |
|
397 initBuffers: function initBuffers(initialSize) { |
|
398 this._getInputBuffer(initialSize); |
|
399 this._getOutputBuffer(initialSize); |
|
400 |
|
401 this._getRandomByteBuffer(this.ivLength); |
|
402 }, |
|
403 |
|
404 encrypt : function(clearTextUCS2, symmetricKey, iv) { |
|
405 this.log("encrypt() called"); |
|
406 |
|
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; |
|
411 |
|
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); |
|
417 |
|
418 outputBuffer = this._commonCrypt(inputBuffer, inputBufferSize, |
|
419 outputBuffer, outputBufferSize, |
|
420 symmetricKey, iv, this.nss.CKA_ENCRYPT); |
|
421 |
|
422 return this.encodeBase64(outputBuffer.address(), outputBuffer.length); |
|
423 }, |
|
424 |
|
425 |
|
426 decrypt : function(cipherText, symmetricKey, iv) { |
|
427 this.log("decrypt() called"); |
|
428 |
|
429 let inputUCS2 = ""; |
|
430 if (cipherText.length) |
|
431 inputUCS2 = atob(cipherText); |
|
432 |
|
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); |
|
441 |
|
442 let outputBuffer = this._commonCrypt(input, len, |
|
443 this._getOutputBuffer(len), len, |
|
444 symmetricKey, iv, this.nss.CKA_DECRYPT); |
|
445 |
|
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 }, |
|
451 |
|
452 _commonCrypt : function (input, inputLength, output, outputLength, symmetricKey, iv, operation) { |
|
453 this.log("_commonCrypt() called"); |
|
454 iv = atob(iv); |
|
455 |
|
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); |
|
462 |
|
463 // We use a single IV SECItem for the sake of efficiency. Fill it here. |
|
464 this.byteCompressInts(iv, this._ivSECItemContents, iv.length); |
|
465 |
|
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); |
|
471 |
|
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); |
|
476 |
|
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); |
|
480 |
|
481 let actualOutputSize = this._commonCryptSignedOutputSize.value; |
|
482 let finalOutput = output.addressOfElement(actualOutputSize); |
|
483 maxOutputSize -= actualOutputSize; |
|
484 |
|
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); |
|
490 |
|
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); |
|
502 |
|
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 }, |
|
507 |
|
508 |
|
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); |
|
516 |
|
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); |
|
520 |
|
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); |
|
525 |
|
526 keydata = this.nss.PK11_GetKeyData(randKey); |
|
527 if (keydata.isNull()) |
|
528 throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE); |
|
529 |
|
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 }, |
|
541 |
|
542 generateRandomIV : function() this.generateRandomBytes(this.ivLength), |
|
543 |
|
544 generateRandomBytes : function(byteCount) { |
|
545 this.log("generateRandomBytes() called"); |
|
546 |
|
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); |
|
551 |
|
552 return this.encodeBase64(this._randomByteBufferAddr, byteCount); |
|
553 }, |
|
554 |
|
555 // |
|
556 // PK11SymKey memoization. |
|
557 // |
|
558 |
|
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; |
|
566 |
|
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 } |
|
579 |
|
580 if (encodedKeyString in memo) |
|
581 return memo[encodedKeyString]; |
|
582 |
|
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); |
|
590 |
|
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); |
|
597 |
|
598 return memo[encodedKeyString] = symKey; |
|
599 } finally { |
|
600 if (slot && !slot.isNull()) |
|
601 this.nss.PK11_FreeSlot(slot); |
|
602 this.freeSECItem(keyItem); |
|
603 } |
|
604 }, |
|
605 |
|
606 |
|
607 // |
|
608 // Utility functions |
|
609 // |
|
610 |
|
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 }, |
|
623 |
|
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 }, |
|
634 |
|
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 }, |
|
644 |
|
645 encodeBase64 : function (data, len) { |
|
646 return btoa(this.expandData(data, len)); |
|
647 }, |
|
648 |
|
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); |
|
656 |
|
657 let len = input.length; |
|
658 let item = this.nss.SECITEM_AllocItem(null, null, len); |
|
659 if (item.isNull()) |
|
660 throw "SECITEM_AllocItem failed."; |
|
661 |
|
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 }, |
|
668 |
|
669 freeSECItem : function(zap) { |
|
670 if (zap && !zap.isNull()) |
|
671 this.nss.SECITEM_ZfreeItem(zap, true); |
|
672 }, |
|
673 |
|
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, |
|
679 |
|
680 initIVSECItem: function initIVSECItem() { |
|
681 if (this._ivSECItem) { |
|
682 this._ivSECItemContents = null; |
|
683 this.freeSECItem(this._ivSECItem); |
|
684 } |
|
685 |
|
686 let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize); |
|
687 if (item.isNull()) |
|
688 throw "SECITEM_AllocItem failed."; |
|
689 |
|
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 }, |
|
697 |
|
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); |
|
705 |
|
706 let pbeAlg = ALGORITHM; |
|
707 let cipherAlg = ALGORITHM; // Ignored by callee when pbeAlg != a pkcs5 mech. |
|
708 |
|
709 // Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported. |
|
710 let prfAlg = this.nss.SEC_OID_HMAC_SHA1; |
|
711 |
|
712 let keyLength = keyLength || 0; // 0 = Callee will pick. |
|
713 let iterations = KEY_DERIVATION_ITERATIONS; |
|
714 |
|
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); |
|
722 |
|
723 slot = this.nss.PK11_GetInternalSlot(); |
|
724 if (slot.isNull()) |
|
725 throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE); |
|
726 |
|
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); |
|
730 |
|
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 } |
|
735 |
|
736 keyData = this.nss.PK11_GetKeyData(symKey); |
|
737 |
|
738 if (keyData.isNull()) |
|
739 throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE); |
|
740 |
|
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); |
|
744 |
|
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); |
|
755 |
|
756 this.freeSECItem(passItem); |
|
757 this.freeSECItem(saltItem); |
|
758 } |
|
759 }, |
|
760 }; |