mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -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:
parent
5b0d5b2e6b
commit
37fa0e0f48
5 changed files with 187 additions and 1 deletions
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue