1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

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)
This commit is contained in:
Divy Srivastava 2024-08-23 09:36:28 -07:00 committed by Luca Casonato
parent 5b0d5b2e6b
commit 37fa0e0f48
No known key found for this signature in database
GPG key ID: 01A83EB62563811F
5 changed files with 187 additions and 1 deletions

View file

@ -232,6 +232,7 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_verify, ops::crypto::op_node_verify,
ops::crypto::op_node_verify_ed25519, ops::crypto::op_node_verify_ed25519,
ops::crypto::keys::op_node_create_private_key, 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_public_key,
ops::crypto::keys::op_node_create_secret_key, ops::crypto::keys::op_node_create_secret_key,
ops::crypto::keys::op_node_derive_public_key_from_private_key, ops::crypto::keys::op_node_derive_public_key_from_private_key,

View file

@ -571,6 +571,50 @@ impl KeyObjectHandle {
Ok(KeyObjectHandle::AsymmetricPublic(key)) 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( pub fn new_asymmetric_public_key_from_js(
key: &[u8], key: &[u8],
format: &str, format: &str,
@ -1027,6 +1071,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] #[op2]
#[cppgc] #[cppgc]
pub fn op_node_create_public_key( pub fn op_node_create_public_key(

View file

@ -12,6 +12,7 @@ const {
} = primordials; } = primordials;
import { import {
op_node_create_ed_raw,
op_node_create_private_key, op_node_create_private_key,
op_node_create_public_key, op_node_create_public_key,
op_node_create_secret_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 { isStringOrBuffer } from "ext:deno_node/internal/crypto/cipher.ts";
import { import {
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_CRYPTO_INVALID_JWK,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_VALUE,
@ -56,6 +58,7 @@ import {
import { import {
validateObject, validateObject,
validateOneOf, validateOneOf,
validateString,
} from "ext:deno_node/internal/validators.mjs"; } from "ext:deno_node/internal/validators.mjs";
import { BufferEncoding } from "ext:deno_node/_global.d.ts"; import { BufferEncoding } from "ext:deno_node/_global.d.ts";
@ -256,6 +259,64 @@ export function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) {
return key[kHandle]; 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( export function prepareAsymmetricKey(
key: key:
| string | string
@ -306,7 +367,12 @@ export function prepareAsymmetricKey(
} else if (isCryptoKey(data)) { } else if (isCryptoKey(data)) {
notImplemented("using CryptoKey as input"); notImplemented("using CryptoKey as input");
} else if (isJwk(data) && format === "jwk") { } 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. // Either PEM or DER using PKCS#1 or SPKI.
if (!isStringOrBuffer(data)) { if (!isStringOrBuffer(data)) {

View file

@ -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 { export class ERR_CRYPTO_INVALID_STATE extends NodeError {
constructor(x: string) { constructor(x: string) {
super("ERR_CRYPTO_INVALID_STATE", `Invalid state for operation ${x}`); super("ERR_CRYPTO_INVALID_STATE", `Invalid state for operation ${x}`);
@ -2733,6 +2739,7 @@ export default {
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_CRYPTO_INVALID_DIGEST, ERR_CRYPTO_INVALID_DIGEST,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_CRYPTO_INVALID_JWK,
ERR_CRYPTO_INVALID_STATE, ERR_CRYPTO_INVALID_STATE,
ERR_CRYPTO_PBKDF2_ERROR, ERR_CRYPTO_PBKDF2_ERROR,
ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, ERR_CRYPTO_SCRYPT_INVALID_PARAMETER,

View file

@ -439,3 +439,61 @@ Deno.test("create private key with invalid utf-8 string", function () {
"not valid utf8", "not valid utf8",
); );
}); });
Deno.test("Ed25519 jwk public key #1", function () {
const key = {
"kty": "OKP",
"crv": "Ed25519",
"d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
};
const keyObject = createPublicKey({ key, format: "jwk" });
assertEquals(keyObject.type, "public");
const spkiActual = keyObject.export({ type: "spki", format: "pem" });
const spkiExpected = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=
-----END PUBLIC KEY-----
`;
assertEquals(spkiActual, spkiExpected);
});
Deno.test("Ed25519 jwk public key #2", function () {
const key = {
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
};
const keyObject = createPublicKey({ key, format: "jwk" });
assertEquals(keyObject.type, "public");
const spki = keyObject.export({ type: "spki", format: "pem" });
const spkiExpected = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=
-----END PUBLIC KEY-----
`;
assertEquals(spki, spkiExpected);
});
Deno.test("Ed25519 jwk private key", function () {
const key = {
"kty": "OKP",
"crv": "Ed25519",
"d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
};
const keyObject = createPrivateKey({ key, format: "jwk" });
assertEquals(keyObject.type, "private");
const pkcs8Actual = keyObject.export({ type: "pkcs8", format: "pem" });
const pkcs8Expected = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJ1hsZ3v/VpguoRK9JLsLMREScVpezJpGXA7rAMcrn9g
-----END PRIVATE KEY-----
`;
assertEquals(pkcs8Actual, pkcs8Expected);
});