1use 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, RecordType, rdata::tsig::TsigAlgorithm};
21use crate::serialize::binary::{BinEncodable, BinEncoder, DecodeError, NameEncoding};
22
23mod algorithm;
24pub use algorithm::Algorithm;
25
26pub 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
56pub struct DnssecIter<'a>(slice::Iter<'a, Record<RData>>);
58
59impl<'a> DnssecIter<'a> {
60 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#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
148#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
149pub enum Nsec3HashAlgorithm {
150 #[default]
152 #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
153 SHA1,
154}
155
156impl Nsec3HashAlgorithm {
157 pub fn hash(self, salt: &[u8], name: &Name, iterations: u16) -> Result<Digest, ProtoError> {
189 match self {
190 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 fn try_from(value: u8) -> Result<Self, Self::Error> {
211 match value {
212 1 => Ok(Self::SHA1),
213 _ => 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#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
243#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
244#[non_exhaustive]
245pub enum DigestType {
246 #[cfg_attr(feature = "serde", serde(rename = "SHA-1"))]
248 SHA1,
249 #[cfg_attr(feature = "serde", serde(rename = "SHA-256"))]
251 SHA256,
252 #[cfg_attr(feature = "serde", serde(rename = "SHA-384"))]
254 SHA384,
255 Unknown(u8),
257}
258
259impl DigestType {
260 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
288pub trait SigningKey: Send + Sync + 'static {
290 fn sign(&self, tbs: &TBS) -> DnsSecResult<Vec<u8>>;
296
297 fn to_public_key(&self) -> DnsSecResult<PublicKeyBuf>;
299
300 fn algorithm(&self) -> Algorithm;
302}
303
304#[derive(Clone, Copy, Debug, Eq, PartialEq)]
306pub enum KeyFormat {
307 Der,
309 Pem,
311 Pkcs8,
313}
314
315pub type DnsSecResult<T> = ::core::result::Result<T, DnsSecError>;
317
318#[derive(Debug, Error)]
320#[non_exhaustive]
321pub enum DnsSecError {
322 #[error("hmac validation failure")]
324 HmacInvalid,
325
326 #[error("{0}")]
328 Message(&'static str),
329
330 #[error("{0}")]
332 Msg(String),
333
334 #[error("proto error: {0}")]
337 Proto(#[from] ProtoError),
338
339 #[error("ring error: {0}")]
341 RingKeyRejected(#[from] ring_like::KeyRejected),
342
343 #[error("ring error: {0}")]
345 RingUnspecified(#[from] ring_like::Unspecified),
346
347 #[error("Tsig unsupported mac algorithm")]
350 TsigUnsupportedMacAlgorithm(TsigAlgorithm),
351
352 #[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 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
388pub enum DnssecSummary {
389 Secure,
391 Bogus,
393 Insecure,
395}
396
397impl DnssecSummary {
398 pub fn from_records<'a>(records: impl Iterator<Item = &'a Record>) -> Self {
402 let mut all_secure = None;
403 for record in records {
404 if record.record_type() == RecordType::RRSIG {
405 continue;
406 }
407
408 match &record.proof {
409 Proof::Secure => {
410 all_secure.get_or_insert(true);
411 }
412 Proof::Bogus => return Self::Bogus,
413 _ => all_secure = Some(false),
414 }
415 }
416
417 if all_secure.unwrap_or(false) {
418 Self::Secure
419 } else {
420 Self::Insecure
421 }
422 }
423}
424
425#[cfg(all(feature = "dnssec-aws-lc-rs", not(feature = "dnssec-ring")))]
426pub(crate) use aws_lc_rs_impl as ring_like;
427#[cfg(feature = "dnssec-ring")]
428pub(crate) use ring_impl as ring_like;
429
430#[cfg(feature = "dnssec-aws-lc-rs")]
431#[cfg_attr(feature = "dnssec-ring", allow(unused_imports))]
432pub(crate) mod aws_lc_rs_impl {
433 pub(crate) use aws_lc_rs::{
434 digest,
435 error::{KeyRejected, Unspecified},
436 hmac,
437 rand::SystemRandom,
438 rsa::PublicKeyComponents,
439 signature::{
440 self, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
441 ED25519_PUBLIC_KEY_LEN, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RSA_PKCS1_SHA256,
442 RSA_PKCS1_SHA512, RsaKeyPair,
443 },
444 };
445}
446
447#[cfg(feature = "dnssec-ring")]
448pub(crate) mod ring_impl {
449 pub(crate) use ring::{
450 digest,
451 error::{KeyRejected, Unspecified},
452 hmac,
453 rand::SystemRandom,
454 rsa::PublicKeyComponents,
455 signature::{
456 self, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
457 ED25519_PUBLIC_KEY_LEN, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RSA_PKCS1_SHA256,
458 RSA_PKCS1_SHA512, RsaKeyPair,
459 },
460 };
461}
462
463#[cfg(test)]
464mod test_utils {
465 use rdata::DNSKEY;
466
467 use super::*;
468
469 pub(super) fn public_key_test(key: &dyn SigningKey) {
470 let pk = key.to_public_key().unwrap();
471
472 let tbs = TBS::from(&b"www.example.com"[..]);
473 let mut sig = key.sign(&tbs).unwrap();
474 assert!(
475 pk.verify(tbs.as_ref(), &sig).is_ok(),
476 "public_key_test() failed to verify (algorithm: {:?})",
477 key.algorithm(),
478 );
479 sig[10] = !sig[10];
480 assert!(
481 pk.verify(tbs.as_ref(), &sig).is_err(),
482 "algorithm: {:?} (public key, neg)",
483 key.algorithm(),
484 );
485 }
486
487 pub(super) fn hash_test(key: &dyn SigningKey, neg: &dyn SigningKey) {
488 let tbs = TBS::from(&b"www.example.com"[..]);
489
490 let pub_key = key.to_public_key().unwrap();
492 let neg_pub_key = neg.to_public_key().unwrap();
493
494 let sig = key.sign(&tbs).unwrap();
495 assert!(
496 pub_key.verify(tbs.as_ref(), &sig).is_ok(),
497 "algorithm: {:?}",
498 key.algorithm(),
499 );
500
501 let pub_key = key.to_public_key().unwrap();
502 let dns_key = DNSKEY::from_key(&pub_key);
503 assert!(
504 dns_key.verify(tbs.as_ref(), &sig).is_ok(),
505 "algorithm: {:?} (dnskey)",
506 pub_key.algorithm(),
507 );
508 assert!(
509 neg_pub_key.verify(tbs.as_ref(), &sig).is_err(),
510 "algorithm: {:?} (neg)",
511 neg_pub_key.algorithm(),
512 );
513
514 let neg_pub_key = neg.to_public_key().unwrap();
515 let neg_dns_key = DNSKEY::from_key(&neg_pub_key);
516 assert!(
517 neg_dns_key.verify(tbs.as_ref(), &sig).is_err(),
518 "algorithm: {:?} (dnskey, neg)",
519 neg_pub_key.algorithm(),
520 );
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use alloc::vec;
527 use core::net::Ipv4Addr;
528
529 use super::rdata::{DNSSECRData, RRSIG, SigInput};
530 use super::*;
531 use crate::rr::SerialNumber;
532
533 fn a_record(name: &Name, proof: Proof) -> Record {
534 let mut record = Record::from_rdata(
535 name.clone(),
536 3600,
537 RData::A(Ipv4Addr::new(192, 0, 2, 1).into()),
538 );
539 record.proof = proof;
540 record
541 }
542
543 fn rrsig_record(name: &Name, algorithm: Algorithm, proof: Proof) -> Record {
544 let input = SigInput {
545 type_covered: RecordType::A,
546 algorithm,
547 num_labels: 2,
548 original_ttl: 3600,
549 sig_expiration: SerialNumber(0),
550 sig_inception: SerialNumber(0),
551 key_tag: 0,
552 signer_name: Name::root(),
553 };
554 let mut record = Record::from_rdata(
555 name.clone(),
556 3600,
557 RData::DNSSEC(DNSSECRData::RRSIG(RRSIG::from_sig(input, vec![]))),
558 );
559 record.proof = proof;
560 record
561 }
562
563 #[test]
565 fn summary_ignores_unused_rrsig() {
566 let name = Name::from_ascii("www.example.").unwrap();
567 let records = [
568 a_record(&name, Proof::Secure),
569 rrsig_record(&name, Algorithm::ECDSAP256SHA256, Proof::Secure),
570 rrsig_record(&name, Algorithm::RSASHA256, Proof::Indeterminate),
571 ];
572
573 assert_eq!(
574 DnssecSummary::from_records(records.iter()),
575 DnssecSummary::Secure
576 );
577 }
578
579 #[test]
580 fn summary_follows_rrset_proof() {
581 let name = Name::from_ascii("www.example.").unwrap();
582 for (proof, expected) in [
583 (Proof::Secure, DnssecSummary::Secure),
584 (Proof::Insecure, DnssecSummary::Insecure),
585 (Proof::Indeterminate, DnssecSummary::Insecure),
586 (Proof::Bogus, DnssecSummary::Bogus),
587 ] {
588 let records = [
589 a_record(&name, proof),
590 rrsig_record(&name, Algorithm::ECDSAP256SHA256, proof),
591 ];
592 assert_eq!(DnssecSummary::from_records(records.iter()), expected);
593 }
594 }
595
596 #[test]
598 fn summary_ignores_rrsig_proof() {
599 let name = Name::from_ascii("www.example.").unwrap();
600 let records = [
601 a_record(&name, Proof::Bogus),
602 rrsig_record(&name, Algorithm::ECDSAP256SHA256, Proof::Secure),
603 ];
604 assert_eq!(
605 DnssecSummary::from_records(records.iter()),
606 DnssecSummary::Bogus
607 );
608
609 let records = [
610 a_record(&name, Proof::Insecure),
611 rrsig_record(&name, Algorithm::ECDSAP256SHA256, Proof::Secure),
612 ];
613 assert_eq!(
614 DnssecSummary::from_records(records.iter()),
615 DnssecSummary::Insecure
616 );
617 }
618
619 #[test]
620 fn summary_of_rrsigs_only_is_insecure() {
621 let name = Name::from_ascii("www.example.").unwrap();
622 let records = [rrsig_record(
623 &name,
624 Algorithm::ECDSAP256SHA256,
625 Proof::Secure,
626 )];
627 assert_eq!(
628 DnssecSummary::from_records(records.iter()),
629 DnssecSummary::Insecure
630 );
631 assert_eq!(
632 DnssecSummary::from_records([].iter()),
633 DnssecSummary::Insecure
634 );
635 }
636}