Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ext/node): implement crypto.createVerify #18703

Merged
merged 3 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/tests/node_compat/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
"test-crypto-hmac.js",
"test-crypto-prime.js",
"test-crypto-secret-keygen.js",
"test-crypto-update-encoding.js",
"test-crypto-x509.js",
"test-dgram-close-during-bind.js",
"test-dgram-close-signal.js",
Expand Down
29 changes: 29 additions & 0 deletions cli/tests/node_compat/test/parallel/test-crypto-update-encoding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.12.1
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

'use strict';
const common = require('../common');

if (!common.hasCrypto)
common.skip('missing crypto');

const crypto = require('crypto');

const zeros = Buffer.alloc;
const key = zeros(16);
const iv = zeros(16);

const cipher = () => crypto.createCipheriv('aes-128-cbc', key, iv);
const decipher = () => crypto.createDecipheriv('aes-128-cbc', key, iv);
const hash = () => crypto.createSign('sha256');
const hmac = () => crypto.createHmac('sha256', key);
const sign = () => crypto.createSign('sha256');
const verify = () => crypto.createVerify('sha256');

for (const f of [cipher, decipher, hash, hmac, sign, verify])
for (const n of [15, 16])
f().update(zeros(n), 'hex'); // Should ignore inputEncoding.
80 changes: 53 additions & 27 deletions cli/tests/unit_node/crypto_sign_test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,72 @@
// 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 {
assert,
assertEquals,
} from "../../../test_util/std/testing/asserts.ts";
import { createSign, createVerify } from "node:crypto";
import { Buffer } from "node:buffer";

const rsaPrivatePem = Buffer.from(
await Deno.readFile(
new URL("./testdata/rsa_private.pem", import.meta.url),
),
);
const rsaPublicPem = Buffer.from(
await Deno.readFile(
new URL("./testdata/rsa_public.pem", import.meta.url),
),
);

const table = [
{
algorithms: ["sha224", "RSA-SHA224"],
signature:
"7ad162b288bd7f4ba9b8a31295ad4136d143a5fd11eb99a72379dc9b53e3e8b5c1b7c9dd8a3864a1f626d921e550c48056982bd8fe7e75333885311b5515de1ecbbfcc6a1dd930f422dff87bfceb7eb38882ac6b4fd9dea9efd462776775976e81b1d677f8db41f5ac8686abfa9838069125be939c59e404aa50550872d84befb8b5f6ce2dd051c62a8ba268f876b6f17a27af43b79938222e4ab8b90c4f5540d0f8b02508ef3e68279d685746956b924f00c92438b7981a3cfcb1e2a97305402d381ea62aeaa803f8707961bc3e10a258352e210772e9846ca4024e3dc0a956a50d6db1c03d2943826cc98c6f36d7bafacf1c94b6c438c7664c300a3be172b1",
},
{
algorithms: ["sha256", "RSA-SHA256"],
signature:
"080313284d7398e1e0e27f6e44f198ceecedddc801e81af63a867d9245ad744e29018099c9ac3c27061c33cabfe27af1db38f44bac09cdcd2c4ab3b00a2a3020f68368f2239db5f911a2dbb7ea2dee322ca7d26d0c88d197482ca4aa1c29ac87b9e6c20075dc974ae71d2d76d2a5b2a15bd541033519465c3aea815cc73b0f1c3ffeedcfb93d6788416623789f86786870d23e86b982ab0df157d7a596097bd3cca3e752f3f47eff4b83754296868b52bc8ff741492dc8a401fe6dc035569e45d1fa1a71c8988d3aadce68fb1bf5c3e756c586af20c8e75c037436ff4c8389e6ce9d943ef7e2566977b84577272181fcec403077cc29e7db1166fff900b36a1d",
},
{
algorithms: ["sha384", "RSA-SHA384"],
signature:
"2f77a5b7ac0168efd652c30ecb082075f3de30629e9c1f51b7e7e671f24b5c3a2606bb72159a217438220fc7aaba887d4b817e3f43fe0cc8f840747368df8cd65ec760c21a3f9296d01caedc80a335030e31d31ac451277fc4bcc1679c168b2c3185dfee21286514113c080af5238a61a677b03777344f476f25053108588aa6bdc02a6138c6b59a20de4d11e3d668482f17e748e75747f83c0512206283acfc64ed0ad963dddc9ec24589cfd459ee806b8e0e67b93cea16651e967762a5deef890f438ffb9db39247469289db06e2ed7fe262aa1df4ab9607e5b5219a17ddc9694283a61bf8643f58fd702f2c5d3b2d53dc7f36bb5e96461174d376950d6d19",
},
{
algorithms: ["sha512", "RSA-SHA512"],
signature:
"072e20a433f255ab2f7e5e9ce69255d5c6d7c15a36af75c8389b9672c41abc6a9532fbd057d9d64270bb2483d3c9923f8f419fba4b59b838dcda82a1322009d245c06e2802a74febaea9cebc0b7f46f8761331c5f52ffb650245b5aefefcc604f209b44f6560fe45370cb239d236622e5f72fbb45377f08a0c733e16a8f15830897679ad4349d2e2e5e50a99796820302f4f47881ed444aede56a6d3330b71acaefc4218ae2e4a3bdfbb0c9432ffc5e5bac8c168278b2205d68a5d6905ccbb91282d519c11eccca52d42c86787de492b2a89679dce98cd14c37b0c183af8427e7a1ec86b1ed3f9b5bebf83f1ef81eb18748e69c716a0f263a8598fe627158647",
},
];

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")
const signature = createSign(algorithm)
.update("some data to sign")
.sign(rsaPrivatePem, "hex");
assertEquals(signature, testCase.expected);
assertEquals(signature, testCase.signature);
}
}
},
});

Deno.test({
name: "crypto.Verify - RSA PEM with SHA224, SHA256, SHA384, SHA512 digests",
fn() {
for (const testCase of table) {
for (const algorithm of testCase.algorithms) {
assert(
createVerify(algorithm).update("some data to sign").verify(
rsaPublicPem,
testCase.signature,
"hex",
),
);
}
}
},
Expand Down
54 changes: 53 additions & 1 deletion ext/node/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub fn op_node_sign(
use signature::hazmat::PrehashSigner;
let key = match key_format {
"pem" => RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)
.map_err(|_| type_error("Invalid RSA key"))?,
.map_err(|_| type_error("Invalid RSA private key"))?,
// TODO(kt3k): Support der and jwk formats
_ => {
return Err(type_error(format!(
Expand Down Expand Up @@ -353,6 +353,58 @@ pub fn op_node_sign(
}
}

#[op]
fn op_node_verify(
digest: &[u8],
digest_type: &str,
key: StringOrBuffer,
key_type: &str,
key_format: &str,
signature: &[u8],
) -> Result<bool, AnyError> {
match key_type {
"rsa" => {
use rsa::pkcs1v15::VerifyingKey;
use signature::hazmat::PrehashVerifier;
let key = match key_format {
"pem" => RsaPublicKey::from_public_key_pem((&key).try_into()?)
.map_err(|_| type_error("Invalid RSA public key"))?,
// TODO(kt3k): Support der and jwk formats
_ => {
return Err(type_error(format!(
"Unsupported key format: {}",
key_format
)))
}
};
Ok(match digest_type {
"sha224" => VerifyingKey::<sha2::Sha224>::new_with_prefix(key)
.verify_prehash(digest, &signature.to_vec().try_into()?)
.is_ok(),
"sha256" => VerifyingKey::<sha2::Sha256>::new_with_prefix(key)
.verify_prehash(digest, &signature.to_vec().try_into()?)
.is_ok(),
"sha384" => VerifyingKey::<sha2::Sha384>::new_with_prefix(key)
.verify_prehash(digest, &signature.to_vec().try_into()?)
.is_ok(),
"sha512" => VerifyingKey::<sha2::Sha512>::new_with_prefix(key)
.verify_prehash(digest, &signature.to_vec().try_into()?)
.is_ok(),
_ => {
return Err(type_error(format!(
"Unknown digest algorithm: {}",
digest_type
)))
}
})
}
_ => Err(type_error(format!(
"Verifying with {} keys is not supported yet",
key_type
))),
}
}

fn pbkdf2_sync(
password: &[u8],
salt: &[u8],
Expand Down
1 change: 1 addition & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ deno_core::extension!(deno_node,
crypto::op_node_generate_secret,
crypto::op_node_generate_secret_async,
crypto::op_node_sign,
crypto::op_node_verify,
crypto::op_node_random_int,
crypto::x509::op_node_x509_parse,
crypto::x509::op_node_x509_ca,
Expand Down
72 changes: 51 additions & 21 deletions ext/node/polyfills/internal/crypto/sig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ export class Sign extends Writable {
}

sign(
privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput,
privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput,
encoding?: BinaryToTextEncoding,
): Buffer | string {
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;
// deno-lint-ignore no-explicit-any
keyData = privateKey as any;
keyType = "rsa";
keyFormat = "pem";
} else {
Expand Down Expand Up @@ -103,35 +104,64 @@ export class Sign extends Writable {
}

export class Verify extends Writable {
hash: Hash;
#digestType: string;

constructor(algorithm: string, _options?: WritableOptions) {
validateString(algorithm, "algorithm");

super();
super({
write(chunk, enc, callback) {
this.update(chunk, enc);
callback();
},
});

algorithm = algorithm.toLowerCase();

notImplemented("crypto.Verify");
if (algorithm.startsWith("rsa-")) {
// Allows RSA-[digest_algorithm] as a valid algorithm
algorithm = algorithm.slice(4);
}

this.#digestType = algorithm;
this.hash = createHash(this.#digestType);
}

update(data: BinaryLike): this;
update(data: string, inputEncoding: Encoding): this;
update(_data: BinaryLike, _inputEncoding?: string): this {
notImplemented("crypto.Sign.prototype.update");
update(data: BinaryLike, encoding?: string): this {
this.hash.update(data, encoding);
return this;
}

verify(
object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
signature: ArrayBufferView,
): boolean;
verify(
object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
signature: string,
signatureEncoding?: BinaryToTextEncoding,
): boolean;
verify(
_object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
_signature: ArrayBufferView | string,
_signatureEncoding?: BinaryToTextEncoding,
publicKey: BinaryLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
signature: BinaryLike,
encoding?: BinaryToTextEncoding,
): boolean {
notImplemented("crypto.Sign.prototype.sign");
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";
} else {
// TODO(kt3k): Add support for the case when publicKey is a KeyObject,
// CryptoKey, etc
notImplemented(
"crypto.Verify.prototype.verify with non BinaryLike input",
);
}
return ops.op_node_verify(
this.hash.digest(),
this.#digestType,
keyData!,
keyType,
keyFormat,
Buffer.from(signature, encoding),
);
}
}

Expand Down