Skip to content
New issue

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

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

Already on GitHub? # to your account

fix(ext/node): support ieee-p1363 ECDSA signatures and pss salt len #24981

Merged
merged 1 commit into from
Aug 11, 2024
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ der = { version = "0.7.9", features = ["derive"] }
digest = { version = "0.10.5", features = ["core-api", "std"] }
dsa = "0.6.1"
ecb.workspace = true
ecdsa = "0.16.9"
ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core", "signature"] }
elliptic-curve.workspace = true
errno = "0.2.8"
Expand Down
21 changes: 18 additions & 3 deletions ext/node/ops/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,18 +362,33 @@ pub fn op_node_sign(
#[cppgc] handle: &KeyObjectHandle,
#[buffer] digest: &[u8],
#[string] digest_type: &str,
#[smi] pss_salt_length: Option<u32>,
#[smi] dsa_signature_encoding: u32,
) -> Result<Box<[u8]>, AnyError> {
handle.sign_prehashed(digest_type, digest)
handle.sign_prehashed(
digest_type,
digest,
pss_salt_length,
dsa_signature_encoding,
)
}

#[op2(fast)]
#[op2]
pub fn op_node_verify(
#[cppgc] handle: &KeyObjectHandle,
#[buffer] digest: &[u8],
#[string] digest_type: &str,
#[buffer] signature: &[u8],
#[smi] pss_salt_length: Option<u32>,
#[smi] dsa_signature_encoding: u32,
) -> Result<bool, AnyError> {
handle.verify_prehashed(digest_type, digest, signature)
handle.verify_prehashed(
digest_type,
digest,
signature,
pss_salt_length,
dsa_signature_encoding,
)
}

fn pbkdf2_sync(
Expand Down
71 changes: 62 additions & 9 deletions ext/node/ops/crypto/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,36 @@ use super::keys::EcPrivateKey;
use super::keys::EcPublicKey;
use super::keys::KeyObjectHandle;
use super::keys::RsaPssHashAlgorithm;
use core::ops::Add;
use ecdsa::der::MaxOverhead;
use ecdsa::der::MaxSize;
use elliptic_curve::generic_array::ArrayLength;
use elliptic_curve::FieldBytesSize;

fn dsa_signature<C: elliptic_curve::PrimeCurve>(
encoding: u32,
signature: ecdsa::Signature<C>,
) -> Result<Box<[u8]>, AnyError>
where
MaxSize<C>: ArrayLength<u8>,
<FieldBytesSize<C> as Add>::Output: Add<MaxOverhead> + ArrayLength<u8>,
{
match encoding {
// DER
0 => Ok(signature.to_der().to_bytes().to_vec().into_boxed_slice()),
// IEEE P1363
1 => Ok(signature.to_bytes().to_vec().into_boxed_slice()),
_ => Err(type_error("invalid DSA signature encoding")),
}
}

impl KeyObjectHandle {
pub fn sign_prehashed(
&self,
digest_type: &str,
digest: &[u8],
pss_salt_length: Option<u32>,
dsa_signature_encoding: u32,
) -> Result<Box<[u8]>, AnyError> {
let private_key = self
.as_private_key()
Expand Down Expand Up @@ -67,6 +91,9 @@ impl KeyObjectHandle {
}
None => {}
};
if let Some(s) = pss_salt_length {
salt_length = Some(s as usize);
}
let pss = match_fixed_digest_with_oid!(
digest_type,
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
Expand Down Expand Up @@ -120,21 +147,24 @@ impl KeyObjectHandle {
let signature: p224::ecdsa::Signature = signing_key
.sign_prehash(digest)
.map_err(|_| type_error("failed to sign digest"))?;
Ok(signature.to_der().to_bytes())

dsa_signature(dsa_signature_encoding, signature)
}
EcPrivateKey::P256(key) => {
let signing_key = p256::ecdsa::SigningKey::from(key);
let signature: p256::ecdsa::Signature = signing_key
.sign_prehash(digest)
.map_err(|_| type_error("failed to sign digest"))?;
Ok(signature.to_der().to_bytes())

dsa_signature(dsa_signature_encoding, signature)
}
EcPrivateKey::P384(key) => {
let signing_key = p384::ecdsa::SigningKey::from(key);
let signature: p384::ecdsa::Signature = signing_key
.sign_prehash(digest)
.map_err(|_| type_error("failed to sign digest"))?;
Ok(signature.to_der().to_bytes())

dsa_signature(dsa_signature_encoding, signature)
}
},
AsymmetricPrivateKey::X25519(_) => {
Expand All @@ -154,6 +184,8 @@ impl KeyObjectHandle {
digest_type: &str,
digest: &[u8],
signature: &[u8],
pss_salt_length: Option<u32>,
dsa_signature_encoding: u32,
) -> Result<bool, AnyError> {
let public_key = self
.as_public_key()
Expand Down Expand Up @@ -195,6 +227,9 @@ impl KeyObjectHandle {
}
None => {}
};
if let Some(s) = pss_salt_length {
salt_length = Some(s as usize);
}
let pss = match_fixed_digest_with_oid!(
digest_type,
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
Expand Down Expand Up @@ -229,20 +264,38 @@ impl KeyObjectHandle {
AsymmetricPublicKey::Ec(key) => match key {
EcPublicKey::P224(key) => {
let verifying_key = p224::ecdsa::VerifyingKey::from(key);
let signature = p224::ecdsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid ECDSA signature"))?;
let signature = if dsa_signature_encoding == 0 {
p224::ecdsa::Signature::from_der(signature)
} else {
p224::ecdsa::Signature::from_bytes(signature.into())
};
let Ok(signature) = signature else {
return Ok(false);
};
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
}
EcPublicKey::P256(key) => {
let verifying_key = p256::ecdsa::VerifyingKey::from(key);
let signature = p256::ecdsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid ECDSA signature"))?;
let signature = if dsa_signature_encoding == 0 {
p256::ecdsa::Signature::from_der(signature)
} else {
p256::ecdsa::Signature::from_bytes(signature.into())
};
let Ok(signature) = signature else {
return Ok(false);
};
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
}
EcPublicKey::P384(key) => {
let verifying_key = p384::ecdsa::VerifyingKey::from(key);
let signature = p384::ecdsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid ECDSA signature"))?;
let signature = if dsa_signature_encoding == 0 {
p384::ecdsa::Signature::from_der(signature)
} else {
p384::ecdsa::Signature::from_bytes(signature.into())
};
let Ok(signature) = signature else {
return Ok(false);
};
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
}
},
Expand Down
47 changes: 47 additions & 0 deletions ext/node/polyfills/internal/crypto/sig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,35 @@ export interface VerifyKeyObjectInput extends SigningOptions {
key: KeyObject;
}

function getSaltLength(options) {
return getIntOption("saltLength", options);
}

function getDSASignatureEncoding(options) {
if (typeof options === "object") {
const { dsaEncoding = "der" } = options;
if (dsaEncoding === "der") {
return 0;
} else if (dsaEncoding === "ieee-p1363") {
return 1;
}
throw new ERR_INVALID_ARG_VALUE("options.dsaEncoding", dsaEncoding);
}

return 0;
}

function getIntOption(name, options) {
const value = options[name];
if (value !== undefined) {
if (value === value >> 0) {
return value;
}
throw new ERR_INVALID_ARG_VALUE(`options.${name}`, value);
}
return undefined;
}

export type KeyLike = string | Buffer | KeyObject;

export class SignImpl extends Writable {
Expand Down Expand Up @@ -86,6 +115,13 @@ export class SignImpl extends Writable {
encoding?: BinaryToTextEncoding,
): Buffer | string {
const res = prepareAsymmetricKey(privateKey, kConsumePrivate);

// Options specific to RSA-PSS
const pssSaltLength = getSaltLength(privateKey);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(privateKey);

let handle;
if ("handle" in res) {
handle = res.handle;
Expand All @@ -101,6 +137,8 @@ export class SignImpl extends Writable {
handle,
this.hash.digest(),
this.#digestType,
pssSaltLength,
dsaSigEnc,
));
return encoding ? ret.toString(encoding) : ret;
}
Expand Down Expand Up @@ -152,6 +190,13 @@ export class VerifyImpl extends Writable {
encoding?: BinaryToTextEncoding,
): boolean {
const res = prepareAsymmetricKey(publicKey, kConsumePublic);

// Options specific to RSA-PSS
const pssSaltLength = getSaltLength(publicKey);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(publicKey);

let handle;
if ("handle" in res) {
handle = res.handle;
Expand All @@ -168,6 +213,8 @@ export class VerifyImpl extends Writable {
this.hash.digest(),
this.#digestType,
Buffer.from(signature, encoding),
pssSaltLength,
dsaSigEnc,
);
}
}
Expand Down
30 changes: 29 additions & 1 deletion tests/unit_node/crypto/crypto_sign_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

import { assert, assertEquals } from "@std/assert";
import { createSign, createVerify, sign, verify } from "node:crypto";
import {
createSign,
createVerify,
generateKeyPairSync,
sign,
verify,
} from "node:crypto";
import { Buffer } from "node:buffer";
import fixtures from "../testdata/crypto_digest_fixtures.json" with {
type: "json",
Expand Down Expand Up @@ -179,3 +185,25 @@ Deno.test("crypto.createVerify|verify - compare with node", async (t) => {
});
}
});

Deno.test("crypto sign|verify dsaEncoding", () => {
const { privateKey, publicKey } = generateKeyPairSync("ec", {
namedCurve: "P-256",
});

const sign = createSign("SHA256");
sign.write("some data to sign");
sign.end();

// @ts-ignore FIXME: types dont allow this
privateKey.dsaEncoding = "ieee-p1363";
const signature = sign.sign(privateKey, "hex");

const verify = createVerify("SHA256");
verify.write("some data to sign");
verify.end();

// @ts-ignore FIXME: types dont allow this
publicKey.dsaEncoding = "ieee-p1363";
assert(verify.verify(publicKey, signature, "hex"));
});