blob: 1d765e24e7caaea55007e0232343024263e888ae [file] [log] [blame]
//! hash functions for DNSSec operations
use super::rdata::{sig, DNSSECRData, SIG};
use crate::error::*;
use crate::rr::dnssec::Algorithm;
use crate::rr::{DNSClass, Name, RData, Record, RecordType};
use crate::serialize::binary::{BinEncodable, BinEncoder, EncodeMode};
/// Data To Be Signed.
pub struct TBS(Vec<u8>);
impl<'a> From<&'a [u8]> for TBS {
fn from(slice: &'a [u8]) -> Self {
TBS(slice.to_owned())
}
}
impl AsRef<[u8]> for TBS {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// Returns the to-be-signed serialization of the given message.
pub fn message_tbs<M: BinEncodable>(message: &M, pre_sig0: &SIG) -> ProtoResult<TBS> {
// TODO: should perform the serialization and sign block by block to reduce the max memory
// usage, though at 4k max, this is probably unnecessary... For AXFR and large zones, it's
// more important
let mut buf: Vec<u8> = Vec::with_capacity(512);
let mut buf2: Vec<u8> = Vec::with_capacity(512);
{
let mut encoder: BinEncoder = BinEncoder::with_mode(&mut buf, EncodeMode::Normal);
assert!(sig::emit_pre_sig(
&mut encoder,
pre_sig0.type_covered(),
pre_sig0.algorithm(),
pre_sig0.num_labels(),
pre_sig0.original_ttl(),
pre_sig0.sig_expiration(),
pre_sig0.sig_inception(),
pre_sig0.key_tag(),
pre_sig0.signer_name(),
)
.is_ok());
// need a separate encoder here, as the encoding references absolute positions
// inside the buffer. If the buffer already contains the sig0 RDATA, offsets
// are wrong and the signature won't match.
let mut encoder2: BinEncoder = BinEncoder::with_mode(&mut buf2, EncodeMode::Signing);
message.emit(&mut encoder2).unwrap(); // coding error if this panics (i think?)
}
buf.append(&mut buf2);
Ok(TBS(buf))
}
/// Returns the to-be-signed serialization of the given record set.
///
/// # Arguments
///
/// * `name` - RRset record name
/// * `dns_class` - DNSClass, i.e. IN, of the records
/// * `num_labels` - number of labels in the name, needed to deal with `*.example.com`
/// * `type_covered` - RecordType of the RRSet being hashed
/// * `algorithm` - The Algorithm type used for the hashing
/// * `original_ttl` - Original TTL is the TTL as specified in the SOA zones RRSet associated record
/// * `sig_expiration` - the epoch seconds of when this hashed signature will expire
/// * `key_inception` - the epoch seconds of when this hashed signature will be valid
/// * `signer_name` - label of the etity responsible for signing this hash
/// * `records` - RRSet to hash
///
/// # Returns
///
/// the binary hash of the specified RRSet and associated information
#[allow(clippy::too_many_arguments)]
pub fn rrset_tbs(
name: &Name,
dns_class: DNSClass,
num_labels: u8,
type_covered: RecordType,
algorithm: Algorithm,
original_ttl: u32,
sig_expiration: u32,
sig_inception: u32,
key_tag: u16,
signer_name: &Name,
records: &[Record],
) -> ProtoResult<TBS> {
// TODO: change this to a BTreeSet so that it's preordered, no sort necessary
let mut rrset: Vec<&Record> = Vec::new();
// collect only the records for this rrset
for record in records {
if dns_class == record.dns_class()
&& type_covered == record.rr_type()
&& name == record.name()
{
rrset.push(record);
}
}
// put records in canonical order
rrset.sort();
let name: Name = if let Some(name) = determine_name(name, num_labels) {
name
} else {
return Err(format!("could not determine name from {}", name).into());
};
// TODO: rather than buffering here, use the Signer/Verifier? might mean fewer allocations...
let mut buf: Vec<u8> = Vec::new();
{
let mut encoder: BinEncoder = BinEncoder::new(&mut buf);
encoder.set_canonical_names(true);
// signed_data = RRSIG_RDATA | RR(1) | RR(2)... where
//
// "|" denotes concatenation
//
// RRSIG_RDATA is the wire format of the RRSIG RDATA fields
// with the Signature field excluded and the Signer's Name
// in canonical form.
assert!(sig::emit_pre_sig(
&mut encoder,
type_covered,
algorithm,
name.num_labels(),
original_ttl,
sig_expiration,
sig_inception,
key_tag,
signer_name,
)
.is_ok());
// construct the rrset signing data
for record in rrset {
// RR(i) = name | type | class | OrigTTL | RDATA length | RDATA
//
// name is calculated according to the function in the RFC 4035
assert!(name
.to_lowercase()
.emit_as_canonical(&mut encoder, true)
.is_ok());
//
// type is the RRset type and all RRs in the class
assert!(type_covered.emit(&mut encoder).is_ok());
//
// class is the RRset's class
assert!(dns_class.emit(&mut encoder).is_ok());
//
// OrigTTL is the value from the RRSIG Original TTL field
assert!(encoder.emit_u32(original_ttl).is_ok());
//
// RDATA length
// TODO: add support to the encoder to set a marker to go back and write the length
let mut rdata_buf = Vec::new();
{
let mut rdata_encoder = BinEncoder::new(&mut rdata_buf);
rdata_encoder.set_canonical_names(true);
assert!(record.rdata().emit(&mut rdata_encoder).is_ok());
}
assert!(encoder.emit_u16(rdata_buf.len() as u16).is_ok());
//
// All names in the RDATA field are in canonical form (set above)
assert!(encoder.emit_vec(&rdata_buf).is_ok());
}
}
Ok(TBS(buf))
}
/// Returns the to-be-signed serialization of the given record set using the information
/// provided from the RRSIG record.
///
/// # Arguments
///
/// * `rrsig` - SIG or RRSIG record, which was produced from the RRSet
/// * `records` - RRSet records to sign with the information in the `rrsig`
///
/// # Return
///
/// binary hash of the RRSet with the information from the RRSIG record
pub fn rrset_tbs_with_rrsig(rrsig: &Record, records: &[Record]) -> ProtoResult<TBS> {
if let RData::DNSSEC(DNSSECRData::SIG(ref sig)) = *rrsig.rdata() {
rrset_tbs_with_sig(rrsig.name(), rrsig.dns_class(), sig, records)
} else {
Err(format!("could not determine name from {}", rrsig.name()).into())
}
}
/// Returns the to-be-signed serialization of the given record set using the information
/// provided from the SIG record.
///
/// # Arguments
///
/// * `name` - labels of the record to sign
/// * `dns_class` - DNSClass of the RRSet, i.e. IN
/// * `sig` - SIG or RRSIG record, which was produced from the RRSet
/// * `records` - RRSet records to sign with the information in the `rrsig`
///
/// # Return
///
/// binary hash of the RRSet with the information from the RRSIG record
pub fn rrset_tbs_with_sig(
name: &Name,
dns_class: DNSClass,
sig: &SIG,
records: &[Record],
) -> ProtoResult<TBS> {
rrset_tbs(
name,
dns_class,
sig.num_labels(),
sig.type_covered(),
sig.algorithm(),
sig.original_ttl(),
sig.sig_expiration(),
sig.sig_inception(),
sig.key_tag(),
sig.signer_name(),
records,
)
}
/// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005
///
/// ```text
///
/// 5.3.2. Reconstructing the Signed Data
/// ...
/// To calculate the name:
/// let rrsig_labels = the value of the RRSIG Labels field
///
/// let fqdn = RRset's fully qualified domain name in
/// canonical form
///
/// let fqdn_labels = Label count of the fqdn above.
///
/// if rrsig_labels = fqdn_labels,
/// name = fqdn
///
/// if rrsig_labels < fqdn_labels,
/// name = "*." | the rightmost rrsig_label labels of the
/// fqdn
///
/// if rrsig_labels > fqdn_labels
/// the RRSIG RR did not pass the necessary validation
/// checks and MUST NOT be used to authenticate this
/// RRset.
///
/// The canonical forms for names and RRsets are defined in [RFC4034].
/// ```
pub fn determine_name(name: &Name, num_labels: u8) -> Option<Name> {
// To calculate the name:
// let rrsig_labels = the value of the RRSIG Labels field
//
// let fqdn = RRset's fully qualified domain name in
// canonical form
//
// let fqdn_labels = Label count of the fqdn above.
let fqdn_labels = name.num_labels();
// if rrsig_labels = fqdn_labels,
// name = fqdn
if fqdn_labels == num_labels {
return Some(name.clone());
}
// if rrsig_labels < fqdn_labels,
// name = "*." | the rightmost rrsig_label labels of the
// fqdn
if num_labels < fqdn_labels {
let mut star_name: Name = Name::from_labels(vec![b"*" as &[u8]]).unwrap();
let rightmost = name.trim_to(num_labels as usize);
if !rightmost.is_root() {
star_name = star_name.append_name(&rightmost);
return Some(star_name);
}
return Some(star_name);
}
//
// if rrsig_labels > fqdn_labels
// the RRSIG RR did not pass the necessary validation
// checks and MUST NOT be used to authenticate this
// RRset.
// TODO: this should be an error
None
}