summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/internal/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/polyfills/internal/crypto')
-rw-r--r--ext/node/polyfills/internal/crypto/_keys.ts10
-rw-r--r--ext/node/polyfills/internal/crypto/_randomFill.mjs33
-rw-r--r--ext/node/polyfills/internal/crypto/cipher.ts4
-rw-r--r--ext/node/polyfills/internal/crypto/diffiehellman.ts4
-rw-r--r--ext/node/polyfills/internal/crypto/hash.ts25
-rw-r--r--ext/node/polyfills/internal/crypto/hkdf.ts10
-rw-r--r--ext/node/polyfills/internal/crypto/keygen.ts117
-rw-r--r--ext/node/polyfills/internal/crypto/keys.ts558
-rw-r--r--ext/node/polyfills/internal/crypto/sig.ts63
-rw-r--r--ext/node/polyfills/internal/crypto/types.ts3
10 files changed, 579 insertions, 248 deletions
diff --git a/ext/node/polyfills/internal/crypto/_keys.ts b/ext/node/polyfills/internal/crypto/_keys.ts
index 9da91f022..e79986245 100644
--- a/ext/node/polyfills/internal/crypto/_keys.ts
+++ b/ext/node/polyfills/internal/crypto/_keys.ts
@@ -1,19 +1,25 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// This file is here because to break a circular dependency between streams and
+// crypto.
+
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { kKeyObject } from "ext:deno_node/internal/crypto/constants.ts";
+import type { KeyObject } from "ext:deno_node/internal/crypto/keys.ts";
export const kKeyType = Symbol("kKeyType");
-export function isKeyObject(obj: unknown): boolean {
+export function isKeyObject(obj: unknown): obj is KeyObject {
return (
obj != null && (obj as Record<symbol, unknown>)[kKeyType] !== undefined
);
}
-export function isCryptoKey(obj: unknown): boolean {
+export function isCryptoKey(
+ obj: unknown,
+): obj is CryptoKey {
return (
obj != null && (obj as Record<symbol, unknown>)[kKeyObject] !== undefined
);
diff --git a/ext/node/polyfills/internal/crypto/_randomFill.mjs b/ext/node/polyfills/internal/crypto/_randomFill.mjs
index e53918b39..808ab4565 100644
--- a/ext/node/polyfills/internal/crypto/_randomFill.mjs
+++ b/ext/node/polyfills/internal/crypto/_randomFill.mjs
@@ -3,14 +3,9 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
-import {
- op_node_generate_secret,
- op_node_generate_secret_async,
-} from "ext:core/ops";
-
-import {
- MAX_SIZE as kMaxUint32,
-} from "ext:deno_node/internal/crypto/_randomBytes.ts";
+import { op_node_fill_random, op_node_fill_random_async } from "ext:core/ops";
+
+import { MAX_SIZE as kMaxUint32 } from "ext:deno_node/internal/crypto/_randomBytes.ts";
import { Buffer } from "node:buffer";
import { isAnyArrayBuffer, isArrayBufferView } from "node:util/types";
import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
@@ -37,12 +32,7 @@ function assertSize(size, offset, length) {
}
}
-export default function randomFill(
- buf,
- offset,
- size,
- cb,
-) {
+export default function randomFill(buf, offset, size, cb) {
if (typeof offset === "function") {
cb = offset;
offset = 0;
@@ -55,14 +45,11 @@ export default function randomFill(
assertOffset(offset, buf.length);
assertSize(size, offset, buf.length);
- op_node_generate_secret_async(Math.floor(size))
- .then(
- (randomData) => {
- const randomBuf = Buffer.from(randomData.buffer);
- randomBuf.copy(buf, offset, 0, size);
- cb(null, buf);
- },
- );
+ op_node_fill_random_async(Math.floor(size)).then((randomData) => {
+ const randomBuf = Buffer.from(randomData.buffer);
+ randomBuf.copy(buf, offset, 0, size);
+ cb(null, buf);
+ });
}
export function randomFillSync(buf, offset = 0, size) {
@@ -89,7 +76,7 @@ export function randomFillSync(buf, offset = 0, size) {
const bytes = isAnyArrayBuffer(buf)
? new Uint8Array(buf, offset, size)
: new Uint8Array(buf.buffer, buf.byteOffset + offset, size);
- op_node_generate_secret(bytes);
+ op_node_fill_random(bytes);
return buf;
}
diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts
index f8a46896d..d83d4fa8f 100644
--- a/ext/node/polyfills/internal/crypto/cipher.ts
+++ b/ext/node/polyfills/internal/crypto/cipher.ts
@@ -41,7 +41,9 @@ import {
isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts";
-export function isStringOrBuffer(val) {
+export function isStringOrBuffer(
+ val: unknown,
+): val is string | Buffer | ArrayBuffer | ArrayBufferView {
return typeof val === "string" ||
isArrayBufferView(val) ||
isAnyArrayBuffer(val) ||
diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts
index 6058433ba..16a1f2498 100644
--- a/ext/node/polyfills/internal/crypto/diffiehellman.ts
+++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts
@@ -6,7 +6,7 @@
import {
op_node_dh_compute_secret,
- op_node_dh_generate2,
+ op_node_dh_keys_generate_and_export,
op_node_ecdh_compute_public_key,
op_node_ecdh_compute_secret,
op_node_ecdh_encode_pubkey,
@@ -198,7 +198,7 @@ export class DiffieHellman {
generateKeys(encoding: BinaryToTextEncoding): string;
generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string {
const generator = this.#checkGenerator();
- const [privateKey, publicKey] = op_node_dh_generate2(
+ const [privateKey, publicKey] = op_node_dh_keys_generate_and_export(
this.#prime,
this.#primeLength ?? 0,
generator,
diff --git a/ext/node/polyfills/internal/crypto/hash.ts b/ext/node/polyfills/internal/crypto/hash.ts
index 2e040be25..c42ca3989 100644
--- a/ext/node/polyfills/internal/crypto/hash.ts
+++ b/ext/node/polyfills/internal/crypto/hash.ts
@@ -6,6 +6,7 @@
import {
op_node_create_hash,
+ op_node_export_secret_key,
op_node_get_hashes,
op_node_hash_clone,
op_node_hash_digest,
@@ -32,7 +33,6 @@ import type {
Encoding,
} from "ext:deno_node/internal/crypto/types.ts";
import {
- getKeyMaterial,
KeyObject,
prepareSecretKey,
} from "ext:deno_node/internal/crypto/keys.ts";
@@ -46,7 +46,10 @@ import {
getDefaultEncoding,
toBuf,
} from "ext:deno_node/internal/crypto/util.ts";
-import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
+import {
+ isAnyArrayBuffer,
+ isArrayBufferView,
+} from "ext:deno_node/internal/util/types.ts";
const { ReflectApply, ObjectSetPrototypeOf } = primordials;
@@ -217,22 +220,28 @@ class HmacImpl extends Transform {
validateString(hmac, "hmac");
- const u8Key = key instanceof KeyObject
- ? getKeyMaterial(key)
- : prepareSecretKey(key, options?.encoding) as Buffer;
+ key = prepareSecretKey(key, options?.encoding);
+ let keyData;
+ if (isArrayBufferView(key)) {
+ keyData = key;
+ } else if (isAnyArrayBuffer(key)) {
+ keyData = new Uint8Array(key);
+ } else {
+ keyData = op_node_export_secret_key(key);
+ }
const alg = hmac.toLowerCase();
this.#algorithm = alg;
const blockSize = (alg === "sha512" || alg === "sha384") ? 128 : 64;
- const keySize = u8Key.length;
+ const keySize = keyData.length;
let bufKey: Buffer;
if (keySize > blockSize) {
const hash = new Hash(alg, options);
- bufKey = hash.update(u8Key).digest() as Buffer;
+ bufKey = hash.update(keyData).digest() as Buffer;
} else {
- bufKey = Buffer.concat([u8Key, this.#ZEROES], blockSize);
+ bufKey = Buffer.concat([keyData, this.#ZEROES], blockSize);
}
this.#ipad = Buffer.allocUnsafe(blockSize);
diff --git a/ext/node/polyfills/internal/crypto/hkdf.ts b/ext/node/polyfills/internal/crypto/hkdf.ts
index cca40a3c6..cb1dbee46 100644
--- a/ext/node/polyfills/internal/crypto/hkdf.ts
+++ b/ext/node/polyfills/internal/crypto/hkdf.ts
@@ -18,13 +18,12 @@ import {
hideStackFrames,
} from "ext:deno_node/internal/errors.ts";
import {
+ kHandle,
toBuf,
validateByteSource,
} from "ext:deno_node/internal/crypto/util.ts";
import {
createSecretKey,
- getKeyMaterial,
- isKeyObject,
KeyObject,
} from "ext:deno_node/internal/crypto/keys.ts";
import type { BinaryLike } from "ext:deno_node/internal/crypto/types.ts";
@@ -33,10 +32,11 @@ import {
isAnyArrayBuffer,
isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts";
+import { isKeyObject } from "ext:deno_node/internal/crypto/_keys.ts";
const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
validateString(hash, "digest");
- key = getKeyMaterial(prepareKey(key));
+ key = prepareKey(key);
validateByteSource(salt, "salt");
validateByteSource(info, "info");
@@ -111,7 +111,7 @@ export function hkdf(
hash = hash.toLowerCase();
- op_node_hkdf_async(hash, key, salt, info, length)
+ op_node_hkdf_async(hash, key[kHandle], salt, info, length)
.then((okm) => callback(null, okm.buffer))
.catch((err) => callback(new ERR_CRYPTO_INVALID_DIGEST(err), undefined));
}
@@ -135,7 +135,7 @@ export function hkdfSync(
const okm = new Uint8Array(length);
try {
- op_node_hkdf(hash, key, salt, info, okm);
+ op_node_hkdf(hash, key[kHandle], salt, info, okm);
} catch (e) {
throw new ERR_CRYPTO_INVALID_DIGEST(e);
}
diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts
index dd5d5ad7e..4e2543cd9 100644
--- a/ext/node/polyfills/internal/crypto/keygen.ts
+++ b/ext/node/polyfills/internal/crypto/keygen.ts
@@ -10,7 +10,6 @@ import {
PrivateKeyObject,
PublicKeyObject,
SecretKeyObject,
- setOwnedKey,
} from "ext:deno_node/internal/crypto/keys.ts";
import { notImplemented } from "ext:deno_node/_utils.ts";
import {
@@ -32,22 +31,26 @@ import { Buffer } from "node:buffer";
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
import {
- op_node_dh_generate,
- op_node_dh_generate_async,
- op_node_dh_generate_group,
- op_node_dh_generate_group_async,
- op_node_dsa_generate,
- op_node_dsa_generate_async,
- op_node_ec_generate,
- op_node_ec_generate_async,
- op_node_ed25519_generate,
- op_node_ed25519_generate_async,
- op_node_generate_rsa,
- op_node_generate_rsa_async,
- op_node_generate_secret,
- op_node_generate_secret_async,
- op_node_x25519_generate,
- op_node_x25519_generate_async,
+ op_node_generate_dh_group_key,
+ op_node_generate_dh_group_key_async,
+ op_node_generate_dh_key,
+ op_node_generate_dh_key_async,
+ op_node_generate_dsa_key,
+ op_node_generate_dsa_key_async,
+ op_node_generate_ec_key,
+ op_node_generate_ec_key_async,
+ op_node_generate_ed25519_key,
+ op_node_generate_ed25519_key_async,
+ op_node_generate_rsa_key,
+ op_node_generate_rsa_key_async,
+ op_node_generate_rsa_pss_key,
+ op_node_generate_rsa_pss_key_async,
+ op_node_generate_secret_key,
+ op_node_generate_secret_key_async,
+ op_node_generate_x25519_key,
+ op_node_generate_x25519_key_async,
+ op_node_get_private_key_from_pair,
+ op_node_get_public_key_from_pair,
} from "ext:core/ops";
function validateGenerateKey(
@@ -82,10 +85,11 @@ export function generateKeySync(
validateGenerateKey(type, options);
const { length } = options;
- const key = new Uint8Array(Math.floor(length / 8));
- op_node_generate_secret(key);
+ const len = Math.floor(length / 8);
- return new SecretKeyObject(setOwnedKey(key));
+ const handle = op_node_generate_secret_key(len);
+
+ return new SecretKeyObject(handle);
}
export function generateKey(
@@ -99,11 +103,11 @@ export function generateKey(
validateFunction(callback, "callback");
const { length } = options;
- op_node_generate_secret_async(Math.floor(length / 8)).then(
- (key) => {
- callback(null, new SecretKeyObject(setOwnedKey(key)));
- },
- );
+ const len = Math.floor(length / 8);
+
+ op_node_generate_secret_key_async(len).then((handle) => {
+ callback(null, new SecretKeyObject(handle));
+ });
}
export interface BasePrivateKeyEncodingOptions<T extends KeyFormat> {
@@ -565,9 +569,12 @@ export function generateKeyPair(
privateKey: any,
) => void,
) {
- createJob(kAsync, type, options).then(([privateKey, publicKey]) => {
- privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type });
- publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type });
+ createJob(kAsync, type, options).then((pair) => {
+ const privateKeyHandle = op_node_get_private_key_from_pair(pair);
+ const publicKeyHandle = op_node_get_public_key_from_pair(pair);
+
+ const privateKey = new PrivateKeyObject(privateKeyHandle);
+ const publicKey = new PublicKeyObject(publicKeyHandle);
if (typeof options === "object" && options !== null) {
const { publicKeyEncoding, privateKeyEncoding } = options as any;
@@ -766,10 +773,13 @@ export function generateKeyPairSync(
):
| KeyPairKeyObjectResult
| KeyPairSyncResult<string | Buffer, string | Buffer> {
- let [privateKey, publicKey] = createJob(kSync, type, options);
+ const pair = createJob(kSync, type, options);
+
+ const privateKeyHandle = op_node_get_private_key_from_pair(pair);
+ const publicKeyHandle = op_node_get_public_key_from_pair(pair);
- privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type });
- publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type });
+ let privateKey = new PrivateKeyObject(privateKeyHandle);
+ let publicKey = new PublicKeyObject(publicKeyHandle);
if (typeof options === "object" && options !== null) {
const { publicKeyEncoding, privateKeyEncoding } = options as any;
@@ -812,12 +822,12 @@ function createJob(mode, type, options) {
if (type === "rsa") {
if (mode === kSync) {
- return op_node_generate_rsa(
+ return op_node_generate_rsa_key(
modulusLength,
publicExponent,
);
} else {
- return op_node_generate_rsa_async(
+ return op_node_generate_rsa_key_async(
modulusLength,
publicExponent,
);
@@ -867,14 +877,20 @@ function createJob(mode, type, options) {
}
if (mode === kSync) {
- return op_node_generate_rsa(
+ return op_node_generate_rsa_pss_key(
modulusLength,
publicExponent,
+ hashAlgorithm,
+ mgf1HashAlgorithm ?? mgf1Hash,
+ saltLength,
);
} else {
- return op_node_generate_rsa_async(
+ return op_node_generate_rsa_pss_key_async(
modulusLength,
publicExponent,
+ hashAlgorithm,
+ mgf1HashAlgorithm ?? mgf1Hash,
+ saltLength,
);
}
}
@@ -891,12 +907,13 @@ function createJob(mode, type, options) {
}
if (mode === kSync) {
- return op_node_dsa_generate(modulusLength, divisorLength);
+ return op_node_generate_dsa_key(modulusLength, divisorLength);
+ } else {
+ return op_node_generate_dsa_key_async(
+ modulusLength,
+ divisorLength,
+ );
}
- return op_node_dsa_generate_async(
- modulusLength,
- divisorLength,
- );
}
case "ec": {
validateObject(options, "options");
@@ -913,22 +930,22 @@ function createJob(mode, type, options) {
}
if (mode === kSync) {
- return op_node_ec_generate(namedCurve);
+ return op_node_generate_ec_key(namedCurve);
} else {
- return op_node_ec_generate_async(namedCurve);
+ return op_node_generate_ec_key_async(namedCurve);
}
}
case "ed25519": {
if (mode === kSync) {
- return op_node_ed25519_generate();
+ return op_node_generate_ed25519_key();
}
- return op_node_ed25519_generate_async();
+ return op_node_generate_ed25519_key_async();
}
case "x25519": {
if (mode === kSync) {
- return op_node_x25519_generate();
+ return op_node_generate_x25519_key();
}
- return op_node_x25519_generate_async();
+ return op_node_generate_x25519_key_async();
}
case "ed448":
case "x448": {
@@ -952,9 +969,9 @@ function createJob(mode, type, options) {
validateString(group, "options.group");
if (mode === kSync) {
- return op_node_dh_generate_group(group);
+ return op_node_generate_dh_group_key(group);
} else {
- return op_node_dh_generate_group_async(group);
+ return op_node_generate_dh_group_key_async(group);
}
}
@@ -979,9 +996,9 @@ function createJob(mode, type, options) {
const g = generator == null ? 2 : generator;
if (mode === kSync) {
- return op_node_dh_generate(prime, primeLength ?? 0, g);
+ return op_node_generate_dh_key(prime, primeLength ?? 0, g);
} else {
- return op_node_dh_generate_async(
+ return op_node_generate_dh_key_async(
prime,
primeLength ?? 0,
g,
diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts
index 26cd86b44..31d674e67 100644
--- a/ext/node/polyfills/internal/crypto/keys.ts
+++ b/ext/node/polyfills/internal/crypto/keys.ts
@@ -14,16 +14,24 @@ const {
import {
op_node_create_private_key,
op_node_create_public_key,
- op_node_export_rsa_public_pem,
- op_node_export_rsa_spki_der,
+ op_node_create_secret_key,
+ op_node_derive_public_key_from_private_key,
+ op_node_export_private_key_der,
+ op_node_export_private_key_pem,
+ op_node_export_public_key_der,
+ op_node_export_public_key_pem,
+ op_node_export_secret_key,
+ op_node_export_secret_key_b64url,
+ op_node_get_asymmetric_key_details,
+ op_node_get_asymmetric_key_type,
+ op_node_get_symmetric_key_size,
+ op_node_key_type,
} from "ext:core/ops";
-import {
- kHandle,
- kKeyObject,
-} from "ext:deno_node/internal/crypto/constants.ts";
+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_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
@@ -41,23 +49,21 @@ import {
} from "ext:deno_node/internal/util/types.ts";
import { hideStackFrames } from "ext:deno_node/internal/errors.ts";
import {
- isCryptoKey as isCryptoKey_,
- isKeyObject as isKeyObject_,
+ isCryptoKey,
+ 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";
+import { BufferEncoding } from "ext:deno_node/_global.d.ts";
export const getArrayBufferOrView = hideStackFrames(
(
- buffer,
- name,
- encoding,
+ buffer: ArrayBufferView | ArrayBuffer | string | Buffer,
+ name: string,
+ encoding?: BufferEncoding | "buffer",
):
| ArrayBuffer
| SharedArrayBuffer
@@ -144,32 +150,30 @@ export interface JwkKeyExportOptions {
format: "jwk";
}
-export function isKeyObject(obj: unknown): obj is KeyObject {
- return isKeyObject_(obj);
+export enum KeyHandleContext {
+ kConsumePublic = 0,
+ kConsumePrivate = 1,
+ kCreatePublic = 2,
+ kCreatePrivate = 3,
}
-export function isCryptoKey(
- obj: unknown,
-): obj is { type: string; [kKeyObject]: KeyObject } {
- return isCryptoKey_(obj);
-}
+export const kConsumePublic = KeyHandleContext.kConsumePublic;
+export const kConsumePrivate = KeyHandleContext.kConsumePrivate;
+export const kCreatePublic = KeyHandleContext.kCreatePublic;
+export const kCreatePrivate = KeyHandleContext.kCreatePrivate;
-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()
- );
+function isJwk(obj: unknown): obj is { kty: unknown } {
+ // @ts-ignore this is fine
+ return typeof obj === "object" && obj != null && obj.kty !== undefined;
}
-const KEY_STORE = new WeakMap();
+export type KeyObjectHandle = { ___keyObjectHandle: true };
export class KeyObject {
[kKeyType]: KeyObjectType;
- [kHandle]: unknown;
+ [kHandle]: KeyObjectHandle;
- constructor(type: KeyObjectType, handle: unknown) {
+ constructor(type: KeyObjectType, handle: KeyObjectHandle) {
if (type !== "secret" && type !== "public" && type !== "private") {
throw new ERR_INVALID_ARG_VALUE("type", type);
}
@@ -184,7 +188,6 @@ export class KeyObject {
get symmetricKeySize(): number | undefined {
notImplemented("crypto.KeyObject.prototype.symmetricKeySize");
-
return undefined;
}
@@ -192,7 +195,6 @@ export class KeyObject {
if (!isCryptoKey(key)) {
throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key);
}
-
notImplemented("crypto.KeyObject.prototype.from");
}
@@ -212,12 +214,13 @@ export class KeyObject {
export(options?: KeyExportOptions<"der">): Buffer;
export(options?: JwkKeyExportOptions): JsonWebKey;
export(_options?: unknown): string | Buffer | JsonWebKey {
- notImplemented("crypto.KeyObject.prototype.asymmetricKeyType");
+ notImplemented("crypto.KeyObject.prototype.export");
}
}
ObjectDefineProperties(KeyObject.prototype, {
[SymbolToStringTag]: {
+ // @ts-expect-error __proto__ is magic
__proto__: null,
configurable: true,
value: "KeyObject",
@@ -229,48 +232,356 @@ export interface JsonWebKeyInput {
format: "jwk";
}
-export function prepareAsymmetricKey(key) {
- if (isStringOrBuffer(key)) {
- return { format: "pem", data: getArrayBufferOrView(key, "key") };
- } else if (isKeyObject(key)) {
+function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) {
+ if (ctx === kCreatePrivate) {
+ throw new ERR_INVALID_ARG_TYPE(
+ "key",
+ ["string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"],
+ key,
+ );
+ }
+
+ if (key.type !== "private") {
+ if (ctx === kConsumePrivate || ctx === kCreatePublic) {
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "private");
+ }
+ if (key.type !== "public") {
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(
+ key.type,
+ "private or public",
+ );
+ }
+ }
+
+ return key[kHandle];
+}
+
+export function prepareAsymmetricKey(
+ key:
+ | string
+ | ArrayBuffer
+ | Buffer
+ | ArrayBufferView
+ | KeyObject
+ | CryptoKey
+ | PrivateKeyInput
+ | PublicKeyInput
+ | JsonWebKeyInput,
+ ctx: KeyHandleContext,
+):
+ | { handle: KeyObjectHandle; format?: "jwk" }
+ | {
+ data: ArrayBuffer | ArrayBufferView;
+ format: KeyFormat;
+ type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined;
+ passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined;
+ } {
+ if (isKeyObject(key)) {
+ // Best case: A key object, as simple as that.
+ return {
+ // @ts-ignore __proto__ is magic
+ __proto__: null,
+ handle: getKeyObjectHandle(key, ctx),
+ };
+ } else if (isCryptoKey(key)) {
+ notImplemented("using CryptoKey as input");
+ } else if (isStringOrBuffer(key)) {
+ // Expect PEM by default, mostly for backward compatibility.
return {
- // Assumes that asymmetric keys are stored as PEM.
+ // @ts-ignore __proto__ is magic
+ __proto__: null,
format: "pem",
- data: getKeyMaterial(key),
+ data: getArrayBufferOrView(key, "key"),
};
- } else if (typeof key == "object") {
- const { key: data, encoding, format, type } = key;
+ } else if (typeof key === "object") {
+ const { key: data, format } = key;
+ // The 'key' property can be a KeyObject as well to allow specifying
+ // additional options such as padding along with the key.
+ if (isKeyObject(data)) {
+ return {
+ // @ts-ignore __proto__ is magic
+ __proto__: null,
+ handle: getKeyObjectHandle(data, ctx),
+ };
+ } else if (isCryptoKey(data)) {
+ notImplemented("using CryptoKey as input");
+ } else if (isJwk(data) && format === "jwk") {
+ notImplemented("using JWK as input");
+ }
+ // Either PEM or DER using PKCS#1 or SPKI.
if (!isStringOrBuffer(data)) {
- throw new TypeError("Invalid key type");
+ throw new ERR_INVALID_ARG_TYPE(
+ "key.key",
+ getKeyTypes(ctx !== kCreatePrivate),
+ data,
+ );
}
+ const isPublic = (ctx === kConsumePrivate || ctx === kCreatePrivate)
+ ? false
+ : undefined;
return {
- data: getArrayBufferOrView(data, "key", encoding),
- format: format ?? "pem",
- encoding,
- type,
+ data: getArrayBufferOrView(
+ data,
+ "key",
+ (key as PrivateKeyInput | PublicKeyInput).encoding,
+ ),
+ ...parseKeyEncoding(key, undefined, isPublic),
};
}
+ throw new ERR_INVALID_ARG_TYPE(
+ "key",
+ getKeyTypes(ctx !== kCreatePrivate),
+ key,
+ );
+}
+
+function parseKeyEncoding(
+ enc: {
+ cipher?: string;
+ passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView;
+ encoding?: BufferEncoding | "buffer";
+ format?: string;
+ type?: string;
+ },
+ keyType: string | undefined,
+ isPublic: boolean | undefined,
+ objName?: string,
+): {
+ format: KeyFormat;
+ type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined;
+ passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined;
+ cipher: string | undefined;
+} {
+ if (enc === null || typeof enc !== "object") {
+ throw new ERR_INVALID_ARG_TYPE("options", "object", enc);
+ }
+
+ const isInput = keyType === undefined;
+
+ const {
+ format,
+ type,
+ } = parseKeyFormatAndType(enc, keyType, isPublic, objName);
+
+ let cipher, passphrase, encoding;
+ if (isPublic !== true) {
+ ({ cipher, passphrase, encoding } = enc);
+
+ if (!isInput) {
+ if (cipher != null) {
+ if (typeof cipher !== "string") {
+ throw new ERR_INVALID_ARG_VALUE(option("cipher", objName), cipher);
+ }
+ if (
+ format === "der" &&
+ (type === "pkcs1" || type === "sec1")
+ ) {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ type,
+ "does not support encryption",
+ );
+ }
+ } else if (passphrase !== undefined) {
+ throw new ERR_INVALID_ARG_VALUE(option("cipher", objName), cipher);
+ }
+ }
- throw new TypeError("Invalid key type");
+ if (
+ (isInput && passphrase !== undefined &&
+ !isStringOrBuffer(passphrase)) ||
+ (!isInput && cipher != null && !isStringOrBuffer(passphrase))
+ ) {
+ throw new ERR_INVALID_ARG_VALUE(
+ option("passphrase", objName),
+ passphrase,
+ );
+ }
+ }
+
+ if (passphrase !== undefined) {
+ passphrase = getArrayBufferOrView(passphrase, "key.passphrase", encoding);
+ }
+
+ return {
+ // @ts-ignore __proto__ is magic
+ __proto__: null,
+ format,
+ type,
+ cipher,
+ passphrase,
+ };
+}
+
+function option(name: string, objName?: string) {
+ return objName === undefined
+ ? `options.${name}`
+ : `options.${objName}.${name}`;
+}
+
+function parseKeyFormatAndType(
+ enc: { format?: string; type?: string },
+ keyType: string | undefined,
+ isPublic: boolean | undefined,
+ objName?: string,
+): {
+ format: KeyFormat;
+ type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined;
+} {
+ const { format: formatStr, type: typeStr } = enc;
+
+ const isInput = keyType === undefined;
+ const format = parseKeyFormat(
+ formatStr,
+ isInput ? "pem" : undefined,
+ option("format", objName),
+ );
+
+ const type = parseKeyType(
+ typeStr,
+ !isInput || format === "der",
+ keyType,
+ isPublic,
+ option("type", objName),
+ );
+
+ return {
+ // @ts-ignore __proto__ is magic
+ __proto__: null,
+ format,
+ type,
+ };
+}
+
+function parseKeyFormat(
+ formatStr: string | undefined,
+ defaultFormat: KeyFormat | undefined,
+ optionName: string,
+): KeyFormat {
+ if (formatStr === undefined && defaultFormat !== undefined) {
+ return defaultFormat;
+ } else if (formatStr === "pem") {
+ return "pem";
+ } else if (formatStr === "der") {
+ return "der";
+ }
+ throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
+}
+
+function parseKeyType(
+ typeStr: string | undefined,
+ required: boolean,
+ keyType: string | undefined,
+ isPublic: boolean | undefined,
+ optionName: string,
+): "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined {
+ if (typeStr === undefined && !required) {
+ return undefined;
+ } else if (typeStr === "pkcs1") {
+ if (keyType !== undefined && keyType !== "rsa") {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ typeStr,
+ "can only be used for RSA keys",
+ );
+ }
+ return "pkcs1";
+ } else if (typeStr === "spki" && isPublic !== false) {
+ return "spki";
+ } else if (typeStr === "pkcs8" && isPublic !== true) {
+ return "pkcs8";
+ } else if (typeStr === "sec1" && isPublic !== true) {
+ if (keyType !== undefined && keyType !== "ec") {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ typeStr,
+ "can only be used for EC keys",
+ );
+ }
+ return "sec1";
+ }
+ throw new ERR_INVALID_ARG_VALUE(optionName, typeStr);
+}
+
+// Parses the public key encoding based on an object. keyType must be undefined
+// when this is used to parse an input encoding and must be a valid key type if
+// used to parse an output encoding.
+function parsePublicKeyEncoding(
+ enc: {
+ cipher?: string;
+ passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView;
+ encoding?: BufferEncoding | "buffer";
+ format?: string;
+ type?: string;
+ },
+ keyType: string | undefined,
+ objName?: string,
+) {
+ return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
+}
+
+// Parses the private key encoding based on an object. keyType must be undefined
+// when this is used to parse an input encoding and must be a valid key type if
+// used to parse an output encoding.
+function parsePrivateKeyEncoding(
+ enc: {
+ cipher?: string;
+ passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView;
+ encoding?: BufferEncoding | "buffer";
+ format?: string;
+ type?: string;
+ },
+ keyType: string | undefined,
+ objName?: string,
+) {
+ return parseKeyEncoding(enc, keyType, false, objName);
}
export function createPrivateKey(
key: PrivateKeyInput | string | Buffer | JsonWebKeyInput,
): PrivateKeyObject {
- const { data, format, type } = prepareAsymmetricKey(key);
- const details = op_node_create_private_key(data, format, type);
- const handle = setOwnedKey(copyBuffer(data));
- return new PrivateKeyObject(handle, details);
+ const res = prepareAsymmetricKey(key, kCreatePrivate);
+ if ("handle" in res) {
+ const type = op_node_key_type(res.handle);
+ if (type === "private") {
+ return new PrivateKeyObject(res.handle);
+ } else {
+ throw new TypeError(`Can not create private key from ${type} key`);
+ }
+ } else {
+ const handle = op_node_create_private_key(
+ res.data,
+ res.format,
+ res.type ?? "",
+ res.passphrase,
+ );
+ return new PrivateKeyObject(handle);
+ }
}
export function createPublicKey(
key: PublicKeyInput | string | Buffer | JsonWebKeyInput,
): PublicKeyObject {
- const { data, format, type } = prepareAsymmetricKey(key);
- const details = op_node_create_public_key(data, format, type);
- const handle = setOwnedKey(copyBuffer(data));
- return new PublicKeyObject(handle, details);
+ const res = prepareAsymmetricKey(
+ key,
+ kCreatePublic,
+ );
+ if ("handle" in res) {
+ const type = op_node_key_type(res.handle);
+ if (type === "private") {
+ const handle = op_node_derive_public_key_from_private_key(res.handle);
+ return new PublicKeyObject(handle);
+ } else if (type === "public") {
+ return new PublicKeyObject(res.handle);
+ } else {
+ throw new TypeError(`Can not create private key from ${type} key`);
+ }
+ } else {
+ const handle = op_node_create_public_key(
+ res.data,
+ res.format,
+ res.type ?? "",
+ );
+ return new PublicKeyObject(handle);
+ }
}
function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) {
@@ -292,10 +603,10 @@ function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) {
}
export function prepareSecretKey(
- key: string | ArrayBufferView | ArrayBuffer | KeyObject,
+ key: string | ArrayBufferView | ArrayBuffer | KeyObject | CryptoKey,
encoding: string | undefined,
bufferOnly = false,
-) {
+): Buffer | ArrayBuffer | ArrayBufferView | KeyObjectHandle {
if (!bufferOnly) {
if (isKeyObject(key)) {
if (key.type !== "secret") {
@@ -303,10 +614,7 @@ export function prepareSecretKey(
}
return key[kHandle];
} else if (isCryptoKey(key)) {
- if (key.type !== "secret") {
- throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret");
- }
- return key[kKeyObject][kHandle];
+ notImplemented("using CryptoKey as input");
}
}
if (
@@ -325,21 +633,20 @@ export function prepareSecretKey(
}
export class SecretKeyObject extends KeyObject {
- constructor(handle: unknown) {
+ constructor(handle: KeyObjectHandle) {
super("secret", handle);
}
get symmetricKeySize() {
- return KEY_STORE.get(this[kHandle]).byteLength;
+ return op_node_get_symmetric_key_size(this[kHandle]);
}
get asymmetricKeyType() {
return undefined;
}
- export(): Buffer;
- export(options?: JwkKeyExportOptions): JsonWebKey {
- const key = KEY_STORE.get(this[kHandle]);
+ export(options?: { format?: "buffer" | "jwk" }): Buffer | JsonWebKey {
+ let format: "buffer" | "jwk" = "buffer";
if (options !== undefined) {
validateObject(options, "options");
validateOneOf(
@@ -347,111 +654,102 @@ export class SecretKeyObject extends KeyObject {
"options.format",
[undefined, "buffer", "jwk"],
);
- if (options.format === "jwk") {
+ format = options.format ?? "buffer";
+ }
+ switch (format) {
+ case "buffer":
+ return Buffer.from(op_node_export_secret_key(this[kHandle]));
+ case "jwk":
return {
kty: "oct",
- k: encodeToBase64Url(key),
+ k: op_node_export_secret_key_b64url(this[kHandle]),
};
- }
}
- return key.slice();
}
}
-const kAsymmetricKeyType = Symbol("kAsymmetricKeyType");
-const kAsymmetricKeyDetails = Symbol("kAsymmetricKeyDetails");
-
class AsymmetricKeyObject extends KeyObject {
- constructor(type: KeyObjectType, handle: unknown, details: unknown) {
+ constructor(type: KeyObjectType, handle: KeyObjectHandle) {
super(type, handle);
- this[kAsymmetricKeyType] = details.type;
- this[kAsymmetricKeyDetails] = { ...details };
}
get asymmetricKeyType() {
- return this[kAsymmetricKeyType];
+ return op_node_get_asymmetric_key_type(this[kHandle]);
}
get asymmetricKeyDetails() {
- return this[kAsymmetricKeyDetails];
+ return op_node_get_asymmetric_key_details(this[kHandle]);
}
}
export class PrivateKeyObject extends AsymmetricKeyObject {
- constructor(handle: unknown, details: unknown) {
- super("private", handle, details);
+ constructor(handle: KeyObjectHandle) {
+ super("private", handle);
}
- export(_options: unknown) {
- notImplemented("crypto.PrivateKeyObject.prototype.export");
+ export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) {
+ if (options && options.format === "jwk") {
+ notImplemented("jwk private key export not implemented");
+ }
+ const {
+ format,
+ type,
+ } = parsePrivateKeyEncoding(options, this.asymmetricKeyType);
+
+ if (format === "pem") {
+ return op_node_export_private_key_pem(this[kHandle], type);
+ } else {
+ return Buffer.from(op_node_export_private_key_der(this[kHandle], type));
+ }
}
}
export class PublicKeyObject extends AsymmetricKeyObject {
- constructor(handle: unknown, details: unknown) {
- super("public", handle, details);
+ constructor(handle: KeyObjectHandle) {
+ super("public", handle);
}
- export(options: unknown) {
- const key = KEY_STORE.get(this[kHandle]);
- switch (this.asymmetricKeyType) {
- case "rsa":
- case "rsa-pss": {
- switch (options.format) {
- case "pem":
- return op_node_export_rsa_public_pem(key);
- case "der": {
- if (options.type == "pkcs1") {
- return key;
- } else {
- return op_node_export_rsa_spki_der(key);
- }
- }
- default:
- throw new TypeError(`exporting ${options.type} is not implemented`);
- }
- }
- default:
- throw new TypeError(
- `exporting ${this.asymmetricKeyType} is not implemented`,
- );
+ export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) {
+ if (options && options.format === "jwk") {
+ notImplemented("jwk public key export not implemented");
}
- }
-}
-
-export function setOwnedKey(key: Uint8Array): unknown {
- const handle = {};
- KEY_STORE.set(handle, key);
- return handle;
-}
+ const {
+ format,
+ type,
+ } = parsePublicKeyEncoding(options, this.asymmetricKeyType);
-export function getKeyMaterial(key: KeyObject): Uint8Array {
- return KEY_STORE.get(key[kHandle]);
+ if (format === "pem") {
+ return op_node_export_public_key_pem(this[kHandle], type);
+ } else {
+ return Buffer.from(op_node_export_public_key_der(this[kHandle], type));
+ }
+ }
}
-export function createSecretKey(key: ArrayBufferView): KeyObject;
-export function createSecretKey(
- key: string,
- encoding: string,
-): KeyObject;
export function createSecretKey(
- key: string | ArrayBufferView,
+ key: string | ArrayBufferView | ArrayBuffer | KeyObject | CryptoKey,
encoding?: string,
): KeyObject {
- key = prepareSecretKey(key, encoding, true);
- const handle = setOwnedKey(copyBuffer(key));
- return new SecretKeyObject(handle);
+ const preparedKey = prepareSecretKey(key, encoding, true);
+ if (isArrayBufferView(preparedKey) || isAnyArrayBuffer(preparedKey)) {
+ const handle = op_node_create_secret_key(preparedKey);
+ return new SecretKeyObject(handle);
+ } else {
+ const type = op_node_key_type(preparedKey);
+ if (type === "secret") {
+ return new SecretKeyObject(preparedKey);
+ } else {
+ throw new TypeError(`can not create secret key from ${type} key`);
+ }
+ }
}
export default {
createPrivateKey,
createPublicKey,
createSecretKey,
- isKeyObject,
- isCryptoKey,
KeyObject,
prepareSecretKey,
- setOwnedKey,
SecretKeyObject,
PrivateKeyObject,
PublicKeyObject,
diff --git a/ext/node/polyfills/internal/crypto/sig.ts b/ext/node/polyfills/internal/crypto/sig.ts
index 473670d2a..c711c7193 100644
--- a/ext/node/polyfills/internal/crypto/sig.ts
+++ b/ext/node/polyfills/internal/crypto/sig.ts
@@ -4,9 +4,13 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
-import { op_node_sign, op_node_verify } from "ext:core/ops";
+import {
+ op_node_create_private_key,
+ op_node_create_public_key,
+ op_node_sign,
+ op_node_verify,
+} from "ext:core/ops";
-import { notImplemented } from "ext:deno_node/_utils.ts";
import {
validateFunction,
validateString,
@@ -22,12 +26,12 @@ import type {
PublicKeyInput,
} from "ext:deno_node/internal/crypto/types.ts";
import {
+ kConsumePrivate,
+ kConsumePublic,
KeyObject,
prepareAsymmetricKey,
} from "ext:deno_node/internal/crypto/keys.ts";
-import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts";
-import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
-import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
+import { createHash } from "ext:deno_node/internal/crypto/hash.ts";
import { ERR_CRYPTO_SIGN_KEY_REQUIRED } from "ext:deno_node/internal/errors.ts";
export type DSAEncoding = "der" | "ieee-p1363";
@@ -72,16 +76,26 @@ export class SignImpl extends Writable {
}
sign(
- privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput,
+ // deno-lint-ignore no-explicit-any
+ privateKey: any,
encoding?: BinaryToTextEncoding,
): Buffer | string {
- const { data, format, type } = prepareAsymmetricKey(privateKey);
+ const res = prepareAsymmetricKey(privateKey, kConsumePrivate);
+ let handle;
+ if ("handle" in res) {
+ handle = res.handle;
+ } else {
+ handle = op_node_create_private_key(
+ res.data,
+ res.format,
+ res.type ?? "",
+ res.passphrase,
+ );
+ }
const ret = Buffer.from(op_node_sign(
+ handle,
this.hash.digest(),
this.#digestType,
- data!,
- type,
- format,
));
return encoding ? ret.toString(encoding) : ret;
}
@@ -127,32 +141,27 @@ export class VerifyImpl extends Writable {
}
verify(
- publicKey: BinaryLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
+ // deno-lint-ignore no-explicit-any
+ publicKey: any,
signature: BinaryLike,
encoding?: BinaryToTextEncoding,
): boolean {
- let keyData: BinaryLike;
- let keyType: KeyType;
- let keyFormat: KeyFormat;
- if (typeof publicKey === "string" || isArrayBufferView(publicKey)) {
- // if the key is BinaryLike, interpret it as a PEM encoded RSA key
- // deno-lint-ignore no-explicit-any
- keyData = publicKey as any;
- keyType = "rsa";
- keyFormat = "pem";
+ const res = prepareAsymmetricKey(publicKey, kConsumePublic);
+ let handle;
+ if ("handle" in res) {
+ handle = res.handle;
} else {
- // TODO(kt3k): Add support for the case when publicKey is a KeyObject,
- // CryptoKey, etc
- notImplemented(
- "crypto.Verify.prototype.verify with non BinaryLike input",
+ handle = op_node_create_public_key(
+ res.data,
+ res.format,
+ res.type ?? "",
+ res.passphrase,
);
}
return op_node_verify(
+ handle,
this.hash.digest(),
this.#digestType,
- keyData!,
- keyType,
- keyFormat,
Buffer.from(signature, encoding),
);
}
diff --git a/ext/node/polyfills/internal/crypto/types.ts b/ext/node/polyfills/internal/crypto/types.ts
index 45c0ea286..17b15127e 100644
--- a/ext/node/polyfills/internal/crypto/types.ts
+++ b/ext/node/polyfills/internal/crypto/types.ts
@@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
+import { BufferEncoding } from "ext:deno_node/_global.d.ts";
import { Buffer } from "../../buffer.ts";
export type HASH_DATA = string | ArrayBufferView | Buffer | ArrayBuffer;
@@ -34,6 +35,7 @@ export type KeyType =
export interface PrivateKeyInput {
key: string | Buffer;
+ encoding: BufferEncoding | "buffer";
format?: KeyFormat | undefined;
type?: "pkcs1" | "pkcs8" | "sec1" | undefined;
passphrase?: string | Buffer | undefined;
@@ -41,6 +43,7 @@ export interface PrivateKeyInput {
export interface PublicKeyInput {
key: string | Buffer;
+ encoding: BufferEncoding | "buffer";
format?: KeyFormat | undefined;
type?: "pkcs1" | "spki" | undefined;
}