From: Matt Corallo Date: Sun, 3 Mar 2024 15:05:08 +0000 (+0000) Subject: Swap `ring` for our own in-crate RSA validator X-Git-Tag: v0.5.4~30 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=82f0e7de99a7d8118e6c6a2d938a1f85014b876e;p=dnssec-prover Swap `ring` for our own in-crate RSA 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 RSA validation using a third-party crate. Instead, we go our own way here, adding an in-crate RSA validator. --- diff --git a/src/crypto/hash.rs b/src/crypto/hash.rs index 2f86c89..a74000d 100644 --- a/src/crypto/hash.rs +++ b/src/crypto/hash.rs @@ -12,7 +12,6 @@ pub(crate) enum Hasher { Sha1(::Engine), Sha256(::Engine), Sha384(::Engine), - #[allow(unused)] Sha512(::Engine), } @@ -38,7 +37,6 @@ impl Hasher { pub(crate) fn sha1() -> Hasher { Hasher::Sha1(Sha1::engine()) } pub(crate) fn sha256() -> Hasher { Hasher::Sha256(Sha256::engine()) } pub(crate) fn sha384() -> Hasher { Hasher::Sha384(Sha384::engine()) } - #[allow(unused)] pub(crate) fn sha512() -> Hasher { Hasher::Sha512(Sha512::engine()) } pub(crate) fn update(&mut self, buf: &[u8]) { diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 06d13f2..24f6121 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -21,3 +21,4 @@ pub mod bigint; pub mod hash; +pub mod rsa; diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs new file mode 100644 index 0000000..42cc9b9 --- /dev/null +++ b/src/crypto/rsa.rs @@ -0,0 +1,67 @@ +//! A simple RSA implementation which handles DNSSEC RSA validation + +use super::bigint::*; + +fn bytes_to_rsa_mod_exp_modlen(pubkey: &[u8]) -> Result<(U4096, u32, usize), ()> { + if pubkey.len() <= 3 { return Err(()); } + + let mut pos = 0; + let exponent_length; + if pubkey[0] == 0 { + exponent_length = ((pubkey[1] as usize) << 8) | (pubkey[2] as usize); + pos += 3; + } else { + exponent_length = pubkey[0] as usize; + pos += 1; + } + + if pubkey.len() <= pos + exponent_length { return Err(()); } + if exponent_length > 4 { return Err(()); } + let mut exp_bytes = [0; 4]; + exp_bytes[4 - exponent_length..].copy_from_slice(&pubkey[pos..pos + exponent_length]); + let exp = u32::from_be_bytes(exp_bytes); + + let mod_bytes = &pubkey[pos + exponent_length..]; + let modlen = pubkey.len() - pos - exponent_length; + let modulus = U4096::from_be_bytes(mod_bytes)?; + Ok((modulus, exp, modlen)) +} + +/// Validates the given RSA signature against the given RSA public key (up to 4096-bit, in +/// DNSSEC-encoded form) and given message digest. +pub fn validate_rsa(pk: &[u8], sig_bytes: &[u8], hash_input: &[u8]) -> Result<(), ()> { + let (modulus, exponent, modulus_byte_len) = bytes_to_rsa_mod_exp_modlen(pk)?; + if modulus_byte_len > 512 { /* implied by the U4096, but explicit here */ return Err(()); } + let sig = U4096::from_be_bytes(sig_bytes)?; + + if sig > modulus { return Err(()); } + + // From https://www.rfc-editor.org/rfc/rfc5702#section-3.1 + const SHA256_PFX: [u8; 20] = hex_lit::hex!("003031300d060960864801650304020105000420"); + const SHA512_PFX: [u8; 20] = hex_lit::hex!("003051300d060960864801650304020305000440"); + let pfx = if hash_input.len() == 512 / 8 { &SHA512_PFX } else { &SHA256_PFX }; + + if 512 - 2 - SHA256_PFX.len() <= hash_input.len() { return Err(()); } + let mut hash_bytes = [0; 512]; + let mut hash_write_pos = 512 - hash_input.len(); + hash_bytes[hash_write_pos..].copy_from_slice(&hash_input); + hash_write_pos -= pfx.len(); + hash_bytes[hash_write_pos..hash_write_pos + pfx.len()].copy_from_slice(pfx); + while 512 + 1 - hash_write_pos < modulus_byte_len { + hash_write_pos -= 1; + hash_bytes[hash_write_pos] = 0xff; + } + hash_bytes[hash_write_pos] = 1; + let hash = U4096::from_be_bytes(&hash_bytes)?; + + if hash > modulus { return Err(()); } + + // While modulus could be even, if it were we'd have already factored the modulus (one of the + // primes is two!), so we don't particularly care if we fail spuriously for such spurious keys. + let res = sig.expmod_odd_mod(exponent, &modulus)?; + if res == hash { + Ok(()) + } else { + Err(()) + } +} diff --git a/src/validation.rs b/src/validation.rs index e6b585f..2fb9744 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -47,27 +47,6 @@ pub enum ValidationError { Invalid, } -pub(crate) fn bytes_to_rsa_pk<'a>(pubkey: &'a [u8]) --> Result, ()> { - if pubkey.len() <= 3 { return Err(()); } - - let mut pos = 0; - let exponent_length; - if pubkey[0] == 0 { - exponent_length = ((pubkey[1] as usize) << 8) | (pubkey[2] as usize); - pos += 3; - } else { - exponent_length = pubkey[0] as usize; - pos += 1; - } - - if pubkey.len() <= pos + exponent_length { return Err(()); } - Ok(signature::RsaPublicKeyComponents { - n: &pubkey[pos + exponent_length..], - e: &pubkey[pos..pos + exponent_length] - }) -} - fn verify_rrsig<'a, RR: Record, Keys>(sig: &RRSig, dnskeys: Keys, mut records: Vec<&RR>) -> Result<(), ValidationError> where Keys: IntoIterator { @@ -82,6 +61,15 @@ where Keys: IntoIterator { if dnskey.flags & 0b1_0000_0000 == 0 { continue; } if dnskey.alg != sig.alg { continue; } + let mut hash_ctx = match sig.alg { + 8 => crypto::hash::Hasher::sha256(), + 10 => crypto::hash::Hasher::sha512(), + 13 => crypto::hash::Hasher::sha256(), + 14 => crypto::hash::Hasher::sha384(), + 15 => crypto::hash::Hasher::sha512(), + _ => return Err(ValidationError::UnsupportedAlgorithm), + }; + let mut signed_data = Vec::with_capacity(2048); signed_data.extend_from_slice(&sig.ty.to_be_bytes()); signed_data.extend_from_slice(&sig.alg.to_be_bytes()); @@ -120,13 +108,9 @@ where Keys: IntoIterator { let sig_validation = match sig.alg { 8|10 => { - let alg = if sig.alg == 8 { - &signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY - } else { - &signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY - }; - bytes_to_rsa_pk(&dnskey.pubkey).map_err(|_| ValidationError::Invalid)? - .verify(alg, &signed_data, &sig.signature) + 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 => {