Newer
Older
/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <assert.h>
#include <dnssec/binary.h>
#include <dnssec/crypto.h>
#include <dnssec/error.h>
#include <dnssec/key.h>
#include <dnssec/sign.h>
#include <libknot/descriptor.h>
#include <libknot/packet/wire.h>
#include <libknot/rdataset.h>
#include <libknot/rrset.h>
#include <libknot/rrtype/dnskey.h>
#include <libknot/rrtype/nsec.h>
#include <libknot/rrtype/rrsig.h>
#include "lib/defines.h"
#include "lib/dnssec/nsec.h"
#include "lib/dnssec/nsec3.h"
#include "lib/dnssec/signature.h"
#include "lib/dnssec.h"
#define DEBUG_MSG(fmt...) fprintf(stderr, fmt)
void kr_crypto_init(void)
{
dnssec_crypto_init();
}
void kr_crypto_cleanup(void)
{
dnssec_crypto_cleanup();
}
void kr_crypto_reinit(void)
{
dnssec_crypto_reinit();
}
#define FLG_WILDCARD_EXPANSION 0x01 /**< Possibly generated by using wildcard expansion. */
/**
* Check the RRSIG RR validity according to RFC4035 5.3.1 .
* @param flags The flags are going to be set according to validation result.
* @param cov_labels Covered RRSet owner label count.
* @param rrsigs RRSet containing the signatures.
* @param sig_pos Specifies the signature within the RRSIG RRSet.
* @param keys Associated DNSKEY RRSet.
* @param key_pos Specifies the key within the DNSKEY RRSet,
* @param zone_name The name of the zone cut.
* @param timestamp Validation time.
*/
static int validate_rrsig_rr(int *flags, int cov_labels,
const knot_rrset_t *rrsigs, size_t sig_pos,
const knot_rrset_t *keys, size_t key_pos, uint16_t keytag,
const knot_dname_t *zone_name, uint32_t timestamp)
if (!flags || !rrsigs || !keys || !zone_name) {
return kr_error(EINVAL);
}
/* bullet 5 */
if (knot_rrsig_sig_expiration(&rrsigs->rrs, sig_pos) < timestamp) {
return kr_error(EINVAL);
}
/* bullet 6 */
if (knot_rrsig_sig_inception(&rrsigs->rrs, sig_pos) > timestamp) {
return kr_error(EINVAL);
}
/* bullet 2 */
const knot_dname_t *signer_name = knot_rrsig_signer_name(&rrsigs->rrs, sig_pos);
if (!signer_name || !knot_dname_is_equal(signer_name, zone_name)) {
return kr_error(EINVAL);
}
/* bullet 4 */
{
int rrsig_labels = knot_rrsig_labels(&rrsigs->rrs, sig_pos);
return kr_error(EINVAL);
}
*flags |= FLG_WILDCARD_EXPANSION;
}
/* bullet 7 */
if ((!knot_dname_is_equal(keys->owner, signer_name)) ||
(knot_dnskey_alg(&keys->rrs, key_pos) != knot_rrsig_algorithm(&rrsigs->rrs, sig_pos)) ||
(keytag != knot_rrsig_key_tag(&rrsigs->rrs, sig_pos))) {
return kr_error(EINVAL);
}
/* bullet 8 */
/* Checked somewhere else. */
/* bullet 9 and 10 */
/* One of the requirements should be always fulfilled. */
return kr_ok();
}
/**
* Returns the number of labels that have been added by wildcard expansion.
* @param expanded Expanded wildcard.
* @param rrsigs RRSet containing the signatures.
* @param sig_pos Specifies the signature within the RRSIG RRSet.
* @return Number of added labels, -1 on error.
*/
static int wildcard_radix_len_diff(const knot_dname_t *expanded,
const knot_rrset_t *rrsigs, size_t sig_pos)
{
if (!expanded || !rrsigs) {
return -1;
}
return knot_dname_labels(expanded, NULL) - knot_rrsig_labels(&rrsigs->rrs, sig_pos);
}
int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, const knot_rrset_t *covered)
if (!vctx) {
return kr_error(EINVAL);
}
if (!vctx->pkt || !covered || !vctx->keys || !vctx->zone_name) {
for (unsigned i = 0; i < vctx->keys->rrs.rr_count; ++i) {
int ret = kr_rrset_validate_with_key(vctx, covered, i, NULL);
int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx,
const knot_rrset_t *covered,
size_t key_pos, const struct dseckey *key)
{
const knot_pkt_t *pkt = vctx->pkt;
knot_section_t section_id = vctx->section_id;
const knot_rrset_t *keys = vctx->keys;
const knot_dname_t *zone_name = vctx->zone_name;
uint32_t timestamp = vctx->timestamp;
bool has_nsec3 = vctx->has_nsec3;
struct dseckey *created_key = NULL;
if (key == NULL) {
const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, key_pos);
int ret = kr_dnssec_key_from_rdata(&created_key, keys->owner,
knot_rdata_data(krr), knot_rdata_rdlen(krr));
vctx->result = ret;
return vctx->result;
}
key = created_key;
}
uint16_t keytag = dnssec_key_get_keytag((dnssec_key_t *)key);
int covered_labels = knot_dname_labels(covered->owner, NULL);
if (knot_dname_is_wildcard(covered->owner)) {
/* The asterisk does not count, RFC4034 3.1.3, paragraph 3. */
--covered_labels;
}
const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id);
for (unsigned i = 0; i < sec->count; ++i) {
/* Consider every RRSIG that matches owner and covers the class/type. */
const knot_rrset_t *rrsig = knot_pkt_rr(sec, i);
if (rrsig->type != KNOT_RRTYPE_RRSIG) {
continue;
}
if ((covered->rclass != rrsig->rclass) || !knot_dname_is_equal(covered->owner, rrsig->owner)) {
continue;
}
for (uint16_t j = 0; j < rrsig->rrs.rr_count; ++j) {
int val_flgs = 0;
int trim_labels = 0;
if (knot_rrsig_type_covered(&rrsig->rrs, j) != covered->type) {
continue;
}
if (validate_rrsig_rr(&val_flgs, covered_labels, rrsig, j,
keys, key_pos, keytag,
zone_name, timestamp) != 0) {
if (val_flgs & FLG_WILDCARD_EXPANSION) {
trim_labels = wildcard_radix_len_diff(covered->owner, rrsig, j);
if (trim_labels < 0) {
break;
}
}
if (kr_check_signature(rrsig, j, (dnssec_key_t *) key, covered, trim_labels) != 0) {
if (val_flgs & FLG_WILDCARD_EXPANSION) {
if (!has_nsec3) {
ret = kr_nsec_wildcard_answer_response_check(pkt, KNOT_AUTHORITY, covered->owner);
} else {
ret = kr_nsec3_wildcard_answer_response_check(pkt, KNOT_AUTHORITY, covered->owner, trim_labels - 1);
}
if (ret != 0) {
continue;
}
/* Validated with current key, OK */
kr_dnssec_key_free(&created_key);
vctx->result = kr_ok();
return vctx->result;
}
/* No applicable key found, cannot be validated. */
kr_dnssec_key_free(&created_key);
vctx->result = kr_error(ENOENT);
return vctx->result;
}
int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rrset_t *ta)
{
const knot_pkt_t *pkt = vctx->pkt;
const knot_rrset_t *keys = vctx->keys;
if (!pkt || !keys || !ta) {
return kr_error(EINVAL);
}
/* RFC4035 5.2, bullet 1
* The supplied DS record has been authenticated.
* It has been validated or is part of a configured trust anchor.
*/
for (uint16_t i = 0; i < keys->rrs.rr_count; ++i) {
/* RFC4035 5.3.1, bullet 8 */ /* ZSK */
const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, i);
const uint8_t *key_data = knot_rdata_data(krr);
Marek Vavruša
committed
if (!kr_dnssec_key_zsk(key_data) || kr_dnssec_key_revoked(key_data)) {
continue;
}
struct dseckey *key;
if (kr_dnssec_key_from_rdata(&key, keys->owner, key_data, knot_rdata_rdlen(krr)) != 0) {
continue;
}
if (kr_authenticate_referral(ta, (dnssec_key_t *) key) != 0) {
kr_dnssec_key_free(&key);
if (kr_rrset_validate_with_key(vctx, keys, i, key) != 0) {
kr_dnssec_key_free(&key);
continue;
}
kr_dnssec_key_free(&key);
assert (vctx->result == 0);
return vctx->result;
/* No useable key found */
vctx->result = kr_error(ENOENT);
return vctx->result;
Marek Vavruša
committed
bool kr_dnssec_key_zsk(const uint8_t *dnskey_rdata)
{
return wire_read_u16(dnskey_rdata) & 0x0100;
}
bool kr_dnssec_key_ksk(const uint8_t *dnskey_rdata)
{
return wire_read_u16(dnskey_rdata) & 0x0001;
/** Return true if the DNSKEY is revoked. */
bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata)
{
return wire_read_u16(dnskey_rdata) & 0x0080;
}
int kr_dnssec_key_tag(uint16_t rrtype, const uint8_t *rdata, size_t rdlen)
{
if (!rdata || rdlen == 0 || (rrtype != KNOT_RRTYPE_DS && rrtype != KNOT_RRTYPE_DNSKEY)) {
return kr_error(EINVAL);
}
if (rrtype == KNOT_RRTYPE_DS) {
return wire_read_u16(rdata);
} else if (rrtype == KNOT_RRTYPE_DNSKEY) {
struct dseckey *key = NULL;
int ret = kr_dnssec_key_from_rdata(&key, NULL, rdata, rdlen);
if (ret != 0) {
return ret;
}
uint16_t keytag = dnssec_key_get_keytag((dnssec_key_t *)key);
kr_dnssec_key_free(&key);
return keytag;
} else {
return kr_error(EINVAL);
}
}
Marek Vavruša
committed
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
int kr_dnssec_key_match(const uint8_t *key_a_rdata, size_t key_a_rdlen,
const uint8_t *key_b_rdata, size_t key_b_rdlen)
{
dnssec_key_t *key_a = NULL, *key_b = NULL;
int ret = kr_dnssec_key_from_rdata((struct dseckey **)&key_a, NULL, key_a_rdata, key_a_rdlen);
if (ret != 0) {
return ret;
}
ret = kr_dnssec_key_from_rdata((struct dseckey **)&key_b, NULL, key_b_rdata, key_b_rdlen);
if (ret != 0) {
dnssec_key_free(key_a);
return ret;
}
/* If the algorithm and the public key match, we can be sure
* that they are the same key. */
ret = kr_error(ENOENT);
dnssec_binary_t pk_a, pk_b;
if (dnssec_key_get_algorithm(key_a) == dnssec_key_get_algorithm(key_b) &&
dnssec_key_get_pubkey(key_a, &pk_a) == DNSSEC_EOK &&
dnssec_key_get_pubkey(key_b, &pk_b) == DNSSEC_EOK) {
if (pk_a.size == pk_b.size && memcmp(pk_a.data, pk_b.data, pk_a.size) == 0) {
ret = 0;
}
}
dnssec_key_free(key_a);
dnssec_key_free(key_b);
return ret;
}
int kr_dnssec_key_from_rdata(struct dseckey **key, const knot_dname_t *kown, const uint8_t *rdata, size_t rdlen)
{
if (!key || !rdata || rdlen == 0) {
return kr_error(EINVAL);
}
dnssec_key_t *new_key = NULL;
const dnssec_binary_t binary_key = {
.size = rdlen,
.data = (uint8_t *)rdata
};
int ret = dnssec_key_new(&new_key);
if (ret != DNSSEC_EOK) {
return kr_error(ENOMEM);
}
ret = dnssec_key_set_rdata(new_key, &binary_key);
if (ret != DNSSEC_EOK) {
dnssec_key_free(new_key);
return kr_error(ENOMEM);
}
if (kown) {
ret = dnssec_key_set_dname(new_key, kown);
if (ret != DNSSEC_EOK) {
dnssec_key_free(new_key);
return kr_error(ENOMEM);
}
}
*key = (struct dseckey *) new_key;
return kr_ok();
}
void kr_dnssec_key_free(struct dseckey **key)
{
assert(key);
dnssec_key_free((dnssec_key_t *) *key);
*key = NULL;
}