diff --git a/lib/dnssec.c b/lib/dnssec.c index 3c6f51ccbe35f79cd8923802edcb5328099e7876..0103ab8b4a6384e4115ad360aed3472675581d11 100644 --- a/lib/dnssec.c +++ b/lib/dnssec.c @@ -23,6 +23,7 @@ #include <libknot/rdataset.h> #include <libknot/rrset.h> #include <libknot/rrtype/dnskey.h> +#include <libknot/rrtype/rrsig.h> #include "lib/defines.h" @@ -76,17 +77,15 @@ void _knot_ds_digest(const knot_rdataset_t *rrs, size_t pos, *digest_size = knot_rdata_rdlen(rr) - 4; } -/* RFC4035 5.2, second bullet */ -static int authenticate_referral(const knot_rdata_t *krdata, const knot_dname_t *kown, - const knot_rrset_t *ds) +/* RFC4035 5.2, bullet 2 */ +static int authenticate_referral(const dnssec_key_t *key, const knot_rrset_t *ds) { - assert(krdata && kown && ds); + assert(key && ds); assert(ds->type == KNOT_RRTYPE_DS); int ret = 0; dnssec_binary_t orig_ds_rdata; dnssec_binary_t generated_ds_rdata = {0, }; - dnssec_key_t *key = NULL; { /* Obtain RDATA of the supplied DS. */ @@ -95,32 +94,6 @@ static int authenticate_referral(const knot_rdata_t *krdata, const knot_dname_t orig_ds_rdata.data = knot_rdata_data(rr); } - /* Set-up DNSKEY. */ - ret = dnssec_key_new(&key); - if (ret != DNSSEC_EOK) { - ret = kr_error(ENOMEM); - goto fail; - } - { - dnssec_binary_t binary_key; - binary_key.size = knot_rdata_rdlen(krdata); - binary_key.data = knot_rdata_data(krdata); - if (!binary_key.size || !binary_key.data) { - ret = kr_error(KNOT_DNSSEC_ENOKEY); - goto fail; - } - ret = dnssec_key_set_rdata(key, &binary_key); - if (ret != DNSSEC_EOK) { - ret = kr_error(ENOMEM); - goto fail; - } - } - ret = dnssec_key_set_dname(key, kown); - if (ret != DNSSEC_EOK) { - ret = kr_error(ENOMEM); - goto fail; - } - /* Compute DS RDATA from the DNSKEY. */ ret = dnssec_key_create_ds(key, _knot_ds_dtype(&ds->rrs, 0), &generated_ds_rdata); if (ret != DNSSEC_EOK) { @@ -136,32 +109,121 @@ static int authenticate_referral(const knot_rdata_t *krdata, const knot_dname_t fail: dnssec_binary_free(&generated_ds_rdata); - dnssec_key_free(key); return ret; } -int kr_dnskey_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, const knot_rrset_t *ta) +/* RFC4035 5.3.1 */ +static int validate_rrsig_rr(const knot_rrset_t *rrset, const knot_rrset_t *rrsig, + const knot_rrset_t *keys, size_t pos, const dnssec_key_t *key, + const knot_dname_t *zone_name, uint32_t timestamp) { - if (!sec || !keys || !ta) { + if (!rrset || !rrsig || !keys || !key || !zone_name) { + return kr_error(EINVAL); + } +#warning TODO: Make the comparison case-insensitive. + /* bullet 1 */ + if ((rrset->rclass != rrsig->rclass) || (knot_dname_cmp(rrset->owner, rrsig->owner) != 0)) { + return kr_error(EINVAL); + } + /* bullet 2 */ + const knot_dname_t *signer_name = knot_rrsig_signer_name(&rrsig->rrs, 0); + if (signer_name == NULL) { + return kr_error(EINVAL); + } + if (knot_dname_cmp(signer_name, zone_name) != 0) { + return kr_error(EINVAL); + } + /* bullet 3 */ + uint16_t tcovered = knot_rrsig_type_covered(&rrsig->rrs, 0); + if (tcovered != rrset->type) { + return kr_error(EINVAL); + } + /* bullet 4 */ + if (knot_rrsig_labels(&rrsig->rrs, 0) > knot_dname_labels(rrset->owner, NULL)) { return kr_error(EINVAL); } + /* bullet 5 */ + if (knot_rrsig_sig_expiration(&rrsig->rrs, 0) < timestamp) { + return kr_error(EINVAL); + } + /* bullet 6 */ + if (knot_rrsig_sig_inception(&rrsig->rrs, 0) > timestamp) { + return kr_error(EINVAL); + } + /* bullet 7 */ + if ((knot_dname_cmp(keys->owner, signer_name) != 0) || + (knot_dnskey_alg(&keys->rrs, pos) != knot_rrsig_algorithm(&rrsig->rrs, 0)) || + (dnssec_key_get_keytag(key) != knot_rrsig_key_tag(&rrsig->rrs, 0))) { + return kr_error(EINVAL); + } + /* bullet 8 */ + /* Checked somewhere else. */ + /* bullet 9 and 10 */ + /* One of the requirements should be always fulfilled. */ -#warning TODO: there should be an error saying that there is no matching key + return kr_ok(); +} + +/** Validate RRSet in canonical format. */ +static int crrset_validate(const knot_pktsection_t *sec, const knot_rrset_t *rrset, + const knot_rrset_t *keys, size_t pos, const dnssec_key_t *key, + const knot_dname_t *zone_name, uint32_t timestamp) +{ int ret = kr_error(KNOT_DNSSEC_ENOKEY); + for (unsigned i = 0; i < sec->count; ++i) { + /* Try every RRSIG. */ + const knot_rrset_t *rr = knot_pkt_rr(sec, i); + if (rr->type != KNOT_RRTYPE_RRSIG) { + continue; + } + if (validate_rrsig_rr(rrset, rr, keys, pos, key, zone_name, timestamp) != 0) { + continue; + } + ret = kr_ok(); + break; + } + + return kr_error(ENOSYS); +} + +int kr_dnskeys_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, + const knot_rrset_t *ta, const knot_dname_t *zone_name, uint32_t timestamp) +{ + if (!sec || !keys || !ta) { + return kr_error(EINVAL); + } - /* The supplied DS record has been authenticated. + /* RFC4035 5.2, bullet 1 + * The supplied DS record has been authenticated. * It has been validated or is part of a configured trust anchor. * * This implementation actually ignores the SEP flag. */ +#warning TODO: there should be an error saying that there is no matching key + int ret = kr_error(KNOT_DNSSEC_ENOKEY); for (uint16_t i = 0; i < keys->rrs.rr_count; ++i) { + /* RFC4035 5.3.1, bullet 8 */ /* ZSK */ + if (!(knot_dnskey_flags(&keys->rrs, i) & 0x0100)) { + continue; + } const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, i); - if (authenticate_referral(krr, keys->owner, ta) != 0) { + struct dseckey *key; + if (kr_dnssec_key_from_rdata(&key, krr, keys->owner) != 0) { + continue; + } + if (authenticate_referral((dnssec_key_t *) key, ta) != 0) { + kr_dnssec_key_free(&key); continue; } #warning TODO: Check the signature of the rrset. + if (crrset_validate(sec, keys, keys, i, (dnssec_key_t *) key, zone_name, timestamp) != 0) { + kr_dnssec_key_free(&key); + continue; + } + kr_dnssec_key_free(&key); ret = kr_ok(); + break; } if (ret != 0) { @@ -170,3 +232,46 @@ int kr_dnskey_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, co return kr_error(ENOSYS); } + +int kr_dnssec_key_from_rdata(struct dseckey **key, const knot_rdata_t *krdata, const knot_dname_t *kown) +{ + assert(key); + + dnssec_key_t *new_key = NULL; + dnssec_binary_t binary_key; + int ret; + + ret = dnssec_key_new(&new_key); + if (ret != DNSSEC_EOK) { + return kr_error(ENOMEM); + } + + binary_key.size = knot_rdata_rdlen(krdata); + binary_key.data = knot_rdata_data(krdata); + if (!binary_key.size || !binary_key.data) { + dnssec_key_free(new_key); + return kr_error(KNOT_DNSSEC_ENOKEY); + } + ret = dnssec_key_set_rdata(new_key, &binary_key); + if (ret != DNSSEC_EOK) { + dnssec_key_free(new_key); + return kr_error(ENOMEM); + } + + 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; +} diff --git a/lib/dnssec.h b/lib/dnssec.h index 2e89c3f3f90a198c1656394c9dfc630e9d6a9023..f10cbc5db4a825a2b87be77ca64c527f6cdc31a5 100644 --- a/lib/dnssec.h +++ b/lib/dnssec.h @@ -36,9 +36,29 @@ void kr_crypto_reinit(void); /** * Check whether the DNSKEY rrset matches the supplied trust anchor RRSet. - * @param sec Packet section containing the DNSKEY RRSet including its signatures. - * @param key DNSKEY RRSet to check. - * @param ta Trust anchor RRSet agains which to validate the DNSKEY. + * @param sec Packet section containing the DNSKEY RRSet including its signatures. + * @param key DNSKEY RRSet to check. + * @param ta Trust anchor RRSet agains which to validate the DNSKEY. + * @param zone_name Name of the zone containing the RRSet. + * @param timestamp Time stamp. * @return 0 or error code. */ -int kr_dnskey_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, const knot_rrset_t *ta); +int kr_dnskeys_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, + const knot_rrset_t *ta, const knot_dname_t *zone_name, uint32_t timestamp); + +/** Opaque DNSSEC key pointer. */ +struct dseckey; + +/** + * Construct a DNSSEC key. + * @param key Pointer to be set to newly created DNSSEC key. + * @param krdata Key RDATA. + * @param kown DNSKEY RRSet owner name. + */ +int kr_dnssec_key_from_rdata(struct dseckey **key, const knot_rdata_t *krdata, const knot_dname_t *kown); + +/** + * Frees the DNSSEC key. + * @param key Pointer to freed key. + */ +void kr_dnssec_key_free(struct dseckey **key); diff --git a/lib/layer/validate.c b/lib/layer/validate.c index c4cc6dd62fdea3e156e3fdc871ad34bad374c8bf..efe247cfb0c1c633edf51be8525483d2f89be869 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -540,9 +540,11 @@ static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer) return kr_error(KNOT_DNSSEC_ENOKEY); } +#warning TODO: Ensure canonical format of the whole DNSKEY RRSet. (Also remove duplicities?) + /* Check if there's a key for current TA. */ #warning TODO: check if there is a DNSKEY we can trust (matching current TA) - int ret = kr_dnskey_trusted(an, qry->zone_cut.key, qry->zone_cut.trust_anchor); + int ret = kr_dnskeys_trusted(an, qry->zone_cut.key, qry->zone_cut.trust_anchor, qry->zone_cut.name, qry->timestamp.tv_sec); if (ret != 0) { knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); return ret;