Coverage Report

Created: 2025-07-23 07:04

/src/hickory-dns/crates/proto/src/dnssec/mod.rs
Line
Count
Source (jump to first uncovered line)
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
10
use alloc::string::String;
11
use alloc::vec::Vec;
12
use core::fmt;
13
14
#[cfg(feature = "backtrace")]
15
use backtrace::Backtrace;
16
#[cfg(feature = "serde")]
17
use serde::{Deserialize, Serialize};
18
use thiserror::Error;
19
20
use crate::error::{ProtoError, ProtoErrorKind};
21
use crate::rr::Record;
22
#[cfg(feature = "backtrace")]
23
use crate::trace;
24
25
mod algorithm;
26
mod dnssec_dns_handle;
27
pub use dnssec_dns_handle::DnssecDnsHandle;
28
#[doc(hidden)]
29
pub use dnssec_dns_handle::verify_nsec;
30
/// Cryptographic backend implementations of DNSSEC traits.
31
pub mod crypto;
32
mod ec_public_key;
33
mod nsec3;
34
pub mod proof;
35
pub mod public_key;
36
pub mod rdata;
37
use rdata::tsig::TsigAlgorithm;
38
mod rsa_public_key;
39
mod signer;
40
mod supported_algorithm;
41
pub mod tbs;
42
mod trust_anchor;
43
pub mod tsig;
44
mod verifier;
45
46
pub use self::algorithm::Algorithm;
47
pub use self::nsec3::Nsec3HashAlgorithm;
48
pub use self::proof::{Proof, ProofError, ProofErrorKind, ProofFlags, Proven};
49
pub use self::public_key::{PublicKey, PublicKeyBuf};
50
pub use self::signer::SigSigner;
51
pub use self::supported_algorithm::SupportedAlgorithms;
52
pub use self::tbs::TBS;
53
pub use self::trust_anchor::TrustAnchors;
54
pub use self::verifier::Verifier;
55
56
/// DNSSEC Delegation Signer (DS) Resource Record (RR) Type Digest Algorithms
57
///
58
/// [IANA Registry](https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml)
59
/// ```text
60
/// Value    Description           Status       Reference
61
///  0        Reserved              -            [RFC3658]
62
///  1        SHA-1                 MANDATORY    [RFC3658]
63
///  2        SHA-256               MANDATORY    [RFC4509]
64
///  3        GOST R 34.11-94       DEPRECATED   [RFC5933][Change the status of GOST Signature Algorithms in DNSSEC in the IETF stream to Historic]
65
///  4        SHA-384               OPTIONAL     [RFC6605]
66
///  5        GOST R 34.11-2012     OPTIONAL     [RFC9558]
67
///  6        SM3                   OPTIONAL     [RFC9563]
68
/// ```
69
///
70
/// <https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml>
71
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
72
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
73
#[non_exhaustive]
74
pub enum DigestType {
75
    /// [RFC 3658](https://tools.ietf.org/html/rfc3658)
76
    #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
77
    SHA1,
78
    /// [RFC 4509](https://tools.ietf.org/html/rfc4509)
79
    #[cfg_attr(feature = "serde", serde(rename = "SHA-256"))]
80
    SHA256,
81
    /// [RFC 6605](https://tools.ietf.org/html/rfc6605)
82
    #[cfg_attr(feature = "serde", serde(rename = "SHA-384"))]
83
    SHA384,
84
    /// An unknown digest type
85
    Unknown(u8),
86
}
87
88
impl DigestType {
89
0
    fn is_supported(&self) -> bool {
90
0
        !matches!(self, Self::Unknown(_))
91
0
    }
92
}
93
94
impl From<u8> for DigestType {
95
36.3k
    fn from(value: u8) -> Self {
96
36.3k
        match value {
97
4.76k
            1 => Self::SHA1,
98
4.11k
            2 => Self::SHA256,
99
1.80k
            4 => Self::SHA384,
100
25.6k
            _ => Self::Unknown(value),
101
        }
102
36.3k
    }
103
}
104
105
impl From<DigestType> for u8 {
106
16.5k
    fn from(a: DigestType) -> Self {
107
16.5k
        match a {
108
2.14k
            DigestType::SHA1 => 1,
109
1.94k
            DigestType::SHA256 => 2,
110
844
            DigestType::SHA384 => 4,
111
11.6k
            DigestType::Unknown(other) => other,
112
        }
113
16.5k
    }
114
}
115
116
/// A key that can be used to sign records.
117
pub trait SigningKey: Send + Sync + 'static {
118
    /// Sign DNS records.
119
    ///
120
    /// # Return value
121
    ///
122
    /// The signature, ready to be stored in an `RData::RRSIG`.
123
    fn sign(&self, tbs: &TBS) -> DnsSecResult<Vec<u8>>;
124
125
    /// Returns a [`PublicKeyBuf`] for this [`SigningKey`].
126
    fn to_public_key(&self) -> DnsSecResult<PublicKeyBuf>;
127
128
    /// Returns the algorithm of the key.
129
    fn algorithm(&self) -> Algorithm;
130
}
131
132
/// The format of the binary key
133
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134
pub enum KeyFormat {
135
    /// A der encoded key
136
    Der,
137
    /// A pem encoded key, the default of OpenSSL
138
    Pem,
139
    /// Pkcs8, a pkcs8 formatted private key
140
    Pkcs8,
141
}
142
143
/// An alias for dnssec results returned by functions of this crate
144
pub type DnsSecResult<T> = ::core::result::Result<T, DnsSecError>;
145
146
/// The error type for dnssec errors that get returned in the crate
147
#[derive(Debug, Clone, Error)]
148
pub struct DnsSecError {
149
    kind: DnsSecErrorKind,
150
    #[cfg(feature = "backtrace")]
151
    backtrack: Option<Backtrace>,
152
}
153
154
impl DnsSecError {
155
    /// Get the kind of the error
156
0
    pub fn kind(&self) -> &DnsSecErrorKind {
157
0
        &self.kind
158
0
    }
159
}
160
161
impl fmt::Display for DnsSecError {
162
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163
0
        cfg_if::cfg_if! {
164
0
            if #[cfg(feature = "backtrace")] {
165
0
                if let Some(backtrace) = &self.backtrack {
166
0
                    fmt::Display::fmt(&self.kind, f)?;
167
0
                    fmt::Debug::fmt(backtrace, f)
168
0
                } else {
169
0
                    fmt::Display::fmt(&self.kind, f)
170
0
                }
171
0
            } else {
172
0
                fmt::Display::fmt(&self.kind, f)
173
0
            }
174
0
        }
175
0
    }
176
}
177
178
impl From<DnsSecErrorKind> for DnsSecError {
179
0
    fn from(kind: DnsSecErrorKind) -> Self {
180
0
        Self {
181
0
            kind,
182
0
            #[cfg(feature = "backtrace")]
183
0
            backtrack: trace!(),
184
0
        }
185
0
    }
186
}
187
188
impl From<&'static str> for DnsSecError {
189
0
    fn from(msg: &'static str) -> Self {
190
0
        DnsSecErrorKind::Message(msg).into()
191
0
    }
192
}
193
194
impl From<String> for DnsSecError {
195
0
    fn from(msg: String) -> Self {
196
0
        DnsSecErrorKind::Msg(msg).into()
197
0
    }
198
}
199
200
impl From<ProtoError> for DnsSecError {
201
0
    fn from(e: ProtoError) -> Self {
202
0
        match e.kind() {
203
0
            ProtoErrorKind::Timeout => DnsSecErrorKind::Timeout.into(),
204
0
            _ => DnsSecErrorKind::from(e).into(),
205
        }
206
0
    }
207
}
208
209
impl From<ring_like::KeyRejected> for DnsSecError {
210
0
    fn from(e: ring_like::KeyRejected) -> Self {
211
0
        DnsSecErrorKind::from(e).into()
212
0
    }
213
}
214
215
impl From<ring_like::Unspecified> for DnsSecError {
216
0
    fn from(e: ring_like::Unspecified) -> Self {
217
0
        DnsSecErrorKind::from(e).into()
218
0
    }
219
}
220
221
/// The error kind for dnssec errors that get returned in the crate
222
#[derive(Debug, Error)]
223
#[non_exhaustive]
224
pub enum DnsSecErrorKind {
225
    /// An HMAC failed to verify
226
    #[error("hmac validation failure")]
227
    HmacInvalid,
228
229
    /// An error with an arbitrary message, referenced as &'static str
230
    #[error("{0}")]
231
    Message(&'static str),
232
233
    /// An error with an arbitrary message, stored as String
234
    #[error("{0}")]
235
    Msg(String),
236
237
    // foreign
238
    /// An error got returned by the hickory-proto crate
239
    #[error("proto error: {0}")]
240
    Proto(#[from] ProtoError),
241
242
    /// A ring error
243
    #[error("ring error: {0}")]
244
    RingKeyRejected(#[from] ring_like::KeyRejected),
245
246
    /// A ring error
247
    #[error("ring error: {0}")]
248
    RingUnspecified(#[from] ring_like::Unspecified),
249
250
    /// A request timed out
251
    #[error("request timed out")]
252
    Timeout,
253
254
    /// Tsig unsupported mac algorithm
255
    /// Supported algorithm documented in `TsigAlgorithm::supported` function.
256
    #[error("Tsig unsupported mac algorithm")]
257
    TsigUnsupportedMacAlgorithm(TsigAlgorithm),
258
259
    /// Tsig key verification failed
260
    #[error("Tsig key wrong key error")]
261
    TsigWrongKey,
262
}
263
264
impl Clone for DnsSecErrorKind {
265
0
    fn clone(&self) -> Self {
266
        use DnsSecErrorKind::*;
267
0
        match self {
268
0
            HmacInvalid => HmacInvalid,
269
0
            Message(msg) => Message(msg),
270
0
            Msg(msg) => Msg(msg.clone()),
271
            // foreign
272
0
            Proto(proto) => Proto(proto.clone()),
273
0
            RingKeyRejected(r) => Msg(format!("Ring rejected key: {r}")),
274
0
            RingUnspecified(_r) => RingUnspecified(ring_like::Unspecified),
275
0
            Timeout => Timeout,
276
0
            TsigUnsupportedMacAlgorithm(alg) => TsigUnsupportedMacAlgorithm(alg.clone()),
277
0
            TsigWrongKey => TsigWrongKey,
278
        }
279
0
    }
280
}
281
282
/// DNSSEC status of an answer
283
#[derive(Clone, Copy, Debug)]
284
pub enum DnssecSummary {
285
    /// All records have been DNSSEC validated
286
    Secure,
287
    /// At least one record is in the Bogus state
288
    Bogus,
289
    /// Insecure / Indeterminate (e.g. "Island of security")
290
    Insecure,
291
}
292
293
impl DnssecSummary {
294
    /// Whether the records have been DNSSEC validated or not
295
0
    pub fn from_records<'a>(records: impl Iterator<Item = &'a Record>) -> Self {
296
0
        let mut all_secure = None;
297
0
        for record in records {
298
0
            match record.proof() {
299
0
                Proof::Secure => {
300
0
                    all_secure.get_or_insert(true);
301
0
                }
302
0
                Proof::Bogus => return Self::Bogus,
303
0
                _ => all_secure = Some(false),
304
            }
305
        }
306
307
0
        if all_secure.unwrap_or(false) {
308
0
            Self::Secure
309
        } else {
310
0
            Self::Insecure
311
        }
312
0
    }
313
}
314
315
#[cfg(all(feature = "dnssec-aws-lc-rs", not(feature = "dnssec-ring")))]
316
pub(crate) use aws_lc_rs_impl as ring_like;
317
#[cfg(feature = "dnssec-ring")]
318
pub(crate) use ring_impl as ring_like;
319
320
#[cfg(feature = "dnssec-aws-lc-rs")]
321
#[cfg_attr(feature = "dnssec-ring", allow(unused_imports))]
322
pub(crate) mod aws_lc_rs_impl {
323
    pub(crate) use aws_lc_rs::{
324
        digest,
325
        error::{KeyRejected, Unspecified},
326
        hmac,
327
        rand::SystemRandom,
328
        rsa::PublicKeyComponents,
329
        signature::{
330
            self, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
331
            ED25519_PUBLIC_KEY_LEN, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RSA_PKCS1_SHA256,
332
            RSA_PKCS1_SHA512, RsaKeyPair,
333
        },
334
    };
335
}
336
337
#[cfg(feature = "dnssec-ring")]
338
pub(crate) mod ring_impl {
339
    pub(crate) use ring::{
340
        digest,
341
        error::{KeyRejected, Unspecified},
342
        hmac,
343
        rand::SystemRandom,
344
        rsa::PublicKeyComponents,
345
        signature::{
346
            self, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
347
            ED25519_PUBLIC_KEY_LEN, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RSA_PKCS1_SHA256,
348
            RSA_PKCS1_SHA512, RsaKeyPair,
349
        },
350
    };
351
}
352
353
#[cfg(test)]
354
mod test_utils {
355
    use rdata::DNSKEY;
356
357
    use super::*;
358
359
    pub(super) fn public_key_test(key: &dyn SigningKey) {
360
        let pk = key.to_public_key().unwrap();
361
362
        let tbs = TBS::from(&b"www.example.com"[..]);
363
        let mut sig = key.sign(&tbs).unwrap();
364
        assert!(
365
            pk.verify(tbs.as_ref(), &sig).is_ok(),
366
            "public_key_test() failed to verify (algorithm: {:?})",
367
            key.algorithm(),
368
        );
369
        sig[10] = !sig[10];
370
        assert!(
371
            pk.verify(tbs.as_ref(), &sig).is_err(),
372
            "algorithm: {:?} (public key, neg)",
373
            key.algorithm(),
374
        );
375
    }
376
377
    pub(super) fn hash_test(key: &dyn SigningKey, neg: &dyn SigningKey) {
378
        let tbs = TBS::from(&b"www.example.com"[..]);
379
380
        // TODO: convert to stored keys...
381
        let pub_key = key.to_public_key().unwrap();
382
        let neg_pub_key = neg.to_public_key().unwrap();
383
384
        let sig = key.sign(&tbs).unwrap();
385
        assert!(
386
            pub_key.verify(tbs.as_ref(), &sig).is_ok(),
387
            "algorithm: {:?}",
388
            key.algorithm(),
389
        );
390
391
        let pub_key = key.to_public_key().unwrap();
392
        let dns_key = DNSKEY::from_key(&pub_key);
393
        assert!(
394
            dns_key.verify(tbs.as_ref(), &sig).is_ok(),
395
            "algorithm: {:?} (dnskey)",
396
            pub_key.algorithm(),
397
        );
398
        assert!(
399
            neg_pub_key.verify(tbs.as_ref(), &sig).is_err(),
400
            "algorithm: {:?} (neg)",
401
            neg_pub_key.algorithm(),
402
        );
403
404
        let neg_pub_key = neg.to_public_key().unwrap();
405
        let neg_dns_key = DNSKEY::from_key(&neg_pub_key);
406
        assert!(
407
            neg_dns_key.verify(tbs.as_ref(), &sig).is_err(),
408
            "algorithm: {:?} (dnskey, neg)",
409
            neg_pub_key.algorithm(),
410
        );
411
    }
412
}