diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2024-08-23 09:36:28 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-23 22:06:28 +0530 |
commit | d9a7b30d1fa93dc742c9a3ee0fe8666be7ce1c0f (patch) | |
tree | f84a25cb776c870fd1e33a88e6df38615e717007 /ext/node | |
parent | d54d29662f30c0fa5e1f048fdce4835e51248682 (diff) |
fix(ext/node): import JWK octet key pairs (#25180)
Ref https://github.com/denoland/deno/issues/24129
`kty: "okp"` is defined in
[rfc8037](https://www.rfc-editor.org/rfc/rfc8037.html)
Diffstat (limited to 'ext/node')
-rw-r--r-- | ext/node/lib.rs | 1 | ||||
-rw-r--r-- | ext/node/ops/crypto/keys.rs | 54 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/keys.ts | 68 | ||||
-rw-r--r-- | ext/node/polyfills/internal/errors.ts | 7 |
4 files changed, 129 insertions, 1 deletions
diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 39b380b99..ed6713eed 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -232,6 +232,7 @@ deno_core::extension!(deno_node, ops::crypto::op_node_verify, ops::crypto::op_node_verify_ed25519, ops::crypto::keys::op_node_create_private_key, + ops::crypto::keys::op_node_create_ed_raw, ops::crypto::keys::op_node_create_public_key, ops::crypto::keys::op_node_create_secret_key, ops::crypto::keys::op_node_derive_public_key_from_private_key, diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs index 45849cbd9..7d7ec140e 100644 --- a/ext/node/ops/crypto/keys.rs +++ b/ext/node/ops/crypto/keys.rs @@ -571,6 +571,50 @@ impl KeyObjectHandle { Ok(KeyObjectHandle::AsymmetricPublic(key)) } + pub fn new_ed_raw( + curve: &str, + data: &[u8], + is_public: bool, + ) -> Result<KeyObjectHandle, AnyError> { + match curve { + "Ed25519" => { + let data = data + .try_into() + .map_err(|_| type_error("invalid Ed25519 key"))?; + if !is_public { + Ok(KeyObjectHandle::AsymmetricPrivate( + AsymmetricPrivateKey::Ed25519( + ed25519_dalek::SigningKey::from_bytes(data), + ), + )) + } else { + Ok(KeyObjectHandle::AsymmetricPublic( + AsymmetricPublicKey::Ed25519( + ed25519_dalek::VerifyingKey::from_bytes(data)?, + ), + )) + } + } + "X25519" => { + let data: [u8; 32] = data + .try_into() + .map_err(|_| type_error("invalid x25519 key"))?; + if !is_public { + Ok(KeyObjectHandle::AsymmetricPrivate( + AsymmetricPrivateKey::X25519(x25519_dalek::StaticSecret::from( + data, + )), + )) + } else { + Ok(KeyObjectHandle::AsymmetricPublic( + AsymmetricPublicKey::X25519(x25519_dalek::PublicKey::from(data)), + )) + } + } + _ => Err(type_error("unsupported curve")), + } + } + pub fn new_asymmetric_public_key_from_js( key: &[u8], format: &str, @@ -1029,6 +1073,16 @@ pub fn op_node_create_private_key( #[op2] #[cppgc] +pub fn op_node_create_ed_raw( + #[string] curve: &str, + #[buffer] key: &[u8], + is_public: bool, +) -> Result<KeyObjectHandle, AnyError> { + KeyObjectHandle::new_ed_raw(curve, key, is_public) +} + +#[op2] +#[cppgc] pub fn op_node_create_public_key( #[buffer] key: &[u8], #[string] format: &str, diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts index 62cec47d6..97e565023 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -12,6 +12,7 @@ const { } = primordials; import { + op_node_create_ed_raw, op_node_create_private_key, op_node_create_public_key, op_node_create_secret_key, @@ -32,6 +33,7 @@ import { kHandle } from "ext:deno_node/internal/crypto/constants.ts"; import { isStringOrBuffer } from "ext:deno_node/internal/crypto/cipher.ts"; import { ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, + ERR_CRYPTO_INVALID_JWK, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -56,6 +58,7 @@ import { import { validateObject, validateOneOf, + validateString, } from "ext:deno_node/internal/validators.mjs"; import { BufferEncoding } from "ext:deno_node/_global.d.ts"; @@ -256,6 +259,64 @@ export function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) { return key[kHandle]; } +function getKeyObjectHandleFromJwk(key, ctx) { + validateObject(key, "key"); + validateOneOf( + key.kty, + "key.kty", + ["RSA", "EC", "OKP"], + ); + const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; + + if (key.kty === "OKP") { + validateString(key.crv, "key.crv"); + validateOneOf( + key.crv, + "key.crv", + ["Ed25519", "Ed448", "X25519", "X448"], + ); + validateString(key.x, "key.x"); + + if (!isPublic) { + validateString(key.d, "key.d"); + } + + let keyData; + if (isPublic) { + keyData = Buffer.from(key.x, "base64"); + } else { + keyData = Buffer.from(key.d, "base64"); + } + + switch (key.crv) { + case "Ed25519": + case "X25519": + if (keyData.byteLength !== 32) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + case "Ed448": + if (keyData.byteLength !== 57) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + case "X448": + if (keyData.byteLength !== 56) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + } + + return op_node_create_ed_raw(key.crv, keyData, isPublic); + } + + if (key.kty === "EC") { + throw new TypeError("ec jwk imports not implemented"); + } + + throw new TypeError("rsa jwk imports not implemented"); +} + export function prepareAsymmetricKey( key: | string @@ -306,7 +367,12 @@ export function prepareAsymmetricKey( } else if (isCryptoKey(data)) { notImplemented("using CryptoKey as input"); } else if (isJwk(data) && format === "jwk") { - notImplemented("using JWK as input"); + return { + // @ts-ignore __proto__ is magic + __proto__: null, + handle: getKeyObjectHandleFromJwk(data, ctx), + format, + }; } // Either PEM or DER using PKCS#1 or SPKI. if (!isStringOrBuffer(data)) { diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index 9ec9f9949..9e0905ef4 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -927,6 +927,12 @@ export class ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE extends NodeTypeError { } } +export class ERR_CRYPTO_INVALID_JWK extends NodeError { + constructor() { + super("ERR_CRYPTO_INVALID_JWK", "Invalid JWK"); + } +} + export class ERR_CRYPTO_INVALID_STATE extends NodeError { constructor(x: string) { super("ERR_CRYPTO_INVALID_STATE", `Invalid state for operation ${x}`); @@ -2733,6 +2739,7 @@ export default { ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, ERR_CRYPTO_INVALID_DIGEST, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_CRYPTO_INVALID_JWK, ERR_CRYPTO_INVALID_STATE, ERR_CRYPTO_PBKDF2_ERROR, ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, |