resolved-dns-dnssec.c revision e0240c64b76ba8f0c9219feb23a5783f23100216
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc This file is part of systemd.
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc Copyright 2015 Lennart Poettering
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc systemd is free software; you can redistribute it and/or modify it
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc under the terms of the GNU Lesser General Public License as published by
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc the Free Software Foundation; either version 2.1 of the License, or
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc (at your option) any later version.
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc systemd is distributed in the hope that it will be useful, but
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc WITHOUT ANY WARRANTY; without even the implied warranty of
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc Lesser General Public License for more details.
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc You should have received a copy of the GNU Lesser General Public License
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc along with systemd; If not, see <http://www.gnu.org/licenses/>.
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh/* Open question:
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh * How does the DNSSEC canonical form of a hostname with a label
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh * containing a dot look like, the way DNS-SD does it?
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh * - Make trust anchor store read additional DS+DNSKEY data from disk
f595a68a3b8953a12aa778c2abd7642df8da8c3ayz * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc * - multi-label zone compatibility
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * - cname/dname compatibility
f595a68a3b8953a12aa778c2abd7642df8da8c3ayz * - per-interface DNSSEC setting
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * - fix TTL for cache entries to match RRSIG TTL
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh * - retry on failed validation?
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh * - DSA support?
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems * The DNSSEC Chain of trust:
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * DS RRs are protected like normal RRs
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * Example chain:
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowministatic void initialize_libgcrypt(void) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini const char *p;
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowministatic bool dnssec_algorithm_supported(int algorithm) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini /* The algorithm from RFC 4034, Appendix B. */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhanstatic int rr_compare(const void *a, const void *b) {
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems /* Let's order the RRs according to RFC 4034, Section 6.3 */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems assert((*y)->wire_format);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = memcmp((*x)->wire_format, (*y)->wire_format, m);
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini if ((*x)->wire_format_size < (*y)->wire_format_size)
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini else if ((*x)->wire_format_size > (*y)->wire_format_size)
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan const void *signature, size_t signature_size,
d62bc4badc1c1f1549c961cfb8b420e650e1272byz gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz "(sig-val (rsa (s %m)))",
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz "(data (flags pkcs1) (hash %s %b))",
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini "(public-key (rsa (n %m) (e %m)))",
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh else if (ge != 0) {
3fd94f8c011031b38162a1db3b554de4371c167fam log_debug("RSA signature check failed: %s", gpg_strerror(ge));
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan /* exponent is > 255 bytes long */
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan exponent = (uint8_t*) dnskey->dnskey.key + 3;
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan if (3 + exponent_size >= dnskey->dnskey.key_size)
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini /* exponent is <= 255 bytes long */
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan rrsig->rrsig.signature, rrsig->rrsig.signature_size,
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini const char *curve,
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan const void *signature_r, size_t signature_r_size,
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini if (ge != 0) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini if (ge != 0) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini "(sig-val (ecdsa (r %m) (s %m)))",
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz "(data (flags rfc6979) (hash %s %b))",
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz "(public-key (ecc (curve %s) (q %m)))",
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz else if (ge != 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz log_debug("ECDSA signature check failed: %s", gpg_strerror(ge));
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems gcry_sexp_release(public_key_sexp);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems const char *hash_algorithm,
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini const char *curve;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) {
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems q = alloca(key_size*2 + 1);
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
4045d94132614e1de2073685a6cdd4fbd86bec33sowministatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowministatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowministatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowministatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini /* Permit a certain amount of clock skew of 10% of the valid
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini * time range. This takes inspiration from unbound's
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini * resolver. */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems skew = (expiration - inception) / 10;
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan return realtime < inception || realtime > expiration;
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhanstatic int algorithm_to_gcrypt(uint8_t algorithm) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Translates a DNSSEC signature algorithm into a gcrypt digest identifier */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Verifies the the RRSet matching the specified "key" in "a",
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * using the signature "rrsig" and the key "dnskey". It's
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * assumed the RRSIG and DNSKEY match. */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (r > 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Collect all relevant RRs in a single array, so that we can look at the RRset */
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini if (r == 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* We need the wire format for ordering, and digest calculation */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (n <= 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Bring the RRs into canonical order */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh /* OK, the RRs are now in canonical order. Let's calculate the digest */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint16(md, rrsig->rrsig.type_covered);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint8(md, rrsig->rrsig.algorithm);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint8(md, rrsig->rrsig.labels);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint32(md, rrsig->rrsig.original_ttl);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint32(md, rrsig->rrsig.expiration);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint32(md, rrsig->rrsig.inception);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems md_add_uint16(md, rrsig->rrsig.key_tag);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini for (k = 0; k < n; k++) {
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc const char *suffix;
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r > 0) /* This is a wildcard! */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems gcry_md_algo_name(algorithm),
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byzint dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc /* Checks if the specified DNSKEY RR matches the key used for
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc * the signature in the specified RRSIG RR */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
d62bc4badc1c1f1549c961cfb8b420e650e1272byzint dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Make sure signer is a parent of the RRset */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r <= 0)
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (!a || a->n_rrs <= 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Iterate through each RRSIG RR. */
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini /* Is this an RRSIG RR that applies to RRs matching our key? */
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini if (r == 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Look for a matching key */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Is this a DNSKEY RR that matches they key of our RRSIG? */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r == 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Take the time here, if it isn't set yet, so
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini * that we do all validations with the same
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems if (realtime == USEC_INFINITY)
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems realtime = now(CLOCK_REALTIME);
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems /* Yay, we found a matching RRSIG with a matching
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems * DNSKEY, awesome. Now let's verify all entries of
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems * the RRSet against the RRSIG and DNSKEY
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems case DNSSEC_VALIDATED:
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems /* Yay, the RR has been validated,
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems * return immediately. */
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems *result = DNSSEC_VALIDATED;
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems /* If the signature is invalid, let's try another
d62bc4badc1c1f1549c961cfb8b420e650e1272byz key and/or signature. After all they
d62bc4badc1c1f1549c961cfb8b420e650e1272byz key_tags and stuff are not unique, and
d62bc4badc1c1f1549c961cfb8b420e650e1272byz might be shared by multiple keys. */
13a55820e952b584554bc6b9d4e9303052a2cf29ar /* If the key algorithm is
13a55820e952b584554bc6b9d4e9303052a2cf29ar unsupported, try another
13a55820e952b584554bc6b9d4e9303052a2cf29ar RRSIG/DNSKEY pair, but remember we
d62bc4badc1c1f1549c961cfb8b420e650e1272byz encountered this, so that we can
d62bc4badc1c1f1549c961cfb8b420e650e1272byz return a proper error when we
d62bc4badc1c1f1549c961cfb8b420e650e1272byz encounter nothing better. */
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems /* If the signature is expired, try
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini another one, but remember it, so
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini that we can return this */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz assert_not_reached("Unexpected DNSSEC validation result");
d62bc4badc1c1f1549c961cfb8b420e650e1272byzint dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r > 0)
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxcint dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Converts the specified hostname into DNSSEC canonicalized
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * form. */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz for (;;) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r == 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r > 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* DNSSEC validation is always done on the ASCII version of the label */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (k < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (k > 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* The DNSSEC canonical form is not clear on what to
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * do with dots appearing in labels, the way DNS-SD
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * does it. Refuse it for now. */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz for (i = 0; i < (size_t) r; i ++) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz c += r + 1;
308903890e892f9c21ee582e3a52fdd67e52870bartem if (c <= 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Not even a single label: this is the root domain name */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems case DNSSEC_DIGEST_SHA384:
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystemsint dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems gcry_md_hd_t md = NULL;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
d62bc4badc1c1f1549c961cfb8b420e650e1272byzint dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystemsint dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems gcry_md_hd_t md = NULL;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems if (nsec3->key->type != DNS_TYPE_NSEC3)
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems algorithm = digest_to_gcrypt(nsec3->nsec3.algorithm);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems gcry_md_write(md, wire_format, r);
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
d62bc4badc1c1f1549c961cfb8b420e650e1272byzstatic int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz const char *a, *b;
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems /* If a second NSEC3 RR is specified, also check if they are from the same zone. */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems if (nsec3 == rr) /* Shortcut */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems if (rr->key->class != nsec3->key->class)
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems a = DNS_RESOURCE_KEY_NAME(rr->key);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = dns_name_parent(&a); /* strip off hash */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems return dns_name_equal(a, b);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystemsstatic int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems _cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems const char *suffix, *p, *pp = NULL;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems DnsResourceRecord *rr, *suffix_rr;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems /* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems suffix = DNS_RESOURCE_KEY_NAME(key);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = nsec3_is_good(suffix_rr, flags, NULL);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems /* Strip one label from the front */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r == 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc for (;;) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems if (hashed_size == -EOPNOTSUPP) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems label = base32hexmem(hashed, hashed_size, false);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = nsec3_is_good(rr, flags, suffix_rr);
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (r == 0)
4045d94132614e1de2073685a6cdd4fbd86bec33sowmini if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc if (r > 0) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems a = flags & DNS_ANSWER_AUTHENTICATED;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* We didn't find the closest encloser with this name,
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * but let's remember this domain name, it might be
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * the next closer name */
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems /* Strip one label from the front */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r == 0)
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems *result = DNSSEC_NSEC_NO_RR;
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc /* We found a closest encloser in 'p'; next closer is 'pp' */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc /* Ensure that this data is from the delegated domain
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * (i.e. originates from the "lower" DNS server), and isn't
0ba2cbe97e0678a691742f98d2532caed0a2c4aaxc * just glue records (i.e. doesn't originate from the "upper"
d62bc4badc1c1f1549c961cfb8b420e650e1272byz * DNS server). */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
d62bc4badc1c1f1549c961cfb8b420e650e1272byz *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini if (r == 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems if (rr->nsec3.flags & 1)
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems *result = DNSSEC_NSEC_NXDOMAIN;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems *authenticated = a && (flags & DNS_ANSWER_AUTHENTICATED);
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems *result = DNSSEC_NSEC_NO_RR;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystemsint dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystems bool have_nsec3 = false;
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
bcb5c89da22515e2ccf139578bad3caebcd716adSowmini Varadhan r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r > 0) {
d62bc4badc1c1f1549c961cfb8b420e650e1272byz *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
d62bc4badc1c1f1549c961cfb8b420e650e1272byz r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r < 0)
d62bc4badc1c1f1549c961cfb8b420e650e1272byz if (r > 0) {
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh /* OK, this was not sufficient. Let's see if NSEC3 can help. */
f4b3ec61df05330d25f55a36b975b4d7519fdeb1dh return dnssec_test_nsec3(answer, key, result, authenticated);
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems /* No approproate NSEC RR found, report this. */
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems *result = DNSSEC_NSEC_NO_RR;
3bc21d0a9c7b31b1132c254e389a4114c23bcf00Aruna Ramakrishna - Sun Microsystemsstatic const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems [DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
d62bc4badc1c1f1549c961cfb8b420e650e1272byzstatic const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
e7801d59e8ceda0cde8ebdfdddd7582ee2ea96efsowmini [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems [DNSSEC_NO_SIGNATURE] = "no-signature",
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems [DNSSEC_MISSING_KEY] = "missing-key",
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
afdda45f890ee5dfc86e5131a30b11b354d51633Vasumathi Sundaram - Sun Microsystems [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
6b9e797c2ea518518cb2b57895991d8bdaa167fesowmini [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",