mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
fix(ext/node): support ieee-p1363 ECDSA signatures and pss salt len (#24981)
Fixes https://github.com/denoland/deno/issues/22919
This commit is contained in:
parent
feba133711
commit
d6f662ac82
6 changed files with 158 additions and 13 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1781,6 +1781,7 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
"dsa",
|
"dsa",
|
||||||
"ecb",
|
"ecb",
|
||||||
|
"ecdsa",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"errno 0.2.8",
|
"errno 0.2.8",
|
||||||
|
|
|
@ -40,6 +40,7 @@ der = { version = "0.7.9", features = ["derive"] }
|
||||||
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
||||||
dsa = "0.6.1"
|
dsa = "0.6.1"
|
||||||
ecb.workspace = true
|
ecb.workspace = true
|
||||||
|
ecdsa = "0.16.9"
|
||||||
ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core", "signature"] }
|
ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core", "signature"] }
|
||||||
elliptic-curve.workspace = true
|
elliptic-curve.workspace = true
|
||||||
errno = "0.2.8"
|
errno = "0.2.8"
|
||||||
|
|
|
@ -362,18 +362,33 @@ pub fn op_node_sign(
|
||||||
#[cppgc] handle: &KeyObjectHandle,
|
#[cppgc] handle: &KeyObjectHandle,
|
||||||
#[buffer] digest: &[u8],
|
#[buffer] digest: &[u8],
|
||||||
#[string] digest_type: &str,
|
#[string] digest_type: &str,
|
||||||
|
#[smi] pss_salt_length: Option<u32>,
|
||||||
|
#[smi] dsa_signature_encoding: u32,
|
||||||
) -> Result<Box<[u8]>, AnyError> {
|
) -> 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(
|
pub fn op_node_verify(
|
||||||
#[cppgc] handle: &KeyObjectHandle,
|
#[cppgc] handle: &KeyObjectHandle,
|
||||||
#[buffer] digest: &[u8],
|
#[buffer] digest: &[u8],
|
||||||
#[string] digest_type: &str,
|
#[string] digest_type: &str,
|
||||||
#[buffer] signature: &[u8],
|
#[buffer] signature: &[u8],
|
||||||
|
#[smi] pss_salt_length: Option<u32>,
|
||||||
|
#[smi] dsa_signature_encoding: u32,
|
||||||
) -> Result<bool, AnyError> {
|
) -> 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(
|
fn pbkdf2_sync(
|
||||||
|
|
|
@ -17,12 +17,36 @@ use super::keys::EcPrivateKey;
|
||||||
use super::keys::EcPublicKey;
|
use super::keys::EcPublicKey;
|
||||||
use super::keys::KeyObjectHandle;
|
use super::keys::KeyObjectHandle;
|
||||||
use super::keys::RsaPssHashAlgorithm;
|
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 {
|
impl KeyObjectHandle {
|
||||||
pub fn sign_prehashed(
|
pub fn sign_prehashed(
|
||||||
&self,
|
&self,
|
||||||
digest_type: &str,
|
digest_type: &str,
|
||||||
digest: &[u8],
|
digest: &[u8],
|
||||||
|
pss_salt_length: Option<u32>,
|
||||||
|
dsa_signature_encoding: u32,
|
||||||
) -> Result<Box<[u8]>, AnyError> {
|
) -> Result<Box<[u8]>, AnyError> {
|
||||||
let private_key = self
|
let private_key = self
|
||||||
.as_private_key()
|
.as_private_key()
|
||||||
|
@ -67,6 +91,9 @@ impl KeyObjectHandle {
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
|
if let Some(s) = pss_salt_length {
|
||||||
|
salt_length = Some(s as usize);
|
||||||
|
}
|
||||||
let pss = match_fixed_digest_with_oid!(
|
let pss = match_fixed_digest_with_oid!(
|
||||||
digest_type,
|
digest_type,
|
||||||
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
|
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
|
||||||
|
@ -120,21 +147,24 @@ impl KeyObjectHandle {
|
||||||
let signature: p224::ecdsa::Signature = signing_key
|
let signature: p224::ecdsa::Signature = signing_key
|
||||||
.sign_prehash(digest)
|
.sign_prehash(digest)
|
||||||
.map_err(|_| type_error("failed to sign digest"))?;
|
.map_err(|_| type_error("failed to sign digest"))?;
|
||||||
Ok(signature.to_der().to_bytes())
|
|
||||||
|
dsa_signature(dsa_signature_encoding, signature)
|
||||||
}
|
}
|
||||||
EcPrivateKey::P256(key) => {
|
EcPrivateKey::P256(key) => {
|
||||||
let signing_key = p256::ecdsa::SigningKey::from(key);
|
let signing_key = p256::ecdsa::SigningKey::from(key);
|
||||||
let signature: p256::ecdsa::Signature = signing_key
|
let signature: p256::ecdsa::Signature = signing_key
|
||||||
.sign_prehash(digest)
|
.sign_prehash(digest)
|
||||||
.map_err(|_| type_error("failed to sign digest"))?;
|
.map_err(|_| type_error("failed to sign digest"))?;
|
||||||
Ok(signature.to_der().to_bytes())
|
|
||||||
|
dsa_signature(dsa_signature_encoding, signature)
|
||||||
}
|
}
|
||||||
EcPrivateKey::P384(key) => {
|
EcPrivateKey::P384(key) => {
|
||||||
let signing_key = p384::ecdsa::SigningKey::from(key);
|
let signing_key = p384::ecdsa::SigningKey::from(key);
|
||||||
let signature: p384::ecdsa::Signature = signing_key
|
let signature: p384::ecdsa::Signature = signing_key
|
||||||
.sign_prehash(digest)
|
.sign_prehash(digest)
|
||||||
.map_err(|_| type_error("failed to sign digest"))?;
|
.map_err(|_| type_error("failed to sign digest"))?;
|
||||||
Ok(signature.to_der().to_bytes())
|
|
||||||
|
dsa_signature(dsa_signature_encoding, signature)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AsymmetricPrivateKey::X25519(_) => {
|
AsymmetricPrivateKey::X25519(_) => {
|
||||||
|
@ -154,6 +184,8 @@ impl KeyObjectHandle {
|
||||||
digest_type: &str,
|
digest_type: &str,
|
||||||
digest: &[u8],
|
digest: &[u8],
|
||||||
signature: &[u8],
|
signature: &[u8],
|
||||||
|
pss_salt_length: Option<u32>,
|
||||||
|
dsa_signature_encoding: u32,
|
||||||
) -> Result<bool, AnyError> {
|
) -> Result<bool, AnyError> {
|
||||||
let public_key = self
|
let public_key = self
|
||||||
.as_public_key()
|
.as_public_key()
|
||||||
|
@ -195,6 +227,9 @@ impl KeyObjectHandle {
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
|
if let Some(s) = pss_salt_length {
|
||||||
|
salt_length = Some(s as usize);
|
||||||
|
}
|
||||||
let pss = match_fixed_digest_with_oid!(
|
let pss = match_fixed_digest_with_oid!(
|
||||||
digest_type,
|
digest_type,
|
||||||
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
|
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
|
||||||
|
@ -229,20 +264,38 @@ impl KeyObjectHandle {
|
||||||
AsymmetricPublicKey::Ec(key) => match key {
|
AsymmetricPublicKey::Ec(key) => match key {
|
||||||
EcPublicKey::P224(key) => {
|
EcPublicKey::P224(key) => {
|
||||||
let verifying_key = p224::ecdsa::VerifyingKey::from(key);
|
let verifying_key = p224::ecdsa::VerifyingKey::from(key);
|
||||||
let signature = p224::ecdsa::Signature::from_der(signature)
|
let signature = if dsa_signature_encoding == 0 {
|
||||||
.map_err(|_| type_error("Invalid ECDSA signature"))?;
|
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())
|
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
|
||||||
}
|
}
|
||||||
EcPublicKey::P256(key) => {
|
EcPublicKey::P256(key) => {
|
||||||
let verifying_key = p256::ecdsa::VerifyingKey::from(key);
|
let verifying_key = p256::ecdsa::VerifyingKey::from(key);
|
||||||
let signature = p256::ecdsa::Signature::from_der(signature)
|
let signature = if dsa_signature_encoding == 0 {
|
||||||
.map_err(|_| type_error("Invalid ECDSA signature"))?;
|
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())
|
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
|
||||||
}
|
}
|
||||||
EcPublicKey::P384(key) => {
|
EcPublicKey::P384(key) => {
|
||||||
let verifying_key = p384::ecdsa::VerifyingKey::from(key);
|
let verifying_key = p384::ecdsa::VerifyingKey::from(key);
|
||||||
let signature = p384::ecdsa::Signature::from_der(signature)
|
let signature = if dsa_signature_encoding == 0 {
|
||||||
.map_err(|_| type_error("Invalid ECDSA signature"))?;
|
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())
|
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -58,6 +58,35 @@ export interface VerifyKeyObjectInput extends SigningOptions {
|
||||||
key: KeyObject;
|
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 type KeyLike = string | Buffer | KeyObject;
|
||||||
|
|
||||||
export class SignImpl extends Writable {
|
export class SignImpl extends Writable {
|
||||||
|
@ -86,6 +115,13 @@ export class SignImpl extends Writable {
|
||||||
encoding?: BinaryToTextEncoding,
|
encoding?: BinaryToTextEncoding,
|
||||||
): Buffer | string {
|
): Buffer | string {
|
||||||
const res = prepareAsymmetricKey(privateKey, kConsumePrivate);
|
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;
|
let handle;
|
||||||
if ("handle" in res) {
|
if ("handle" in res) {
|
||||||
handle = res.handle;
|
handle = res.handle;
|
||||||
|
@ -101,6 +137,8 @@ export class SignImpl extends Writable {
|
||||||
handle,
|
handle,
|
||||||
this.hash.digest(),
|
this.hash.digest(),
|
||||||
this.#digestType,
|
this.#digestType,
|
||||||
|
pssSaltLength,
|
||||||
|
dsaSigEnc,
|
||||||
));
|
));
|
||||||
return encoding ? ret.toString(encoding) : ret;
|
return encoding ? ret.toString(encoding) : ret;
|
||||||
}
|
}
|
||||||
|
@ -152,6 +190,13 @@ export class VerifyImpl extends Writable {
|
||||||
encoding?: BinaryToTextEncoding,
|
encoding?: BinaryToTextEncoding,
|
||||||
): boolean {
|
): boolean {
|
||||||
const res = prepareAsymmetricKey(publicKey, kConsumePublic);
|
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;
|
let handle;
|
||||||
if ("handle" in res) {
|
if ("handle" in res) {
|
||||||
handle = res.handle;
|
handle = res.handle;
|
||||||
|
@ -168,6 +213,8 @@ export class VerifyImpl extends Writable {
|
||||||
this.hash.digest(),
|
this.hash.digest(),
|
||||||
this.#digestType,
|
this.#digestType,
|
||||||
Buffer.from(signature, encoding),
|
Buffer.from(signature, encoding),
|
||||||
|
pssSaltLength,
|
||||||
|
dsaSigEnc,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
import { assert, assertEquals } from "@std/assert";
|
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 { Buffer } from "node:buffer";
|
||||||
import fixtures from "../testdata/crypto_digest_fixtures.json" with {
|
import fixtures from "../testdata/crypto_digest_fixtures.json" with {
|
||||||
type: "json",
|
type: "json",
|
||||||
|
@ -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"));
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue