Skip to main content

hickory_proto/dnssec/
mod.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! dns security extension related modules
9
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::slice;
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18use crate::dnssec::crypto::Digest;
19use crate::error::ProtoError;
20use crate::rr::{Name, RData, Record, rdata::tsig::TsigAlgorithm};
21use crate::serialize::binary::{BinEncodable, BinEncoder, DecodeError, NameEncoding};
22
23mod algorithm;
24pub use algorithm::Algorithm;
25
26/// Cryptographic backend implementations of DNSSEC traits.
27pub mod crypto;
28
29mod ec_public_key;
30
31mod proof;
32pub use proof::{Proof, ProofFlags, Proven};
33
34mod public_key;
35pub use public_key::{PublicKey, PublicKeyBuf};
36
37pub mod rdata;
38
39mod rsa_public_key;
40
41mod signer;
42pub use signer::DnssecSigner;
43
44mod supported_algorithm;
45pub use supported_algorithm::SupportedAlgorithms;
46
47mod tbs;
48pub use tbs::TBS;
49
50mod trust_anchor;
51pub use trust_anchor::TrustAnchors;
52
53mod verifier;
54pub use verifier::Verifier;
55
56/// An iterator over record data with all data wrapped in a Proven type for dnssec validation
57pub struct DnssecIter<'a>(slice::Iter<'a, Record<RData>>);
58
59impl<'a> DnssecIter<'a> {
60    /// Create a new DnssecIter from any iterator of Record references
61    pub fn new(records: &'a [Record<RData>]) -> Self {
62        Self(records.iter())
63    }
64}
65
66impl<'a> Iterator for DnssecIter<'a> {
67    type Item = Proven<&'a Record>;
68
69    fn next(&mut self) -> Option<Self::Item> {
70        self.0.next().map(Proven::from)
71    }
72}
73
74/// ```text
75/// RFC 5155                         NSEC3                        March 2008
76///
77/// 11.  IANA Considerations
78///
79///    Although the NSEC3 and NSEC3PARAM RR formats include a hash algorithm
80///    parameter, this document does not define a particular mechanism for
81///    safely transitioning from one NSEC3 hash algorithm to another.  When
82///    specifying a new hash algorithm for use with NSEC3, a transition
83///    mechanism MUST also be defined.
84///
85///    This document updates the IANA registry "DOMAIN NAME SYSTEM
86///    PARAMETERS" (https://www.iana.org/assignments/dns-parameters) in sub-
87///    registry "TYPES", by defining two new types.  Section 3 defines the
88///    NSEC3 RR type 50.  Section 4 defines the NSEC3PARAM RR type 51.
89///
90///    This document updates the IANA registry "DNS SECURITY ALGORITHM
91///    NUMBERS -- per [RFC4035]"
92///    (https://www.iana.org/assignments/dns-sec-alg-numbers).  Section 2
93///    defines the aliases DSA-NSEC3-SHA1 (6) and RSASHA1-NSEC3-SHA1 (7) for
94///    respectively existing registrations DSA and RSASHA1 in combination
95///    with NSEC3 hash algorithm SHA1.
96///
97///    Since these algorithm numbers are aliases for existing DNSKEY
98///    algorithm numbers, the flags that exist for the original algorithm
99///    are valid for the alias algorithm.
100///
101///    This document creates a new IANA registry for NSEC3 flags.  This
102///    registry is named "DNSSEC NSEC3 Flags".  The initial contents of this
103///    registry are:
104///
105///      0   1   2   3   4   5   6   7
106///    +---+---+---+---+---+---+---+---+
107///    |   |   |   |   |   |   |   |Opt|
108///    |   |   |   |   |   |   |   |Out|
109///    +---+---+---+---+---+---+---+---+
110///
111///       bit 7 is the Opt-Out flag.
112///
113///       bits 0 - 6 are available for assignment.
114///
115///    Assignment of additional NSEC3 Flags in this registry requires IETF
116///    Standards Action [RFC2434].
117///
118///    This document creates a new IANA registry for NSEC3PARAM flags.  This
119///    registry is named "DNSSEC NSEC3PARAM Flags".  The initial contents of
120///    this registry are:
121///
122///      0   1   2   3   4   5   6   7
123///    +---+---+---+---+---+---+---+---+
124///    |   |   |   |   |   |   |   | 0 |
125///    +---+---+---+---+---+---+---+---+
126///
127///       bit 7 is reserved and must be 0.
128///
129///       bits 0 - 6 are available for assignment.
130///
131///    Assignment of additional NSEC3PARAM Flags in this registry requires
132///    IETF Standards Action [RFC2434].
133///
134///    Finally, this document creates a new IANA registry for NSEC3 hash
135///    algorithms.  This registry is named "DNSSEC NSEC3 Hash Algorithms".
136///    The initial contents of this registry are:
137///
138///       0 is Reserved.
139///
140///       1 is SHA-1.
141///
142///       2-255 Available for assignment.
143///
144///    Assignment of additional NSEC3 hash algorithms in this registry
145///    requires IETF Standards Action [RFC2434].
146/// ```
147#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
148#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
149pub enum Nsec3HashAlgorithm {
150    /// Hash for the Nsec3 records
151    #[default]
152    #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
153    SHA1,
154}
155
156impl Nsec3HashAlgorithm {
157    /// ```text
158    /// Laurie, et al.              Standards Track                    [Page 14]
159    ///
160    /// RFC 5155                         NSEC3                        March 2008
161    ///
162    /// Define H(x) to be the hash of x using the Hash Algorithm selected by
163    ///    the NSEC3 RR, k to be the number of Iterations, and || to indicate
164    ///    concatenation.  Then define:
165    ///
166    ///       IH(salt, x, 0) = H(x || salt), and
167    ///
168    ///       IH(salt, x, k) = H(IH(salt, x, k-1) || salt), if k > 0
169    ///
170    ///    Then the calculated hash of an owner name is
171    ///
172    ///       IH(salt, owner name, iterations),
173    ///
174    ///    where the owner name is in the canonical form, defined as:
175    ///
176    ///    The wire format of the owner name where:
177    ///
178    ///    1.  The owner name is fully expanded (no DNS name compression) and
179    ///        fully qualified;
180    ///
181    ///    2.  All uppercase US-ASCII letters are replaced by the corresponding
182    ///        lowercase US-ASCII letters;
183    ///
184    ///    3.  If the owner name is a wildcard name, the owner name is in its
185    ///        original unexpanded form, including the "*" label (no wildcard
186    ///        substitution);
187    /// ```
188    pub fn hash(self, salt: &[u8], name: &Name, iterations: u16) -> Result<Digest, ProtoError> {
189        match self {
190            // if there ever is more than just SHA1 support, this should be a genericized method
191            Self::SHA1 => {
192                let mut buf: Vec<u8> = Vec::new();
193                {
194                    let mut encoder = BinEncoder::new(&mut buf);
195                    let mut encoder =
196                        encoder.with_name_encoding(NameEncoding::UncompressedLowercase);
197                    name.emit(&mut encoder)?;
198                }
199
200                Ok(Digest::iterated(salt, &buf, DigestType::SHA1, iterations)?)
201            }
202        }
203    }
204}
205
206impl TryFrom<u8> for Nsec3HashAlgorithm {
207    type Error = DecodeError;
208
209    /// <https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml>
210    fn try_from(value: u8) -> Result<Self, Self::Error> {
211        match value {
212            1 => Ok(Self::SHA1),
213            // TODO: where/when is SHA2?
214            _ => Err(DecodeError::UnknownNsec3HashAlgorithm(value)),
215        }
216    }
217}
218
219impl From<Nsec3HashAlgorithm> for u8 {
220    fn from(a: Nsec3HashAlgorithm) -> Self {
221        match a {
222            Nsec3HashAlgorithm::SHA1 => 1,
223        }
224    }
225}
226
227/// DNSSEC Delegation Signer (DS) Resource Record (RR) Type Digest Algorithms
228///
229/// [IANA Registry](https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml)
230/// ```text
231/// Value    Description           Status       Reference
232///  0        Reserved              -            [RFC3658]
233///  1        SHA-1                 MANDATORY    [RFC3658]
234///  2        SHA-256               MANDATORY    [RFC4509]
235///  3        GOST R 34.11-94       DEPRECATED   [RFC5933][Change the status of GOST Signature Algorithms in DNSSEC in the IETF stream to Historic]
236///  4        SHA-384               OPTIONAL     [RFC6605]
237///  5        GOST R 34.11-2012     OPTIONAL     [RFC9558]
238///  6        SM3                   OPTIONAL     [RFC9563]
239/// ```
240///
241/// <https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml>
242#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
243#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
244#[non_exhaustive]
245pub enum DigestType {
246    /// [RFC 3658](https://tools.ietf.org/html/rfc3658)
247    #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
248    SHA1,
249    /// [RFC 4509](https://tools.ietf.org/html/rfc4509)
250    #[cfg_attr(feature = "serde", serde(rename = "SHA-256"))]
251    SHA256,
252    /// [RFC 6605](https://tools.ietf.org/html/rfc6605)
253    #[cfg_attr(feature = "serde", serde(rename = "SHA-384"))]
254    SHA384,
255    /// An unknown digest type
256    Unknown(u8),
257}
258
259impl DigestType {
260    /// Whether this is a supported digest type
261    pub fn is_supported(&self) -> bool {
262        !matches!(self, Self::Unknown(_))
263    }
264}
265
266impl From<u8> for DigestType {
267    fn from(value: u8) -> Self {
268        match value {
269            1 => Self::SHA1,
270            2 => Self::SHA256,
271            4 => Self::SHA384,
272            _ => Self::Unknown(value),
273        }
274    }
275}
276
277impl From<DigestType> for u8 {
278    fn from(a: DigestType) -> Self {
279        match a {
280            DigestType::SHA1 => 1,
281            DigestType::SHA256 => 2,
282            DigestType::SHA384 => 4,
283            DigestType::Unknown(other) => other,
284        }
285    }
286}
287
288/// A key that can be used to sign records.
289pub trait SigningKey: Send + Sync + 'static {
290    /// Sign DNS records.
291    ///
292    /// # Return value
293    ///
294    /// The signature, ready to be stored in an `RData::RRSIG`.
295    fn sign(&self, tbs: &TBS) -> DnsSecResult<Vec<u8>>;
296
297    /// Returns a [`PublicKeyBuf`] for this [`SigningKey`].
298    fn to_public_key(&self) -> DnsSecResult<PublicKeyBuf>;
299
300    /// Returns the algorithm of the key.
301    fn algorithm(&self) -> Algorithm;
302}
303
304/// The format of the binary key
305#[derive(Clone, Copy, Debug, Eq, PartialEq)]
306pub enum KeyFormat {
307    /// A der encoded key
308    Der,
309    /// A pem encoded key, the default of OpenSSL
310    Pem,
311    /// Pkcs8, a pkcs8 formatted private key
312    Pkcs8,
313}
314
315/// An alias for dnssec results returned by functions of this crate
316pub type DnsSecResult<T> = ::core::result::Result<T, DnsSecError>;
317
318/// The error kind for dnssec errors that get returned in the crate
319#[derive(Debug, Error)]
320#[non_exhaustive]
321pub enum DnsSecError {
322    /// An HMAC failed to verify
323    #[error("hmac validation failure")]
324    HmacInvalid,
325
326    /// An error with an arbitrary message, referenced as &'static str
327    #[error("{0}")]
328    Message(&'static str),
329
330    /// An error with an arbitrary message, stored as String
331    #[error("{0}")]
332    Msg(String),
333
334    // foreign
335    /// An error got returned by the hickory-proto crate
336    #[error("proto error: {0}")]
337    Proto(#[from] ProtoError),
338
339    /// A ring error
340    #[error("ring error: {0}")]
341    RingKeyRejected(#[from] ring_like::KeyRejected),
342
343    /// A ring error
344    #[error("ring error: {0}")]
345    RingUnspecified(#[from] ring_like::Unspecified),
346
347    /// Tsig unsupported mac algorithm
348    /// Supported algorithm documented in `TsigAlgorithm::supported` function.
349    #[error("Tsig unsupported mac algorithm")]
350    TsigUnsupportedMacAlgorithm(TsigAlgorithm),
351
352    /// Tsig key verification failed
353    #[error("Tsig key wrong key error")]
354    TsigWrongKey,
355}
356
357impl From<String> for DnsSecError {
358    fn from(msg: String) -> Self {
359        Self::Msg(msg)
360    }
361}
362
363impl From<&'static str> for DnsSecError {
364    fn from(msg: &'static str) -> Self {
365        Self::Message(msg)
366    }
367}
368
369impl Clone for DnsSecError {
370    fn clone(&self) -> Self {
371        use DnsSecError::*;
372        match self {
373            HmacInvalid => HmacInvalid,
374            Message(msg) => Message(msg),
375            Msg(msg) => Msg(msg.clone()),
376            // foreign
377            Proto(proto) => Proto(proto.clone()),
378            RingKeyRejected(r) => Msg(format!("Ring rejected key: {r}")),
379            RingUnspecified(_r) => RingUnspecified(ring_like::Unspecified),
380            TsigUnsupportedMacAlgorithm(alg) => TsigUnsupportedMacAlgorithm(alg.clone()),
381            TsigWrongKey => TsigWrongKey,
382        }
383    }
384}
385
386/// DNSSEC status of an answer
387#[derive(Clone, Copy, Debug)]
388pub enum DnssecSummary {
389    /// All records have been DNSSEC validated
390    Secure,
391    /// At least one record is in the Bogus state
392    Bogus,
393    /// Insecure / Indeterminate (e.g. "Island of security")
394    Insecure,
395}
396
397impl DnssecSummary {
398    /// Whether the records have been DNSSEC validated or not
399    pub fn from_records<'a>(records: impl Iterator<Item = &'a Record>) -> Self {
400        let mut all_secure = None;
401        for record in records {
402            match &record.proof {
403                Proof::Secure => {
404                    all_secure.get_or_insert(true);
405                }
406                Proof::Bogus => return Self::Bogus,
407                _ => all_secure = Some(false),
408            }
409        }
410
411        if all_secure.unwrap_or(false) {
412            Self::Secure
413        } else {
414            Self::Insecure
415        }
416    }
417}
418
419#[cfg(all(feature = "dnssec-aws-lc-rs", not(feature = "dnssec-ring")))]
420pub(crate) use aws_lc_rs_impl as ring_like;
421#[cfg(feature = "dnssec-ring")]
422pub(crate) use ring_impl as ring_like;
423
424#[cfg(feature = "dnssec-aws-lc-rs")]
425#[cfg_attr(feature = "dnssec-ring", allow(unused_imports))]
426pub(crate) mod aws_lc_rs_impl {
427    pub(crate) use aws_lc_rs::{
428        digest,
429        error::{KeyRejected, Unspecified},
430        hmac,
431        rand::SystemRandom,
432        rsa::PublicKeyComponents,
433        signature::{
434            self, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
435            ED25519_PUBLIC_KEY_LEN, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RSA_PKCS1_SHA256,
436            RSA_PKCS1_SHA512, RsaKeyPair,
437        },
438    };
439}
440
441#[cfg(feature = "dnssec-ring")]
442pub(crate) mod ring_impl {
443    pub(crate) use ring::{
444        digest,
445        error::{KeyRejected, Unspecified},
446        hmac,
447        rand::SystemRandom,
448        rsa::PublicKeyComponents,
449        signature::{
450            self, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
451            ED25519_PUBLIC_KEY_LEN, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RSA_PKCS1_SHA256,
452            RSA_PKCS1_SHA512, RsaKeyPair,
453        },
454    };
455}
456
457#[cfg(test)]
458mod test_utils {
459    use rdata::DNSKEY;
460
461    use super::*;
462
463    pub(super) fn public_key_test(key: &dyn SigningKey) {
464        let pk = key.to_public_key().unwrap();
465
466        let tbs = TBS::from(&b"www.example.com"[..]);
467        let mut sig = key.sign(&tbs).unwrap();
468        assert!(
469            pk.verify(tbs.as_ref(), &sig).is_ok(),
470            "public_key_test() failed to verify (algorithm: {:?})",
471            key.algorithm(),
472        );
473        sig[10] = !sig[10];
474        assert!(
475            pk.verify(tbs.as_ref(), &sig).is_err(),
476            "algorithm: {:?} (public key, neg)",
477            key.algorithm(),
478        );
479    }
480
481    pub(super) fn hash_test(key: &dyn SigningKey, neg: &dyn SigningKey) {
482        let tbs = TBS::from(&b"www.example.com"[..]);
483
484        // TODO: convert to stored keys...
485        let pub_key = key.to_public_key().unwrap();
486        let neg_pub_key = neg.to_public_key().unwrap();
487
488        let sig = key.sign(&tbs).unwrap();
489        assert!(
490            pub_key.verify(tbs.as_ref(), &sig).is_ok(),
491            "algorithm: {:?}",
492            key.algorithm(),
493        );
494
495        let pub_key = key.to_public_key().unwrap();
496        let dns_key = DNSKEY::from_key(&pub_key);
497        assert!(
498            dns_key.verify(tbs.as_ref(), &sig).is_ok(),
499            "algorithm: {:?} (dnskey)",
500            pub_key.algorithm(),
501        );
502        assert!(
503            neg_pub_key.verify(tbs.as_ref(), &sig).is_err(),
504            "algorithm: {:?} (neg)",
505            neg_pub_key.algorithm(),
506        );
507
508        let neg_pub_key = neg.to_public_key().unwrap();
509        let neg_dns_key = DNSKEY::from_key(&neg_pub_key);
510        assert!(
511            neg_dns_key.verify(tbs.as_ref(), &sig).is_err(),
512            "algorithm: {:?} (dnskey, neg)",
513            neg_pub_key.algorithm(),
514        );
515    }
516}