Coverage Report

Created: 2026-02-14 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hickory-dns/crates/proto/src/dnssec/signer.rs
Line
Count
Source
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
9
use alloc::{boxed::Box, vec::Vec};
10
use core::time::Duration;
11
12
use super::{DnsSecResult, SigningKey};
13
use 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
/// ```
222
pub struct DnssecSigner {
223
    dnskey: DNSKEY,
224
    key: Box<dyn SigningKey>,
225
    signer_name: Name,
226
    sig_duration: Duration,
227
}
228
229
impl 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
0
    pub fn new(
239
0
        dnskey: DNSKEY,
240
0
        key: Box<dyn SigningKey>,
241
0
        signer_name: Name,
242
0
        sig_duration: Duration,
243
0
    ) -> Self {
244
0
        Self {
245
0
            dnskey,
246
0
            key,
247
0
            signer_name,
248
0
            sig_duration,
249
0
        }
250
0
    }
251
252
    /// Return the key used for validation/signing
253
0
    pub fn key(&self) -> &dyn SigningKey {
254
0
        &*self.key
255
0
    }
256
257
    /// Returns the duration that this signature is valid for
258
0
    pub fn sig_duration(&self) -> Duration {
259
0
        self.sig_duration
260
0
    }
261
262
    /// Returns whether this DNSKEY has the zone key flag set
263
0
    pub fn is_zone_signing_key(&self) -> bool {
264
0
        self.dnskey.zone_key()
265
0
    }
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
0
    pub fn sign(&self, tbs: &TBS) -> ProtoResult<Vec<u8>> {
279
0
        self.key
280
0
            .sign(tbs)
281
0
            .map_err(|e| ProtoError::Msg(format!("signing error: {e}")))
282
0
    }
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
0
    pub fn signer_name(&self) -> &Name {
288
0
        &self.signer_name
289
0
    }
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
0
    pub fn calculate_key_tag(&self) -> ProtoResult<u16> {
344
0
        let mut bytes: Vec<u8> = Vec::with_capacity(512);
345
        {
346
0
            let mut e = BinEncoder::new(&mut bytes);
347
0
            self.dnskey.emit(&mut e)?;
348
        }
349
0
        Ok(DNSKEY::calculate_key_tag_internal(&bytes))
350
0
    }
351
352
    /// Returns a reference to the DNSKEY for this signer
353
0
    pub fn dnskey(&self) -> &DNSKEY {
354
0
        &self.dnskey
355
0
    }
356
357
    /// Returns a clone of the DNSKEY for this signer
358
0
    pub fn to_dnskey(&self) -> DNSKEY {
359
0
        self.dnskey.clone()
360
0
    }
361
362
    /// Test that this key is capable of signing and verifying data
363
0
    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
0
        Ok(())
374
0
    }
375
}
376
377
#[cfg(test)]
378
mod 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 rrset = [
425
            Record::from_rdata(
426
                origin.clone(),
427
                86400,
428
                RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
429
            )
430
            .set_dns_class(DNSClass::IN)
431
            .clone(),
432
            Record::from_rdata(
433
                origin.clone(),
434
                86400,
435
                RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
436
            )
437
            .set_dns_class(DNSClass::IN)
438
            .clone(),
439
        ];
440
441
        let tbs = TBS::from_input(&origin, DNSClass::IN, &input, rrset.iter()).unwrap();
442
        let sig = signer.sign(&tbs).unwrap();
443
444
        let pub_key = signer.key().to_public_key().unwrap();
445
        assert!(pub_key.verify(tbs.as_ref(), &sig).is_ok());
446
    }
447
448
    #[test]
449
    #[allow(deprecated)]
450
    fn test_calculate_key_tag_pem() {
451
        let key =
452
            RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
453
                .unwrap();
454
        let pub_key = key.to_public_key().unwrap();
455
        let dnskey = DNSKEY::from_key(&pub_key);
456
        let signer = DnssecSigner::new(
457
            dnskey,
458
            Box::new(key),
459
            Name::root(),
460
            Duration::from_secs(300),
461
        );
462
        let key_tag = signer.calculate_key_tag().unwrap();
463
464
        assert_eq!(key_tag, 3257);
465
    }
466
467
    #[test]
468
    fn test_rrset_tbs() {
469
        let key =
470
            RsaSigningKey::from_pkcs8(&PrivatePkcs8KeyDer::from(RSA_KEY), Algorithm::RSASHA256)
471
                .unwrap();
472
        let pub_key = key.to_public_key().unwrap();
473
        let dnskey = DNSKEY::from_key(&pub_key);
474
        let signer = DnssecSigner::new(
475
            dnskey,
476
            Box::new(key),
477
            Name::root(),
478
            Duration::from_secs(300),
479
        );
480
481
        let origin = Name::parse("example.com.", None).unwrap();
482
        let input = SigInput {
483
            type_covered: RecordType::NS,
484
            algorithm: Algorithm::RSASHA256,
485
            num_labels: origin.num_labels(),
486
            original_ttl: 86400,
487
            sig_expiration: SerialNumber(5),
488
            sig_inception: SerialNumber(0),
489
            key_tag: signer.calculate_key_tag().unwrap(),
490
            signer_name: origin.clone(),
491
        };
492
493
        let rrset = [
494
            Record::from_rdata(
495
                origin.clone(),
496
                86400,
497
                RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
498
            )
499
            .set_dns_class(DNSClass::IN)
500
            .clone(),
501
            Record::from_rdata(
502
                origin.clone(),
503
                86400,
504
                RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
505
            )
506
            .set_dns_class(DNSClass::IN)
507
            .clone(),
508
        ];
509
510
        let tbs = TBS::from_input(&origin, DNSClass::IN, &input, rrset.iter()).unwrap();
511
        assert!(!tbs.as_ref().is_empty());
512
513
        let rrset = [
514
            Record::from_rdata(
515
                origin.clone(),
516
                86400,
517
                RData::CNAME(CNAME(Name::parse("a.iana-servers.net.", None).unwrap())),
518
            )
519
            .set_dns_class(DNSClass::IN)
520
            .clone(), // different type
521
            Record::from_rdata(
522
                Name::parse("www.example.com.", None).unwrap(),
523
                86400,
524
                RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
525
            )
526
            .set_dns_class(DNSClass::IN)
527
            .clone(), // different name
528
            Record::from_rdata(
529
                origin.clone(),
530
                86400,
531
                RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
532
            )
533
            .set_dns_class(DNSClass::CH)
534
            .clone(), // different class
535
            Record::from_rdata(
536
                origin.clone(),
537
                86400,
538
                RData::NS(NS(Name::parse("a.iana-servers.net.", None).unwrap())),
539
            )
540
            .set_dns_class(DNSClass::IN)
541
            .clone(),
542
            Record::from_rdata(
543
                origin.clone(),
544
                86400,
545
                RData::NS(NS(Name::parse("b.iana-servers.net.", None).unwrap())),
546
            )
547
            .set_dns_class(DNSClass::IN)
548
            .clone(),
549
        ];
550
551
        let filtered_tbs = TBS::from_input(&origin, DNSClass::IN, &input, rrset.iter()).unwrap();
552
        assert!(!filtered_tbs.as_ref().is_empty());
553
        assert_eq!(tbs.as_ref(), filtered_tbs.as_ref());
554
    }
555
556
    const RSA_KEY: &[u8] = include_bytes!("../../tests/test-data/rsa-2048-private-key-1.pk8");
557
}