| OLD | NEW |
| (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 } | |
| OLD | NEW |