hickory_proto/dnssec/signer.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//! signer is a structure for performing many of the signing processes of the DNSSEC specification
9use alloc::{boxed::Box, vec::Vec};
10use core::time::Duration;
11
12use super::{DnsSecResult, SigningKey};
13use crate::{
14 dnssec::{TBS, rdata::DNSKEY},
15 error::{ProtoError, ProtoResult},
16 rr::Name,
17 serialize::binary::{BinEncodable, BinEncoder},
18};
19
20/// A DNSSEC signer that bundles a DNSKEY with its corresponding private key for signing operations.
21///
22/// This type is used to create RRSIG records for zone signing. It holds the public key material
23/// (DNSKEY), the private signing key, and metadata needed for signature creation.
24///
25/// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005
26///
27/// ```text
28/// 5.3. Authenticating an RRset with an RRSIG RR
29///
30/// A validator can use an RRSIG RR and its corresponding DNSKEY RR to
31/// attempt to authenticate RRsets. The validator first checks the RRSIG
32/// RR to verify that it covers the RRset, has a valid time interval, and
33/// identifies a valid DNSKEY RR. The validator then constructs the
34/// canonical form of the signed data by appending the RRSIG RDATA
35/// (excluding the Signature Field) with the canonical form of the
36/// covered RRset. Finally, the validator uses the public key and
37/// signature to authenticate the signed data. Sections 5.3.1, 5.3.2,
38/// and 5.3.3 describe each step in detail.
39///
40/// 5.3.1. Checking the RRSIG RR Validity
41///
42/// A security-aware resolver can use an RRSIG RR to authenticate an
43/// RRset if all of the following conditions hold:
44///
45/// o The RRSIG RR and the RRset MUST have the same owner name and the
46/// same class.
47///
48/// o The RRSIG RR's Signer's Name field MUST be the name of the zone
49/// that contains the RRset.
50///
51/// o The RRSIG RR's Type Covered field MUST equal the RRset's type.
52///
53/// o The number of labels in the RRset owner name MUST be greater than
54/// or equal to the value in the RRSIG RR's Labels field.
55///
56/// o The validator's notion of the current time MUST be less than or
57/// equal to the time listed in the RRSIG RR's Expiration field.
58///
59/// o The validator's notion of the current time MUST be greater than or
60/// equal to the time listed in the RRSIG RR's Inception field.
61///
62/// o The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST
63/// match the owner name, algorithm, and key tag for some DNSKEY RR in
64/// the zone's apex DNSKEY RRset.
65///
66/// o The matching DNSKEY RR MUST be present in the zone's apex DNSKEY
67/// RRset, and MUST have the Zone Flag bit (DNSKEY RDATA Flag bit 7)
68/// set.
69///
70/// It is possible for more than one DNSKEY RR to match the conditions
71/// above. In this case, the validator cannot predetermine which DNSKEY
72/// RR to use to authenticate the signature, and it MUST try each
73/// matching DNSKEY RR until either the signature is validated or the
74/// validator has run out of matching public keys to try.
75///
76/// Note that this authentication process is only meaningful if the
77/// validator authenticates the DNSKEY RR before using it to validate
78/// signatures. The matching DNSKEY RR is considered to be authentic if:
79///
80/// o the apex DNSKEY RRset containing the DNSKEY RR is considered
81/// authentic; or
82///
83/// o the RRset covered by the RRSIG RR is the apex DNSKEY RRset itself,
84/// and the DNSKEY RR either matches an authenticated DS RR from the
85/// parent zone or matches a trust anchor.
86///
87/// 5.3.2. Reconstructing the Signed Data
88///
89/// Once the RRSIG RR has met the validity requirements described in
90/// Section 5.3.1, the validator has to reconstruct the original signed
91/// data. The original signed data includes RRSIG RDATA (excluding the
92/// Signature field) and the canonical form of the RRset. Aside from
93/// being ordered, the canonical form of the RRset might also differ from
94/// the received RRset due to DNS name compression, decremented TTLs, or
95/// wildcard expansion. The validator should use the following to
96/// reconstruct the original signed data:
97///
98/// signed_data = RRSIG_RDATA | RR(1) | RR(2)... where
99///
100/// "|" denotes concatenation
101///
102/// RRSIG_RDATA is the wire format of the RRSIG RDATA fields
103/// with the Signature field excluded and the Signer's Name
104/// in canonical form.
105///
106/// RR(i) = name | type | class | OrigTTL | RDATA length | RDATA
107///
108/// name is calculated according to the function below
109///
110/// class is the RRset's class
111///
112/// type is the RRset type and all RRs in the class
113///
114/// OrigTTL is the value from the RRSIG Original TTL field
115///
116/// All names in the RDATA field are in canonical form
117///
118/// The set of all RR(i) is sorted into canonical order.
119///
120/// To calculate the name:
121/// let rrsig_labels = the value of the RRSIG Labels field
122///
123/// let fqdn = RRset's fully qualified domain name in
124/// canonical form
125///
126/// let fqdn_labels = Label count of the fqdn above.
127///
128/// if rrsig_labels = fqdn_labels,
129/// name = fqdn
130///
131/// if rrsig_labels < fqdn_labels,
132/// name = "*." | the rightmost rrsig_label labels of the
133/// fqdn
134///
135/// if rrsig_labels > fqdn_labels
136/// the RRSIG RR did not pass the necessary validation
137/// checks and MUST NOT be used to authenticate this
138/// RRset.
139///
140/// The canonical forms for names and RRsets are defined in [RFC4034].
141///
142/// NSEC RRsets at a delegation boundary require special processing.
143/// There are two distinct NSEC RRsets associated with a signed delegated
144/// name. One NSEC RRset resides in the parent zone, and specifies which
145/// RRsets are present at the parent zone. The second NSEC RRset resides
146/// at the child zone and identifies which RRsets are present at the apex
147/// in the child zone. The parent NSEC RRset and child NSEC RRset can
148/// always be distinguished as only a child NSEC RR will indicate that an
149/// SOA RRset exists at the name. When reconstructing the original NSEC
150/// RRset for the delegation from the parent zone, the NSEC RRs MUST NOT
151/// be combined with NSEC RRs from the child zone. When reconstructing
152/// the original NSEC RRset for the apex of the child zone, the NSEC RRs
153/// MUST NOT be combined with NSEC RRs from the parent zone.
154///
155/// Note that each of the two NSEC RRsets at a delegation point has a
156/// corresponding RRSIG RR with an owner name matching the delegated
157/// name, and each of these RRSIG RRs is authoritative data associated
158/// with the same zone that contains the corresponding NSEC RRset. If
159/// necessary, a resolver can tell these RRSIG RRs apart by checking the
160/// Signer's Name field.
161///
162/// 5.3.3. Checking the Signature
163///
164/// Once the resolver has validated the RRSIG RR as described in Section
165/// 5.3.1 and reconstructed the original signed data as described in
166/// Section 5.3.2, the validator can attempt to use the cryptographic
167/// signature to authenticate the signed data, and thus (finally!)
168/// authenticate the RRset.
169///
170/// The Algorithm field in the RRSIG RR identifies the cryptographic
171/// algorithm used to generate the signature. The signature itself is
172/// contained in the Signature field of the RRSIG RDATA, and the public
173/// key used to verify the signature is contained in the Public Key field
174/// of the matching DNSKEY RR(s) (found in Section 5.3.1). [RFC4034]
175/// provides a list of algorithm types and provides pointers to the
176/// documents that define each algorithm's use.
177///
178/// Note that it is possible for more than one DNSKEY RR to match the
179/// conditions in Section 5.3.1. In this case, the validator can only
180/// determine which DNSKEY RR is correct by trying each matching public
181/// key until the validator either succeeds in validating the signature
182/// or runs out of keys to try.
183///
184/// If the Labels field of the RRSIG RR is not equal to the number of
185/// labels in the RRset's fully qualified owner name, then the RRset is
186/// either invalid or the result of wildcard expansion. The resolver
187/// MUST verify that wildcard expansion was applied properly before
188/// considering the RRset to be authentic. Section 5.3.4 describes how
189/// to determine whether a wildcard was applied properly.
190///
191/// If other RRSIG RRs also cover this RRset, the local resolver security
192/// policy determines whether the resolver also has to test these RRSIG
193/// RRs and how to resolve conflicts if these RRSIG RRs lead to differing
194/// results.
195///
196/// If the resolver accepts the RRset as authentic, the validator MUST
197/// set the TTL of the RRSIG RR and each RR in the authenticated RRset to
198/// a value no greater than the minimum of:
199///
200/// o the RRset's TTL as received in the response;
201///
202/// o the RRSIG RR's TTL as received in the response;
203///
204/// o the value in the RRSIG RR's Original TTL field; and
205///
206/// o the difference of the RRSIG RR's Signature Expiration time and the
207/// current time.
208///
209/// 5.3.4. Authenticating a Wildcard Expanded RRset Positive Response
210///
211/// If the number of labels in an RRset's owner name is greater than the
212/// Labels field of the covering RRSIG RR, then the RRset and its
213/// covering RRSIG RR were created as a result of wildcard expansion.
214/// Once the validator has verified the signature, as described in
215/// Section 5.3, it must take additional steps to verify the non-
216/// existence of an exact match or closer wildcard match for the query.
217/// Section 5.4 discusses these steps.
218///
219/// Note that the response received by the resolver should include all
220/// NSEC RRs needed to authenticate the response (see Section 3.1.3).
221/// ```
222pub struct DnssecSigner {
223 dnskey: DNSKEY,
224 key: Box<dyn SigningKey>,
225 signer_name: Name,
226 sig_duration: Duration,
227}
228
229impl DnssecSigner {
230 /// Creates a new DNSSEC signer for creating RRSIGs.
231 ///
232 /// # Arguments
233 ///
234 /// * `dnskey` - the DNSKEY containing the public key material
235 /// * `key` - the private key for signing
236 /// * `signer_name` - name in the zone to which this DNSKEY is bound
237 /// * `sig_duration` - time period for which signatures created by this key are valid
238 pub fn new(
239 dnskey: DNSKEY,
240 key: Box<dyn SigningKey>,
241 signer_name: Name,
242 sig_duration: Duration,
243 ) -> Self {
244 Self {
245 dnskey,
246 key,
247 signer_name,
248 sig_duration,
249 }
250 }
251
252 /// Return the key used for validation/signing
253 pub fn key(&self) -> &dyn SigningKey {
254 &*self.key
255 }
256
257 /// Returns the duration that this signature is valid for
258 pub fn sig_duration(&self) -> Duration {
259 self.sig_duration
260 }
261
262 /// Returns whether this DNSKEY has the zone key flag set
263 pub fn is_zone_signing_key(&self) -> bool {
264 self.dnskey.zone_key()
265 }
266
267 /// Signs a hash.
268 ///
269 /// This will panic if the `key` is not a private key and can be used for signing.
270 ///
271 /// # Arguments
272 ///
273 /// * `hash` - the hashed resource record set, see `rrset_tbs`.
274 ///
275 /// # Return value
276 ///
277 /// The signature, ready to be stored in an `RData::RRSIG`.
278 pub fn sign(&self, tbs: &TBS) -> ProtoResult<Vec<u8>> {
279 self.key
280 .sign(tbs)
281 .map_err(|e| ProtoError::Msg(format!("signing error: {e}")))
282 }
283
284 /// The name of the signing entity, e.g. the DNS server name.
285 ///
286 /// This should match the name on key in the zone.
287 pub fn signer_name(&self) -> &Name {
288 &self.signer_name
289 }
290
291 // TODO: move this to DNSKEY/KEY?
292 /// The key tag is calculated as a hash to more quickly lookup a DNSKEY.
293 ///
294 /// ```text
295 /// RFC 2535 DNS Security Extensions March 1999
296 ///
297 /// 4.1.6 Key Tag Field
298 ///
299 /// The "key Tag" is a two octet quantity that is used to efficiently
300 /// select between multiple keys which may be applicable and thus check
301 /// that a public key about to be used for the computationally expensive
302 /// effort to check the signature is possibly valid. For algorithm 1
303 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
304 /// octets of the public key modulus needed to decode the signature
305 /// field. That is to say, the most significant 16 of the least
306 /// significant 24 bits of the modulus in network (big endian) order. For
307 /// all other algorithms, including private algorithms, it is calculated
308 /// as a simple checksum of the KEY RR as described in Appendix C.
309 ///
310 /// Appendix C: Key Tag Calculation
311 ///
312 /// The key tag field in the SIG RR is just a means of more efficiently
313 /// selecting the correct KEY RR to use when there is more than one KEY
314 /// RR candidate available, for example, in verifying a signature. It is
315 /// possible for more than one candidate key to have the same tag, in
316 /// which case each must be tried until one works or all fail. The
317 /// following reference implementation of how to calculate the Key Tag,
318 /// for all algorithms other than algorithm 1, is in ANSI C. It is coded
319 /// for clarity, not efficiency. (See section 4.1.6 for how to determine
320 /// the Key Tag of an algorithm 1 key.)
321 ///
322 /// /* assumes int is at least 16 bits
323 /// first byte of the key tag is the most significant byte of return
324 /// value
325 /// second byte of the key tag is the least significant byte of
326 /// return value
327 /// */
328 ///
329 /// int keytag (
330 ///
331 /// unsigned char key[], /* the RDATA part of the KEY RR */
332 /// unsigned int keysize, /* the RDLENGTH */
333 /// )
334 /// {
335 /// long int ac; /* assumed to be 32 bits or larger */
336 ///
337 /// for ( ac = 0, i = 0; i < keysize; ++i )
338 /// ac += (i&1) ? key[i] : key[i]<<8;
339 /// ac += (ac>>16) & 0xFFFF;
340 /// return ac & 0xFFFF;
341 /// }
342 /// ```
343 pub fn calculate_key_tag(&self) -> ProtoResult<u16> {
344 let mut bytes: Vec<u8> = Vec::with_capacity(512);
345 {
346 let mut e = BinEncoder::new(&mut bytes);
347 self.dnskey.emit(&mut e)?;
348 }
349 Ok(DNSKEY::calculate_key_tag_internal(&bytes))
350 }
351
352 /// Returns a reference to the DNSKEY for this signer
353 pub fn dnskey(&self) -> &DNSKEY {
354 &self.dnskey
355 }
356
357 /// Returns a clone of the DNSKEY for this signer
358 pub fn to_dnskey(&self) -> DNSKEY {
359 self.dnskey.clone()
360 }
361
362 /// Test that this key is capable of signing and verifying data
363 pub fn test_key(&self) -> DnsSecResult<()> {
364 // use proto::rr::dnssec::PublicKey;
365
366 // // TODO: why doesn't this work for ecdsa_256 and 384?
367 // let test_data = TBS::from(b"DEADBEEF" as &[u8]);
368
369 // let signature = self.sign(&test_data).map_err(|e| {println!("failed to sign, {:?}", e); e})?;
370 // let pk = self.key.to_public_key()?;
371 // pk.verify(self.algorithm, test_data.as_ref(), &signature).map_err(|e| {println!("failed to verify, {:?}", e); e})?;
372
373 Ok(())
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 #![allow(clippy::dbg_macro, clippy::print_stdout)]
380
381 use rustls_pki_types::PrivatePkcs8KeyDer;
382
383 use super::*;
384 use crate::dnssec::{
385 Algorithm, PublicKey, SigningKey, TBS, crypto::RsaSigningKey, rdata::SigInput,
386 };
387 use crate::rr::rdata::{CNAME, NS};
388 use crate::rr::{DNSClass, Name, RData, Record, RecordType, SerialNumber};
389
390 fn assert_send_and_sync<T: Send + Sync>() {}
391
392 #[test]
393 fn test_send_and_sync() {
394 assert_send_and_sync::<DnssecSigner>();
395 }
396
397 #[test]
398 #[allow(deprecated)]
399 fn test_sign_and_verify_rrset() {
400 let key =
401 RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
402 .unwrap();
403 let pub_key = key.to_public_key().unwrap();
404 let dnskey = DNSKEY::from_key(&pub_key);
405 let signer = DnssecSigner::new(
406 dnskey,
407 Box::new(key),
408 Name::root(),
409 Duration::from_secs(300),
410 );
411
412 let origin = Name::parse("example.com.", None).unwrap();
413 let input = SigInput {
414 type_covered: RecordType::NS,
415 algorithm: Algorithm::RSASHA256,
416 num_labels: origin.num_labels(),
417 original_ttl: 86400,
418 sig_expiration: SerialNumber(5),
419 sig_inception: SerialNumber(0),
420 key_tag: signer.calculate_key_tag().unwrap(),
421 signer_name: origin.clone(),
422 };
423
424 let mut a = Record::from_rdata(
425 origin.clone(),
426 86400,
427 RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
428 );
429 a.dns_class = DNSClass::IN;
430
431 let mut b = Record::from_rdata(
432 origin.clone(),
433 86400,
434 RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
435 );
436 b.dns_class = DNSClass::IN;
437 let rrset = [a, b];
438
439 let tbs = TBS::from_input(&origin, DNSClass::IN, &input, rrset.iter()).unwrap();
440 let sig = signer.sign(&tbs).unwrap();
441
442 let pub_key = signer.key().to_public_key().unwrap();
443 assert!(pub_key.verify(tbs.as_ref(), &sig).is_ok());
444 }
445
446 #[test]
447 #[allow(deprecated)]
448 fn test_calculate_key_tag_pem() {
449 let key =
450 RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
451 .unwrap();
452 let pub_key = key.to_public_key().unwrap();
453 let dnskey = DNSKEY::from_key(&pub_key);
454 let signer = DnssecSigner::new(
455 dnskey,
456 Box::new(key),
457 Name::root(),
458 Duration::from_secs(300),
459 );
460 let key_tag = signer.calculate_key_tag().unwrap();
461
462 assert_eq!(key_tag, 3257);
463 }
464
465 #[test]
466 fn test_rrset_tbs() {
467 let key =
468 RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
469 .unwrap();
470 let pub_key = key.to_public_key().unwrap();
471 let dnskey = DNSKEY::from_key(&pub_key);
472 let signer = DnssecSigner::new(
473 dnskey,
474 Box::new(key),
475 Name::root(),
476 Duration::from_secs(300),
477 );
478
479 let origin = Name::parse("example.com.", None).unwrap();
480 let input = SigInput {
481 type_covered: RecordType::NS,
482 algorithm: Algorithm::RSASHA256,
483 num_labels: origin.num_labels(),
484 original_ttl: 86400,
485 sig_expiration: SerialNumber(5),
486 sig_inception: SerialNumber(0),
487 key_tag: signer.calculate_key_tag().unwrap(),
488 signer_name: origin.clone(),
489 };
490
491 let rrset = [
492 Record::from_rdata(
493 origin.clone(),
494 86400,
495 RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
496 ),
497 Record::from_rdata(
498 origin.clone(),
499 86400,
500 RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
501 ),
502 ];
503
504 let tbs = TBS::from_input(&origin, DNSClass::IN, &input, rrset.iter()).unwrap();
505 assert!(!tbs.as_ref().is_empty());
506
507 let mut a_ch = Record::from_rdata(
508 origin.clone(),
509 86400,
510 RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
511 );
512 a_ch.dns_class = DNSClass::CH;
513
514 let rrset = [
515 Record::from_rdata(
516 origin.clone(),
517 86400,
518 RData::CNAME(CNAME(Name::parse("a.iana-servers.net.", None).unwrap())),
519 ), // different type
520 Record::from_rdata(
521 Name::parse("www.example.com.", None).unwrap(),
522 86400,
523 RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
524 ), // different name
525 a_ch, // different class
526 Record::from_rdata(
527 origin.clone(),
528 86400,
529 RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
530 ),
531 Record::from_rdata(
532 origin.clone(),
533 86400,
534 RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
535 ),
536 ];
537
538 let filtered_tbs = TBS::from_input(&origin, DNSClass::IN, &input, rrset.iter()).unwrap();
539 assert!(!filtered_tbs.as_ref().is_empty());
540 assert_eq!(tbs.as_ref(), filtered_tbs.as_ref());
541 }
542
543 const RSA_KEY: &[u8] = include_bytes!("../../tests/test-data/rsa-2048-private-key-1.pk8");
544}