Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(172)

Side by Side Diff: third_party/WebKit/LayoutTests/external/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.js

Issue 2838603002: Added [SecureContext] to the subtle attribute (Closed)
Patch Set: Added https version of idlharness test Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Tests for wrapKey and unwrapKey round tripping
2
3 function run_test() {
4 var subtle = self.crypto.subtle;
5
6 var wrappers = []; // Things we wrap (and upwrap) keys with
7 var keys = []; // Things to wrap and unwrap
8 var ecdhPeerKey; // ECDH peer public key needed for non-extractable ECDH key comparison
9
10 // Generate all the keys needed, then iterate over all combinations
11 // to test wrapping and unwrapping.
12 Promise.all([generateWrappingKeys(), generateKeysToWrap(), generateEcdhPeerK ey()])
13 .then(function(results) {
14 var promises = [];
15 wrappers.forEach(function(wrapper) {
16 keys.forEach(function(key) {
17 promises.push(testWrapping(wrapper, key));
18 })
19 });
20 return Promise.all(promises);
21 }, function(err) {
22 promise_test(function(test) {
23 assert_unreached("A key failed to generate: " + err.name + ": " + er r.message)
24 }, "Could not run all tests")
25 })
26 .then(function() {
27 done();
28 }, function(err) {
29 promise_test(function(test) {
30 assert_unreached("A test failed to run: " + err.name + ": " + err.me ssage)
31 }, "Could not run all tests")
32 });
33
34
35 function generateWrappingKeys() {
36 // There are five algorithms that can be used for wrapKey/unwrapKey.
37 // Generate one key with typical parameters for each kind.
38 //
39 // Note: we don't need cryptographically strong parameters for things
40 // like IV - just any legal value will do.
41 var parameters = [
42 {
43 name: "RSA-OAEP",
44 generateParameters: {name: "RSA-OAEP", modulusLength: 4096, publ icExponent: new Uint8Array([1,0,1]), hash: "SHA-256"},
45 wrapParameters: {name: "RSA-OAEP", label: new Uint8Array(8)}
46 },
47 {
48 name: "AES-CTR",
49 generateParameters: {name: "AES-CTR", length: 128},
50 wrapParameters: {name: "AES-CTR", counter: new Uint8Array(16), l ength: 64}
51 },
52 {
53 name: "AES-CBC",
54 generateParameters: {name: "AES-CBC", length: 128},
55 wrapParameters: {name: "AES-CBC", iv: new Uint8Array(16)}
56 },
57 {
58 name: "AES-GCM",
59 generateParameters: {name: "AES-GCM", length: 128},
60 wrapParameters: {name: "AES-GCM", iv: new Uint8Array(16), additi onalData: new Uint8Array(16), tagLength: 64}
61 },
62 {
63 name: "AES-KW",
64 generateParameters: {name: "AES-KW", length: 128},
65 wrapParameters: {name: "AES-KW"}
66 }
67 ];
68
69 return Promise.all(parameters.map(function(params) {
70 return subtle.generateKey(params.generateParameters, true, ["wrapKey ", "unwrapKey"])
71 .then(function(key) {
72 var wrapper;
73 if (params.name === "RSA-OAEP") { // we have a key pair, not jus t a key
74 wrapper = {wrappingKey: key.publicKey, unwrappingKey: key.pr ivateKey, parameters: params};
75 } else {
76 wrapper = {wrappingKey: key, unwrappingKey: key, parameters: params};
77 }
78 wrappers.push(wrapper);
79 return true;
80 })
81 }));
82 }
83
84
85 function generateKeysToWrap() {
86 var parameters = [
87 {algorithm: {name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicE xponent: new Uint8Array([1,0,1]), hash: "SHA-256"}, privateUsages: ["sign"], pub licUsages: ["verify"]},
88 {algorithm: {name: "RSA-PSS", modulusLength: 1024, publicExponent: n ew Uint8Array([1,0,1]), hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
89 {algorithm: {name: "RSA-OAEP", modulusLength: 1024, publicExponent: new Uint8Array([1,0,1]), hash: "SHA-256"}, privateUsages: ["decrypt"], publicUsa ges: ["encrypt"]},
90 {algorithm: {name: "ECDSA", namedCurve: "P-256"}, privateUsages: ["s ign"], publicUsages: ["verify"]},
91 {algorithm: {name: "ECDH", namedCurve: "P-256"}, privateUsages: ["de riveBits"], publicUsages: []},
92 {algorithm: {name: "AES-CTR", length: 128}, usages: ["encrypt", "dec rypt"]},
93 {algorithm: {name: "AES-CBC", length: 128}, usages: ["encrypt", "dec rypt"]},
94 {algorithm: {name: "AES-GCM", length: 128}, usages: ["encrypt", "dec rypt"]},
95 {algorithm: {name: "AES-KW", length: 128}, usages: ["wrapKey", "unwr apKey"]},
96 {algorithm: {name: "HMAC", length: 128, hash: "SHA-256"}, usages: [" sign", "verify"]}
97 ];
98
99 return Promise.all(parameters.map(function(params) {
100 var usages;
101 if ("usages" in params) {
102 usages = params.usages;
103 } else {
104 usages = params.publicUsages.concat(params.privateUsages);
105 }
106
107 return subtle.generateKey(params.algorithm, true, usages)
108 .then(function(result) {
109 if (result.constructor === CryptoKey) {
110 keys.push({name: params.algorithm.name, algorithm: params.al gorithm, usages: params.usages, key: result});
111 } else {
112 keys.push({name: params.algorithm.name + " public key", algo rithm: params.algorithm, usages: params.publicUsages, key: result.publicKey});
113 keys.push({name: params.algorithm.name + " private key", alg orithm: params.algorithm, usages: params.privateUsages, key: result.privateKey}) ;
114 }
115 return true;
116 });
117 }));
118 }
119
120 function generateEcdhPeerKey() {
121 return subtle.generateKey({name: "ECDH", namedCurve: "P-256"},true,["der iveBits"])
122 .then(function(result){
123 ecdhPeerKey = result.publicKey;
124 });
125 }
126
127 // Can we successfully "round-trip" (wrap, then unwrap, a key)?
128 function testWrapping(wrapper, toWrap) {
129 var formats;
130
131 if (toWrap.name.includes("private")) {
132 formats = ["pkcs8", "jwk"];
133 } else if (toWrap.name.includes("public")) {
134 formats = ["spki", "jwk"]
135 } else {
136 formats = ["raw", "jwk"]
137 }
138
139 return Promise.all(formats.map(function(fmt) {
140 var originalExport;
141 return subtle.exportKey(fmt, toWrap.key).then(function(exportedKey) {
142 originalExport = exportedKey;
143 if (wrappingIsPossible(originalExport, wrapper.parameters.name)) {
144 promise_test(function(test) {
145 return subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingK ey, wrapper.parameters.wrapParameters)
146 .then(function(wrappedResult) {
147 return subtle.unwrapKey(fmt, wrappedResult, wrapper. unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap .usages);
148 }).then(function(unwrappedResult) {
149 assert_true(unwrappedResult.extractable, "Unwrapped result is extractable");
150 return subtle.exportKey(fmt, unwrappedResult)
151 }).then(function(roundTripExport) {
152 assert_true(equalExport(originalExport, roundTripExp ort), "Post-wrap export matches original export");
153 }, function(err) {
154 assert_unreached("Round trip for extractable key thr ew an error - " + err.name + ': "' + err.message + '"');
155 });
156 }, "Can wrap and unwrap " + toWrap.name + " keys using " + f mt + " and " + wrapper.parameters.name);
157
158 if (canCompareNonExtractableKeys(toWrap.key)) {
159 promise_test(function(test){
160 return subtle.wrapKey(fmt, toWrap.key, wrapper.wrapp ingKey, wrapper.parameters.wrapParameters)
161 .then(function(wrappedResult) {
162 return subtle.unwrapKey(fmt, wrappedResult, wrap per.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, t oWrap.usages);
163 }).then(function(unwrappedResult){
164 assert_false(unwrappedResult.extractable, "Unwra pped result is non-extractable");
165 return equalKeys(toWrap.key, unwrappedResult);
166 }).then(function(result){
167 assert_true(result, "Unwrapped key matches origi nal");
168 }).catch(function(err){
169 assert_unreached("Round trip for key unwrapped n on-extractable threw an error - " + err.name + ': "' + err.message + '"');
170 });
171 }, "Can wrap and unwrap " + toWrap.name + " keys as non- extractable using " + fmt + " and " + wrapper.parameters.name);
172
173 if (fmt === "jwk") {
174 promise_test(function(test){
175 var wrappedKey;
176 return wrapAsNonExtractableJwk(toWrap.key,wrappe r).then(function(wrappedResult){
177 wrappedKey = wrappedResult;
178 return subtle.unwrapKey("jwk", wrappedKey, w rapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false , toWrap.usages);
179 }).then(function(unwrappedResult){
180 assert_false(unwrappedResult.extractable, "U nwrapped key is non-extractable");
181 return equalKeys(toWrap.key,unwrappedResult) ;
182 }).then(function(result){
183 assert_true(result, "Unwrapped key matches o riginal");
184 }).catch(function(err){
185 assert_unreached("Round trip for non-extract able key threw an error - " + err.name + ': "' + err.message + '"');
186 }).then(function(){
187 return subtle.unwrapKey("jwk", wrappedKey, w rapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages);
188 }).then(function(unwrappedResult){
189 assert_unreached("Unwrapping a non-extractab le JWK as extractable should fail");
190 }).catch(function(err){
191 assert_equals(err.name, "DataError", "Unwrap ping a non-extractable JWK as extractable fails with DataError");
192 });
193 }, "Can unwrap " + toWrap.name + " non-extractable k eys using jwk and " + wrapper.parameters.name);
194 }
195 }
196 }
197 });
198 }));
199 }
200
201 // Implement key wrapping by hand to wrap a key as non-extractable JWK
202 function wrapAsNonExtractableJwk(key, wrapper){
203 var wrappingKey = wrapper.wrappingKey,
204 encryptKey;
205
206 return subtle.exportKey("jwk",wrappingKey)
207 .then(function(jwkWrappingKey){
208 // Update the key generation parameters to work as key import parame ters
209 var params = Object.create(wrapper.parameters.generateParameters);
210 if(params.name === "AES-KW") {
211 params.name = "AES-CBC";
212 jwkWrappingKey.alg = "A"+params.length+"CBC";
213 } else if (params.name === "RSA-OAEP") {
214 params.modulusLength = undefined;
215 params.publicExponent = undefined;
216 }
217 jwkWrappingKey.key_ops = ["encrypt"];
218 return subtle.importKey("jwk", jwkWrappingKey, params, true, ["encry pt"]);
219 }).then(function(importedWrappingKey){
220 encryptKey = importedWrappingKey;
221 return subtle.exportKey("jwk",key);
222 }).then(function(exportedKey){
223 exportedKey.ext = false;
224 var jwk = JSON.stringify(exportedKey)
225 if (wrappingKey.algorithm.name === "AES-KW") {
226 return aeskw(encryptKey, str2ab(jwk.slice(0,-1) + " ".repeat(jwk .length%8 ? 8-jwk.length%8 : 0) + "}"));
227 } else {
228 return subtle.encrypt(wrapper.parameters.wrapParameters,encryptK ey,str2ab(jwk));
229 }
230 });
231 }
232
233
234 // RSA-OAEP can only wrap relatively small payloads. AES-KW can only
235 // wrap payloads a multiple of 8 bytes long.
236 //
237 // Note that JWK payloads will be converted to ArrayBuffer for wrapping,
238 // and should automatically be padded if needed for AES-KW.
239 function wrappingIsPossible(exportedKey, algorithmName) {
240 if ("byteLength" in exportedKey && algorithmName === "AES-KW") {
241 return exportedKey.byteLength % 8 === 0;
242 }
243
244 if ("byteLength" in exportedKey && algorithmName === "RSA-OAEP") {
245 // RSA-OAEP can only encrypt payloads with lengths shorter
246 // than modulusLength - 2*hashLength - 1 bytes long. For
247 // a 4096 bit modulus and SHA-256, that comes to
248 // 4096/8 - 2*(256/8) - 1 = 512 - 2*32 - 1 = 447 bytes.
249 return exportedKey.byteLength <= 446;
250 }
251
252 if ("kty" in exportedKey && algorithmName === "RSA-OAEP") {
253 return JSON.stringify(exportedKey).length <= 478;
254 }
255
256 return true;
257 }
258
259
260 // Helper methods follow:
261
262 // Are two exported keys equal
263 function equalExport(originalExport, roundTripExport) {
264 if ("byteLength" in originalExport) {
265 return equalBuffers(originalExport, roundTripExport);
266 } else {
267 return equalJwk(originalExport, roundTripExport);
268 }
269 }
270
271 // Are two array buffers the same?
272 function equalBuffers(a, b) {
273 if (a.byteLength !== b.byteLength) {
274 return false;
275 }
276
277 var aBytes = new Uint8Array(a);
278 var bBytes = new Uint8Array(b);
279
280 for (var i=0; i<a.byteLength; i++) {
281 if (aBytes[i] !== bBytes[i]) {
282 return false;
283 }
284 }
285
286 return true;
287 }
288
289 // Are two Jwk objects "the same"? That is, does the object returned include
290 // matching values for each property that was expected? It's okay if the
291 // returned object has extra methods; they aren't checked.
292 function equalJwk(expected, got) {
293 var fields = Object.keys(expected);
294 var fieldName;
295
296 for(var i=0; i<fields.length; i++) {
297 fieldName = fields[i];
298 if (!(fieldName in got)) {
299 return false;
300 }
301 if (objectToString(expected[fieldName]) !== objectToString(got[field Name])) {
302 return false;
303 }
304 }
305
306 return true;
307 }
308
309 // Character representation of any object we may use as a parameter.
310 function objectToString(obj) {
311 var keyValuePairs = [];
312
313 if (Array.isArray(obj)) {
314 return "[" + obj.map(function(elem){return objectToString(elem);}).j oin(", ") + "]";
315 } else if (typeof obj === "object") {
316 Object.keys(obj).sort().forEach(function(keyName) {
317 keyValuePairs.push(keyName + ": " + objectToString(obj[keyName]) );
318 });
319 return "{" + keyValuePairs.join(", ") + "}";
320 } else if (typeof obj === "undefined") {
321 return "undefined";
322 } else {
323 return obj.toString();
324 }
325
326 var keyValuePairs = [];
327
328 Object.keys(obj).sort().forEach(function(keyName) {
329 var value = obj[keyName];
330 if (typeof value === "object") {
331 value = objectToString(value);
332 } else if (typeof value === "array") {
333 value = "[" + value.map(function(elem){return objectToString(ele m);}).join(", ") + "]";
334 } else {
335 value = value.toString();
336 }
337
338 keyValuePairs.push(keyName + ": " + value);
339 });
340
341 return "{" + keyValuePairs.join(", ") + "}";
342 }
343
344 // Can we compare key values by using them
345 function canCompareNonExtractableKeys(key){
346 if (key.usages.indexOf("decrypt") !== -1) {
347 return true;
348 }
349 if (key.usages.indexOf("sign") !== -1) {
350 return true;
351 }
352 if (key.usages.indexOf("wrapKey") !== -1) {
353 return true;
354 }
355 if (key.usages.indexOf("deriveBits") !== -1) {
356 return true;
357 }
358 return false;
359 }
360
361 // Compare two keys by using them (works for non-extractable keys)
362 function equalKeys(expected, got){
363 if ( expected.algorithm.name !== got.algorithm.name ) {
364 return Promise.resolve(false);
365 }
366
367 var cryptParams, signParams, wrapParams, deriveParams;
368 switch(expected.algorithm.name){
369 case "AES-CTR" :
370 cryptParams = {name: "AES-CTR", counter: new Uint8Array(16), len gth: 64};
371 break;
372 case "AES-CBC" :
373 cryptParams = {name: "AES-CBC", iv: new Uint8Array(16) };
374 break;
375 case "AES-GCM" :
376 cryptParams = {name: "AES-GCM", iv: new Uint8Array(16) };
377 break;
378 case "RSA-OAEP" :
379 cryptParams = {name: "RSA-OAEP", label: new Uint8Array(8) };
380 break;
381 case "RSASSA-PKCS1-v1_5" :
382 signParams = {name: "RSASSA-PKCS1-v1_5"};
383 break;
384 case "RSA-PSS" :
385 signParams = {name: "RSA-PSS", saltLength: 32 };
386 break;
387 case "ECDSA" :
388 signParams = {name: "ECDSA", hash: "SHA-256"};
389 break;
390 case "HMAC" :
391 signParams = {name: "HMAC"};
392 break;
393 case "AES-KW" :
394 wrapParams = {name: "AES-KW"};
395 break;
396 case "ECDH" :
397 deriveParams = {name: "ECDH", public: ecdhPeerKey};
398 break;
399 default:
400 throw new Error("Unsupported algorithm for key comparison");
401 }
402
403 if (cryptParams) {
404 return subtle.exportKey("jwk",expected)
405 .then(function(jwkExpectedKey){
406 if (expected.algorithm.name === "RSA-OAEP") {
407 ["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
408 }
409 jwkExpectedKey.key_ops = ["encrypt"];
410 return subtle.importKey("jwk", jwkExpectedKey, expected.algorith m, true, ["encrypt"]);
411 }).then(function(expectedEncryptKey){
412 return subtle.encrypt(cryptParams, expectedEncryptKey, new Uint8 Array(32));
413 }).then(function(encryptedData){
414 return subtle.decrypt(cryptParams, got, encryptedData);
415 }).then(function(decryptedData){
416 var result = new Uint8Array(decryptedData);
417 return !result.some(x => x);
418 });
419 } else if (signParams) {
420 var verifyKey;
421 return subtle.exportKey("jwk",expected)
422 .then(function(jwkExpectedKey){
423 if (expected.algorithm.name === "RSA-PSS" || expected.algorithm. name === "RSASSA-PKCS1-v1_5") {
424 ["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
425 }
426 if (expected.algorithm.name === "ECDSA") {
427 delete jwkExpectedKey["d"];
428 }
429 jwkExpectedKey.key_ops = ["verify"];
430 return subtle.importKey("jwk", jwkExpectedKey, expected.algorith m, true, ["verify"]);
431 }).then(function(expectedVerifyKey){
432 verifyKey = expectedVerifyKey;
433 return subtle.sign(signParams, got, new Uint8Array(32));
434 }).then(function(signature){
435 return subtle.verify(signParams, verifyKey, signature, new Uint8 Array(32));
436 });
437 } else if (wrapParams) {
438 var aKeyToWrap, wrappedWithExpected;
439 return subtle.importKey("raw", new Uint8Array(16), "AES-CBC", true, ["encrypt"])
440 .then(function(key){
441 aKeyToWrap = key;
442 return subtle.wrapKey("raw", aKeyToWrap, expected, wrapParams);
443 }).then(function(wrapResult){
444 wrappedWithExpected = Array.from((new Uint8Array(wrapResult)).va lues());
445 return subtle.wrapKey("raw", aKeyToWrap, got, wrapParams);
446 }).then(function(wrapResult){
447 var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).val ues());
448 return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i ]);
449 });
450 } else {
451 var expectedDerivedBits;
452 return subtle.deriveBits(deriveParams, expected, 128)
453 .then(function(result){
454 expectedDerivedBits = Array.from((new Uint8Array(result)).values ());
455 return subtle.deriveBits(deriveParams, got, 128);
456 }).then(function(result){
457 var gotDerivedBits = Array.from((new Uint8Array(result)).values( ));
458 return gotDerivedBits.every((x,i) => x === expectedDerivedBits[i ]);
459 });
460 }
461 }
462
463 // Raw AES encryption
464 function aes( k, p ) {
465 return subtle.encrypt({name: "AES-CBC", iv: new Uint8Array(16) }, k, p). then(function(ciphertext){return ciphertext.slice(0,16);});
466 }
467
468 // AES Key Wrap
469 function aeskw(key, data) {
470 if (data.byteLength % 8 !== 0) {
471 throw new Error("AES Key Wrap data must be a multiple of 8 bytes in length");
472 }
473
474 var A = Uint8Array.from([0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0, 0, 0, 0, 0, 0, 0, 0]),
475 Av = new DataView(A.buffer),
476 R = [],
477 n = data.byteLength / 8;
478
479 for(var i = 0; i<data.byteLength; i+=8) {
480 R.push(new Uint8Array(data.slice(i,i+8)));
481 }
482
483 function aeskw_step(j, i, final, B) {
484 A.set(new Uint8Array(B.slice(0,8)));
485 Av.setUint32(4,Av.getUint32(4) ^ (n*j+i+1));
486 R[i] = new Uint8Array(B.slice(8,16));
487 if (final) {
488 R.unshift(A.slice(0,8));
489 var result = new Uint8Array(R.length * 8);
490 R.forEach(function(Ri,i){ result.set(Ri, i*8); });
491 return result;
492 } else {
493 A.set(R[(i+1)%n],8);
494 return aes(key,A);
495 }
496 }
497
498 var p = new Promise(function(resolve){
499 A.set(R[0],8);
500 resolve(aes(key,A));
501 });
502
503 for(var j=0;j<6;++j) {
504 for(var i=0;i<n;++i) {
505 p = p.then(aeskw_step.bind(undefined, j, i,j===5 && i===(n-1)));
506 }
507 }
508
509 return p;
510 }
511
512 function str2ab(str) { return Uint8Array.from( str.split(''), functio n(s){return s.charCodeAt(0)} ); }
513 function ab2str(ab) { return String.fromCharCode.apply(null, new Uin t8Array(ab)); }
514
515
516 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698