/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 | | } |