summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-03-24 19:43:26 +0530
committerGitHub <noreply@github.com>2023-03-24 14:13:26 +0000
commitd740a9e43dad5b3824c3ff2f4aa66cc57a1db185 (patch)
tree2511b522f07ef949a82d3e2d39a0263a49dad9df
parent3d75fb2be758772ab9d538ef40243e8317878e84 (diff)
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
-rw-r--r--cli/tests/unit_node/crypto_key.ts47
-rw-r--r--ext/node/polyfills/internal/crypto/hash.ts10
-rw-r--r--ext/node/polyfills/internal/crypto/keys.ts84
3 files changed, 121 insertions, 20 deletions
diff --git a/cli/tests/unit_node/crypto_key.ts b/cli/tests/unit_node/crypto_key.ts
new file mode 100644
index 000000000..d1a33db9e
--- /dev/null
+++ b/cli/tests/unit_node/crypto_key.ts
@@ -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",
+ );
+ },
+});
diff --git a/ext/node/polyfills/internal/crypto/hash.ts b/ext/node/polyfills/internal/crypto/hash.ts
index d81844f30..63c92190c 100644
--- a/ext/node/polyfills/internal/crypto/hash.ts
+++ b/ext/node/polyfills/internal/crypto/hash.ts
@@ -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;
diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts
index 0348ee0e4..eb7990b06 100644
--- a/ext/node/polyfills/internal/crypto/keys.ts
+++ b/ext/node/polyfills/internal/crypto/keys.ts
@@ -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 {