mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
feat(ext/node): implement crypto.createSecretKey (#18413)
This commit adds the `crypto.createSecretKey` API. Key management: This follows the same approach as our WebCrypto CryptoKey impl where we use WeakMap for storing key material and a handle is passed around, such that (only internal) JS can access the key material and we don't have to explicitly close a Rust resource. As a result, `createHmac` now accepts a secret KeyObject. Closes https://github.com/denoland/deno/issues/17844
This commit is contained in:
parent
dc77002b99
commit
4f7b778700
3 changed files with 121 additions and 20 deletions
47
cli/tests/unit_node/crypto_key.ts
Normal file
47
cli/tests/unit_node/crypto_key.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
import { createSecretKey, randomBytes } from "node:crypto";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { assertEquals } from "../../../test_util/std/testing/asserts.ts";
|
||||
import { createHmac } from "node:crypto";
|
||||
|
||||
Deno.test({
|
||||
name: "create secret key",
|
||||
fn() {
|
||||
const key = createSecretKey(Buffer.alloc(0));
|
||||
assertEquals(key.type, "secret");
|
||||
assertEquals(key.asymmetricKeyType, undefined);
|
||||
assertEquals(key.symmetricKeySize, 0);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "export secret key",
|
||||
fn() {
|
||||
const material = Buffer.from(randomBytes(32));
|
||||
const key = createSecretKey(material);
|
||||
assertEquals(Buffer.from(key.export()), material);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "export jwk secret key",
|
||||
fn() {
|
||||
const material = Buffer.from("secret");
|
||||
const key = createSecretKey(material);
|
||||
assertEquals(key.export({ format: "jwk" }), {
|
||||
kty: "oct",
|
||||
k: "c2VjcmV0",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "createHmac with secret key",
|
||||
fn() {
|
||||
const key = createSecretKey(Buffer.from("secret"));
|
||||
assertEquals(
|
||||
createHmac("sha256", key).update("hello").digest().toString("hex"),
|
||||
"88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b",
|
||||
);
|
||||
},
|
||||
});
|
|
@ -15,10 +15,10 @@ import type {
|
|||
Encoding,
|
||||
} from "ext:deno_node/internal/crypto/types.ts";
|
||||
import {
|
||||
getKeyMaterial,
|
||||
KeyObject,
|
||||
prepareSecretKey,
|
||||
} from "ext:deno_node/internal/crypto/keys.ts";
|
||||
import { notImplemented } from "ext:deno_node/_utils.ts";
|
||||
|
||||
const { ops } = globalThis.__bootstrap.core;
|
||||
|
||||
|
@ -170,12 +170,12 @@ class HmacImpl extends Transform {
|
|||
});
|
||||
// deno-lint-ignore no-this-alias
|
||||
const self = this;
|
||||
if (key instanceof KeyObject) {
|
||||
notImplemented("Hmac: KeyObject key is not implemented");
|
||||
}
|
||||
|
||||
validateString(hmac, "hmac");
|
||||
const u8Key = prepareSecretKey(key, options?.encoding) as Buffer;
|
||||
|
||||
const u8Key = key instanceof KeyObject
|
||||
? getKeyMaterial(key)
|
||||
: prepareSecretKey(key, options?.encoding) as Buffer;
|
||||
|
||||
const alg = hmac.toLowerCase();
|
||||
this.#algorithm = alg;
|
||||
|
|
|
@ -28,6 +28,13 @@ import {
|
|||
isKeyObject as isKeyObject_,
|
||||
kKeyType,
|
||||
} from "ext:deno_node/internal/crypto/_keys.ts";
|
||||
import {
|
||||
validateObject,
|
||||
validateOneOf,
|
||||
} from "ext:deno_node/internal/validators.mjs";
|
||||
import {
|
||||
forgivingBase64UrlEncode as encodeToBase64Url,
|
||||
} from "ext:deno_web/00_infra.js";
|
||||
|
||||
const getArrayBufferOrView = hideStackFrames(
|
||||
(
|
||||
|
@ -130,6 +137,17 @@ export function isCryptoKey(
|
|||
return isCryptoKey_(obj);
|
||||
}
|
||||
|
||||
function copyBuffer(input: string | Buffer | ArrayBufferView) {
|
||||
if (typeof input === "string") return Buffer.from(input);
|
||||
return (
|
||||
(ArrayBuffer.isView(input)
|
||||
? new Uint8Array(input.buffer, input.byteOffset, input.byteLength)
|
||||
: new Uint8Array(input)).slice()
|
||||
);
|
||||
}
|
||||
|
||||
const KEY_STORE = new WeakMap();
|
||||
|
||||
export class KeyObject {
|
||||
[kKeyType]: KeyObjectType;
|
||||
[kHandle]: unknown;
|
||||
|
@ -139,18 +157,8 @@ export class KeyObject {
|
|||
throw new ERR_INVALID_ARG_VALUE("type", type);
|
||||
}
|
||||
|
||||
if (typeof handle !== "object") {
|
||||
throw new ERR_INVALID_ARG_TYPE("handle", "object", handle);
|
||||
}
|
||||
|
||||
this[kKeyType] = type;
|
||||
|
||||
Object.defineProperty(this, kHandle, {
|
||||
value: handle,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
this[kHandle] = handle;
|
||||
}
|
||||
|
||||
get type(): KeyObjectType {
|
||||
|
@ -239,7 +247,7 @@ function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) {
|
|||
}
|
||||
|
||||
export function prepareSecretKey(
|
||||
key: string | ArrayBuffer | KeyObject,
|
||||
key: string | ArrayBufferView | ArrayBuffer | KeyObject,
|
||||
encoding: string | undefined,
|
||||
bufferOnly = false,
|
||||
) {
|
||||
|
@ -271,16 +279,62 @@ export function prepareSecretKey(
|
|||
return getArrayBufferOrView(key, "key", encoding);
|
||||
}
|
||||
|
||||
class SecretKeyObject extends KeyObject {
|
||||
constructor(handle: unknown) {
|
||||
super("secret", handle);
|
||||
}
|
||||
|
||||
get symmetricKeySize() {
|
||||
return KEY_STORE.get(this[kHandle]).byteLength;
|
||||
}
|
||||
|
||||
get asymmetricKeyType() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export(): Buffer;
|
||||
export(options?: JwkKeyExportOptions): JsonWebKey {
|
||||
const key = KEY_STORE.get(this[kHandle]);
|
||||
if (options !== undefined) {
|
||||
validateObject(options, "options");
|
||||
validateOneOf(
|
||||
options.format,
|
||||
"options.format",
|
||||
[undefined, "buffer", "jwk"],
|
||||
);
|
||||
if (options.format === "jwk") {
|
||||
return {
|
||||
kty: "oct",
|
||||
k: encodeToBase64Url(key),
|
||||
};
|
||||
}
|
||||
}
|
||||
return key.slice();
|
||||
}
|
||||
}
|
||||
|
||||
function setOwnedKey(key: Uint8Array): unknown {
|
||||
const handle = {};
|
||||
KEY_STORE.set(handle, key);
|
||||
return handle;
|
||||
}
|
||||
|
||||
export function getKeyMaterial(key: KeyObject): Uint8Array {
|
||||
return KEY_STORE.get(key[kHandle]);
|
||||
}
|
||||
|
||||
export function createSecretKey(key: ArrayBufferView): KeyObject;
|
||||
export function createSecretKey(
|
||||
key: string,
|
||||
encoding: string,
|
||||
): KeyObject;
|
||||
export function createSecretKey(
|
||||
_key: string | ArrayBufferView,
|
||||
_encoding?: string,
|
||||
key: string | ArrayBufferView,
|
||||
encoding?: string,
|
||||
): KeyObject {
|
||||
notImplemented("crypto.createSecretKey");
|
||||
key = prepareSecretKey(key, encoding, true);
|
||||
const handle = setOwnedKey(copyBuffer(key));
|
||||
return new SecretKeyObject(handle);
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
Loading…
Reference in a new issue