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]
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")]
imp::run_server(listener, resolver_sockaddr).await;
}
-#[cfg(feature = "tokio")]
+#[cfg(any(feature = "build_server", all(feature = "tokio", feature = "validation")))]
mod imp {
use super::*;
}
}
-#[cfg(all(feature = "tokio", test))]
+#[cfg(all(feature = "tokio", feature = "validation", test))]
mod test {
use super::*;
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);
+ }
}
//!
//! 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)]
extern crate alloc;
pub mod rr;
+pub mod ser;
+
+#[cfg(feature = "validation")]
pub mod validation;
-mod ser;
#[cfg(feature = "std")]
pub mod query;
#[cfg(feature = "tokio")]
use tokio_crate::io::{AsyncReadExt, AsyncWriteExt};
-
-use crate::validation::write_rr;
use crate::rr::*;
use crate::ser::*;
build_proof_async(resolver, domain, TLSA::TYPE).await
}
-#[cfg(test)]
+#[cfg(all(feature = "validation", test))]
mod tests {
use super::*;
use crate::validation::*;
-//! 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<u8, ()> {
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); } }
+#[cfg(feature = "validation")]
impl Writer for ring::digest::Context { 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();
parse_wire_packet_rr(inp, &[]).map(|(rr, _)| rr)
}
-pub(crate) fn bytes_to_rsa_pk<'a>(pubkey: &'a [u8])
--> Result<signature::RsaPublicKeyComponents<&'a [u8]>, ()> {
- 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<Vec<RR>, ()> {
+ 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: Record>(rr: &RR, ttl: u32, out: &mut Vec<u8>) {
+ 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);
}
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
///
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<Vec<RR>, ()> {
- 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: Record>(rr: &RR, ttl: u32, out: &mut Vec<u8>) {
- 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 {
Invalid,
}
+pub(crate) fn bytes_to_rsa_pk<'a>(pubkey: &'a [u8])
+-> Result<signature::RsaPublicKeyComponents<&'a [u8]>, ()> {
+ 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<Item = &'a DnsKey> {
use alloc::borrow::ToOwned;
+ use crate::ser::{parse_rr_stream, write_rr};
+
use hex_conservative::FromHex;
use rand::seq::SliceRandom;
#!/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