/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_GCRYPT
#include <gcrypt.h>
#endif
#include "alloc-util.h"
#include "dns-domain.h"
#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"
/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
/*
* The DNSSEC Chain of trust:
*
* Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
* DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
* DS RRs are protected like normal RRs
*
* Example chain:
*/
const uint8_t *p;
size_t i;
/* The algorithm from RFC 4034, Appendix B. */
if (mask_revoke)
f &= ~DNSKEY_FLAG_REVOKE;
}
size_t c = 0;
int r;
/* Converts the specified hostname into DNSSEC canonicalized
* form. */
if (buffer_max < 2)
return -ENOBUFS;
for (;;) {
if (r < 0)
return r;
if (r == 0)
break;
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. */
return -EINVAL;
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 */
buffer[0] = '.';
buffer[1] = 0;
return 1;
}
return (int) c;
}
#ifdef HAVE_GCRYPT
static void initialize_libgcrypt(void) {
const char *p;
return;
p = gcry_check_version("1.4.5");
assert(p);
}
static int rr_compare(const void *a, const void *b) {
size_t m;
int r;
/* Let's order the RRs according to RFC 4034, Section 6.3 */
assert(x);
assert(*x);
assert((*x)->wire_format);
assert(y);
assert(*y);
assert((*y)->wire_format);
if (r != 0)
return r;
if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return -1;
else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return 1;
return 0;
}
static int dnssec_rsa_verify_raw(
const char *hash_algorithm,
int r;
if (ge != 0) {
r = -EIO;
goto finish;
}
if (ge != 0) {
r = -EIO;
goto finish;
}
if (ge != 0) {
r = -EIO;
goto finish;
}
NULL,
"(sig-val (rsa (s %m)))",
s);
if (ge != 0) {
r = -EIO;
goto finish;
}
NULL,
"(data (flags pkcs1) (hash %s %b))",
(int) data_size,
data);
if (ge != 0) {
r = -EIO;
goto finish;
}
NULL,
"(public-key (rsa (n %m) (e %m)))",
n,
e);
if (ge != 0) {
r = -EIO;
goto finish;
}
r = 0;
else if (ge != 0) {
r = -EIO;
} else
r = 1;
if (e)
gcry_mpi_release(e);
if (n)
gcry_mpi_release(n);
if (s)
gcry_mpi_release(s);
if (public_key_sexp)
if (signature_sexp)
if (data_sexp)
return r;
}
static int dnssec_rsa_verify(
const char *hash_algorithm,
/* exponent is > 255 bytes long */
if (exponent_size < 256)
return -EINVAL;
return -EINVAL;
} else {
/* exponent is <= 255 bytes long */
if (exponent_size <= 0)
return -EINVAL;
return -EINVAL;
}
return dnssec_rsa_verify_raw(
}
static int dnssec_ecdsa_verify_raw(
const char *hash_algorithm,
const char *curve,
int k;
if (ge != 0) {
k = -EIO;
goto finish;
}
if (ge != 0) {
k = -EIO;
goto finish;
}
if (ge != 0) {
k = -EIO;
goto finish;
}
NULL,
"(sig-val (ecdsa (r %m) (s %m)))",
r,
s);
if (ge != 0) {
k = -EIO;
goto finish;
}
NULL,
"(data (flags rfc6979) (hash %s %b))",
(int) data_size,
data);
if (ge != 0) {
k = -EIO;
goto finish;
}
NULL,
"(public-key (ecc (curve %s) (q %m)))",
q);
if (ge != 0) {
k = -EIO;
goto finish;
}
k = 0;
else if (ge != 0) {
k = -EIO;
} else
k = 1;
if (r)
gcry_mpi_release(r);
if (s)
gcry_mpi_release(s);
if (q)
gcry_mpi_release(q);
if (public_key_sexp)
if (signature_sexp)
if (data_sexp)
return k;
}
static int dnssec_ecdsa_verify(
const char *hash_algorithm,
int algorithm,
const char *curve;
uint8_t *q;
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;
return -EINVAL;
return -EINVAL;
q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
return dnssec_ecdsa_verify_raw(
}
gcry_md_write(md, &v, sizeof(v));
}
v = htobe16(v);
gcry_md_write(md, &v, sizeof(v));
}
v = htobe32(v);
gcry_md_write(md, &v, sizeof(v));
}
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. */
/* Check if this RRSIG RR is already prepared */
return 0;
return -EINVAL;
if (n_key_labels < 0)
return n_key_labels;
return -EINVAL;
if (n_signer_labels < 0)
return n_signer_labels;
return -EINVAL;
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
/* Check if the signer is really a suffix of us */
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
return 0;
}
if (realtime == USEC_INFINITY)
/* 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. */
inception = 0;
else
else
expiration += skew;
}
/* 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:
return GCRY_MD_SHA1;
return GCRY_MD_SHA256;
return GCRY_MD_SHA384;
return GCRY_MD_SHA512;
default:
return -EOPNOTSUPP;
}
}
static void dnssec_fix_rrset_ttl(
unsigned n,
unsigned k;
assert(n > 0);
for (k = 0; k < n; 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 */
/* Copy over information about the signer and wildcard source of synthesis */
}
}
int dnssec_verify_rrset(
DnsAnswer *a,
const DnsResourceKey *key,
DnssecResult *result) {
int r, md_algorithm;
size_t k, n = 0;
void *hash;
bool wildcard;
/* 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. */
if (md_algorithm == -EOPNOTSUPP) {
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;
if (r < 0)
return r;
if (r > 0) {
return 0;
}
/* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */
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 (r < 0)
return r;
if (r > 0) {
*result = DNSSEC_INVALID;
return 0;
}
}
/* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */
if (r < 0)
return r;
/* 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. */
if (r < 0)
return r;
if (r > 0)
wildcard = r == 0;
} else
wildcard = r > 0;
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
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;
if (n > VERIFY_RRS_MAX)
return -E2BIG;
}
if (n <= 0)
return -ENODATA;
/* Bring the RRs into canonical order */
/* OK, the RRs are now in canonical order. Let's calculate the digest */
if (!md)
return -EIO;
if (r < 0)
goto finish;
/* Convert the source of synthesis into wire format */
if (r < 0)
goto finish;
for (k = 0; k < n; k++) {
size_t l;
/* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */
if (wildcard)
assert(l <= 0xFFFF);
}
if (!hash) {
r = -EIO;
goto finish;
}
case DNSSEC_ALGORITHM_RSASHA1:
r = dnssec_rsa_verify(
dnskey);
break;
r = dnssec_ecdsa_verify(
dnskey);
break;
}
if (r < 0)
goto finish;
/* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */
if (r > 0)
if (r == 0)
*result = DNSSEC_INVALID;
else if (wildcard)
else
r = 0;
return r;
}
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
/* Checks if the specified DNSKEY RR matches the key used for
* the signature in the specified RRSIG RR */
return -EINVAL;
return 0;
return 0;
return 0;
return 0;
return 0;
return 0;
return 0;
}
/* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
return 0;
return 0;
return 0;
}
DnsAnswer *a,
const DnsResourceKey *key,
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
int r;
/* 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. */
/* Is this an RRSIG RR that applies to RRs matching our key? */
if (r < 0)
return r;
if (r == 0)
continue;
found_rrsig = true;
/* Look for a matching key */
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
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)
/* 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. */
if (r < 0)
return r;
switch (one_result) {
case DNSSEC_VALIDATED:
/* Yay, the RR has been validated,
* return immediately, but fix up the expiry */
if (ret_rrsig)
*result = one_result;
return 0;
case DNSSEC_INVALID:
/* If the signature is invalid, let's try another
key_tags and stuff are not unique, and
might be shared by multiple keys. */
found_invalid = true;
continue;
/* If the key algorithm is
unsupported, try another
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)
else if (found_unsupported_algorithm)
else if (found_invalid)
*result = DNSSEC_INVALID;
else if (found_rrsig)
else
if (ret_rrsig)
return 0;
}
int r;
/* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
if (r < 0)
return r;
if (r > 0)
return 1;
}
return 0;
}
/* 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) {
int md_algorithm, r;
void *result;
/* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
return -EINVAL;
return -EINVAL;
return -EKEYREJECTED;
return -EKEYREJECTED;
return -EKEYREJECTED;
return 0;
return 0;
if (md_algorithm < 0)
return md_algorithm;
return 0;
if (r < 0)
return r;
if (!md)
return -EIO;
if (mask_revoke)
else
if (!result) {
r = -EIO;
goto finish;
}
return r;
}
int r;
return 0;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
continue;
continue;
if (r < 0)
return r;
if (r == 0)
continue;
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;
}
/* 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 algorithm;
void *result;
unsigned k;
int r;
return -EINVAL;
log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3));
return -EOPNOTSUPP;
}
if (algorithm < 0)
return algorithm;
return -EINVAL;
if (r < 0)
return r;
if (!md)
return -EIO;
if (!result) {
r = -EIO;
goto finish;
}
if (!result) {
r = -EIO;
goto finish;
}
}
r = (int) hash_size;
return r;
}
const char *a, *b;
int r;
return 0;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
return 0;
/* Ignore NSEC3 RRs whose algorithm we don't know */
return 0;
/* Ignore NSEC3 RRs with an excessive number of required iterations */
return 0;
/* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this
* check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */
return 0;
/* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */
return 0;
if (!nsec3)
return 1;
/* If a second NSEC3 RR is specified, also check if they are from the same zone. */
return 1;
return 0;
return 0;
return 0;
return 0;
return 0;
r = dns_name_parent(&a); /* strip off hash */
if (r < 0)
return r;
if (r == 0)
return 0;
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_size > 0);
if (!l)
return -ENOMEM;
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) {
int hashed_size;
if (hashed_size < 0)
return hashed_size;
}
/* 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) {
int hashed_size, r;
/* 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. */
for (;;) {
if (r < 0)
return r;
if (r == 0)
continue;
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;
}
return 0;
/* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
p = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
if (hashed_size == -EOPNOTSUPP) {
return 0;
}
if (hashed_size < 0)
return hashed_size;
if (r < 0)
return r;
if (r == 0)
continue;
continue;
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;
}
return 0;
/* We found a closest encloser in 'p'; next closer is 'pp' */
if (!pp) {
/* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR
* from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are
* appropriately set. */
return -EBADMSG;
} else {
return -EBADMSG;
}
/* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
else
if (authenticated)
*authenticated = a;
if (ttl)
return 0;
}
/* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
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). */
return -EBADMSG;
/* Prove that there is no next closer and whether or not there is a wildcard domain. */
if (r < 0)
return r;
if (r != hashed_size)
return -EBADMSG;
if (r < 0)
return r;
if (r != hashed_size)
return -EBADMSG;
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;
if (r < 0)
return r;
if (r > 0) {
optout = true;
a = a && (flags & DNS_ANSWER_AUTHENTICATED);
no_closer = true;
}
if (r < 0)
return r;
if (r > 0) {
a = a && (flags & DNS_ANSWER_AUTHENTICATED);
wildcard_rr = rr;
}
if (r < 0)
return r;
if (r > 0) {
/* 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) {
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. */
else
} 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. */
else if (no_wildcard)
else {
return 0;
}
}
if (authenticated)
*authenticated = a;
if (ttl)
return 0;
}
const char *n;
int r;
/* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */
return 0;
if (r <= 0)
return r;
return 0;
return dns_name_endswith(name, n);
}
int r;
/* 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. */
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. */
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;
}
int r;
/* Checks whether this NSEC originates to the parent zone or the child zone. */
r = dns_name_parent(&name);
if (r <= 0)
return r;
if (r <= 0)
return r;
/* DNAME, and NS without SOA is an indication for a delegation. */
return 1;
return 1;
return 0;
}
const char *common_suffix, *p;
int r;
/* Checks whether the "Next Closer" is witin the space covered by the specified RR. */
r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
if (r < 0)
return r;
for (;;) {
p = name;
r = dns_name_parent(&name);
if (r < 0)
return r;
if (r == 0)
return 0;
if (r < 0)
return r;
if (r > 0)
break;
}
/* p is now the "Next Closer". */
}
int r;
/* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified
* RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as
* suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label.
*
* NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist
* NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...)
* NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either...
*/
r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
if (r < 0)
return r;
/* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */
if (r <= 0)
return r;
}
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
const char *name;
int r;
continue;
continue;
/* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */
if (r < 0)
return r;
if (r > 0)
continue;
/* Check if this is a direct match. If so, we have encountered a NODATA case */
if (r < 0)
return r;
if (r == 0) {
/* If it's not a direct match, maybe it's a wild card match? */
if (r < 0)
return r;
}
if (r > 0) {
/* 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 */
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. */
continue;
}
else
if (authenticated)
if (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. */
if (r < 0)
return r;
if (r > 0) {
if (authenticated)
if (ttl)
return 0;
}
/* The following two "covering" checks, are not useful if the NSEC is from the parent */
if (r < 0)
return r;
if (r > 0)
continue;
/* Check if this NSEC RR proves the absence of an explicit RR under this name */
if (r < 0)
return r;
if (r > 0 && (!covering_rr || !covering_rr_authenticated)) {
covering_rr = rr;
}
/* Check if this NSEC RR proves the absence of a wildcard RR under this name */
if (r < 0)
return r;
if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) {
wildcard_rr = rr;
}
}
if (covering_rr && wildcard_rr) {
/* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we
* proved the NXDOMAIN case. */
if (authenticated)
if (ttl)
return 0;
}
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
if (have_nsec3)
/* No approproate NSEC RR found, report this. */
return 0;
}
static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) {
int r;
/* 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'. */
bool found = false;
continue;
case DNS_TYPE_NSEC:
/* We only care for NSEC RRs from the indicated zone */
if (r < 0)
return r;
if (r == 0)
continue;
if (r < 0)
return r;
found = r > 0;
break;
case DNS_TYPE_NSEC3: {
/* We only care for NSEC3 RRs from the indicated zone */
if (r < 0)
return r;
if (r == 0)
continue;
if (r < 0)
return r;
if (r == 0)
break;
/* Format the domain we are testing with the NSEC3 RR's hash function */
rr,
name,
zone,
if (r < 0)
return r;
break;
/* Format the NSEC3's next hashed name as proper domain name */
zone,
if (r < 0)
return r;
if (r < 0)
return r;
found = r > 0;
break;
}
default:
continue;
}
if (found) {
if (authenticated)
return 1;
}
}
return 0;
}
static int dnssec_test_positive_wildcard_nsec3(
const char *name,
const char *source,
const char *zone,
bool *authenticated) {
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;
if (r < 0)
return r;
if (r > 0)
break;
}
}
static int dnssec_test_positive_wildcard_nsec(
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 (;;) {
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. */
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? */
if (r < 0)
return r;
if (r > 0) {
/* Successful exit */
return 1;
}
/* Safety check, that the source of synthesis is still our suffix */
if (r < 0)
return r;
if (r == 0)
return -EBADMSG;
/* Replace the label we stripped off with an asterisk */
if (!wc)
return -ENOMEM;
/* And check if the proof holds for the asterisk name, too */
if (r <= 0)
return r;
authenticated = authenticated && a;
/* In the next iteration we'll check the non-asterisk-prefixed version */
}
}
const char *name,
const char *source,
const char *zone,
bool *authenticated) {
int r;
if (r < 0)
return r;
if (r > 0)
else
}
#else
int dnssec_verify_rrset(
DnsAnswer *a,
const DnsResourceKey *key,
DnssecResult *result) {
return -EOPNOTSUPP;
}
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
DnsAnswer *a,
const DnsResourceKey *key,
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
return -EOPNOTSUPP;
}
const char *name,
const char *source,
const char *zone,
bool *authenticated) {
return -EOPNOTSUPP;
}
#endif
[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",
};
[DNSSEC_SECURE] = "secure",
[DNSSEC_INSECURE] = "insecure",
[DNSSEC_BOGUS] = "bogus",
[DNSSEC_INDETERMINATE] = "indeterminate",
};