* - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
* - multi-label zone compatibility
* - cname/dname compatibility
- * - per-interface DNSSEC setting
* - nxdomain on qname
- * - retry on failed validation?
- * - DNSSEC key revocation support? https://tools.ietf.org/html/rfc5011
- * - when doing negative caching, use NSEC/NSEC3 RR instead of SOA for TTL
+ * - bus calls to override DNSEC setting per interface
+ * - log all DNSSEC downgrades
+ * - enable by default
*
+ * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
+ * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
+ * - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
* */
#define VERIFY_RRS_MAX 256
/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
-/* Maximum number of NSEC3 iterations we'll do. */
-#define NSEC3_ITERATIONS_MAX 2048
+/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
+#define NSEC3_ITERATIONS_MAX 2500
/*
* The DNSSEC Chain of trust:
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
-uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
+uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
const uint8_t *p;
- uint32_t sum;
+ uint32_t sum, f;
size_t i;
/* The algorithm from RFC 4034, Appendix B. */
assert(dnskey);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
- sum = (uint32_t) dnskey->dnskey.flags +
- ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
+ f = (uint32_t) dnskey->dnskey.flags;
+
+ if (mask_revoke)
+ f &= ~DNSKEY_FLAG_REVOKE;
+
+ sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
p = dnskey->dnskey.key;
assert(*y);
assert((*y)->wire_format);
- m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
+ m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
- r = memcmp((*x)->wire_format, (*y)->wire_format, m);
+ r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
if (r != 0)
return r;
- if ((*x)->wire_format_size < (*y)->wire_format_size)
+ if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return -1;
- else if ((*x)->wire_format_size > (*y)->wire_format_size)
+ else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return 1;
return 0;
expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
inception = rrsig->rrsig.inception * USEC_PER_SEC;
+ /* Consider inverted validity intervals as expired */
if (inception > expiration)
- return -EKEYREJECTED;
+ return true;
/* Permit a certain amount of clock skew of 10% of the valid
* time range. This takes inspiration from unbound's
int dnssec_verify_rrset(
DnsAnswer *a,
- DnsResourceKey *key,
+ const DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
usec_t realtime,
gcry_md_hd_t md = NULL;
int r, md_algorithm;
size_t k, n = 0;
+ bool wildcard;
+ const char *source;
assert(key);
assert(rrsig);
return 0;
}
+ /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */
+ r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(key), 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(DNS_RESOURCE_KEY_NAME(key), "*");
+ if (r < 0)
+ return r;
+ if (r > 0)
+ source = DNS_RESOURCE_KEY_NAME(key);
+
+ 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 *, a->n_rrs);
+ list = newa(DnsResourceRecord *, dns_answer_size(a));
DNS_ANSWER_FOREACH(rr, a) {
r = dns_resource_key_equal(key, rr->key);
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++) {
- const char *suffix;
size_t l;
+
rr = list[k];
- r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
- if (r < 0)
- goto finish;
- if (r > 0) /* This is a wildcard! */
+ /* 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);
-
- r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
- if (r < 0)
- goto finish;
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);
- assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
- l = rr->wire_format_size - rr->wire_format_rdata_offset;
+ l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
assert(l <= 0xFFFF);
md_add_uint16(md, (uint16_t) l);
- gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
+ gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
}
hash = gcry_md_read(md, 0);
if (r < 0)
goto finish;
- *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
+ if (!r)
+ *result = DNSSEC_INVALID;
+ else if (wildcard)
+ *result = DNSSEC_VALIDATED_WILDCARD;
+ else
+ *result = DNSSEC_VALIDATED;
r = 0;
finish:
return r;
}
-int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
+int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
assert(rrsig);
assert(dnskey);
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) != rrsig->rrsig.key_tag)
+ 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_verify_rrset_search(
DnsAnswer *a,
- DnsResourceKey *key,
+ const DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
usec_t realtime,
- DnssecResult *result) {
+ DnssecResult *result,
+ DnsResourceRecord **ret_rrsig) {
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
DnsResourceRecord *rrsig;
continue;
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
- r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
+ r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
if (r < 0)
return r;
if (r == 0)
switch (one_result) {
case DNSSEC_VALIDATED:
+ 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;
- *result = DNSSEC_VALIDATED;
+ if (ret_rrsig)
+ *ret_rrsig = rrsig;
+
+ *result = one_result;
return 0;
case DNSSEC_INVALID:
else
*result = DNSSEC_NO_SIGNATURE;
+ if (ret_rrsig)
+ *ret_rrsig = NULL;
+
return 0;
}
return -ENOBUFS;
for (;;) {
- size_t i;
-
r = dns_label_unescape(&n, buffer, buffer_max);
if (r < 0)
return r;
if (memchr(buffer, '.', r))
return -EINVAL;
- for (i = 0; i < (size_t) r; i ++) {
- if (buffer[i] >= 'A' && buffer[i] <= 'Z')
- buffer[i] = buffer[i] - 'A' + 'a';
- }
-
+ ascii_strlower_n(buffer, (size_t) r);
buffer[r] = '.';
buffer += r + 1;
}
}
-int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
+int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
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) != ds->ds.key_tag)
+ if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag)
return 0;
initialize_libgcrypt();
return -EIO;
gcry_md_write(md, owner_name, r);
- md_add_uint16(md, dnskey->dnskey.flags);
+ 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);
if (r == 0)
continue;
- r = dnssec_verify_dnskey(dnskey, ds);
+ r = dnssec_verify_dnskey(dnskey, ds, false);
+ if (r == -EKEYREJECTED)
+ return 0; /* The DNSKEY is revoked or otherwise invalid, we won't bless it */
if (r < 0)
return r;
if (r > 0)
}
}
-int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret) {
+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;
if (nsec3->key->type != DNS_TYPE_NSEC3)
return -EINVAL;
- if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX)
+ 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 r;
}
-static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
+static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) {
const char *a, *b;
int r;
return dns_name_equal(a, b);
}
-static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
- _cleanup_free_ char *l = NULL, *hashed_domain = NULL;
+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;
if (hashed_size < 0)
return hashed_size;
- l = base32hexmem(hashed, hashed_size, false);
- if (!l)
- return -ENOMEM;
-
- hashed_domain = strjoin(l, ".", zone, NULL);
- if (!hashed_domain)
- return -ENOMEM;
-
- *ret = hashed_domain;
- hashed_domain = NULL;
-
- return hashed_size;
+ return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret);
}
/* See RFC 5155, Section 8
* 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) {
- _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL;
- const char *zone, *p, *pp = NULL;
- DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL;
+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);
- assert(authenticated);
/* 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
* parameters. */
zone = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
- DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
- r = nsec3_is_good(suffix_rr, flags, NULL);
+ 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(suffix_rr->key), 1, zone);
+ r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone);
if (r < 0)
return r;
if (r > 0)
for (;;) {
_cleanup_free_ char *hashed_domain = NULL;
- hashed_size = nsec3_hashed_domain(suffix_rr, p, zone, &hashed_domain);
+ hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain);
if (hashed_size == -EOPNOTSUPP) {
*result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
return 0;
DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) {
- r = nsec3_is_good(enclosure_rr, flags, suffix_rr);
+ r = nsec3_is_good(enclosure_rr, zone_rr);
if (r < 0)
return r;
if (r == 0)
else
*result = DNSSEC_NSEC_NODATA;
- *authenticated = a;
+ 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 = strappend("*.", p);
- if (!wildcard)
- return -ENOMEM;
-
- r = nsec3_hashed_domain(enclosure_rr, wildcard, zone, &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(enclosure_rr, pp, zone, &next_closer_domain);
+ 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 *label = NULL, *next_hashed_domain = NULL;
+ _cleanup_free_ char *next_hashed_domain = NULL;
- r = nsec3_is_good(rr, flags, suffix_rr);
+ r = nsec3_is_good(rr, zone_rr);
if (r < 0)
return r;
if (r == 0)
continue;
- label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
- if (!label)
- return -ENOMEM;
-
- next_hashed_domain = strjoin(label, ".", zone, NULL);
- if (!next_hashed_domain)
- return -ENOMEM;
+ 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)
if (!no_closer) {
*result = DNSSEC_NSEC_NO_RR;
-
return 0;
}
}
}
- *authenticated = a;
+ if (authenticated)
+ *authenticated = a;
+
+ if (ttl)
+ *ttl = enclosure_rr->ttl;
return 0;
}
-int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
+int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
DnsResourceRecord *rr;
bool have_nsec3 = false;
DnsAnswerFlags flags;
assert(key);
assert(result);
- assert(authenticated);
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
*result = DNSSEC_NSEC_CNAME;
else
*result = DNSSEC_NSEC_NODATA;
- *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ if (ttl)
+ *ttl = rr->ttl;
+
return 0;
}
return r;
if (r > 0) {
*result = DNSSEC_NSEC_NXDOMAIN;
- *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ if (ttl)
+ *ttl = rr->ttl;
+
return 0;
}
break;
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
if (have_nsec3)
- return dnssec_test_nsec3(answer, key, result, authenticated);
+ return dnssec_test_nsec3(answer, key, result, authenticated, ttl);
/* No approproate NSEC RR found, report this. */
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
-static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
- [DNSSEC_NO] = "no",
- [DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
- [DNSSEC_YES] = "yes",
-};
-DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
+int dnssec_nsec_test_between(DnsAnswer *answer, 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;
+
+ 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:
+ 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;
+
+ 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 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",