File Coverage

blib/lib/Zonemaster/Engine/Test/DNSSEC.pm
Criterion Covered Total %
statement 358 392 91.3
branch 156 204 76.4
condition 39 69 56.5
subroutine 27 27 100.0
pod 16 16 100.0
total 596 708 84.1


line stmt bran cond sub pod time code
1             package Zonemaster::Engine::Test::DNSSEC;
2              
3 26     26   97854 use version; our $VERSION = version->declare("v1.0.7");
  26         68  
  26         204  
4              
5             ###
6             ### This test module implements DNSSEC tests.
7             ###
8              
9 26     26   2434 use strict;
  26         63  
  26         559  
10 26     26   134 use warnings;
  26         55  
  26         682  
11              
12 26     26   470 use 5.014002;
  26         94  
13              
14 26     26   143 use Zonemaster::Engine;
  26         65  
  26         542  
15 26     26   156 use Zonemaster::Engine::Util;
  26         63  
  26         1757  
16 26     26   158 use Zonemaster::Engine::Constants qw[:algo :soa];
  26         54  
  26         4848  
17 26     26   178 use List::Util qw[min];
  26         53  
  26         1363  
18 26     26   143 use List::MoreUtils qw[none];
  26         56  
  26         211  
19              
20 26     26   15928 use Carp;
  26         52  
  26         109807  
21              
22             ### Table fetched from IANA on 2017-03-09
23             Readonly::Hash our %algo_properties => (
24             0 => {
25             status => $ALGO_STATUS_RESERVED,
26             description => q{Reserved},
27             },
28             1 => {
29             status => $ALGO_STATUS_DEPRECATED,
30             description => q{RSA/MD5},
31             mnemonic => q{RSAMD5},
32             },
33             2 => {
34             status => $ALGO_STATUS_VALID,
35             description => q{Diffie-Hellman},
36             mnemonic => q{DH},
37             },
38             3 => {
39             status => $ALGO_STATUS_VALID,
40             description => q{DSA/SHA1},
41             mnemonic => q{DSA},
42             },
43             4 => {
44             status => $ALGO_STATUS_RESERVED,
45             description => q{Reserved},
46             },
47             5 => {
48             status => $ALGO_STATUS_VALID,
49             description => q{RSA/SHA1},
50             mnemonic => q{RSASHA1},
51             },
52             6 => {
53             status => $ALGO_STATUS_VALID,
54             description => q{DSA-NSEC3-SHA1},
55             mnemonic => q{DSA-NSEC3-SHA1},
56             },
57             7 => {
58             status => $ALGO_STATUS_VALID,
59             description => q{RSASHA1-NSEC3-SHA1},
60             mnemonic => q{RSASHA1-NSEC3-SHA1},
61             },
62             8 => {
63             status => $ALGO_STATUS_VALID,
64             description => q{RSA/SHA-256},
65             mnemonic => q{RSA/SHA256},
66             },
67             9 => {
68             status => $ALGO_STATUS_RESERVED,
69             description => q{Reserved},
70             },
71             10 => {
72             status => $ALGO_STATUS_VALID,
73             description => q{RSA/SHA-512},
74             mnemonic => q{RSA/SHA512},
75             },
76             11 => {
77             status => $ALGO_STATUS_RESERVED,
78             description => q{Reserved},
79             },
80             12 => {
81             status => $ALGO_STATUS_VALID,
82             description => q{GOST R 34.10-2001},
83             mnemonic => q{ECC-GOST},
84             },
85             13 => {
86             status => $ALGO_STATUS_VALID,
87             description => q{ECDSA Curve P-256 with SHA-256},
88             mnemonic => q{ECDSAP256SHA256},
89             },
90             14 => {
91             status => $ALGO_STATUS_VALID,
92             description => q{ECDSA Curve P-384 with SHA-384},
93             mnemonic => q{ECDSAP384SHA384},
94             },
95             15 => {
96             status => $ALGO_STATUS_VALID,
97             description => q{Ed25519},
98             mnemonic => q{Ed25519},
99             },
100             16 => {
101             status => $ALGO_STATUS_VALID,
102             description => q{Ed448},
103             mnemonic => q{Ed448},
104             },
105             (
106             map { $_ => { status => $ALGO_STATUS_UNASSIGNED, description => q{Unassigned}, } } ( 17 .. 122 )
107             ),
108             (
109             map { $_ => { status => $ALGO_STATUS_RESERVED, description => q{Reserved}, } } ( 123 .. 251 )
110             ),
111             252 => {
112             status => $ALGO_STATUS_RESERVED,
113             description => q{Reserved for Indirect Keys},
114             mnemonic => q{INDIRECT},
115             },
116             253 => {
117             status => $ALGO_STATUS_PRIVATE,
118             description => q{private algorithm},
119             mnemonic => q{PRIVATEDNS},
120             },
121             254 => {
122             status => $ALGO_STATUS_PRIVATE,
123             description => q{private algorithm OID},
124             mnemonic => q{PRIVATEOID},
125             },
126             255 => {
127             status => $ALGO_STATUS_RESERVED,
128             description => q{Reserved},
129             },
130             );
131              
132             ###
133             ### Entry points
134             ###
135              
136             sub all {
137 12     12 1 37 my ( $class, $zone ) = @_;
138 12         25 my @results;
139              
140 12 100       45 if ( Zonemaster::Engine->config->should_run('dnssec07') ) {
141 3         17 push @results, $class->dnssec07( $zone );
142             }
143              
144 12 50 66     51 if ( Zonemaster::Engine->config->should_run('dnssec07') and grep { $_->tag eq 'NEITHER_DNSKEY_NOR_DS' } @results ) {
  3         80  
145 0         0 push @results,
146             info(
147             NOT_SIGNED => {
148             zone => q{} . $zone->name
149             }
150             );
151              
152             } else {
153              
154 12 100       50 if ( Zonemaster::Engine->config->should_run('dnssec01') ) {
155 3         19 push @results, $class->dnssec01( $zone );
156             }
157              
158 12 100   8   131 if ( none { $_->tag eq 'NO_DS' } @results ) {
  8         215  
159 11 100       45 if ( Zonemaster::Engine->config->should_run('dnssec02') ) {
160 2         12 push @results, $class->dnssec02( $zone );
161             }
162             }
163              
164 12 100       71 if ( Zonemaster::Engine->config->should_run('dnssec03') ) {
165 3         15 push @results, $class->dnssec03( $zone );
166             }
167              
168 12 100       43 if ( Zonemaster::Engine->config->should_run('dnssec04') ) {
169 3         15 push @results, $class->dnssec04( $zone );
170             }
171              
172 12 100       50 if ( Zonemaster::Engine->config->should_run('dnssec05') ) {
173 3         17 push @results, $class->dnssec05( $zone );
174             }
175            
176 12 100       43 if ( grep { $_->tag eq q{DNSKEY_BUT_NOT_DS} or $_->tag eq q{DNSKEY_AND_DS} } @results ) {
  51 100       1223  
177 3 100       14 if ( Zonemaster::Engine->config->should_run('dnssec06') ) {
178 2         10 push @results, $class->dnssec06( $zone );
179             }
180             }
181             else {
182 9         45 push @results,
183             info( ADDITIONAL_DNSKEY_SKIPPED => {} );
184             }
185              
186 12 100       259 if ( Zonemaster::Engine->config->should_run('dnssec08') ) {
187 3         20 push @results, $class->dnssec08( $zone );
188             }
189              
190 12 100       51 if ( Zonemaster::Engine->config->should_run('dnssec09') ) {
191 3         16 push @results, $class->dnssec09( $zone );
192             }
193              
194 12 100       43 if ( Zonemaster::Engine->config->should_run('dnssec10') ) {
195 3         21 push @results, $class->dnssec10( $zone );
196             }
197              
198 12 100       46 if ( Zonemaster::Engine->config->should_run('dnssec11') ) {
199 3         23 push @results, $class->dnssec11( $zone );
200             }
201              
202             }
203              
204 12         78 return @results;
205             } ## end sub all
206              
207             ###
208             ### Metadata Exposure
209             ###
210              
211             sub metadata {
212 64     64 1 190 my ( $class ) = @_;
213              
214             return {
215 64         1350 dnssec01 => [
216             qw(
217             DS_DIGTYPE_OK
218             DS_DIGTYPE_NOT_OK
219             NO_DS
220             )
221             ],
222             dnssec02 => [
223             qw(
224             NO_DS
225             DS_FOUND
226             NO_DNSKEY
227             DS_RFC4509_NOT_VALID
228             COMMON_KEYTAGS
229             DS_MATCHES_DNSKEY
230             DS_DOES_NOT_MATCH_DNSKEY
231             DS_MATCH_FOUND
232             DS_MATCH_NOT_FOUND
233             NO_COMMON_KEYTAGS
234             )
235             ],
236             dnssec03 => [
237             qw(
238             NO_NSEC3PARAM
239             NO_DNSKEY
240             MANY_ITERATIONS
241             TOO_MANY_ITERATIONS
242             ITERATIONS_OK
243             )
244             ],
245             dnssec04 => [
246             qw(
247             RRSIG_EXPIRATION
248             RRSIG_EXPIRED
249             REMAINING_SHORT
250             REMAINING_LONG
251             DURATION_LONG
252             DURATION_OK
253             )
254             ],
255             dnssec05 => [
256             qw(
257             ALGORITHM_DEPRECATED
258             ALGORITHM_RESERVED
259             ALGORITHM_UNASSIGNED
260             ALGORITHM_PRIVATE
261             ALGORITHM_OK
262             ALGORITHM_UNKNOWN
263             KEY_DETAILS
264             )
265             ],
266             dnssec06 => [
267             qw(
268             EXTRA_PROCESSING_OK
269             EXTRA_PROCESSING_BROKEN
270             )
271             ],
272             dnssec07 => [
273             qw(
274             ADDITIONAL_DNSKEY_SKIPPED
275             DNSKEY_BUT_NOT_DS
276             DNSKEY_AND_DS
277             NEITHER_DNSKEY_NOR_DS
278             DS_BUT_NOT_DNSKEY
279             )
280             ],
281             dnssec08 => [
282             qw(
283             DNSKEY_SIGNATURE_OK
284             DNSKEY_SIGNATURE_NOT_OK
285             DNSKEY_SIGNED
286             DNSKEY_NOT_SIGNED
287             NO_KEYS_OR_NO_SIGS
288             )
289             ],
290             dnssec09 => [
291             qw(
292             NO_KEYS_OR_NO_SIGS_OR_NO_SOA
293             SOA_SIGNATURE_OK
294             SOA_SIGNATURE_NOT_OK
295             SOA_SIGNED
296             SOA_NOT_SIGNED
297             )
298             ],
299             dnssec10 => [
300             qw(
301             INVALID_NAME_RCODE
302             NSEC_COVERS
303             NSEC_COVERS_NOT
304             NSEC_SIG_VERIFY_ERROR
305             NSEC_SIGNED
306             NSEC_NOT_SIGNED
307             HAS_NSEC
308             NSEC3_COVERS
309             NSEC3_COVERS_NOT
310             NSEC3_SIG_VERIFY_ERROR
311             NSEC3_SIGNED
312             NSEC3_NOT_SIGNED
313             HAS_NSEC3
314             HAS_NSEC3_OPTOUT )
315             ],
316             dnssec11 => [
317             qw(
318             DELEGATION_NOT_SIGNED
319             DELEGATION_SIGNED
320             ),
321             ],
322             };
323             } ## end sub metadata
324              
325             sub translation {
326             return {
327 1     1 1 52 "ADDITIONAL_DNSKEY_SKIPPED" => "No DNSKEYs found. Additional tests skipped.",
328             "ALGORITHM_DEPRECATED" =>
329             "The DNSKEY with tag {keytag} uses deprecated algorithm number {algorithm}/({description}).",
330             "ALGORITHM_OK" =>
331             "The DNSKEY with tag {keytag} uses algorithm number {algorithm}/({description}), which is OK.",
332             "ALGORITHM_RESERVED" =>
333             "The DNSKEY with tag {keytag} uses reserved algorithm number {algorithm}/({description}).",
334             "ALGORITHM_UNASSIGNED" =>
335             "The DNSKEY with tag {keytag} uses unassigned algorithm number {algorithm}/({description}).",
336             "ALGORITHM_PRIVATE" =>
337             "The DNSKEY with tag {keytag} uses private algorithm number {algorithm}/({description}).",
338             "ALGORITHM_UNKNOWN" => "The DNSKEY with tag {keytag} uses unknown algorithm number {algorithm}.",
339             "COMMON_KEYTAGS" => "There are both DS and DNSKEY records with key tags {keytags}.",
340             "DNSKEY_AND_DS" => "{parent} sent a DS record, and {child} a DNSKEY record.",
341             "DNSKEY_BUT_NOT_DS" => "{child} sent a DNSKEY record, but {parent} did not send a DS record.",
342             "DNSKEY_NOT_SIGNED" => "The apex DNSKEY RRset was not correctly signed.",
343             "DNSKEY_SIGNATURE_NOT_OK" => "Signature for DNSKEY with tag {signature} failed to verify with error '{error}'.",
344             "DNSKEY_SIGNATURE_OK" => "A signature for DNSKEY with tag {signature} was correctly signed.",
345             "DNSKEY_SIGNED" => "The apex DNSKEY RRset was correcly signed.",
346             "DS_BUT_NOT_DNSKEY" => "{parent} sent a DS record, but {child} did not send a DNSKEY record.",
347             "DS_DIGTYPE_NOT_OK" => "DS record with keytag {keytag} uses forbidden digest type {digtype}.",
348             "DS_DIGTYPE_OK" => "DS record with keytag {keytag} uses digest type {digtype}, which is OK.",
349             "DS_DOES_NOT_MATCH_DNSKEY" => "DS record with keytag {keytag} and digest type {digtype} does not match the DNSKEY with the same tag.",
350             "DS_FOUND" => "Found DS records with tags {keytags}.",
351             "DS_MATCHES_DNSKEY" => "DS record with keytag {keytag} and digest type {digtype} matches the DNSKEY with the same tag.",
352             "DS_MATCH_FOUND" => "At least one DS record with a matching DNSKEY record was found.",
353             "DS_MATCH_NOT_FOUND" => "No DS record with a matching DNSKEY record was found.",
354             "DS_RFC4509_NOT_VALID" => "Existing DS with digest type 2, while they do not match DNSKEY records, prevent use of DS with digest type 1 (RFC4509, section 3).",
355             "DURATION_LONG" =>
356             "RRSIG with keytag {tag} and covering type(s) {types} has a duration of {duration} seconds, which is too long.",
357             "DURATION_OK" =>
358             "RRSIG with keytag {tag} and covering type(s) {types} has a duration of {duration} seconds, which is just fine.",
359             "RRSIG_EXPIRATION" =>
360             "RRSIG with keytag {tag} and covering type(s) {types} expires at : {date}.",
361             "RRSIG_EXPIRED" =>
362             "RRSIG with keytag {tag} and covering type(s) {types} has already expired (expiration is: {expiration}).",
363             "REMAINING_SHORT" =>
364             "RRSIG with keytag {tag} and covering type(s) {types} has a remaining validity of {duration} seconds, which is too short.",
365             "REMAINING_LONG" =>
366             "RRSIG with keytag {tag} and covering type(s) {types} has a remaining validity of {duration} seconds, which is too long.",
367             "EXTRA_PROCESSING_BROKEN" => "Server at {server} sent {keys} DNSKEY records, and {sigs} RRSIG records.",
368             "EXTRA_PROCESSING_OK" => "Server at {server} sent {keys} DNSKEY records and {sigs} RRSIG records.",
369             "HAS_NSEC" => "The zone has NSEC records.",
370             "HAS_NSEC3" => "The zone has NSEC3 records.",
371             "HAS_NSEC3_OPTOUT" => "The zone has NSEC3 opt-out records.",
372             "INVALID_NAME_RCODE" => "When asked for the name {name}, which must not exist, the response had RCODE {rcode}.",
373             "ITERATIONS_OK" => "The number of NSEC3 iterations is {count}, which is OK.",
374             "KEY_DETAILS" => "Key with keytag {keytag} details : Size = {keysize}, Flags ({sep}, {rfc5011}).",
375             "MANY_ITERATIONS" => "The number of NSEC3 iterations is {count}, which is on the high side.",
376             "NEITHER_DNSKEY_NOR_DS" => "There are neither DS nor DNSKEY records for the zone.",
377             "NOT_SIGNED" => "The zone is not signed with DNSSEC.",
378             "NO_COMMON_KEYTAGS" => "No DS record had a DNSKEY with a matching keytag.",
379             "NO_DNSKEY" => "No DNSKEYs were returned.",
380             "NO_DS" => "{from} returned no DS records for {zone}.",
381             "NO_KEYS_OR_NO_SIGS" =>
382             "Cannot test DNSKEY signatures, because we got {keys} DNSKEY records and {sigs} RRSIG records.",
383             "NO_KEYS_OR_NO_SIGS_OR_NO_SOA" =>
384             "Cannot test SOA signatures, because we got {keys} DNSKEY records, {sigs} RRSIG records and {soas} SOA records.",
385             "NO_NSEC3PARAM" => "{server} returned no NSEC3PARAM records.",
386             "NSEC3_SIG_VERIFY_ERROR" => "Trying to verify NSEC3 RRset with RRSIG {sig} gave error '{error}'.",
387             "NSEC3_COVERS" => "NSEC3 record covers {name}.",
388             "NSEC3_COVERS_NOT" => "NSEC3 record does not cover {name}.",
389             "NSEC3_NOT_SIGNED" => "No signature correctly signed the NSEC3 RRset.",
390             "NSEC3_SIGNED" => "At least one signature correctly signed the NSEC3 RRset.",
391             "NSEC_COVERS" => "NSEC covers {name}.",
392             "NSEC_COVERS_NOT" => "NSEC does not cover {name}.",
393             "NSEC_NOT_SIGNED" => "No signature correctly signed the NSEC RRset.",
394             "NSEC_SIGNED" => "At least one signature correctly signed the NSEC RRset.",
395             "NSEC_SIG_VERIFY_ERROR" => "Trying to verify NSEC RRset with RRSIG {sig} gave error '{error}'.",
396             "SOA_NOT_SIGNED" => "No RRSIG correctly signed the SOA RRset.",
397             "SOA_SIGNATURE_NOT_OK" => "Trying to verify SOA RRset with signature {signature} gave error '{error}'.",
398             "SOA_SIGNATURE_OK" => "RRSIG {signature} correctly signs SOA RRset.",
399             "SOA_SIGNED" => "At least one RRSIG correctly signs the SOA RRset.",
400             "TOO_MANY_ITERATIONS" =>
401             "The number of NSEC3 iterations is {count}, which is too high for key length {keylength}.",
402             "DELEGATION_NOT_SIGNED" => "Delegation from parent to child is not properly signed {reason}.",
403             "DELEGATION_SIGNED" => "Delegation from parent to child is properly signed.",
404             };
405             } ## end sub translation
406              
407             sub policy {
408             return {
409 150     150 1 5698 "ADDITIONAL_DNSKEY_SKIPPED" => "DEBUG",
410             "ALGORITHM_DEPRECATED" => "WARNING",
411             "ALGORITHM_OK" => "INFO",
412             "ALGORITHM_RESERVED" => "ERROR",
413             "ALGORITHM_UNASSIGNED" => "ERROR",
414             "COMMON_KEYTAGS" => "INFO",
415             "DNSKEY_AND_DS" => "DEBUG",
416             "DNSKEY_BUT_NOT_DS" => "WARNING",
417             "DNSKEY_NOT_SIGNED" => "ERROR",
418             "DNSKEY_SIGNATURE_NOT_OK" => "ERROR",
419             "DNSKEY_SIGNATURE_OK" => "DEBUG",
420             "DNSKEY_SIGNED" => "DEBUG",
421             "DS_BUT_NOT_DNSKEY" => "ERROR",
422             "DS_DIGTYPE_NOT_OK" => "ERROR",
423             "DS_DIGTYPE_OK" => "DEBUG",
424             "DS_DOES_NOT_MATCH_DNSKEY" => "ERROR",
425             "DS_FOUND" => "INFO",
426             "DS_MATCHES_DNSKEY" => "INFO",
427             "DS_MATCH_FOUND" => "INFO",
428             "DS_MATCH_NOT_FOUND" => "ERROR",
429             "DS_RFC4509_NOT_VALID" => "ERROR",
430             "DURATION_LONG" => "WARNING",
431             "DURATION_OK" => "DEBUG",
432             "EXTRA_PROCESSING_BROKEN" => "ERROR",
433             "EXTRA_PROCESSING_OK" => "DEBUG",
434             "HAS_NSEC" => "INFO",
435             "HAS_NSEC3" => "INFO",
436             "HAS_NSEC3_OPTOUT" => "INFO",
437             "INVALID_NAME_RCODE" => "NOTICE",
438             "ITERATIONS_OK" => "DEBUG",
439             "KEY_DETAILS" => "DEBUG",
440             "MANY_ITERATIONS" => "NOTICE",
441             "NEITHER_DNSKEY_NOR_DS" => "NOTICE",
442             "NOT_SIGNED" => "NOTICE",
443             "NO_COMMON_KEYTAGS" => "ERROR",
444             "NO_DNSKEY" => "ERROR",
445             "NO_DS" => "NOTICE",
446             "NO_KEYS_OR_NO_SIGS" => "DEBUG",
447             "NO_KEYS_OR_NO_SIGS_OR_NO_SOA" => "DEBUG",
448             "NO_NSEC3PARAM" => "DEBUG",
449             "NSEC3_SIG_VERIFY_ERROR" => "ERROR",
450             "NSEC3_COVERS" => "DEBUG",
451             "NSEC3_COVERS_NOT" => "WARNING",
452             "NSEC3_NOT_SIGNED" => "ERROR",
453             "NSEC3_SIGNED" => "DEBUG",
454             "NSEC_COVERS" => "DEBUG",
455             "NSEC_COVERS_NOT" => "WARNING",
456             "NSEC_NOT_SIGNED" => "ERROR",
457             "NSEC_SIGNED" => "DEBUG",
458             "NSEC_SIG_VERIFY_ERROR" => "ERROR",
459             "REMAINING_LONG" => "WARNING",
460             "REMAINING_SHORT" => "WARNING",
461             "RRSIG_EXPIRATION" => "INFO",
462             "RRSIG_EXPIRED" => "ERROR",
463             "SOA_NOT_SIGNED" => "ERROR",
464             "SOA_SIGNATURE_NOT_OK" => "ERROR",
465             "SOA_SIGNATURE_OK" => "DEBUG",
466             "SOA_SIGNED" => "DEBUG",
467             "TOO_MANY_ITERATIONS" => "WARNING",
468             "DELEGATION_NOT_SIGNED" => "NOTICE",
469             "DELEGATION_SIGNED" => "INFO",
470             };
471             } ## end sub policy
472              
473             sub version {
474 74     74 1 875 return "$Zonemaster::Engine::Test::DNSSEC::VERSION";
475             }
476              
477             ###
478             ### Tests
479             ###
480              
481             sub dnssec01 {
482 7     7 1 26 my ( $class, $zone ) = @_;
483 7         16 my @results;
484              
485 7         49 my %type = ( 1 => 'SHA-1', 2 => 'SHA-256', 3 => 'GOST R 34.11-94', 4 => 'SHA-384' );
486              
487 7 50       199 return if not $zone->parent;
488 7         170 my $ds_p = $zone->parent->query_one( $zone->name, 'DS', { dnssec => 1 } );
489 7 50       32 die "No response from parent nameservers" if not $ds_p;
490 7         33 my @ds = $ds_p->get_records( 'DS', 'answer' );
491              
492 7 100       26 if ( @ds == 0 ) {
493 2         53 push @results,
494             info(
495             NO_DS => {
496             zone => q{} . $zone->name,
497             from => $ds_p->answerfrom
498             }
499             );
500             }
501             else {
502 5         14 foreach my $ds ( @ds ) {
503 8 100       47 if ( $type{ $ds->digtype } ) {
504             push @results,
505             info(
506             DS_DIGTYPE_OK => {
507             keytag => $ds->keytag,
508 6         45 digtype => $type{ $ds->digtype },
509             }
510             );
511             }
512             else {
513 2         17 push @results,
514             info(
515             DS_DIGTYPE_NOT_OK => {
516             keytag => $ds->keytag,
517             digtype => $ds->digtype
518             }
519             );
520             }
521             } ## end foreach my $ds ( @ds )
522             } ## end else [ if ( @ds == 0 ) ]
523              
524 7         58 return @results;
525             } ## end sub dnssec01
526              
527             sub dnssec02 {
528 9     9 1 27 my ( $class, $zone ) = @_;
529 9         17 my @results;
530              
531 9 50       268 return if not $zone->parent;
532              
533             # 1. Retrieve the DS RR set from the parent zone. If there are no DS RR present, exit the test
534 9         257 my $ds_p = $zone->parent->query_one( $zone->name, 'DS', { dnssec => 1 } );
535 9 50       40 die "No response from parent nameservers" if not $ds_p;
536 9         44 my %ds = map { $_->keytag => $_ } $ds_p->get_records( 'DS', 'answer' );
  14         65  
537              
538 9 100       63 if ( scalar( keys %ds ) == 0 ) {
539 2         53 push @results,
540             info(
541             NO_DS => {
542             zone => q{} . $zone->name,
543             from => $ds_p->answerfrom,
544             }
545             );
546             }
547             else {
548             push @results,
549             info(
550             DS_FOUND => {
551 7         29 keytags => join( q{:}, map { $_->keytag } values %ds ),
  7         61  
552             }
553             );
554              
555             # 2. Retrieve the DNSKEY RR set from the child zone. If there are no DNSKEY RR present, then the test case fail
556 7         200 my $dnskey_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
557              
558 7         23 my %dnskey;
559 7 50       44 %dnskey = map { $_->keytag => $_ } $dnskey_p->get_records( 'DNSKEY', 'answer' ) if $dnskey_p;
  12         73  
560 7 100       36 if ( scalar( keys %dnskey ) == 0 ) {
561 1         6 push @results,
562             info( NO_DNSKEY => {} );
563 1         15 return @results;
564             }
565              
566             # Pick out keys with a tag that a DS has using a hash slice
567 6         15 my @common = grep { exists $ds{$_->keytag} } values %dnskey;
  12         55  
568 6 100       19 if ( @common ) {
569             push @results,
570             info(
571             COMMON_KEYTAGS => {
572 5         17 keytags => join( q{:}, map { $_->keytag } @common ),
  5         45  
573             }
574             );
575              
576 5         13 my $found = 0;
577 5         11 my $rfc4509_compliant = 1;
578             # 4. Match all DS RR with type digest algorithm “2” with DNSKEY RR from the child. If no DS RRs with algorithm 2 matches a
579             # DNSKEY RR from the child, this test case fails.
580 5         19 my %ds_digtype2 = map { $_->keytag => $_ } grep { $_->digtype == 2 } $ds_p->get_records( 'DS', 'answer' );
  5         28  
  10         36  
581 5 50       28 if ( scalar( keys %ds_digtype2 ) >= 1 ) {
582 5         12 @common = grep { exists $ds_digtype2{$_->keytag} } values %dnskey;
  10         54  
583              
584 5         16 foreach my $key ( @common ) {
585 5 100       184 if ( $ds_digtype2{ $key->keytag }->verify( $key ) ) {
586 3         26 push @results,
587             info(
588             DS_MATCHES_DNSKEY => {
589             keytag => $key->keytag,
590             digtype => 2,
591             }
592             );
593 3         13 $found = 1;
594             }
595             else {
596 2         113 push @results,
597             info(
598             DS_DOES_NOT_MATCH_DNSKEY => {
599             keytag => $key->keytag,
600             digtype => 2,
601             }
602             );
603             }
604             }
605              
606 5 100       20 if ( not grep { $_->tag eq q{DS_MATCHES_DNSKEY} } @results ) {
  15         383  
607 2         4 $rfc4509_compliant = 0;
608 2         8 push @results,
609             info( DS_RFC4509_NOT_VALID => {} );
610             }
611            
612             }
613              
614             # 5. Match all DS RR with type digest algorithm “1” with DNSKEY RR from the child. If no DS RRs with algorithm 1 matches a
615             # DNSKEY RR from the child, this test case fails.
616 5         243 my %ds_digtype1 = map { $_->keytag => $_ } grep { $_->digtype == 1 } $ds_p->get_records( 'DS', 'answer' );
  5         24  
  10         33  
617 5         22 @common = grep { exists $ds_digtype1{$_->keytag} } values %dnskey;
  10         48  
618 5         14 foreach my $key ( @common ) {
619 5 100       125 if ( $ds_digtype1{ $key->keytag }->verify( $key ) ) {
620 3         24 push @results,
621             info(
622             DS_MATCHES_DNSKEY => {
623             keytag => $key->keytag,
624             digtype => 1,
625             }
626             );
627 3         9 $found = 1;
628             }
629             else {
630 2         13 push @results,
631             info(
632             DS_DOES_NOT_MATCH_DNSKEY => {
633             keytag => $key->keytag,
634             digtype => 1,
635             }
636             );
637             }
638             }
639              
640 5 100       17 if ( $found ) {
641 3         12 push @results,
642             info( DS_MATCH_FOUND => {} );
643             }
644             else {
645 2         8 push @results,
646             info( DS_MATCH_NOT_FOUND => {} );
647             }
648             } ## end if ( @common )
649             else {
650             # 3. If no Key Tag from the DS RR matches any Key Tag from the DNSKEY RR, this test case fails
651 1         9 push @results,
652             info(
653             NO_COMMON_KEYTAGS => {
654             dstags => join( q{:}, keys %ds ),
655             dnskeytags => join( q{:}, keys %dnskey ),
656             }
657             );
658             }
659             } ## end else [ if ( scalar( keys %ds ...))]
660              
661 8         53 return @results;
662             } ## end sub dnssec02
663              
664             sub dnssec03 {
665 9     9 1 26 my ( $self, $zone ) = @_;
666 9         19 my @results;
667              
668 9         239 my $param_p = $zone->query_one( $zone->name, 'NSEC3PARAM', { dnssec => 1 } );
669              
670 9         34 my @nsec3params;
671 9 50       60 @nsec3params = $param_p->get_records( 'NSEC3PARAM', 'answer' ) if $param_p;
672              
673 9 100       36 if ( @nsec3params == 0 ) {
674 3 50       20 push @results,
675             info(
676             NO_NSEC3PARAM => {
677             server => ( $param_p ? $param_p->answerfrom : '<no response>' ),
678             }
679             );
680             }
681             else {
682 6         179 my $dk_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
683              
684 6         45 my @dnskey;
685 6 50       80 @dnskey = $dk_p->get_records( 'DNSKEY', 'answer' ) if $dk_p;
686              
687 6         17 my $min_len = 0;
688 6 50       25 if ( @dnskey ) {
689 6         16 $min_len = min map { $_->keysize } @dnskey;
  15         246  
690             # Do rounding as per RFC5155 section 10.3
691 6 100       29 if ($min_len > 2048) {
    100          
692 1         2 $min_len = 4096;
693             }
694             elsif ($min_len > 1024) {
695 1         3 $min_len = 2048;
696             }
697             else {
698 4         11 $min_len = 1024;
699             }
700             }
701             else {
702 0         0 push @results,
703             info( NO_DNSKEY => {} );
704             }
705              
706 6         21 foreach my $n3p ( @nsec3params ) {
707 6         34 my $iter = $n3p->iterations;
708 6 100       24 if ( $iter > 100 ) {
    50          
709 4         29 push @results,
710             info(
711             MANY_ITERATIONS => {
712             count => $iter,
713             }
714             );
715 4 100 66     113 if ( ( $min_len >= 4096 and $iter > 2500 )
      100        
      66        
      33        
      66        
      100        
      66        
716             or ( $min_len < 4096 and $min_len >= 2048 and $iter > 500 )
717             or ( $min_len < 2048 and $min_len >= 1024 and $iter > 150 ) )
718             {
719 1         5 push @results,
720             info(
721             TOO_MANY_ITERATIONS => {
722             count => $iter,
723             keylength => $min_len,
724             }
725             );
726             }
727             } ## end if ( $iter > 100 )
728             elsif ( $min_len > 0 )
729             {
730 2         10 push @results,
731             info(
732             ITERATIONS_OK => {
733             count => $iter,
734             }
735             );
736             }
737             } ## end foreach my $n3p ( @nsec3params)
738             } ## end else [ if ( @nsec3params == 0)]
739              
740 9         52 return @results;
741             } ## end sub dnssec03
742              
743             sub dnssec04 {
744 6     6 1 20 my ( $self, $zone ) = @_;
745 6         12 my @results;
746              
747 6         153 my $key_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
748 6 50       27 if ( not $key_p ) {
749 0         0 return;
750             }
751 6         25 my @keys = $key_p->get_records( 'DNSKEY', 'answer' );
752 6         22 my @key_sigs = $key_p->get_records( 'RRSIG', 'answer' );
753              
754 6         156 my $soa_p = $zone->query_one( $zone->name, 'SOA', { dnssec => 1 } );
755 6 50       25 if ( not $soa_p ) {
756 0         0 return;
757             }
758 6         25 my @soas = $soa_p->get_records( 'SOA', 'answer' );
759 6         25 my @soa_sigs = $soa_p->get_records( 'RRSIG', 'answer' );
760              
761 6         22 foreach my $sig ( @key_sigs, @soa_sigs ) {
762 17         88 my $duration = $sig->expiration - $sig->inception;
763 17         69 my $remaining = $sig->expiration - int( $key_p->timestamp );
764 17         467 push @results,
765             info(
766             RRSIG_EXPIRATION => {
767             date => scalar( gmtime($sig->expiration) ),
768             tag => $sig->keytag,
769             types => $sig->typecovered,
770             }
771             );
772 17 50       95 if ( $remaining < 0 ) { # already expired
    50          
    100          
    100          
773 0         0 push @results,
774             info(
775             RRSIG_EXPIRED => {
776             expiration => $sig->expiration,
777             tag => $sig->keytag,
778             types => $sig->typecovered,
779             }
780             );
781             }
782             elsif ( $remaining < ( $DURATION_12_HOURS_IN_SECONDS ) ) {
783 0         0 push @results,
784             info(
785             REMAINING_SHORT => {
786             duration => $remaining,
787             tag => $sig->keytag,
788             types => $sig->typecovered,
789             }
790             );
791             }
792             elsif ( $remaining > ( $DURATION_180_DAYS_IN_SECONDS ) ) {
793 3         50 push @results,
794             info(
795             REMAINING_LONG => {
796             duration => $remaining,
797             tag => $sig->keytag,
798             types => $sig->typecovered,
799             }
800             );
801             }
802             elsif ( $duration > ( $DURATION_180_DAYS_IN_SECONDS ) ) {
803 3         54 push @results,
804             info(
805             DURATION_LONG => {
806             duration => $duration,
807             tag => $sig->keytag,
808             types => $sig->typecovered,
809             }
810             );
811             }
812             else {
813 11         279 push @results,
814             info(
815             DURATION_OK => {
816             duration => $duration,
817             tag => $sig->keytag,
818             types => $sig->typecovered,
819             }
820             );
821             }
822             } ## end foreach my $sig ( @key_sigs...)
823              
824 6         94 return @results;
825             } ## end sub dnssec04
826              
827             sub dnssec05 {
828 12     12 1 40 my ( $self, $zone ) = @_;
829 12         27 my @results;
830              
831 12         311 my $key_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
832 12 50       76 if ( not $key_p ) {
833 0         0 return;
834             }
835 12         62 my @keys = $key_p->get_records( 'DNSKEY', 'answer' );
836              
837 12         50 foreach my $key ( @keys ) {
838 25         117 my $algo = $key->algorithm;
839 25 100       150 if ( $algo_properties{$algo}{status} == $ALGO_STATUS_DEPRECATED ) {
    100          
    100          
    100          
    50          
840             push @results,
841             info(
842             ALGORITHM_DEPRECATED => {
843             algorithm => $algo,
844             keytag => $key->keytag,
845             description => $algo_properties{$algo}{description},
846             }
847 4         81 );
848             }
849             elsif ( $algo_properties{$algo}{status} == $ALGO_STATUS_RESERVED ) {
850             push @results,
851             info(
852             ALGORITHM_RESERVED => {
853             algorithm => $algo,
854             keytag => $key->keytag,
855             description => $algo_properties{$algo}{description},
856             }
857 4         126 );
858             }
859             elsif ( $algo_properties{$algo}{status} == $ALGO_STATUS_UNASSIGNED ) {
860             push @results,
861             info(
862             ALGORITHM_UNASSIGNED => {
863             algorithm => $algo,
864             keytag => $key->keytag,
865             description => $algo_properties{$algo}{description},
866             }
867 4         186 );
868             }
869             elsif ( $algo_properties{$algo}{status} == $ALGO_STATUS_PRIVATE ) {
870             push @results,
871             info(
872             ALGORITHM_PRIVATE => {
873             algorithm => $algo,
874             keytag => $key->keytag,
875             description => $algo_properties{$algo}{description},
876             }
877 4         201 );
878             }
879             elsif ( $algo_properties{$algo}{status} == $ALGO_STATUS_VALID ) {
880             push @results,
881             info(
882             ALGORITHM_OK => {
883             algorithm => $algo,
884             keytag => $key->keytag,
885             description => $algo_properties{$algo}{description},
886             }
887 9         568 );
888 9 50       82 if ( $key->flags & 256 ) { # This is a Key
889 9 100       122 push @results,
    50          
890             info(
891             KEY_DETAILS => {
892             keytag => $key->keytag,
893             keysize => $key->keysize,
894             sep => $key->flags & 1 ? q{SEP bit set} : q{SEP bit *not* set},
895             rfc5011 => $key->flags & 128 ? q{RFC 5011 revocation bit set} : q{RFC 5011 revocation bit *not* set},
896             }
897             );
898             }
899             }
900             else {
901 0         0 push @results,
902             info(
903             ALGORITHM_UNKNOWN => {
904             algorithm => $algo,
905             keytag => $key->keytag,
906             }
907             );
908             }
909             } ## end foreach my $key ( @keys )
910              
911 12         93 return @results;
912             } ## end sub dnssec05
913              
914             sub dnssec06 {
915 7     7 1 28 my ( $self, $zone ) = @_;
916 7         37 my @results;
917              
918 7         183 my $key_aref = $zone->query_all( $zone->name, 'DNSKEY', { dnssec => 1 } );
919 7         23 foreach my $key_p ( @{$key_aref} ) {
  7         26  
920 22 100       62 next if not $key_p;
921              
922 21         74 my @keys = $key_p->get_records( 'DNSKEY', 'answer' );
923 21         57 my @sigs = $key_p->get_records( 'RRSIG', 'answer' );
924 21 100 66     113 if ( @sigs > 0 and @keys > 0 ) {
    50 33        
      33        
925 13         43 push @results,
926             info(
927             EXTRA_PROCESSING_OK => {
928             server => $key_p->answerfrom,
929             keys => scalar( @keys ),
930             sigs => scalar( @sigs ),
931             }
932             );
933             }
934             elsif ( $key_p->rcode eq q{NOERROR} and ( @sigs == 0 or @keys == 0 ) ) {
935 8         137 push @results,
936             info(
937             EXTRA_PROCESSING_BROKEN => {
938             server => $key_p->answerfrom,
939             keys => scalar( @keys ),
940             sigs => scalar( @sigs )
941             }
942             );
943             }
944             } ## end foreach my $key_p ( @{$key_aref...})
945              
946 7         34 return @results;
947             } ## end sub dnssec06
948              
949             sub dnssec07 {
950 11     11 1 42 my ( $self, $zone ) = @_;
951 11         24 my @results;
952              
953 11 50       349 return if not $zone->parent;
954 11         339 my $key_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
955 11 50       52 if ( not $key_p ) {
956 0         0 return;
957             }
958 11         56 my ( $dnskey ) = $key_p->get_records( 'DNSKEY', 'answer' );
959              
960 11         617 my $ds_p = $zone->parent->query_one( $zone->name, 'DS', { dnssec => 1 } );
961 11 50       51 if ( not $ds_p ) {
962 0         0 return;
963             }
964 11         52 my ( $ds ) = $ds_p->get_records( 'DS', 'answer' );
965              
966 11 100 100     212 if ( $dnskey and not $ds ) {
    100 66        
    100 66        
967 3         124 push @results,
968             info(
969             DNSKEY_BUT_NOT_DS => {
970             child => $key_p->answerfrom,
971             parent => $ds_p->answerfrom,
972             }
973             );
974             }
975             elsif ( $dnskey and $ds ) {
976 4         432 push @results,
977             info(
978             DNSKEY_AND_DS => {
979             child => $key_p->answerfrom,
980             parent => $ds_p->answerfrom,
981             }
982             );
983             }
984             elsif ( not $dnskey and $ds ) {
985 2         154 push @results,
986             info(
987             DS_BUT_NOT_DNSKEY => {
988             child => $key_p->answerfrom,
989             parent => $ds_p->answerfrom,
990             }
991             );
992             }
993             else {
994 2         12 push @results,
995             info(
996             NEITHER_DNSKEY_NOR_DS => {
997             child => $key_p->answerfrom,
998             parent => $ds_p->answerfrom,
999             }
1000             );
1001             }
1002              
1003 11         75 return @results;
1004             } ## end sub dnssec07
1005              
1006             sub dnssec08 {
1007 8     8 1 28 my ( $self, $zone ) = @_;
1008 8         18 my @results;
1009              
1010 8         228 my $key_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
1011 8 50       41 if ( not $key_p ) {
1012 0         0 return;
1013             }
1014 8         37 my @dnskeys = $key_p->get_records( 'DNSKEY', 'answer' );
1015 8         33 my @sigs = $key_p->get_records( 'RRSIG', 'answer' );
1016              
1017 8 100 100     53 if ( @dnskeys == 0 or @sigs == 0 ) {
1018 2         14 push @results,
1019             info(
1020             NO_KEYS_OR_NO_SIGS => {
1021             keys => scalar( @dnskeys ),
1022             sigs => scalar( @sigs ),
1023             }
1024             );
1025 2         14 return @results;
1026             }
1027              
1028 6         17 my $ok = 0;
1029 6         21 foreach my $sig ( @sigs ) {
1030 11         23 my $msg = q{};
1031 11         44 my $time = $key_p->timestamp;
1032 11 100       1930 if ( $sig->verify_time( \@dnskeys, \@dnskeys, $time, $msg ) ) {
1033 8         73 push @results,
1034             info(
1035             DNSKEY_SIGNATURE_OK => {
1036             signature => $sig->keytag,
1037             }
1038             );
1039 8         42 $ok = $sig->keytag;
1040             }
1041             else {
1042 3 50 33     27 if ($sig->algorithm == 12 and $msg =~ /Unknown cryptographic algorithm/) {
1043 0         0 $msg = 'no GOST support';
1044             }
1045 3         25 push @results,
1046             info(
1047             DNSKEY_SIGNATURE_NOT_OK => {
1048             signature => $sig->keytag,
1049             error => $msg,
1050             time => $time,
1051             }
1052             );
1053             }
1054             } ## end foreach my $sig ( @sigs )
1055              
1056 6 100       24 if ( $ok ) {
1057 5         23 push @results,
1058             info(
1059             DNSKEY_SIGNED => {
1060             keytag => $ok,
1061             }
1062             );
1063             }
1064             else {
1065 1         4 push @results,
1066             info( DNSKEY_NOT_SIGNED => {} );
1067             }
1068              
1069 6         59 return @results;
1070             } ## end sub dnssec08
1071              
1072             sub dnssec09 {
1073 6     6 1 21 my ( $self, $zone ) = @_;
1074 6         14 my @results;
1075              
1076 6         172 my $key_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
1077 6 50       29 if ( not $key_p ) {
1078 0         0 return;
1079             }
1080 6         31 my @dnskeys = $key_p->get_records( 'DNSKEY', 'answer' );
1081              
1082 6         167 my $soa_p = $zone->query_one( $zone->name, 'SOA', { dnssec => 1 } );
1083 6 50       27 if ( not $soa_p ) {
1084 0         0 return;
1085             }
1086 6         32 my @soa = $soa_p->get_records( 'SOA', 'answer' );
1087 6         28 my @sigs = $soa_p->get_records( 'RRSIG', 'answer' );
1088              
1089 6 50 66     57 if ( @dnskeys == 0 or @sigs == 0 or @soa == 0 ) {
      66        
1090 1         9 push @results,
1091             info(
1092             NO_KEYS_OR_NO_SIGS_OR_NO_SOA => {
1093             keys => scalar( @dnskeys ),
1094             sigs => scalar( @sigs ),
1095             soas => scalar( @soa ),
1096             }
1097             );
1098 1         9 return @results;
1099             }
1100              
1101 5         14 my $ok = 0;
1102 5         14 foreach my $sig ( @sigs ) {
1103 5         13 my $msg = q{};
1104 5         21 my $time = $soa_p->timestamp;
1105 5 100       712 if ( $sig->verify_time( \@soa, \@dnskeys, $time, $msg ) ) {
1106 4         37 push @results,
1107             info(
1108             SOA_SIGNATURE_OK => {
1109             signature => $sig->keytag,
1110             }
1111             );
1112 4         23 $ok = $sig->keytag;
1113             }
1114             else {
1115 1 50 33     10 if ($sig->algorithm == 12 and $msg =~ /Unknown cryptographic algorithm/) {
1116 0         0 $msg = 'no GOST support';
1117             }
1118 1         11 push @results,
1119             info(
1120             SOA_SIGNATURE_NOT_OK => {
1121             signature => $sig->keytag,
1122             error => $msg,
1123             }
1124             );
1125             }
1126             } ## end foreach my $sig ( @sigs )
1127              
1128 5 100       23 if ( $ok ) {
1129 4         21 push @results,
1130             info(
1131             SOA_SIGNED => {
1132             keytag => $ok,
1133             }
1134             );
1135             }
1136             else {
1137 1         4 push @results,
1138             info( SOA_NOT_SIGNED => {} );
1139             }
1140              
1141 5         48 return @results;
1142             } ## end sub dnssec09
1143              
1144             sub dnssec10 {
1145 9     9 1 30 my ( $self, $zone ) = @_;
1146 9         20 my @results;
1147              
1148 9         251 my $key_p = $zone->query_one( $zone->name, 'DNSKEY', { dnssec => 1 } );
1149 9 50       45 if ( not $key_p ) {
1150 0         0 return;
1151             }
1152 9         43 my @dnskeys = $key_p->get_records( 'DNSKEY', 'answer' );
1153              
1154 9         251 my $name = $zone->name->prepend( 'xx--example' );
1155 9         53 my $test_p = $zone->query_one( $name, 'A', { dnssec => 1 } );
1156 9 50       42 if ( not $test_p ) {
1157 0         0 return;
1158             }
1159              
1160 9 50 33     45 if ( $test_p->rcode ne 'NXDOMAIN' and $test_p->rcode ne 'NOERROR' ) {
1161 0         0 push @results,
1162             info(
1163             INVALID_NAME_RCODE => {
1164             name => $name,
1165             rcode => $test_p->rcode,
1166             }
1167             );
1168 0         0 return @results;
1169             }
1170              
1171 9         172 my @nsec = $test_p->get_records( 'NSEC', 'authority' );
1172 9 100       37 if ( @nsec ) {
1173 3         12 push @results, info( HAS_NSEC => {} );
1174 3         8 my $covered = 0;
1175 3         10 foreach my $nsec ( @nsec ) {
1176              
1177 6 100       23 if ( $nsec->covers( $name ) ) {
1178 3         6 $covered = 1;
1179              
1180 3         20 my @sigs = grep { $_->typecovered eq 'NSEC' } $test_p->get_records_for_name( 'RRSIG', $nsec->name );
  3         31  
1181 3         15 my $ok = 0;
1182 3         8 foreach my $sig ( @sigs ) {
1183 3         7 my $msg = q{};
1184 3 50       14 if (@dnskeys == 0) {
    50          
1185 0         0 push @results, info( NSEC_SIG_VERIFY_ERROR => { error => 'DNSKEY missing', sig => $sig->keytag } );
1186             }
1187             elsif (
1188             $sig->verify_time(
1189 6         18 [ grep { name( $_->name ) eq name( $sig->name ) } @nsec ],
1190             \@dnskeys, $test_p->timestamp, $msg
1191             )
1192             )
1193             {
1194 3         413 $ok = 1;
1195             }
1196             else {
1197 0 0 0     0 if ($sig->algorithm == 12 and $msg =~ /Unknown cryptographic algorithm/) {
1198 0         0 $msg = 'no GOST support';
1199             }
1200 0         0 push @results,
1201             info(
1202             NSEC_SIG_VERIFY_ERROR => {
1203             error => $msg,
1204             sig => $sig->keytag,
1205             }
1206             );
1207             }
1208              
1209 3 50       16 if ( $ok ) {
1210 3         15 push @results,
1211             info( NSEC_SIGNED => {} );
1212             }
1213             else {
1214 0         0 push @results,
1215             info( NSEC_NOT_SIGNED => {} );
1216             }
1217             } ## end foreach my $sig ( @sigs )
1218             } ## end if ( $nsec->covers( $name...))
1219             } ## end foreach my $nsec ( @nsec )
1220 3 50       16 if ( $covered ) {
1221 3         22 push @results,
1222             info(
1223             NSEC_COVERS => {
1224             name => $name,
1225             }
1226             );
1227             }
1228             else {
1229 0         0 push @results,
1230             info(
1231             NSEC_COVERS_NOT => {
1232             name => $name,
1233             }
1234             );
1235             }
1236             } ## end if ( @nsec )
1237              
1238 9         37 my @nsec3 = $test_p->get_records( 'NSEC3', 'authority' );
1239 9 100       35 if ( @nsec3 ) {
1240 6         16 my $covered = 0;
1241 6         14 my $opt_out = 0;
1242 6         30 push @results, info( HAS_NSEC3 => {} );
1243 6         23 foreach my $nsec3 ( @nsec3 ) {
1244 17 100       83 if ( $nsec3->optout ) {
1245 14         25 $opt_out = 1;
1246             }
1247 17 100       62 if ( $nsec3->covers( $name ) ) {
1248 6         15 $covered = 1;
1249              
1250 6         36 my @sigs = grep { $_->typecovered eq 'NSEC3' } $test_p->get_records_for_name( 'RRSIG', $nsec3->name );
  6         68  
1251 6         45 my $ok = 0;
1252 6         17 foreach my $sig ( @sigs ) {
1253 6         14 my $msg = q{};
1254 6 100       17 if (
1255             $sig->verify_time(
1256 17         49 [ grep { name( $_->name ) eq name( $sig->name ) } @nsec3 ],
1257             \@dnskeys, $test_p->timestamp, $msg
1258             )
1259             )
1260             {
1261 5         907 $ok = 1;
1262             }
1263             else {
1264 1 50 33     205 if ($sig->algorithm == 12 and $msg =~ /Unknown cryptographic algorithm/) {
1265 0         0 $msg = 'no GOST support';
1266             }
1267 1         9 push @results,
1268             info(
1269             NSEC3_SIG_VERIFY_ERROR => {
1270             sig => $sig->keytag,
1271             error => $msg,
1272             }
1273             );
1274             }
1275 6 100       24 if ( $ok ) {
1276 5         23 push @results,
1277             info( NSEC3_SIGNED => {} );
1278             }
1279             else {
1280 1         5 push @results,
1281             info( NSE3C_NOT_SIGNED => {} );
1282             }
1283             } ## end foreach my $sig ( @sigs )
1284             } ## end if ( $nsec3->covers( $name...))
1285             } ## end foreach my $nsec3 ( @nsec3 )
1286 6 50       26 if ( $covered ) {
1287 6         31 push @results,
1288             info(
1289             NSEC3_COVERS => {
1290             name => $name,
1291             }
1292             );
1293             }
1294             else {
1295 0         0 push @results,
1296             info(
1297             NSEC3_COVERS_NOT => {
1298             name => $name,
1299             }
1300             );
1301             }
1302 6 100       26 if ( $opt_out ) {
1303 5         20 push @results, info( HAS_NSEC3_OPTOUT => {} );
1304             }
1305             } ## end if ( @nsec3 )
1306              
1307 9         112 return @results;
1308             } ## end sub dnssec10
1309              
1310             ### The error reporting in dnssec11 is deliberately simple, since the point of
1311             ### the test case is to give a pass/fail test for the delegation step from the
1312             ### parent as a whole.
1313             sub dnssec11 {
1314 9     9 1 32 my ( $class, $zone ) = @_;
1315 9         22 my @results;
1316              
1317 9         254 my $ds_p = $zone->parent->query_auth( $zone->name->string, 'DS' );
1318 9 50       44 if ( not $ds_p ) {
1319 0         0 return info( DELEGATION_NOT_SIGNED => { keytag => 'none', reason => 'no_ds_packet' } );
1320             }
1321              
1322 9         252 my $dnskey_p = $zone->query_auth( $zone->name->string, 'DNSKEY', { dnssec => 1 } );
1323 9 50       42 if ( not $dnskey_p ) {
1324 0         0 return info( DELEGATION_NOT_SIGNED => { keytag => 'none', reason => 'no_dnskey_packet' } );
1325             }
1326              
1327 9         254 my %ds = map { $_->keytag => $_ } $ds_p->get_records_for_name( 'DS', $zone->name->string );
  14         82  
1328 9         257 my %dnskey = map { $_->keytag => $_ } $dnskey_p->get_records_for_name( 'DNSKEY', $zone->name->string );
  13         90  
1329 9         249 my %rrsig = map { $_->keytag => $_ } $dnskey_p->get_records_for_name( 'RRSIG', $zone->name->string );
  11         54  
1330              
1331 9         25 my $pass = 0;
1332 9         17 my @fail;
1333 9 100       37 if ( scalar( keys %ds ) > 0 ) {
1334 7         27 foreach my $tag ( keys %ds ) {
1335 7         19 my $ds = $ds{$tag};
1336 7         17 my $key = $dnskey{$tag};
1337 7         14 my $sig = $rrsig{$tag};
1338              
1339 7 100       30 if ( $key ) {
1340 5 50       267 if ( $ds->verify( $key ) ) {
1341 5 50       132 if ( $sig ) {
1342 5         152 my $msg = '';
1343 5         43 my $ok =
1344             $sig->verify_time( [ values %dnskey ], [ values %dnskey ], $dnskey_p->timestamp, $msg );
1345 5 50       1636 if ( $ok ) {
1346 5         24 $pass = $tag;
1347             }
1348             else {
1349 0 0 0     0 if ($sig->algorithm == 12 and $msg =~ /Unknown cryptographic algorithm/) {
1350 0         0 $msg = 'no GOST support';
1351             }
1352 0         0 push @fail, "signature: $msg" ;
1353             }
1354             }
1355             else {
1356 0         0 push @fail, 'no_signature';
1357             }
1358             }
1359             else {
1360 0         0 push @fail, 'dnskey_no_match';
1361             }
1362             } ## end if ( $key )
1363             else {
1364 2         7 push @fail, 'no_dnskey';
1365             }
1366             } ## end foreach my $tag ( keys %ds )
1367             } ## end if ( scalar( keys %ds ...))
1368             else {
1369 2         7 push @fail, 'no_ds';
1370             }
1371              
1372 9 100       29 if ($pass) {
1373 5         48 push @results, info( DELEGATION_SIGNED => { keytag => $pass } )
1374             } else {
1375 4         33 push @results, info( DELEGATION_NOT_SIGNED => { keytag => 'info', reason => join(';', @fail) } )
1376             }
1377              
1378 9         96 return @results;
1379             } ## end sub dnssec11
1380              
1381             1;
1382              
1383             =head1 NAME
1384              
1385             Zonemaster::Engine::Test::DNSSEC - dnssec module showing the expected structure of Zonemaster test modules
1386              
1387             =head1 SYNOPSIS
1388              
1389             my @results = Zonemaster::Engine::Test::DNSSEC->all($zone);
1390              
1391             =head1 METHODS
1392              
1393             =over
1394              
1395             =item all($zone)
1396              
1397             Runs the default set of tests and returns a list of log entries made by the tests.
1398              
1399             =item metadata()
1400              
1401             Returns a reference to a hash, the keys of which are the names of all test methods in the module, and the corresponding values are references to
1402             lists with all the tags that the method can use in log entries.
1403              
1404             =item translation()
1405              
1406             Returns a reference to a nested hash, where the outermost keys are language
1407             codes, the keys below that are message tags and their values are translation
1408             strings.
1409              
1410             =item policy()
1411              
1412             Returns a reference to a hash with the default policy for the module. The keys
1413             are message tags, and the corresponding values are their default log levels.
1414              
1415             =item version()
1416              
1417             Returns a version string for the module.
1418              
1419             =back
1420              
1421             =head1 TESTS
1422              
1423             =over
1424              
1425             =item dnssec01($zone)
1426              
1427             Verifies that all DS records have digest types registered with IANA.
1428              
1429             =item dnssec02($zone)
1430              
1431             Verifies that all DS records have a matching DNSKEY.
1432              
1433             =item dnssec03($zone)
1434              
1435             Check iteration counts for NSEC3.
1436              
1437             =item dnssec04($zone)
1438              
1439             Checks the durations of the signatures for the DNSKEY and SOA RRsets.
1440              
1441             =item dnssec05($zone)
1442              
1443             Check DNSKEY algorithms.
1444              
1445             =item dnssec06($zone)
1446              
1447             Check for DNSSEC extra processing at child nameservers.
1448              
1449             =item dnssec07($zone)
1450              
1451             Check that both DS and DNSKEY are present.
1452              
1453             =item dnssec08($zone)
1454              
1455             Check that the DNSKEY RRset is signed.
1456              
1457             =item dnssec09($zone)
1458              
1459             Check that the SOA RRset is signed.
1460              
1461             =item dnssec10($zone)
1462              
1463             Check for the presence of either NSEC or NSEC3, with proper coverage and signatures.
1464              
1465             =item dnssec11($zone)
1466              
1467             Check that the delegation step from parent is properly signed.
1468              
1469             =back
1470              
1471             =cut