mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -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_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,
|
||||
|
|
|
@ -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,
|
||||
|
@ -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]
|
||||
#[cppgc]
|
||||
pub fn op_node_create_public_key(
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -439,3 +439,61 @@ Deno.test("create private key with invalid utf-8 string", function () {
|
|||
"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