Use `bitcoin_hashes` rather than `ring` for hashing
authorMatt Corallo <git@bluematt.me>
Wed, 21 Feb 2024 02:21:26 +0000 (02:21 +0000)
committerMatt Corallo <git@bluematt.me>
Sat, 2 Mar 2024 16:41:07 +0000 (16:41 +0000)
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, for the best platform support, we'd like to avoid both. Here
we take the first of several steps towards that goal, using
`bitcoin_hashes` for our SHA-1/SHA-2 operations instead.

Cargo.toml
src/crypto/hash.rs [new file with mode: 0644]
src/crypto/mod.rs [new file with mode: 0644]
src/http.rs
src/lib.rs
src/ser.rs
src/validation.rs

index e94264d6035d8c2f61aa45059a3e3122a9591c9a..71db9ba4b5f909a95994f37f5bd13af4c8046510 100644 (file)
@@ -16,12 +16,13 @@ features = ["std", "validation", "tokio"]
 [features]
 default = ["validation"]
 std = []
-validation = ["ring", "hex_lit"]
+validation = ["bitcoin_hashes", "ring", "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.13", 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/src/crypto/hash.rs b/src/crypto/hash.rs
new file mode 100644 (file)
index 0000000..8b913de
--- /dev/null
@@ -0,0 +1,55 @@
+//! Simple wrapper around various hash options to provide a single enum which can calculate
+//! different hashes.
+
+use bitcoin_hashes::Hash;
+use bitcoin_hashes::HashEngine as _;
+use bitcoin_hashes::sha1::Hash as Sha1;
+use bitcoin_hashes::sha256::Hash as Sha256;
+use bitcoin_hashes::sha512::Hash as Sha512;
+
+pub(crate) enum Hasher {
+       Sha1(<Sha1 as Hash>::Engine),
+       Sha256(<Sha256 as Hash>::Engine),
+       #[allow(unused)]
+       Sha512(<Sha512 as Hash>::Engine),
+}
+
+pub(crate) enum HashResult {
+       Sha1(Sha1),
+       Sha256(Sha256),
+       Sha512(Sha512),
+}
+
+impl AsRef<[u8]> for HashResult {
+       fn as_ref(&self) -> &[u8] {
+               match self {
+                       HashResult::Sha1(hash) => hash.as_ref(),
+                       HashResult::Sha256(hash) => hash.as_ref(),
+                       HashResult::Sha512(hash) => hash.as_ref(),
+               }
+       }
+}
+
+impl Hasher {
+       pub(crate) fn sha1() -> Hasher { Hasher::Sha1(Sha1::engine()) }
+       pub(crate) fn sha256() -> Hasher { Hasher::Sha256(Sha256::engine()) }
+       #[allow(unused)]
+       pub(crate) fn sha512() -> Hasher { Hasher::Sha512(Sha512::engine()) }
+
+       pub(crate) fn update(&mut self, buf: &[u8]) {
+               match self {
+                       Hasher::Sha1(hasher) => hasher.input(buf),
+                       Hasher::Sha256(hasher) => hasher.input(buf),
+                       Hasher::Sha512(hasher) => hasher.input(buf),
+               }
+       }
+
+       pub(crate) fn finish(self) -> HashResult {
+               match self {
+                       Hasher::Sha1(hasher) => HashResult::Sha1(Sha1::from_engine(hasher)),
+                       Hasher::Sha256(hasher) => HashResult::Sha256(Sha256::from_engine(hasher)),
+                       Hasher::Sha512(hasher) => HashResult::Sha512(Sha512::from_engine(hasher)),
+               }
+       }
+}
+
diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs
new file mode 100644 (file)
index 0000000..4c501c8
--- /dev/null
@@ -0,0 +1,22 @@
+//! Implementations of cryptographic verification
+//!
+//! Sadly, the choices for cryptographic verification in Rust are somewhat limited. For us (RSA and
+//! secp256r1/secp384r1) there's really only `ring` and `RustCrypto`.
+//!
+//! While `ring` is great, it struggles with platform support and has a fairly involved dependency
+//! tree due to its reliance on C backends.
+//!
+//! `RustCrypto`, on the other hand, tries to stick to Rust, which is great, but in doing so takes
+//! on more (unnecessary) dependencies and has a particularly unusable MSRV policy. Thus, its
+//! somewhat difficult to take on as a dependency.
+//!
+//! Instead, we go our own way here, and luckily actually implementing the required algorithms
+//! isn't all that difficult, at least if we're okay with performance being marginally sub-par.
+//! Because we don't ever do any signing, we don't need to worry about constant-time-ness, further
+//! reducing complexity.
+//!
+//! While we could similarly go our own way on hashing, too, rust-bitcoin's `bitcoin_hashes` crate
+//! does what we need without any unnecessary dependencies and with a very conservative MSRV
+//! policy. Thus we go ahead and use that for our hashing needs.
+
+pub mod hash;
index 073ba66b50ce701ec10cdffb14e0ef24401a219f..9ed34e78e6425cb57616fd29da481acb7a775a8d 100644 (file)
@@ -8,6 +8,8 @@ pub mod rr;
 pub mod ser;
 pub mod query;
 
+#[cfg(feature = "validation")]
+mod crypto;
 #[cfg(feature = "validation")]
 pub mod validation;
 
index 902f6a35853966c26ed8b99214863cad0d6c3052..c0d5f1f4f10920e81bc90869ecb9152d83b1abe0 100644 (file)
@@ -36,5 +36,7 @@ pub mod rr;
 pub mod ser;
 pub mod query;
 
+#[cfg(feature = "validation")]
+mod crypto;
 #[cfg(feature = "validation")]
 pub mod validation;
index 6063ba950b1a9150fe544f88422e8024a77fbbdb..dfd89932e6afd15e46591504dd3f098c7885697a 100644 (file)
@@ -109,7 +109,7 @@ pub(crate) trait Writer { fn write(&mut self, buf: &[u8]); }
 impl Writer for Vec<u8> { fn write(&mut self, buf: &[u8]) { self.extend_from_slice(buf); } }
 impl Writer for QueryBuf { fn write(&mut self, buf: &[u8]) { self.extend_from_slice(buf); } }
 #[cfg(feature = "validation")]
-impl Writer for ring::digest::Context { fn write(&mut self, buf: &[u8]) { self.update(buf); } }
+impl Writer for crate::crypto::hash::Hasher { fn write(&mut self, buf: &[u8]) { self.update(buf); } }
 pub(crate) fn write_name<W: Writer>(out: &mut W, name: &str) {
        let canonical_name = name.to_ascii_lowercase();
        if canonical_name == "." {
index cfcb66f9f73c4321a9c6c86275911cab38f609ca..5d75726e3feb53e88bf278da3deec9157438b7ec 100644 (file)
@@ -7,6 +7,7 @@ use core::cmp;
 
 use ring::signature;
 
+use crate::crypto;
 use crate::rr::*;
 use crate::ser::write_name;
 
@@ -185,13 +186,12 @@ where T: IntoIterator<IntoIter = I>, I: Iterator<Item = &'a DS> + Clone {
                for ds in dses.clone() {
                        if ds.alg != dnskey.alg { continue; }
                        if dnskey.key_tag() == ds.key_tag {
-                               let alg = match ds.digest_type {
-                                       1 if trust_sha1 => &ring::digest::SHA1_FOR_LEGACY_USE_ONLY,
-                                       2 => &ring::digest::SHA256,
-                                       4 => &ring::digest::SHA384,
+                               let mut ctx = match ds.digest_type {
+                                       1 if trust_sha1 => crypto::hash::Hasher::sha1(),
+                                       2 => crypto::hash::Hasher::sha256(),
+                                       // TODO: 4 => crypto::hash::Hasher::sha384(),
                                        _ => continue,
                                };
-                               let mut ctx = ring::digest::Context::new(alg);
                                write_name(&mut ctx, &dnskey.name);
                                ctx.update(&dnskey.flags.to_be_bytes());
                                ctx.update(&dnskey.protocol.to_be_bytes());