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.c406
1 files changed, 366 insertions, 40 deletions
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index 792c9d8692..814cb1c0f9 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -23,6 +23,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"
@@ -34,14 +35,13 @@
*
* TODO:
*
- * - Iterative validation
- * - NSEC proof of non-existance
- * - NSEC3 proof of non-existance
* - Make trust anchor store read additional DS+DNSKEY data from disk
* - wildcard zones compatibility
* - multi-label zone compatibility
- * - DNSSEC cname/dname compatibility
+ * - cname/dname compatibility
* - per-interface DNSSEC setting
+ * - fix TTL for cache entries to match RRSIG TTL
+ * - retry on failed validation?
* - DSA support
* - EC support?
*
@@ -64,6 +64,19 @@
* Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
*/
+static void initialize_libgcrypt(void) {
+ const char *p;
+
+ if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+ return;
+
+ p = gcry_check_version("1.4.5");
+ assert(p);
+
+ gcry_control(GCRYCTL_DISABLE_SECMEM);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
static bool dnssec_algorithm_supported(int algorithm) {
return IN_SET(algorithm,
DNSSEC_ALGORITHM_RSASHA1,
@@ -72,12 +85,6 @@ static bool dnssec_algorithm_supported(int algorithm) {
DNSSEC_ALGORITHM_RSASHA512);
}
-static bool dnssec_digest_supported(int digest) {
- return IN_SET(digest,
- DNSSEC_DIGEST_SHA1,
- DNSSEC_DIGEST_SHA256);
-}
-
uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
const uint8_t *p;
uint32_t sum;
@@ -335,6 +342,8 @@ 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) {
@@ -487,7 +496,7 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
}
-int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
+int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
assert(key);
assert(rrsig);
@@ -517,7 +526,7 @@ int dnssec_verify_rrset_search(
assert(key);
assert(result);
- /* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */
+ /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
@@ -525,6 +534,7 @@ int dnssec_verify_rrset_search(
/* Iterate through each RRSIG RR. */
DNS_ANSWER_FOREACH(rrsig, a) {
DnsResourceRecord *dnskey;
+ DnsAnswerFlags flags;
/* Is this an RRSIG RR that applies to RRs matching our key? */
r = dnssec_key_match_rrsig(key, rrsig);
@@ -536,9 +546,12 @@ int dnssec_verify_rrset_search(
found_rrsig = true;
/* Look for a matching key */
- DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
+ DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
DnssecResult one_result;
+ if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
+ continue;
+
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
if (r < 0)
@@ -614,6 +627,23 @@ int dnssec_verify_rrset_search(
return 0;
}
+int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
+ DnsResourceRecord *rr;
+ int r;
+
+ /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
+
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dnssec_key_match_rrsig(key, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
size_t c = 0;
int r;
@@ -679,9 +709,28 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return (int) c;
}
+static int digest_to_gcrypt(uint8_t algorithm) {
+
+ /* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */
+
+ switch (algorithm) {
+
+ case DNSSEC_DIGEST_SHA1:
+ return GCRY_MD_SHA1;
+
+ case DNSSEC_DIGEST_SHA256:
+ return GCRY_MD_SHA256;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
- gcry_md_hd_t md = NULL;
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
+ gcry_md_hd_t md = NULL;
+ size_t hash_size;
+ int algorithm;
void *result;
int r;
@@ -704,38 +753,26 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
if (dnssec_keytag(dnskey) != ds->ds.key_tag)
return 0;
- if (!dnssec_digest_supported(ds->ds.digest_type))
- return -EOPNOTSUPP;
-
- switch (ds->ds.digest_type) {
+ initialize_libgcrypt();
- case DNSSEC_DIGEST_SHA1:
-
- if (ds->ds.digest_size != 20)
- return 0;
-
- gcry_md_open(&md, GCRY_MD_SHA1, 0);
- break;
-
- case DNSSEC_DIGEST_SHA256:
+ algorithm = digest_to_gcrypt(ds->ds.digest_type);
+ if (algorithm < 0)
+ return algorithm;
- if (ds->ds.digest_size != 32)
- return 0;
+ hash_size = gcry_md_get_algo_dlen(algorithm);
+ assert(hash_size > 0);
- gcry_md_open(&md, GCRY_MD_SHA256, 0);
- break;
+ if (ds->ds.digest_size != hash_size)
+ return 0;
- default:
- assert_not_reached("Unknown digest");
- }
+ r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
+ if (r < 0)
+ return r;
+ gcry_md_open(&md, algorithm, 0);
if (!md)
return -EIO;
- r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
- if (r < 0)
- goto finish;
-
gcry_md_write(md, owner_name, r);
md_add_uint16(md, dnskey->dnskey.flags);
md_add_uint8(md, dnskey->dnskey.protocol);
@@ -757,6 +794,7 @@ finish:
int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
DnsResourceRecord *ds;
+ DnsAnswerFlags flags;
int r;
assert(dnskey);
@@ -764,7 +802,10 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return 0;
- DNS_ANSWER_FOREACH(ds, validated_ds) {
+ DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {
+
+ if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
+ continue;
if (ds->key->type != DNS_TYPE_DS)
continue;
@@ -779,9 +820,293 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
return 0;
}
+int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
+ uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
+ gcry_md_hd_t md = NULL;
+ size_t hash_size;
+ int algorithm;
+ void *result;
+ unsigned k;
+ int r;
+
+ assert(nsec3);
+ assert(name);
+ assert(ret);
+
+ if (nsec3->key->type != DNS_TYPE_NSEC3)
+ return -EINVAL;
+
+ algorithm = digest_to_gcrypt(nsec3->nsec3.algorithm);
+ if (algorithm < 0)
+ return algorithm;
+
+ initialize_libgcrypt();
+
+ hash_size = gcry_md_get_algo_dlen(algorithm);
+ assert(hash_size > 0);
+
+ if (nsec3->nsec3.next_hashed_name_size != hash_size)
+ return -EINVAL;
+
+ r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
+ if (r < 0)
+ return r;
+
+ gcry_md_open(&md, algorithm, 0);
+ if (!md)
+ return -EIO;
+
+ gcry_md_write(md, wire_format, r);
+ gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
+
+ result = gcry_md_read(md, 0);
+ if (!result) {
+ r = -EIO;
+ goto finish;
+ }
+
+ for (k = 0; k < nsec3->nsec3.iterations; k++) {
+ uint8_t tmp[hash_size];
+ memcpy(tmp, result, hash_size);
+
+ gcry_md_reset(md);
+ gcry_md_write(md, tmp, hash_size);
+ gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
+
+ result = gcry_md_read(md, 0);
+ if (!result) {
+ r = -EIO;
+ goto finish;
+ }
+ }
+
+ memcpy(ret, result, hash_size);
+ r = (int) hash_size;
+
+finish:
+ gcry_md_close(md);
+ return r;
+}
+
+static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
+ _cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
+ uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
+ const char *p, *pp = NULL;
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+ int hashed_size, r;
+
+ assert(key);
+ assert(result);
+
+ /* First step, look for 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;
+
+ 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;
+
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), p);
+ 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;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto found;
+ }
+
+ /* We didn't find the closest encloser with this name,
+ * but let's remember this domain name, it might be
+ * the next closer name */
+
+ pp = p;
+
+ /* Strip one label from the front */
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
+
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+
+found:
+ /* We found a closest encloser in 'p'; next closer is 'pp' */
+
+ /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
+ if (bitmap_isset(rr->nsec3.types, DNS_TYPE_DNAME))
+ return -EBADMSG;
+
+ /* Ensure that this data is from the delegated domain
+ * (i.e. originates from the "lower" DNS server), and isn't
+ * just glue records (i.e. doesn't originate from the "upper"
+ * DNS server). */
+ if (bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) &&
+ !bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA))
+ return -EBADMSG;
+
+ 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;
+ return 0;
+ }
+
+ r = dnssec_nsec3_hash(rr, pp, hashed);
+ if (r < 0)
+ return r;
+ if (r != hashed_size)
+ return -EBADMSG;
+
+ l = base32hexmem(hashed, hashed_size, false);
+ if (!l)
+ return -ENOMEM;
+
+ next_closer_domain = strjoin(l, ".", p, NULL);
+ if (!next_closer_domain)
+ return -ENOMEM;
+
+ 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);
+ 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, ".", p, NULL);
+ if (!next_hashed_domain)
+ return -ENOMEM;
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (rr->nsec3.flags & 1)
+ *result = DNSSEC_NSEC_OPTOUT;
+ else
+ *result = DNSSEC_NSEC_NXDOMAIN;
+
+ return 1;
+ }
+ }
+
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+}
+
+int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
+ DnsResourceRecord *rr;
+ bool have_nsec3 = false;
+ DnsAnswerFlags flags;
+ int r;
+
+ assert(key);
+ assert(result);
+
+ /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
+
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+
+ if (rr->key->class != key->class)
+ continue;
+
+ if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
+ continue;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_NSEC:
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
+ return 0;
+ }
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = DNSSEC_NSEC_NXDOMAIN;
+ return 0;
+ }
+ break;
+
+ case DNS_TYPE_NSEC3:
+ have_nsec3 = true;
+ break;
+ }
+ }
+
+ /* OK, this was not sufficient. Let's see if NSEC3 can help. */
+ if (have_nsec3)
+ return dnssec_test_nsec3(answer, key, result);
+
+ /* No approproate NSEC RR found, report this. */
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+}
+
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
- [DNSSEC_TRUST] = "trust",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
@@ -795,5 +1120,6 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_MISSING_KEY] = "missing-key",
[DNSSEC_UNSIGNED] = "unsigned",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
+ [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);