1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-03 17:08:35 -05:00
denoland-deno/ext/node/polyfills/internal/crypto/sig.ts
Luca Casonato 4fa8869f24
feat(ext/node): rewrite crypto keys (#24463)
This completely rewrites how we handle key material in ext/node. Changes
in this
PR:

- **Signing**
  - RSA
  - RSA-PSS 🆕
  - DSA 🆕
  - EC
  - ED25519 🆕
- **Verifying**
  - RSA
  - RSA-PSS 🆕
  - DSA 🆕
  - EC 🆕
  - ED25519 🆕
- **Private key import**
  - Passphrase encrypted private keys 🆕
  - RSA
    - PEM
    - DER (PKCS#1) 🆕
    - DER (PKCS#8) 🆕
  - RSA-PSS
    - PEM
    - DER (PKCS#1) 🆕
    - DER (PKCS#8) 🆕
  - DSA 🆕
  - EC
    - PEM
    - DER (SEC1) 🆕
    - DER (PKCS#8) 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH
- **Public key import**
  - RSA
    - PEM
    - DER (PKCS#1) 🆕
    - DER (PKCS#8) 🆕
  - RSA-PSS 🆕
  - DSA 🆕
  - EC 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH 🆕
- **Private key export**
  - RSA 🆕
  - DSA 🆕
  - EC 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH 🆕
- **Public key export**
  - RSA
  - DSA 🆕
  - EC 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH 🆕
- **Key pair generation**
  - Overhauled, but supported APIs unchanged

This PR adds a lot of new individual functionality. But most importantly
because
of the new key material representation, it is now trivial to add new
algorithms
(as shown by this PR).

Now, when adding a new algorithm, it is also widely supported - for
example
previously we supported ED25519 key pair generation, but we could not
import,
export, sign or verify with ED25519. We can now do all of those things.
2024-08-07 08:43:58 +02:00

236 lines
5.5 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import {
op_node_create_private_key,
op_node_create_public_key,
op_node_sign,
op_node_verify,
} from "ext:core/ops";
import {
validateFunction,
validateString,
} from "ext:deno_node/internal/validators.mjs";
import { Buffer } from "node:buffer";
import type { WritableOptions } from "ext:deno_node/_stream.d.ts";
import Writable from "ext:deno_node/internal/streams/writable.mjs";
import type {
BinaryLike,
BinaryToTextEncoding,
Encoding,
PrivateKeyInput,
PublicKeyInput,
} from "ext:deno_node/internal/crypto/types.ts";
import {
kConsumePrivate,
kConsumePublic,
KeyObject,
prepareAsymmetricKey,
} from "ext:deno_node/internal/crypto/keys.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";
export interface SigningOptions {
padding?: number | undefined;
saltLength?: number | undefined;
dsaEncoding?: DSAEncoding | undefined;
}
export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {}
export interface SignKeyObjectInput extends SigningOptions {
key: KeyObject;
}
export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {}
export interface VerifyKeyObjectInput extends SigningOptions {
key: KeyObject;
}
export type KeyLike = string | Buffer | KeyObject;
export class SignImpl extends Writable {
hash: Hash;
#digestType: string;
constructor(algorithm: string, _options?: WritableOptions) {
validateString(algorithm, "algorithm");
super({
write(chunk, enc, callback) {
this.update(chunk, enc);
callback();
},
});
algorithm = algorithm.toLowerCase();
this.#digestType = algorithm;
this.hash = createHash(this.#digestType);
}
sign(
// deno-lint-ignore no-explicit-any
privateKey: any,
encoding?: BinaryToTextEncoding,
): Buffer | string {
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,
));
return encoding ? ret.toString(encoding) : ret;
}
update(
data: BinaryLike | string,
encoding?: Encoding,
): this {
this.hash.update(data, encoding);
return this;
}
}
export function Sign(algorithm: string, options?: WritableOptions) {
return new SignImpl(algorithm, options);
}
Sign.prototype = SignImpl.prototype;
export class VerifyImpl extends Writable {
hash: Hash;
#digestType: string;
constructor(algorithm: string, _options?: WritableOptions) {
validateString(algorithm, "algorithm");
super({
write(chunk, enc, callback) {
this.update(chunk, enc);
callback();
},
});
algorithm = algorithm.toLowerCase();
this.#digestType = algorithm;
this.hash = createHash(this.#digestType);
}
update(data: BinaryLike, encoding?: string): this {
this.hash.update(data, encoding);
return this;
}
verify(
// deno-lint-ignore no-explicit-any
publicKey: any,
signature: BinaryLike,
encoding?: BinaryToTextEncoding,
): boolean {
const res = prepareAsymmetricKey(publicKey, kConsumePublic);
let handle;
if ("handle" in res) {
handle = res.handle;
} else {
handle = op_node_create_public_key(
res.data,
res.format,
res.type ?? "",
res.passphrase,
);
}
return op_node_verify(
handle,
this.hash.digest(),
this.#digestType,
Buffer.from(signature, encoding),
);
}
}
export function Verify(algorithm: string, options?: WritableOptions) {
return new VerifyImpl(algorithm, options);
}
Verify.prototype = VerifyImpl.prototype;
export function signOneShot(
algorithm: string | null | undefined,
data: ArrayBufferView,
key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput,
callback?: (error: Error | null, data: Buffer) => void,
): Buffer | void {
if (algorithm != null) {
validateString(algorithm, "algorithm");
}
if (callback !== undefined) {
validateFunction(callback, "callback");
}
if (!key) {
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
}
const result = Sign(algorithm!).update(data).sign(key);
if (callback) {
setTimeout(() => callback(null, result));
} else {
return result;
}
}
export function verifyOneShot(
algorithm: string | null | undefined,
data: BinaryLike,
key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
signature: BinaryLike,
callback?: (error: Error | null, result: boolean) => void,
): boolean | void {
if (algorithm != null) {
validateString(algorithm, "algorithm");
}
if (callback !== undefined) {
validateFunction(callback, "callback");
}
if (!key) {
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
}
const result = Verify(algorithm!).update(data).verify(key, signature);
if (callback) {
setTimeout(() => callback(null, result));
} else {
return result;
}
}
export default {
signOneShot,
verifyOneShot,
Sign,
Verify,
};