From a3db237b8f1b97867395e1419f39b8ba5749b777 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 11 Dec 2015 20:19:05 +0100 Subject: resolved: apparently not all names are used in canonical form for DNSSEC validation Specifically, it appears as if the NSEC next domain name should be in the original casing rather than canonical form, when validating. --- src/resolve/resolved-dns-packet.c | 28 ++++++----- src/resolve/resolved-dns-packet.h | 4 +- src/resolve/test-dnssec.c | 97 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 13 deletions(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index b0739fcf12..6714ceaadc 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -438,10 +438,15 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_ return 0; } -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { uint8_t *w; int r; + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + assert(p); assert(d); @@ -454,7 +459,7 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start *(w++) = (uint8_t) l; - if (p->canonical_form) { + if (p->canonical_form && canonical_candidate) { size_t i; /* Generate in canonical form, as defined by DNSSEC @@ -479,6 +484,7 @@ int dns_packet_append_name( DnsPacket *p, const char *name, bool allow_compression, + bool canonical_candidate, size_t *start) { size_t saved_size; @@ -533,7 +539,7 @@ int dns_packet_append_name( if (k > 0) r = k; - r = dns_packet_append_label(p, label, r, &n); + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); if (r < 0) goto fail; @@ -574,7 +580,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); if (r < 0) goto fail; @@ -762,14 +768,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->srv.name, true, NULL); + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); break; case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, NULL); + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); break; case DNS_TYPE_HINFO: @@ -812,11 +818,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, true, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); if (r < 0) goto fail; @@ -844,7 +850,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); break; case DNS_TYPE_LOC: @@ -948,7 +954,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); if (r < 0) goto fail; @@ -956,7 +962,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL); + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); if (r < 0) goto fail; diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 5b6a71dc01..082e92833c 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -169,8 +169,8 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start); -int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); +int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index a2118513f1..cbcb0fd94f 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -28,6 +28,102 @@ #include "resolved-dns-rr.h" #include "string-util.h" +static void test_dnssec_verify_rrset2(void) { + + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, + 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, + 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, + 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, + 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, + 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, + 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, + 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, + 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, + 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, + 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, + 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, + 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, + 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, + 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, + 0x74, 0x62, 0xfe, 0xd7, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; + DnssecResult result; + + nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); + assert_se(nsec); + + nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); + assert_se(nsec->nsec.next_domain_name); + + nsec->nsec.types = bitmap_new(); + assert_se(nsec->nsec.types); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); + assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); + + assert_se(dns_resource_record_to_string(nsec, &x) >= 0); + log_info("NSEC: %s", x); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_NSEC; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 300; + rrsig->rrsig.expiration = 0x5689002f; + rrsig->rrsig.inception = 0x56617230; + rrsig->rrsig.key_tag = 30390; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + assert_se(dns_resource_record_to_string(rrsig, &y) >= 0); + log_info("RRSIG: %s", y); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + assert_se(dns_resource_record_to_string(dnskey, &z) >= 0); + log_info("DNSKEY: %s", z); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + + assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, nsec, 0) >= 0); + + /* Validate the RR as it if was 2015-12-11 today */ + assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + static void test_dnssec_verify_rrset(void) { static const uint8_t signature_blob[] = { @@ -214,6 +310,7 @@ int main(int argc, char*argv[]) { test_dnssec_canonicalize(); test_dnssec_verify_dns_key(); test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); return 0; } -- cgit v1.2.3-54-g00ecf From a1972a9185fcce580a984df0d240d02c5a7cde3c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:20:05 +0100 Subject: resolved: rework how we get the gcrypt digest algorithm ID from DNSSEC digest ids Let's move this into a function digest_to_gcrypt() that we can reuse later on when implementing NSEC3 validation. --- src/resolve/resolved-dns-dnssec.c | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 792c9d8692..a0433b2387 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -72,12 +72,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; @@ -679,9 +673,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 +717,24 @@ 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) { - - case DNSSEC_DIGEST_SHA1: + algorithm = digest_to_gcrypt(ds->ds.digest_type); + if (algorithm < 0) + return algorithm; - if (ds->ds.digest_size != 20) - return 0; + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); - gcry_md_open(&md, GCRY_MD_SHA1, 0); - break; - - case DNSSEC_DIGEST_SHA256: - - if (ds->ds.digest_size != 32) - return 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); -- cgit v1.2.3-54-g00ecf From 0638401af347c002ab4f8272e100ba209c3ab947 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:21:16 +0100 Subject: resolved: initialize libgcrypt before using it --- src/resolve/resolved-dns-dnssec.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index a0433b2387..9eb54d44db 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -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, @@ -329,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) { @@ -717,6 +732,8 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { if (dnssec_keytag(dnskey) != ds->ds.key_tag) return 0; + initialize_libgcrypt(); + algorithm = digest_to_gcrypt(ds->ds.digest_type); if (algorithm < 0) return algorithm; -- cgit v1.2.3-54-g00ecf From 5264131a9ab9cf0b1bd6bc217e0361536338a980 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:21:59 +0100 Subject: resolved: don't choke on NULL DNS transactions when determining query candidate state --- src/resolve/resolved-dns-query.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index a6565f2ba2..405882a6ea 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -185,6 +185,14 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { switch (t->state) { + case DNS_TRANSACTION_NULL: + /* If there's a NULL transaction pending, then + * this means not all transactions where + * started yet, and we were called from within + * the stackframe that is supposed to start + * remaining transactions. In this case, + * simply claim the candidate is pending. */ + case DNS_TRANSACTION_PENDING: case DNS_TRANSACTION_VALIDATING: /* If there's one transaction currently in @@ -197,9 +205,6 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { state = t->state; break; - case DNS_TRANSACTION_NULL: - assert_not_reached("Transaction not started?"); - default: if (state != DNS_TRANSACTION_SUCCESS) state = t->state; -- cgit v1.2.3-54-g00ecf From 24a5b982cf5aac97488eb94dba18d71e8b2b411a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:22:40 +0100 Subject: resolved: always consider NSEC/NSEC3 RRs as "primary" It's not OK to drop these for our proof of non-existance checks. --- src/resolve/resolved-dns-transaction.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 82b49c1440..045627340b 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1288,7 +1288,10 @@ static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRec /* Check if the specified RR is the "primary" response, * i.e. either matches the question precisely or is a - * CNAME/DNAME for it */ + * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */ + + if (IN_SET(rr->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) + return 1; r = dns_resource_key_match_rr(t->key, rr, NULL); if (r != 0) -- cgit v1.2.3-54-g00ecf From e1a9f1a81dd438f972353f1907a99325c51bdfda Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:23:54 +0100 Subject: resolved: constify a parameter --- src/resolve/resolved-dns-packet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 6714ceaadc..8fc3094b67 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -602,7 +602,7 @@ fail: return r; } -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) { +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { size_t saved_size; int r; -- cgit v1.2.3-54-g00ecf From d0ae14ff09fca330d0ae3b41ab15e0d42210967b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:26:15 +0100 Subject: resolved: when serializing NSEC3 windows, don't write more windows than necessary --- src/resolve/resolved-dns-packet.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 8fc3094b67..c34ecc44f8 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -659,15 +659,16 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { } window = n >> 8; - entry = n & 255; bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); } - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } if (start) *start = saved_size; -- cgit v1.2.3-54-g00ecf From 72667f0890372a952a7c5b8cc498ec3cf9440973 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:26:42 +0100 Subject: resolved: add basic proof of non-existance support for NSEC+NSEC3 Note that this is not complete yet, as we don't handle wildcard domains correctly, nor handle domains correctly that use empty non-terminals. --- src/resolve/resolved-dns-answer.c | 1 + src/resolve/resolved-dns-dnssec.c | 183 +++++++++++++++++++++++++++++++++ src/resolve/resolved-dns-dnssec.h | 15 +++ src/resolve/resolved-dns-transaction.c | 53 +++++++++- src/resolve/test-dnssec.c | 35 +++++++ 5 files changed, 285 insertions(+), 2 deletions(-) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 6a37345e7e..5355303bd3 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -22,6 +22,7 @@ #include "alloc-util.h" #include "dns-domain.h" #include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.h" #include "string-util.h" DnsAnswer *dns_answer_new(unsigned n) { diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 9eb54d44db..d7d50c164c 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" @@ -795,6 +796,187 @@ 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; +} + +int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) { + DnsResourceRecord *rr; + int r; + + assert(key); + assert(result); + + /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ + + DNS_ANSWER_FOREACH(rr, answer) { + + if (rr->key->class != key->class) + 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: { + _cleanup_free_ void *decoded = NULL; + size_t decoded_size; + char label[DNS_LABEL_MAX]; + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int label_length, c, q; + const char *p; + bool covered; + + /* 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; + + p = DNS_RESOURCE_KEY_NAME(rr->key); + label_length = dns_label_unescape(&p, label, sizeof(label)); + if (label_length < 0) + return label_length; + if (label_length == 0) + continue; + + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), p); + if (r < 0) + return r; + if (r == 0) + continue; + + r = unbase32hexmem(label, label_length, false, &decoded, &decoded_size); + if (r == -EINVAL) + continue; + if (r < 0) + return r; + + if (decoded_size != rr->nsec3.next_hashed_name_size) + continue; + + c = memcmp(decoded, rr->nsec3.next_hashed_name, decoded_size); + if (c == 0) + continue; + + r = dnssec_nsec3_hash(rr, DNS_RESOURCE_KEY_NAME(key), hashed); + /* RFC 5155, Section 8.1 says we MUST ignore NSEC3 RRs with unknown algorithms */ + if (r == -EOPNOTSUPP) + continue; + if (r < 0) + return r; + if ((size_t) r != decoded_size) + continue; + + r = memcmp(decoded, hashed, decoded_size); + if (r == 0) { + *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; + return 0; + } + + q = memcmp(hashed, rr->nsec3.next_hashed_name, decoded_size); + + covered = c < 0 ? + r < 0 && q < 0 : + q < 0 || r < 0; + + if (covered) { + *result = DNSSEC_NSEC_NXDOMAIN; + return 0; + } + + break; + } + + default: + break; + } + } + + /* 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", @@ -811,5 +993,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); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index f33abe3e11..442e301302 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -56,12 +56,16 @@ enum DnssecResult { /* These two are added by the DnsTransaction logic */ DNSSEC_UNSIGNED, DNSSEC_FAILED_AUXILIARY, + DNSSEC_NSEC_MISMATCH, _DNSSEC_RESULT_MAX, _DNSSEC_RESULT_INVALID = -1 }; #define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) +/* The longest digest we'll ever generate, of all digest algorithms we support */ +#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) + int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey); int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig); @@ -75,6 +79,17 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey); int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); + +typedef enum DnssecNsecResult { + DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_NXDOMAIN, + DNSSEC_NSEC_NODATA, + DNSSEC_NSEC_FOUND, +} DnssecNsecResult; + +int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result); + const char* dnssec_mode_to_string(DnssecMode m) _const_; DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 045627340b..f09788b0c6 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1472,8 +1472,57 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { /* Everything that's now in t->answer is known to be good, hence cacheable. */ t->n_answer_cacheable = (unsigned) -1; /* everything! */ - t->answer_authenticated = true; - t->dnssec_result = DNSSEC_VALIDATED; + /* At this point the answer only contains validated + * RRsets. Now, let's see if it actually answers the question + * we asked. If so, great! If it doesn't, then see if + * NSEC/NSEC3 can prove this. */ + r = dns_answer_match_key(t->answer, t->key); + if (r < 0) + return r; + if (r > 0) { + /* Yes, it answer the question, everything is authenticated. */ + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + } else if (r == 0) { + DnssecNsecResult nr; + + /* Bummer! Let's check NSEC/NSEC3 */ + r = dnssec_test_nsec(t->answer, t->key, &nr); + if (r < 0) + return r; + + switch (nr) { + + case DNSSEC_NSEC_NXDOMAIN: + /* NSEC proves the domain doesn't exist. Very good. */ + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_NXDOMAIN; + t->answer_authenticated = true; + break; + + case DNSSEC_NSEC_NODATA: + /* NSEC proves that there's no data here, very good. */ + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + break; + + case DNSSEC_NSEC_NO_RR: + /* No NSEC data? Bummer! */ + t->dnssec_result = DNSSEC_UNSIGNED; + break; + + case DNSSEC_NSEC_FOUND: + /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ + t->dnssec_result = DNSSEC_NSEC_MISMATCH; + break; + + default: + assert_not_reached("Unexpected NSEC result."); + } + } + return 1; } diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index cbcb0fd94f..807eeb3d9a 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -27,6 +27,7 @@ #include "resolved-dns-dnssec.h" #include "resolved-dns-rr.h" #include "string-util.h" +#include "hexdecoct.h" static void test_dnssec_verify_rrset2(void) { @@ -305,12 +306,46 @@ static void test_dnssec_canonicalize(void) { test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); } +static void test_dnssec_nsec3_hash(void) { + static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; + static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *a = NULL, *b = NULL; + uint8_t h[DNSSEC_HASH_SIZE_MAX]; + int k; + + /* The NSEC3 RR for eurid.eu on 2015-12-14. */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); + assert_se(rr); + + rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; + rr->nsec3.flags = 1; + rr->nsec3.iterations = 1; + rr->nsec3.salt = memdup(salt, sizeof(salt)); + assert_se(rr->nsec3.salt); + rr->nsec3.salt_size = sizeof(salt); + rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); + assert_se(rr->nsec3.next_hashed_name); + rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); + + assert_se(dns_resource_record_to_string(rr, &a) >= 0); + log_info("NSEC3: %s", a); + + k = dnssec_nsec3_hash(rr, "eurid.eu", &h); + assert_se(k >= 0); + + b = base32hexmem(h, k, false); + assert_se(b); + assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); +} + int main(int argc, char*argv[]) { test_dnssec_canonicalize(); test_dnssec_verify_dns_key(); test_dnssec_verify_rrset(); test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); return 0; } -- cgit v1.2.3-54-g00ecf From 73b8d8e92894c09669c6634a1a936ba604ebc26a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 14 Dec 2015 21:32:17 +0100 Subject: resolved: update DNSSEC TODO --- src/resolve/resolved-dns-dnssec.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/resolve') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index d7d50c164c..ed126505ad 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -43,6 +43,8 @@ * - multi-label zone compatibility * - DNSSEC cname/dname compatibility * - per-interface DNSSEC setting + * - retry on failed validation + * - fix TTL for cache entries to match RRSIG TTL * - DSA support * - EC support? * -- cgit v1.2.3-54-g00ecf