1
0
Fork 0
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:
Divy Srivastava 2023-03-24 19:43:26 +05:30 committed by Matt Mastracci
parent dc77002b99
commit 4f7b778700
3 changed files with 121 additions and 20 deletions

View 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",
);
},
});

View file

@ -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;

View file

@ -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 {