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 keyData = { 14 'Ed25519': { 15 jwsAlg: 'EdDSA', 16 spki: Buffer.from( 17 '302a300506032b6570032100a054b618c12b26c8d43595a5c38dd2b0140b944a' + 18 '151f75003278c2b6c58ec08f', 'hex'), 19 pkcs8: Buffer.from( 20 '302e020100300506032b657004220420d53150bdcd17b4d4b21ae756d4965639' + 21 'd75b28f56ff9111b1f88326913e445bc', 'hex'), 22 jwk: { 23 kty: 'OKP', 24 crv: 'Ed25519', 25 x: 'oFS2GMErJsjUNZWlw43SsBQLlEoVH3UAMnjCtsWOwI8', 26 d: '1TFQvc0XtNSyGudW1JZWOddbKPVv-REbH4gyaRPkRbw' 27 } 28 }, 29 'Ed448': { 30 jwsAlg: 'EdDSA', 31 spki: Buffer.from( 32 '3043300506032b6571033a0008cc38160c85bca5656ac4924af7ea97a9161b20' + 33 '2528273dcb84afd2eeb99ac912a401b34ef15ef4d9486406a6eecc31e5909219' + 34 'bd54866800', 'hex'), 35 pkcs8: Buffer.from( 36 '3047020100300506032b6571043b0439afd05b2fbb153b47c18dfa66baaed0de' + 37 'fb4e88c651487cdee0fafc40fa3d048fe1cd145a44143243c0468166b5bc161a' + 38 '82e3b904f3e2fcaaf9', 'hex'), 39 jwk: { 40 kty: 'OKP', 41 crv: 'Ed448', 42 x: 'CMw4FgyFvKVlasSSSvfql6kWGyAlKCc9y4Sv0u65mskSpAGzTvFe9NlIZAam7' + 43 'swx5ZCSGb1UhmgA', 44 d: 'r9BbL7sVO0fBjfpmuq7Q3vtOiMZRSHze4Pr8QPo9BI_hzRRaRBQyQ8BGgWa1v' + 45 'BYaguO5BPPi_Kr5' 46 } 47 }, 48 'X25519': { 49 jwsAlg: 'ECDH-ES', 50 spki: Buffer.from( 51 '302a300506032b656e032100f38d9f4e621a44e0428176a4c8a534b34f07f8db' + 52 '30152f9ca0167aabf598fe65', 'hex'), 53 pkcs8: Buffer.from( 54 '302e020100300506032b656e04220420a8327850317b4b03a5a8b4e923413b1d' + 55 'a4a642e0d6f7a72cf4d16a549e628a5f', 'hex'), 56 jwk: { 57 kty: 'OKP', 58 crv: 'X25519', 59 x: '842fTmIaROBCgXakyKU0s08H-NswFS-coBZ6q_WY_mU', 60 d: 'qDJ4UDF7SwOlqLTpI0E7HaSmQuDW96cs9NFqVJ5iil8' 61 } 62 }, 63 'X448': { 64 jwsAlg: 'ECDH-ES', 65 spki: Buffer.from( 66 '3042300506032b656f0339001d451c8c0c369a42eadfc2875cd44953caeb46c4' + 67 '66dc86568280bfdbbb01f4709a1b0b1e0dd66cf7b11c84119ddc98890db72891' + 68 '29e30da4', 'hex'), 69 pkcs8: Buffer.from( 70 '3046020100300506032b656f043a0438fc818f6546a81f963c27765dc1c05bfd' + 71 'b169667e5e0cf45318ed1cb93872217ab0d9004e0c7dd0dcb00192f72039cc1a' + 72 '1dff750ec31c8afb', 'hex'), 73 jwk: { 74 kty: 'OKP', 75 crv: 'X448', 76 x: 'HUUcjAw2mkLq38KHXNRJU8rrRsRm3IZWgoC_27sB9HCaGwseDdZs97EchBGd3' + 77 'JiJDbcokSnjDaQ', 78 d: '_IGPZUaoH5Y8J3ZdwcBb_bFpZn5eDPRTGO0cuThyIXqw2QBODH3Q3LABkvcgO' + 79 'cwaHf91DsMcivs' 80 } 81 } 82}; 83 84const testVectors = [ 85 { 86 name: 'Ed25519', 87 privateUsages: ['sign'], 88 publicUsages: ['verify'] 89 }, 90 { 91 name: 'Ed448', 92 privateUsages: ['sign'], 93 publicUsages: ['verify'] 94 }, 95 { 96 name: 'X25519', 97 privateUsages: ['deriveKey', 'deriveBits'], 98 publicUsages: [] 99 }, 100 { 101 name: 'X448', 102 privateUsages: ['deriveKey', 'deriveBits'], 103 publicUsages: [] 104 }, 105]; 106 107async function testImportSpki({ name, publicUsages }, extractable) { 108 const key = await subtle.importKey( 109 'spki', 110 keyData[name].spki, 111 { name }, 112 extractable, 113 publicUsages); 114 assert.strictEqual(key.type, 'public'); 115 assert.strictEqual(key.extractable, extractable); 116 assert.deepStrictEqual(key.usages, publicUsages); 117 assert.deepStrictEqual(key.algorithm.name, name); 118 119 if (extractable) { 120 // Test the roundtrip 121 const spki = await subtle.exportKey('spki', key); 122 assert.strictEqual( 123 Buffer.from(spki).toString('hex'), 124 keyData[name].spki.toString('hex')); 125 } else { 126 await assert.rejects( 127 subtle.exportKey('spki', key), { 128 message: /key is not extractable/ 129 }); 130 } 131 132 // Bad usage 133 await assert.rejects( 134 subtle.importKey( 135 'spki', 136 keyData[name].spki, 137 { name }, 138 extractable, 139 ['wrapKey']), 140 { message: /Unsupported key usage/ }); 141} 142 143async function testImportPkcs8({ name, privateUsages }, extractable) { 144 const key = await subtle.importKey( 145 'pkcs8', 146 keyData[name].pkcs8, 147 { name }, 148 extractable, 149 privateUsages); 150 assert.strictEqual(key.type, 'private'); 151 assert.strictEqual(key.extractable, extractable); 152 assert.deepStrictEqual(key.usages, privateUsages); 153 assert.deepStrictEqual(key.algorithm.name, name); 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[name].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[name].pkcs8, 172 { name }, 173 extractable, 174 [/* empty usages */]), 175 { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); 176} 177 178async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { 179 180 const jwk = keyData[name].jwk; 181 182 const [ 183 publicKey, 184 privateKey, 185 ] = await Promise.all([ 186 subtle.importKey( 187 'jwk', 188 { 189 kty: jwk.kty, 190 crv: jwk.crv, 191 x: jwk.x, 192 }, 193 { name }, 194 extractable, publicUsages), 195 subtle.importKey( 196 'jwk', 197 jwk, 198 { name }, 199 extractable, 200 privateUsages), 201 subtle.importKey( 202 'jwk', 203 { 204 alg: keyData[name].jwsAlg, 205 kty: jwk.kty, 206 crv: jwk.crv, 207 x: jwk.x, 208 }, 209 { name }, 210 extractable, publicUsages), 211 subtle.importKey( 212 'jwk', 213 { 214 ...jwk, 215 alg: keyData[name].jwsAlg, 216 }, 217 { name }, 218 extractable, 219 privateUsages), 220 ]); 221 222 assert.strictEqual(publicKey.type, 'public'); 223 assert.strictEqual(privateKey.type, 'private'); 224 assert.strictEqual(publicKey.extractable, extractable); 225 assert.strictEqual(privateKey.extractable, extractable); 226 assert.deepStrictEqual(publicKey.usages, publicUsages); 227 assert.deepStrictEqual(privateKey.usages, privateUsages); 228 assert.strictEqual(publicKey.algorithm.name, name); 229 assert.strictEqual(privateKey.algorithm.name, name); 230 231 if (extractable) { 232 // Test the round trip 233 const [ 234 pubJwk, 235 pvtJwk, 236 ] = await Promise.all([ 237 subtle.exportKey('jwk', publicKey), 238 subtle.exportKey('jwk', privateKey), 239 ]); 240 241 assert.deepStrictEqual(pubJwk.key_ops, publicUsages); 242 assert.strictEqual(pubJwk.ext, true); 243 assert.strictEqual(pubJwk.kty, 'OKP'); 244 assert.strictEqual(pubJwk.x, jwk.x); 245 assert.strictEqual(pubJwk.crv, jwk.crv); 246 247 assert.deepStrictEqual(pvtJwk.key_ops, privateUsages); 248 assert.strictEqual(pvtJwk.ext, true); 249 assert.strictEqual(pvtJwk.kty, 'OKP'); 250 assert.strictEqual(pvtJwk.x, jwk.x); 251 assert.strictEqual(pvtJwk.crv, jwk.crv); 252 assert.strictEqual(pvtJwk.d, jwk.d); 253 254 if (jwk.crv.startsWith('Ed')) { 255 assert.strictEqual(pubJwk.alg, 'EdDSA'); 256 assert.strictEqual(pvtJwk.alg, 'EdDSA'); 257 } else { 258 assert.strictEqual(pubJwk.alg, undefined); 259 assert.strictEqual(pvtJwk.alg, undefined); 260 } 261 } else { 262 await assert.rejects( 263 subtle.exportKey('jwk', publicKey), { 264 message: /key is not extractable/ 265 }); 266 await assert.rejects( 267 subtle.exportKey('jwk', privateKey), { 268 message: /key is not extractable/ 269 }); 270 } 271 272 { 273 const invalidUse = name.startsWith('X') ? 'sig' : 'enc'; 274 await assert.rejects( 275 subtle.importKey( 276 'jwk', 277 { ...jwk, use: invalidUse }, 278 { name }, 279 extractable, 280 privateUsages), 281 { message: 'Invalid JWK "use" Parameter' }); 282 } 283 284 if (name.startsWith('Ed')) { 285 await assert.rejects( 286 subtle.importKey( 287 'jwk', 288 { kty: jwk.kty, x: jwk.x, crv: jwk.crv, alg: 'foo' }, 289 { name }, 290 extractable, 291 publicUsages), 292 { message: 'JWK "alg" does not match the requested algorithm' }); 293 294 await assert.rejects( 295 subtle.importKey( 296 'jwk', 297 { ...jwk, alg: 'foo' }, 298 { name }, 299 extractable, 300 privateUsages), 301 { message: 'JWK "alg" does not match the requested algorithm' }); 302 } 303 304 for (const crv of [undefined, name === 'Ed25519' ? 'Ed448' : 'Ed25519']) { 305 await assert.rejects( 306 subtle.importKey( 307 'jwk', 308 { kty: jwk.kty, x: jwk.x, y: jwk.y, crv }, 309 { name }, 310 extractable, 311 publicUsages), 312 { message: 'JWK "crv" Parameter and algorithm name mismatch' }); 313 314 await assert.rejects( 315 subtle.importKey( 316 'jwk', 317 { ...jwk, crv }, 318 { name }, 319 extractable, 320 privateUsages), 321 { message: 'JWK "crv" Parameter and algorithm name mismatch' }); 322 } 323 324 await assert.rejects( 325 subtle.importKey( 326 'jwk', 327 { ...jwk }, 328 { name }, 329 extractable, 330 [/* empty usages */]), 331 { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); 332} 333 334async function testImportRaw({ name, publicUsages }) { 335 const jwk = keyData[name].jwk; 336 337 const publicKey = await subtle.importKey( 338 'raw', 339 Buffer.from(jwk.x, 'base64url'), 340 { name }, 341 true, publicUsages); 342 343 assert.strictEqual(publicKey.type, 'public'); 344 assert.deepStrictEqual(publicKey.usages, publicUsages); 345 assert.strictEqual(publicKey.algorithm.name, name); 346} 347 348(async function() { 349 const tests = []; 350 testVectors.forEach((vector) => { 351 [true, false].forEach((extractable) => { 352 tests.push(testImportSpki(vector, extractable)); 353 tests.push(testImportPkcs8(vector, extractable)); 354 tests.push(testImportJwk(vector, extractable)); 355 }); 356 tests.push(testImportRaw(vector)); 357 }); 358 359 await Promise.all(tests); 360})().then(common.mustCall()); 361 362{ 363 const rsaPublic = crypto.createPublicKey( 364 fixtures.readKey('rsa_public_2048.pem')); 365 const rsaPrivate = crypto.createPrivateKey( 366 fixtures.readKey('rsa_private_2048.pem')); 367 368 for (const [name, publicUsages, privateUsages] of [ 369 ['Ed25519', ['verify'], ['sign']], 370 ['X448', [], ['deriveBits']], 371 ]) { 372 assert.rejects(subtle.importKey( 373 'spki', 374 rsaPublic.export({ format: 'der', type: 'spki' }), 375 { name }, 376 true, publicUsages), { message: /Invalid key type/ }); 377 assert.rejects(subtle.importKey( 378 'pkcs8', 379 rsaPrivate.export({ format: 'der', type: 'pkcs8' }), 380 { name }, 381 true, privateUsages), { message: /Invalid key type/ }); 382 } 383} 384