///|
/// DNSKEY set authenticated from an out-of-band key or signed parent DS.
pub struct TrustedKeys {
priv zone : Name
priv keys : Array[Dnskey]
priv verified_at : UInt32
priv lifetime : UInt32
priv depth : Int32
} derive(Debug)
///|
pub extend TrustedKeys with @debug.Debug::{to_repr}
///|
pub fn TrustedKeys::zone(self : TrustedKeys) -> Name {
self.zone
}
///|
pub fn TrustedKeys::depth(self : TrustedKeys) -> Int32 {
self.depth
}
///|
pub fn TrustedKeys::remaining(
self : TrustedKeys,
now : UInt32,
) -> Result[UInt32, DnsError] {
let elapsed = now - self.verified_at
if elapsed >= self.lifetime {
Err(Stale("trusted key set expired or clock moved backwards"))
} else {
Ok(self.lifetime - elapsed)
}
}
///|
fn keys_from_set(
records : Array[Record],
zone : Name,
limits : Limits,
) -> Result[Array[Dnskey], DnsError] {
if records.is_empty() || records.length() > limits.max_records {
return Err(ResourceLimit("DNSKEY set size"))
}
let keys : Array[Dnskey] = []
for rr in records {
if rr.rrtype != 48 || rr.owner != zone {
return Err(Malformed("expected zone DNSKEY RRset"))
}
match Dnskey::parse(rr.data) {
Ok(k) => if supported_algorithm(k.algorithm) { keys.push(k) }
Err(Bogus(_)) => () // Non-zone/revoked keys cannot authenticate, but may exist in the signed set.
Err(e) => return Err(e)
}
}
if keys.is_empty() {
return Err(Indeterminate("no enabled zone signing key"))
}
Ok(keys)
}
///|
/// Authenticate the complete DNSKEY RRset, not merely an anchor's presence.
pub fn anchor_keys(
zone : Name,
anchor : Dnskey,
records : Array[Record],
signatures : Array[Rrsig],
time : TimePolicy,
limits : Limits,
) -> Result[TrustedKeys, DnsError] {
match limits.validate() {
Ok(_) => ()
Err(e) => return Err(e)
}
let keys = match keys_from_set(records, zone, limits) {
Ok(k) => k
Err(e) => return Err(e)
}
if !keys.contains(anchor) {
return Err(Bogus("anchor key absent from DNSKEY set"))
}
let set = match
authenticate_with_key(records, signatures, zone, anchor, time, limits) {
Ok(s) => s
Err(e) => return Err(e)
}
Ok({ zone, keys, verified_at: time.now, lifetime: set.lifetime, depth: 0, })
}
///|
pub fn authenticate_rrset(
records : Array[Record],
signatures : Array[Rrsig],
trusted : TrustedKeys,
time : TimePolicy,
limits : Limits,
) -> Result[AuthenticatedSet, DnsError] {
match limits.validate() {
Ok(_) => ()
Err(e) => return Err(e)
}
let trust_ttl = match trusted.remaining(time.now) {
Ok(n) => n
Err(e) => return Err(e)
}
if signatures.is_empty() {
return Err(Indeterminate("no signatures"))
}
// Bound the complete search, including key-tag hashing and failed candidates.
if signatures.length() > limits.max_signatures ||
trusted.keys.length() > limits.max_signatures / signatures.length() {
return Err(ResourceLimit("signature/key candidate budget"))
}
let mut failure : DnsError = Indeterminate("no usable signing key")
for key in trusted.keys {
match
authenticate_with_key(
records,
signatures,
trusted.zone,
key,
time,
limits,
) {
Ok(set) =>
return Ok({
..set,
lifetime: if set.lifetime < trust_ttl {
set.lifetime
} else {
trust_ttl
},
})
Err(e) => failure = e
}
}
Err(failure)
}