1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

fix(ext/node): implement crypto.Sign (RSA/PEM/SHA{224,256,384,512}) (#18471)

This commit is contained in:
Yoshiya Hinosawa 2023-03-28 21:46:48 +09:00 committed by GitHub
parent e5b2815b39
commit 4358ab2d84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 164 additions and 17 deletions

1
Cargo.lock generated
View file

@ -1167,6 +1167,7 @@ dependencies = [
"sha-1 0.10.0", "sha-1 0.10.0",
"sha2", "sha2",
"sha3", "sha3",
"signature",
"tokio", "tokio",
"typenum", "typenum",
] ]

View file

@ -121,6 +121,7 @@ serde_bytes = "0.11"
serde_json = "1.0.85" serde_json = "1.0.85"
serde_repr = "=0.1.9" serde_repr = "=0.1.9"
sha2 = { version = "0.10.6", features = ["oid"] } sha2 = { version = "0.10.6", features = ["oid"] }
signature = "=1.6.4"
smallvec = "1.8" smallvec = "1.8"
socket2 = "0.4.7" socket2 = "0.4.7"
tar = "=0.4.38" tar = "=0.4.38"
@ -136,7 +137,7 @@ uuid = { version = "1.3.0", features = ["v4"] }
zstd = "=0.11.2" zstd = "=0.11.2"
# crypto # crypto
rsa = { version = "0.7.0", default-features = false, features = ["std", "pem"] } rsa = { version = "0.7.0", default-features = false, features = ["std", "pem", "hazmat"] } # hazmat needed for PrehashSigner in ext/node
# macros # macros
proc-macro2 = "1" proc-macro2 = "1"

View file

@ -0,0 +1,47 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { assertEquals } from "../../../test_util/std/testing/asserts.ts";
import { createSign } from "node:crypto";
import { Buffer } from "node:buffer";
const rsaPrivatePem = Buffer.from(
await Deno.readFile(
new URL("./testdata/rsa_private.pem", import.meta.url),
),
);
Deno.test({
name: "crypto.Sign - RSA PEM with SHA224, SHA256, SHA384, SHA512 digests",
fn() {
const table = [
{
algorithms: ["sha224", "RSA-SHA224"],
expected:
"7ad162b288bd7f4ba9b8a31295ad4136d143a5fd11eb99a72379dc9b53e3e8b5c1b7c9dd8a3864a1f626d921e550c48056982bd8fe7e75333885311b5515de1ecbbfcc6a1dd930f422dff87bfceb7eb38882ac6b4fd9dea9efd462776775976e81b1d677f8db41f5ac8686abfa9838069125be939c59e404aa50550872d84befb8b5f6ce2dd051c62a8ba268f876b6f17a27af43b79938222e4ab8b90c4f5540d0f8b02508ef3e68279d685746956b924f00c92438b7981a3cfcb1e2a97305402d381ea62aeaa803f8707961bc3e10a258352e210772e9846ca4024e3dc0a956a50d6db1c03d2943826cc98c6f36d7bafacf1c94b6c438c7664c300a3be172b1",
},
{
algorithms: ["sha256", "RSA-SHA256"],
expected:
"080313284d7398e1e0e27f6e44f198ceecedddc801e81af63a867d9245ad744e29018099c9ac3c27061c33cabfe27af1db38f44bac09cdcd2c4ab3b00a2a3020f68368f2239db5f911a2dbb7ea2dee322ca7d26d0c88d197482ca4aa1c29ac87b9e6c20075dc974ae71d2d76d2a5b2a15bd541033519465c3aea815cc73b0f1c3ffeedcfb93d6788416623789f86786870d23e86b982ab0df157d7a596097bd3cca3e752f3f47eff4b83754296868b52bc8ff741492dc8a401fe6dc035569e45d1fa1a71c8988d3aadce68fb1bf5c3e756c586af20c8e75c037436ff4c8389e6ce9d943ef7e2566977b84577272181fcec403077cc29e7db1166fff900b36a1d",
},
{
algorithms: ["sha384", "RSA-SHA384"],
expected:
"2f77a5b7ac0168efd652c30ecb082075f3de30629e9c1f51b7e7e671f24b5c3a2606bb72159a217438220fc7aaba887d4b817e3f43fe0cc8f840747368df8cd65ec760c21a3f9296d01caedc80a335030e31d31ac451277fc4bcc1679c168b2c3185dfee21286514113c080af5238a61a677b03777344f476f25053108588aa6bdc02a6138c6b59a20de4d11e3d668482f17e748e75747f83c0512206283acfc64ed0ad963dddc9ec24589cfd459ee806b8e0e67b93cea16651e967762a5deef890f438ffb9db39247469289db06e2ed7fe262aa1df4ab9607e5b5219a17ddc9694283a61bf8643f58fd702f2c5d3b2d53dc7f36bb5e96461174d376950d6d19",
},
{
algorithms: ["sha512", "RSA-SHA512"],
expected:
"072e20a433f255ab2f7e5e9ce69255d5c6d7c15a36af75c8389b9672c41abc6a9532fbd057d9d64270bb2483d3c9923f8f419fba4b59b838dcda82a1322009d245c06e2802a74febaea9cebc0b7f46f8761331c5f52ffb650245b5aefefcc604f209b44f6560fe45370cb239d236622e5f72fbb45377f08a0c733e16a8f15830897679ad4349d2e2e5e50a99796820302f4f47881ed444aede56a6d3330b71acaefc4218ae2e4a3bdfbb0c9432ffc5e5bac8c168278b2205d68a5d6905ccbb91282d519c11eccca52d42c86787de492b2a89679dce98cd14c37b0c183af8427e7a1ec86b1ed3f9b5bebf83f1ef81eb18748e69c716a0f263a8598fe627158647",
},
];
for (const testCase of table) {
for (const algorithm of testCase.algorithms) {
const signature = createSign(algorithm).update("some data to sign")
.sign(rsaPrivatePem, "hex");
assertEquals(signature, testCase.expected);
}
}
},
});

View file

@ -39,7 +39,7 @@ serde.workspace = true
serde_bytes.workspace = true serde_bytes.workspace = true
sha1 = { version = "0.10.5", features = ["oid"] } sha1 = { version = "0.10.5", features = ["oid"] }
sha2.workspace = true sha2.workspace = true
signature = "1.6.4" signature.workspace = true
spki = "0.6.0" spki = "0.6.0"
tokio.workspace = true tokio.workspace = true
uuid.workspace = true uuid.workspace = true

View file

@ -39,5 +39,6 @@ serde = "1.0.149"
sha-1 = "0.10.0" sha-1 = "0.10.0"
sha2 = "0.10.6" sha2 = "0.10.6"
sha3 = "0.10.5" sha3 = "0.10.5"
signature.workspace = true
tokio.workspace = true tokio.workspace = true
typenum = "1.15.0" typenum = "1.15.0"

View file

@ -290,6 +290,64 @@ pub fn op_node_decipheriv_final(
context.r#final(input, output) context.r#final(input, output)
} }
#[op]
pub fn op_node_sign(
digest: &[u8],
digest_type: &str,
key: StringOrBuffer,
key_type: &str,
key_format: &str,
) -> Result<ZeroCopyBuf, AnyError> {
match key_type {
"rsa" => {
use rsa::pkcs1v15::SigningKey;
use signature::hazmat::PrehashSigner;
let key = match key_format {
"pem" => RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)
.map_err(|_| type_error("Invalid RSA key"))?,
// TODO(kt3k): Support der and jwk formats
_ => {
return Err(type_error(format!(
"Unsupported key format: {}",
key_format
)))
}
};
Ok(
match digest_type {
"sha224" => {
let signing_key = SigningKey::<sha2::Sha224>::new_with_prefix(key);
signing_key.sign_prehash(digest)?.to_vec()
}
"sha256" => {
let signing_key = SigningKey::<sha2::Sha256>::new_with_prefix(key);
signing_key.sign_prehash(digest)?.to_vec()
}
"sha384" => {
let signing_key = SigningKey::<sha2::Sha384>::new_with_prefix(key);
signing_key.sign_prehash(digest)?.to_vec()
}
"sha512" => {
let signing_key = SigningKey::<sha2::Sha512>::new_with_prefix(key);
signing_key.sign_prehash(digest)?.to_vec()
}
_ => {
return Err(type_error(format!(
"Unknown digest algorithm: {}",
digest_type
)))
}
}
.into(),
)
}
_ => Err(type_error(format!(
"Signing with {} keys is not supported yet",
key_type
))),
}
}
fn pbkdf2_sync( fn pbkdf2_sync(
password: &[u8], password: &[u8],
salt: &[u8], salt: &[u8],

View file

@ -119,6 +119,7 @@ deno_core::extension!(deno_node,
crypto::op_node_check_prime_bytes_async, crypto::op_node_check_prime_bytes_async,
crypto::op_node_pbkdf2, crypto::op_node_pbkdf2,
crypto::op_node_pbkdf2_async, crypto::op_node_pbkdf2_async,
crypto::op_node_sign,
winerror::op_node_sys_to_uv_error, winerror::op_node_sys_to_uv_error,
v8::op_v8_cached_data_version_tag, v8::op_v8_cached_data_version_tag,
v8::op_v8_get_heap_statistics, v8::op_v8_get_heap_statistics,
@ -406,7 +407,7 @@ pub fn initialize_runtime(
let source_code = format!( let source_code = format!(
r#"(function loadBuiltinNodeModules(nodeGlobalThisName, usesLocalNodeModulesDir, argv0) {{ r#"(function loadBuiltinNodeModules(nodeGlobalThisName, usesLocalNodeModulesDir, argv0) {{
Deno[Deno.internal].node.initialize( Deno[Deno.internal].node.initialize(
nodeGlobalThisName, nodeGlobalThisName,
usesLocalNodeModulesDir, usesLocalNodeModulesDir,
argv0 argv0
); );

View file

@ -73,7 +73,7 @@ export class Hash extends Transform {
if (typeof algorithm === "string") { if (typeof algorithm === "string") {
this.#context = ops.op_node_create_hash( this.#context = ops.op_node_create_hash(
algorithm, algorithm.toLowerCase(),
); );
if (this.#context === 0) { if (this.#context === 0) {
throw new TypeError(`Unknown hash algorithm: ${algorithm}`); throw new TypeError(`Unknown hash algorithm: ${algorithm}`);

View file

@ -14,6 +14,12 @@ import type {
PublicKeyInput, PublicKeyInput,
} from "ext:deno_node/internal/crypto/types.ts"; } from "ext:deno_node/internal/crypto/types.ts";
import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; import { KeyObject } 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";
const { core } = globalThis.__bootstrap;
const { ops } = core;
export type DSAEncoding = "der" | "ieee-p1363"; export type DSAEncoding = "der" | "ieee-p1363";
@ -37,30 +43,62 @@ export interface VerifyKeyObjectInput extends SigningOptions {
export type KeyLike = string | Buffer | KeyObject; export type KeyLike = string | Buffer | KeyObject;
export class Sign extends Writable { export class Sign extends Writable {
hash: Hash;
#digestType: string;
constructor(algorithm: string, _options?: WritableOptions) { constructor(algorithm: string, _options?: WritableOptions) {
validateString(algorithm, "algorithm"); validateString(algorithm, "algorithm");
super(); super({
write(chunk, enc, callback) {
this.update(chunk, enc);
callback();
},
});
notImplemented("crypto.Sign"); algorithm = algorithm.toLowerCase();
if (algorithm.startsWith("rsa-")) {
// Allows RSA-[digest_algorithm] as a valid algorithm
algorithm = algorithm.slice(4);
}
this.#digestType = algorithm;
this.hash = createHash(this.#digestType);
} }
sign(privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput): Buffer;
sign( sign(
privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput,
outputFormat: BinaryToTextEncoding, encoding?: BinaryToTextEncoding,
): string;
sign(
_privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput,
_outputEncoding?: BinaryToTextEncoding,
): Buffer | string { ): Buffer | string {
notImplemented("crypto.Sign.prototype.sign"); let keyData: Uint8Array;
let keyType: KeyType;
let keyFormat: KeyFormat;
if (typeof privateKey === "string" || isArrayBufferView(privateKey)) {
// if the key is BinaryLike, interpret it as a PEM encoded RSA key
keyData = privateKey;
keyType = "rsa";
keyFormat = "pem";
} else {
// TODO(kt3k): Add support for the case when privateKey is a KeyObject,
// CryptoKey, etc
notImplemented("crypto.Sign.prototype.sign with non BinaryLike input");
}
const ret = Buffer.from(ops.op_node_sign(
this.hash.digest(),
this.#digestType,
keyData!,
keyType,
keyFormat,
));
return encoding ? ret.toString(encoding) : ret;
} }
update(data: BinaryLike): this; update(
update(data: string, inputEncoding: Encoding): this; data: BinaryLike | string,
update(_data: BinaryLike | string, _inputEncoding?: Encoding): this { encoding?: Encoding,
notImplemented("crypto.Sign.prototype.update"); ): this {
this.hash.update(data, encoding);
return this;
} }
} }