summaryrefslogtreecommitdiff
path: root/src/resolve/resolved-dns-dnssec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve/resolved-dns-dnssec.c')
-rw-r--r--src/resolve/resolved-dns-dnssec.c590
1 files changed, 439 insertions, 151 deletions
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index 814cb1c0f9..a3aa90e98d 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -36,14 +36,13 @@
* TODO:
*
* - Make trust anchor store read additional DS+DNSKEY data from disk
- * - wildcard zones compatibility
+ * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
* - multi-label zone compatibility
* - cname/dname compatibility
* - per-interface DNSSEC setting
- * - fix TTL for cache entries to match RRSIG TTL
+ * - nxdomain on qname
* - retry on failed validation?
- * - DSA support
- * - EC support?
+ * - DSA support?
*
* */
@@ -77,14 +76,6 @@ static void initialize_libgcrypt(void) {
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
-static bool dnssec_algorithm_supported(int algorithm) {
- return IN_SET(algorithm,
- DNSSEC_ALGORITHM_RSASHA1,
- DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
- DNSSEC_ALGORITHM_RSASHA256,
- DNSSEC_ALGORITHM_RSASHA512);
-}
-
uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
const uint8_t *p;
uint32_t sum;
@@ -136,7 +127,7 @@ static int rr_compare(const void *a, const void *b) {
return 0;
}
-static int dnssec_rsa_verify(
+static int dnssec_rsa_verify_raw(
const char *hash_algorithm,
const void *signature, size_t signature_size,
const void *data, size_t data_size,
@@ -226,6 +217,196 @@ finish:
return r;
}
+static int dnssec_rsa_verify(
+ const char *hash_algorithm,
+ const void *hash, size_t hash_size,
+ DnsResourceRecord *rrsig,
+ DnsResourceRecord *dnskey) {
+
+ size_t exponent_size, modulus_size;
+ void *exponent, *modulus;
+
+ assert(hash_algorithm);
+ assert(hash);
+ assert(hash_size > 0);
+ assert(rrsig);
+ assert(dnskey);
+
+ if (*(uint8_t*) dnskey->dnskey.key == 0) {
+ /* exponent is > 255 bytes long */
+
+ exponent = (uint8_t*) dnskey->dnskey.key + 3;
+ exponent_size =
+ ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
+ ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
+
+ if (exponent_size < 256)
+ return -EINVAL;
+
+ if (3 + exponent_size >= dnskey->dnskey.key_size)
+ return -EINVAL;
+
+ modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
+ modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
+
+ } else {
+ /* exponent is <= 255 bytes long */
+
+ exponent = (uint8_t*) dnskey->dnskey.key + 1;
+ exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
+
+ if (exponent_size <= 0)
+ return -EINVAL;
+
+ if (1 + exponent_size >= dnskey->dnskey.key_size)
+ return -EINVAL;
+
+ modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
+ modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
+ }
+
+ return dnssec_rsa_verify_raw(
+ hash_algorithm,
+ rrsig->rrsig.signature, rrsig->rrsig.signature_size,
+ hash, hash_size,
+ exponent, exponent_size,
+ modulus, modulus_size);
+}
+
+static int dnssec_ecdsa_verify_raw(
+ const char *hash_algorithm,
+ const char *curve,
+ const void *signature_r, size_t signature_r_size,
+ const void *signature_s, size_t signature_s_size,
+ const void *data, size_t data_size,
+ const void *key, size_t key_size) {
+
+ gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
+ gcry_mpi_t q = NULL, r = NULL, s = NULL;
+ gcry_error_t ge;
+ int k;
+
+ assert(hash_algorithm);
+
+ ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_sexp_build(&signature_sexp,
+ NULL,
+ "(sig-val (ecdsa (r %m) (s %m)))",
+ r,
+ s);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_sexp_build(&data_sexp,
+ NULL,
+ "(data (flags rfc6979) (hash %s %b))",
+ hash_algorithm,
+ (int) data_size,
+ data);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_sexp_build(&public_key_sexp,
+ NULL,
+ "(public-key (ecc (curve %s) (q %m)))",
+ curve,
+ q);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
+ if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
+ k = 0;
+ else if (ge != 0) {
+ log_debug("ECDSA signature check failed: %s", gpg_strerror(ge));
+ k = -EIO;
+ } else
+ k = 1;
+finish:
+ if (r)
+ gcry_mpi_release(r);
+ if (s)
+ gcry_mpi_release(s);
+ if (q)
+ gcry_mpi_release(q);
+
+ if (public_key_sexp)
+ gcry_sexp_release(public_key_sexp);
+ if (signature_sexp)
+ gcry_sexp_release(signature_sexp);
+ if (data_sexp)
+ gcry_sexp_release(data_sexp);
+
+ return k;
+}
+
+static int dnssec_ecdsa_verify(
+ const char *hash_algorithm,
+ int algorithm,
+ const void *hash, size_t hash_size,
+ DnsResourceRecord *rrsig,
+ DnsResourceRecord *dnskey) {
+
+ const char *curve;
+ size_t key_size;
+ uint8_t *q;
+
+ assert(hash);
+ assert(hash_size);
+ assert(rrsig);
+ assert(dnskey);
+
+ if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) {
+ key_size = 32;
+ curve = "NIST P-256";
+ } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) {
+ key_size = 48;
+ curve = "NIST P-384";
+ } else
+ return -EOPNOTSUPP;
+
+ if (dnskey->dnskey.key_size != key_size * 2)
+ return -EINVAL;
+
+ if (rrsig->rrsig.signature_size != key_size * 2)
+ return -EINVAL;
+
+ q = alloca(key_size*2 + 1);
+ q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
+ memcpy(q+1, dnskey->dnskey.key, key_size*2);
+
+ return dnssec_ecdsa_verify_raw(
+ hash_algorithm,
+ curve,
+ rrsig->rrsig.signature, key_size,
+ (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
+ hash, hash_size,
+ q, key_size*2+1);
+}
+
static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
gcry_md_write(md, &v, sizeof(v));
}
@@ -275,6 +456,31 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
return realtime < inception || realtime > expiration;
}
+static int algorithm_to_gcrypt_md(uint8_t algorithm) {
+
+ /* Translates a DNSSEC signature algorithm into a gcrypt digest identifier */
+
+ switch (algorithm) {
+
+ case DNSSEC_ALGORITHM_RSASHA1:
+ case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
+ return GCRY_MD_SHA1;
+
+ case DNSSEC_ALGORITHM_RSASHA256:
+ case DNSSEC_ALGORITHM_ECDSAP256SHA256:
+ return GCRY_MD_SHA256;
+
+ case DNSSEC_ALGORITHM_ECDSAP384SHA384:
+ return GCRY_MD_SHA384;
+
+ case DNSSEC_ALGORITHM_RSASHA512:
+ return GCRY_MD_SHA512;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
int dnssec_verify_rrset(
DnsAnswer *a,
DnsResourceKey *key,
@@ -284,12 +490,12 @@ int dnssec_verify_rrset(
DnssecResult *result) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
- size_t exponent_size, modulus_size, hash_size;
- void *exponent, *modulus, *hash;
+ size_t hash_size;
+ void *hash;
DnsResourceRecord **list, *rr;
gcry_md_hd_t md = NULL;
+ int r, md_algorithm;
size_t k, n = 0;
- int r;
assert(key);
assert(rrsig);
@@ -302,10 +508,13 @@ int dnssec_verify_rrset(
* using the signature "rrsig" and the key "dnskey". It's
* assumed the RRSIG and DNSKEY match. */
- if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) {
+ md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
+ if (md_algorithm == -EOPNOTSUPP) {
*result = DNSSEC_UNSUPPORTED_ALGORITHM;
return 0;
}
+ if (md_algorithm < 0)
+ return md_algorithm;
if (a->n_rrs > VERIFY_RRS_MAX)
return -E2BIG;
@@ -342,31 +551,13 @@ int dnssec_verify_rrset(
/* Bring the RRs into canonical order */
qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
- initialize_libgcrypt();
-
/* OK, the RRs are now in canonical order. Let's calculate the digest */
- switch (rrsig->rrsig.algorithm) {
-
- case DNSSEC_ALGORITHM_RSASHA1:
- case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
- gcry_md_open(&md, GCRY_MD_SHA1, 0);
- hash_size = 20;
- break;
-
- case DNSSEC_ALGORITHM_RSASHA256:
- gcry_md_open(&md, GCRY_MD_SHA256, 0);
- hash_size = 32;
- break;
-
- case DNSSEC_ALGORITHM_RSASHA512:
- gcry_md_open(&md, GCRY_MD_SHA512, 0);
- hash_size = 64;
- break;
+ initialize_libgcrypt();
- default:
- assert_not_reached("Unknown digest");
- }
+ hash_size = gcry_md_get_algo_dlen(md_algorithm);
+ assert(hash_size > 0);
+ gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
@@ -384,10 +575,17 @@ int dnssec_verify_rrset(
gcry_md_write(md, wire_format_name, r);
for (k = 0; k < n; k++) {
+ const char *suffix;
size_t l;
rr = list[k];
- r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
+ 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! */
+ gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
+
+ r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
@@ -410,53 +608,30 @@ int dnssec_verify_rrset(
goto finish;
}
- if (*(uint8_t*) dnskey->dnskey.key == 0) {
- /* exponent is > 255 bytes long */
-
- exponent = (uint8_t*) dnskey->dnskey.key + 3;
- exponent_size =
- ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
- ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
-
- if (exponent_size < 256) {
- r = -EINVAL;
- goto finish;
- }
-
- if (3 + exponent_size >= dnskey->dnskey.key_size) {
- r = -EINVAL;
- goto finish;
- }
-
- modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
- modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
-
- } else {
- /* exponent is <= 255 bytes long */
-
- exponent = (uint8_t*) dnskey->dnskey.key + 1;
- exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
-
- if (exponent_size <= 0) {
- r = -EINVAL;
- goto finish;
- }
+ switch (rrsig->rrsig.algorithm) {
- if (1 + exponent_size >= dnskey->dnskey.key_size) {
- r = -EINVAL;
- goto finish;
- }
+ case DNSSEC_ALGORITHM_RSASHA1:
+ case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
+ case DNSSEC_ALGORITHM_RSASHA256:
+ case DNSSEC_ALGORITHM_RSASHA512:
+ r = dnssec_rsa_verify(
+ gcry_md_algo_name(md_algorithm),
+ hash, hash_size,
+ rrsig,
+ dnskey);
+ break;
- modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
- modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
+ case DNSSEC_ALGORITHM_ECDSAP256SHA256:
+ case DNSSEC_ALGORITHM_ECDSAP384SHA384:
+ r = dnssec_ecdsa_verify(
+ gcry_md_algo_name(md_algorithm),
+ rrsig->rrsig.algorithm,
+ hash, hash_size,
+ rrsig,
+ dnskey);
+ break;
}
- r = dnssec_rsa_verify(
- gcry_md_algo_name(gcry_md_get_algo(md)),
- rrsig->rrsig.signature, rrsig->rrsig.signature_size,
- hash, hash_size,
- exponent, exponent_size,
- modulus, modulus_size);
if (r < 0)
goto finish;
@@ -497,6 +672,8 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
}
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
+ int r;
+
assert(key);
assert(rrsig);
@@ -509,9 +686,45 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig)
if (rrsig->rrsig.type_covered != key->type)
return 0;
+ /* Make sure signer is a parent of the RRset */
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
+ if (r <= 0)
+ return r;
+
+ /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
+ r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
+ if (r < 0)
+ return r;
+ if (r < rrsig->rrsig.labels)
+ return 0;
+
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
+static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(key);
+ assert(rrsig);
+
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dns_resource_key_equal(key, rr->key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* Pick the TTL as the minimum of the RR's TTL, the
+ * RR's original TTL according to the RRSIG and the
+ * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */
+ rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl);
+ rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
+ }
+
+ return 0;
+}
+
int dnssec_verify_rrset_search(
DnsAnswer *a,
DnsResourceKey *key,
@@ -578,7 +791,11 @@ int dnssec_verify_rrset_search(
case DNSSEC_VALIDATED:
/* Yay, the RR has been validated,
- * return immediately. */
+ * return immediately, but fix up the expiry */
+ r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime);
+ if (r < 0)
+ return r;
+
*result = DNSSEC_VALIDATED;
return 0;
@@ -709,9 +926,9 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return (int) c;
}
-static int digest_to_gcrypt(uint8_t algorithm) {
+static int digest_to_gcrypt_md(uint8_t algorithm) {
- /* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */
+ /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
switch (algorithm) {
@@ -721,6 +938,9 @@ static int digest_to_gcrypt(uint8_t algorithm) {
case DNSSEC_DIGEST_SHA256:
return GCRY_MD_SHA256;
+ case DNSSEC_DIGEST_SHA384:
+ return GCRY_MD_SHA384;
+
default:
return -EOPNOTSUPP;
}
@@ -730,9 +950,8 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
- int algorithm;
+ int md_algorithm, r;
void *result;
- int r;
assert(dnskey);
assert(ds);
@@ -755,11 +974,11 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
initialize_libgcrypt();
- algorithm = digest_to_gcrypt(ds->ds.digest_type);
- if (algorithm < 0)
- return algorithm;
+ md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type);
+ if (md_algorithm < 0)
+ return md_algorithm;
- hash_size = gcry_md_get_algo_dlen(algorithm);
+ hash_size = gcry_md_get_algo_dlen(md_algorithm);
assert(hash_size > 0);
if (ds->ds.digest_size != hash_size)
@@ -769,7 +988,7 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
if (r < 0)
return r;
- gcry_md_open(&md, algorithm, 0);
+ gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
@@ -810,6 +1029,15 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
if (ds->key->type != DNS_TYPE_DS)
continue;
+ if (ds->key->class != dnskey->key->class)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
r = dnssec_verify_dnskey(dnskey, ds);
if (r < 0)
return r;
@@ -836,7 +1064,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
if (nsec3->key->type != DNS_TYPE_NSEC3)
return -EINVAL;
- algorithm = digest_to_gcrypt(nsec3->nsec3.algorithm);
+ algorithm = digest_to_gcrypt_md(nsec3->nsec3.algorithm);
if (algorithm < 0)
return algorithm;
@@ -888,62 +1116,136 @@ finish:
return r;
}
-static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
+static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
+ const char *a, *b;
+ int r;
+
+ assert(rr);
+
+ if (rr->key->type != DNS_TYPE_NSEC3)
+ return 0;
+
+ /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
+ if (!IN_SET(rr->nsec3.flags, 0, 1))
+ return 0;
+
+ if (!nsec3)
+ return 1;
+
+ /* If a second NSEC3 RR is specified, also check if they are from the same zone. */
+
+ if (nsec3 == rr) /* Shortcut */
+ return 1;
+
+ if (rr->key->class != nsec3->key->class)
+ return 0;
+ if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
+ return 0;
+ if (rr->nsec3.iterations != nsec3->nsec3.iterations)
+ return 0;
+ if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
+ return 0;
+ if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
+ return 0;
+
+ a = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_parent(&a); /* strip off hash */
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ b = DNS_RESOURCE_KEY_NAME(nsec3->key);
+ r = dns_name_parent(&b); /* strip off hash */
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ return dns_name_equal(a, b);
+}
+
+static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
_cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
- const char *p, *pp = NULL;
- DnsResourceRecord *rr;
+ const char *suffix, *p, *pp = NULL;
+ DnsResourceRecord *rr, *suffix_rr;
DnsAnswerFlags flags;
int hashed_size, r;
+ bool a;
assert(key);
assert(result);
+ assert(authenticated);
+
+ /* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
+ suffix = DNS_RESOURCE_KEY_NAME(key);
+ for (;;) {
+ DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
+ r = nsec3_is_good(suffix_rr, flags, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto found_suffix;
+ }
+
+ /* Strip one label from the front */
+ r = dns_name_parent(&suffix);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
- /* First step, look for the closest encloser NSEC3 RR in 'answer' that matches 'key' */
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+
+found_suffix:
+ /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
p = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
- DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
- _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
+ _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
- if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
- continue;
+ hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed);
+ if (hashed_size == -EOPNOTSUPP) {
+ *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
+ return 0;
+ }
+ if (hashed_size < 0)
+ return hashed_size;
- if (rr->key->type != DNS_TYPE_NSEC3)
- continue;
+ label = base32hexmem(hashed, hashed_size, false);
+ if (!label)
+ return -ENOMEM;
- /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
- if (!IN_SET(rr->nsec3.flags, 0, 1))
- continue;
+ hashed_domain = strjoin(label, ".", suffix, NULL);
+ if (!hashed_domain)
+ return -ENOMEM;
- r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), p);
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+
+ r = nsec3_is_good(rr, flags, suffix_rr);
if (r < 0)
return r;
if (r == 0)
continue;
- hashed_size = dnssec_nsec3_hash(rr, p, hashed);
- if (hashed_size == -EOPNOTSUPP) {
- *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
- return 0;
- }
- if (hashed_size < 0)
- return hashed_size;
if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
- return -EBADMSG;
-
- label = base32hexmem(hashed, hashed_size, false);
- if (!label)
- return -ENOMEM;
-
- hashed_domain = strjoin(label, ".", p, NULL);
- if (!hashed_domain)
- return -ENOMEM;
+ continue;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
if (r < 0)
return r;
- if (r > 0)
- goto found;
+ if (r > 0) {
+ a = flags & DNS_ANSWER_AUTHENTICATED;
+ goto found_closest_encloser;
+ }
}
/* We didn't find the closest encloser with this name,
@@ -963,7 +1265,7 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR
*result = DNSSEC_NSEC_NO_RR;
return 0;
-found:
+found_closest_encloser:
/* We found a closest encloser in 'p'; next closer is 'pp' */
/* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
@@ -981,6 +1283,7 @@ found:
if (!pp) {
/* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
*result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
+ *authenticated = a;
return 0;
}
@@ -1000,26 +1303,8 @@ found:
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
_cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
- const char *nsec3_parent;
-
- if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
- continue;
-
- if (rr->key->type != DNS_TYPE_NSEC3)
- continue;
- /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
- if (!IN_SET(rr->nsec3.flags, 0, 1))
- continue;
-
- nsec3_parent = DNS_RESOURCE_KEY_NAME(rr->key);
- r = dns_name_parent(&nsec3_parent);
- if (r < 0)
- return r;
- if (r == 0)
- continue;
-
- r = dns_name_equal(p, nsec3_parent);
+ r = nsec3_is_good(rr, flags, suffix_rr);
if (r < 0)
return r;
if (r == 0)
@@ -1042,6 +1327,7 @@ found:
else
*result = DNSSEC_NSEC_NXDOMAIN;
+ *authenticated = a && (flags & DNS_ANSWER_AUTHENTICATED);
return 1;
}
}
@@ -1050,7 +1336,7 @@ found:
return 0;
}
-int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
+int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
DnsResourceRecord *rr;
bool have_nsec3 = false;
DnsAnswerFlags flags;
@@ -1058,6 +1344,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
assert(key);
assert(result);
+ assert(authenticated);
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
@@ -1066,9 +1353,6 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
if (rr->key->class != key->class)
continue;
- if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
- continue;
-
switch (rr->key->type) {
case DNS_TYPE_NSEC:
@@ -1078,6 +1362,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return r;
if (r > 0) {
*result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
return 0;
}
@@ -1086,6 +1371,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return r;
if (r > 0) {
*result = DNSSEC_NSEC_NXDOMAIN;
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
return 0;
}
break;
@@ -1098,7 +1384,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
if (have_nsec3)
- return dnssec_test_nsec3(answer, key, result);
+ return dnssec_test_nsec3(answer, key, result, authenticated);
/* No approproate NSEC RR found, report this. */
*result = DNSSEC_NSEC_NO_RR;
@@ -1107,6 +1393,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
+ [DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
@@ -1121,5 +1408,6 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_UNSIGNED] = "unsigned",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
+ [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);