157 lines
4.7 KiB
C
157 lines
4.7 KiB
C
/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include <contrib/cleanup.h>
|
|
#include <libknot/descriptor.h>
|
|
#include <libknot/rdataset.h>
|
|
#include <libknot/rrset.h>
|
|
#include <libknot/packet/wire.h>
|
|
#include <libdnssec/key.h>
|
|
#include <libdnssec/error.h>
|
|
|
|
#include "lib/defines.h"
|
|
#include "lib/dnssec.h"
|
|
#include "lib/dnssec/ta.h"
|
|
#include "lib/resolve.h"
|
|
#include "lib/utils.h"
|
|
|
|
knot_rrset_t *kr_ta_get(trie_t *trust_anchors, const knot_dname_t *name)
|
|
{
|
|
trie_val_t *val = trie_get_try(trust_anchors, (const char *)name, strlen((const char *)name));
|
|
return (val) ? *val : NULL;
|
|
}
|
|
|
|
const knot_dname_t * kr_ta_closest(const struct kr_context *ctx, const knot_dname_t *name,
|
|
const uint16_t type)
|
|
{
|
|
kr_require(ctx && name);
|
|
if (type == KNOT_RRTYPE_DS && name[0] != '\0') {
|
|
/* DS is parent-side record, so the parent name needs to be covered. */
|
|
name = knot_dname_next_label(name);
|
|
}
|
|
do {
|
|
struct kr_context *ctx_nc = (struct kr_context *)/*const-cast*/ctx;
|
|
if (kr_ta_get(ctx_nc->trust_anchors, name)) {
|
|
return name;
|
|
}
|
|
if (kr_ta_get(ctx_nc->negative_anchors, name)) {
|
|
return NULL;
|
|
}
|
|
if (name[0] == '\0') {
|
|
return NULL;
|
|
} else {
|
|
name = knot_dname_next_label(name);
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
/* @internal Create DS from DNSKEY, caller MUST free dst if successful. */
|
|
static int dnskey2ds(dnssec_binary_t *dst, const knot_dname_t *owner, const uint8_t *rdata, uint16_t rdlen)
|
|
{
|
|
dnssec_key_t *key = NULL;
|
|
int ret = dnssec_key_new(&key);
|
|
if (ret) goto cleanup;
|
|
/* Create DS from DNSKEY and reinsert */
|
|
const dnssec_binary_t key_data = { .size = rdlen, .data = (uint8_t *)rdata };
|
|
ret = dnssec_key_set_rdata(key, &key_data);
|
|
if (ret) goto cleanup;
|
|
/* Accept only keys with Zone and SEP flags that aren't revoked,
|
|
* as a precaution. RFC 5011 also utilizes these flags.
|
|
* TODO: kr_dnssec_key_* names are confusing. */
|
|
const bool flags_ok = kr_dnssec_key_zsk(rdata) && !kr_dnssec_key_revoked(rdata);
|
|
if (!flags_ok) {
|
|
auto_free char *owner_str = kr_dname_text(owner);
|
|
kr_log_error(TA, "refusing to trust %s DNSKEY because of flags %d\n",
|
|
owner_str, dnssec_key_get_flags(key));
|
|
ret = kr_error(EILSEQ);
|
|
goto cleanup;
|
|
} else if (!kr_dnssec_key_ksk(rdata)) {
|
|
auto_free char *owner_str = kr_dname_text(owner);
|
|
int flags = dnssec_key_get_flags(key);
|
|
kr_log_warning(TA, "warning: %s DNSKEY is missing the SEP bit; "
|
|
"flags %d instead of %d\n",
|
|
owner_str, flags, flags + 1/*a little ugly*/);
|
|
}
|
|
ret = dnssec_key_set_dname(key, owner);
|
|
if (ret) goto cleanup;
|
|
ret = dnssec_key_create_ds(key, DNSSEC_KEY_DIGEST_SHA256, dst);
|
|
cleanup:
|
|
dnssec_key_free(key);
|
|
return kr_error(ret);
|
|
}
|
|
|
|
/* @internal Insert new TA to trust anchor set, rdata MUST be of DS type. */
|
|
static int insert_ta(trie_t *trust_anchors, const knot_dname_t *name,
|
|
uint32_t ttl, const uint8_t *rdata, uint16_t rdlen)
|
|
{
|
|
bool is_new_key = false;
|
|
knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, name);
|
|
if (!ta_rr) {
|
|
ta_rr = knot_rrset_new(name, KNOT_RRTYPE_DS, KNOT_CLASS_IN, ttl, NULL);
|
|
is_new_key = true;
|
|
}
|
|
/* Merge-in new key data */
|
|
if (!ta_rr || (rdlen > 0 && knot_rrset_add_rdata(ta_rr, rdata, rdlen, NULL) != 0)) {
|
|
knot_rrset_free(ta_rr, NULL);
|
|
return kr_error(ENOMEM);
|
|
}
|
|
if (is_new_key) {
|
|
trie_val_t *val = trie_get_ins(trust_anchors, (const char *)name, strlen((const char *)name));
|
|
if (kr_fails_assert(val))
|
|
return kr_error(EINVAL);
|
|
*val = ta_rr;
|
|
}
|
|
return kr_ok();
|
|
}
|
|
|
|
int kr_ta_add(trie_t *trust_anchors, const knot_dname_t *name, uint16_t type,
|
|
uint32_t ttl, const uint8_t *rdata, uint16_t rdlen)
|
|
{
|
|
if (!trust_anchors || !name) {
|
|
return kr_error(EINVAL);
|
|
}
|
|
|
|
/* DS/DNSKEY types are accepted, for DNSKEY we
|
|
* need to compute a DS digest. */
|
|
if (type == KNOT_RRTYPE_DS) {
|
|
return insert_ta(trust_anchors, name, ttl, rdata, rdlen);
|
|
} else if (type == KNOT_RRTYPE_DNSKEY) {
|
|
dnssec_binary_t ds_rdata = { 0, };
|
|
int ret = dnskey2ds(&ds_rdata, name, rdata, rdlen);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
ret = insert_ta(trust_anchors, name, ttl, ds_rdata.data, ds_rdata.size);
|
|
dnssec_binary_free(&ds_rdata);
|
|
return ret;
|
|
} else { /* Invalid type for TA */
|
|
return kr_error(EINVAL);
|
|
}
|
|
}
|
|
|
|
/* Delete record data */
|
|
static int del_record(trie_val_t *v, void *ext)
|
|
{
|
|
knot_rrset_t *ta_rr = *v;
|
|
if (ta_rr) {
|
|
knot_rrset_free(ta_rr, NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int kr_ta_del(trie_t *trust_anchors, const knot_dname_t *name)
|
|
{
|
|
knot_rrset_t *ta_rr;
|
|
int ret = trie_del(trust_anchors, (const char *)name, strlen((const char *)name),
|
|
(trie_val_t *) &ta_rr);
|
|
if (ret == KNOT_EOK && ta_rr)
|
|
knot_rrset_free(ta_rr, NULL);
|
|
return kr_ok();
|
|
}
|
|
|
|
void kr_ta_clear(trie_t *trust_anchors)
|
|
{
|
|
trie_apply(trust_anchors, del_record, NULL);
|
|
trie_clear(trust_anchors);
|
|
}
|