resolved: when validating an RRset, store information about the synthesizing source and zone in each RR

Having this information available is useful when we need to check whether various RRs are suitable for proofs. This
information is stored in the RRs as number of labels to skip from the beginning of the owner name to reach the
synthesizing source/signer. Simple accessor calls are then added to retrieve the signer/source from the RR using this
information.

This also moves validation of a a number of RRSIG parameters into a new call dnssec_rrsig_prepare() that as side-effect
initializes the two numeric values.
This commit is contained in:
Lennart Poettering 2016-01-14 18:03:03 +01:00
parent 1827a1582c
commit 97c67192ea
6 changed files with 206 additions and 60 deletions

View File

@ -430,6 +430,57 @@ static void md_add_uint32(gcry_md_hd_t md, uint32_t 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;
@ -499,6 +550,35 @@ static int algorithm_to_gcrypt_md(uint8_t algorithm) {
}
}
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,
@ -536,6 +616,14 @@ int dnssec_verify_rrset(
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;
@ -699,12 +787,17 @@ int dnssec_verify_rrset(
if (r < 0)
goto finish;
if (!r)
/* 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:
@ -743,8 +836,6 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
}
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
int r;
assert(key);
assert(rrsig);
@ -757,45 +848,9 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig)
if (rrsig->rrsig.type_covered != key->type)
return 0;
/* Make sure signer is a parent of the RRset */
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
if (r <= 0)
return r;
/* Make sure the owner name has at least as many labels as the "label" fields indicates. */
r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
if (r < 0)
return r;
if (r < rrsig->rrsig.labels)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) {
DnsResourceRecord *rr;
int r;
assert(key);
assert(rrsig);
DNS_ANSWER_FOREACH(rr, a) {
r = dns_resource_key_equal(key, rr->key);
if (r < 0)
return r;
if (r == 0)
continue;
/* Pick the TTL as the minimum of the RR's TTL, the
* RR's original TTL according to the RRSIG and the
* RRSIG's own TTL, see RFC 4035, Section 5.3.3 */
rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl);
rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
}
return 0;
}
int dnssec_verify_rrset_search(
DnsAnswer *a,
const DnsResourceKey *key,
@ -865,10 +920,6 @@ int dnssec_verify_rrset_search(
case DNSSEC_VALIDATED_WILDCARD:
/* Yay, the RR has been validated,
* return immediately, but fix up the expiry */
r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime);
if (r < 0)
return r;
if (ret_rrsig)
*ret_rrsig = rrsig;
@ -1658,15 +1709,17 @@ int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name
if (rr->key->type != type && type != DNS_TYPE_ANY)
continue;
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone);
if (r < 0)
return r;
if (r == 0)
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;
@ -1677,6 +1730,13 @@ int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name
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;

View File

@ -344,6 +344,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) {
rr->n_ref = 1;
rr->key = dns_resource_key_ref(key);
rr->expiry = USEC_INFINITY;
rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1;
return rr;
}
@ -1085,6 +1086,63 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) {
return 0;
}
int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) {
const char *n;
int r;
assert(rr);
assert(ret);
/* Returns the RRset's signer, if it is known. */
if (rr->n_skip_labels_signer == (unsigned) -1)
return -ENODATA;
n = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_skip(n, rr->n_skip_labels_signer, &n);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
*ret = n;
return 0;
}
int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) {
const char *n;
int r;
assert(rr);
assert(ret);
/* Returns the RRset's synthesizing source, if it is known. */
if (rr->n_skip_labels_source == (unsigned) -1)
return -ENODATA;
n = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_skip(n, rr->n_skip_labels_source, &n);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
*ret = n;
return 0;
}
int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) {
const char *signer;
int r;
r = dns_resource_record_signer(rr, &signer);
if (r < 0)
return r;
return dns_name_equal(zone, signer);
}
static void dns_resource_record_hash_func(const void *i, struct siphash *state) {
const DnsResourceRecord *rr = i;

View File

@ -108,14 +108,24 @@ struct DnsTxtItem {
struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
char *to_string;
uint32_t ttl;
usec_t expiry; /* RRSIG signature expiry */
/* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */
unsigned n_skip_labels_signer;
/* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */
unsigned n_skip_labels_source;
bool unparseable:1;
bool wire_format_canonical:1;
void *wire_format;
size_t wire_format_size;
size_t wire_format_rdata_offset;
union {
struct {
void *data;
@ -296,6 +306,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret);
int dns_resource_record_source(DnsResourceRecord *rr, const char **ret);
int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone);
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);

View File

@ -2537,11 +2537,9 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
* that no matching non-wildcard RR exists.*/
/* First step, determine the source of synthesis */
r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &source);
r = dns_resource_record_source(rrsig, &source);
if (r < 0)
return r;
if (r == 0)
return -EBADMSG;
r = dnssec_test_positive_wildcard(
validated,

View File

@ -1189,6 +1189,26 @@ int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
return (int) (n - n_labels);
}
int dns_name_skip(const char *a, unsigned n_labels, const char **ret) {
int r;
assert(a);
assert(ret);
for (; n_labels > 0; n_labels --) {
r = dns_name_parent(&a);
if (r < 0)
return r;
if (r == 0) {
*ret = "";
return 0;
}
}
*ret = a;
return 1;
}
int dns_name_count_labels(const char *name) {
unsigned n = 0;
const char *p;
@ -1219,14 +1239,9 @@ int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
assert(a);
assert(b);
while (n_labels > 0) {
r = dns_name_parent(&a);
if (r <= 0)
return r;
n_labels --;
}
r = dns_name_skip(a, n_labels, &a);
if (r <= 0)
return r;
return dns_name_equal(a, b);
}

View File

@ -104,4 +104,5 @@ int dns_service_split(const char *joined, char **name, char **type, char **domai
int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
int dns_name_count_labels(const char *name);
int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);