use alloc::{boxed::Box, vec::Vec};
use core::time::Duration;
use tracing::debug;
use super::{DnsSecResult, SigningKey};
use crate::{
dnssec::{
TBS,
rdata::{DNSKEY, DNSSECRData, KEY, SIG},
tbs,
},
error::{ProtoErrorKind, ProtoResult},
op::{Message, MessageFinalizer, MessageVerifier},
rr::{
Record, {DNSClass, Name, RData, RecordType},
},
serialize::binary::{BinEncodable, BinEncoder},
};
pub struct SigSigner {
key_rdata: RData,
key: Box<dyn SigningKey>,
signer_name: Name,
sig_duration: Duration,
is_zone_signing_key: bool,
}
impl SigSigner {
pub fn dnssec(
key_rdata: DNSKEY,
key: Box<dyn SigningKey>,
signer_name: Name,
sig_duration: Duration,
) -> Self {
Self {
is_zone_signing_key: key_rdata.zone_key(),
key_rdata: key_rdata.into(),
key,
signer_name,
sig_duration,
}
}
pub fn sig0(key_rdata: KEY, key: Box<dyn SigningKey>, signer_name: Name) -> Self {
Self {
key_rdata: key_rdata.into(),
key,
signer_name,
sig_duration: Duration::ZERO,
is_zone_signing_key: false,
}
}
#[deprecated(note = "use SIG0 or DNSSEC constructors")]
pub fn new(
key: Box<dyn SigningKey>,
signer_name: Name,
sig_duration: Duration,
is_zone_signing_key: bool,
_: bool,
) -> Self {
let pub_key = key.to_public_key().expect("key is not a private key");
let dnskey = DNSKEY::from_key(&pub_key);
Self {
key_rdata: dnskey.into(),
key,
signer_name,
sig_duration,
is_zone_signing_key,
}
}
pub fn key(&self) -> &dyn SigningKey {
&*self.key
}
pub fn sig_duration(&self) -> Duration {
self.sig_duration
}
pub fn is_zone_signing_key(&self) -> bool {
self.is_zone_signing_key
}
pub fn sign(&self, tbs: &TBS) -> ProtoResult<Vec<u8>> {
self.key
.sign(tbs)
.map_err(|e| ProtoErrorKind::Msg(format!("signing error: {e}")).into())
}
pub fn signer_name(&self) -> &Name {
&self.signer_name
}
pub fn calculate_key_tag(&self) -> ProtoResult<u16> {
let mut bytes: Vec<u8> = Vec::with_capacity(512);
{
let mut e = BinEncoder::new(&mut bytes);
self.key_rdata.emit(&mut e)?;
}
Ok(DNSKEY::calculate_key_tag_internal(&bytes))
}
pub fn sign_message(&self, message: &Message, pre_sig0: &SIG) -> ProtoResult<Vec<u8>> {
tbs::message_tbs(message, pre_sig0).and_then(|tbs| self.sign(&tbs))
}
pub fn to_dnskey(&self) -> DnsSecResult<DNSKEY> {
let pub_key = self.key.to_public_key()?;
Ok(DNSKEY::new(self.is_zone_signing_key, true, false, pub_key))
}
pub fn test_key(&self) -> DnsSecResult<()> {
Ok(())
}
}
impl MessageFinalizer for SigSigner {
fn finalize_message(
&self,
message: &Message,
current_time: u32,
) -> ProtoResult<(Vec<Record>, Option<MessageVerifier>)> {
debug!("signing message: {message:?}");
let key_tag: u16 = self.calculate_key_tag()?;
let name = Name::root();
let ttl = 0;
let num_labels = name.num_labels();
let expiration_time: u32 = current_time + (5 * 60);
let pre_sig0 = SIG::new(
RecordType::ZERO,
self.key.algorithm(),
num_labels,
0,
expiration_time,
current_time,
key_tag,
self.signer_name().clone(),
Vec::new(),
);
let signature: Vec<u8> = self.sign_message(message, &pre_sig0)?;
let rdata = RData::DNSSEC(DNSSECRData::SIG(pre_sig0.set_sig(signature)));
let mut sig0 = Record::from_rdata(name, ttl, rdata);
sig0.set_dns_class(DNSClass::ANY);
Ok((vec![sig0], None))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
#[cfg(feature = "std")]
use std::println;
use rustls_pki_types::PrivatePkcs8KeyDer;
use super::*;
use crate::dnssec::{
Algorithm, PublicKey, SigningKey, TBS, Verifier,
crypto::RsaSigningKey,
rdata::{DNSSECRData, KEY, RRSIG, SIG, key::KeyUsage},
};
use crate::op::{Message, Query};
use crate::rr::rdata::{CNAME, NS};
use crate::rr::{DNSClass, Name, RData, Record, RecordType};
fn assert_send_and_sync<T: Send + Sync>() {}
#[test]
fn test_send_and_sync() {
assert_send_and_sync::<SigSigner>();
}
fn pre_sig0(signer: &SigSigner, inception_time: u32, expiration_time: u32) -> SIG {
SIG::new(
RecordType::ZERO,
signer.key().algorithm(),
0,
0,
expiration_time,
inception_time,
signer.calculate_key_tag().unwrap(),
signer.signer_name().clone(),
Vec::new(),
)
}
#[test]
fn test_sign_and_verify_message_sig0() {
let origin: Name = Name::parse("example.com.", None).unwrap();
let mut question: Message = Message::new();
let mut query: Query = Query::new();
query.set_name(origin);
question.add_query(query);
let key =
RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
.unwrap();
let pub_key = key.to_public_key().unwrap();
let sig0key = KEY::new_sig0key(&pub_key);
let signer = SigSigner::sig0(sig0key.clone(), Box::new(key), Name::root());
let pre_sig0 = pre_sig0(&signer, 0, 300);
let sig = signer.sign_message(&question, &pre_sig0).unwrap();
#[cfg(feature = "std")]
println!("sig: {sig:?}");
assert!(!sig.is_empty());
assert!(sig0key.verify_message(&question, &sig, &pre_sig0).is_ok());
assert!(question.sig0().is_empty());
question.finalize(&signer, 0).expect("should have signed");
assert!(!question.sig0().is_empty());
let sig = signer.sign_message(&question, &pre_sig0);
#[cfg(feature = "std")]
println!("sig after sign: {sig:?}");
if let RData::DNSSEC(DNSSECRData::SIG(sig)) = question.sig0()[0].data() {
assert!(sig0key.verify_message(&question, sig.sig(), sig).is_ok());
}
}
#[test]
#[allow(deprecated)]
fn test_sign_and_verify_rrset() {
let key =
RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
.unwrap();
let pub_key = key.to_public_key().unwrap();
let sig0key = KEY::new_sig0key_with_usage(&pub_key, KeyUsage::Zone);
let signer = SigSigner::sig0(sig0key, Box::new(key), Name::root());
let origin: Name = Name::parse("example.com.", None).unwrap();
let rrsig = Record::from_rdata(
origin.clone(),
86400,
RRSIG::new(
RecordType::NS,
Algorithm::RSASHA256,
origin.num_labels(),
86400,
5,
0,
signer.calculate_key_tag().unwrap(),
origin.clone(),
vec![],
),
);
let rrset = [
Record::from_rdata(
origin.clone(),
86400,
RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(),
Record::from_rdata(
origin,
86400,
RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(),
];
let tbs = TBS::from_rrsig(&rrsig, rrset.iter()).unwrap();
let sig = signer.sign(&tbs).unwrap();
let pub_key = signer.key().to_public_key().unwrap();
assert!(pub_key.verify(tbs.as_ref(), &sig).is_ok());
}
#[test]
#[allow(deprecated)]
fn test_calculate_key_tag_pem() {
let key =
RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
.unwrap();
let pub_key = key.to_public_key().unwrap();
let sig0key = KEY::new_sig0key_with_usage(&pub_key, KeyUsage::Zone);
let signer = SigSigner::sig0(sig0key, Box::new(key), Name::root());
let key_tag = signer.calculate_key_tag().unwrap();
assert_eq!(key_tag, 3256);
}
#[test]
fn test_rrset_tbs() {
let key =
RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
.unwrap();
let pub_key = key.to_public_key().unwrap();
let sig0key = KEY::new_sig0key(&pub_key);
let signer = SigSigner::sig0(sig0key, Box::new(key), Name::root());
let origin = Name::parse("example.com.", None).unwrap();
let rrsig = Record::from_rdata(
origin.clone(),
86400,
RRSIG::new(
RecordType::NS,
Algorithm::RSASHA256,
origin.num_labels(),
86400,
5,
0,
signer.calculate_key_tag().unwrap(),
origin.clone(),
vec![],
),
);
let rrset = [
Record::from_rdata(
origin.clone(),
86400,
RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(),
Record::from_rdata(
origin.clone(),
86400,
RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(),
];
let tbs = TBS::from_rrsig(&rrsig, rrset.iter()).unwrap();
assert!(!tbs.as_ref().is_empty());
let rrset = [
Record::from_rdata(
origin.clone(),
86400,
RData::CNAME(CNAME(Name::parse("a.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(), Record::from_rdata(
Name::parse("www.example.com.", None).unwrap(),
86400,
RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(), Record::from_rdata(
origin.clone(),
86400,
RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::CH)
.clone(), Record::from_rdata(
origin.clone(),
86400,
RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(),
Record::from_rdata(
origin,
86400,
RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
)
.set_dns_class(DNSClass::IN)
.clone(),
];
let filtered_tbs = TBS::from_rrsig(&rrsig, rrset.iter()).unwrap();
assert!(!filtered_tbs.as_ref().is_empty());
assert_eq!(tbs.as_ref(), filtered_tbs.as_ref());
}
const RSA_KEY: &[u8] = include_bytes!("../../tests/test-data/rsa-2048-private-key-1.pk8");
}