Skip to content

Commit

Permalink
Merge pull request #15 from niclabs/decrypt-signature
Browse files Browse the repository at this point in the history
Decrypt signature
  • Loading branch information
joalopez1206 authored Jul 14, 2024
2 parents 62831ba + 51104d5 commit 7512265
Show file tree
Hide file tree
Showing 8 changed files with 525 additions and 150 deletions.
382 changes: 233 additions & 149 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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"

Expand Down
1 change: 1 addition & 0 deletions src/dnssec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod dnssec_encryption;
pub mod dnssec_decryption;


use std::str::FromStr;
Expand Down
17 changes: 17 additions & 0 deletions src/dnssec/dnssec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use crate::message::{DnsMessage, Rdata, ResourceRecord};
use crate::dnssec_message_processing::extract_dnssec_records;
use crate::rrset_signature::{verify_rrsig, verify_ds};
use crate::message::rdata::DnskeyRdata;
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)
}
1 change: 0 additions & 1 deletion src/dnssec/dnssec_encryption.rs

This file was deleted.

19 changes: 19 additions & 0 deletions src/dnssec/dnssec_message_processing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::message::{DnsMessage, Rdata, 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)
}
69 changes: 69 additions & 0 deletions src/dnssec/rrset_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use sha2::{Sha256, Digest};
use rust_crypto::digest::Digest as RustDigest;
use rust_crypto::sha1::Sha1;
use base64::encode;
use crate::message::rdata::{DnskeyRdata, RrsigRdata, Rdata};
use crate::message::resource_record::ResourceRecord;
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(&rrsig.type_covered.to_be_bytes());
rrsig_data.push(rrsig.algorithm);
rrsig_data.push(rrsig.labels);
rrsig_data.extend_from_slice(&rrsig.original_ttl.to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.expiration.to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.inception.to_be_bytes());
rrsig_data.extend_from_slice(&rrsig.key_tag.to_be_bytes());
rrsig_data.extend_from_slice(rrsig.signer_name.to_bytes()?);

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

for rr in rrset_sorted.iter() {
rrsig_data.extend_from_slice(rr.name.to_bytes()?);
rrsig_data.extend_from_slice(&rr.ttl.to_be_bytes());
rrsig_data.extend_from_slice(&(rr.rdata.to_bytes().len() as u16).to_be_bytes());
rrsig_data.extend_from_slice(&rr.rdata.to_bytes()?);
}

let signature = rrsig.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::new("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()?;
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::new("Unknown DS algorithm")),
};

Ok(ds_rdata.digest == hashed_key)
} else {
Err(ClientError::new("Provided record is not a DS record"))
}
}
185 changes: 185 additions & 0 deletions src/message/rrset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use crate::message::Rtype;
use crate::message::Rclass;
use crate::message::resource_record::ResourceRecord;
use std::collections::HashSet;

/// Represents a set of resource records (RRset).
#[derive(Debug)]
pub struct RRset {
/// The name of the domain associated with this RRset.
name: String,
/// The type of resource record in this RRset.
rtype: Rtype,
/// The class of resource record in this RRset.
rclass: Rclass,
/// The time to live (TTL) value for records in this RRset.
ttl: u32,
/// The set of resource records belonging to this RRset.
records: HashSet<ResourceRecord>,
}

impl RRset {
/// Creates a new RRset.
pub fn new(name: String, rtype: Rtype, rclass: Rclass, ttl: u32) -> RRset {
RRset {
name,
rtype,
rclass,
ttl,
records: HashSet::new(),
}
}

/// Adds a resource record to this RRset.
pub fn add_record(&mut self, record: ResourceRecord) {
self.records.insert(record);
}

/// Gets the name of the domain associated with this RRset.
pub fn get_name(&self) -> &String {
&self.name
}

/// Gets the type of resource record in this RRset.
pub fn get_type(&self) -> Rtype {
self.rtype
}

/// Gets the class of resource record in this RRset.
pub fn get_class(&self) -> Rclass {
self.rclass
}

/// Gets the time to live (TTL) value for records in this RRset.
pub fn get_ttl(&self) -> u32 {
self.ttl
}

/// Gets the set of resource records belonging to this RRset.
pub fn get_records(&self) -> &HashSet<ResourceRecord> {
&self.records
}

/// Gets the labels of the domain associated with this RRset.
pub fn get_labels(&self) -> usize {
self.name.split('.').count()
}

/// Serializes the RRset to a byte array for signing.
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
for record in &self.records {
bytes.extend(record.to_bytes()); // Assuming ResourceRecord has a to_bytes method
}
bytes
}
}


#[cfg(test)]
mod tests {
use super::*;
use crate::message::Rtype;
use crate::message::Rclass;
use crate::message::resource_record::{ResourceRecord, Rdata, ARdata, NsRdata, CnameRdata};
use std::net::IpAddr;
use std::collections::HashSet;

#[test]
fn test_create_rrset() {
let name = "example.com".to_string();
let rtype = Rtype::A;
let rclass = Rclass::IN;
let ttl = 3600;

let rrset = RRset::new(name.clone(), rtype, rclass, ttl);

assert_eq!(rrset.get_name(), &name);
assert_eq!(rrset.get_type(), Rtype::A);
assert_eq!(rrset.get_class(), Rclass::IN);
assert_eq!(rrset.get_ttl(), 3600);
assert_eq!(rrset.get_labels(), 2);
assert!(rrset.get_records().is_empty());
}

#[test]
fn test_add_record() {
let name = "example.com".to_string();
let rtype = Rtype::A;
let rclass = Rclass::IN;
let ttl = 3600;

let mut rrset = RRset::new(name.clone(), rtype, rclass, ttl);

let mut a_rdata = Rdata::A(ARdata::new());
match a_rdata {
Rdata::A(ref mut val) => val.set_address(IpAddr::from([127, 0, 0, 1])),
_ => unreachable!(),
}

let record = ResourceRecord::new(a_rdata);
rrset.add_record(record);

assert_eq!(rrset.get_records().len(), 1);
}

#[test]
fn test_get_name() {
let name = "example.com".to_string();
let rtype = Rtype::A;
let rclass = Rclass::IN;
let ttl = 3600;

let rrset = RRset::new(name.clone(), rtype, rclass, ttl);

assert_eq!(rrset.get_name(), &name);
}

#[test]
fn test_get_type() {
let name = "example.com".to_string();
let rtype = Rtype::NS;
let rclass = Rclass::IN;
let ttl = 3600;

let rrset = RRset::new(name.clone(), rtype, rclass, ttl);

assert_eq!(rrset.get_type(), Rtype::NS);
}

#[test]
fn test_get_class() {
let name = "example.com".to_string();
let rtype = Rtype::MX;
let rclass = Rclass::CH;
let ttl = 3600;

let rrset = RRset::new(name.clone(), rtype, rclass, ttl);

assert_eq!(rrset.get_class(), Rclass::CH);
}

#[test]
fn test_get_ttl() {
let name = "example.com".to_string();
let rtype = Rtype::A;
let rclass = Rclass::IN;
let ttl = 7200;

let rrset = RRset::new(name.clone(), rtype, rclass, ttl);

assert_eq!(rrset.get_ttl(), 7200);
}

#[test]
fn test_get_labels() {
let name = "sub.example.com".to_string();
let rtype = Rtype::A;
let rclass = Rclass::IN;
let ttl = 3600;

let rrset = RRset::new(name.clone(), rtype, rclass, ttl);

assert_eq!(rrset.get_labels(), 3);
}
}

0 comments on commit 7512265

Please sign in to comment.