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> {
let mut had_ds = false;
for ds in dses.clone() {
had_ds = true;
- if ds.digest_type == 2 || ds.digest_type == 4 {
+ if ds.digest_type == 1 || ds.digest_type == 2 || ds.digest_type == 4 {
had_known_digest_type = true;
break;
}
if !had_known_digest_type { return Err(ValidationError::UnsupportedAlgorithm); }
for dnskey in records.iter() {
+ // Only use SHA1 DS records if we don't have any SHA256/SHA384 DS RRs.
+ let trust_sha1 = dses.clone().all(|ds| ds.digest_type != 2 && ds.digest_type != 4);
for ds in dses.clone() {
- if ds.digest_type != 2 && ds.digest_type != 4 { continue; }
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,
_ => continue,
/// Given a set of [`RR`]s, [`verify_rr_stream`] checks what it can and returns the set of
/// non-[`RRSig`]/[`DnsKey`]/[`DS`] records which it was able to verify using this struct.
///
-/// It also contains
+/// It also contains signing and expiry times, which must be validated before considering the
+/// contained records verified.
#[derive(Debug, Clone)]
pub struct VerifiedRRStream<'a> {
/// The set of verified [`RR`]s.
use alloc::borrow::ToOwned;
+ use crate::ser::{parse_rr_stream, write_rr};
+
use hex_conservative::FromHex;
use rand::seq::SliceRandom;
(cname_resp, cname_rrsig, txt_resp, txt_rrsig)
}
+ fn matcorallo_txt_sort_edge_cases_records() -> (Vec<Txt>, RRSig) {
+ let txts = vec![Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaa".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned().into_bytes(),
+ }, Txt {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba".to_owned().into_bytes(),
+ }];
+ let rrsig = RRSig {
+ name: "txt_sort_order.matcorallo.com.".try_into().unwrap(),
+ ty: Txt::TYPE, alg: 13, labels: 3, orig_ttl: 30, expiration: 1708449632,
+ inception: 1707234632, key_tag: 34530, key_name: "matcorallo.com.".try_into().unwrap(),
+ signature: base64::decode("elAhELwzkGpUMvzeiYZpg1+yRFPjmOeEd1ir1vYx2Dku9kzsXmAlejOYDPWdaJ6ekvHdMejCN/MtyI+iFAYqsw==").unwrap(),
+ };
+ (txts, rrsig)
+ }
+
#[test]
fn check_txt_record_a() {
let dnskeys = mattcorallo_dnskey().0;
} else { panic!(); }
}
+ #[test]
+ fn check_txt_sort_order() {
+ let mut rr_stream = Vec::new();
+ for rr in root_dnskey().1 { write_rr(&rr, 1, &mut rr_stream); }
+ for rr in com_dnskey().1 { write_rr(&rr, 1, &mut rr_stream); }
+ for rr in matcorallo_dnskey().1 { write_rr(&rr, 1, &mut rr_stream); }
+ let (mut txts, rrsig) = matcorallo_txt_sort_edge_cases_records();
+ write_rr(&rrsig, 1, &mut rr_stream);
+ for txt in txts.iter() { write_rr(txt, 1, &mut rr_stream); }
+
+ let mut rrs = parse_rr_stream(&rr_stream).unwrap();
+ rrs.shuffle(&mut rand::rngs::OsRng);
+ let verified_rrs = verify_rr_stream(&rrs).unwrap();
+ let mut verified_txts = verified_rrs.verified_rrs
+ .iter().map(|rr| if let RR::Txt(txt) = rr { txt.clone() } else { panic!(); })
+ .collect::<Vec<_>>();
+ verified_txts.sort();
+ txts.sort();
+ assert_eq!(verified_txts, txts);
+ }
+
#[test]
fn rfc9102_parse_test() {
// Note that this is the `AuthenticationChain` field only, and ignores the