From e4503cf90b6ad5ed9edc99389956ceffb1c35502 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 7 Feb 2024 04:25:32 +0000 Subject: [PATCH] Feature-gate validation and document crate features --- Cargo.toml | 6 ++++-- src/http.rs | 44 +++++++++++++++++++++++++++++++++++++----- src/lib.rs | 15 ++++++++++++++- src/query.rs | 4 +--- src/ser.rs | 42 +++++++++++++++++++++------------------- src/validation.rs | 49 +++++++++++++++++++++++------------------------ test.sh | 7 ++++--- 7 files changed, 108 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 602205b..5a71c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,15 @@ edition = "2021" rust-version = "1.60.0" [features] +default = ["validation"] std = [] +validation = ["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"] } -hex_lit = { version = "0.1", default-features = false, features = ["rust_v_1_46"] } +ring = { version = "0.17", default-features = false, features = ["alloc"], 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 } [dev-dependencies] diff --git a/src/http.rs b/src/http.rs index 53a0b18..944bea1 100644 --- a/src/http.rs +++ b/src/http.rs @@ -5,11 +5,13 @@ extern crate alloc; pub mod rr; -pub mod validation; -mod ser; +pub mod ser; pub mod query; -#[cfg(feature = "tokio")] +#[cfg(feature = "validation")] +pub mod validation; + +#[cfg(any(feature = "build_server", all(feature = "tokio", feature = "validation")))] use tokio_crate as tokio; #[cfg(feature = "build_server")] @@ -26,7 +28,7 @@ async fn main() { imp::run_server(listener, resolver_sockaddr).await; } -#[cfg(feature = "tokio")] +#[cfg(any(feature = "build_server", all(feature = "tokio", feature = "validation")))] mod imp { use super::*; @@ -134,7 +136,7 @@ mod imp { } } -#[cfg(all(feature = "tokio", test))] +#[cfg(all(feature = "tokio", feature = "validation", test))] mod test { use super::*; @@ -157,4 +159,36 @@ mod test { let verified_rrs = verify_rr_stream(&rrs).unwrap(); assert_eq!(verified_rrs.verified_rrs.len(), 1); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + async fn test_lookup_a() { + let ns = "4.4.4.4:53".parse().unwrap(); + let listener = tokio::net::TcpListener::bind("127.0.0.1:17493").await + .expect("Failed to bind to socket"); + tokio::spawn(imp::run_server(listener, ns)); + let resp = minreq::get( + "http://127.0.0.1:17493/dnssecproof?d=cloudflare.com.&t=a" + ).send().unwrap(); + + assert_eq!(resp.status_code, 200); + let rrs = parse_rr_stream(resp.as_bytes()).unwrap(); + let verified_rrs = verify_rr_stream(&rrs).unwrap(); + assert_eq!(verified_rrs.verified_rrs.len(), 1); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + async fn test_lookup_tlsa() { + let ns = "1.1.1.1:53".parse().unwrap(); + let listener = tokio::net::TcpListener::bind("127.0.0.1:17494").await + .expect("Failed to bind to socket"); + tokio::spawn(imp::run_server(listener, ns)); + let resp = minreq::get( + "http://127.0.0.1:17494/dnssecproof?d=_25._tcp.mail.as397444.net.&t=TLSA" + ).send().unwrap(); + + assert_eq!(resp.status_code, 200); + let rrs = parse_rr_stream(resp.as_bytes()).unwrap(); + let verified_rrs = verify_rr_stream(&rrs).unwrap(); + assert_eq!(verified_rrs.verified_rrs.len(), 1); + } } diff --git a/src/lib.rs b/src/lib.rs index b25eb99..c7027b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,17 @@ //! //! It is no-std (but requires `alloc`) and seeks to have minimal dependencies and a reasonably //! conservative MSRV policy, allowing it to be used in as many places as possible. +//! +//! Most of the crate's logic is feature-gated: +//! * By default, the `validate` feature is set, using `ring` to validate DNSSEC signatures and +//! proofs using the [`validation`] module. +//! * The `std` feature enables the [`query`] module, allowing for the building of proofs by +//! querying a recursive resolver over TCP. +//! * The `tokio` feature further enables async versions of the [`query`] methods, doing the same +//! querying async using `tokio`'s TCP streams. +//! * Finally, the crate can be built as a binary using the `build_server` feature, responding to +//! queries over HTTP GET calls to `/dnssecproof?d=domain.name.&t=RecordType` with DNSSEC +//! proofs. #![deny(missing_docs)] @@ -20,8 +31,10 @@ extern crate alloc; pub mod rr; +pub mod ser; + +#[cfg(feature = "validation")] pub mod validation; -mod ser; #[cfg(feature = "std")] pub mod query; diff --git a/src/query.rs b/src/query.rs index 548ff15..27f2160 100644 --- a/src/query.rs +++ b/src/query.rs @@ -9,8 +9,6 @@ use tokio_crate::net::TcpStream as TokioTcpStream; #[cfg(feature = "tokio")] use tokio_crate::io::{AsyncReadExt, AsyncWriteExt}; - -use crate::validation::write_rr; use crate::rr::*; use crate::ser::*; @@ -191,7 +189,7 @@ pub async fn build_tlsa_proof_async(resolver: SocketAddr, domain: Name) -> Resul build_proof_async(resolver, domain, TLSA::TYPE).await } -#[cfg(test)] +#[cfg(all(feature = "validation", test))] mod tests { use super::*; use crate::validation::*; diff --git a/src/ser.rs b/src/ser.rs index 462ad92..0f7da16 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,10 +1,8 @@ -//! Serialization/Deserialization logic lives here +//! Logic to read and write resource record (streams) use alloc::vec::Vec; use alloc::string::String; -use ring::signature; - use crate::rr::*; pub(crate) fn read_u8(inp: &mut &[u8]) -> Result { @@ -56,6 +54,7 @@ pub(crate) fn read_wire_packet_name(inp: &mut &[u8], wire_packet: &[u8]) -> Resu pub(crate) trait Writer { fn write(&mut self, buf: &[u8]); } impl Writer for Vec { 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); } } pub(crate) fn write_name(out: &mut W, name: &str) { let canonical_name = name.to_ascii_lowercase(); @@ -110,23 +109,26 @@ pub(crate) fn parse_rr(inp: &mut &[u8]) -> Result { parse_wire_packet_rr(inp, &[]).map(|(rr, _)| rr) } -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; +/// Parse a stream of [`RR`]s from the format described in [RFC 9102](https://www.rfc-editor.org/rfc/rfc9102.html). +/// +/// Note that this is only the series of `AuthenticationChain` records, and does not read the +/// `ExtSupportLifetime` field at the start of a `DnssecChainExtension`. +pub fn parse_rr_stream(mut inp: &[u8]) -> Result, ()> { + let mut res = Vec::with_capacity(32); + while !inp.is_empty() { + res.push(parse_rr(&mut inp)?); } + Ok(res) +} - if pubkey.len() <= pos + exponent_length { return Err(()); } - Ok(signature::RsaPublicKeyComponents { - n: &pubkey[pos + exponent_length..], - e: &pubkey[pos..pos + exponent_length] - }) +/// Writes the given resource record in its wire encoding to the given `Vec`. +/// +/// An [RFC 9102](https://www.rfc-editor.org/rfc/rfc9102.html) `AuthenticationChain` is simply a +/// series of such records with no additional bytes in between. +pub fn write_rr(rr: &RR, ttl: u32, out: &mut Vec) { + write_name(out, rr.name()); + out.extend_from_slice(&rr.ty().to_be_bytes()); + out.extend_from_slice(&1u16.to_be_bytes()); // The INternet class + out.extend_from_slice(&ttl.to_be_bytes()); + rr.write_u16_len_prefixed_data(out); } diff --git a/src/validation.rs b/src/validation.rs index 491cbaa..892cbf5 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -7,7 +7,7 @@ use core::cmp; use ring::signature; use crate::rr::*; -use crate::ser::{bytes_to_rsa_pk, parse_rr, write_name}; +use crate::ser::write_name; /// Gets the trusted root anchors /// @@ -30,30 +30,6 @@ pub fn root_hints() -> Vec { res } -/// Parse a stream of [`RR`]s from the format described in [RFC 9102](https://www.rfc-editor.org/rfc/rfc9102.html). -/// -/// Note that this is only the series of `AuthenticationChain` records, and does not read the -/// `ExtSupportLifetime` field at the start of a `DnssecChainExtension`. -pub fn parse_rr_stream(mut inp: &[u8]) -> Result, ()> { - let mut res = Vec::with_capacity(32); - while !inp.is_empty() { - res.push(parse_rr(&mut inp)?); - } - Ok(res) -} - -/// Writes the given resource record in its wire encoding to the given `Vec`. -/// -/// An [RFC 9102](https://www.rfc-editor.org/rfc/rfc9102.html) `AuthenticationChain` is simply a -/// series of such records with no additional bytes in between. -pub fn write_rr(rr: &RR, ttl: u32, out: &mut Vec) { - write_name(out, rr.name()); - out.extend_from_slice(&rr.ty().to_be_bytes()); - out.extend_from_slice(&1u16.to_be_bytes()); // The INternet class - out.extend_from_slice(&ttl.to_be_bytes()); - rr.write_u16_len_prefixed_data(out); -} - #[derive(Debug, PartialEq)] /// An error when validating DNSSEC signatures or other data pub enum ValidationError { @@ -68,6 +44,27 @@ 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 { @@ -358,6 +355,8 @@ mod tests { use alloc::borrow::ToOwned; + use crate::ser::{parse_rr_stream, write_rr}; + use hex_conservative::FromHex; use rand::seq::SliceRandom; diff --git a/test.sh b/test.sh index 36609fd..3346b74 100755 --- a/test.sh +++ b/test.sh @@ -1,9 +1,10 @@ #!/bin/sh set -eox +cargo test --no-default-features cargo test -cargo test --features std -cargo test --features tokio -cargo test --features build_server +cargo test --no-default-features --features std +cargo test --no-default-features --features tokio +cargo test --no-default-features --features build_server cargo build --lib cargo build --lib --features std cargo build --lib --features tokio -- 2.39.5