diff options
Diffstat (limited to 'ext/node/polyfills/internal')
-rw-r--r-- | ext/node/polyfills/internal/crypto/_keys.ts | 10 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/_randomFill.mjs | 33 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/cipher.ts | 4 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/diffiehellman.ts | 4 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/hash.ts | 25 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/hkdf.ts | 10 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/keygen.ts | 117 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/keys.ts | 558 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/sig.ts | 63 | ||||
-rw-r--r-- | ext/node/polyfills/internal/crypto/types.ts | 3 |
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; } |