diff options
Diffstat (limited to 'src/resolve/resolved-dns-dnssec.c')
-rw-r--r-- | src/resolve/resolved-dns-dnssec.c | 195 |
1 files changed, 147 insertions, 48 deletions
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 51fe710795..43fcbe1460 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -39,11 +39,13 @@ * - multi-label zone compatibility * - cname/dname compatibility * - nxdomain on qname - * - workable hack for the .corp, .home, .box case * - bus calls to override DNSEC setting per interface * - log all DNSSEC downgrades * - enable by default * + * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies) + * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone) + * - RFC 6840, Section 4.3 (check for CNAME on NSEC too) * */ #define VERIFY_RRS_MAX 256 @@ -52,8 +54,8 @@ /* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ #define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) -/* Maximum number of NSEC3 iterations we'll do. */ -#define NSEC3_ITERATIONS_MAX 2048 +/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ +#define NSEC3_ITERATIONS_MAX 2500 /* * The DNSSEC Chain of trust: @@ -510,6 +512,7 @@ int dnssec_verify_rrset( DnsResourceRecord **list, *rr; gcry_md_hd_t md = NULL; int r, md_algorithm; + bool wildcard = false; size_t k, n = 0; assert(key); @@ -540,7 +543,7 @@ int dnssec_verify_rrset( } /* Collect all relevant RRs in a single array, so that we can look at the RRset */ - list = newa(DnsResourceRecord *, a->n_rrs); + list = newa(DnsResourceRecord *, dns_answer_size(a)); DNS_ANSWER_FOREACH(rr, a) { r = dns_resource_key_equal(key, rr->key); @@ -597,8 +600,10 @@ int dnssec_verify_rrset( r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix); if (r < 0) goto finish; - if (r > 0) /* This is a wildcard! */ + if (r > 0) /* This is a wildcard! */ { gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); + wildcard = true; + } r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true); if (r < 0) @@ -649,7 +654,12 @@ int dnssec_verify_rrset( if (r < 0) goto finish; - *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID; + if (!r) + *result = DNSSEC_INVALID; + else if (wildcard) + *result = DNSSEC_VALIDATED_WILDCARD; + else + *result = DNSSEC_VALIDATED; r = 0; finish: @@ -746,7 +756,8 @@ int dnssec_verify_rrset_search( const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, - DnssecResult *result) { + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; DnsResourceRecord *rrsig; @@ -806,13 +817,17 @@ int dnssec_verify_rrset_search( switch (one_result) { case DNSSEC_VALIDATED: + case DNSSEC_VALIDATED_WILDCARD: /* Yay, the RR has been validated, * return immediately, but fix up the expiry */ r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime); if (r < 0) return r; - *result = DNSSEC_VALIDATED; + if (ret_rrsig) + *ret_rrsig = rrsig; + + *result = one_result; return 0; case DNSSEC_INVALID: @@ -857,6 +872,9 @@ int dnssec_verify_rrset_search( else *result = DNSSEC_NO_SIGNATURE; + if (ret_rrsig) + *ret_rrsig = NULL; + return 0; } @@ -888,8 +906,6 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { return -ENOBUFS; for (;;) { - size_t i; - r = dns_label_unescape(&n, buffer, buffer_max); if (r < 0) return r; @@ -916,11 +932,7 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { if (memchr(buffer, '.', r)) return -EINVAL; - for (i = 0; i < (size_t) r; i ++) { - if (buffer[i] >= 'A' && buffer[i] <= 'Z') - buffer[i] = buffer[i] - 'A' + 'a'; - } - + ascii_strlower_n(buffer, (size_t) r); buffer[r] = '.'; buffer += r + 1; @@ -1158,7 +1170,7 @@ finish: return r; } -static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) { +static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { const char *a, *b; int r; @@ -1214,8 +1226,28 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc return dns_name_equal(a, b); } -static int nsec3_hashed_domain(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { - _cleanup_free_ char *l = NULL, *hashed_domain = NULL; +static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL; + char *j; + + assert(hashed); + assert(hashed_size > 0); + assert(zone); + assert(ret); + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + j = strjoin(l, ".", zone, NULL); + if (!j) + return -ENOMEM; + + *ret = j; + return (int) hashed_size; +} + +static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; int hashed_size; @@ -1228,18 +1260,7 @@ static int nsec3_hashed_domain(DnsResourceRecord *nsec3, const char *domain, con if (hashed_size < 0) return hashed_size; - l = base32hexmem(hashed, hashed_size, false); - if (!l) - return -ENOMEM; - - hashed_domain = strjoin(l, ".", zone, NULL); - if (!hashed_domain) - return -ENOMEM; - - *ret = hashed_domain; - hashed_domain = NULL; - - return hashed_size; + return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); } /* See RFC 5155, Section 8 @@ -1255,7 +1276,7 @@ static int nsec3_hashed_domain(DnsResourceRecord *nsec3, const char *domain, con static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL; const char *zone, *p, *pp = NULL; - DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL; + DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; DnsAnswerFlags flags; int hashed_size, r; bool a, no_closer = false, no_wildcard = false, optout = false; @@ -1270,14 +1291,14 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR * parameters. */ zone = DNS_RESOURCE_KEY_NAME(key); for (;;) { - DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) { - r = nsec3_is_good(suffix_rr, flags, NULL); + DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { + r = nsec3_is_good(zone_rr, NULL); if (r < 0) return r; if (r == 0) continue; - r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, zone); + r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone); if (r < 0) return r; if (r > 0) @@ -1301,7 +1322,7 @@ found_zone: for (;;) { _cleanup_free_ char *hashed_domain = NULL; - hashed_size = nsec3_hashed_domain(suffix_rr, p, zone, &hashed_domain); + hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); if (hashed_size == -EOPNOTSUPP) { *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; return 0; @@ -1311,7 +1332,7 @@ found_zone: DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { - r = nsec3_is_good(enclosure_rr, flags, suffix_rr); + r = nsec3_is_good(enclosure_rr, zone_rr); if (r < 0) return r; if (r == 0) @@ -1384,34 +1405,30 @@ found_closest_encloser: if (!wildcard) return -ENOMEM; - r = nsec3_hashed_domain(enclosure_rr, wildcard, zone, &wildcard_domain); + r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); if (r < 0) return r; if (r != hashed_size) return -EBADMSG; - r = nsec3_hashed_domain(enclosure_rr, pp, zone, &next_closer_domain); + r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); if (r < 0) return r; if (r != hashed_size) return -EBADMSG; DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL; + _cleanup_free_ char *next_hashed_domain = NULL; - r = nsec3_is_good(rr, flags, suffix_rr); + r = nsec3_is_good(rr, zone_rr); if (r < 0) return r; if (r == 0) continue; - label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); - if (!label) - return -ENOMEM; - - next_hashed_domain = strjoin(label, ".", zone, NULL); - if (!next_hashed_domain) - return -ENOMEM; + r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); + if (r < 0) + return r; r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain); if (r < 0) @@ -1500,7 +1517,7 @@ found_closest_encloser: return 0; } -int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { DnsResourceRecord *rr; bool have_nsec3 = false; DnsAnswerFlags flags; @@ -1569,8 +1586,90 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r return 0; } +int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int r; + + assert(name); + assert(zone); + + /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified + * 'zone'. The 'zone' must be a suffix of the 'name'. */ + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + bool found = false; + + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone); + if (r < 0) + return r; + if (r == 0) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name); + if (r < 0) + return r; + + found = r > 0; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; + + r = nsec3_is_good(rr, NULL); + if (r < 0) + return r; + if (r == 0) + break; + + /* Format the domain we are testing with the NSEC3 RR's hash function */ + r = nsec3_hashed_domain_make( + rr, + name, + zone, + &hashed_domain); + if (r < 0) + return r; + if ((size_t) r != rr->nsec3.next_hashed_name_size) + break; + + /* Format the NSEC3's next hashed name as proper domain name */ + r = nsec3_hashed_domain_format( + rr->nsec3.next_hashed_name, + rr->nsec3.next_hashed_name_size, + zone, + &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain); + if (r < 0) + return r; + + found = r > 0; + break; + } + + default: + continue; + } + + if (found) { + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + return 1; + } + } + + return 0; +} + static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_VALIDATED] = "validated", + [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", [DNSSEC_INVALID] = "invalid", [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", |