From af22c65b272f0e7a1c0518c222749f3c09d05438 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 12:58:37 +0100 Subject: resolved: add SHA384 digest support --- src/resolve/resolved-dns-dnssec.c | 3 +++ src/resolve/resolved-dns-rr.h | 1 + 2 files changed, 4 insertions(+) (limited to 'src') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index a856f0717e..3ebaeba76e 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -742,6 +742,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; } diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index f2997883a8..a9a0e61767 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -64,6 +64,7 @@ enum { enum { DNSSEC_DIGEST_SHA1 = 1, DNSSEC_DIGEST_SHA256 = 2, + DNSSEC_DIGEST_SHA384 = 4, _DNSSEC_DIGEST_MAX_DEFINED }; -- cgit v1.2.3-54-g00ecf From fbf1a66d78626b028fd7a5f89c5b605cd74d1e9c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 13:07:36 +0100 Subject: resolved: simplify MD algorithm initialization a bit --- src/resolve/resolved-dns-dnssec.c | 56 ++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 3ebaeba76e..abf4c18ade 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -275,6 +275,27 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { return realtime < inception || realtime > expiration; } +static int algorithm_to_gcrypt(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: + return GCRY_MD_SHA256; + + case DNSSEC_ALGORITHM_RSASHA512: + return GCRY_MD_SHA512; + + default: + return -EOPNOTSUPP; + } +} + int dnssec_verify_rrset( DnsAnswer *a, DnsResourceKey *key, @@ -288,8 +309,8 @@ int dnssec_verify_rrset( void *exponent, *modulus, *hash; DnsResourceRecord **list, *rr; gcry_md_hd_t md = NULL; + int r, algorithm; size_t k, n = 0; - int r; assert(key); assert(rrsig); @@ -342,31 +363,17 @@ 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; + initialize_libgcrypt(); - case DNSSEC_ALGORITHM_RSASHA512: - gcry_md_open(&md, GCRY_MD_SHA512, 0); - hash_size = 64; - break; + algorithm = algorithm_to_gcrypt(rrsig->rrsig.algorithm); + if (algorithm < 0) + return algorithm; - default: - assert_not_reached("Unknown digest"); - } + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); + gcry_md_open(&md, algorithm, 0); if (!md) return -EIO; @@ -732,7 +739,7 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { static int digest_to_gcrypt(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) { @@ -754,9 +761,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 algorithm, r; void *result; - int r; assert(dnskey); assert(ds); -- cgit v1.2.3-54-g00ecf From ea3a892fe39037d71afa43aca12bf1e408a686b4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 14:05:45 +0100 Subject: resolved: split out RSA-specific code from dnssec_verify_rrset() In preparation for ECDSA support. --- src/resolve/resolved-dns-dnssec.c | 110 +++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index abf4c18ade..263ab3f412 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -136,7 +136,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 +226,62 @@ 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 void md_add_uint8(gcry_md_hd_t md, uint8_t v) { gcry_md_write(md, &v, sizeof(v)); } @@ -305,8 +361,8 @@ 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, algorithm; @@ -424,53 +480,11 @@ 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; - } - - if (1 + exponent_size >= dnskey->dnskey.key_size) { - r = -EINVAL; - goto finish; - } - - modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; - } - r = dnssec_rsa_verify( - gcry_md_algo_name(gcry_md_get_algo(md)), - rrsig->rrsig.signature, rrsig->rrsig.signature_size, + gcry_md_algo_name(algorithm), hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); + rrsig, + dnskey); if (r < 0) goto finish; -- cgit v1.2.3-54-g00ecf From c7feab764702e483ef84b47429387a9a54d451ff Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 21:14:29 +0100 Subject: shared: relax restrictions on valid domain name characters a bit Previously, we'd not allow control characters to be embedded in domain names, even when escaped. Since cloudflare uses \000 however to implement its synthethic minimally covering NSEC RRs, we should allow them, as long as they are properly escaped. --- src/shared/dns-domain.c | 14 +++++++++----- src/test/test-dns-domain.c | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 0273b9e3c9..68404ca9e5 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -98,8 +98,13 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { ((unsigned) (n[1] - '0') * 10) + ((unsigned) (n[2] - '0')); - /* Don't allow CC characters or anything that doesn't fit in 8bit */ - if (k < ' ' || k > 255 || k == 127) + /* Don't allow anything that doesn't + * fit in 8bit. Note that we do allow + * control characters, as some servers + * (e.g. cloudflare) are happy to + * generate labels with them + * inside. */ + if (k > 255) return -EINVAL; if (d) @@ -245,7 +250,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { *(q++) = *p; sz -= 1; - } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) { + } else { /* Everything else */ @@ -259,8 +264,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { sz -= 4; - } else - return -EINVAL; + } p++; l--; diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 1b0cb153f7..6c3c49908f 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -168,7 +168,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex static void test_dns_label_escape(void) { test_dns_label_escape_one("", 0, NULL, -EINVAL); test_dns_label_escape_one("hallo", 5, "hallo", 5); - test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); + test_dns_label_escape_one("hallo", 6, "hallo\\000", 9); test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); } @@ -190,7 +190,7 @@ static void test_dns_name_normalize(void) { test_dns_name_normalize_one("f", "f", 0); test_dns_name_normalize_one("f.waldi", "f.waldi", 0); test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); - test_dns_name_normalize_one("\\000", NULL, -EINVAL); + test_dns_name_normalize_one("\\000", "\\000", 0); test_dns_name_normalize_one("..", NULL, -EINVAL); test_dns_name_normalize_one(".foobar", NULL, -EINVAL); test_dns_name_normalize_one("foobar.", "foobar", 0); @@ -216,7 +216,7 @@ static void test_dns_name_equal(void) { test_dns_name_equal_one("abc.def", "CBA.def", false); test_dns_name_equal_one("", "xxx", false); test_dns_name_equal_one("ab", "a", false); - test_dns_name_equal_one("\\000", "xxxx", -EINVAL); + test_dns_name_equal_one("\\000", "\\000", true); test_dns_name_equal_one(".", "", true); test_dns_name_equal_one(".", ".", true); test_dns_name_equal_one("..", "..", -EINVAL); -- cgit v1.2.3-54-g00ecf From e0240c64b76ba8f0c9219feb23a5783f23100216 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 21:35:00 +0100 Subject: resolved: add ECDSA signature support --- src/resolve/resolved-dns-dnssec.c | 174 ++++++++++++++++++++++++++++++++++++-- src/resolve/resolved-dns-rr.h | 2 + 2 files changed, 168 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 263ab3f412..3e8766d73e 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -42,8 +42,7 @@ * - per-interface DNSSEC setting * - fix TTL for cache entries to match RRSIG TTL * - retry on failed validation? - * - DSA support - * - EC support? + * - DSA support? * * */ @@ -82,7 +81,9 @@ static bool dnssec_algorithm_supported(int algorithm) { DNSSEC_ALGORITHM_RSASHA1, DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, DNSSEC_ALGORITHM_RSASHA256, - DNSSEC_ALGORITHM_RSASHA512); + DNSSEC_ALGORITHM_RSASHA512, + DNSSEC_ALGORITHM_ECDSAP256SHA256, + DNSSEC_ALGORITHM_ECDSAP384SHA384); } uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { @@ -282,6 +283,140 @@ static int dnssec_rsa_verify( 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)); } @@ -342,8 +477,12 @@ static int algorithm_to_gcrypt(uint8_t algorithm) { 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; @@ -480,11 +619,30 @@ int dnssec_verify_rrset( goto finish; } - r = dnssec_rsa_verify( - gcry_md_algo_name(algorithm), - hash, hash_size, - rrsig, - dnskey); + switch (rrsig->rrsig.algorithm) { + + 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(algorithm), + hash, hash_size, + rrsig, + dnskey); + break; + + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + r = dnssec_ecdsa_verify( + gcry_md_algo_name(algorithm), + rrsig->rrsig.algorithm, + hash, hash_size, + rrsig, + dnskey); + break; + } + if (r < 0) goto finish; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index a9a0e61767..95d260f7aa 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -53,6 +53,8 @@ enum { DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ + DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ DNSSEC_ALGORITHM_INDIRECT = 252, DNSSEC_ALGORITHM_PRIVATEDNS, DNSSEC_ALGORITHM_PRIVATEOID, -- cgit v1.2.3-54-g00ecf From 502a7985993758e0bc64778e20ebb7d3941a6f55 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 22:22:39 +0100 Subject: resolve-host: log RR parsing errors --- src/resolve-host/resolve-host.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c index 67c93c2618..793c560373 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve-host/resolve-host.c @@ -408,13 +408,11 @@ static int resolve_record(sd_bus *bus, const char *name) { r = dns_packet_read_rr(p, &rr, NULL, NULL); if (r < 0) - return log_error_errno(r, "Failed to parse RR."); + return log_error_errno(r, "Failed to parse RR: %m"); s = dns_resource_record_to_string(rr); - if (!s) { - log_error("Failed to format RR."); - return -ENOMEM; - } + if (!s) + return log_oom(); ifname[0] = 0; if (ifindex > 0 && !if_indextoname(ifindex, ifname)) -- cgit v1.2.3-54-g00ecf From ca994e853c5408fcdeee66580e8b5056ad9e2c15 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 22:56:08 +0100 Subject: resolved: only keep a single list of supported signature algorithms This removes dnssec_algorithm_supported() and simply uses the algorithm_to_gcrypt() result as indication whether a DNSSEC algorithm is supported. The patch also renames "algorithm" to "md_algorithm", in a few cases, in order to avoid confusion between DNSSEC signature algorithms and gcrypt message digest algorithms. --- src/resolve/resolved-dns-dnssec.c | 47 +++++++++++++++------------------------ 1 file changed, 18 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 3e8766d73e..57093ad80c 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -76,16 +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, - DNSSEC_ALGORITHM_ECDSAP256SHA256, - DNSSEC_ALGORITHM_ECDSAP384SHA384); -} - uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { const uint8_t *p; uint32_t sum; @@ -466,7 +456,7 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { return realtime < inception || realtime > expiration; } -static int algorithm_to_gcrypt(uint8_t algorithm) { +static int algorithm_to_gcrypt_md(uint8_t algorithm) { /* Translates a DNSSEC signature algorithm into a gcrypt digest identifier */ @@ -504,7 +494,7 @@ int dnssec_verify_rrset( void *hash; DnsResourceRecord **list, *rr; gcry_md_hd_t md = NULL; - int r, algorithm; + int r, md_algorithm; size_t k, n = 0; assert(key); @@ -518,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; @@ -561,14 +554,10 @@ int dnssec_verify_rrset( /* OK, the RRs are now in canonical order. Let's calculate the digest */ initialize_libgcrypt(); - algorithm = algorithm_to_gcrypt(rrsig->rrsig.algorithm); - if (algorithm < 0) - return algorithm; - - hash_size = gcry_md_get_algo_dlen(algorithm); + hash_size = gcry_md_get_algo_dlen(md_algorithm); assert(hash_size > 0); - gcry_md_open(&md, algorithm, 0); + gcry_md_open(&md, md_algorithm, 0); if (!md) return -EIO; @@ -626,7 +615,7 @@ int dnssec_verify_rrset( case DNSSEC_ALGORITHM_RSASHA256: case DNSSEC_ALGORITHM_RSASHA512: r = dnssec_rsa_verify( - gcry_md_algo_name(algorithm), + gcry_md_algo_name(md_algorithm), hash, hash_size, rrsig, dnskey); @@ -635,7 +624,7 @@ int dnssec_verify_rrset( case DNSSEC_ALGORITHM_ECDSAP256SHA256: case DNSSEC_ALGORITHM_ECDSAP384SHA384: r = dnssec_ecdsa_verify( - gcry_md_algo_name(algorithm), + gcry_md_algo_name(md_algorithm), rrsig->rrsig.algorithm, hash, hash_size, rrsig, @@ -909,7 +898,7 @@ 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 identifier */ @@ -933,7 +922,7 @@ 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, r; + int md_algorithm, r; void *result; assert(dnskey); @@ -957,11 +946,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) @@ -971,7 +960,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; @@ -1047,7 +1036,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; -- cgit v1.2.3-54-g00ecf From f535705a457f9bee976a45baf20272b7228d0c65 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 27 Dec 2015 22:58:17 +0100 Subject: resolved: clean up dns_transaction_stop() This renames dns_transaction_stop() to dns_transaction_stop_timeout() and makes it only about stopping the transaction timeout. This is safe, as in most occasions we call dns_transaction_stop() at the same time as dns_transaction_close_connection() anyway, which does the rest of what dns_transaction_stop() used to do. And in the one where we don't call it, it's implicitly called by the UDP emission or TCP connection code. This also closes the connections as we enter the validation phase of a transaction, so that no further messages may be received then. --- src/resolve/resolved-dns-transaction.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 3ca4a5ab74..fb95554db3 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -48,14 +48,10 @@ static void dns_transaction_close_connection(DnsTransaction *t) { t->dns_udp_fd = safe_close(t->dns_udp_fd); } -static void dns_transaction_stop(DnsTransaction *t) { +static void dns_transaction_stop_timeout(DnsTransaction *t) { assert(t); t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); - t->stream = dns_stream_free(t->stream); - - /* Note that we do not drop the UDP socket here, as we want to - * reuse it to repeat the interaction. */ } DnsTransaction* dns_transaction_free(DnsTransaction *t) { @@ -67,7 +63,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { return NULL; dns_transaction_close_connection(t); - dns_transaction_stop(t); + dns_transaction_stop_timeout(t); dns_packet_unref(t->sent); dns_transaction_reset_answer(t); @@ -264,7 +260,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { t->state = state; dns_transaction_close_connection(t); - dns_transaction_stop(t); + dns_transaction_stop_timeout(t); /* Notify all queries that are interested, but make sure the * transaction isn't freed while we are still looking at it */ @@ -725,7 +721,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { if (r > 0) { /* There are DNSSEC transactions pending now. Update the state accordingly. */ t->state = DNS_TRANSACTION_VALIDATING; - dns_transaction_stop(t); + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); return; } } @@ -869,7 +866,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { assert(t); - dns_transaction_stop(t); + dns_transaction_stop_timeout(t); if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); -- cgit v1.2.3-54-g00ecf From ee3d6aff9bd73c1b23e29d1fa1fa6f7a1ef0533b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 28 Dec 2015 00:30:56 +0100 Subject: resolved: use RRSIG expiry and original TTL for cache management When we verified a signature, fix up the RR's TTL to the original TTL mentioned in the signature, and store the signature expiry information in the RR, too. Then, use that when adding RRs to the cache. --- src/resolve/resolved-dns-cache.c | 28 ++++++++++++++++++++++++++-- src/resolve/resolved-dns-dnssec.c | 30 +++++++++++++++++++++++++++++- src/resolve/resolved-dns-rr.c | 1 + src/resolve/resolved-dns-rr.h | 1 + 4 files changed, 57 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 49d5090d36..413e1080d9 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -272,6 +272,30 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { return NULL; } +static usec_t calculate_until(DnsResourceRecord *rr, usec_t timestamp) { + usec_t ttl; + + assert(rr); + + ttl = rr->ttl * USEC_PER_SEC; + + if (ttl > CACHE_TTL_MAX_USEC) + ttl = CACHE_TTL_MAX_USEC; + + if (rr->expiry != USEC_INFINITY) { + usec_t left; + + /* Make use of the DNSSEC RRSIG expiry info, if we have it */ + + left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); + + if (ttl > left) + ttl = left; + } + + return timestamp + ttl; +} + static void dns_cache_item_update_positive( DnsCache *c, DnsCacheItem *i, @@ -302,7 +326,7 @@ static void dns_cache_item_update_positive( dns_resource_key_unref(i->key); i->key = dns_resource_key_ref(rr->key); - i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->until = calculate_until(rr, timestamp); i->authenticated = authenticated; i->shared_owner = shared_owner; @@ -383,7 +407,7 @@ static int dns_cache_put_positive( i->type = DNS_CACHE_POSITIVE; i->key = dns_resource_key_ref(rr->key); i->rr = dns_resource_record_ref(rr); - i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->until = calculate_until(rr, timestamp); i->authenticated = authenticated; i->shared_owner = shared_owner; i->owner_family = owner_family; diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 57093ad80c..94543fef3a 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -701,6 +701,30 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) 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, @@ -767,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; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 04d442bf03..d479de7125 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -339,6 +339,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { rr->n_ref = 1; rr->key = dns_resource_key_ref(key); + rr->expiry = USEC_INFINITY; return rr; } diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 95d260f7aa..fccc4dba6a 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -100,6 +100,7 @@ struct DnsResourceRecord { DnsResourceKey *key; char *to_string; uint32_t ttl; + usec_t expiry; /* RRSIG signature expiry */ bool unparseable:1; bool wire_format_canonical:1; void *wire_format; -- cgit v1.2.3-54-g00ecf From b211dc7e8368973d726af694e4165045bf0dfc52 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 28 Dec 2015 01:16:28 +0100 Subject: resolved: also use RRSIG expiry for negative caching This makes sure that we also honour the RRSIG expiry for negative caching. --- src/resolve/resolved-dns-cache.c | 45 ++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 413e1080d9..3193985542 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -272,28 +272,40 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { return NULL; } -static usec_t calculate_until(DnsResourceRecord *rr, usec_t timestamp) { - usec_t ttl; +static usec_t calculate_until(DnsResourceRecord *rr, usec_t timestamp, bool use_soa_minimum) { + uint32_t ttl; + usec_t u; assert(rr); - ttl = rr->ttl * USEC_PER_SEC; + ttl = rr->ttl; + if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { + /* If this is a SOA RR, and it is requested, clamp to + * the SOA's minimum field. This is used when we do + * negative caching, to determine the TTL for the + * negative caching entry. See RFC 2308, Section + * 5. */ - if (ttl > CACHE_TTL_MAX_USEC) - ttl = CACHE_TTL_MAX_USEC; + if (ttl > rr->soa.minimum) + ttl = rr->soa.minimum; + } + + u = ttl * USEC_PER_SEC; + if (u > CACHE_TTL_MAX_USEC) + u = CACHE_TTL_MAX_USEC; if (rr->expiry != USEC_INFINITY) { usec_t left; - /* Make use of the DNSSEC RRSIG expiry info, if we have it */ + /* Make use of the DNSSEC RRSIG expiry info, if we + * have it */ left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); - - if (ttl > left) - ttl = left; + if (u > left) + u = left; } - return timestamp + ttl; + return timestamp + u; } static void dns_cache_item_update_positive( @@ -326,7 +338,7 @@ static void dns_cache_item_update_positive( dns_resource_key_unref(i->key); i->key = dns_resource_key_ref(rr->key); - i->until = calculate_until(rr, timestamp); + i->until = calculate_until(rr, timestamp, false); i->authenticated = authenticated; i->shared_owner = shared_owner; @@ -407,7 +419,7 @@ static int dns_cache_put_positive( i->type = DNS_CACHE_POSITIVE; i->key = dns_resource_key_ref(rr->key); i->rr = dns_resource_record_ref(rr); - i->until = calculate_until(rr, timestamp); + i->until = calculate_until(rr, timestamp, false); i->authenticated = authenticated; i->shared_owner = shared_owner; i->owner_family = owner_family; @@ -436,7 +448,7 @@ static int dns_cache_put_negative( int rcode, bool authenticated, usec_t timestamp, - uint32_t soa_ttl, + DnsResourceRecord *soa, int owner_family, const union in_addr_union *owner_address) { @@ -446,6 +458,7 @@ static int dns_cache_put_negative( assert(c); assert(key); + assert(soa); assert(owner_address); /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly @@ -456,7 +469,7 @@ static int dns_cache_put_negative( if (dns_type_is_pseudo(key->type)) return 0; - if (soa_ttl <= 0) { + if (soa->soa.minimum <= 0 || soa->ttl <= 0) { if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) @@ -482,7 +495,7 @@ static int dns_cache_put_negative( return -ENOMEM; i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->until = calculate_until(soa, timestamp, true); i->authenticated = authenticated; i->owner_family = owner_family; i->owner_address = *owner_address; @@ -656,7 +669,7 @@ int dns_cache_put( rcode, authenticated, timestamp, - MIN(soa->soa.minimum, soa->ttl), + soa, owner_family, owner_address); if (r < 0) goto fail; -- cgit v1.2.3-54-g00ecf From 160fbda9079d8edb5f9c4f6c650f23d27578f469 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 28 Dec 2015 01:18:40 +0100 Subject: resolved: update DNSSEC TODO --- src/resolve/resolved-dns-dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 94543fef3a..ba8c83ebb9 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -40,7 +40,7 @@ * - 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? * -- cgit v1.2.3-54-g00ecf