Skip to main content

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}