From fbc5205702c7f6f431d9f1043c553d7fb62ddfdb Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 23 Dec 2014 15:46:08 +0000 Subject: Fix problems validating NSEC3 and wildcards. --- src/dnssec.c | 253 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 128 insertions(+), 125 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 3208ac7..9350d3e 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -615,6 +615,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int Return code: STAT_SECURE if it validates. STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. + (In this case *wildcard_out points to the "body" of the wildcard within name.) STAT_NO_SIG no RRsigs found. STAT_INSECURE RRset empty. STAT_BOGUS signature is wrong, bad packet. @@ -625,8 +626,8 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int name is unchanged on exit. keyname is used as workspace and trashed. */ -static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, - int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) +static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, + char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) { static unsigned char **rrset = NULL, **sigs = NULL; static int rrset_sz = 0, sig_sz = 0; @@ -798,8 +799,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in { int k; for (k = name_labels - labels; k != 0; k--) - while (*name_start != '.' && *name_start != 0) - name_start++; + { + while (*name_start != '.' && *name_start != 0) + name_start++; + if (k != 1) + name_start++; + } + + if (wildcard_out) + *wildcard_out = name_start+1; + name_start--; *name_start = '*'; } @@ -974,7 +983,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch if (recp1->addr.ds.keylen == (int)hash->digest_size && (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && - validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag) == STAT_SECURE) + validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) { valid = 1; break; @@ -1443,11 +1452,88 @@ static int base32_decode(char *in, unsigned char *out) return p - out; } +static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, + char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count) +{ + int i, hash_len, salt_len, base32_len, rdlen; + unsigned char *p, *psave; + + for (i = 0; i < nsec_count; i++) + if ((p = nsecs[i])) + { + if (!extract_name(header, plen, &p, workspace1, 1, 0) || + !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) + return 0; + + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); + psave = p; + p += 4; /* algo, flags, iterations */ + salt_len = *p++; /* salt_len */ + p += salt_len; /* salt */ + hash_len = *p++; /* p now points to next hashed name */ + + if (!CHECK_LEN(header, p, plen, hash_len)) + return 0; + + if (digest_len == base32_len && hash_len == base32_len) + { + int rc = memcmp(workspace2, digest, digest_len); + + if (rc == 0) + { + /* We found an NSEC3 whose hashed name exactly matches the query, so + we just need to check the type map. p points to the RR data for the record. */ + + int offset = (type & 0xff) >> 3; + int mask = 0x80 >> (type & 0x07); + + p += hash_len; /* skip next-domain hash */ + rdlen -= p - psave; + + if (!CHECK_LEN(header, p, plen, rdlen)) + return 0; + + while (rdlen >= 2) + { + if (p[0] == type >> 8) + { + /* Does the NSEC3 say our type exists? */ + if (offset < p[1] && (p[offset+2] & mask) != 0) + return STAT_BOGUS; + + break; /* finshed checking */ + } + + rdlen -= p[1]; + p += p[1]; + } + + return 1; + } + else if (rc <= 0) + { + /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, + wrap around case, name-hash falls between NSEC3 name-hash and end */ + if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) + return 1; + } + else + { + /* wrap around case, name falls between start and next domain name */ + if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) + return 1; + } + } + } + return 0; +} + static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, - char *workspace1, char *workspace2, char *name, int type) + char *workspace1, char *workspace2, char *name, int type, char *wildname) { unsigned char *salt, *p, *digest; - int digest_len, i, iterations, salt_len, hash_len, base32_len, algo = 0; + int digest_len, i, iterations, salt_len, base32_len, algo = 0; struct nettle_hash const *hash; char *closest_encloser, *next_closest, *wildcard; @@ -1520,7 +1606,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (!(hash = hash_find("sha1"))) return STAT_BOGUS; - /* Now, we need the "closest encloser NSEC3" */ + if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; + + if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count)) + return STAT_SECURE; + + /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" + or an answer inferred from a wildcard record. */ closest_encloser = name; next_closest = NULL; @@ -1529,6 +1622,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (*closest_encloser == '.') closest_encloser++; + if (wildname && hostname_isequal(closest_encloser, wildname)) + break; + if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) return STAT_BOGUS; @@ -1551,127 +1647,33 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns } while ((closest_encloser = strchr(closest_encloser, '.'))); - /* No usable NSEC3s */ - if (i == nsec_count) + if (!closest_encloser) return STAT_BOGUS; - if (!next_closest) - { - /* We found an NSEC3 whose hashed name exactly matches the query, so - Now we just need to check the type map. p points to the RR data for the record. */ - int rdlen; - unsigned char *psave; - int offset = (type & 0xff) >> 3; - int mask = 0x80 >> (type & 0x07); - - p += 8; /* class, type, TTL */ - GETSHORT(rdlen, p); - psave = p; - p += 5 + salt_len; /* algo, flags, iterations, salt_len, salt */ - hash_len = *p++; - if (!CHECK_LEN(header, p, plen, hash_len)) - return STAT_BOGUS; /* bad packet */ - p += hash_len; - rdlen -= p - psave; - - while (rdlen >= 2) - { - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_BOGUS; - - if (p[0] == type >> 8) - { - /* Does the NSEC3 say our type exists? */ - if (offset < p[1] && (p[offset+2] & mask) != 0) - return STAT_BOGUS; - - break; /* finshed checking */ - } - - rdlen -= p[1]; - p += p[1]; - } - - return STAT_SECURE; - } - /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) return STAT_BOGUS; - for (i = 0; i < nsec_count; i++) - if ((p = nsecs[i])) - { - if (!extract_name(header, plen, &p, workspace1, 1, 0) || - !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) - return STAT_BOGUS; - - p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */ - hash_len = *p++; /* p now points to next hashed name */ - - if (!CHECK_LEN(header, p, plen, hash_len)) - return STAT_BOGUS; - - if (digest_len == base32_len && hash_len == base32_len) - { - if (memcmp(workspace2, digest, digest_len) <= 0) - { - /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, - wrap around case, name-hash falls between NSEC3 name-hash and end */ - if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) - return STAT_SECURE; - } - else - { - /* wrap around case, name falls between start and next domain name */ - if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) - return STAT_SECURE; - } - } - } - - /* Finally, check that there's no seat of wildcard synthesis */ - if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) - return STAT_BOGUS; - - wildcard--; - *wildcard = '*'; - - if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count)) return STAT_BOGUS; - for (i = 0; i < nsec_count; i++) - if ((p = nsecs[i])) - { - if (!extract_name(header, plen, &p, workspace1, 1, 0) || - !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) - return STAT_BOGUS; - - p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */ - hash_len = *p++; /* p now points to next hashed name */ - - if (!CHECK_LEN(header, p, plen, hash_len)) - return STAT_BOGUS; - - if (digest_len == base32_len && hash_len == base32_len) - { - if (memcmp(workspace2, digest, digest_len) <= 0) - { - /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, - wrap around case, name-hash falls between NSEC3 name-hash and end */ - if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) - return STAT_SECURE; - } - else - { - /* wrap around case, name falls between start and next domain name */ - if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) - return STAT_SECURE; - } - } - } + /* Finally, check that there's no seat of wildcard synthesis */ + if (!wildname) + { + if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) + return STAT_BOGUS; + + wildcard--; + *wildcard = '*'; + + if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; + + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count)) + return STAT_BOGUS; + } - return STAT_BOGUS; + return STAT_SECURE; } /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ @@ -1792,8 +1794,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch struct all_addr a; struct blockdata *key; struct crec *crecp; - - rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0); + char *wildname; + + rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0); if (rc == STAT_SECURE_WILDCARD) { @@ -1807,7 +1810,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch if (nsec_type == T_NSEC) rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1); else - rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1); + rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, wildname); if (rc != STAT_SECURE) return rc; @@ -1933,7 +1936,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch if (nsec_type == T_NSEC) return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype); else - return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype); + return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL); } /* Chase the CNAME chain in the packet until the first record which _doesn't validate. @@ -1980,7 +1983,7 @@ int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char return STAT_INSECURE; /* validate CNAME chain, return if insecure or need more data */ - rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, 0, 0, 0); + rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, NULL, 0, 0, 0); if (rc != STAT_SECURE) { if (rc == STAT_NO_SIG) -- cgit v1.2.1