1'use strict'; 2 3const common = require('../common'); 4if (!common.hasCrypto) 5 common.skip('missing crypto'); 6 7const assert = require('assert'); 8const { 9 createCipheriv, 10 createDecipheriv, 11 createSign, 12 createVerify, 13 createSecretKey, 14 createPublicKey, 15 createPrivateKey, 16 KeyObject, 17 randomBytes, 18 publicDecrypt, 19 publicEncrypt, 20 privateDecrypt, 21 privateEncrypt 22} = require('crypto'); 23 24const fixtures = require('../common/fixtures'); 25 26const publicPem = fixtures.readKey('rsa_public.pem', 'ascii'); 27const privatePem = fixtures.readKey('rsa_private.pem', 'ascii'); 28 29const publicDsa = fixtures.readKey('dsa_public_1025.pem', 'ascii'); 30const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', 31 'ascii'); 32 33{ 34 // Attempting to create an empty key should throw. 35 assert.throws(() => { 36 createSecretKey(Buffer.alloc(0)); 37 }, { 38 name: 'RangeError', 39 code: 'ERR_OUT_OF_RANGE', 40 message: 'The value of "key.byteLength" is out of range. ' + 41 'It must be > 0. Received 0' 42 }); 43} 44 45{ 46 // Attempting to create a key of a wrong type should throw 47 const TYPE = 'wrong_type'; 48 49 assert.throws(() => new KeyObject(TYPE), { 50 name: 'TypeError', 51 code: 'ERR_INVALID_ARG_VALUE', 52 message: `The argument 'type' is invalid. Received '${TYPE}'` 53 }); 54} 55 56{ 57 // Attempting to create a key with non-object handle should throw 58 assert.throws(() => new KeyObject('secret', ''), { 59 name: 'TypeError', 60 code: 'ERR_INVALID_ARG_TYPE', 61 message: 62 'The "handle" argument must be of type object. Received type ' + 63 "string ('')" 64 }); 65} 66 67{ 68 const keybuf = randomBytes(32); 69 const key = createSecretKey(keybuf); 70 assert.strictEqual(key.type, 'secret'); 71 assert.strictEqual(key.symmetricKeySize, 32); 72 assert.strictEqual(key.asymmetricKeyType, undefined); 73 74 const exportedKey = key.export(); 75 assert(keybuf.equals(exportedKey)); 76 77 const plaintext = Buffer.from('Hello world', 'utf8'); 78 79 const cipher = createCipheriv('aes-256-ecb', key, null); 80 const ciphertext = Buffer.concat([ 81 cipher.update(plaintext), cipher.final(), 82 ]); 83 84 const decipher = createDecipheriv('aes-256-ecb', key, null); 85 const deciphered = Buffer.concat([ 86 decipher.update(ciphertext), decipher.final(), 87 ]); 88 89 assert(plaintext.equals(deciphered)); 90} 91 92{ 93 // Passing an existing public key object to createPublicKey should throw. 94 const publicKey = createPublicKey(publicPem); 95 assert.throws(() => createPublicKey(publicKey), { 96 name: 'TypeError', 97 code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE', 98 message: 'Invalid key object type public, expected private.' 99 }); 100 101 // Constructing a private key from a public key should be impossible, even 102 // if the public key was derived from a private key. 103 assert.throws(() => createPrivateKey(createPublicKey(privatePem)), { 104 name: 'TypeError', 105 code: 'ERR_INVALID_ARG_TYPE', 106 message: 'The "key" argument must be of type string or an instance of ' + 107 'Buffer, TypedArray, or DataView. Received an instance of ' + 108 'PublicKeyObject' 109 }); 110 111 // Similarly, passing an existing private key object to createPrivateKey 112 // should throw. 113 const privateKey = createPrivateKey(privatePem); 114 assert.throws(() => createPrivateKey(privateKey), { 115 name: 'TypeError', 116 code: 'ERR_INVALID_ARG_TYPE', 117 message: 'The "key" argument must be of type string or an instance of ' + 118 'Buffer, TypedArray, or DataView. Received an instance of ' + 119 'PrivateKeyObject' 120 }); 121} 122 123{ 124 const publicKey = createPublicKey(publicPem); 125 assert.strictEqual(publicKey.type, 'public'); 126 assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); 127 assert.strictEqual(publicKey.symmetricKeySize, undefined); 128 129 const privateKey = createPrivateKey(privatePem); 130 assert.strictEqual(privateKey.type, 'private'); 131 assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); 132 assert.strictEqual(privateKey.symmetricKeySize, undefined); 133 134 // It should be possible to derive a public key from a private key. 135 const derivedPublicKey = createPublicKey(privateKey); 136 assert.strictEqual(derivedPublicKey.type, 'public'); 137 assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa'); 138 assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined); 139 140 // Test exporting with an invalid options object, this should throw. 141 for (const opt of [undefined, null, 'foo', 0, NaN]) { 142 assert.throws(() => publicKey.export(opt), { 143 name: 'TypeError', 144 code: 'ERR_INVALID_ARG_TYPE', 145 message: /^The "options" argument must be of type object/ 146 }); 147 } 148 149 const publicDER = publicKey.export({ 150 format: 'der', 151 type: 'pkcs1' 152 }); 153 154 const privateDER = privateKey.export({ 155 format: 'der', 156 type: 'pkcs1' 157 }); 158 159 assert(Buffer.isBuffer(publicDER)); 160 assert(Buffer.isBuffer(privateDER)); 161 162 const plaintext = Buffer.from('Hello world', 'utf8'); 163 const testDecryption = (fn, ciphertexts, decryptionKeys) => { 164 for (const ciphertext of ciphertexts) { 165 for (const key of decryptionKeys) { 166 const deciphered = fn(key, ciphertext); 167 assert.deepStrictEqual(deciphered, plaintext); 168 } 169 } 170 }; 171 172 testDecryption(privateDecrypt, [ 173 // Encrypt using the public key. 174 publicEncrypt(publicKey, plaintext), 175 publicEncrypt({ key: publicKey }, plaintext), 176 177 // Encrypt using the private key. 178 publicEncrypt(privateKey, plaintext), 179 publicEncrypt({ key: privateKey }, plaintext), 180 181 // Encrypt using a public key derived from the private key. 182 publicEncrypt(derivedPublicKey, plaintext), 183 publicEncrypt({ key: derivedPublicKey }, plaintext), 184 185 // Test distinguishing PKCS#1 public and private keys based on the 186 // DER-encoded data only. 187 publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext), 188 publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext), 189 ], [ 190 privateKey, 191 { format: 'pem', key: privatePem }, 192 { format: 'der', type: 'pkcs1', key: privateDER }, 193 ]); 194 195 testDecryption(publicDecrypt, [ 196 privateEncrypt(privateKey, plaintext), 197 ], [ 198 // Decrypt using the public key. 199 publicKey, 200 { format: 'pem', key: publicPem }, 201 { format: 'der', type: 'pkcs1', key: publicDER }, 202 203 // Decrypt using the private key. 204 privateKey, 205 { format: 'pem', key: privatePem }, 206 { format: 'der', type: 'pkcs1', key: privateDER }, 207 ]); 208} 209 210{ 211 // This should not cause a crash: https://github.com/nodejs/node/issues/25247 212 assert.throws(() => { 213 createPrivateKey({ key: '' }); 214 }, { 215 message: 'error:2007E073:BIO routines:BIO_new_mem_buf:null parameter', 216 code: 'ERR_OSSL_BIO_NULL_PARAMETER', 217 reason: 'null parameter', 218 library: 'BIO routines', 219 function: 'BIO_new_mem_buf', 220 }); 221 222 // This should not abort either: https://github.com/nodejs/node/issues/29904 223 assert.throws(() => { 224 createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'spki' }); 225 }, { 226 code: 'ERR_INVALID_OPT_VALUE', 227 message: 'The value "spki" is invalid for option "type"' 228 }); 229 230 // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys), 231 // so it should be accepted by createPrivateKey, but OpenSSL won't parse it. 232 assert.throws(() => { 233 const key = createPublicKey(publicPem).export({ 234 format: 'der', 235 type: 'pkcs1' 236 }); 237 createPrivateKey({ key, format: 'der', type: 'pkcs1' }); 238 }, { 239 message: /asn1 encoding/, 240 library: 'asn1 encoding routines' 241 }); 242} 243 244[ 245 { private: fixtures.readKey('ed25519_private.pem', 'ascii'), 246 public: fixtures.readKey('ed25519_public.pem', 'ascii'), 247 keyType: 'ed25519' }, 248 { private: fixtures.readKey('ed448_private.pem', 'ascii'), 249 public: fixtures.readKey('ed448_public.pem', 'ascii'), 250 keyType: 'ed448' }, 251 { private: fixtures.readKey('x25519_private.pem', 'ascii'), 252 public: fixtures.readKey('x25519_public.pem', 'ascii'), 253 keyType: 'x25519' }, 254 { private: fixtures.readKey('x448_private.pem', 'ascii'), 255 public: fixtures.readKey('x448_public.pem', 'ascii'), 256 keyType: 'x448' }, 257].forEach((info) => { 258 const keyType = info.keyType; 259 260 { 261 const exportOptions = { type: 'pkcs8', format: 'pem' }; 262 const key = createPrivateKey(info.private); 263 assert.strictEqual(key.type, 'private'); 264 assert.strictEqual(key.asymmetricKeyType, keyType); 265 assert.strictEqual(key.symmetricKeySize, undefined); 266 assert.strictEqual(key.export(exportOptions), info.private); 267 } 268 269 { 270 const exportOptions = { type: 'spki', format: 'pem' }; 271 [info.private, info.public].forEach((pem) => { 272 const key = createPublicKey(pem); 273 assert.strictEqual(key.type, 'public'); 274 assert.strictEqual(key.asymmetricKeyType, keyType); 275 assert.strictEqual(key.symmetricKeySize, undefined); 276 assert.strictEqual(key.export(exportOptions), info.public); 277 }); 278 } 279}); 280 281{ 282 // Reading an encrypted key without a passphrase should fail. 283 assert.throws(() => createPrivateKey(privateDsa), { 284 name: 'TypeError', 285 code: 'ERR_MISSING_PASSPHRASE', 286 message: 'Passphrase required for encrypted key' 287 }); 288 289 // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer 290 // size limit should fail with an appropriate error code. 291 assert.throws(() => createPrivateKey({ 292 key: privateDsa, 293 format: 'pem', 294 passphrase: Buffer.alloc(1025, 'a') 295 }), { 296 code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ', 297 name: 'Error' 298 }); 299 300 // The buffer has a size of 1024 bytes, so this passphrase should be permitted 301 // (but will fail decryption). 302 assert.throws(() => createPrivateKey({ 303 key: privateDsa, 304 format: 'pem', 305 passphrase: Buffer.alloc(1024, 'a') 306 }), { 307 message: /bad decrypt/ 308 }); 309 310 const publicKey = createPublicKey(publicDsa); 311 assert.strictEqual(publicKey.type, 'public'); 312 assert.strictEqual(publicKey.asymmetricKeyType, 'dsa'); 313 assert.strictEqual(publicKey.symmetricKeySize, undefined); 314 315 const privateKey = createPrivateKey({ 316 key: privateDsa, 317 format: 'pem', 318 passphrase: 'secret' 319 }); 320 assert.strictEqual(privateKey.type, 'private'); 321 assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); 322 assert.strictEqual(privateKey.symmetricKeySize, undefined); 323 324} 325 326{ 327 // Test RSA-PSS. 328 { 329 // This key pair does not restrict the message digest algorithm or salt 330 // length. 331 const publicPem = fixtures.readKey('rsa_pss_public_2048.pem'); 332 const privatePem = fixtures.readKey('rsa_pss_private_2048.pem'); 333 334 const publicKey = createPublicKey(publicPem); 335 const privateKey = createPrivateKey(privatePem); 336 337 assert.strictEqual(publicKey.type, 'public'); 338 assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); 339 340 assert.strictEqual(privateKey.type, 'private'); 341 assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); 342 343 for (const key of [privatePem, privateKey]) { 344 // Any algorithm should work. 345 for (const algo of ['sha1', 'sha256']) { 346 // Any salt length should work. 347 for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { 348 const signature = createSign(algo) 349 .update('foo') 350 .sign({ key, saltLength }); 351 352 for (const pkey of [key, publicKey, publicPem]) { 353 const okay = createVerify(algo) 354 .update('foo') 355 .verify({ key: pkey, saltLength }, signature); 356 357 assert.ok(okay); 358 } 359 } 360 } 361 } 362 363 // Exporting the key using PKCS#1 should not work since this would discard 364 // any algorithm restrictions. 365 assert.throws(() => { 366 publicKey.export({ format: 'pem', type: 'pkcs1' }); 367 }, { 368 code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' 369 }); 370 } 371 372 { 373 // This key pair enforces sha256 as the message digest and the MGF1 374 // message digest and a salt length of at least 16 bytes. 375 const publicPem = 376 fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem'); 377 const privatePem = 378 fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem'); 379 380 const publicKey = createPublicKey(publicPem); 381 const privateKey = createPrivateKey(privatePem); 382 383 assert.strictEqual(publicKey.type, 'public'); 384 assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); 385 386 assert.strictEqual(privateKey.type, 'private'); 387 assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); 388 389 for (const key of [privatePem, privateKey]) { 390 // Signing with anything other than sha256 should fail. 391 assert.throws(() => { 392 createSign('sha1').sign(key); 393 }, /digest not allowed/); 394 395 // Signing with salt lengths less than 16 bytes should fail. 396 for (const saltLength of [8, 10, 12]) { 397 assert.throws(() => { 398 createSign('sha1').sign({ key, saltLength }); 399 }, /pss saltlen too small/); 400 } 401 402 // Signing with sha256 and appropriate salt lengths should work. 403 for (const saltLength of [undefined, 16, 18, 20]) { 404 const signature = createSign('sha256') 405 .update('foo') 406 .sign({ key, saltLength }); 407 408 for (const pkey of [key, publicKey, publicPem]) { 409 const okay = createVerify('sha256') 410 .update('foo') 411 .verify({ key: pkey, saltLength }, signature); 412 413 assert.ok(okay); 414 } 415 } 416 } 417 } 418 419 { 420 // This key enforces sha512 as the message digest and sha256 as the MGF1 421 // message digest. 422 const publicPem = 423 fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem'); 424 const privatePem = 425 fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem'); 426 427 const publicKey = createPublicKey(publicPem); 428 const privateKey = createPrivateKey(privatePem); 429 430 assert.strictEqual(publicKey.type, 'public'); 431 assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); 432 433 assert.strictEqual(privateKey.type, 'private'); 434 assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); 435 436 // Node.js usually uses the same hash function for the message and for MGF1. 437 // However, when a different MGF1 message digest algorithm has been 438 // specified as part of the key, it should automatically switch to that. 439 // This behavior is required by sections 3.1 and 3.3 of RFC4055. 440 for (const key of [privatePem, privateKey]) { 441 // sha256 matches the MGF1 hash function and should be used internally, 442 // but it should not be permitted as the main message digest algorithm. 443 for (const algo of ['sha1', 'sha256']) { 444 assert.throws(() => { 445 createSign(algo).sign(key); 446 }, /digest not allowed/); 447 } 448 449 // sha512 should produce a valid signature. 450 const signature = createSign('sha512') 451 .update('foo') 452 .sign(key); 453 454 for (const pkey of [key, publicKey, publicPem]) { 455 const okay = createVerify('sha512') 456 .update('foo') 457 .verify(pkey, signature); 458 459 assert.ok(okay); 460 } 461 } 462 } 463} 464 465{ 466 // Exporting an encrypted private key requires a cipher 467 const privateKey = createPrivateKey(privatePem); 468 assert.throws(() => { 469 privateKey.export({ 470 format: 'pem', type: 'pkcs8', passphrase: 'super-secret' 471 }); 472 }, { 473 name: 'TypeError', 474 code: 'ERR_INVALID_OPT_VALUE', 475 message: 'The value "undefined" is invalid for option "cipher"' 476 }); 477} 478