resolved-dns-dnssec.c revision b9282bc12840aff500a334836226f6b8df24926d
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/***
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering This file is part of systemd.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering Copyright 2015 Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering systemd is free software; you can redistribute it and/or modify it
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering under the terms of the GNU Lesser General Public License as published by
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering (at your option) any later version.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering systemd is distributed in the hope that it will be useful, but
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering Lesser General Public License for more details.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering You should have received a copy of the GNU Lesser General Public License
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering***/
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering#include <gcrypt.h>
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering#include "alloc-util.h"
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering#include "dns-domain.h"
50f1e641a93cacfc693b0c3d300bee5df0c8c460Tom Gundersen#include "hexdecoct.h"
71d35b6b5563817dfbe757ab9e3b9f018b2db491Thomas Hindoe Paaboel Andersen#include "resolved-dns-dnssec.h"
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering#include "resolved-dns-packet.h"
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering#include "string-table.h"
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/* Open question:
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering *
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering * containing a dot look like, the way DNS-SD does it?
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering *
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * TODO:
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering *
28b8191e2f391f043d380d47eb79ed9ff66f14bdLennart Poettering * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
28b8191e2f391f043d380d47eb79ed9ff66f14bdLennart Poettering * - multi-label zone compatibility
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - cname/dname compatibility
23502de3b0891455c8ce499a9eb61b69d060a829Daniel Mack * - nxdomain on qname
23502de3b0891455c8ce499a9eb61b69d060a829Daniel Mack * - bus calls to override DNSEC setting per interface
23502de3b0891455c8ce499a9eb61b69d060a829Daniel Mack * - log all DNSSEC downgrades
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - enable by default
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering *
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * */
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering#define VERIFY_RRS_MAX 256
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering#define MAX_KEY_SIZE (32*1024)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
e0240c64b76ba8f0c9219feb23a5783f23100216Lennart Poettering#define NSEC3_ITERATIONS_MAX 2500
e0240c64b76ba8f0c9219feb23a5783f23100216Lennart Poettering
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering/*
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * The DNSSEC Chain of trust:
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering *
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * DS RRs are protected like normal RRs
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering *
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * Example chain:
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering */
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poetteringstatic void initialize_libgcrypt(void) {
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering const char *p;
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering return;
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering p = gcry_check_version("1.4.5");
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering assert(p);
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering gcry_control(GCRYCTL_DISABLE_SECMEM);
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering}
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering const uint8_t *p;
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering uint32_t sum, f;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering size_t i;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering assert(dnskey);
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering f = (uint32_t) dnskey->dnskey.flags;
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering if (mask_revoke)
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering f &= ~DNSKEY_FLAG_REVOKE;
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering p = dnskey->dnskey.key;
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering sum += (sum >> 16) & UINT32_C(0xFFFF);
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering return sum & UINT32_C(0xFFFF);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering}
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poetteringstatic int rr_compare(const void *a, const void *b) {
7b50eb2efa122200e39646c19a29abab302f7d24Lennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering size_t m;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering int r;
ee3d6aff9bd73c1b23e29d1fa1fa6f7a1ef0533bLennart Poettering
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering assert(x);
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering assert(*x);
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering assert((*x)->wire_format);
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering assert(y);
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering assert(*y);
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering assert((*y)->wire_format);
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering if (r != 0)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering return r;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
f5430a3ef308f3a102899fcaf7fbece757082f2aLennart Poettering return -1;
d75acfb059ece4512278b8820a9103664996f1e5Lennart Poettering else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering return 1;
9c92ce6d67f88beb31dd6555d12ae3f632218a39Lennart Poettering
9c92ce6d67f88beb31dd6555d12ae3f632218a39Lennart Poettering return 0;
9c92ce6d67f88beb31dd6555d12ae3f632218a39Lennart Poettering}
9c92ce6d67f88beb31dd6555d12ae3f632218a39Lennart Poettering
9c92ce6d67f88beb31dd6555d12ae3f632218a39Lennart Poetteringstatic int dnssec_rsa_verify_raw(
9c92ce6d67f88beb31dd6555d12ae3f632218a39Lennart Poettering const char *hash_algorithm,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *signature, size_t signature_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *data, size_t data_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *exponent, size_t exponent_size,
8ac4e9e1e54397f6d1745c2a7a806132418c7da2Lennart Poettering const void *modulus, size_t modulus_size) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_mpi_t n = NULL, e = NULL, s = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_error_t ge;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering int r;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
2e276efc7b0398a3086629a52970bdd4ab7252f9Zbigniew Jędrzejewski-Szmek assert(hash_algorithm);
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering
c0eb11cfd016381fe02875a4ef29c1ade00c94e7Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (ge != 0) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = -EIO;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering goto finish;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering }
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (ge != 0) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = -EIO;
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering goto finish;
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering }
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering if (ge != 0) {
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering r = -EIO;
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering goto finish;
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering }
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering ge = gcry_sexp_build(&signature_sexp,
946c70944ebdf428ffeb9991a7449edbd4011461Zbigniew Jędrzejewski-Szmek NULL,
946c70944ebdf428ffeb9991a7449edbd4011461Zbigniew Jędrzejewski-Szmek "(sig-val (rsa (s %m)))",
946c70944ebdf428ffeb9991a7449edbd4011461Zbigniew Jędrzejewski-Szmek s);
946c70944ebdf428ffeb9991a7449edbd4011461Zbigniew Jędrzejewski-Szmek
946c70944ebdf428ffeb9991a7449edbd4011461Zbigniew Jędrzejewski-Szmek if (ge != 0) {
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek r = -EIO;
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering goto finish;
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek }
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek ge = gcry_sexp_build(&data_sexp,
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek NULL,
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek "(data (flags pkcs1) (hash %s %b))",
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek hash_algorithm,
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek (int) data_size,
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek data);
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek if (ge != 0) {
42cc2eebb01056beb7acd3ecfe8e533558237f84Lennart Poettering r = -EIO;
549c1a2564b56f2bb38f1203d59c747ea15817f3Tom Gundersen goto finish;
42cc2eebb01056beb7acd3ecfe8e533558237f84Lennart Poettering }
42cc2eebb01056beb7acd3ecfe8e533558237f84Lennart Poettering
42cc2eebb01056beb7acd3ecfe8e533558237f84Lennart Poettering ge = gcry_sexp_build(&public_key_sexp,
549c1a2564b56f2bb38f1203d59c747ea15817f3Tom Gundersen NULL,
549c1a2564b56f2bb38f1203d59c747ea15817f3Tom Gundersen "(public-key (rsa (n %m) (e %m)))",
42cc2eebb01056beb7acd3ecfe8e533558237f84Lennart Poettering n,
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek e);
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek if (ge != 0) {
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek r = -EIO;
f91dc2400dc33e9a0745ecaaef7489af116dca38Lennart Poettering goto finish;
f91dc2400dc33e9a0745ecaaef7489af116dca38Lennart Poettering }
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek r = 0;
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek else if (ge != 0) {
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek log_debug("RSA signature check failed: %s", gpg_strerror(ge));
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek r = -EIO;
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek } else
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek r = 1;
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmekfinish:
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek if (e)
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek gcry_mpi_release(e);
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek if (n)
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek gcry_mpi_release(n);
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek if (s)
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek gcry_mpi_release(s);
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek
50f1e641a93cacfc693b0c3d300bee5df0c8c460Tom Gundersen if (public_key_sexp)
9ead3519c54b6d1b79b35541873b5cf7c8b3a7d3Lennart Poettering gcry_sexp_release(public_key_sexp);
50f1e641a93cacfc693b0c3d300bee5df0c8c460Tom Gundersen if (signature_sexp)
50f1e641a93cacfc693b0c3d300bee5df0c8c460Tom Gundersen gcry_sexp_release(signature_sexp);
50f1e641a93cacfc693b0c3d300bee5df0c8c460Tom Gundersen if (data_sexp)
50f1e641a93cacfc693b0c3d300bee5df0c8c460Tom Gundersen gcry_sexp_release(data_sexp);
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering return r;
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering}
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poetteringstatic int dnssec_rsa_verify(
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering const char *hash_algorithm,
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering const void *hash, size_t hash_size,
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering DnsResourceRecord *rrsig,
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering DnsResourceRecord *dnskey) {
6af47493de0ef2b66d4c3fbcdd4a2e12fec4bfbaLennart Poettering
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen size_t exponent_size, modulus_size;
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen void *exponent, *modulus;
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen assert(hash_algorithm);
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen assert(hash);
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen assert(hash_size > 0);
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen assert(rrsig);
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen assert(dnskey);
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen
5d45a8808431987c370706d365fb0cc95cf03d52Tom Gundersen if (*(uint8_t*) dnskey->dnskey.key == 0) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* exponent is > 255 bytes long */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 3;
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering exponent_size =
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) |
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]);
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering if (exponent_size < 256)
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering return -EINVAL;
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering if (3 + exponent_size >= dnskey->dnskey.key_size)
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering return -EINVAL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering } else {
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering /* exponent is <= 255 bytes long */
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 1;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering if (exponent_size <= 0)
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering return -EINVAL;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering if (1 + exponent_size >= dnskey->dnskey.key_size)
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering return -EINVAL;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering }
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering return dnssec_rsa_verify_raw(
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering hash_algorithm,
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering rrsig->rrsig.signature, rrsig->rrsig.signature_size,
36d9205d669bcdcb04fa730d1f3549a9fc9a9001Tom Gundersen hash, hash_size,
801ad6a6a9cd8fbd58b9f9c27f20dbb3c87d47ddLennart Poettering exponent, exponent_size,
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering modulus, modulus_size);
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering}
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering
28b9b7640603f88cb49f95609331fa5072715f15Lennart Poetteringstatic int dnssec_ecdsa_verify_raw(
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering const char *hash_algorithm,
105e151299dc1208855380be2b22d0db2d66ebc6Lennart Poettering const char *curve,
5d27351f8546530cf779847b0b04b0172c09f9d0Tom Gundersen const void *signature_r, size_t signature_r_size,
547973dea7abd6c124ff6c79fe2bbe322a7314aeLennart Poettering const void *signature_s, size_t signature_s_size,
2d4c5cbc0ed3ccb09dc086a040088b454c22c644Lennart Poettering const void *data, size_t data_size,
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering const void *key, size_t key_size) {
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering
7778dffff3d8bd7438fe19a248c16203668324c9Daniel Mack gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
7778dffff3d8bd7438fe19a248c16203668324c9Daniel Mack gcry_mpi_t q = NULL, r = NULL, s = NULL;
7778dffff3d8bd7438fe19a248c16203668324c9Daniel Mack gcry_error_t ge;
7778dffff3d8bd7438fe19a248c16203668324c9Daniel Mack int k;
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering
8bf52d3d17d364438191077d0750b8b80b5dc53aLennart Poettering assert(hash_algorithm);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering if (ge != 0) {
78c6a153c47f8d597c827bdcaf8c4e42ac87f738Lennart Poettering k = -EIO;
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering goto finish;
7b50eb2efa122200e39646c19a29abab302f7d24Lennart Poettering }
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering if (ge != 0) {
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering k = -EIO;
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering goto finish;
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering }
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering
ab481675f98d3d3f12e7e48ba6d2159123b9c7bfLennart Poettering ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering if (ge != 0) {
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering k = -EIO;
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering goto finish;
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering }
d5099efc47d4e6ac60816b5381a5f607ab03f06eMichal Schmidt
c9c72065419e6595131a6fe1e663e2184a843f7cLennart Poettering ge = gcry_sexp_build(&signature_sexp,
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering NULL,
8e54f5d90a6b9dd1ff672fb97ea98de66c49e332Lennart Poettering "(sig-val (ecdsa (r %m) (s %m)))",
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering r,
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering s);
8e54f5d90a6b9dd1ff672fb97ea98de66c49e332Lennart Poettering if (ge != 0) {
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering 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));
}
static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
v = htobe16(v);
gcry_md_write(md, &v, sizeof(v));
}
static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
v = htobe32(v);
gcry_md_write(md, &v, sizeof(v));
}
static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) {
int n_key_labels, n_signer_labels;
const char *name;
int r;
/* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and
* .n_skip_labels_signer fields so that we can use them later on. */
assert(rrsig);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
/* Check if this RRSIG RR is already prepared */
if (rrsig->n_skip_labels_source != (unsigned) -1)
return 0;
if (rrsig->rrsig.inception > rrsig->rrsig.expiration)
return -EINVAL;
name = DNS_RESOURCE_KEY_NAME(rrsig->key);
n_key_labels = dns_name_count_labels(name);
if (n_key_labels < 0)
return n_key_labels;
if (rrsig->rrsig.labels > n_key_labels)
return -EINVAL;
n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer);
if (n_signer_labels < 0)
return n_signer_labels;
if (n_signer_labels > rrsig->rrsig.labels)
return -EINVAL;
r = dns_name_skip(name, n_key_labels - n_signer_labels, &name);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
/* Check if the signer is really a suffix of us */
r = dns_name_equal(name, rrsig->rrsig.signer);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels;
rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels;
return 0;
}
static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
usec_t expiration, inception, skew;
assert(rrsig);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
if (realtime == USEC_INFINITY)
realtime = now(CLOCK_REALTIME);
expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
inception = rrsig->rrsig.inception * USEC_PER_SEC;
/* Consider inverted validity intervals as expired */
if (inception > expiration)
return true;
/* Permit a certain amount of clock skew of 10% of the valid
* time range. This takes inspiration from unbound's
* resolver. */
skew = (expiration - inception) / 10;
if (skew > SKEW_MAX)
skew = SKEW_MAX;
if (inception < skew)
inception = 0;
else
inception -= skew;
if (expiration + skew < expiration)
expiration = USEC_INFINITY;
else
expiration += skew;
return realtime < inception || realtime > expiration;
}
static int algorithm_to_gcrypt_md(uint8_t algorithm) {
/* Translates a DNSSEC signature algorithm into a gcrypt
* digest identifier.
*
* Note that we implement all algorithms listed as "Must
* implement" and "Recommended to Implement" in RFC6944. We
* don't implement any algorithms that are listed as
* "Optional" or "Must Not Implement". Specifically, we do not
* implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and
* GOST-ECC. */
switch (algorithm) {
case DNSSEC_ALGORITHM_RSASHA1:
case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
return GCRY_MD_SHA1;
case DNSSEC_ALGORITHM_RSASHA256:
case DNSSEC_ALGORITHM_ECDSAP256SHA256:
return GCRY_MD_SHA256;
case DNSSEC_ALGORITHM_ECDSAP384SHA384:
return GCRY_MD_SHA384;
case DNSSEC_ALGORITHM_RSASHA512:
return GCRY_MD_SHA512;
default:
return -EOPNOTSUPP;
}
}
static void dnssec_fix_rrset_ttl(
DnsResourceRecord *list[],
unsigned n,
DnsResourceRecord *rrsig,
usec_t realtime) {
unsigned k;
assert(list);
assert(n > 0);
assert(rrsig);
for (k = 0; k < n; k++) {
DnsResourceRecord *rr = list[k];
/* 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;
/* Copy over information about the signer and wildcard source of synthesis */
rr->n_skip_labels_source = rrsig->n_skip_labels_source;
rr->n_skip_labels_signer = rrsig->n_skip_labels_signer;
}
rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
}
int dnssec_verify_rrset(
DnsAnswer *a,
const DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
usec_t realtime,
DnssecResult *result) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
DnsResourceRecord **list, *rr;
const char *source, *name;
gcry_md_hd_t md = NULL;
int r, md_algorithm;
size_t k, n = 0;
size_t hash_size;
void *hash;
bool wildcard;
assert(key);
assert(rrsig);
assert(dnskey);
assert(result);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
/* Verifies the the RRSet matching the specified "key" in "a",
* using the signature "rrsig" and the key "dnskey". It's
* assumed the RRSIG and DNSKEY match. */
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;
r = dnssec_rrsig_prepare(rrsig);
if (r == -EINVAL) {
*result = DNSSEC_INVALID;
return r;
}
if (r < 0)
return r;
r = dnssec_rrsig_expired(rrsig, realtime);
if (r < 0)
return r;
if (r > 0) {
*result = DNSSEC_SIGNATURE_EXPIRED;
return 0;
}
name = DNS_RESOURCE_KEY_NAME(key);
/* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */
if (dns_type_apex_only(rrsig->rrsig.type_covered)) {
r = dns_name_equal(rrsig->rrsig.signer, name);
if (r < 0)
return r;
if (r == 0) {
*result = DNSSEC_INVALID;
return 0;
}
}
/* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */
if (rrsig->rrsig.type_covered == DNS_TYPE_DS) {
r = dns_name_equal(rrsig->rrsig.signer, name);
if (r < 0)
return r;
if (r > 0) {
*result = DNSSEC_INVALID;
return 0;
}
}
/* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */
r = dns_name_suffix(name, rrsig->rrsig.labels, &source);
if (r < 0)
return r;
if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) {
/* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */
*result = DNSSEC_INVALID;
return 0;
}
if (r == 1) {
/* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really
* synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */
r = dns_name_startswith(name, "*");
if (r < 0)
return r;
if (r > 0)
source = name;
wildcard = r == 0;
} else
wildcard = r > 0;
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
list = newa(DnsResourceRecord *, dns_answer_size(a));
DNS_ANSWER_FOREACH(rr, a) {
r = dns_resource_key_equal(key, rr->key);
if (r < 0)
return r;
if (r == 0)
continue;
/* We need the wire format for ordering, and digest calculation */
r = dns_resource_record_to_wire_format(rr, true);
if (r < 0)
return r;
list[n++] = rr;
if (n > VERIFY_RRS_MAX)
return -E2BIG;
}
if (n <= 0)
return -ENODATA;
/* Bring the RRs into canonical order */
qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
/* OK, the RRs are now in canonical order. Let's calculate the digest */
initialize_libgcrypt();
hash_size = gcry_md_get_algo_dlen(md_algorithm);
assert(hash_size > 0);
gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
md_add_uint16(md, rrsig->rrsig.type_covered);
md_add_uint8(md, rrsig->rrsig.algorithm);
md_add_uint8(md, rrsig->rrsig.labels);
md_add_uint32(md, rrsig->rrsig.original_ttl);
md_add_uint32(md, rrsig->rrsig.expiration);
md_add_uint32(md, rrsig->rrsig.inception);
md_add_uint16(md, rrsig->rrsig.key_tag);
r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
/* Convert the source of synthesis into wire format */
r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
for (k = 0; k < n; k++) {
size_t l;
rr = list[k];
/* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */
if (wildcard)
gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
gcry_md_write(md, wire_format_name, r);
md_add_uint16(md, rr->key->type);
md_add_uint16(md, rr->key->class);
md_add_uint32(md, rrsig->rrsig.original_ttl);
l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
assert(l <= 0xFFFF);
md_add_uint16(md, (uint16_t) l);
gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
}
hash = gcry_md_read(md, 0);
if (!hash) {
r = -EIO;
goto finish;
}
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(md_algorithm),
hash, hash_size,
rrsig,
dnskey);
break;
case DNSSEC_ALGORITHM_ECDSAP256SHA256:
case DNSSEC_ALGORITHM_ECDSAP384SHA384:
r = dnssec_ecdsa_verify(
gcry_md_algo_name(md_algorithm),
rrsig->rrsig.algorithm,
hash, hash_size,
rrsig,
dnskey);
break;
}
if (r < 0)
goto finish;
/* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */
if (r > 0)
dnssec_fix_rrset_ttl(list, n, rrsig, realtime);
if (r == 0)
*result = DNSSEC_INVALID;
else if (wildcard)
*result = DNSSEC_VALIDATED_WILDCARD;
else
*result = DNSSEC_VALIDATED;
r = 0;
finish:
gcry_md_close(md);
return r;
}
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
assert(rrsig);
assert(dnskey);
/* Checks if the specified DNSKEY RR matches the key used for
* the signature in the specified RRSIG RR */
if (rrsig->key->type != DNS_TYPE_RRSIG)
return -EINVAL;
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return 0;
if (dnskey->key->class != rrsig->key->class)
return 0;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return 0;
if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
return 0;
if (dnskey->dnskey.protocol != 3)
return 0;
if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
return 0;
if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
}
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
assert(key);
assert(rrsig);
/* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
if (rrsig->key->type != DNS_TYPE_RRSIG)
return 0;
if (rrsig->key->class != key->class)
return 0;
if (rrsig->rrsig.type_covered != key->type)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
int dnssec_verify_rrset_search(
DnsAnswer *a,
const DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
usec_t realtime,
DnssecResult *result,
DnsResourceRecord **ret_rrsig) {
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
DnsResourceRecord *rrsig;
int r;
assert(key);
assert(result);
/* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
/* Iterate through each RRSIG RR. */
DNS_ANSWER_FOREACH(rrsig, a) {
DnsResourceRecord *dnskey;
DnsAnswerFlags flags;
/* Is this an RRSIG RR that applies to RRs matching our key? */
r = dnssec_key_match_rrsig(key, rrsig);
if (r < 0)
return r;
if (r == 0)
continue;
found_rrsig = true;
/* Look for a matching key */
DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
DnssecResult one_result;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
if (r < 0)
return r;
if (r == 0)
continue;
/* Take the time here, if it isn't set yet, so
* that we do all validations with the same
* time. */
if (realtime == USEC_INFINITY)
realtime = now(CLOCK_REALTIME);
/* Yay, we found a matching RRSIG with a matching
* DNSKEY, awesome. Now let's verify all entries of
* the RRSet against the RRSIG and DNSKEY
* combination. */
r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
if (r < 0)
return r;
switch (one_result) {
case DNSSEC_VALIDATED:
case DNSSEC_VALIDATED_WILDCARD:
/* Yay, the RR has been validated,
* return immediately, but fix up the expiry */
if (ret_rrsig)
*ret_rrsig = rrsig;
*result = one_result;
return 0;
case DNSSEC_INVALID:
/* If the signature is invalid, let's try another
key and/or signature. After all they
key_tags and stuff are not unique, and
might be shared by multiple keys. */
found_invalid = true;
continue;
case DNSSEC_UNSUPPORTED_ALGORITHM:
/* If the key algorithm is
unsupported, try another
RRSIG/DNSKEY pair, but remember we
encountered this, so that we can
return a proper error when we
encounter nothing better. */
found_unsupported_algorithm = true;
continue;
case DNSSEC_SIGNATURE_EXPIRED:
/* If the signature is expired, try
another one, but remember it, so
that we can return this */
found_expired_rrsig = true;
continue;
default:
assert_not_reached("Unexpected DNSSEC validation result");
}
}
}
if (found_expired_rrsig)
*result = DNSSEC_SIGNATURE_EXPIRED;
else if (found_unsupported_algorithm)
*result = DNSSEC_UNSUPPORTED_ALGORITHM;
else if (found_invalid)
*result = DNSSEC_INVALID;
else if (found_rrsig)
*result = DNSSEC_MISSING_KEY;
else
*result = DNSSEC_NO_SIGNATURE;
if (ret_rrsig)
*ret_rrsig = NULL;
return 0;
}
int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
DnsResourceRecord *rr;
int r;
/* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
DNS_ANSWER_FOREACH(rr, a) {
r = dnssec_key_match_rrsig(key, rr);
if (r < 0)
return r;
if (r > 0)
return 1;
}
return 0;
}
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
size_t c = 0;
int r;
/* Converts the specified hostname into DNSSEC canonicalized
* form. */
if (buffer_max < 2)
return -ENOBUFS;
for (;;) {
r = dns_label_unescape(&n, buffer, buffer_max);
if (r < 0)
return r;
if (r == 0)
break;
if (r > 0) {
int k;
/* DNSSEC validation is always done on the ASCII version of the label */
k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
if (k < 0)
return k;
if (k > 0)
r = k;
}
if (buffer_max < (size_t) r + 2)
return -ENOBUFS;
/* The DNSSEC canonical form is not clear on what to
* do with dots appearing in labels, the way DNS-SD
* does it. Refuse it for now. */
if (memchr(buffer, '.', r))
return -EINVAL;
ascii_strlower_n(buffer, (size_t) r);
buffer[r] = '.';
buffer += r + 1;
c += r + 1;
buffer_max -= r + 1;
}
if (c <= 0) {
/* Not even a single label: this is the root domain name */
assert(buffer_max > 2);
buffer[0] = '.';
buffer[1] = 0;
return 1;
}
return (int) c;
}
static int digest_to_gcrypt_md(uint8_t algorithm) {
/* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
switch (algorithm) {
case DNSSEC_DIGEST_SHA1:
return GCRY_MD_SHA1;
case DNSSEC_DIGEST_SHA256:
return GCRY_MD_SHA256;
case DNSSEC_DIGEST_SHA384:
return GCRY_MD_SHA384;
default:
return -EOPNOTSUPP;
}
}
int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
int md_algorithm, r;
void *result;
assert(dnskey);
assert(ds);
/* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return -EINVAL;
if (ds->key->type != DNS_TYPE_DS)
return -EINVAL;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return -EKEYREJECTED;
if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
return -EKEYREJECTED;
if (dnskey->dnskey.protocol != 3)
return -EKEYREJECTED;
if (dnskey->dnskey.algorithm != ds->ds.algorithm)
return 0;
if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag)
return 0;
initialize_libgcrypt();
md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type);
if (md_algorithm < 0)
return md_algorithm;
hash_size = gcry_md_get_algo_dlen(md_algorithm);
assert(hash_size > 0);
if (ds->ds.digest_size != hash_size)
return 0;
r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
if (r < 0)
return r;
gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
gcry_md_write(md, owner_name, r);
if (mask_revoke)
md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE);
else
md_add_uint16(md, dnskey->dnskey.flags);
md_add_uint8(md, dnskey->dnskey.protocol);
md_add_uint8(md, dnskey->dnskey.algorithm);
gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
result = gcry_md_read(md, 0);
if (!result) {
r = -EIO;
goto finish;
}
r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
finish:
gcry_md_close(md);
return r;
}
int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
DnsResourceRecord *ds;
DnsAnswerFlags flags;
int r;
assert(dnskey);
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return 0;
DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
if (ds->key->type != DNS_TYPE_DS)
continue;
if (ds->key->class != dnskey->key->class)
continue;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
if (r < 0)
return r;
if (r == 0)
continue;
r = dnssec_verify_dnskey_by_ds(dnskey, ds, false);
if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP))
return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */
if (r < 0)
return r;
if (r > 0)
return 1;
}
return 0;
}
static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) {
/* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */
switch (algorithm) {
case NSEC3_ALGORITHM_SHA1:
return GCRY_MD_SHA1;
default:
return -EOPNOTSUPP;
}
}
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;
if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) {
log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3));
return -EOPNOTSUPP;
}
algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm);
if (algorithm < 0)
return algorithm;
initialize_libgcrypt();
hash_size = gcry_md_get_algo_dlen(algorithm);
assert(hash_size > 0);
if (nsec3->nsec3.next_hashed_name_size != hash_size)
return -EINVAL;
r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
if (r < 0)
return r;
gcry_md_open(&md, algorithm, 0);
if (!md)
return -EIO;
gcry_md_write(md, wire_format, r);
gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
result = gcry_md_read(md, 0);
if (!result) {
r = -EIO;
goto finish;
}
for (k = 0; k < nsec3->nsec3.iterations; k++) {
uint8_t tmp[hash_size];
memcpy(tmp, result, hash_size);
gcry_md_reset(md);
gcry_md_write(md, tmp, hash_size);
gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
result = gcry_md_read(md, 0);
if (!result) {
r = -EIO;
goto finish;
}
}
memcpy(ret, result, hash_size);
r = (int) hash_size;
finish:
gcry_md_close(md);
return r;
}
static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) {
const char *a, *b;
int r;
assert(rr);
if (rr->key->type != DNS_TYPE_NSEC3)
return 0;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
return 0;
/* Ignore NSEC3 RRs whose algorithm we don't know */
if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0)
return 0;
/* Ignore NSEC3 RRs with an excessive number of required iterations */
if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX)
return 0;
/* Ignore NSEC3 RRs generated from wildcards */
if (rr->n_skip_labels_source != 0)
return 0;
/* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */
if (rr->n_skip_labels_signer != 1)
return 0;
if (!nsec3)
return 1;
/* If a second NSEC3 RR is specified, also check if they are from the same zone. */
if (nsec3 == rr) /* Shortcut */
return 1;
if (rr->key->class != nsec3->key->class)
return 0;
if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
return 0;
if (rr->nsec3.iterations != nsec3->nsec3.iterations)
return 0;
if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
return 0;
if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
return 0;
a = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_parent(&a); /* strip off hash */
if (r < 0)
return r;
if (r == 0)
return 0;
b = DNS_RESOURCE_KEY_NAME(nsec3->key);
r = dns_name_parent(&b); /* strip off hash */
if (r < 0)
return r;
if (r == 0)
return 0;
/* Make sure both have the same parent */
return dns_name_equal(a, b);
}
static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) {
_cleanup_free_ char *l = NULL;
char *j;
assert(hashed);
assert(hashed_size > 0);
assert(zone);
assert(ret);
l = base32hexmem(hashed, hashed_size, false);
if (!l)
return -ENOMEM;
j = strjoin(l, ".", zone, NULL);
if (!j)
return -ENOMEM;
*ret = j;
return (int) hashed_size;
}
static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
int hashed_size;
assert(nsec3);
assert(domain);
assert(zone);
assert(ret);
hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed);
if (hashed_size < 0)
return hashed_size;
return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret);
}
/* See RFC 5155, Section 8
* First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest
* enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there
* is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that
* matches the wildcard domain.
*
* Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or
* that there is no proof either way. The latter is the case if a the proof of non-existence of a given
* name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records
* to conclude anything we indicate this by returning NO_RR. */
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
_cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL;
const char *zone, *p, *pp = NULL, *wildcard;
DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL;
DnsAnswerFlags flags;
int hashed_size, r;
bool a, no_closer = false, no_wildcard = false, optout = false;
assert(key);
assert(result);
/* First step, find the zone name and the NSEC3 parameters of the zone.
* it is sufficient to look for the longest common suffix we find with
* any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3
* records from a given zone in a response must use the same
* parameters. */
zone = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) {
r = nsec3_is_good(zone_rr, NULL);
if (r < 0)
return r;
if (r == 0)
continue;
r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone);
if (r < 0)
return r;
if (r > 0)
goto found_zone;
}
/* Strip one label from the front */
r = dns_name_parent(&zone);
if (r < 0)
return r;
if (r == 0)
break;
}
*result = DNSSEC_NSEC_NO_RR;
return 0;
found_zone:
/* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
p = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
_cleanup_free_ char *hashed_domain = NULL;
hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain);
if (hashed_size == -EOPNOTSUPP) {
*result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
return 0;
}
if (hashed_size < 0)
return hashed_size;
DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) {
r = nsec3_is_good(enclosure_rr, zone_rr);
if (r < 0)
return r;
if (r == 0)
continue;
if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
continue;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain);
if (r < 0)
return r;
if (r > 0) {
a = flags & DNS_ANSWER_AUTHENTICATED;
goto found_closest_encloser;
}
}
/* We didn't find the closest encloser with this name,
* but let's remember this domain name, it might be
* the next closer name */
pp = p;
/* Strip one label from the front */
r = dns_name_parent(&p);
if (r < 0)
return r;
if (r == 0)
break;
}
*result = DNSSEC_NSEC_NO_RR;
return 0;
found_closest_encloser:
/* We found a closest encloser in 'p'; next closer is 'pp' */
/* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME))
return -EBADMSG;
/* Ensure that this data is from the delegated domain
* (i.e. originates from the "lower" DNS server), and isn't
* just glue records (i.e. doesn't originate from the "upper"
* DNS server). */
if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) &&
!bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA))
return -EBADMSG;
if (!pp) {
/* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
if (bitmap_isset(enclosure_rr->nsec3.types, key->type))
*result = DNSSEC_NSEC_FOUND;
else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME))
*result = DNSSEC_NSEC_CNAME;
else
*result = DNSSEC_NSEC_NODATA;
if (authenticated)
*authenticated = a;
if (ttl)
*ttl = enclosure_rr->ttl;
return 0;
}
/* Prove that there is no next closer and whether or not there is a wildcard domain. */
wildcard = strjoina("*.", p);
r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain);
if (r < 0)
return r;
if (r != hashed_size)
return -EBADMSG;
r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain);
if (r < 0)
return r;
if (r != hashed_size)
return -EBADMSG;
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
_cleanup_free_ char *next_hashed_domain = NULL;
r = nsec3_is_good(rr, zone_rr);
if (r < 0)
return r;
if (r == 0)
continue;
r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain);
if (r < 0)
return r;
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
if (r < 0)
return r;
if (r > 0) {
if (rr->nsec3.flags & 1)
optout = true;
a = a && (flags & DNS_ANSWER_AUTHENTICATED);
no_closer = true;
}
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain);
if (r < 0)
return r;
if (r > 0) {
a = a && (flags & DNS_ANSWER_AUTHENTICATED);
wildcard_rr = rr;
}
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain);
if (r < 0)
return r;
if (r > 0) {
if (rr->nsec3.flags & 1)
/* This only makes sense if we have a wildcard delegation, which is
* very unlikely, see RFC 4592, Section 4.2, but we cannot rely on
* this not happening, so hence cannot simply conclude NXDOMAIN as
* we would wish */
optout = true;
a = a && (flags & DNS_ANSWER_AUTHENTICATED);
no_wildcard = true;
}
}
if (wildcard_rr && no_wildcard)
return -EBADMSG;
if (!no_closer) {
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
if (wildcard_rr) {
/* A wildcard exists that matches our query. */
if (optout)
/* This is not specified in any RFC to the best of my knowledge, but
* if the next closer enclosure is covered by an opt-out NSEC3 RR
* it means that we cannot prove that the source of synthesis is
* correct, as there may be a closer match. */
*result = DNSSEC_NSEC_OPTOUT;
else if (bitmap_isset(wildcard_rr->nsec3.types, key->type))
*result = DNSSEC_NSEC_FOUND;
else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME))
*result = DNSSEC_NSEC_CNAME;
else
*result = DNSSEC_NSEC_NODATA;
} else {
if (optout)
/* The RFC only specifies that we have to care for optout for NODATA for
* DS records. However, children of an insecure opt-out delegation should
* also be considered opt-out, rather than verified NXDOMAIN.
* Note that we do not require a proof of wildcard non-existence if the
* next closer domain is covered by an opt-out, as that would not provide
* any additional information. */
*result = DNSSEC_NSEC_OPTOUT;
else if (no_wildcard)
*result = DNSSEC_NSEC_NXDOMAIN;
else {
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
}
if (authenticated)
*authenticated = a;
if (ttl)
*ttl = enclosure_rr->ttl;
return 0;
}
static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
const char *nn, *common_suffix;
int r;
assert(rr);
assert(rr->key->type == DNS_TYPE_NSEC);
/* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT)
*
* A couple of examples:
*
* NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT
* NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs
* NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs
*/
/* First, determine parent of next domain. */
nn = rr->nsec.next_domain_name;
r = dns_name_parent(&nn);
if (r <= 0)
return r;
/* If the name we just determined is not equal or child of the name we are interested in, then we can't say
* anything at all. */
r = dns_name_endswith(nn, name);
if (r <= 0)
return r;
/* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */
r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
if (r < 0)
return r;
return dns_name_endswith(name, common_suffix);
}
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
bool have_nsec3 = false;
DnsResourceRecord *rr;
DnsAnswerFlags flags;
const char *name;
int r;
assert(key);
assert(result);
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
name = DNS_RESOURCE_KEY_NAME(key);
DNS_ANSWER_FOREACH_FLAGS(rr, flags, 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), name);
if (r < 0)
return r;
if (r > 0) {
if (key->type == DNS_TYPE_DS) {
/* If we look for a DS RR and the server sent us the NSEC RR of the child zone
* we have a problem. For DS RRs we want the NSEC RR from the parent */
if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
continue;
} else {
/* For all RR types, ensure that if NS is set SOA is set too, so that we know
* we got the child's NSEC. */
if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) &&
!bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
continue;
}
if (bitmap_isset(rr->nsec.types, key->type))
*result = DNSSEC_NSEC_FOUND;
else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME))
*result = DNSSEC_NSEC_CNAME;
else
*result = DNSSEC_NSEC_NODATA;
if (authenticated)
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (ttl)
*ttl = rr->ttl;
return 0;
}
/* Check if the name we are looking for is an empty non-terminal within the owner or next name
* of the NSEC RR. */
r = dnssec_nsec_in_path(rr, name);
if (r < 0)
return r;
if (r > 0) {
*result = DNSSEC_NSEC_NODATA;
if (authenticated)
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (ttl)
*ttl = rr->ttl;
return 0;
}
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
if (r < 0)
return r;
if (r > 0)
*result = DNSSEC_NSEC_NXDOMAIN;
if (authenticated)
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (ttl)
*ttl = rr->ttl;
return 0;
case DNS_TYPE_NSEC3:
have_nsec3 = true;
break;
}
}
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
if (have_nsec3)
return dnssec_test_nsec3(answer, key, result, authenticated, ttl);
/* No approproate NSEC RR found, report this. */
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) {
DnsResourceRecord *rr;
DnsAnswerFlags flags;
int r;
assert(name);
assert(zone);
/* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified
* 'zone'. The 'zone' must be a suffix of the 'name'. */
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
bool found = false;
if (rr->key->type != type && type != DNS_TYPE_ANY)
continue;
switch (rr->key->type) {
case DNS_TYPE_NSEC:
/* We only care for NSEC RRs from the indicated zone */
r = dns_resource_record_is_signer(rr, zone);
if (r < 0)
return r;
if (r == 0)
continue;
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
if (r < 0)
return r;
found = r > 0;
break;
case DNS_TYPE_NSEC3: {
_cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL;
/* We only care for NSEC3 RRs from the indicated zone */
r = dns_resource_record_is_signer(rr, zone);
if (r < 0)
return r;
if (r == 0)
continue;
r = nsec3_is_good(rr, NULL);
if (r < 0)
return r;
if (r == 0)
break;
/* Format the domain we are testing with the NSEC3 RR's hash function */
r = nsec3_hashed_domain_make(
rr,
name,
zone,
&hashed_domain);
if (r < 0)
return r;
if ((size_t) r != rr->nsec3.next_hashed_name_size)
break;
/* Format the NSEC3's next hashed name as proper domain name */
r = nsec3_hashed_domain_format(
rr->nsec3.next_hashed_name,
rr->nsec3.next_hashed_name_size,
zone,
&next_hashed_domain);
if (r < 0)
return r;
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain);
if (r < 0)
return r;
found = r > 0;
break;
}
default:
continue;
}
if (found) {
if (authenticated)
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
return 1;
}
}
return 0;
}
static int dnssec_test_positive_wildcard_nsec3(
DnsAnswer *answer,
const char *name,
const char *source,
const char *zone,
bool *authenticated) {
const char *next_closer = NULL;
int r;
/* Run a positive NSEC3 wildcard proof. Specifically:
*
* A proof that the the "next closer" of the generating wildcard does not exist.
*
* Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for
* empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name
* exists for the NSEC3 RR and we are done.
*
* To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that
* c.d.e.f does not exist. */
for (;;) {
next_closer = name;
r = dns_name_parent(&name);
if (r < 0)
return r;
if (r == 0)
return 0;
r = dns_name_equal(name, source);
if (r < 0)
return r;
if (r > 0)
break;
}
return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated);
}
static int dnssec_test_positive_wildcard_nsec(
DnsAnswer *answer,
const char *name,
const char *source,
const char *zone,
bool *_authenticated) {
bool authenticated = true;
int r;
/* Run a positive NSEC wildcard proof. Specifically:
*
* A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and
* a prefix of the synthesizing source "source" in the zone "zone".
*
* See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4
*
* Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we
* have to prove that none of the following exist:
*
* 1) a.b.c.d.e.f
* 2) *.b.c.d.e.f
* 3) b.c.d.e.f
* 4) *.c.d.e.f
* 5) c.d.e.f
*
*/
for (;;) {
_cleanup_free_ char *wc = NULL;
bool a = false;
/* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing,
* i.e between the owner name and the next name of an NSEC RR. */
r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a);
if (r <= 0)
return r;
authenticated = authenticated && a;
/* Strip one label off */
r = dns_name_parent(&name);
if (r <= 0)
return r;
/* Did we reach the source of synthesis? */
r = dns_name_equal(name, source);
if (r < 0)
return r;
if (r > 0) {
/* Successful exit */
*_authenticated = authenticated;
return 1;
}
/* Safety check, that the source of synthesis is still our suffix */
r = dns_name_endswith(name, source);
if (r < 0)
return r;
if (r == 0)
return -EBADMSG;
/* Replace the label we stripped off with an asterisk */
wc = strappend("*.", name);
if (!wc)
return -ENOMEM;
/* And check if the proof holds for the asterisk name, too */
r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a);
if (r <= 0)
return r;
authenticated = authenticated && a;
/* In the next iteration we'll check the non-asterisk-prefixed version */
}
}
int dnssec_test_positive_wildcard(
DnsAnswer *answer,
const char *name,
const char *source,
const char *zone,
bool *authenticated) {
int r;
assert(name);
assert(source);
assert(zone);
assert(authenticated);
r = dns_answer_contains_zone_nsec3(answer, zone);
if (r < 0)
return r;
if (r > 0)
return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated);
else
return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated);
}
static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_VALIDATED] = "validated",
[DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard",
[DNSSEC_INVALID] = "invalid",
[DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
[DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
[DNSSEC_NO_SIGNATURE] = "no-signature",
[DNSSEC_MISSING_KEY] = "missing-key",
[DNSSEC_UNSIGNED] = "unsigned",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);