implement DNSSEC and TLS configuration checks using external tools

parent b237dfa9
......@@ -29,6 +29,11 @@ responsees).
query the server directly.
- `--help` - show this help.
# DEPENDENCIES
This script needs `ssllabs-scan` to do TLS configuration checks, and `dnsviz`
to do DNSSEC validation. On MacOS, these can be installed using `homebrew`.
# COPYRIGHT
Copyright 2018 CentralNic Ltd. This program is free software, you can
......
......@@ -128,49 +128,33 @@ sub check_gtld_conformance {
# str is a a test score, such as "A+", "C-", "F", etc
#
my $str = uc($json->{$host});
# generate an integer based on the first character (its ASCII value, minus 64, x 3)
my $grade = 3 * (ord(substr($str, 0, 1)) - 64);
#
# increment if it's a "minus" grade
#
$grade++ if ('-' eq substr($str, 1, 1));
#
# decrement if it's a "plus" grade
#
$grade-- if ('+' eq substr($str, 1, 1));
my $msg = sprintf('TLS configuration grade is "%s"', $str);
# 4 is "A-":
if ($grade > 4) {
fail($msg);
if (length($str) < 1) {
fail('no valid grade returned by sslabs-scan');
} else {
pass($msg);
# generate an integer based on the first character (its ASCII value, minus 64, x 3)
my $grade = 3 * (ord(substr($str, 0, 1)) - 64);
}
}
#
# increment if it's a "minus" grade
#
$grade++ if ('-' eq substr($str, 1, 1));
# Implementation Guide - RDAP Protocol - 1.4: An RDAP client SHOULD be able to successfully validate the TLS certificate used for the RDAP service with a ​TLSA​ record from the DNS (​RFC6698​ and RFC7671​) published by the RDAP service provider. The certificate(s) for the RDAP service associated by DNS-Based Authentication of Named Entities (DANE) SHOULD satisfy the requirements of section 1.5.
my $answer = $resolver->query(sprintf('_443._tcp.%s.', $url->host), 'TLSA');
if (!$answer) {
fail(sprintf('No answer to TLSA query for %s', uc($url->host)));
#
# decrement if it's a "plus" grade
#
$grade-- if ('+' eq substr($str, 1, 1));
} elsif ('NOERROR' ne $answer->header->rcode) {
fail(sprintf('%s received in answer to TLSA query for %s', $answer->header->rcode, uc($url->host)));
my $msg = sprintf('TLS configuration grade is "%s"', $str);
} else {
my @rrs = grep { 'TLSA' eq $_->type } $answer->answer;
if (scalar(@rrs) < 1) {
fail(sprintf('No records found in answer TLSA query for %s', uc($url->host)));
# 4 is "A-":
if ($grade > 4) {
fail($msg);
} else {
pass('%d TLSA record(s) found for %s', scalar(@rrs), uc($url->host));
} else {
pass($msg);
note('TLSA record validation is not currently available');
}
}
}
......@@ -232,44 +216,66 @@ sub check_gtld_conformance {
}
# Implementation Guide - RDAP Protocol - 1.9.1: The resource records for the RDAP service MUST be signed with DNSSEC, and the DNSSEC chain of trust from the root trust anchor to the name of the RDAP server MUST be valid.
# Implementation Guide - RDAP Protocol - 1.4: An RDAP client SHOULD be able to successfully validate the TLS certificate used for the RDAP service with a ​TLSA​ record from the DNS (​RFC6698​ and RFC7671​) published by the RDAP service provider. The certificate(s) for the RDAP service associated by DNS-Based Authentication of Named Entities (DANE) SHOULD satisfy the requirements of section 1.5.
# Use dnsviz to test dnssec?
my $records = [
{ 'name' => sprintf('_443._tcp.%s', $host), 'type' => 'TLSA' },
{ 'name' => $host, 'type' => 'A' },
{ 'name' => $host, 'type' => 'AAAA' },
];
my $rrsig_answer = $resolver->query($host, 'RRSIG');
my @sigs = scalar(grep { 'RRSIG' eq $_->type } $rrsig_answer->answer);
if (scalar(@sigs) > 0) {
pass(sprintf('%s appears to be signed using DNSSEC', uc($url->host)));
note('performing DNSSEC and TLSA record validation...');
my $ok = 1;
foreach my $record (@{$records}) {
my $ppid = open3(undef, \*POUT, \*PERR, 'dnsviz', 'probe', '-R', $record->{'type'}, $record->{'name'});
my $gpid = open3(\*IN, \*GOUT, \*GERR, 'dnsviz', 'grok', '-c');
} else {
fail(sprintf('%s does not appear to be signed using DNSSEC', uc($url->host)));
}
print IN <POUT>;
close(IN);
my @answers = ($v4answer, $v6answer); # $rrsig_answer is not included as responses to queries for DNSSEC records never have the AD flag set
my $ad = 0;
foreach my $answer (@answers) {
if ($answer->header->ad) {
$ad++;
pass(sprintf('AD bit set on answer to query for %s/%s', ($answer->question)[0]->name, ($answer->question)[0]->type));
waitpid($ppid, 0);
if (abs($? >> 8) > 0) {
fail(
'Unable to perform DNSSEC check',
[ split(/\n/, <PERR>) ]
);
} else {
fail(sprintf('AD bit NOT set on answer to query for %s/%s', ($answer->question)[0]->name, ($answer->question)[0]->type));
waitpid($gpid, 0);
if (abs($? >> 8) > 0) {
fail(
'Unable to perform DNSSEC check',
[ split(/\n/, <GOUT>) ]
);
} else {
my $result = from_json(<GOUT>);
if ('NOERROR' ne $result->{$record->{'name'}}->{'status'}) {
warning(sprintf('result for %s/%s is %s', $record->{'name'}, $record->{'type'}, $result->{$record->{'name'}}->{'status'}));
$ok = undef;
last;
} else {
note(sprintf('result for %s/%s is %s', $record->{'name'}, $record->{'type'}, $result->{$record->{'name'}}->{'status'}));
}
}
}
}
if ($ad == scalar(@answers)) {
pass(sprintf('AD bit set on all answers needed to resolve %s', $host));
if ($ok) {
pass(sprintf('DNSSEC validation for %s passed', $host));
} else {
fail(sprintf('AD bit NOT set on all answers needed to resolve %s', $host));
fail(sprintf('DNSSEC validation for %s failed', $host));
}
note('Full DNSSEC chain-of-trust validation is not currently available');
# (out of scope)
# Implementation Guide - RDAP Protocol - 1.11.2: When the RDAP service base URL needs to be changed, the previous URL and the new one MUST remain in operation until: 1) the IANA's Bootstrap Service registry for Domain Name Space is updated, and 2) the date and time in the Expires HTTP header of a HTTP/GET request performed on the IANA's Bootstrap registry for Domain Name Space (after the new URL has been published) has elapsed.
# TODO:
# Implementation Guide - RDAP Protocol - 1.10: RDAP servers MUST only use fully qualified domain names in RDAP responses.
# Implementation Guide - RDAP Protocol - 1.11.2: When the RDAP service base URL needs to be changed, the previous URL and the new one MUST remain in operation until: 1) the IANA's Bootstrap Service registry for Domain Name Space is updated, and 2) the date and time in the Expires HTTP header of a HTTP/GET request performed on the IANA's Bootstrap registry for Domain Name Space (after the new URL has been published) has elapsed.
# (to test these requirements, we need to know an IDN name that we can query)
# Implementation Guide - Responses to RDAP queries - 2.1: The RDAP server MUST support Internationalized Domain Name (IDN) RDAP lookup queries using A-label and MAY support U-label format [​RFC5890​] for domain names and name server objects.
# Implementation Guide - Responses to RDAP queries - 2.2: An RDAP server that receives a query string with a mixture of A-labels and U-labels SHOULD reject the query.
......@@ -292,6 +298,7 @@ sub check_gtld_conformance {
# Implementation Guide - Responses to RDAP queries - 2.4.1: The terms of service of the RDAP service MUST be specified in the notices​ object in the initial JSON object of the response.
# Implementation Guide - Responses to RDAP queries - 2.4.2: The ​notices​ object MUST contain a l​inks​ object [​RFC7483​] containing an URL of the RDAP service provider.
# Implementation Guide - Responses to RDAP queries - 2.4.3: The RDAP service provider MUST provide a web page with the terms of service of the RDAP service at the URL contained in the links object (2.4.2) which MAY be the same as the terms or service in the notices object (2.4.1) or MAY expand upon them.
my @notices = $response->notices;
if (scalar(@notices) < 1) {
fail('At least one notice must be present, containing the Terms of Service');
......@@ -309,10 +316,11 @@ sub check_gtld_conformance {
}
# TODO:
# Implementation Guide - Responses to RDAP queries - 2.4.3: The RDAP service provider MUST provide a web page with the terms of service of the RDAP service at the URL contained in the links object (2.4.2) which MAY be the same as the terms or service in the notices object (2.4.1) or MAY expand upon them.
# Implementation Guide - Responses to RDAP queries - 2.5: RDAP Help queries [​RFC7482​] MUST be answered and include a ​links​ member with a URL to a document that provides usage information, policy and other explanatory material.
# (to test these requirements, we need to know of an object that has definitely been truncated)
# Implementation Guide - Responses to RDAP queries - 2.6: Truncated RDAP responses MUST contain a ​notices​ member describing the reason for the truncation. The ​notices​ object type MUST be of the form “Response truncated due to {authorization|load|unexplainable reason}”.
# Implementation Guide - Responses to RDAP queries - 2.7: Truncated RDAP objects MUST contain a ​remarks​ member describing the reason for the truncation. The ​remarks​ object type MUST be of the form “Result set truncated due to {authorization|load|unexplainable reason}”.
# (not testable)
# Implementation Guide - Responses to RDAP queries - 2.8: In the case where the RDAP service provider is querying its database directly, and therefore, using real-time data, the ​eventAction​ type ​last update of RDAP database​ MUST show the timestamp of the response to the query.
}
......@@ -333,14 +341,12 @@ sub check_domain_conformance {
}
#
# not explicitly stated but we should check that the name matches the queried name
#
# Implementation Guide - RDAP Protocol - 1.10: RDAP servers MUST only use fully qualified domain names in RDAP responses.
if (lc($domain->name) == lc($response->name->name)) {
pass('domain name in response matches (case-insensitive) name in lookup');
pass('domain name in response matches (case-insensitive) name in lookup and is fully-qualified');
} else {
fail(sprintf("domain name in response ('%s') does not match (case-insensitive) name in lookup ('%s')", lc($domain->name), lc($domain->name->name)));
fail(sprintf("domain name in response ('%s') does not match (case-insensitive) name in lookup ('%s') or is not fully qualified", lc($domain->name), lc($domain->name->name)));
}
......@@ -447,6 +453,9 @@ sub check_help_conformance {
my $response = shift;
note('Help validation is not currently available');
# TODO
# Implementation Guide - Responses to RDAP queries - 2.5: RDAP Help queries [​RFC7482​] MUST be answered and include a ​links​ member with a URL to a document that provides usage information, policy and other explanatory material.
}
sub note {
......@@ -532,6 +541,11 @@ query the server directly.
=back
=head1 DEPENDENCIES
This script needs C<ssllabs-scan> to do TLS configuration checks, and C<dnsviz>
to do DNSSEC validation. On MacOS, these can be installed using C<homebrew>.
=head1 COPYRIGHT
Copyright 2018 CentralNic Ltd. This program is free software, you can
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment