1'use strict'; 2 3const common = require('../common'); 4const fixtures = require('../common/fixtures'); 5 6if (!common.hasCrypto) 7 common.skip('missing crypto'); 8 9const assert = require('assert'); 10const crypto = require('crypto'); 11const { subtle } = crypto.webcrypto; 12 13const curves = ['P-256', 'P-384', 'P-521']; 14 15const keyData = { 16 'P-521': { 17 jwsAlg: 'ES512', 18 spki: Buffer.from( 19 '30819b301006072a8648ce3d020106052b8104002303818600040156f479f8df' + 20 '1e20a7ffc04ce420c3e154ae251996bee42f034b84d41b743f34e45f311b813a' + 21 '9cdec8cda59bbbbd31d460b3292521e7c1b722e5667c03db2fae753f01501736' + 22 'cfe247394320d8e4afc2fd39b5a9331061b81e2241282b9e17891822b5b79e05' + 23 '2f4597b59643fd39379c51bd5125c4f48bc3f025ce3cd36953286ccb38fb', 24 'hex'), 25 pkcs8: Buffer.from( 26 '3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020' + 27 '101044200f408758368ba930f30f76ae054fe5cd2ce7fda2c9f76a6d436cf75' + 28 'd66c440bfe6331c7c172a12478193c8251487bc91263fa50217f85ff636f59c' + 29 'd546e3ab483b4a1818903818600040156f479f8df1e20a7ffc04ce420c3e154' + 30 'ae251996bee42f034b84d41b743f34e45f311b813a9cdec8cda59bbbbd31d46' + 31 '0b3292521e7c1b722e5667c03db2fae753f01501736cfe247394320d8e4afc2' + 32 'fd39b5a9331061b81e2241282b9e17891822b5b79e052f4597b59643fd39379' + 33 'c51bd5125c4f48bc3f025ce3cd36953286ccb38fb', 'hex'), 34 jwk: { 35 kty: 'EC', 36 crv: 'P-521', 37 x: 'AVb0efjfHiCn_8BM5CDD4VSuJRmWvuQvA0uE1Bt0PzTkXzEbgTqc3sjN' + 38 'pZu7vTHUYLMpJSHnwbci5WZ8A9svrnU_', 39 y: 'AVAXNs_iRzlDINjkr8L9ObWpMxBhuB4iQSgrnheJGCK1t54FL0W' + 40 'XtZZD_Tk3nFG9USXE9IvD8CXOPNNpUyhsyzj7', 41 d: 'APQIdYNoupMPMPdq4FT-XNLOf9osn3am1DbPddZsRAv-YzHHw' + 42 'XKhJHgZPIJRSHvJEmP6UCF_hf9jb1nNVG46tIO0' 43 } 44 }, 45 'P-384': { 46 jwsAlg: 'ES384', 47 spki: Buffer.from( 48 '3076301006072a8648ce3d020106052b8104002203620004219c14d66617b36e' + 49 'c6d8856b385b73a74d344fd8ae75ef046435dda54e3b44bd5fbdebd1d08dd69e' + 50 '2d7dc1dc218cb435bd28138cc778337a842f6bd61b240e74249f24667c2a5810' + 51 'a76bfc28e0335f88a6501dec01976da85afb00869cb6ace8', 'hex'), 52 pkcs8: Buffer.from( 53 '3081b6020100301006072a8648ce3d020106052b8104002204819e30819b0201' + 54 '0104304537b5990784d3c2d22e96a8f92fa1aa492ee873e576a41582e144183c' + 55 '9888d10e6b9eb4ced4b2cc4012e4ac5ea84073a16403620004219c14d66617b3' + 56 '6ec6d8856b385b73a74d344fd8ae75ef046435dda54e3b44bd5fbdebd1d08dd6' + 57 '9e2d7dc1dc218cb435bd28138cc778337a842f6bd61b240e74249f24667c2a58' + 58 '10a76bfc28e0335f88a6501dec01976da85afb00869cb6ace8', 'hex'), 59 jwk: { 60 kty: 'EC', 61 crv: 'P-384', 62 x: 'IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1', 63 y: 'vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo', 64 d: 'RTe1mQeE08LSLpao-S-hqkku6HPldqQVguFEGDyYiNEOa560ztSyzEAS5KxeqEBz' 65 } 66 }, 67 'P-256': { 68 jwsAlg: 'ES256', 69 spki: Buffer.from( 70 '3059301306072a8648ce3d020106082a8648ce3d03010703420004d6e8328a95' + 71 'fe29afcdc30977b9251efbb219022807f6b14bb34695b6b4bdb93ee6684548a4' + 72 'ad13c49d00433c45315e8274f3540f58f5d79ef7a1b184f4c21d17', 'hex'), 73 pkcs8: Buffer.from( 74 '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02' + 75 '010104202bc2eda265e46866efa8f8f99da993175b6c85c246e15dceaed7e307' + 76 '0f13fbf8a14403420004d6e8328a95fe29afcdc30977b9251efbb219022807f6' + 77 'b14bb34695b6b4bdb93ee6684548a4ad13c49d00433c45315e8274f3540f58f5' + 78 'd79ef7a1b184f4c21d17', 'hex'), 79 jwk: { 80 kty: 'EC', 81 crv: 'P-256', 82 x: '1ugyipX-Ka_Nwwl3uSUe-7IZAigH9rFLs0aVtrS9uT4', 83 y: '5mhFSKStE8SdAEM8RTFegnTzVA9Y9dee96GxhPTCHRc', 84 d: 'K8LtomXkaGbvqPj5namTF1tshcJG4V3OrtfjBw8T-_g' 85 } 86 }, 87}; 88 89const testVectors = [ 90 { 91 name: 'ECDSA', 92 privateUsages: ['sign'], 93 publicUsages: ['verify'] 94 }, 95 { 96 name: 'ECDH', 97 privateUsages: ['deriveKey', 'deriveBits'], 98 publicUsages: [] 99 }, 100]; 101 102async function testImportSpki({ name, publicUsages }, namedCurve, extractable) { 103 const key = await subtle.importKey( 104 'spki', 105 keyData[namedCurve].spki, 106 { name, namedCurve }, 107 extractable, 108 publicUsages); 109 assert.strictEqual(key.type, 'public'); 110 assert.strictEqual(key.extractable, extractable); 111 assert.deepStrictEqual(key.usages, publicUsages); 112 assert.deepStrictEqual(key.algorithm.name, name); 113 assert.deepStrictEqual(key.algorithm.namedCurve, namedCurve); 114 115 if (extractable) { 116 // Test the roundtrip 117 const spki = await subtle.exportKey('spki', key); 118 assert.strictEqual( 119 Buffer.from(spki).toString('hex'), 120 keyData[namedCurve].spki.toString('hex')); 121 } else { 122 await assert.rejects( 123 subtle.exportKey('spki', key), { 124 message: /key is not extractable/ 125 }); 126 } 127 128 // Bad usage 129 await assert.rejects( 130 subtle.importKey( 131 'spki', 132 keyData[namedCurve].spki, 133 { name, namedCurve }, 134 extractable, 135 ['wrapKey']), 136 { message: /Unsupported key usage/ }); 137} 138 139async function testImportPkcs8( 140 { name, privateUsages }, 141 namedCurve, 142 extractable) { 143 const key = await subtle.importKey( 144 'pkcs8', 145 keyData[namedCurve].pkcs8, 146 { name, namedCurve }, 147 extractable, 148 privateUsages); 149 assert.strictEqual(key.type, 'private'); 150 assert.strictEqual(key.extractable, extractable); 151 assert.deepStrictEqual(key.usages, privateUsages); 152 assert.deepStrictEqual(key.algorithm.name, name); 153 assert.deepStrictEqual(key.algorithm.namedCurve, namedCurve); 154 155 if (extractable) { 156 // Test the roundtrip 157 const pkcs8 = await subtle.exportKey('pkcs8', key); 158 assert.strictEqual( 159 Buffer.from(pkcs8).toString('hex'), 160 keyData[namedCurve].pkcs8.toString('hex')); 161 } else { 162 await assert.rejects( 163 subtle.exportKey('pkcs8', key), { 164 message: /key is not extractable/ 165 }); 166 } 167 168 await assert.rejects( 169 subtle.importKey( 170 'pkcs8', 171 keyData[namedCurve].pkcs8, 172 { name, namedCurve }, 173 extractable, 174 [/* empty usages */]), 175 { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); 176} 177 178async function testImportJwk( 179 { name, publicUsages, privateUsages }, 180 namedCurve, 181 extractable) { 182 183 const jwk = keyData[namedCurve].jwk; 184 185 const [ 186 publicKey, 187 privateKey, 188 ] = await Promise.all([ 189 subtle.importKey( 190 'jwk', 191 { 192 kty: jwk.kty, 193 crv: jwk.crv, 194 x: jwk.x, 195 y: jwk.y, 196 }, 197 { name, namedCurve }, 198 extractable, publicUsages), 199 subtle.importKey( 200 'jwk', 201 jwk, 202 { name, namedCurve }, 203 extractable, 204 privateUsages), 205 subtle.importKey( 206 'jwk', 207 { 208 alg: name === 'ECDSA' ? keyData[namedCurve].jwsAlg : 'ECDH-ES', 209 kty: jwk.kty, 210 crv: jwk.crv, 211 x: jwk.x, 212 y: jwk.y, 213 }, 214 { name, namedCurve }, 215 extractable, publicUsages), 216 subtle.importKey( 217 'jwk', 218 { 219 ...jwk, 220 alg: name === 'ECDSA' ? keyData[namedCurve].jwsAlg : 'ECDH-ES', 221 }, 222 { name, namedCurve }, 223 extractable, 224 privateUsages), 225 ]); 226 227 assert.strictEqual(publicKey.type, 'public'); 228 assert.strictEqual(privateKey.type, 'private'); 229 assert.strictEqual(publicKey.extractable, extractable); 230 assert.strictEqual(privateKey.extractable, extractable); 231 assert.deepStrictEqual(publicKey.usages, publicUsages); 232 assert.deepStrictEqual(privateKey.usages, privateUsages); 233 assert.strictEqual(publicKey.algorithm.name, name); 234 assert.strictEqual(privateKey.algorithm.name, name); 235 assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve); 236 assert.strictEqual(privateKey.algorithm.namedCurve, namedCurve); 237 238 if (extractable) { 239 // Test the round trip 240 const [ 241 pubJwk, 242 pvtJwk, 243 ] = await Promise.all([ 244 subtle.exportKey('jwk', publicKey), 245 subtle.exportKey('jwk', privateKey), 246 ]); 247 248 assert.deepStrictEqual(pubJwk.key_ops, publicUsages); 249 assert.strictEqual(pubJwk.ext, true); 250 assert.strictEqual(pubJwk.kty, 'EC'); 251 assert.strictEqual(pubJwk.x, jwk.x); 252 assert.strictEqual(pubJwk.y, jwk.y); 253 assert.strictEqual(pubJwk.crv, jwk.crv); 254 255 assert.deepStrictEqual(pvtJwk.key_ops, privateUsages); 256 assert.strictEqual(pvtJwk.ext, true); 257 assert.strictEqual(pvtJwk.kty, 'EC'); 258 assert.strictEqual(pvtJwk.x, jwk.x); 259 assert.strictEqual(pvtJwk.y, jwk.y); 260 assert.strictEqual(pvtJwk.crv, jwk.crv); 261 assert.strictEqual(pvtJwk.d, jwk.d); 262 } else { 263 await assert.rejects( 264 subtle.exportKey('jwk', publicKey), { 265 message: /key is not extractable/ 266 }); 267 await assert.rejects( 268 subtle.exportKey('jwk', privateKey), { 269 message: /key is not extractable/ 270 }); 271 } 272 273 { 274 const invalidUse = name === 'ECDH' ? 'sig' : 'enc'; 275 await assert.rejects( 276 subtle.importKey( 277 'jwk', 278 { ...jwk, use: invalidUse }, 279 { name, namedCurve }, 280 extractable, 281 privateUsages), 282 { message: 'Invalid JWK "use" Parameter' }); 283 } 284 285 if (name === 'ECDSA') { 286 await assert.rejects( 287 subtle.importKey( 288 'jwk', 289 { kty: jwk.kty, x: jwk.x, y: jwk.y, crv: jwk.crv, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' }, 290 { name, namedCurve }, 291 extractable, 292 publicUsages), 293 { message: 'JWK "alg" does not match the requested algorithm' }); 294 295 await assert.rejects( 296 subtle.importKey( 297 'jwk', 298 { ...jwk, alg: jwk.crv === 'P-256' ? 'ES384' : 'ES256' }, 299 { name, namedCurve }, 300 extractable, 301 privateUsages), 302 { message: 'JWK "alg" does not match the requested algorithm' }); 303 } 304 305 for (const crv of [undefined, namedCurve === 'P-256' ? 'P-384' : 'P-256']) { 306 await assert.rejects( 307 subtle.importKey( 308 'jwk', 309 { kty: jwk.kty, x: jwk.x, y: jwk.y, crv }, 310 { name, namedCurve }, 311 extractable, 312 publicUsages), 313 { message: 'JWK "crv" does not match the requested algorithm' }); 314 315 await assert.rejects( 316 subtle.importKey( 317 'jwk', 318 { ...jwk, crv }, 319 { name, namedCurve }, 320 extractable, 321 privateUsages), 322 { message: 'JWK "crv" does not match the requested algorithm' }); 323 } 324 325 await assert.rejects( 326 subtle.importKey( 327 'jwk', 328 { ...jwk }, 329 { name, namedCurve }, 330 extractable, 331 [/* empty usages */]), 332 { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); 333} 334 335async function testImportRaw({ name, publicUsages }, namedCurve) { 336 const jwk = keyData[namedCurve].jwk; 337 338 const [publicKey] = await Promise.all([ 339 subtle.importKey( 340 'raw', 341 Buffer.concat([ 342 Buffer.alloc(1, 0x04), 343 Buffer.from(jwk.x, 'base64url'), 344 Buffer.from(jwk.y, 'base64url'), 345 ]), 346 { name, namedCurve }, 347 true, publicUsages), 348 subtle.importKey( 349 'raw', 350 Buffer.concat([ 351 Buffer.alloc(1, 0x03), 352 Buffer.from(jwk.x, 'base64url'), 353 ]), 354 { name, namedCurve }, 355 true, publicUsages), 356 ]); 357 358 assert.strictEqual(publicKey.type, 'public'); 359 assert.deepStrictEqual(publicKey.usages, publicUsages); 360 assert.strictEqual(publicKey.algorithm.name, name); 361 assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve); 362} 363 364(async function() { 365 const tests = []; 366 testVectors.forEach((vector) => { 367 curves.forEach((namedCurve) => { 368 [true, false].forEach((extractable) => { 369 tests.push(testImportSpki(vector, namedCurve, extractable)); 370 tests.push(testImportPkcs8(vector, namedCurve, extractable)); 371 tests.push(testImportJwk(vector, namedCurve, extractable)); 372 }); 373 tests.push(testImportRaw(vector, namedCurve)); 374 }); 375 }); 376 377 await Promise.all(tests); 378})().then(common.mustCall()); 379 380 381// https://github.com/nodejs/node/issues/45859 382(async function() { 383 const compressed = Buffer.from([48, 57, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 34, 0, 2, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209]); // eslint-disable-line max-len 384 const uncompressed = Buffer.from([48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, 131, 97, 232]); // eslint-disable-line max-len 385 for (const name of ['ECDH', 'ECDSA']) { 386 const options = { name, namedCurve: 'P-256' }; 387 const key = await subtle.importKey('spki', compressed, options, true, []); 388 const spki = await subtle.exportKey('spki', key); 389 assert.deepStrictEqual(uncompressed, Buffer.from(spki)); 390 } 391})().then(common.mustCall()); 392 393{ 394 const rsaPublic = crypto.createPublicKey( 395 fixtures.readKey('rsa_public_2048.pem')); 396 const rsaPrivate = crypto.createPrivateKey( 397 fixtures.readKey('rsa_private_2048.pem')); 398 399 for (const [name, publicUsages, privateUsages] of [ 400 ['ECDSA', ['verify'], ['sign']], 401 ['ECDH', [], ['deriveBits', 'deriveBits']], 402 ]) { 403 assert.rejects( 404 subtle.importKey( 405 'spki', 406 rsaPublic.export({ format: 'der', type: 'spki' }), 407 { name, hash: 'SHA-256', namedCurve: 'P-256' }, 408 true, publicUsages), { message: /Invalid key type/ }, 409 ).then(common.mustCall()); 410 assert.rejects( 411 subtle.importKey( 412 'pkcs8', 413 rsaPrivate.export({ format: 'der', type: 'pkcs8' }), 414 { name, hash: 'SHA-256', namedCurve: 'P-256' }, 415 true, privateUsages), { message: /Invalid key type/ }, 416 ).then(common.mustCall()); 417 } 418} 419 420// Bad private keys 421{ 422 for (const { namedCurve, key: pkcs8 } of [ 423 // The private key is exactly equal to the order, and the public key is 424 // private key * order. 425 { 426 namedCurve: 'P-256', 427 key: Buffer.from( 428 '3066020100301306072a8648ce3d020106082a8648ce3d030107044c304a0201' + 429 '010420ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc' + 430 '632551a12303210000ffffff00000000ffffffffffffffffbce6faada7179e84' + 431 'f3b9cac2fc632551', 'hex'), 432 }, 433 // The private key is exactly equal to the order, and the public key is 434 // omitted. 435 { 436 namedCurve: 'P-256', 437 key: Buffer.from( 438 '3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201' + 439 '010420ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc' + 440 '632551', 'hex'), 441 }, 442 // The private key is exactly equal to the order + 11, and the public key is 443 // private key * order. 444 { 445 namedCurve: 'P-521', 446 key: Buffer.from( 447 '3081ee020100301006072a8648ce3d020106052b810400230481d63081d30201' + 448 '01044201ffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + 449 'fffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb7' + 450 '1e91386414a181890381860004008a75841259fdedff546f1a39573b4315cfed' + 451 '5dc7ed7c17849543ef2c54f2991652f3dbc5332663da1bd19b1aebe319108501' + 452 '5c024fa4c9a902ecc0e02dda0cdb9a0096fb303fcbba2129849d0ca877054fb2' + 453 '293add566210bd0493ed2e95d4e0b9b82b1bc8a90e8b42a4ab3892331914a953' + 454 '36dcac80e3f4819b5d58874f92ce48c808', 'hex'), 455 }, 456 // The private key is exactly equal to the order + 11, and the public key is 457 // omitted. 458 { 459 namedCurve: 'P-521', 460 key: Buffer.from( 461 '3060020100301006072a8648ce3d020106052b81040023044930470201010442' + 462 '01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + 463 'fffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138' + 464 '6414', 'hex'), 465 }, 466 ]) { 467 for (const [name, privateUsages] of [ 468 ['ECDSA', ['sign']], 469 ['ECDH', ['deriveBits', 'deriveBits']], 470 ]) { 471 assert.rejects( 472 subtle.importKey( 473 'pkcs8', 474 pkcs8, 475 { name, hash: 'SHA-256', namedCurve }, 476 true, privateUsages), { name: 'DataError', message: /Invalid keyData/ }, 477 ).then(common.mustCall()); 478 } 479 } 480} 481