From: Matt Corallo Date: Mon, 4 Mar 2024 03:22:08 +0000 (+0000) Subject: Swap `ring` for our own in-crate ECDSA validator X-Git-Tag: v0.5.4~27 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=c9fd1919a8cf2bf5e9d0d570b99f58d83854f990;p=dnssec-prover Swap `ring` for our own in-crate ECDSA validator While `ring` is great, it struggles with platform support and has a fairly involved dependency tree due to its reliance on C backends. Further, while the `RustCrypto` org tries to stick to Rust, in doing so it takes on more (unnecessary) dependencies and has a particularly unusable MSRV policy. Finally, its contributor base has historically not been particularly friendly. Thus, sadly, there's not really a good option for doing ECDSA (non- secp256k1) validation using a third-party crate. Instead, we go our own way here, adding an in-crate ECDSA validator over secp{256,384}r1. This also adds a new bench, showing our secp256r1 validation is, sadly, something like 50x slower than OpenSSL. --- diff --git a/Cargo.toml b/Cargo.toml index 7dd5d41..cab20d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,11 @@ features = ["std", "validation", "tokio"] [features] default = ["validation"] std = [] -validation = ["bitcoin_hashes", "ring", "hex_lit"] +validation = ["bitcoin_hashes", "hex_lit"] tokio = ["tokio_crate/net", "tokio_crate/io-util", "std"] build_server = ["tokio", "tokio_crate/rt-multi-thread", "tokio_crate/macros"] [dependencies] -ring = { version = "0.17", default-features = false, features = ["alloc"], optional = true } bitcoin_hashes = { version = "0.14", default-features = false, optional = true } hex_lit = { version = "0.1", default-features = false, features = ["rust_v_1_46"], optional = true } tokio_crate = { package = "tokio", version = "1.0", default-features = false, optional = true } diff --git a/bench/benches/bench.rs b/bench/benches/bench.rs index 2d963bc..4b5d6e4 100644 --- a/bench/benches/bench.rs +++ b/bench/benches/bench.rs @@ -1,6 +1,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use dnssec_prover::crypto::rsa::validate_rsa; +use dnssec_prover::crypto::secp256r1; pub fn bench_validate_rsa(bench: &mut Criterion) { // A signature by the root key over the root DNSKEY RRSet @@ -12,7 +13,18 @@ pub fn bench_validate_rsa(bench: &mut Criterion) { })); } +pub fn bench_validate_secp256r1(bench: &mut Criterion) { + // A signature by the com. key over the com. DNSKEY RRSet + let pk = [183, 31, 4, 101, 16, 29, 219, 226, 191, 12, 148, 85, 209, 47, 161, 108, 28, 218, 68, 244, 191, 27, 162, 85, 52, 24, 173, 31, 58, 169, 176, 105, 115, 242, 27, 132, 235, 83, 44, 244, 3, 94, 232, 212, 131, 44, 162, 109, 137, 48, 106, 125, 50, 86, 12, 12, 176, 18, 157, 69, 10, 193, 8, 53]; + let sig = [207, 89, 121, 239, 214, 5, 201, 157, 91, 15, 126, 57, 251, 60, 13, 82, 33, 137, 67, 212, 128, 161, 32, 93, 133, 247, 165, 154, 143, 126, 112, 177, 71, 23, 220, 48, 182, 191, 235, 38, 123, 7, 183, 244, 255, 239, 156, 194, 199, 48, 23, 100, 97, 240, 232, 81, 92, 31, 150, 66, 123, 249, 135, 224]; + let rrset_hash = [148, 51, 14, 246, 192, 70, 100, 66, 31, 67, 171, 251, 100, 195, 224, 203, 145, 160, 238, 28, 26, 167, 115, 82, 17, 58, 83, 114, 97, 53, 182, 30]; + bench.bench_function("validate_secp256r1", |b| b.iter(|| { + secp256r1::validate_ecdsa(&pk, &sig, &rrset_hash).unwrap(); + })); +} + criterion_group!(benches, bench_validate_rsa, + bench_validate_secp256r1, ); criterion_main!(benches); diff --git a/src/crypto/ec.rs b/src/crypto/ec.rs new file mode 100644 index 0000000..bd8c5cf --- /dev/null +++ b/src/crypto/ec.rs @@ -0,0 +1,292 @@ +//! Simple verification of ECDSA signatures over SECP Random curves + +use super::bigint::*; + +pub(super) trait IntMod: Clone + Eq + Sized { + type I: Int; + fn from_i(v: Self::I) -> Self; + fn from_modinv_of(v: Self::I) -> Result; + + const ZERO: Self; + const ONE: Self; + + fn mul(&self, o: &Self) -> Self; + fn square(&self) -> Self; + fn add(&self, o: &Self) -> Self; + fn sub(&self, o: &Self) -> Self; + fn double(&self) -> Self; + fn times_three(&self) -> Self; + fn times_four(&self) -> Self; + fn times_eight(&self) -> Self; + + fn into_i(self) -> Self::I; +} +impl + Clone + Eq> IntMod for U256Mod { + type I = U256; + fn from_i(v: Self::I) -> Self { U256Mod::from_u256(v) } + fn from_modinv_of(v: Self::I) -> Result { U256Mod::from_modinv_of(v) } + + const ZERO: Self = U256Mod::::from_u256_panicking(U256::zero()); + const ONE: Self = U256Mod::::from_u256_panicking(U256::one()); + + fn mul(&self, o: &Self) -> Self { self.mul(o) } + fn square(&self) -> Self { self.square() } + fn add(&self, o: &Self) -> Self { self.add(o) } + fn sub(&self, o: &Self) -> Self { self.sub(o) } + fn double(&self) -> Self { self.double() } + fn times_three(&self) -> Self { self.times_three() } + fn times_four(&self) -> Self { self.times_four() } + fn times_eight(&self) -> Self { self.times_eight() } + + fn into_i(self) -> Self::I { self.into_u256() } +} +impl + Clone + Eq> IntMod for U384Mod { + type I = U384; + fn from_i(v: Self::I) -> Self { U384Mod::from_u384(v) } + fn from_modinv_of(v: Self::I) -> Result { U384Mod::from_modinv_of(v) } + + const ZERO: Self = U384Mod::::from_u384_panicking(U384::zero()); + const ONE: Self = U384Mod::::from_u384_panicking(U384::one()); + + fn mul(&self, o: &Self) -> Self { self.mul(o) } + fn square(&self) -> Self { self.square() } + fn add(&self, o: &Self) -> Self { self.add(o) } + fn sub(&self, o: &Self) -> Self { self.sub(o) } + fn double(&self) -> Self { self.double() } + fn times_three(&self) -> Self { self.times_three() } + fn times_four(&self) -> Self { self.times_four() } + fn times_eight(&self) -> Self { self.times_eight() } + + fn into_i(self) -> Self::I { self.into_u384() } +} + +pub(super) trait Curve : Copy { + type Int: Int; + + // With const generics, both IntModP and IntModN can be replaced with a single IntMod. + type IntModP: IntMod; + type IntModN: IntMod; + + type P: PrimeModulus; + type N: PrimeModulus; + + // Curve parameters y^2 = x^3 + ax + b + const A: Self::IntModP; + const B: Self::IntModP; + + const G: Point; +} + +#[derive(Clone, PartialEq, Eq)] +pub(super) struct Point { + x: C::IntModP, + y: C::IntModP, + z: C::IntModP, +} + +impl Point { + fn on_curve(x: &C::IntModP, y: &C::IntModP) -> Result<(), ()> { + let x_2 = x.square(); + let x_3 = x_2.mul(&x); + let v = x_3.add(&C::A.mul(&x)).add(&C::B); + + let y_2 = y.square(); + if y_2 != v { + Err(()) + } else { + Ok(()) + } + } + + #[cfg(debug_assertions)] + fn on_curve_z(x: &C::IntModP, y: &C::IntModP, z: &C::IntModP) -> Result<(), ()> { + let m = C::IntModP::from_modinv_of(z.clone().into_i())?; + let m_2 = m.square(); + let m_3 = m_2.mul(&m); + let x_norm = x.mul(&m_2); + let y_norm = y.mul(&m_3); + Self::on_curve(&x_norm, &y_norm) + } + + #[cfg(test)] + fn normalize_x(&self) -> Result { + let m = C::IntModP::from_modinv_of(self.z.clone().into_i())?; + Ok(self.x.mul(&m.square())) + } + + fn from_xy(x: C::Int, y: C::Int) -> Result { + let x = C::IntModP::from_i(x); + let y = C::IntModP::from_i(y); + Self::on_curve(&x, &y)?; + Ok(Point { x, y, z: C::IntModP::ONE }) + } + + pub(super) const fn from_xy_assuming_on_curve(x: C::IntModP, y: C::IntModP) -> Self { + Point { x, y, z: C::IntModP::ONE } + } + + /// Checks that `expected_x` is equal to our X affine coordinate (without modular inversion). + fn eq_x(&self, expected_x: &C::IntModN) -> Result<(), ()> { + debug_assert!(expected_x.clone().into_i() < C::P::PRIME, "N is < P"); + + // If x is between N and P the below calculations will fail and we'll spuriously reject a + // signature and the wycheproof tests will fail. We should in theory accept such + // signatures, but the probability of this happening at random is roughly 1/2^128, i.e. we + // really don't need to handle it in practice. Thus, we only bother to do this in tests. + #[allow(unused_mut, unused_assignments)] + let mut slow_check = None; + #[cfg(test)] { + slow_check = Some(C::IntModN::from_i(self.normalize_x()?.into_i()) == *expected_x); + } + + let e: C::IntModP = C::IntModP::from_i(expected_x.clone().into_i()); + if self.z == C::IntModP::ZERO { return Err(()); } + let ezz = e.mul(&self.z).mul(&self.z); + if self.x == ezz { Ok(()) } else { + if slow_check == Some(true) { Ok(()) } else { Err(()) } + } + } + + fn double(&self) -> Result { + if self.y == C::IntModP::ZERO { return Err(()); } + if self.z == C::IntModP::ZERO { return Err(()); } + + let s = self.x.times_four().mul(&self.y.square()); + let z_2 = self.z.square(); + let z_4 = z_2.square(); + let y_2 = self.y.square(); + let y_4 = y_2.square(); + let x_2 = self.x.square(); + let m = x_2.times_three().add(&C::A.mul(&z_4)); + let x = m.square().sub(&s.double()); + let y = m.mul(&s.sub(&x)).sub(&y_4.times_eight()); + let z = self.y.double().mul(&self.z); + + #[cfg(debug_assertions)] { assert!(Self::on_curve_z(&x, &y, &z).is_ok()); } + Ok(Point { x, y, z }) + } + + fn add(&self, o: &Self) -> Result { + let o_z_2 = o.z.square(); + let self_z_2 = self.z.square(); + + let u1 = self.x.mul(&o_z_2); + let u2 = o.x.mul(&self_z_2); + let s1 = self.y.mul(&o.z.mul(&o_z_2)); + let s2 = o.y.mul(&self.z.mul(&self_z_2)); + if u1 == u2 { + if s1 != s2 { /* PAI */ return Err(()); } + return self.double(); + } + let h = u2.sub(&u1); + let h_2 = h.square(); + let h_3 = h.mul(&h_2); + let r = s2.sub(&s1); + let x = r.square().sub(&h_3).sub(&u1.double().mul(&h_2)); + let y = r.mul(&u1.mul(&h_2).sub(&x)).sub(&s1.mul(&h_3)); + let z = h.mul(&self.z).mul(&o.z); + + #[cfg(debug_assertions)] { assert!(Self::on_curve_z(&x, &y, &z).is_ok()); } + Ok(Point { x, y, z}) + } +} + +/// Calculates i * I + j * J +#[allow(non_snake_case)] +fn add_two_mul(i: C::IntModN, I: &Point, j: C::IntModN, J: &Point) -> Result, ()> { + let i = i.into_i(); + let j = j.into_i(); + + if i == C::Int::ZERO { /* Infinity */ return Err(()); } + if j == C::Int::ZERO { /* Infinity */ return Err(()); } + + let mut res_opt: Result, ()> = Err(()); + let i_limbs = i.limbs(); + let j_limbs = j.limbs(); + let mut skip_limb = 0; + let mut limbs_skip_iter = i_limbs.iter().zip(j_limbs.iter()); + while limbs_skip_iter.next() == Some((&0, &0)) { + skip_limb += 1; + } + for (idx, (il, jl)) in i_limbs.iter().zip(j_limbs.iter()).skip(skip_limb).enumerate() { + let start_bit = if idx == 0 { + core::cmp::min(il.leading_zeros(), jl.leading_zeros()) + } else { 0 }; + for b in start_bit..64 { + let i_bit = (*il & (1 << (63 - b))) != 0; + let j_bit = (*jl & (1 << (63 - b))) != 0; + if let Ok(res) = res_opt.as_mut() { + *res = res.double()?; + } + if i_bit { + if let Ok(res) = res_opt.as_mut() { + // The wycheproof tests expect to see signatures pass even if we hit PAI on an + // intermediate result. While that's fine, I'm too lazy to go figure out if all + // our PAI definitions are right and the probability of this happening at + // random is, basically, the probability of guessing a private key anyway, so + // its not really worth actually handling outside of tests. + #[cfg(test)] { + res_opt = res.add(I); + } + #[cfg(not(test))] { + *res = res.add(I)?; + } + } else { + res_opt = Ok(I.clone()); + } + } + if j_bit { + if let Ok(res) = res_opt.as_mut() { + // The wycheproof tests expect to see signatures pass even if we hit PAI on an + // intermediate result. While that's fine, I'm too lazy to go figure out if all + // our PAI definitions are right and the probability of this happening at + // random is, basically, the probability of guessing a private key anyway, so + // its not really worth actually handling outside of tests. + #[cfg(test)] { + res_opt = res.add(J); + } + #[cfg(not(test))] { + *res = res.add(J)?; + } + } else { + res_opt = Ok(J.clone()); + } + } + } + } + res_opt +} + +/// Validates the given signature against the given public key and message digest. +pub(super) fn validate_ecdsa(pk: &[u8], sig: &[u8], hash_input: &[u8]) -> Result<(), ()> { + #![allow(non_snake_case)] + + if pk.len() != C::Int::BYTES * 2 { return Err(()); } + if sig.len() != C::Int::BYTES * 2 { return Err(()); } + + let (r_bytes, s_bytes) = sig.split_at(C::Int::BYTES); + let (pk_x_bytes, pk_y_bytes) = pk.split_at(C::Int::BYTES); + + let pk_x = C::Int::from_be_bytes(pk_x_bytes)?; + let pk_y = C::Int::from_be_bytes(pk_y_bytes)?; + let PK = Point::from_xy(pk_x, pk_y)?; + + // from_i and from_modinv_of both will simply mod if the value is out of range. While its + // perfectly safe to do so, the wycheproof tests expect such signatures to be rejected, so we + // do so here. + let r_u256 = C::Int::from_be_bytes(r_bytes)?; + if r_u256 > C::N::PRIME { return Err(()); } + let s_u256 = C::Int::from_be_bytes(s_bytes)?; + if s_u256 > C::N::PRIME { return Err(()); } + + let r = C::IntModN::from_i(r_u256); + let s_inv = C::IntModN::from_modinv_of(s_u256)?; + + let z = C::IntModN::from_i(C::Int::from_be_bytes(hash_input)?); + + let u_a = z.mul(&s_inv); + let u_b = r.mul(&s_inv); + + let V = add_two_mul(u_a, &C::G, u_b, &PK)?; + V.eq_x(&r) +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 24f6121..eaeeb92 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -20,5 +20,8 @@ //! policy. Thus we go ahead and use that for our hashing needs. pub mod bigint; +mod ec; pub mod hash; pub mod rsa; +pub mod secp256r1; +pub mod secp384r1; diff --git a/src/crypto/secp256r1.rs b/src/crypto/secp256r1.rs new file mode 100644 index 0000000..41d4851 --- /dev/null +++ b/src/crypto/secp256r1.rs @@ -0,0 +1,54 @@ +//! secp256r1 validation for DNSSEC signatures + +use super::bigint::*; +use super::ec; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct P(); +impl PrimeModulus for P { + const PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff")); + const R_SQUARED_MOD_PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "00000004fffffffdfffffffffffffffefffffffbffffffff0000000000000003")); + const NEGATIVE_PRIME_INV_MOD_R: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "ffffffff00000002000000000000000000000001000000000000000000000001")); +} +#[derive(Clone, Copy, PartialEq, Eq)] +struct N(); +impl PrimeModulus for N { + const PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551")); + const R_SQUARED_MOD_PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "66e12d94f3d956202845b2392b6bec594699799c49bd6fa683244c95be79eea2")); + const NEGATIVE_PRIME_INV_MOD_R: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "60d06633a9d6281c50fe77ecc588c6f648c944087d74d2e4ccd1c8aaee00bc4f")); +} + +#[derive(Clone, Copy)] +struct P256(); + +impl ec::Curve for P256 { + type Int = U256; + type IntModP = U256Mod

; + type IntModN = U256Mod; + + type P = P; + type N = N; + + const A: U256Mod

= U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "ffffffff00000001000000000000000000000000fffffffffffffffffffffffc"))); + const B: U256Mod

= U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"))); + + const G: ec::Point = ec::Point::from_xy_assuming_on_curve( + U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"))), + U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!( + "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"))), + ); +} + +/// Validates the given signature against the given public key and message digest. +pub fn validate_ecdsa(pk: &[u8], sig: &[u8], hash_input: &[u8]) -> Result<(), ()> { + ec::validate_ecdsa::(pk, sig, hash_input) +} diff --git a/src/crypto/secp384r1.rs b/src/crypto/secp384r1.rs new file mode 100644 index 0000000..9c99ad3 --- /dev/null +++ b/src/crypto/secp384r1.rs @@ -0,0 +1,54 @@ +//! secp384r1 validation for DNSSEC signatures + +use super::bigint::*; +use super::ec; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct P(); +impl PrimeModulus for P { + const PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff")); + const R_SQUARED_MOD_PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "000000000000000000000000000000010000000200000000fffffffe000000000000000200000000fffffffe00000001")); + const NEGATIVE_PRIME_INV_MOD_R: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "00000014000000140000000c00000002fffffffcfffffffafffffffbfffffffe00000000000000010000000100000001")); +} +#[derive(Clone, Copy, PartialEq, Eq)] +struct N(); +impl PrimeModulus for N { + const PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973")); + const R_SQUARED_MOD_PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "0c84ee012b39bf213fb05b7a28266895d40d49174aab1cc5bc3e483afcb82947ff3d81e5df1aa4192d319b2419b409a9")); + const NEGATIVE_PRIME_INV_MOD_R: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "355ca87de39dbb1fa150206ce4f194ac78d4ba5866d61787ee6c8e3df45624ce54a885995d20bb2b6ed46089e88fdc45")); +} + +#[derive(Clone, Copy)] +struct P384(); + +impl ec::Curve for P384 { + type Int = U384; + type IntModP = U384Mod

; + type IntModN = U384Mod; + + type P = P; + type N = N; + + const A: U384Mod

= U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"))); + const B: U384Mod

= U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"))); + + const G: ec::Point = ec::Point::from_xy_assuming_on_curve( + U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"))), + U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!( + "3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"))), + ); +} + +/// Validates the given signature against the given public key and message digest. +pub fn validate_ecdsa(pk: &[u8], sig: &[u8], hash_input: &[u8]) -> Result<(), ()> { + ec::validate_ecdsa::(pk, sig, hash_input) +} diff --git a/src/validation.rs b/src/validation.rs index 2fb9744..692bb63 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -5,8 +5,6 @@ use alloc::vec::Vec; use alloc::vec; use core::cmp::{self, Ordering}; -use ring::signature; - use crate::base32; use crate::crypto; use crate::rr::*; @@ -106,34 +104,16 @@ where Keys: IntoIterator { record.write_u16_len_prefixed_data(&mut signed_data); } + hash_ctx.update(&signed_data); + let hash = hash_ctx.finish(); let sig_validation = match sig.alg { - 8|10 => { - hash_ctx.update(&signed_data); - let hash = hash_ctx.finish(); - crypto::rsa::validate_rsa(&dnskey.pubkey, &sig.signature, hash.as_ref()) - .map_err(|_| ValidationError::Invalid) - }, - 13|14 => { - let alg = if sig.alg == 13 { - &signature::ECDSA_P256_SHA256_FIXED - } else { - &signature::ECDSA_P384_SHA384_FIXED - }; - - // Add 0x4 identifier to the ECDSA pubkey as expected by ring. - let mut key = Vec::with_capacity(dnskey.pubkey.len() + 1); - key.push(0x4); - key.extend_from_slice(&dnskey.pubkey); - - signature::UnparsedPublicKey::new(alg, &key) - .verify(&signed_data, &sig.signature) - .map_err(|_| ValidationError::Invalid) - }, - 15 => { - signature::UnparsedPublicKey::new(&signature::ED25519, &dnskey.pubkey) - .verify(&signed_data, &sig.signature) - .map_err(|_| ValidationError::Invalid) - }, + 8|10 => crypto::rsa::validate_rsa(&dnskey.pubkey, &sig.signature, hash.as_ref()) + .map_err(|_| ValidationError::Invalid), + 13 => crypto::secp256r1::validate_ecdsa(&dnskey.pubkey, &sig.signature, hash.as_ref()) + .map_err(|_| ValidationError::Invalid), + 14 => crypto::secp384r1::validate_ecdsa(&dnskey.pubkey, &sig.signature, hash.as_ref()) + .map_err(|_| ValidationError::Invalid), + // TODO: 15 => ED25519 _ => return Err(ValidationError::UnsupportedAlgorithm), }; #[cfg(fuzzing)] {