@@ -26,6 +26,7 @@ const { |
26 | 26 | const { |
27 | 27 | validateObject, |
28 | 28 | validateOneOf, |
| 29 | + validateString, |
29 | 30 | } = require('internal/validators'); |
30 | 31 |
|
31 | 32 | const { |
@@ -38,6 +39,7 @@ const { |
38 | 39 | ERR_OPERATION_FAILED, |
39 | 40 | ERR_CRYPTO_JWK_UNSUPPORTED_CURVE, |
40 | 41 | ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE, |
| 42 | + ERR_CRYPTO_INVALID_JWK, |
41 | 43 | } |
42 | 44 | } = require('internal/errors'); |
43 | 45 |
|
@@ -65,6 +67,8 @@ const { |
65 | 67 |
|
66 | 68 | const { inspect } = require('internal/util/inspect'); |
67 | 69 |
|
| 70 | +const { Buffer } = require('buffer'); |
| 71 | + |
68 | 72 | const kAlgorithm = Symbol('kAlgorithm'); |
69 | 73 | const kExtractable = Symbol('kExtractable'); |
70 | 74 | const kKeyType = Symbol('kKeyType'); |
@@ -413,6 +417,122 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) { |
413 | 417 | return types; |
414 | 418 | } |
415 | 419 |
|
| 420 | +function getKeyObjectHandleFromJwk(key, ctx) { |
| 421 | + validateObject(key, 'key'); |
| 422 | + validateOneOf( |
| 423 | + key.kty, 'key.kty', ['RSA', 'EC', 'OKP']); |
| 424 | + const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; |
| 425 | + |
| 426 | + if (key.kty === 'OKP') { |
| 427 | + validateString(key.crv, 'key.crv'); |
| 428 | + validateOneOf( |
| 429 | + key.crv, 'key.crv', ['Ed25519', 'Ed448', 'X25519', 'X448']); |
| 430 | + validateString(key.x, 'key.x'); |
| 431 | + |
| 432 | + if (!isPublic) |
| 433 | + validateString(key.d, 'key.d'); |
| 434 | + |
| 435 | + let keyData; |
| 436 | + if (isPublic) |
| 437 | + keyData = Buffer.from(key.x, 'base64'); |
| 438 | + else |
| 439 | + keyData = Buffer.from(key.d, 'base64'); |
| 440 | + |
| 441 | + switch (key.crv) { |
| 442 | + case 'Ed25519': |
| 443 | + case 'X25519': |
| 444 | + if (keyData.byteLength !== 32) { |
| 445 | + throw new ERR_CRYPTO_INVALID_JWK(); |
| 446 | + } |
| 447 | + break; |
| 448 | + case 'Ed448': |
| 449 | + if (keyData.byteLength !== 57) { |
| 450 | + throw new ERR_CRYPTO_INVALID_JWK(); |
| 451 | + } |
| 452 | + break; |
| 453 | + case 'X448': |
| 454 | + if (keyData.byteLength !== 56) { |
| 455 | + throw new ERR_CRYPTO_INVALID_JWK(); |
| 456 | + } |
| 457 | + break; |
| 458 | + } |
| 459 | + |
| 460 | + const handle = new KeyObjectHandle(); |
| 461 | + if (isPublic) { |
| 462 | + handle.initEDRaw( |
| 463 | + `NODE-${key.crv.toUpperCase()}`, |
| 464 | + keyData, |
| 465 | + kKeyTypePublic); |
| 466 | + } else { |
| 467 | + handle.initEDRaw( |
| 468 | + `NODE-${key.crv.toUpperCase()}`, |
| 469 | + keyData, |
| 470 | + kKeyTypePrivate); |
| 471 | + } |
| 472 | + |
| 473 | + return handle; |
| 474 | + } |
| 475 | + |
| 476 | + if (key.kty === 'EC') { |
| 477 | + validateString(key.crv, 'key.crv'); |
| 478 | + validateOneOf( |
| 479 | + key.crv, 'key.crv', ['P-256', 'secp256k1', 'P-384', 'P-521']); |
| 480 | + validateString(key.x, 'key.x'); |
| 481 | + validateString(key.y, 'key.y'); |
| 482 | + |
| 483 | + const jwk = { |
| 484 | + kty: key.kty, |
| 485 | + crv: key.crv, |
| 486 | + x: key.x, |
| 487 | + y: key.y |
| 488 | + }; |
| 489 | + |
| 490 | + if (!isPublic) { |
| 491 | + validateString(key.d, 'key.d'); |
| 492 | + jwk.d = key.d; |
| 493 | + } |
| 494 | + |
| 495 | + const handle = new KeyObjectHandle(); |
| 496 | + const type = handle.initJwk(jwk, jwk.crv); |
| 497 | + if (type === undefined) |
| 498 | + throw new ERR_CRYPTO_INVALID_JWK(); |
| 499 | + |
| 500 | + return handle; |
| 501 | + } |
| 502 | + |
| 503 | + // RSA |
| 504 | + validateString(key.n, 'key.n'); |
| 505 | + validateString(key.e, 'key.e'); |
| 506 | + |
| 507 | + const jwk = { |
| 508 | + kty: key.kty, |
| 509 | + n: key.n, |
| 510 | + e: key.e |
| 511 | + }; |
| 512 | + |
| 513 | + if (!isPublic) { |
| 514 | + validateString(key.d, 'key.d'); |
| 515 | + validateString(key.p, 'key.p'); |
| 516 | + validateString(key.q, 'key.q'); |
| 517 | + validateString(key.dp, 'key.dp'); |
| 518 | + validateString(key.dq, 'key.dq'); |
| 519 | + validateString(key.qi, 'key.qi'); |
| 520 | + jwk.d = key.d; |
| 521 | + jwk.p = key.p; |
| 522 | + jwk.q = key.q; |
| 523 | + jwk.dp = key.dp; |
| 524 | + jwk.dq = key.dq; |
| 525 | + jwk.qi = key.qi; |
| 526 | + } |
| 527 | + |
| 528 | + const handle = new KeyObjectHandle(); |
| 529 | + const type = handle.initJwk(jwk); |
| 530 | + if (type === undefined) |
| 531 | + throw new ERR_CRYPTO_INVALID_JWK(); |
| 532 | + |
| 533 | + return handle; |
| 534 | +} |
| 535 | + |
416 | 536 | function prepareAsymmetricKey(key, ctx) { |
417 | 537 | if (isKeyObject(key)) { |
418 | 538 | // Best case: A key object, as simple as that. |
@@ -423,13 +543,15 @@ function prepareAsymmetricKey(key, ctx) { |
423 | 543 | // Expect PEM by default, mostly for backward compatibility. |
424 | 544 | return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') }; |
425 | 545 | } else if (typeof key === 'object') { |
426 | | - const { key: data, encoding } = key; |
| 546 | + const { key: data, encoding, format } = key; |
427 | 547 | // The 'key' property can be a KeyObject as well to allow specifying |
428 | 548 | // additional options such as padding along with the key. |
429 | 549 | if (isKeyObject(data)) |
430 | 550 | return { data: getKeyObjectHandle(data, ctx) }; |
431 | 551 | else if (isCryptoKey(data)) |
432 | 552 | return { data: getKeyObjectHandle(data[kKeyObject], ctx) }; |
| 553 | + else if (isJwk(data) && format === 'jwk') |
| 554 | + return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' }; |
433 | 555 | // Either PEM or DER using PKCS#1 or SPKI. |
434 | 556 | if (!isStringOrBuffer(data)) { |
435 | 557 | throw new ERR_INVALID_ARG_TYPE( |
@@ -494,16 +616,26 @@ function createSecretKey(key, encoding) { |
494 | 616 | function createPublicKey(key) { |
495 | 617 | const { format, type, data, passphrase } = |
496 | 618 | prepareAsymmetricKey(key, kCreatePublic); |
497 | | - const handle = new KeyObjectHandle(); |
498 | | - handle.init(kKeyTypePublic, data, format, type, passphrase); |
| 619 | + let handle; |
| 620 | + if (format === 'jwk') { |
| 621 | + handle = data; |
| 622 | + } else { |
| 623 | + handle = new KeyObjectHandle(); |
| 624 | + handle.init(kKeyTypePublic, data, format, type, passphrase); |
| 625 | + } |
499 | 626 | return new PublicKeyObject(handle); |
500 | 627 | } |
501 | 628 |
|
502 | 629 | function createPrivateKey(key) { |
503 | 630 | const { format, type, data, passphrase } = |
504 | 631 | prepareAsymmetricKey(key, kCreatePrivate); |
505 | | - const handle = new KeyObjectHandle(); |
506 | | - handle.init(kKeyTypePrivate, data, format, type, passphrase); |
| 632 | + let handle; |
| 633 | + if (format === 'jwk') { |
| 634 | + handle = data; |
| 635 | + } else { |
| 636 | + handle = new KeyObjectHandle(); |
| 637 | + handle.init(kKeyTypePrivate, data, format, type, passphrase); |
| 638 | + } |
507 | 639 | return new PrivateKeyObject(handle); |
508 | 640 | } |
509 | 641 |
|
@@ -609,6 +741,10 @@ function isCryptoKey(obj) { |
609 | 741 | return obj != null && obj[kKeyObject] !== undefined; |
610 | 742 | } |
611 | 743 |
|
| 744 | +function isJwk(obj) { |
| 745 | + return obj != null && obj.kty !== undefined; |
| 746 | +} |
| 747 | + |
612 | 748 | module.exports = { |
613 | 749 | // Public API. |
614 | 750 | createSecretKey, |
|