mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 10:10:06 +00:00
Also disable the semantic patch as the code needs tweaks here and there because some destroy functions might not destroy the object and return early if the object is still in use.
3405 lines
87 KiB
C
3405 lines
87 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <isc/base32.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/md.h>
|
|
#include <isc/print.h>
|
|
#include <isc/string.h>
|
|
#include <isc/task.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/client.h>
|
|
#include <dns/db.h>
|
|
#include <dns/dnssec.h>
|
|
#include <dns/ds.h>
|
|
#include <dns/events.h>
|
|
#include <dns/keytable.h>
|
|
#include <dns/keyvalues.h>
|
|
#include <dns/log.h>
|
|
#include <dns/message.h>
|
|
#include <dns/ncache.h>
|
|
#include <dns/nsec.h>
|
|
#include <dns/nsec3.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatatype.h>
|
|
#include <dns/resolver.h>
|
|
#include <dns/result.h>
|
|
#include <dns/validator.h>
|
|
#include <dns/view.h>
|
|
|
|
/*! \file
|
|
* \brief
|
|
* Basic processing sequences:
|
|
*
|
|
* \li When called with rdataset and sigrdataset:
|
|
* validator_start -> validate_answer -> proveunsecure
|
|
* validator_start -> validate_answer -> validate_nx (if secure wildcard)
|
|
*
|
|
* \li When called with rdataset but no sigrdataset:
|
|
* validator_start -> proveunsecure
|
|
*
|
|
* \li When called with no rdataset or sigrdataset:
|
|
* validator_start -> validate_nx-> proveunsecure
|
|
*
|
|
* validator_start: determine what type of validation to do.
|
|
* validate_answer: attempt to perform a positive validation.
|
|
* proveunsecure: attempt to prove the answer comes from an unsecure zone.
|
|
* validate_nx: attempt to prove a negative response.
|
|
*/
|
|
|
|
#define VALIDATOR_MAGIC ISC_MAGIC('V', 'a', 'l', '?')
|
|
#define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC)
|
|
|
|
#define VALATTR_SHUTDOWN 0x0001 /*%< Shutting down. */
|
|
#define VALATTR_CANCELED 0x0002 /*%< Canceled. */
|
|
#define VALATTR_TRIEDVERIFY 0x0004 /*%< We have found a key and
|
|
* have attempted a verify. */
|
|
#define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */
|
|
|
|
/*!
|
|
* NSEC proofs to be looked for.
|
|
*/
|
|
#define VALATTR_NEEDNOQNAME 0x00000100
|
|
#define VALATTR_NEEDNOWILDCARD 0x00000200
|
|
#define VALATTR_NEEDNODATA 0x00000400
|
|
|
|
/*!
|
|
* NSEC proofs that have been found.
|
|
*/
|
|
#define VALATTR_FOUNDNOQNAME 0x00001000
|
|
#define VALATTR_FOUNDNOWILDCARD 0x00002000
|
|
#define VALATTR_FOUNDNODATA 0x00004000
|
|
#define VALATTR_FOUNDCLOSEST 0x00008000
|
|
#define VALATTR_FOUNDOPTOUT 0x00010000
|
|
#define VALATTR_FOUNDUNKNOWN 0x00020000
|
|
|
|
#define NEEDNODATA(val) ((val->attributes & VALATTR_NEEDNODATA) != 0)
|
|
#define NEEDNOQNAME(val) ((val->attributes & VALATTR_NEEDNOQNAME) != 0)
|
|
#define NEEDNOWILDCARD(val) ((val->attributes & VALATTR_NEEDNOWILDCARD) != 0)
|
|
#define FOUNDNODATA(val) ((val->attributes & VALATTR_FOUNDNODATA) != 0)
|
|
#define FOUNDNOQNAME(val) ((val->attributes & VALATTR_FOUNDNOQNAME) != 0)
|
|
#define FOUNDNOWILDCARD(val) ((val->attributes & VALATTR_FOUNDNOWILDCARD) != 0)
|
|
#define FOUNDCLOSEST(val) ((val->attributes & VALATTR_FOUNDCLOSEST) != 0)
|
|
#define FOUNDOPTOUT(val) ((val->attributes & VALATTR_FOUNDOPTOUT) != 0)
|
|
|
|
#define SHUTDOWN(v) (((v)->attributes & VALATTR_SHUTDOWN) != 0)
|
|
#define CANCELED(v) (((v)->attributes & VALATTR_CANCELED) != 0)
|
|
|
|
#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
|
|
#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
|
|
|
|
static void
|
|
destroy(dns_validator_t *val);
|
|
|
|
static isc_result_t
|
|
select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset);
|
|
|
|
static isc_result_t
|
|
validate_answer(dns_validator_t *val, bool resume);
|
|
|
|
static isc_result_t
|
|
validate_dnskey(dns_validator_t *val);
|
|
|
|
static isc_result_t
|
|
validate_nx(dns_validator_t *val, bool resume);
|
|
|
|
static isc_result_t
|
|
proveunsecure(dns_validator_t *val, bool have_ds, bool resume);
|
|
|
|
static void
|
|
validator_logv(dns_validator_t *val, isc_logcategory_t *category,
|
|
isc_logmodule_t *module, int level, const char *fmt, va_list ap)
|
|
ISC_FORMAT_PRINTF(5, 0);
|
|
|
|
static void
|
|
validator_log(void *val, int level, const char *fmt, ...)
|
|
ISC_FORMAT_PRINTF(3, 4);
|
|
|
|
static void
|
|
validator_logcreate(dns_validator_t *val,
|
|
dns_name_t *name, dns_rdatatype_t type,
|
|
const char *caller, const char *operation);
|
|
|
|
/*%
|
|
* Ensure the validator's rdatasets are marked as expired.
|
|
*/
|
|
static void
|
|
expire_rdatasets(dns_validator_t *val) {
|
|
if (dns_rdataset_isassociated(&val->frdataset)) {
|
|
dns_rdataset_expire(&val->frdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&val->fsigrdataset)) {
|
|
dns_rdataset_expire(&val->fsigrdataset);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Ensure the validator's rdatasets are disassociated.
|
|
*/
|
|
static void
|
|
disassociate_rdatasets(dns_validator_t *val) {
|
|
if (dns_rdataset_isassociated(&val->frdataset)) {
|
|
dns_rdataset_disassociate(&val->frdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&val->fsigrdataset)) {
|
|
dns_rdataset_disassociate(&val->fsigrdataset);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Mark the rdatasets in val->event with trust level "answer",
|
|
* indicating that they did not validate, but could be cached as insecure.
|
|
*
|
|
* If we are validating a name that is marked as "must be secure", log a
|
|
* warning and return DNS_R_MUSTBESECURE instead.
|
|
*/
|
|
static inline isc_result_t
|
|
markanswer(dns_validator_t *val, const char *where, const char *mbstext) {
|
|
if (val->mustbesecure && mbstext != NULL) {
|
|
validator_log(val, ISC_LOG_WARNING,
|
|
"must be secure failure, %s", mbstext);
|
|
return (DNS_R_MUSTBESECURE);
|
|
}
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "marking as answer (%s)", where);
|
|
if (val->event->rdataset != NULL) {
|
|
dns_rdataset_settrust(val->event->rdataset, dns_trust_answer);
|
|
}
|
|
if (val->event->sigrdataset != NULL) {
|
|
dns_rdataset_settrust(val->event->sigrdataset,
|
|
dns_trust_answer);
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*%
|
|
* Mark the RRsets in val->event with trust level secure.
|
|
*/
|
|
static inline void
|
|
marksecure(dns_validatorevent_t *event) {
|
|
dns_rdataset_settrust(event->rdataset, dns_trust_secure);
|
|
if (event->sigrdataset != NULL) {
|
|
dns_rdataset_settrust(event->sigrdataset, dns_trust_secure);
|
|
}
|
|
event->secure = true;
|
|
}
|
|
|
|
/*
|
|
* Validator 'val' is finished; send the completion event to the task
|
|
* that called dns_validator_create(), with result `result`.
|
|
*/
|
|
static void
|
|
validator_done(dns_validator_t *val, isc_result_t result) {
|
|
isc_task_t *task;
|
|
|
|
if (val->event == NULL) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Caller must be holding the lock.
|
|
*/
|
|
|
|
val->event->result = result;
|
|
task = val->event->ev_sender;
|
|
val->event->ev_sender = val;
|
|
val->event->ev_type = DNS_EVENT_VALIDATORDONE;
|
|
val->event->ev_action = val->action;
|
|
val->event->ev_arg = val->arg;
|
|
isc_task_sendanddetach(&task, (isc_event_t **)&val->event);
|
|
}
|
|
|
|
/*
|
|
* Called when deciding whether to destroy validator 'val'.
|
|
*/
|
|
static inline bool
|
|
exit_check(dns_validator_t *val) {
|
|
/*
|
|
* Caller must be holding the lock.
|
|
*/
|
|
if (!SHUTDOWN(val)) {
|
|
return (false);
|
|
}
|
|
|
|
INSIST(val->event == NULL);
|
|
|
|
if (val->fetch != NULL || val->subvalidator != NULL) {
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*%
|
|
* Look in the NSEC record returned from a DS query to see if there is
|
|
* a NS RRset at this name. If it is found we are at a delegation point.
|
|
*/
|
|
static bool
|
|
isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
isc_result_t dbresult)
|
|
{
|
|
dns_fixedname_t fixed;
|
|
dns_label_t hashlabel;
|
|
dns_name_t nsec3name;
|
|
dns_rdata_nsec3_t nsec3;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdataset_t set;
|
|
int order;
|
|
int scope;
|
|
bool found;
|
|
isc_buffer_t buffer;
|
|
isc_result_t result;
|
|
unsigned char hash[NSEC3_MAX_HASH_LENGTH];
|
|
unsigned char owner[NSEC3_MAX_HASH_LENGTH];
|
|
unsigned int length;
|
|
|
|
REQUIRE(dbresult == DNS_R_NXRRSET || dbresult == DNS_R_NCACHENXRRSET);
|
|
|
|
dns_rdataset_init(&set);
|
|
if (dbresult == DNS_R_NXRRSET) {
|
|
dns_rdataset_clone(rdataset, &set);
|
|
} else {
|
|
result = dns_ncache_getrdataset(rdataset, name,
|
|
dns_rdatatype_nsec, &set);
|
|
if (result == ISC_R_NOTFOUND) {
|
|
goto trynsec3;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
INSIST(set.type == dns_rdatatype_nsec);
|
|
|
|
found = false;
|
|
result = dns_rdataset_first(&set);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_rdataset_current(&set, &rdata);
|
|
found = dns_nsec_typepresent(&rdata, dns_rdatatype_ns);
|
|
dns_rdata_reset(&rdata);
|
|
}
|
|
dns_rdataset_disassociate(&set);
|
|
return (found);
|
|
|
|
trynsec3:
|
|
/*
|
|
* Iterate over the ncache entry.
|
|
*/
|
|
found = false;
|
|
dns_name_init(&nsec3name, NULL);
|
|
dns_fixedname_init(&fixed);
|
|
dns_name_downcase(name, dns_fixedname_name(&fixed), NULL);
|
|
name = dns_fixedname_name(&fixed);
|
|
for (result = dns_rdataset_first(rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset))
|
|
{
|
|
dns_ncache_current(rdataset, &nsec3name, &set);
|
|
if (set.type != dns_rdatatype_nsec3) {
|
|
dns_rdataset_disassociate(&set);
|
|
continue;
|
|
}
|
|
dns_name_getlabel(&nsec3name, 0, &hashlabel);
|
|
isc_region_consume(&hashlabel, 1);
|
|
isc_buffer_init(&buffer, owner, sizeof(owner));
|
|
result = isc_base32hexnp_decoderegion(&hashlabel, &buffer);
|
|
if (result != ISC_R_SUCCESS) {
|
|
dns_rdataset_disassociate(&set);
|
|
continue;
|
|
}
|
|
for (result = dns_rdataset_first(&set);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(&set))
|
|
{
|
|
dns_rdata_reset(&rdata);
|
|
dns_rdataset_current(&set, &rdata);
|
|
(void)dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
|
if (nsec3.hash != 1) {
|
|
continue;
|
|
}
|
|
length = isc_iterated_hash(hash, nsec3.hash,
|
|
nsec3.iterations, nsec3.salt,
|
|
nsec3.salt_length,
|
|
name->ndata, name->length);
|
|
if (length != isc_buffer_usedlength(&buffer)) {
|
|
continue;
|
|
}
|
|
order = memcmp(hash, owner, length);
|
|
if (order == 0) {
|
|
found = dns_nsec3_typepresent(&rdata,
|
|
dns_rdatatype_ns);
|
|
dns_rdataset_disassociate(&set);
|
|
return (found);
|
|
}
|
|
if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) {
|
|
continue;
|
|
}
|
|
/*
|
|
* Does this optout span cover the name?
|
|
*/
|
|
scope = memcmp(owner, nsec3.next, nsec3.next_length);
|
|
if ((scope < 0 && order > 0 &&
|
|
memcmp(hash, nsec3.next, length) < 0) ||
|
|
(scope >= 0 && (order > 0 ||
|
|
memcmp(hash, nsec3.next,
|
|
length) < 0)))
|
|
{
|
|
dns_rdataset_disassociate(&set);
|
|
return (true);
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&set);
|
|
}
|
|
return (found);
|
|
}
|
|
|
|
/*%
|
|
* We have been asked to look for a key.
|
|
* If found, resume the validation process.
|
|
* If not found, fail the validation process.
|
|
*/
|
|
static void
|
|
fetch_callback_dnskey(isc_task_t *task, isc_event_t *event) {
|
|
dns_fetchevent_t *devent;
|
|
dns_validator_t *val;
|
|
dns_rdataset_t *rdataset;
|
|
bool want_destroy;
|
|
isc_result_t result;
|
|
isc_result_t eresult;
|
|
isc_result_t saved_result;
|
|
dns_fetch_t *fetch;
|
|
|
|
UNUSED(task);
|
|
INSIST(event->ev_type == DNS_EVENT_FETCHDONE);
|
|
devent = (dns_fetchevent_t *)event;
|
|
val = devent->ev_arg;
|
|
rdataset = &val->frdataset;
|
|
eresult = devent->result;
|
|
|
|
/* Free resources which are not of interest. */
|
|
if (devent->node != NULL) {
|
|
dns_db_detachnode(devent->db, &devent->node);
|
|
}
|
|
if (devent->db != NULL) {
|
|
dns_db_detach(&devent->db);
|
|
}
|
|
if (dns_rdataset_isassociated(&val->fsigrdataset)) {
|
|
dns_rdataset_disassociate(&val->fsigrdataset);
|
|
}
|
|
isc_event_free(&event);
|
|
|
|
INSIST(val->event != NULL);
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey");
|
|
LOCK(&val->lock);
|
|
fetch = val->fetch;
|
|
val->fetch = NULL;
|
|
if (CANCELED(val)) {
|
|
validator_done(val, ISC_R_CANCELED);
|
|
} else if (eresult == ISC_R_SUCCESS) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"keyset with trust %s",
|
|
dns_trust_totext(rdataset->trust));
|
|
/*
|
|
* Only extract the dst key if the keyset is secure.
|
|
*/
|
|
if (rdataset->trust >= dns_trust_secure) {
|
|
result = select_signing_key(val, rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
val->keyset = &val->frdataset;
|
|
}
|
|
}
|
|
result = validate_answer(val, true);
|
|
if (result == DNS_R_NOVALIDSIG &&
|
|
(val->attributes & VALATTR_TRIEDVERIFY) == 0)
|
|
{
|
|
saved_result = result;
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"falling back to insecurity proof");
|
|
result = proveunsecure(val, false, false);
|
|
if (result == DNS_R_NOTINSECURE) {
|
|
result = saved_result;
|
|
}
|
|
}
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
} else {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"fetch_callback_dnskey: got %s",
|
|
isc_result_totext(eresult));
|
|
if (eresult == ISC_R_CANCELED) {
|
|
validator_done(val, eresult);
|
|
} else {
|
|
validator_done(val, DNS_R_BROKENCHAIN);
|
|
}
|
|
}
|
|
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
|
|
if (fetch != NULL) {
|
|
dns_resolver_destroyfetch(&fetch);
|
|
}
|
|
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* We have been asked to look for a DS. This may be part of
|
|
* walking a trust chain, or an insecurity proof.
|
|
*/
|
|
static void
|
|
fetch_callback_ds(isc_task_t *task, isc_event_t *event) {
|
|
dns_fetchevent_t *devent;
|
|
dns_validator_t *val;
|
|
dns_rdataset_t *rdataset;
|
|
bool want_destroy, trustchain;
|
|
isc_result_t result;
|
|
isc_result_t eresult;
|
|
dns_fetch_t *fetch;
|
|
|
|
UNUSED(task);
|
|
INSIST(event->ev_type == DNS_EVENT_FETCHDONE);
|
|
devent = (dns_fetchevent_t *)event;
|
|
val = devent->ev_arg;
|
|
rdataset = &val->frdataset;
|
|
eresult = devent->result;
|
|
|
|
/*
|
|
* Set 'trustchain' to true if we're walking a chain of
|
|
* trust; false if we're attempting to prove insecurity.
|
|
*/
|
|
trustchain = ((val->attributes & VALATTR_INSECURITY) == 0);
|
|
|
|
/* Free resources which are not of interest. */
|
|
if (devent->node != NULL) {
|
|
dns_db_detachnode(devent->db, &devent->node);
|
|
}
|
|
if (devent->db != NULL) {
|
|
dns_db_detach(&devent->db);
|
|
}
|
|
if (dns_rdataset_isassociated(&val->fsigrdataset)) {
|
|
dns_rdataset_disassociate(&val->fsigrdataset);
|
|
}
|
|
|
|
INSIST(val->event != NULL);
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds");
|
|
LOCK(&val->lock);
|
|
fetch = val->fetch;
|
|
val->fetch = NULL;
|
|
|
|
if (CANCELED(val)) {
|
|
validator_done(val, ISC_R_CANCELED);
|
|
goto done;
|
|
}
|
|
|
|
switch (eresult) {
|
|
case DNS_R_NXDOMAIN:
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
/*
|
|
* These results only make sense if we're attempting
|
|
* an insecurity proof, not when walking a chain of trust.
|
|
*/
|
|
if (trustchain) {
|
|
goto unexpected;
|
|
}
|
|
|
|
/* FALLTHROUGH */
|
|
case ISC_R_SUCCESS:
|
|
if (trustchain) {
|
|
/*
|
|
* We looked for a DS record as part of
|
|
* following a key chain upwards; resume following
|
|
* the chain.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"dsset with trust %s",
|
|
dns_trust_totext(rdataset->trust));
|
|
val->dsset = &val->frdataset;
|
|
INSIST(val->keynode == NULL);
|
|
result = validate_dnskey(val);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
} else {
|
|
/*
|
|
* There is a DS which may or may not be a zone cut.
|
|
* In either case we are still in a secure zone,
|
|
* so keep looking for the break in the chain
|
|
* of trust.
|
|
*/
|
|
result = proveunsecure(val, (eresult == ISC_R_SUCCESS),
|
|
true);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
}
|
|
break;
|
|
case DNS_R_CNAME:
|
|
case DNS_R_NXRRSET:
|
|
case DNS_R_NCACHENXRRSET:
|
|
case DNS_R_SERVFAIL: /* RFC 1034 parent? */
|
|
if (trustchain) {
|
|
/*
|
|
* Failed to find a DS while following the
|
|
* chain of trust; now we need to prove insecurity.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"falling back to insecurity proof (%s)",
|
|
dns_result_totext(eresult));
|
|
result = proveunsecure(val, false, false);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
} else if (eresult == DNS_R_SERVFAIL) {
|
|
goto unexpected;
|
|
} else if (eresult != DNS_R_CNAME &&
|
|
isdelegation(dns_fixedname_name(&devent->foundname),
|
|
&val->frdataset, eresult))
|
|
{
|
|
/*
|
|
* Failed to find a DS while trying to prove
|
|
* insecurity. If this is a zone cut, that
|
|
* means we're insecure.
|
|
*/
|
|
result = markanswer(val, "fetch_callback_ds",
|
|
"no DS and this is a delegation");
|
|
validator_done(val, result);
|
|
} else {
|
|
/*
|
|
* Not a zone cut, so we have to keep looking for
|
|
* the break point in the chain of trust.
|
|
*/
|
|
result = proveunsecure(val, false, true);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
unexpected:
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"fetch_callback_ds: got %s",
|
|
isc_result_totext(eresult));
|
|
if (eresult == ISC_R_CANCELED) {
|
|
validator_done(val, eresult);
|
|
} else {
|
|
validator_done(val, DNS_R_BROKENCHAIN);
|
|
}
|
|
}
|
|
done:
|
|
|
|
isc_event_free(&event);
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
|
|
if (fetch != NULL) {
|
|
dns_resolver_destroyfetch(&fetch);
|
|
}
|
|
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Callback from when a DNSKEY RRset has been validated.
|
|
*
|
|
* Resumes the stalled validation process.
|
|
*/
|
|
static void
|
|
validator_callback_dnskey(isc_task_t *task, isc_event_t *event) {
|
|
dns_validatorevent_t *devent;
|
|
dns_validator_t *val;
|
|
bool want_destroy;
|
|
isc_result_t result;
|
|
isc_result_t eresult;
|
|
isc_result_t saved_result;
|
|
|
|
UNUSED(task);
|
|
INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
|
|
|
|
devent = (dns_validatorevent_t *)event;
|
|
val = devent->ev_arg;
|
|
eresult = devent->result;
|
|
|
|
isc_event_free(&event);
|
|
dns_validator_destroy(&val->subvalidator);
|
|
|
|
INSIST(val->event != NULL);
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
|
|
LOCK(&val->lock);
|
|
if (CANCELED(val)) {
|
|
validator_done(val, ISC_R_CANCELED);
|
|
} else if (eresult == ISC_R_SUCCESS) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"keyset with trust %s",
|
|
dns_trust_totext(val->frdataset.trust));
|
|
/*
|
|
* Only extract the dst key if the keyset is secure.
|
|
*/
|
|
if (val->frdataset.trust >= dns_trust_secure) {
|
|
(void) select_signing_key(val, &val->frdataset);
|
|
}
|
|
result = validate_answer(val, true);
|
|
if (result == DNS_R_NOVALIDSIG &&
|
|
(val->attributes & VALATTR_TRIEDVERIFY) == 0)
|
|
{
|
|
saved_result = result;
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"falling back to insecurity proof");
|
|
result = proveunsecure(val, false, false);
|
|
if (result == DNS_R_NOTINSECURE) {
|
|
result = saved_result;
|
|
}
|
|
}
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
} else {
|
|
if (eresult != DNS_R_BROKENCHAIN) {
|
|
expire_rdatasets(val);
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"validator_callback_dnskey: got %s",
|
|
isc_result_totext(eresult));
|
|
validator_done(val, DNS_R_BROKENCHAIN);
|
|
}
|
|
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Callback when the DS record has been validated.
|
|
*
|
|
* Resumes validation of the zone key or the unsecure zone proof.
|
|
*/
|
|
static void
|
|
validator_callback_ds(isc_task_t *task, isc_event_t *event) {
|
|
dns_validatorevent_t *devent;
|
|
dns_validator_t *val;
|
|
bool want_destroy;
|
|
isc_result_t result;
|
|
isc_result_t eresult;
|
|
|
|
UNUSED(task);
|
|
INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
|
|
|
|
devent = (dns_validatorevent_t *)event;
|
|
val = devent->ev_arg;
|
|
eresult = devent->result;
|
|
|
|
isc_event_free(&event);
|
|
dns_validator_destroy(&val->subvalidator);
|
|
|
|
INSIST(val->event != NULL);
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
|
|
LOCK(&val->lock);
|
|
if (CANCELED(val)) {
|
|
validator_done(val, ISC_R_CANCELED);
|
|
} else if (eresult == ISC_R_SUCCESS) {
|
|
bool have_dsset;
|
|
dns_name_t *name;
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"%s with trust %s",
|
|
val->frdataset.type == dns_rdatatype_ds ?
|
|
"dsset" : "ds non-existence",
|
|
dns_trust_totext(val->frdataset.trust));
|
|
have_dsset = (val->frdataset.type == dns_rdatatype_ds);
|
|
name = dns_fixedname_name(&val->fname);
|
|
if ((val->attributes & VALATTR_INSECURITY) != 0 &&
|
|
val->frdataset.covers == dns_rdatatype_ds &&
|
|
NEGATIVE(&val->frdataset) &&
|
|
isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET))
|
|
{
|
|
result = markanswer(val, "validator_callback_ds",
|
|
"no DS and this is a delegation");
|
|
} else if ((val->attributes & VALATTR_INSECURITY) != 0) {
|
|
result = proveunsecure(val, have_dsset, true);
|
|
} else {
|
|
result = validate_dnskey(val);
|
|
}
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
} else {
|
|
if (eresult != DNS_R_BROKENCHAIN) {
|
|
expire_rdatasets(val);
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"validator_callback_ds: got %s",
|
|
isc_result_totext(eresult));
|
|
validator_done(val, DNS_R_BROKENCHAIN);
|
|
}
|
|
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Callback when the CNAME record has been validated.
|
|
*
|
|
* Resumes validation of the unsecure zone proof.
|
|
*/
|
|
static void
|
|
validator_callback_cname(isc_task_t *task, isc_event_t *event) {
|
|
dns_validatorevent_t *devent;
|
|
dns_validator_t *val;
|
|
bool want_destroy;
|
|
isc_result_t result;
|
|
isc_result_t eresult;
|
|
|
|
UNUSED(task);
|
|
INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
|
|
|
|
devent = (dns_validatorevent_t *)event;
|
|
val = devent->ev_arg;
|
|
eresult = devent->result;
|
|
|
|
isc_event_free(&event);
|
|
dns_validator_destroy(&val->subvalidator);
|
|
|
|
INSIST(val->event != NULL);
|
|
INSIST((val->attributes & VALATTR_INSECURITY) != 0);
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
|
|
LOCK(&val->lock);
|
|
if (CANCELED(val)) {
|
|
validator_done(val, ISC_R_CANCELED);
|
|
} else if (eresult == ISC_R_SUCCESS) {
|
|
validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s",
|
|
dns_trust_totext(val->frdataset.trust));
|
|
result = proveunsecure(val, false, true);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
} else {
|
|
if (eresult != DNS_R_BROKENCHAIN) {
|
|
expire_rdatasets(val);
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"validator_callback_cname: got %s",
|
|
isc_result_totext(eresult));
|
|
validator_done(val, DNS_R_BROKENCHAIN);
|
|
}
|
|
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Callback for when NSEC records have been validated.
|
|
*
|
|
* Looks for NOQNAME, NODATA and OPTOUT proofs.
|
|
*
|
|
* Resumes the negative response validation by calling validate_nx().
|
|
*/
|
|
static void
|
|
validator_callback_nsec(isc_task_t *task, isc_event_t *event) {
|
|
dns_validatorevent_t *devent;
|
|
dns_validator_t *val;
|
|
dns_rdataset_t *rdataset;
|
|
bool want_destroy;
|
|
isc_result_t result;
|
|
bool exists, data;
|
|
|
|
UNUSED(task);
|
|
INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE);
|
|
|
|
devent = (dns_validatorevent_t *)event;
|
|
rdataset = devent->rdataset;
|
|
val = devent->ev_arg;
|
|
result = devent->result;
|
|
dns_validator_destroy(&val->subvalidator);
|
|
|
|
INSIST(val->event != NULL);
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
|
|
LOCK(&val->lock);
|
|
if (CANCELED(val)) {
|
|
validator_done(val, ISC_R_CANCELED);
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"validator_callback_nsec: got %s",
|
|
isc_result_totext(result));
|
|
if (result == DNS_R_BROKENCHAIN) {
|
|
val->authfail++;
|
|
}
|
|
if (result == ISC_R_CANCELED) {
|
|
validator_done(val, result);
|
|
} else {
|
|
result = validate_nx(val, true);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
}
|
|
} else {
|
|
dns_name_t **proofs = val->event->proofs;
|
|
dns_name_t *wild = dns_fixedname_name(&val->wild);
|
|
|
|
if (rdataset->type == dns_rdatatype_nsec &&
|
|
rdataset->trust == dns_trust_secure &&
|
|
(NEEDNODATA(val) || NEEDNOQNAME(val)) &&
|
|
!FOUNDNODATA(val) && !FOUNDNOQNAME(val) &&
|
|
dns_nsec_noexistnodata(val->event->type, val->event->name,
|
|
devent->name, rdataset, &exists,
|
|
&data, wild, validator_log, val)
|
|
== ISC_R_SUCCESS)
|
|
{
|
|
if (exists && !data) {
|
|
val->attributes |= VALATTR_FOUNDNODATA;
|
|
if (NEEDNODATA(val)) {
|
|
proofs[DNS_VALIDATOR_NODATAPROOF] =
|
|
devent->name;
|
|
}
|
|
}
|
|
if (!exists) {
|
|
dns_name_t *closest;
|
|
unsigned int clabels;
|
|
|
|
val->attributes |= VALATTR_FOUNDNOQNAME;
|
|
|
|
closest = dns_fixedname_name(&val->closest);
|
|
clabels = dns_name_countlabels(closest);
|
|
/*
|
|
* If we are validating a wildcard response
|
|
* clabels will not be zero. We then need
|
|
* to check if the generated wilcard from
|
|
* dns_nsec_noexistnodata is consistent with
|
|
* the wildcard used to generate the response.
|
|
*/
|
|
if (clabels == 0 ||
|
|
dns_name_countlabels(wild) == clabels + 1)
|
|
{
|
|
val->attributes |= VALATTR_FOUNDCLOSEST;
|
|
}
|
|
/*
|
|
* The NSEC noqname proof also contains
|
|
* the closest encloser.
|
|
*/
|
|
if (NEEDNOQNAME(val)) {
|
|
proofs[DNS_VALIDATOR_NOQNAMEPROOF] =
|
|
devent->name;
|
|
}
|
|
}
|
|
}
|
|
|
|
result = validate_nx(val, true);
|
|
if (result != DNS_R_WAIT) {
|
|
validator_done(val, result);
|
|
}
|
|
}
|
|
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
|
|
/*
|
|
* Free stuff from the event.
|
|
*/
|
|
isc_event_free(&event);
|
|
}
|
|
|
|
/*%
|
|
* Looks for the requested name and type in the view (zones and cache).
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_SUCCESS
|
|
* \li ISC_R_NOTFOUND
|
|
* \li DNS_R_NCACHENXDOMAIN
|
|
* \li DNS_R_NCACHENXRRSET
|
|
* \li DNS_R_NXRRSET
|
|
* \li DNS_R_NXDOMAIN
|
|
* \li DNS_R_BROKENCHAIN
|
|
*/
|
|
static inline isc_result_t
|
|
view_find(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) {
|
|
dns_fixedname_t fixedname;
|
|
dns_name_t *foundname;
|
|
isc_result_t result;
|
|
unsigned int options;
|
|
isc_time_t now;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
disassociate_rdatasets(val);
|
|
|
|
if (isc_time_now(&now) == ISC_R_SUCCESS &&
|
|
dns_resolver_getbadcache(val->view->resolver, name, type, &now))
|
|
{
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
|
|
validator_log(val, ISC_LOG_INFO, "bad cache hit (%s/%s)",
|
|
namebuf, typebuf);
|
|
return (DNS_R_BROKENCHAIN);
|
|
}
|
|
|
|
options = DNS_DBFIND_PENDINGOK;
|
|
foundname = dns_fixedname_initname(&fixedname);
|
|
result = dns_view_find(val->view, name, type, 0, options,
|
|
false, false, NULL, NULL, foundname,
|
|
&val->frdataset, &val->fsigrdataset);
|
|
|
|
if (result == DNS_R_NXDOMAIN) {
|
|
goto notfound;
|
|
} else if (result != ISC_R_SUCCESS &&
|
|
result != DNS_R_NCACHENXDOMAIN &&
|
|
result != DNS_R_NCACHENXRRSET &&
|
|
result != DNS_R_EMPTYNAME &&
|
|
result != DNS_R_NXRRSET &&
|
|
result != ISC_R_NOTFOUND)
|
|
{
|
|
result = ISC_R_NOTFOUND;
|
|
goto notfound;
|
|
}
|
|
|
|
return (result);
|
|
|
|
notfound:
|
|
disassociate_rdatasets(val);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Checks to make sure we are not going to loop. As we use a SHARED fetch
|
|
* the validation process will stall if looping was to occur.
|
|
*/
|
|
static inline bool
|
|
check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset)
|
|
{
|
|
dns_validator_t *parent;
|
|
|
|
for (parent = val; parent != NULL; parent = parent->parent) {
|
|
if (parent->event != NULL &&
|
|
parent->event->type == type &&
|
|
dns_name_equal(parent->event->name, name) &&
|
|
/*
|
|
* As NSEC3 records are meta data you sometimes
|
|
* need to prove a NSEC3 record which says that
|
|
* itself doesn't exist.
|
|
*/
|
|
(parent->event->type != dns_rdatatype_nsec3 ||
|
|
rdataset == NULL || sigrdataset == NULL ||
|
|
parent->event->message == NULL ||
|
|
parent->event->rdataset != NULL ||
|
|
parent->event->sigrdataset != NULL))
|
|
{
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"continuing validation would lead to "
|
|
"deadlock: aborting validation");
|
|
return (true);
|
|
}
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
/*%
|
|
* Start a fetch for the requested name and type.
|
|
*/
|
|
static inline isc_result_t
|
|
create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
|
|
isc_taskaction_t callback, const char *caller)
|
|
{
|
|
unsigned int fopts = 0;
|
|
|
|
disassociate_rdatasets(val);
|
|
|
|
if (check_deadlock(val, name, type, NULL, NULL)) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"deadlock found (create_fetch)");
|
|
return (DNS_R_NOVALIDSIG);
|
|
}
|
|
|
|
if ((val->options & DNS_VALIDATOR_NOCDFLAG) != 0) {
|
|
fopts |= DNS_FETCHOPT_NOCDFLAG;
|
|
}
|
|
|
|
if ((val->options & DNS_VALIDATOR_NONTA) != 0) {
|
|
fopts |= DNS_FETCHOPT_NONTA;
|
|
}
|
|
|
|
validator_logcreate(val, name, type, caller, "fetch");
|
|
return (dns_resolver_createfetch(val->view->resolver, name, type,
|
|
NULL, NULL, NULL, NULL, 0, fopts,
|
|
0, NULL, val->event->ev_sender,
|
|
callback, val,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
&val->fetch));
|
|
}
|
|
|
|
/*%
|
|
* Start a subvalidation process.
|
|
*/
|
|
static inline isc_result_t
|
|
create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
|
|
isc_taskaction_t action, const char *caller)
|
|
{
|
|
isc_result_t result;
|
|
unsigned int vopts = 0;
|
|
dns_rdataset_t *sig = NULL;
|
|
|
|
if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) {
|
|
sig = sigrdataset;
|
|
}
|
|
|
|
if (check_deadlock(val, name, type, rdataset, sig)) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"deadlock found (create_validator)");
|
|
return (DNS_R_NOVALIDSIG);
|
|
}
|
|
|
|
/* OK to clear other options, but preserve NOCDFLAG and NONTA. */
|
|
vopts |= (val->options & (DNS_VALIDATOR_NOCDFLAG |
|
|
DNS_VALIDATOR_NONTA));
|
|
|
|
validator_logcreate(val, name, type, caller, "validator");
|
|
result = dns_validator_create(val->view, name, type,
|
|
rdataset, sig, NULL,
|
|
vopts, val->task, action, val,
|
|
&val->subvalidator);
|
|
if (result == ISC_R_SUCCESS) {
|
|
val->subvalidator->parent = val;
|
|
val->subvalidator->depth = val->depth + 1;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Try to find a key that could have signed val->siginfo among those in
|
|
* 'rdataset'. If found, build a dst_key_t for it and point val->key at
|
|
* it.
|
|
*
|
|
* If val->key is already non-NULL, locate it in the rdataset and then
|
|
* search past it for the *next* key that could have signed 'siginfo', then
|
|
* set val->key to that.
|
|
*
|
|
* Returns ISC_R_SUCCESS if a possible matching key has been found,
|
|
* ISC_R_NOTFOUND if not. Any other value indicates error.
|
|
*/
|
|
static isc_result_t
|
|
select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) {
|
|
isc_result_t result;
|
|
dns_rdata_rrsig_t *siginfo = val->siginfo;
|
|
isc_buffer_t b;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dst_key_t *oldkey = val->key;
|
|
bool foundold;
|
|
|
|
if (oldkey == NULL) {
|
|
foundold = true;
|
|
} else {
|
|
foundold = false;
|
|
val->key = NULL;
|
|
}
|
|
|
|
result = dns_rdataset_first(rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto failure;
|
|
}
|
|
do {
|
|
dns_rdataset_current(rdataset, &rdata);
|
|
|
|
isc_buffer_init(&b, rdata.data, rdata.length);
|
|
isc_buffer_add(&b, rdata.length);
|
|
INSIST(val->key == NULL);
|
|
result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b,
|
|
val->view->mctx, &val->key);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto failure;
|
|
}
|
|
if (siginfo->algorithm ==
|
|
(dns_secalg_t)dst_key_alg(val->key) &&
|
|
siginfo->keyid ==
|
|
(dns_keytag_t)dst_key_id(val->key) &&
|
|
dst_key_iszonekey(val->key))
|
|
{
|
|
if (foundold) {
|
|
/*
|
|
* This is the key we're looking for.
|
|
*/
|
|
return (ISC_R_SUCCESS);
|
|
} else if (dst_key_compare(oldkey, val->key)) {
|
|
foundold = true;
|
|
dst_key_free(&oldkey);
|
|
}
|
|
}
|
|
dst_key_free(&val->key);
|
|
dns_rdata_reset(&rdata);
|
|
result = dns_rdataset_next(rdataset);
|
|
} while (result == ISC_R_SUCCESS);
|
|
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_NOTFOUND;
|
|
}
|
|
|
|
failure:
|
|
if (oldkey != NULL) {
|
|
dst_key_free(&oldkey);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Get the key that generated the signature in val->siginfo.
|
|
*/
|
|
static isc_result_t
|
|
seek_dnskey(dns_validator_t *val) {
|
|
isc_result_t result;
|
|
dns_rdata_rrsig_t *siginfo = val->siginfo;
|
|
unsigned int nlabels;
|
|
int order;
|
|
dns_namereln_t namereln;
|
|
|
|
/*
|
|
* Is the signer name appropriate for this signature?
|
|
*
|
|
* The signer name must be at the same level as the owner name
|
|
* or closer to the DNS root.
|
|
*/
|
|
namereln = dns_name_fullcompare(val->event->name, &siginfo->signer,
|
|
&order, &nlabels);
|
|
if (namereln != dns_namereln_subdomain &&
|
|
namereln != dns_namereln_equal)
|
|
{
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
|
|
if (namereln == dns_namereln_equal) {
|
|
/*
|
|
* If this is a self-signed keyset, it must not be a zone key
|
|
* (since seek_dnskey is not called from validate_dnskey).
|
|
*/
|
|
if (val->event->rdataset->type == dns_rdatatype_dnskey) {
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
|
|
/*
|
|
* Records appearing in the parent zone at delegation
|
|
* points cannot be self-signed.
|
|
*/
|
|
if (dns_rdatatype_atparent(val->event->rdataset->type)) {
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
} else {
|
|
/*
|
|
* SOA and NS RRsets can only be signed by a key with
|
|
* the same name.
|
|
*/
|
|
if (val->event->rdataset->type == dns_rdatatype_soa ||
|
|
val->event->rdataset->type == dns_rdatatype_ns)
|
|
{
|
|
const char *type;
|
|
|
|
if (val->event->rdataset->type == dns_rdatatype_soa) {
|
|
type = "SOA";
|
|
} else {
|
|
type = "NS";
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"%s signer mismatch", type);
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do we know about this key?
|
|
*/
|
|
result = view_find(val, &siginfo->signer, dns_rdatatype_dnskey);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
/*
|
|
* We have an rrset for the given keyname.
|
|
*/
|
|
val->keyset = &val->frdataset;
|
|
if ((DNS_TRUST_PENDING(val->frdataset.trust) ||
|
|
DNS_TRUST_ANSWER(val->frdataset.trust)) &&
|
|
dns_rdataset_isassociated(&val->fsigrdataset))
|
|
{
|
|
/*
|
|
* We know the key but haven't validated it yet or
|
|
* we have a key of trust answer but a DS
|
|
* record for the zone may have been added.
|
|
*/
|
|
result = create_validator(val, &siginfo->signer,
|
|
dns_rdatatype_dnskey,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
validator_callback_dnskey,
|
|
"seek_dnskey");
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
return (DNS_R_WAIT);
|
|
} else if (DNS_TRUST_PENDING(val->frdataset.trust)) {
|
|
/*
|
|
* Having a pending key with no signature means that
|
|
* something is broken.
|
|
*/
|
|
result = DNS_R_CONTINUE;
|
|
} else if (val->frdataset.trust < dns_trust_secure) {
|
|
/*
|
|
* The key is legitimately insecure. There's no
|
|
* point in even attempting verification.
|
|
*/
|
|
val->key = NULL;
|
|
result = ISC_R_SUCCESS;
|
|
} else {
|
|
/*
|
|
* See if we've got the key used in the signature.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"keyset with trust %s",
|
|
dns_trust_totext(val->frdataset.trust));
|
|
result = select_signing_key(val, val->keyset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
/*
|
|
* Either the key we're looking for is not
|
|
* in the rrset, or something bad happened.
|
|
* Give up.
|
|
*/
|
|
result = DNS_R_CONTINUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ISC_R_NOTFOUND:
|
|
/*
|
|
* We don't know anything about this key.
|
|
*/
|
|
result = create_fetch(val, &siginfo->signer,
|
|
dns_rdatatype_dnskey,
|
|
fetch_callback_dnskey, "seek_dnskey");
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
return (DNS_R_WAIT);
|
|
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
case DNS_R_NCACHENXRRSET:
|
|
case DNS_R_EMPTYNAME:
|
|
case DNS_R_NXDOMAIN:
|
|
case DNS_R_NXRRSET:
|
|
/*
|
|
* This key doesn't exist.
|
|
*/
|
|
result = DNS_R_CONTINUE;
|
|
break;
|
|
|
|
case DNS_R_BROKENCHAIN:
|
|
return (result);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (dns_rdataset_isassociated(&val->frdataset) &&
|
|
val->keyset != &val->frdataset)
|
|
{
|
|
dns_rdataset_disassociate(&val->frdataset);
|
|
}
|
|
if (dns_rdataset_isassociated(&val->fsigrdataset)) {
|
|
dns_rdataset_disassociate(&val->fsigrdataset);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Compute the tag for a key represented in a DNSKEY rdata.
|
|
*/
|
|
static dns_keytag_t
|
|
compute_keytag(dns_rdata_t *rdata) {
|
|
isc_region_t r;
|
|
|
|
dns_rdata_toregion(rdata, &r);
|
|
return (dst_region_computeid(&r));
|
|
}
|
|
|
|
/*%
|
|
* Is the DNSKEY rrset in val->event->rdataset self-signed?
|
|
*/
|
|
static bool
|
|
selfsigned_dnskey(dns_validator_t *val) {
|
|
dns_rdataset_t *rdataset = val->event->rdataset;
|
|
dns_rdataset_t *sigrdataset = val->event->sigrdataset;
|
|
dns_name_t *name = val->event->name;
|
|
isc_result_t result;
|
|
isc_mem_t *mctx = val->view->mctx;
|
|
bool answer = false;
|
|
|
|
if (rdataset->type != dns_rdatatype_dnskey) {
|
|
return (false);
|
|
}
|
|
|
|
for (result = dns_rdataset_first(rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset))
|
|
{
|
|
dns_rdata_t keyrdata = DNS_RDATA_INIT;
|
|
dns_rdata_t sigrdata = DNS_RDATA_INIT;
|
|
dns_rdata_dnskey_t key;
|
|
dns_rdata_rrsig_t sig;
|
|
dns_keytag_t keytag;
|
|
|
|
dns_rdata_reset(&keyrdata);
|
|
dns_rdataset_current(rdataset, &keyrdata);
|
|
result = dns_rdata_tostruct(&keyrdata, &key, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
keytag = compute_keytag(&keyrdata);
|
|
|
|
for (result = dns_rdataset_first(sigrdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(sigrdataset))
|
|
{
|
|
dst_key_t *dstkey = NULL;
|
|
|
|
dns_rdata_reset(&sigrdata);
|
|
dns_rdataset_current(sigrdataset, &sigrdata);
|
|
result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (sig.algorithm != key.algorithm ||
|
|
sig.keyid != keytag ||
|
|
!dns_name_equal(name, &sig.signer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx,
|
|
&dstkey);
|
|
if (result != ISC_R_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
result = dns_dnssec_verify(name, rdataset, dstkey,
|
|
true,
|
|
val->view->maxbits,
|
|
mctx, &sigrdata, NULL);
|
|
dst_key_free(&dstkey);
|
|
if (result != ISC_R_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
|
|
answer = true;
|
|
continue;
|
|
}
|
|
|
|
dns_view_untrust(val->view, name, &key);
|
|
}
|
|
}
|
|
|
|
return (answer);
|
|
}
|
|
|
|
/*%
|
|
* Attempt to verify the rdataset using the given key and rdata (RRSIG).
|
|
* The signature was good and from a wildcard record and the QNAME does
|
|
* not match the wildcard we need to look for a NOQNAME proof.
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_SUCCESS if the verification succeeds.
|
|
* \li Others if the verification fails.
|
|
*/
|
|
static isc_result_t
|
|
verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
|
|
uint16_t keyid)
|
|
{
|
|
isc_result_t result;
|
|
dns_fixedname_t fixed;
|
|
bool ignore = false;
|
|
dns_name_t *wild;
|
|
|
|
val->attributes |= VALATTR_TRIEDVERIFY;
|
|
wild = dns_fixedname_initname(&fixed);
|
|
again:
|
|
result = dns_dnssec_verify(val->event->name, val->event->rdataset,
|
|
key, ignore, val->view->maxbits,
|
|
val->view->mctx, rdata, wild);
|
|
if ((result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) &&
|
|
val->view->acceptexpired)
|
|
{
|
|
ignore = true;
|
|
goto again;
|
|
}
|
|
|
|
if (ignore &&
|
|
(result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD))
|
|
{
|
|
validator_log(val, ISC_LOG_INFO,
|
|
"accepted expired %sRRSIG (keyid=%u)",
|
|
(result == DNS_R_FROMWILDCARD) ?
|
|
"wildcard " : "", keyid);
|
|
} else if (result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) {
|
|
validator_log(val, ISC_LOG_INFO,
|
|
"verify failed due to bad signature (keyid=%u): "
|
|
"%s", keyid, isc_result_totext(result));
|
|
} else {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"verify rdataset (keyid=%u): %s",
|
|
keyid, isc_result_totext(result));
|
|
}
|
|
if (result == DNS_R_FROMWILDCARD) {
|
|
if (!dns_name_equal(val->event->name, wild)) {
|
|
dns_name_t *closest;
|
|
unsigned int labels;
|
|
|
|
/*
|
|
* Compute the closest encloser in case we need it
|
|
* for the NSEC3 NOQNAME proof.
|
|
*/
|
|
closest = dns_fixedname_name(&val->closest);
|
|
dns_name_copynf(wild, closest);
|
|
labels = dns_name_countlabels(closest) - 1;
|
|
dns_name_getlabelsequence(closest, 1, labels, closest);
|
|
val->attributes |= VALATTR_NEEDNOQNAME;
|
|
}
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Attempts positive response validation of a normal RRset.
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_SUCCESS Validation completed successfully
|
|
* \li DNS_R_WAIT Validation has started but is waiting
|
|
* for an event.
|
|
* \li Other return codes are possible and all indicate failure.
|
|
*/
|
|
static isc_result_t
|
|
validate_answer(dns_validator_t *val, bool resume) {
|
|
isc_result_t result, vresult = DNS_R_NOVALIDSIG;
|
|
dns_validatorevent_t *event;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
/*
|
|
* Caller must be holding the validator lock.
|
|
*/
|
|
|
|
event = val->event;
|
|
|
|
if (resume) {
|
|
/*
|
|
* We already have a sigrdataset.
|
|
*/
|
|
result = ISC_R_SUCCESS;
|
|
validator_log(val, ISC_LOG_DEBUG(3), "resuming validate");
|
|
} else {
|
|
result = dns_rdataset_first(event->sigrdataset);
|
|
}
|
|
|
|
for (;
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(event->sigrdataset))
|
|
{
|
|
dns_rdata_reset(&rdata);
|
|
dns_rdataset_current(event->sigrdataset, &rdata);
|
|
if (val->siginfo == NULL) {
|
|
val->siginfo = isc_mem_get(val->view->mctx,
|
|
sizeof(*val->siginfo));
|
|
}
|
|
result = dns_rdata_tostruct(&rdata, val->siginfo, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* At this point we could check that the signature algorithm
|
|
* was known and "sufficiently good".
|
|
*/
|
|
if (!dns_resolver_algorithm_supported(val->view->resolver,
|
|
event->name,
|
|
val->siginfo->algorithm))
|
|
{
|
|
resume = false;
|
|
continue;
|
|
}
|
|
|
|
if (!resume) {
|
|
result = seek_dnskey(val);
|
|
if (result == DNS_R_CONTINUE) {
|
|
continue; /* Try the next SIG RR. */
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* There isn't a secure DNSKEY for this signature so move
|
|
* onto the next RRSIG.
|
|
*/
|
|
if (val->key == NULL) {
|
|
resume = false;
|
|
continue;
|
|
}
|
|
|
|
do {
|
|
isc_result_t tresult;
|
|
vresult = verify(val, val->key, &rdata,
|
|
val->siginfo->keyid);
|
|
if (vresult == ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
|
|
tresult = select_signing_key(val, val->keyset);
|
|
if (tresult != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
} while (1);
|
|
if (vresult != ISC_R_SUCCESS) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"failed to verify rdataset");
|
|
} else {
|
|
dns_rdataset_trimttl(event->rdataset,
|
|
event->sigrdataset,
|
|
val->siginfo, val->start,
|
|
val->view->acceptexpired);
|
|
}
|
|
|
|
if (val->key != NULL) {
|
|
dst_key_free(&val->key);
|
|
}
|
|
if (val->keyset != NULL) {
|
|
dns_rdataset_disassociate(val->keyset);
|
|
val->keyset = NULL;
|
|
}
|
|
val->key = NULL;
|
|
if (NEEDNOQNAME(val)) {
|
|
if (val->event->message == NULL) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no message available "
|
|
"for noqname proof");
|
|
return (DNS_R_NOVALIDSIG);
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"looking for noqname proof");
|
|
return (validate_nx(val, false));
|
|
} else if (vresult == ISC_R_SUCCESS) {
|
|
marksecure(event);
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"marking as secure, "
|
|
"noqname proof not needed");
|
|
return (ISC_R_SUCCESS);
|
|
} else {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"verify failure: %s",
|
|
isc_result_totext(result));
|
|
resume = false;
|
|
}
|
|
}
|
|
if (result != ISC_R_NOMORE) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"failed to iterate signatures: %s",
|
|
isc_result_totext(result));
|
|
return (result);
|
|
}
|
|
|
|
validator_log(val, ISC_LOG_INFO, "no valid signature found");
|
|
return (vresult);
|
|
}
|
|
|
|
/*%
|
|
* Check whether this DNSKEY (keyrdata) signed the DNSKEY RRset
|
|
* (val->event->rdataset).
|
|
*/
|
|
static isc_result_t
|
|
check_signer(dns_validator_t *val, dns_rdata_t *keyrdata,
|
|
uint16_t keyid, dns_secalg_t algorithm)
|
|
{
|
|
dns_rdata_rrsig_t sig;
|
|
dst_key_t *dstkey = NULL;
|
|
isc_result_t result;
|
|
|
|
for (result = dns_rdataset_first(val->event->sigrdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(val->event->sigrdataset))
|
|
{
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_rdataset_current(val->event->sigrdataset, &rdata);
|
|
result = dns_rdata_tostruct(&rdata, &sig, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
if (keyid != sig.keyid || algorithm != sig.algorithm) {
|
|
continue;
|
|
}
|
|
if (dstkey == NULL) {
|
|
result = dns_dnssec_keyfromrdata(val->event->name,
|
|
keyrdata,
|
|
val->view->mctx,
|
|
&dstkey);
|
|
if (result != ISC_R_SUCCESS) {
|
|
/*
|
|
* This really shouldn't happen, but...
|
|
*/
|
|
continue;
|
|
}
|
|
}
|
|
result = verify(val, dstkey, &rdata, sig.keyid);
|
|
if (result == ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dstkey != NULL) {
|
|
dst_key_free(&dstkey);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* get_dsset() is called to look up a DS RRset corresponding to the name
|
|
* of a DNSKEY record, either in the cache or, if necessary, by starting a
|
|
* fetch. This is done in the context of validating a zone key to build a
|
|
* trust chain.
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_COMPLETE a DS has not been found; the caller should
|
|
* stop trying to validate the zone key and
|
|
* return the result code in '*resp'.
|
|
* \li DNS_R_CONTINUE a DS has been found and the caller may
|
|
* continue the zone key validation.
|
|
*/
|
|
static isc_result_t
|
|
get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
|
|
isc_result_t result;
|
|
|
|
result = view_find(val, tname, dns_rdatatype_ds);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
/*
|
|
* We have a DS RRset.
|
|
*/
|
|
val->dsset = &val->frdataset;
|
|
INSIST(val->keynode == NULL);
|
|
if ((DNS_TRUST_PENDING(val->frdataset.trust) ||
|
|
DNS_TRUST_ANSWER(val->frdataset.trust)) &&
|
|
dns_rdataset_isassociated(&val->fsigrdataset))
|
|
{
|
|
/*
|
|
* ... which is signed but not yet validated.
|
|
*/
|
|
result = create_validator(val, tname,
|
|
dns_rdatatype_ds,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
validator_callback_ds,
|
|
"validate_dnskey");
|
|
*resp = DNS_R_WAIT;
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
return (ISC_R_COMPLETE);
|
|
} else if (DNS_TRUST_PENDING(val->frdataset.trust)) {
|
|
/*
|
|
* There should never be an unsigned DS.
|
|
*/
|
|
disassociate_rdatasets(val);
|
|
validator_log(val, ISC_LOG_DEBUG(2),
|
|
"unsigned DS record");
|
|
*resp = DNS_R_NOVALIDSIG;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
break;
|
|
|
|
case ISC_R_NOTFOUND:
|
|
/*
|
|
* We don't have the DS. Find it.
|
|
*/
|
|
result = create_fetch(val, tname, dns_rdatatype_ds,
|
|
fetch_callback_ds, "validate_dnskey");
|
|
*resp = DNS_R_WAIT;
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
return (ISC_R_COMPLETE);
|
|
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
case DNS_R_NCACHENXRRSET:
|
|
case DNS_R_EMPTYNAME:
|
|
case DNS_R_NXDOMAIN:
|
|
case DNS_R_NXRRSET:
|
|
case DNS_R_CNAME:
|
|
/*
|
|
* The DS does not exist.
|
|
*/
|
|
disassociate_rdatasets(val);
|
|
validator_log(val, ISC_LOG_DEBUG(2), "no DS record");
|
|
*resp = DNS_R_NOVALIDSIG;
|
|
return (ISC_R_COMPLETE);
|
|
|
|
case DNS_R_BROKENCHAIN:
|
|
*resp = result;
|
|
return (ISC_R_COMPLETE);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
|
|
/*%
|
|
* Attempts positive response validation of an RRset containing zone keys
|
|
* (i.e. a DNSKEY rrset).
|
|
*
|
|
* Caller must be holding the validator lock.
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_SUCCESS Validation completed successfully
|
|
* \li DNS_R_WAIT Validation has started but is waiting
|
|
* for an event.
|
|
* \li Other return codes are possible and all indicate failure.
|
|
*/
|
|
static isc_result_t
|
|
validate_dnskey(dns_validator_t *val) {
|
|
isc_result_t result;
|
|
dns_rdata_t dsrdata = DNS_RDATA_INIT;
|
|
dns_rdata_t keyrdata = DNS_RDATA_INIT;
|
|
dns_keynode_t *keynode = NULL;
|
|
dns_rdata_ds_t ds;
|
|
bool supported_algorithm;
|
|
char digest_types[256];
|
|
|
|
/*
|
|
* If we don't already have a DS RRset, check to see if there's
|
|
* a DS style trust anchor configured for this key.
|
|
*/
|
|
if (val->dsset == NULL) {
|
|
result = dns_keytable_find(val->keytable,
|
|
val->event->name, &keynode);
|
|
if (result == ISC_R_SUCCESS) {
|
|
val->dsset = dns_keynode_dsset(keynode);
|
|
if (val->dsset == NULL) {
|
|
dns_keytable_detachkeynode(val->keytable,
|
|
&keynode);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No trust anchor for this name, so we look up the DS at the parent.
|
|
*/
|
|
if (val->dsset == NULL) {
|
|
isc_result_t tresult = ISC_R_SUCCESS;
|
|
|
|
/*
|
|
* If this is the root name and there was no trust anchor,
|
|
* we can give up now, since there's no DS at the root.
|
|
*/
|
|
if (dns_name_equal(val->event->name, dns_rootname)) {
|
|
if ((val->attributes & VALATTR_TRIEDVERIFY) != 0) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"root key failed to validate");
|
|
} else {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no trusted root key");
|
|
}
|
|
result = DNS_R_NOVALIDSIG;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Look up the DS RRset for this name.
|
|
*/
|
|
result = get_dsset(val, val->event->name, &tresult);
|
|
if (result == ISC_R_COMPLETE) {
|
|
if (tresult == DNS_R_WAIT) {
|
|
/*
|
|
* Keep the keynode attached so we don't
|
|
* lose val->dsset.
|
|
*/
|
|
val->keynode = keynode;
|
|
}
|
|
|
|
result = tresult;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We have a DS set.
|
|
*/
|
|
INSIST(val->dsset != NULL);
|
|
|
|
if (val->dsset->trust < dns_trust_secure) {
|
|
INSIST(keynode == NULL);
|
|
return (markanswer(val, "validate_dnskey (2)", "insecure DS"));
|
|
}
|
|
|
|
/*
|
|
* Look through the DS record and find the keys that can sign the
|
|
* key set and the matching signature. For each such key, attempt
|
|
* verification.
|
|
*/
|
|
|
|
supported_algorithm = false;
|
|
|
|
/*
|
|
* If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we
|
|
* are required to prefer it over DNS_DSDIGEST_SHA1. This in
|
|
* practice means that we need to ignore DNS_DSDIGEST_SHA1 if a
|
|
* DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present.
|
|
*/
|
|
memset(digest_types, 1, sizeof(digest_types));
|
|
for (result = dns_rdataset_first(val->dsset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(val->dsset))
|
|
{
|
|
dns_rdata_reset(&dsrdata);
|
|
dns_rdataset_current(val->dsset, &dsrdata);
|
|
result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (!dns_resolver_ds_digest_supported(val->view->resolver,
|
|
val->event->name,
|
|
ds.digest_type))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!dns_resolver_algorithm_supported(val->view->resolver,
|
|
val->event->name,
|
|
ds.algorithm))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((ds.digest_type == DNS_DSDIGEST_SHA256 &&
|
|
ds.length == ISC_SHA256_DIGESTLENGTH) ||
|
|
(ds.digest_type == DNS_DSDIGEST_SHA384 &&
|
|
ds.length == ISC_SHA384_DIGESTLENGTH))
|
|
{
|
|
digest_types[DNS_DSDIGEST_SHA1] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (result = dns_rdataset_first(val->dsset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(val->dsset))
|
|
{
|
|
dns_rdata_reset(&dsrdata);
|
|
dns_rdataset_current(val->dsset, &dsrdata);
|
|
result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (digest_types[ds.digest_type] == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (!dns_resolver_ds_digest_supported(val->view->resolver,
|
|
val->event->name,
|
|
ds.digest_type))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!dns_resolver_algorithm_supported(val->view->resolver,
|
|
val->event->name,
|
|
ds.algorithm))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
supported_algorithm = true;
|
|
|
|
/*
|
|
* Find the DNSKEY matching the DS...
|
|
*/
|
|
result = dns_dnssec_matchdskey(val->event->name, &dsrdata,
|
|
val->event->rdataset,
|
|
&keyrdata);
|
|
if (result != ISC_R_SUCCESS) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no DNSKEY matching DS");
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* ... and check that it signed the DNSKEY RRset.
|
|
*/
|
|
result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
|
|
if (result == ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no RRSIG matching DS key");
|
|
}
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
marksecure(val->event);
|
|
validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
|
|
} else if (result == ISC_R_NOMORE && !supported_algorithm) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no supported algorithm/digest (DS)");
|
|
result = markanswer(val, "validate_dnskey (3)",
|
|
"no supported algorithm/digest (DS)");
|
|
} else {
|
|
validator_log(val, ISC_LOG_INFO,
|
|
"no valid signature found (DS)");
|
|
result = DNS_R_NOVALIDSIG;
|
|
}
|
|
|
|
cleanup:
|
|
if (keynode != NULL) {
|
|
val->dsset = NULL;
|
|
dns_keytable_detachkeynode(val->keytable, &keynode);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* val_rdataset_first and val_rdataset_next provide iteration methods
|
|
* that hide whether we are iterating across the AUTHORITY section of
|
|
* a message, or a negative cache rdataset.
|
|
*/
|
|
static isc_result_t
|
|
val_rdataset_first(dns_validator_t *val, dns_name_t **namep,
|
|
dns_rdataset_t **rdatasetp)
|
|
{
|
|
dns_message_t *message = val->event->message;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(rdatasetp != NULL);
|
|
REQUIRE(namep != NULL);
|
|
if (message == NULL) {
|
|
REQUIRE(*rdatasetp != NULL);
|
|
REQUIRE(*namep != NULL);
|
|
} else {
|
|
REQUIRE(*rdatasetp == NULL);
|
|
REQUIRE(*namep == NULL);
|
|
}
|
|
|
|
if (message != NULL) {
|
|
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY, namep);
|
|
*rdatasetp = ISC_LIST_HEAD((*namep)->list);
|
|
INSIST(*rdatasetp != NULL);
|
|
} else {
|
|
result = dns_rdataset_first(val->event->rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_ncache_current(val->event->rdataset, *namep,
|
|
*rdatasetp);
|
|
}
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
val_rdataset_next(dns_validator_t *val, dns_name_t **namep,
|
|
dns_rdataset_t **rdatasetp)
|
|
{
|
|
dns_message_t *message = val->event->message;
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
|
|
REQUIRE(rdatasetp != NULL && *rdatasetp != NULL);
|
|
REQUIRE(namep != NULL && *namep != NULL);
|
|
|
|
if (message != NULL) {
|
|
dns_rdataset_t *rdataset = *rdatasetp;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link);
|
|
if (rdataset == NULL) {
|
|
*namep = NULL;
|
|
result = dns_message_nextname(message,
|
|
DNS_SECTION_AUTHORITY);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_message_currentname(message,
|
|
DNS_SECTION_AUTHORITY,
|
|
namep);
|
|
rdataset = ISC_LIST_HEAD((*namep)->list);
|
|
INSIST(rdataset != NULL);
|
|
}
|
|
}
|
|
*rdatasetp = rdataset;
|
|
} else {
|
|
dns_rdataset_disassociate(*rdatasetp);
|
|
result = dns_rdataset_next(val->event->rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
dns_ncache_current(val->event->rdataset, *namep,
|
|
*rdatasetp);
|
|
}
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Look for NODATA at the wildcard and NOWILDCARD proofs in the
|
|
* previously validated NSEC records. As these proofs are mutually
|
|
* exclusive we stop when one is found.
|
|
*
|
|
* Returns
|
|
* \li ISC_R_SUCCESS
|
|
*/
|
|
static isc_result_t
|
|
checkwildcard(dns_validator_t *val, dns_rdatatype_t type,
|
|
dns_name_t *zonename)
|
|
{
|
|
dns_name_t *name, *wild, tname;
|
|
isc_result_t result;
|
|
bool exists, data;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
dns_rdataset_t *rdataset, trdataset;
|
|
|
|
dns_name_init(&tname, NULL);
|
|
dns_rdataset_init(&trdataset);
|
|
wild = dns_fixedname_name(&val->wild);
|
|
|
|
if (dns_name_countlabels(wild) == 0) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"in checkwildcard: no wildcard to check");
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
dns_name_format(wild, namebuf, sizeof(namebuf));
|
|
validator_log(val, ISC_LOG_DEBUG(3), "in checkwildcard: %s", namebuf);
|
|
|
|
if (val->event->message == NULL) {
|
|
name = &tname;
|
|
rdataset = &trdataset;
|
|
} else {
|
|
name = NULL;
|
|
rdataset = NULL;
|
|
}
|
|
|
|
for (result = val_rdataset_first(val, &name, &rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = val_rdataset_next(val, &name, &rdataset))
|
|
{
|
|
if (rdataset->type != type ||
|
|
rdataset->trust != dns_trust_secure)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (rdataset->type == dns_rdatatype_nsec &&
|
|
(NEEDNODATA(val) || NEEDNOWILDCARD(val)) &&
|
|
!FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) &&
|
|
dns_nsec_noexistnodata(val->event->type, wild, name,
|
|
rdataset, &exists, &data, NULL,
|
|
validator_log, val)
|
|
== ISC_R_SUCCESS)
|
|
{
|
|
dns_name_t **proofs = val->event->proofs;
|
|
if (exists && !data) {
|
|
val->attributes |= VALATTR_FOUNDNODATA;
|
|
}
|
|
if (exists && !data && NEEDNODATA(val)) {
|
|
proofs[DNS_VALIDATOR_NODATAPROOF] =
|
|
name;
|
|
}
|
|
if (!exists) {
|
|
val->attributes |=
|
|
VALATTR_FOUNDNOWILDCARD;
|
|
}
|
|
if (!exists && NEEDNOQNAME(val)) {
|
|
proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
|
|
}
|
|
if (dns_rdataset_isassociated(&trdataset)) {
|
|
dns_rdataset_disassociate(&trdataset);
|
|
}
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
if (rdataset->type == dns_rdatatype_nsec3 &&
|
|
(NEEDNODATA(val) || NEEDNOWILDCARD(val)) &&
|
|
!FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) &&
|
|
dns_nsec3_noexistnodata(val->event->type, wild, name,
|
|
rdataset, zonename, &exists, &data,
|
|
NULL, NULL, NULL, NULL, NULL, NULL,
|
|
validator_log, val)
|
|
== ISC_R_SUCCESS)
|
|
{
|
|
dns_name_t **proofs = val->event->proofs;
|
|
if (exists && !data) {
|
|
val->attributes |= VALATTR_FOUNDNODATA;
|
|
}
|
|
if (exists && !data && NEEDNODATA(val)) {
|
|
proofs[DNS_VALIDATOR_NODATAPROOF] = name;
|
|
}
|
|
if (!exists) {
|
|
val->attributes |= VALATTR_FOUNDNOWILDCARD;
|
|
}
|
|
if (!exists && NEEDNOQNAME(val)) {
|
|
proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
|
|
}
|
|
if (dns_rdataset_isassociated(&trdataset)) {
|
|
dns_rdataset_disassociate(&trdataset);
|
|
}
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (dns_rdataset_isassociated(&trdataset)) {
|
|
dns_rdataset_disassociate(&trdataset);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Look for the needed proofs for a negative or wildcard response
|
|
* from a zone using NSEC3, and set flags in the validator as they
|
|
* are found.
|
|
*/
|
|
static isc_result_t
|
|
findnsec3proofs(dns_validator_t *val) {
|
|
dns_name_t *name, tname;
|
|
isc_result_t result;
|
|
bool exists, data, optout, unknown;
|
|
bool setclosest, setnearest, *setclosestp;
|
|
dns_fixedname_t fclosest, fnearest, fzonename;
|
|
dns_name_t *closest, *nearest, *zonename, *closestp;
|
|
dns_name_t **proofs = val->event->proofs;
|
|
dns_rdataset_t *rdataset, trdataset;
|
|
|
|
dns_name_init(&tname, NULL);
|
|
dns_rdataset_init(&trdataset);
|
|
closest = dns_fixedname_initname(&fclosest);
|
|
nearest = dns_fixedname_initname(&fnearest);
|
|
zonename = dns_fixedname_initname(&fzonename);
|
|
|
|
if (val->event->message == NULL) {
|
|
name = &tname;
|
|
rdataset = &trdataset;
|
|
} else {
|
|
name = NULL;
|
|
rdataset = NULL;
|
|
}
|
|
|
|
for (result = val_rdataset_first(val, &name, &rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = val_rdataset_next(val, &name, &rdataset))
|
|
{
|
|
if (rdataset->type != dns_rdatatype_nsec3 ||
|
|
rdataset->trust != dns_trust_secure)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result = dns_nsec3_noexistnodata(val->event->type,
|
|
val->event->name, name,
|
|
rdataset, zonename, NULL,
|
|
NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, validator_log,
|
|
val);
|
|
if (result != ISC_R_IGNORE && result != ISC_R_SUCCESS) {
|
|
if (dns_rdataset_isassociated(&trdataset)) {
|
|
dns_rdataset_disassociate(&trdataset);
|
|
}
|
|
return (result);
|
|
}
|
|
}
|
|
if (result != ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
POST(result);
|
|
|
|
if (dns_name_countlabels(zonename) == 0) {
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* If the val->closest is set then we want to use it otherwise
|
|
* we need to discover it.
|
|
*/
|
|
if (dns_name_countlabels(dns_fixedname_name(&val->closest)) != 0) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_name_format(dns_fixedname_name(&val->closest),
|
|
namebuf, sizeof(namebuf));
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"closest encloser from wildcard signature '%s'",
|
|
namebuf);
|
|
dns_name_copynf(dns_fixedname_name(&val->closest), closest);
|
|
closestp = NULL;
|
|
setclosestp = NULL;
|
|
} else {
|
|
closestp = closest;
|
|
setclosestp = &setclosest;
|
|
}
|
|
|
|
for (result = val_rdataset_first(val, &name, &rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = val_rdataset_next(val, &name, &rdataset))
|
|
{
|
|
if (rdataset->type != dns_rdatatype_nsec3 ||
|
|
rdataset->trust != dns_trust_secure)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We process all NSEC3 records to find the closest
|
|
* encloser and nearest name to the closest encloser.
|
|
*/
|
|
setclosest = setnearest = false;
|
|
optout = false;
|
|
unknown = false;
|
|
result = dns_nsec3_noexistnodata(val->event->type,
|
|
val->event->name,
|
|
name, rdataset, zonename,
|
|
&exists, &data, &optout,
|
|
&unknown, setclosestp,
|
|
&setnearest, closestp,
|
|
nearest, validator_log, val);
|
|
if (unknown) {
|
|
val->attributes |= VALATTR_FOUNDUNKNOWN;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
continue;
|
|
}
|
|
if (setclosest) {
|
|
proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
|
|
}
|
|
if (exists && !data && NEEDNODATA(val)) {
|
|
val->attributes |= VALATTR_FOUNDNODATA;
|
|
proofs[DNS_VALIDATOR_NODATAPROOF] = name;
|
|
}
|
|
if (!exists && setnearest) {
|
|
val->attributes |= VALATTR_FOUNDNOQNAME;
|
|
proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
|
|
if (optout) {
|
|
val->attributes |= VALATTR_FOUNDOPTOUT;
|
|
}
|
|
}
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* To know we have a valid noqname and optout proofs we need to also
|
|
* have a valid closest encloser. Otherwise we could still be looking
|
|
* at proofs from the parent zone.
|
|
*/
|
|
if (dns_name_countlabels(closest) > 0 &&
|
|
dns_name_countlabels(nearest) ==
|
|
dns_name_countlabels(closest) + 1 &&
|
|
dns_name_issubdomain(nearest, closest))
|
|
{
|
|
val->attributes |= VALATTR_FOUNDCLOSEST;
|
|
result = dns_name_concatenate(dns_wildcardname, closest,
|
|
dns_fixedname_name(&val->wild),
|
|
NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
} else {
|
|
val->attributes &= ~VALATTR_FOUNDNOQNAME;
|
|
val->attributes &= ~VALATTR_FOUNDOPTOUT;
|
|
proofs[DNS_VALIDATOR_NOQNAMEPROOF] = NULL;
|
|
}
|
|
|
|
/*
|
|
* Do we need to check for the wildcard?
|
|
*/
|
|
if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
|
|
((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val)))
|
|
{
|
|
result = checkwildcard(val, dns_rdatatype_nsec3, zonename);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Start a validator for negative response data.
|
|
*
|
|
* Returns:
|
|
* \li DNS_R_CONTINUE Validation skipped, continue
|
|
* \li DNS_R_WAIT Validation is in progress
|
|
*
|
|
* \li Other return codes indicate failure.
|
|
*/
|
|
static isc_result_t
|
|
validate_neg_rrset(dns_validator_t *val, dns_name_t *name,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset)
|
|
{
|
|
isc_result_t result;
|
|
|
|
/*
|
|
* If a signed zone is missing the zone key, bad
|
|
* things could happen. A query for data in the zone
|
|
* would lead to a query for the zone key, which
|
|
* would return a negative answer, which would contain
|
|
* an SOA and an NSEC signed by the missing key, which
|
|
* would trigger another query for the DNSKEY (since
|
|
* the first one is still in progress), and go into an
|
|
* infinite loop. Avoid that.
|
|
*/
|
|
if (val->event->type == dns_rdatatype_dnskey &&
|
|
rdataset->type == dns_rdatatype_nsec &&
|
|
dns_name_equal(name, val->event->name))
|
|
{
|
|
dns_rdata_t nsec = DNS_RDATA_INIT;
|
|
|
|
result = dns_rdataset_first(rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
dns_rdataset_current(rdataset, &nsec);
|
|
if (dns_nsec_typepresent(&nsec, dns_rdatatype_soa)) {
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
}
|
|
|
|
val->currentset = rdataset;
|
|
result = create_validator(val, name, rdataset->type,
|
|
rdataset, sigrdataset,
|
|
validator_callback_nsec,
|
|
"validate_neg_rrset");
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
|
|
val->authcount++;
|
|
return (DNS_R_WAIT);
|
|
}
|
|
|
|
/*%
|
|
* Validate the authority section records.
|
|
*/
|
|
static isc_result_t
|
|
validate_authority(dns_validator_t *val, bool resume) {
|
|
dns_name_t *name;
|
|
dns_message_t *message = val->event->message;
|
|
isc_result_t result;
|
|
|
|
if (!resume) {
|
|
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
|
|
} else {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
for (;
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY))
|
|
{
|
|
dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
|
|
|
|
name = NULL;
|
|
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
|
|
if (resume) {
|
|
rdataset = ISC_LIST_NEXT(val->currentset, link);
|
|
val->currentset = NULL;
|
|
resume = false;
|
|
} else {
|
|
rdataset = ISC_LIST_HEAD(name->list);
|
|
}
|
|
|
|
for (;
|
|
rdataset != NULL;
|
|
rdataset = ISC_LIST_NEXT(rdataset, link))
|
|
{
|
|
if (rdataset->type == dns_rdatatype_rrsig) {
|
|
continue;
|
|
}
|
|
|
|
for (sigrdataset = ISC_LIST_HEAD(name->list);
|
|
sigrdataset != NULL;
|
|
sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
|
|
{
|
|
if (sigrdataset->type == dns_rdatatype_rrsig &&
|
|
sigrdataset->covers == rdataset->type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
result = validate_neg_rrset(val, name, rdataset,
|
|
sigrdataset);
|
|
if (result != DNS_R_CONTINUE) {
|
|
return (result);
|
|
}
|
|
}
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Validate negative cache elements.
|
|
*/
|
|
static isc_result_t
|
|
validate_ncache(dns_validator_t *val, bool resume) {
|
|
dns_name_t *name;
|
|
isc_result_t result;
|
|
|
|
if (!resume) {
|
|
result = dns_rdataset_first(val->event->rdataset);
|
|
} else {
|
|
result = dns_rdataset_next(val->event->rdataset);
|
|
}
|
|
|
|
for (;
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(val->event->rdataset))
|
|
{
|
|
dns_rdataset_t *rdataset, *sigrdataset = NULL;
|
|
|
|
disassociate_rdatasets(val);
|
|
|
|
name = dns_fixedname_initname(&val->fname);
|
|
rdataset = &val->frdataset;
|
|
dns_ncache_current(val->event->rdataset, name, rdataset);
|
|
|
|
if (val->frdataset.type == dns_rdatatype_rrsig) {
|
|
continue;
|
|
}
|
|
|
|
result = dns_ncache_getsigrdataset(val->event->rdataset, name,
|
|
rdataset->type,
|
|
&val->fsigrdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
sigrdataset = &val->fsigrdataset;
|
|
}
|
|
|
|
result = validate_neg_rrset(val, name, rdataset, sigrdataset);
|
|
if (result == DNS_R_CONTINUE) {
|
|
continue;
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
if (result == ISC_R_NOMORE) {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Prove a negative answer is good or that there is a NOQNAME when the
|
|
* answer is from a wildcard.
|
|
*
|
|
* Loop through the authority section looking for NODATA, NOWILDCARD
|
|
* and NOQNAME proofs in the NSEC records by calling
|
|
* validator_callback_nsec().
|
|
*
|
|
* If the required proofs are found we are done.
|
|
*
|
|
* If the proofs are not found attempt to prove this is an unsecure
|
|
* response.
|
|
*/
|
|
static isc_result_t
|
|
validate_nx(dns_validator_t *val, bool resume) {
|
|
isc_result_t result;
|
|
|
|
if (resume) {
|
|
validator_log(val, ISC_LOG_DEBUG(3), "resuming validate_nx");
|
|
}
|
|
|
|
if (val->event->message == NULL) {
|
|
result = validate_ncache(val, resume);
|
|
} else {
|
|
result = validate_authority(val, resume);
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Do we only need to check for NOQNAME? To get here we must have
|
|
* had a secure wildcard answer.
|
|
*/
|
|
if (!NEEDNODATA(val) && !NEEDNOWILDCARD(val) && NEEDNOQNAME(val)) {
|
|
if (!FOUNDNOQNAME(val)) {
|
|
findnsec3proofs(val);
|
|
}
|
|
|
|
if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
|
|
!FOUNDOPTOUT(val))
|
|
{
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"marking as secure, noqname proof found");
|
|
marksecure(val->event);
|
|
return (ISC_R_SUCCESS);
|
|
} else if (FOUNDOPTOUT(val) &&
|
|
dns_name_countlabels(dns_fixedname_name(&val->wild)) != 0)
|
|
{
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"optout proof found");
|
|
val->event->optout = true;
|
|
markanswer(val, "validate_nx (1)", NULL);
|
|
return (ISC_R_SUCCESS);
|
|
} else if ((val->attributes & VALATTR_FOUNDUNKNOWN) != 0) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"unknown NSEC3 hash algorithm found");
|
|
markanswer(val, "validate_nx (2)", NULL);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"noqname proof not found");
|
|
return (DNS_R_NOVALIDNSEC);
|
|
}
|
|
|
|
if (!FOUNDNOQNAME(val) && !FOUNDNODATA(val)) {
|
|
findnsec3proofs(val);
|
|
}
|
|
|
|
/*
|
|
* Do we need to check for the wildcard?
|
|
*/
|
|
if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
|
|
((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val)))
|
|
{
|
|
result = checkwildcard(val, dns_rdatatype_nsec, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
}
|
|
|
|
if ((NEEDNODATA(val) && (FOUNDNODATA(val) || FOUNDOPTOUT(val))) ||
|
|
(NEEDNOQNAME(val) && FOUNDNOQNAME(val) &&
|
|
NEEDNOWILDCARD(val) && FOUNDNOWILDCARD(val) &&
|
|
FOUNDCLOSEST(val)))
|
|
{
|
|
if ((val->attributes & VALATTR_FOUNDOPTOUT) != 0) {
|
|
val->event->optout = true;
|
|
}
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"nonexistence proof(s) found");
|
|
if (val->event->message == NULL) {
|
|
marksecure(val->event);
|
|
} else {
|
|
val->event->secure = true;
|
|
}
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
if (val->authfail != 0 && val->authcount == val->authfail) {
|
|
return (DNS_R_BROKENCHAIN);
|
|
}
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"nonexistence proof(s) not found");
|
|
return (proveunsecure(val, false, false));
|
|
}
|
|
|
|
/*%
|
|
* Check that DS rdataset has at least one record with
|
|
* a supported algorithm and digest.
|
|
*/
|
|
static bool
|
|
check_ds_algs(dns_validator_t *val, dns_name_t *name, dns_rdataset_t *rdataset)
|
|
{
|
|
dns_rdata_t dsrdata = DNS_RDATA_INIT;
|
|
dns_rdata_ds_t ds;
|
|
isc_result_t result;
|
|
|
|
for (result = dns_rdataset_first(rdataset);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_rdataset_next(rdataset))
|
|
{
|
|
dns_rdataset_current(rdataset, &dsrdata);
|
|
result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
|
|
if (dns_resolver_ds_digest_supported(val->view->resolver,
|
|
name, ds.digest_type) &&
|
|
dns_resolver_algorithm_supported(val->view->resolver,
|
|
name, ds.algorithm))
|
|
{
|
|
dns_rdata_reset(&dsrdata);
|
|
return (true);
|
|
}
|
|
dns_rdata_reset(&dsrdata);
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
/*%
|
|
* seek_ds is called to look up DS rrsets at the label of val->event->name
|
|
* indicated by val->labels. This is done while building an insecurity
|
|
* proof, and so it will attempt validation of NXDOMAIN, NXRRSET or CNAME
|
|
* responses.
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_COMPLETE a result has been determined and copied
|
|
* into `*resp`; ISC_R_SUCCESS indicates that
|
|
* the name has been proven insecure and any
|
|
* other result indicates failure.
|
|
* \li DNS_R_CONTINUE result is indeterminate; caller should
|
|
* continue walking down labels.
|
|
*/
|
|
static isc_result_t
|
|
seek_ds(dns_validator_t *val, isc_result_t *resp) {
|
|
isc_result_t result;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
dns_fixedname_t fixedfound;
|
|
dns_name_t *found = dns_fixedname_initname(&fixedfound);
|
|
dns_name_t *tname = dns_fixedname_initname(&val->fname);
|
|
|
|
if (val->labels == dns_name_countlabels(val->event->name)) {
|
|
dns_name_copynf(val->event->name, tname);
|
|
} else {
|
|
dns_name_split(val->event->name, val->labels, NULL, tname);
|
|
}
|
|
|
|
dns_name_format(tname, namebuf, sizeof(namebuf));
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"checking existence of DS at '%s'", namebuf);
|
|
|
|
result = view_find(val, tname, dns_rdatatype_ds);
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
/*
|
|
* There is a DS here. If it's already been
|
|
* validated, continue walking down labels.
|
|
*/
|
|
if (val->frdataset.trust >= dns_trust_secure) {
|
|
if (!check_ds_algs(val, tname, &val->frdataset)) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no supported algorithm/"
|
|
"digest (%s/DS)",
|
|
namebuf);
|
|
*resp = markanswer(val,
|
|
"proveunsecure (5)",
|
|
"no supported "
|
|
"algorithm/digest (DS)");
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, try to validate it now.
|
|
*/
|
|
if (dns_rdataset_isassociated(&val->fsigrdataset)) {
|
|
result = create_validator(val, tname,
|
|
dns_rdatatype_ds,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
validator_callback_ds,
|
|
"proveunsecure");
|
|
*resp = DNS_R_WAIT;
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
} else {
|
|
/*
|
|
* There should never be an unsigned DS.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"unsigned DS record");
|
|
*resp = DNS_R_NOVALIDSIG;
|
|
}
|
|
|
|
return (ISC_R_COMPLETE);
|
|
|
|
case ISC_R_NOTFOUND:
|
|
/*
|
|
* We don't know anything about the DS. Find it.
|
|
*/
|
|
*resp = DNS_R_WAIT;
|
|
result = create_fetch(val, tname, dns_rdatatype_ds,
|
|
fetch_callback_ds, "proveunsecure");
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
return (ISC_R_COMPLETE);
|
|
|
|
case DNS_R_NXRRSET:
|
|
case DNS_R_NCACHENXRRSET:
|
|
/*
|
|
* There is no DS. If this is a delegation,
|
|
* we may be done.
|
|
*
|
|
* If we have "trust == answer" then this namespace
|
|
* has switched from insecure to should be secure.
|
|
*/
|
|
if (DNS_TRUST_PENDING(val->frdataset.trust) ||
|
|
DNS_TRUST_ANSWER(val->frdataset.trust))
|
|
{
|
|
result = create_validator(val, tname,
|
|
dns_rdatatype_ds,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
validator_callback_ds,
|
|
"proveunsecure");
|
|
*resp = DNS_R_WAIT;
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
/*
|
|
* Zones using NSEC3 don't return a NSEC RRset so
|
|
* we need to use dns_view_findzonecut2 to find
|
|
* the zone cut.
|
|
*/
|
|
if (result == DNS_R_NXRRSET &&
|
|
!dns_rdataset_isassociated(&val->frdataset) &&
|
|
dns_view_findzonecut(val->view, tname, found, NULL,
|
|
0, 0, false, false,
|
|
NULL, NULL) == ISC_R_SUCCESS &&
|
|
dns_name_equal(tname, found))
|
|
{
|
|
*resp = markanswer(val, "proveunsecure (3)",
|
|
"no DS at zone cut");
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (val->frdataset.trust < dns_trust_secure) {
|
|
/*
|
|
* This shouldn't happen, since the negative
|
|
* response should have been validated. Since
|
|
* there's no way of validating existing
|
|
* negative response blobs, give up.
|
|
*/
|
|
validator_log(val, ISC_LOG_WARNING,
|
|
"can't validate existing "
|
|
"negative responses (no DS)");
|
|
*resp = DNS_R_MUSTBESECURE;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
if (isdelegation(tname, &val->frdataset, result)) {
|
|
*resp = markanswer(val, "proveunsecure (4)",
|
|
"this is a delegation");
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DNS_R_NXDOMAIN:
|
|
case DNS_R_NCACHENXDOMAIN:
|
|
/*
|
|
* This is not a zone cut. Assuming things are
|
|
* as expected, continue.
|
|
*/
|
|
if (!dns_rdataset_isassociated(&val->frdataset)) {
|
|
/*
|
|
* There should be an NSEC here, since we
|
|
* are still in a secure zone.
|
|
*/
|
|
*resp = DNS_R_NOVALIDNSEC;
|
|
return (ISC_R_COMPLETE);
|
|
} else if (DNS_TRUST_PENDING(val->frdataset.trust) ||
|
|
DNS_TRUST_ANSWER(val->frdataset.trust))
|
|
{
|
|
/*
|
|
* If we have "trust == answer" then this
|
|
* namespace has switched from insecure to
|
|
* should be secure.
|
|
*/
|
|
*resp = DNS_R_WAIT;
|
|
result = create_validator(val, tname,
|
|
dns_rdatatype_ds,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
validator_callback_ds,
|
|
"proveunsecure");
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
return (ISC_R_COMPLETE);
|
|
} else if (val->frdataset.trust < dns_trust_secure) {
|
|
/*
|
|
* This shouldn't happen, since the negative
|
|
* response should have been validated. Since
|
|
* there's no way of validating existing
|
|
* negative response blobs, give up.
|
|
*/
|
|
validator_log(val, ISC_LOG_WARNING,
|
|
"can't validate existing "
|
|
"negative responses "
|
|
"(not a zone cut)");
|
|
*resp = DNS_R_NOVALIDSIG;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
break;
|
|
|
|
case DNS_R_CNAME:
|
|
if (DNS_TRUST_PENDING(val->frdataset.trust) ||
|
|
DNS_TRUST_ANSWER(val->frdataset.trust))
|
|
{
|
|
result = create_validator(val, tname,
|
|
dns_rdatatype_cname,
|
|
&val->frdataset,
|
|
&val->fsigrdataset,
|
|
validator_callback_cname,
|
|
"proveunsecure "
|
|
"(cname)");
|
|
*resp = DNS_R_WAIT;
|
|
if (result != ISC_R_SUCCESS) {
|
|
*resp = result;
|
|
}
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
*resp = result;
|
|
return (ISC_R_COMPLETE);
|
|
}
|
|
|
|
/*
|
|
* No definite answer yet; continue walking down labels.
|
|
*/
|
|
return (DNS_R_CONTINUE);
|
|
}
|
|
|
|
/*%
|
|
* proveunsecure walks down, label by label, from the closest enclosing
|
|
* trust anchor to the name that is being validated, looking for an
|
|
* endpoint in the chain of trust. That occurs when we can prove that
|
|
* a DS record does not exist at a delegation point, or that a DS exists
|
|
* at a delegation point but we don't support its algorithm/digest. If
|
|
* no such endpoint is found, then the response should have been secure.
|
|
*
|
|
* Returns:
|
|
* \li ISC_R_SUCCESS val->event->name is in an unsecure zone
|
|
* \li DNS_R_WAIT validation is in progress.
|
|
* \li DNS_R_MUSTBESECURE val->event->name is supposed to be secure
|
|
* (policy) but we proved that it is unsecure.
|
|
* \li DNS_R_NOVALIDSIG
|
|
* \li DNS_R_NOVALIDNSEC
|
|
* \li DNS_R_NOTINSECURE
|
|
* \li DNS_R_BROKENCHAIN
|
|
*/
|
|
static isc_result_t
|
|
proveunsecure(dns_validator_t *val, bool have_ds, bool resume) {
|
|
isc_result_t result;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
dns_fixedname_t fixedsecroot;
|
|
dns_name_t *secroot = dns_fixedname_initname(&fixedsecroot);
|
|
unsigned int labels;
|
|
|
|
/*
|
|
* We're attempting to prove insecurity.
|
|
*/
|
|
val->attributes |= VALATTR_INSECURITY;
|
|
|
|
dns_name_copynf(val->event->name, secroot);
|
|
|
|
/*
|
|
* If this is a response to a DS query, we need to look in
|
|
* the parent zone for the trust anchor.
|
|
*/
|
|
labels = dns_name_countlabels(secroot);
|
|
if (val->event->type == dns_rdatatype_ds && labels > 1U) {
|
|
dns_name_getlabelsequence(secroot, 1, labels - 1, secroot);
|
|
}
|
|
|
|
result = dns_keytable_finddeepestmatch(val->keytable, secroot, secroot);
|
|
if (result == ISC_R_NOTFOUND) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"not beneath secure root");
|
|
return (markanswer(val, "proveunsecure (1)",
|
|
"not beneath secure root"));
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
return (result);
|
|
}
|
|
|
|
if (!resume) {
|
|
/*
|
|
* We are looking for interruptions in the chain of trust.
|
|
* That can only happen *below* the trust anchor, so we
|
|
* start looking at the next label down.
|
|
*/
|
|
val->labels = dns_name_countlabels(secroot) + 1;
|
|
} else {
|
|
validator_log(val, ISC_LOG_DEBUG(3), "resuming proveunsecure");
|
|
|
|
/*
|
|
* If we have a DS rdataset and it is secure, check whether
|
|
* it has a supported algorithm combination. If not, this is
|
|
* an insecure delegation as far as this resolver is concerned.
|
|
*/
|
|
if (have_ds && val->frdataset.trust >= dns_trust_secure &&
|
|
!check_ds_algs(val, dns_fixedname_name(&val->fname),
|
|
&val->frdataset))
|
|
{
|
|
dns_name_format(dns_fixedname_name(&val->fname),
|
|
namebuf, sizeof(namebuf));
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"no supported algorithm/digest (%s/DS)",
|
|
namebuf);
|
|
result = markanswer(val, "proveunsecure (2)", namebuf);
|
|
goto out;
|
|
}
|
|
val->labels++;
|
|
}
|
|
|
|
/*
|
|
* Walk down through each of the remaining labels in the name,
|
|
* looking for DS records.
|
|
*/
|
|
while (val->labels <= dns_name_countlabels(val->event->name)) {
|
|
isc_result_t tresult;
|
|
|
|
result = seek_ds(val, &tresult);
|
|
if (result == ISC_R_COMPLETE) {
|
|
result = tresult;
|
|
goto out;
|
|
}
|
|
|
|
INSIST(result == DNS_R_CONTINUE);
|
|
val->labels++;
|
|
}
|
|
|
|
/* Couldn't complete insecurity proof. */
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"insecurity proof failed: %s",
|
|
isc_result_totext(result));
|
|
return (DNS_R_NOTINSECURE);
|
|
|
|
out:
|
|
if (result != DNS_R_WAIT) {
|
|
disassociate_rdatasets(val);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*%
|
|
* Start the validation process.
|
|
*
|
|
* Attempt to validate the answer based on the category it appears to
|
|
* fall in.
|
|
* \li 1. secure positive answer.
|
|
* \li 2. unsecure positive answer.
|
|
* \li 3. a negative answer (secure or unsecure).
|
|
*
|
|
* Note an answer that appears to be a secure positive answer may actually
|
|
* be an unsecure positive answer.
|
|
*/
|
|
static void
|
|
validator_start(isc_task_t *task, isc_event_t *event) {
|
|
dns_validator_t *val;
|
|
dns_validatorevent_t *vevent;
|
|
bool want_destroy = false;
|
|
isc_result_t result = ISC_R_FAILURE;
|
|
|
|
UNUSED(task);
|
|
REQUIRE(event->ev_type == DNS_EVENT_VALIDATORSTART);
|
|
vevent = (dns_validatorevent_t *)event;
|
|
val = vevent->validator;
|
|
|
|
/* If the validator has been canceled, val->event == NULL */
|
|
if (val->event == NULL) {
|
|
return;
|
|
}
|
|
|
|
validator_log(val, ISC_LOG_DEBUG(3), "starting");
|
|
|
|
LOCK(&val->lock);
|
|
|
|
if (val->event->rdataset != NULL && val->event->sigrdataset != NULL) {
|
|
isc_result_t saved_result;
|
|
|
|
/*
|
|
* This looks like a simple validation. We say "looks like"
|
|
* because it might end up requiring an insecurity proof.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"attempting positive response validation");
|
|
|
|
INSIST(dns_rdataset_isassociated(val->event->rdataset));
|
|
INSIST(dns_rdataset_isassociated(val->event->sigrdataset));
|
|
if (selfsigned_dnskey(val)) {
|
|
result = validate_dnskey(val);
|
|
} else {
|
|
result = validate_answer(val, false);
|
|
}
|
|
if (result == DNS_R_NOVALIDSIG &&
|
|
(val->attributes & VALATTR_TRIEDVERIFY) == 0)
|
|
{
|
|
saved_result = result;
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"falling back to insecurity proof");
|
|
result = proveunsecure(val, false, false);
|
|
if (result == DNS_R_NOTINSECURE) {
|
|
result = saved_result;
|
|
}
|
|
}
|
|
} else if (val->event->rdataset != NULL &&
|
|
val->event->rdataset->type != 0)
|
|
{
|
|
/*
|
|
* This is either an unsecure subdomain or a response
|
|
* from a broken server.
|
|
*/
|
|
INSIST(dns_rdataset_isassociated(val->event->rdataset));
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"attempting insecurity proof");
|
|
|
|
result = proveunsecure(val, false, false);
|
|
if (result == DNS_R_NOTINSECURE) {
|
|
validator_log(val, ISC_LOG_INFO,
|
|
"got insecure response; "
|
|
"parent indicates it should be secure");
|
|
}
|
|
} else if ((val->event->rdataset == NULL &&
|
|
val->event->sigrdataset == NULL))
|
|
{
|
|
/*
|
|
* This is a validation of a negative response.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"attempting negative response validation "
|
|
"from message");
|
|
|
|
if (val->event->message->rcode == dns_rcode_nxdomain) {
|
|
val->attributes |= VALATTR_NEEDNOQNAME;
|
|
val->attributes |= VALATTR_NEEDNOWILDCARD;
|
|
} else {
|
|
val->attributes |= VALATTR_NEEDNODATA;
|
|
}
|
|
|
|
result = validate_nx(val, false);
|
|
} else if ((val->event->rdataset != NULL &&
|
|
NEGATIVE(val->event->rdataset)))
|
|
{
|
|
/*
|
|
* This is a delayed validation of a negative cache entry.
|
|
*/
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
"attempting negative response validation "
|
|
"from cache");
|
|
|
|
if (NXDOMAIN(val->event->rdataset)) {
|
|
val->attributes |= VALATTR_NEEDNOQNAME;
|
|
val->attributes |= VALATTR_NEEDNOWILDCARD;
|
|
} else {
|
|
val->attributes |= VALATTR_NEEDNODATA;
|
|
}
|
|
|
|
result = validate_nx(val, false);
|
|
} else {
|
|
INSIST(0);
|
|
ISC_UNREACHABLE();
|
|
}
|
|
|
|
if (result != DNS_R_WAIT) {
|
|
want_destroy = exit_check(val);
|
|
validator_done(val, result);
|
|
}
|
|
|
|
UNLOCK(&val->lock);
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
isc_result_t
|
|
dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
|
|
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
|
|
dns_message_t *message, unsigned int options,
|
|
isc_task_t *task, isc_taskaction_t action, void *arg,
|
|
dns_validator_t **validatorp)
|
|
{
|
|
isc_result_t result = ISC_R_FAILURE;
|
|
dns_validator_t *val;
|
|
isc_task_t *tclone = NULL;
|
|
dns_validatorevent_t *event;
|
|
|
|
REQUIRE(name != NULL);
|
|
REQUIRE(rdataset != NULL ||
|
|
(rdataset == NULL && sigrdataset == NULL && message != NULL));
|
|
REQUIRE(validatorp != NULL && *validatorp == NULL);
|
|
|
|
event = (dns_validatorevent_t *)
|
|
isc_event_allocate(view->mctx, task,
|
|
DNS_EVENT_VALIDATORSTART,
|
|
validator_start, NULL,
|
|
sizeof(dns_validatorevent_t));
|
|
|
|
isc_task_attach(task, &tclone);
|
|
event->result = ISC_R_FAILURE;
|
|
event->name = name;
|
|
event->type = type;
|
|
event->rdataset = rdataset;
|
|
event->sigrdataset = sigrdataset;
|
|
event->message = message;
|
|
memset(event->proofs, 0, sizeof(event->proofs));
|
|
event->optout = false;
|
|
event->secure = false;
|
|
|
|
val = isc_mem_get(view->mctx, sizeof(*val));
|
|
*val = (dns_validator_t) {
|
|
.event = event,
|
|
.options = options,
|
|
.task = task,
|
|
.action = action,
|
|
.arg = arg
|
|
};
|
|
|
|
dns_view_weakattach(view, &val->view);
|
|
isc_mutex_init(&val->lock);
|
|
|
|
result = dns_view_getsecroots(val->view, &val->keytable);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
val->mustbesecure = dns_resolver_getmustbesecure(view->resolver, name);
|
|
dns_rdataset_init(&val->frdataset);
|
|
dns_rdataset_init(&val->fsigrdataset);
|
|
dns_fixedname_init(&val->wild);
|
|
dns_fixedname_init(&val->closest);
|
|
isc_stdtime_get(&val->start);
|
|
ISC_LINK_INIT(val, link);
|
|
val->magic = VALIDATOR_MAGIC;
|
|
|
|
event->validator = val;
|
|
|
|
if ((options & DNS_VALIDATOR_DEFER) == 0) {
|
|
isc_task_send(task, ISC_EVENT_PTR(&event));
|
|
}
|
|
|
|
*validatorp = val;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
isc_mutex_destroy(&val->lock);
|
|
|
|
isc_task_detach(&tclone);
|
|
isc_event_free(ISC_EVENT_PTR(&event));
|
|
|
|
dns_view_weakdetach(&val->view);
|
|
isc_mem_put(view->mctx, val, sizeof(*val));
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_validator_send(dns_validator_t *validator) {
|
|
isc_event_t *event;
|
|
REQUIRE(VALID_VALIDATOR(validator));
|
|
|
|
LOCK(&validator->lock);
|
|
|
|
INSIST((validator->options & DNS_VALIDATOR_DEFER) != 0);
|
|
event = (isc_event_t *)validator->event;
|
|
validator->options &= ~DNS_VALIDATOR_DEFER;
|
|
UNLOCK(&validator->lock);
|
|
|
|
isc_task_send(validator->task, ISC_EVENT_PTR(&event));
|
|
}
|
|
|
|
void
|
|
dns_validator_cancel(dns_validator_t *validator) {
|
|
dns_fetch_t *fetch = NULL;
|
|
|
|
REQUIRE(VALID_VALIDATOR(validator));
|
|
|
|
LOCK(&validator->lock);
|
|
|
|
validator_log(validator, ISC_LOG_DEBUG(3), "dns_validator_cancel");
|
|
|
|
if ((validator->attributes & VALATTR_CANCELED) == 0) {
|
|
validator->attributes |= VALATTR_CANCELED;
|
|
if (validator->event != NULL) {
|
|
fetch = validator->fetch;
|
|
validator->fetch = NULL;
|
|
|
|
if (validator->subvalidator != NULL) {
|
|
dns_validator_cancel(validator->subvalidator);
|
|
}
|
|
if ((validator->options & DNS_VALIDATOR_DEFER) != 0) {
|
|
validator->options &= ~DNS_VALIDATOR_DEFER;
|
|
validator_done(validator, ISC_R_CANCELED);
|
|
}
|
|
}
|
|
}
|
|
UNLOCK(&validator->lock);
|
|
|
|
/* Need to cancel and destroy the fetch outside validator lock */
|
|
if (fetch != NULL) {
|
|
dns_resolver_cancelfetch(fetch);
|
|
dns_resolver_destroyfetch(&fetch);
|
|
}
|
|
}
|
|
|
|
static void
|
|
destroy(dns_validator_t *val) {
|
|
isc_mem_t *mctx;
|
|
|
|
REQUIRE(SHUTDOWN(val));
|
|
REQUIRE(val->event == NULL);
|
|
REQUIRE(val->fetch == NULL);
|
|
|
|
val->magic = 0;
|
|
if (val->key != NULL) {
|
|
dst_key_free(&val->key);
|
|
}
|
|
if (val->keytable != NULL) {
|
|
if (val->keynode != NULL) {
|
|
dns_keytable_detachkeynode(val->keytable,
|
|
&val->keynode);
|
|
}
|
|
dns_keytable_detach(&val->keytable);
|
|
}
|
|
if (val->subvalidator != NULL) {
|
|
dns_validator_destroy(&val->subvalidator);
|
|
}
|
|
disassociate_rdatasets(val);
|
|
mctx = val->view->mctx;
|
|
if (val->siginfo != NULL) {
|
|
isc_mem_put(mctx, val->siginfo, sizeof(*val->siginfo));
|
|
}
|
|
isc_mutex_destroy(&val->lock);
|
|
dns_view_weakdetach(&val->view);
|
|
isc_mem_put(mctx, val, sizeof(*val));
|
|
}
|
|
|
|
void
|
|
dns_validator_destroy(dns_validator_t **validatorp) {
|
|
dns_validator_t *val;
|
|
bool want_destroy = false;
|
|
|
|
REQUIRE(validatorp != NULL);
|
|
val = *validatorp;
|
|
*validatorp = NULL;
|
|
REQUIRE(VALID_VALIDATOR(val));
|
|
|
|
LOCK(&val->lock);
|
|
|
|
val->attributes |= VALATTR_SHUTDOWN;
|
|
validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_destroy");
|
|
|
|
want_destroy = exit_check(val);
|
|
UNLOCK(&val->lock);
|
|
if (want_destroy) {
|
|
destroy(val);
|
|
}
|
|
}
|
|
|
|
static void
|
|
validator_logv(dns_validator_t *val, isc_logcategory_t *category,
|
|
isc_logmodule_t *module, int level, const char *fmt, va_list ap)
|
|
{
|
|
char msgbuf[2048];
|
|
static const char spaces[] = " *";
|
|
int depth = val->depth * 2;
|
|
const char *viewname, *sep1, *sep2;
|
|
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
|
|
|
|
if ((unsigned int) depth >= sizeof spaces) {
|
|
depth = sizeof spaces - 1;
|
|
}
|
|
|
|
/*
|
|
* Log the view name unless it's:
|
|
* * "_default/IN" (which means there's only one view
|
|
* configured in the server), or
|
|
* * "_dnsclient/IN" (which means this is being called
|
|
* from an application using dns/client.c).
|
|
*/
|
|
if (val->view->rdclass == dns_rdataclass_in &&
|
|
(strcmp(val->view->name, "_default") == 0 ||
|
|
strcmp(val->view->name, DNS_CLIENTVIEW_NAME) == 0))
|
|
{
|
|
sep1 = viewname = sep2 = "";
|
|
} else {
|
|
sep1 = "view ";
|
|
viewname = val->view->name;
|
|
sep2 = ": ";
|
|
}
|
|
|
|
if (val->event != NULL && val->event->name != NULL) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_name_format(val->event->name, namebuf, sizeof(namebuf));
|
|
dns_rdatatype_format(val->event->type, typebuf,
|
|
sizeof(typebuf));
|
|
isc_log_write(dns_lctx, category, module, level,
|
|
"%s%s%s%.*svalidating %s/%s: %s",
|
|
sep1, viewname, sep2, depth, spaces,
|
|
namebuf, typebuf, msgbuf);
|
|
} else {
|
|
isc_log_write(dns_lctx, category, module, level,
|
|
"%s%s%s%.*svalidator @%p: %s",
|
|
sep1, viewname, sep2, depth, spaces,
|
|
val, msgbuf);
|
|
}
|
|
}
|
|
|
|
static void
|
|
validator_log(void *val, int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
|
|
if (!isc_log_wouldlog(dns_lctx, level)) {
|
|
return;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
|
|
validator_logv(val, DNS_LOGCATEGORY_DNSSEC,
|
|
DNS_LOGMODULE_VALIDATOR, level, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void
|
|
validator_logcreate(dns_validator_t *val, dns_name_t *name,
|
|
dns_rdatatype_t type, const char *caller,
|
|
const char *operation)
|
|
{
|
|
char namestr[DNS_NAME_FORMATSIZE];
|
|
char typestr[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
dns_name_format(name, namestr, sizeof(namestr));
|
|
dns_rdatatype_format(type, typestr, sizeof(typestr));
|
|
validator_log(val, ISC_LOG_DEBUG(9), "%s: creating %s for %s %s",
|
|
caller, operation, namestr, typestr);
|
|
}
|