Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dnssec' into dnssec-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Litr0 committed Jul 25, 2024
2 parents 7ff2732 + 25c7575 commit 6e8e030
Show file tree
Hide file tree
Showing 19 changed files with 1,953 additions and 229 deletions.
593 changes: 439 additions & 154 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ tokio-stream = "0.1"
thiserror = "1.0.20"
futures-util = "0.3.28"
async-trait = "0.1.77"
sha2 = "0.10.2"
hmac = "0.12.1"
rust-crypto = "0.2"
base64 = "0.22.1"

lru = "0.12.3"

[lib]
Expand Down
4 changes: 4 additions & 0 deletions src/dnssec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod dnssec_message;
pub mod dnssec_message_processing;
pub mod dnssec_fetch;
pub mod rrset_signature;
20 changes: 20 additions & 0 deletions src/dnssec/dnssec_fetch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::message::DnsMessage;
use crate::message::rdata::Rdata;
use crate::message::rdata::dnskey_rdata::DnskeyRdata;
use crate::message::resource_record::ResourceRecord;
use crate::dnssec::dnssec_message_processing::extract_dnssec_records;
use crate::dnssec::rrset_signature::{verify_rrsig, verify_ds};

use crate::client::client_error::ClientError;

pub async fn fetch_dnskey_records(dns_response: &DnsMessage) -> Result<Vec<DnskeyRdata>, ClientError> {
let mut dnskey_records = Vec::new();

for record in dns_response.get_answer() {
if let Rdata::DNSKEY(dnskey) = &record.get_rdata() {
dnskey_records.push(dnskey.clone());
}
}

Ok(dnskey_records)
}
86 changes: 86 additions & 0 deletions src/dnssec/dnssec_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::str::FromStr;
use crate::domain_name::DomainName;
use crate::message::rclass::Rclass;
use crate::message::DnsMessage;
use crate::message::rdata::opt_rdata::OptRdata;
use crate::message::rdata::Rdata;
use crate::message::resource_record::{FromBytes, ResourceRecord, ToBytes};
use crate::message::rcode;
use crate::message::rcode::Rcode;
use crate::message::rrtype::Rrtype;

const EDNS_VERSION: u8 = 0;
const REQUESTED_UDP_LEN: u16 = 4096;
/*
The mechanism chosen for the explicit notification of the ability of
the client to accept (if not understand) DNSSEC security RRs is using
the most significant bit of the Z field on the EDNS0 OPT header in
the query. This bit is referred to as the "DNSSEC OK" (DO) bit. In
the context of the EDNS0 OPT meta-RR, the DO bit is the first bit of
the third and fourth bytes of the "extended RCODE and flags" portion
of the EDNS0 OPT meta-RR, structured as follows:
+0 (MSB) +1 (LSB)
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
0: | EXTENDED-RCODE | VERSION |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
2: |DO| Z |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
fn create_opt_rr(capacity: u16 ,e_rcode :u8, version: u8, do_bit: bool) -> ResourceRecord {
let opt_rdata = OptRdata::new();
let rdata = Rdata::OPT(opt_rdata);
let mut rr = ResourceRecord::new(rdata);

let do_val: u16 = if do_bit {0x8000} else {0x0};
let ttl: u32 = (e_rcode as u32) << 24 | (version as u32) << 16| (do_val as u32);
rr.set_ttl(ttl);
rr.set_rclass(Rclass::UNKNOWN(capacity));
println!("EL ttl es: {:#05x?}", ttl);
rr
}

fn read_opt_rr(opt_rr: ResourceRecord) -> String {
let requested_udp_len = Rclass::from(opt_rr.get_rclass());
let data = opt_rr.get_ttl().to_be_bytes();
let (e_rcode, version) = (data[0], data[1]);
let z = u16::from_be_bytes([data[2], data[3]]);

let do_bit = ((z & 0x8000) > 0) as u8 ;
format!("OPT PSEUDO-RR\n\trequested_udp_len: {requested_udp_len}\n\terror code: {e_rcode}\n\tversion: EDNS{version}\n\tuse dnssec: {do_bit}")
}

/*
A security-aware resolver MUST include an EDNS ([RFC2671]) OPT
pseudo-RR with the DO ([RFC3225]) bit set when sending queries.
*/
fn create_dns_message_with_dnssec(mut msg: DnsMessage) -> DnsMessage {
// We create a opt rr with the do bit set to 1
// with NOERR as rcode and EDNS0
let rr = create_opt_rr(REQUESTED_UDP_LEN,
Rcode::from(Rcode::NOERROR).into(),
EDNS_VERSION,
true);

let vec = vec![rr];
msg.add_additionals(vec);
msg
}

#[test]
fn see_dnssec_message() {
let query = DnsMessage::new_query_message(
DomainName::new_from_str("example.com"),
Rrtype::A,
Rclass::UNKNOWN(4096),
1,
true,
2000
);
let query= create_dns_message_with_dnssec(query);
assert_eq!(String::from_str
("OPT PSEUDO-RR\n\trequested_udp_len: 4096\n\terror code: 0\n\tversion: EDNS0\n\tuse dnssec: 1")
.expect("Not a utf8 str"),
read_opt_rr(query.get_additional().pop().expect("No OPT Record!"))
)
}
21 changes: 21 additions & 0 deletions src/dnssec/dnssec_message_processing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::message::DnsMessage;
use crate::message::rdata::Rdata;
use crate::message::resource_record::ResourceRecord;

pub fn extract_dnssec_records(dns_response: &DnsMessage) -> (Vec<ResourceRecord>, Vec<ResourceRecord>) {
let answers = dns_response.get_answer();
let additionals = dns_response.get_additional();

let mut dnskey_records = Vec::new();
let mut rrsig_records = Vec::new();

for record in answers.iter().chain(additionals.iter()) {
match record.get_rdata() {
Rdata::DNSKEY(_) => dnskey_records.push(record.clone()),
Rdata::RRSIG(_) => rrsig_records.push(record.clone()),
_ => {}
}
}

(dnskey_records, rrsig_records)
}
72 changes: 72 additions & 0 deletions src/dnssec/rrset_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use sha2::{Sha256, Digest};
use crypto::digest::Digest as RustDigest;
use crypto::sha1::Sha1;
use base64::encode;
use crate::message::rdata::Rdata;
use crate::message::rdata::dnskey_rdata::DnskeyRdata;
use crate::message::rdata::rrsig_rdata::RRSIGRdata;
use crate::message::rrtype::Rrtype;
use crate::message::resource_record::{ResourceRecord, ToBytes};
use crate::client::client_error::ClientError;

pub fn verify_rrsig(rrsig: &RRSIGRdata, dnskey: &DnskeyRdata, rrset: &[ResourceRecord]) -> Result<bool, ClientError> {
let mut rrsig_data = Vec::new();
rrsig_data.extend_from_slice(&u16::from(rrsig.get_type_covered()).to_be_bytes());
rrsig_data.push(rrsig.get_algorithm());
rrsig_data.push(rrsig.get_labels());
rrsig_data.extend_from_slice(&rrsig.get_original_ttl().to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.get_signature_expiration().to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.get_signature_inception().to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.get_key_tag().to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.get_signer_name().to_bytes());//Try?

let mut rrset_sorted = rrset.to_vec();
rrset_sorted.sort_by(|a, b| a.get_name().cmp(&b.get_name()));

for rr in rrset_sorted.iter() {
rrsig_data.extend_from_slice(&rr.get_name().to_bytes()); //Try?
rrsig_data.extend_from_slice(&rr.get_ttl().to_be_bytes());
rrsig_data.extend_from_slice(&(rr.get_rdata().to_bytes().len() as u16).to_be_bytes());
rrsig_data.extend_from_slice(&rr.get_rdata().to_bytes());//Try?
}

let signature = rrsig.get_signature().clone();
let hashed = Sha256::digest(&rrsig_data);

match dnskey.algorithm {
3 | 5 => {
// (DSA/RSA)/SHA1
let mut sha1 = Sha1::new();
sha1.input(&rrsig_data);
let digest = sha1.result_str();
Ok(digest == encode(&signature))
},
8 => {
// RSA/SHA256
Ok(encode(&hashed) == encode(&signature))
},
_ => Err(ClientError::NotImplemented("Unknown DNSKEY algorithm")),
}
}

pub fn verify_ds(ds_record: &ResourceRecord, dnskey: &DnskeyRdata) -> Result<bool, ClientError> {
if let Rdata::DS(ds_rdata) = &ds_record.get_rdata() {
let dnskey_bytes = dnskey.to_bytes(); //Try?
let hashed_key = match ds_rdata.algorithm {
1 => {
let mut hasher = Sha1::new();
hasher.input(&dnskey_bytes);
hasher.result_str()
},
2 => {
let hashed = Sha256::digest(&dnskey_bytes);
encode(&hashed)
},
_ => return Err(ClientError::NotImplemented("Unknown DS algorithm")),
};

Ok(ds_rdata.digest == hashed_key.as_bytes())
} else {
Err(ClientError::FormatError("Provided record is not a DS record"))
}
}
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@ pub mod domain_name;
pub mod message;
pub mod async_resolver;
pub mod truncated_dns_message;




pub mod tsig;
pub mod dnssec;
22 changes: 17 additions & 5 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ impl DnsMessage {
let mut msg_answers = self.get_answer();

msg_answers.append(&mut answers);
self.header.set_ancount(msg_answers.len() as u16);
self.set_answer(msg_answers);
}

Expand All @@ -640,6 +641,7 @@ impl DnsMessage {
let mut msg_authorities = self.get_authority();

msg_authorities.append(&mut authorities);
self.header.set_nscount(msg_authorities.len() as u16);
self.set_answer(msg_authorities);
}

Expand All @@ -656,6 +658,7 @@ impl DnsMessage {
let mut msg_additionals = self.get_additional();

msg_additionals.append(&mut additionals);
self.header.set_arcount(msg_additionals.len() as u16);
self.set_additional(msg_additionals);
}

Expand Down Expand Up @@ -683,14 +686,17 @@ impl DnsMessage {
impl fmt::Display for DnsMessage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut result = String::new();
let question = self.get_question();
let answers = self.get_answer().into_iter();
let authority = self.get_authority().into_iter();
let additional = self.get_additional().into_iter();
result.push_str(&format!("Answer\n"));
result.push_str(&format!("Question section\n"));
result.push_str(&format!("{}\n", question));
result.push_str(&format!("Answer section\n"));
answers.for_each(|answer| result.push_str(&format!("{}\n", answer)));
result.push_str(&format!("Authority\n"));
result.push_str(&format!("Authority section\n"));
authority.for_each(|authority| result.push_str(&format!("{}\n", authority)));
result.push_str(&format!("Additional\n"));
result.push_str(&format!("Additional section\n"));
additional.for_each(|additional| result.push_str(&format!("{}\n", additional)));
write!(f, "{}", result)
}
Expand Down Expand Up @@ -984,7 +990,7 @@ mod message_test {
*/
let bytes: [u8; 50] = [
//test passes with this one
0b00100100, 0b10010101, 0b10010010, 0b00000000, 0, 1, 0b00000000, 1, 0, 0, 0, 0, 4, 116,
0b00100100, 0b10010101, 0b10010010, 0b00100000, 0, 1, 0b00000000, 1, 0, 0, 0, 0, 4, 116,
101, 115, 116, 3, 99, 111, 109, 0, 0, 16, 0, 1, 3, 100, 99, 99, 2, 99, 108, 0, 0, 16, 0,
1, 0, 0, 0b00010110, 0b00001010, 0, 6, 5, 104, 101, 108, 108, 111,
];
Expand All @@ -1002,7 +1008,10 @@ mod message_test {
assert_eq!(header.get_qr(), true);
assert_eq!(header.get_op_code(), 2);
assert_eq!(header.get_tc(), true);

assert_eq!(header.get_ad(), true);
assert_eq!(header.get_rcode(), Rcode::NOERROR);

assert_eq!(header.get_ancount(), 1);

// Question
Expand Down Expand Up @@ -1041,7 +1050,10 @@ mod message_test {
header.set_qr(true);
header.set_op_code(2);
header.set_tc(true);

header.set_ad(true);
header.set_rcode(Rcode::UNKNOWN(8));

header.set_ancount(0b0000000000000001);
header.set_qdcount(1);

Expand Down Expand Up @@ -1081,7 +1093,7 @@ mod message_test {
let msg_bytes = &dns_msg.to_bytes();

let real_bytes: [u8; 50] = [
0b00100100, 0b10010101, 0b10010010, 0b00001000, 0, 1, 0b00000000, 0b00000001, 0, 0, 0,
0b00100100, 0b10010101, 0b10010010, 0b00101000, 0, 1, 0b00000000, 0b00000001, 0, 0, 0,
0, 4, 116, 101, 115, 116, 3, 99, 111, 109, 0, 0, 5, 0, 2, 3, 100, 99, 99, 2, 99, 108,
0, 0, 16, 0, 1, 0, 0, 0b00010110, 0b00001010, 0, 6, 5, 104, 101, 108, 108, 111,
];
Expand Down
Loading

0 comments on commit 6e8e030

Please sign in to comment.