From: Wouter Wijngaards Date: Thu, 9 Aug 2007 09:58:04 +0000 (+0000) Subject: rrsig checks. X-Git-Tag: release-0.5~139 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=45f95a18afb1738827b653d4ac2360a70b83ffa5;p=thirdparty%2Funbound.git rrsig checks. git-svn-id: file:///svn/unbound/trunk@502 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index e843be86d..189acf168 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +9 August 2007: Wouter + - canonicalization, signature checks + - dname signature label count and unit test. + 8 August 2007: Wouter - ldns _raw routines created (in ldns trunk). - sigcrypt DS digest routines diff --git a/testcode/unitdname.c b/testcode/unitdname.c index 03e3a6e61..25e33e707 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -76,21 +76,21 @@ static void dname_test_qdtl(ldns_buffer* buff) { ldns_buffer_write_at(buff, 0, "\012abCDeaBCde\003cOm\000", 16); - query_dname_tolower(ldns_buffer_begin(buff), 16); + query_dname_tolower(ldns_buffer_begin(buff)); unit_assert( memcmp(ldns_buffer_begin(buff), "\012abcdeabcde\003com\000", 16) == 0); ldns_buffer_write_at(buff, 0, "\001+\012abC{e-ZYXe\003NET\000", 18); - query_dname_tolower(ldns_buffer_begin(buff), 18); + query_dname_tolower(ldns_buffer_begin(buff)); unit_assert( memcmp(ldns_buffer_begin(buff), "\001+\012abc{e-zyxe\003net\000", 18) == 0); ldns_buffer_write_at(buff, 0, "\000", 1); - query_dname_tolower(ldns_buffer_begin(buff), 1); + query_dname_tolower(ldns_buffer_begin(buff)); unit_assert( memcmp(ldns_buffer_begin(buff), "\000", 1) == 0); ldns_buffer_write_at(buff, 0, "\002NL\000", 4); - query_dname_tolower(ldns_buffer_begin(buff), 4); + query_dname_tolower(ldns_buffer_begin(buff)); unit_assert( memcmp(ldns_buffer_begin(buff), "\002nl\000", 4) == 0); } @@ -463,6 +463,25 @@ dname_test_removelabel() unit_assert( l == 1 ); } +/** test dname_signame_label_count */ +static void +dname_test_sigcount() +{ + unit_assert(dname_signame_label_count((uint8_t*)"\000") == 0); + unit_assert(dname_signame_label_count((uint8_t*)"\001*\000") == 0); + unit_assert(dname_signame_label_count((uint8_t*)"\003xom\000") == 1); + unit_assert(dname_signame_label_count( + (uint8_t*)"\001*\003xom\000") == 1); + unit_assert(dname_signame_label_count( + (uint8_t*)"\007example\003xom\000") == 2); + unit_assert(dname_signame_label_count( + (uint8_t*)"\001*\007example\003xom\000") == 2); + unit_assert(dname_signame_label_count( + (uint8_t*)"\003www\007example\003xom\000") == 3); + unit_assert(dname_signame_label_count( + (uint8_t*)"\001*\003www\007example\003xom\000") == 3); +} + void dname_test() { ldns_buffer* buff = ldns_buffer_new(65800); @@ -478,5 +497,6 @@ void dname_test() dname_test_subdomain(); dname_test_isroot(); dname_test_removelabel(); + dname_test_sigcount(); ldns_buffer_free(buff); } diff --git a/util/config_file.c b/util/config_file.c index 0647fcffb..2be1ff784 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -113,6 +113,7 @@ config_create() cfg->version = NULL; cfg->trust_anchor_file_list = NULL; cfg->trust_anchor_list = NULL; + cfg->val_date_override = 0; if(!(cfg->module_conf = strdup("iterator"))) goto error_exit; return cfg; error_exit: diff --git a/util/config_file.h b/util/config_file.h index d91082e52..d70f33c28 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -144,6 +144,9 @@ struct config_file { /** list of trustanchor keys, linked list */ struct config_strlist* trust_anchor_list; + /** if not 0, this value is the validation date for RRSIGs */ + int32_t val_date_override; + /** daemonize, i.e. fork into the background. */ int do_daemonize; }; diff --git a/util/data/dname.c b/util/data/dname.c index f6ee7a677..9d446b9c1 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -125,11 +125,10 @@ query_dname_compare(uint8_t* d1, uint8_t* d2) } void -query_dname_tolower(uint8_t* dname, size_t len) +query_dname_tolower(uint8_t* dname) { /* the dname is stored uncompressed */ uint8_t labellen; - log_assert(len > 0); labellen = *dname; while(labellen) { dname++; @@ -595,3 +594,22 @@ dname_remove_label(uint8_t** dname, size_t* len) *len -= lablen+1; *dname += lablen+1; } + +int +dname_signame_label_count(uint8_t* dname) +{ + uint8_t lablen; + int count = 0; + if(!*dname) + return 0; + if(dname[0] == 1 && dname[1] == '*') + dname += 2; + lablen = dname[0]; + while(lablen) { + count++; + dname += lablen; + dname += 1; + lablen = dname[0]; + } + return count; +} diff --git a/util/data/dname.h b/util/data/dname.h index 3b673388e..57f2c2b27 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -63,7 +63,7 @@ size_t query_dname_len(ldns_buffer* query); size_t dname_valid(uint8_t* dname, size_t len); /** lowercase query dname */ -void query_dname_tolower(uint8_t* dname, size_t len); +void query_dname_tolower(uint8_t* dname); /** * Compare query dnames (uncompressed storage). The Dnames passed do not @@ -222,4 +222,11 @@ int dname_is_root(uint8_t* dname); */ void dname_remove_label(uint8_t** dname, size_t* len); +/** + * Count labels for the RRSIG signature label field. + * Like a normal labelcount, but "*" wildcard and "." root are not counted. + * @param dname: valid uncompressed wireformat. + */ +int dname_signame_label_count(uint8_t* dname); + #endif /* UTIL_DATA_DNAME_H */ diff --git a/util/net_help.h b/util/net_help.h index 7ee4460a6..4847a55f8 100644 --- a/util/net_help.h +++ b/util/net_help.h @@ -72,6 +72,11 @@ /** byte size of ip6 address */ #define INET6_SIZE 16 +/** DNSKEY zone sign key flag */ +#define DNSKEY_BIT_ZSK 0x10 +/** DNSKEY secure entry point, KSK flag */ +#define DNSKEY_BIT_SEP 0x01 + /** * See if string is ip4 or ip6. * @param str: IP specification. diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 0808d1dd7..614310d7a 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -42,9 +42,11 @@ */ #include "config.h" #include "validator/val_sigcrypt.h" +#include "validator/validator.h" #include "util/data/msgreply.h" #include "util/data/dname.h" #include "util/module.h" +#include "util/net_help.h" #include "util/region-allocator.h" #ifndef HAVE_SSL @@ -116,6 +118,20 @@ rrset_get_rdata(struct ub_packed_rrset_key* k, size_t idx, uint8_t** rdata, *len = d->rr_len[idx]; } +uint16_t +dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx) +{ + uint8_t* rdata; + size_t len; + uint16_t f; + rrset_get_rdata(k, idx, &rdata, &len); + if(len < 2+2) + return 0; + memmove(&f, rdata+2, 2); + f = ntohs(f); + return f; +} + int dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx) { @@ -244,7 +260,7 @@ ds_create_dnskey_digest(struct module_env* env, ldns_buffer_clear(b); ldns_buffer_write(b, dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len); - query_dname_tolower(ldns_buffer_begin(b), dnskey_rrset->rk.dname_len); + query_dname_tolower(ldns_buffer_begin(b)); ldns_buffer_write(b, dnskey_rdata+2, dnskey_len-2); /* skip rdatalen*/ ldns_buffer_flip(b); @@ -416,20 +432,337 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, return sec_status_bogus; } +/** + * Sort RRs for rrset in canonical order. + * Does not actually canonicalize the RR rdatas. + * Does not touch rrsigs. + * @param rrset: to sort. + */ +static void +canonical_sort(struct ub_packed_rrset_key* rrset) +{ + /* check if already sorted */ + /* remove duplicates */ +} + +/** + * Inser canonical owner name into buffer. + * @param buf: buffer to insert into at current position. + * @param k: rrset with its owner name. + * @param sig: signature with signer name and label count. + * must be length checked, at least 18 bytes long. + * @param can_owner: position in buffer returned for future use. + * @param can_owner_len: length of canonical owner name. + */ +static void +insert_can_owner(ldns_buffer* buf, struct ub_packed_rrset_key* k, + uint8_t* sig, uint8_t** can_owner, size_t* can_owner_len) +{ + int rrsig_labels = (int)sig[3]; + int fqdn_labels = dname_signame_label_count(k->rk.dname); + *can_owner = ldns_buffer_current(buf); + if(rrsig_labels == fqdn_labels) { + /* no change */ + ldns_buffer_write(buf, k->rk.dname, k->rk.dname_len); + query_dname_tolower(*can_owner); + *can_owner_len = k->rk.dname_len; + return; + } + log_assert(rrsig_labels < fqdn_labels); + /* *. | fqdn(rightmost rrsig_labels) */ + if(rrsig_labels < fqdn_labels) { + int i; + uint8_t* nm = k->rk.dname; + size_t len = k->rk.dname_len; + /* so skip fqdn_labels-rrsig_labels */ + for(i=0; irk.type)) { + case LDNS_RR_TYPE_NXT: + case LDNS_RR_TYPE_NSEC: /* type starts with the name */ + case LDNS_RR_TYPE_NS: + case LDNS_RR_TYPE_MD: + case LDNS_RR_TYPE_MF: + case LDNS_RR_TYPE_CNAME: + case LDNS_RR_TYPE_MB: + case LDNS_RR_TYPE_MG: + case LDNS_RR_TYPE_MR: + case LDNS_RR_TYPE_PTR: + case LDNS_RR_TYPE_DNAME: + /* type only has a single argument, the name */ + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_MINFO: + case LDNS_RR_TYPE_RP: + case LDNS_RR_TYPE_SOA: + /* two names after another */ + query_dname_tolower(datstart); + query_dname_tolower(datstart + + dname_valid(datstart, len-2)); + return; + case LDNS_RR_TYPE_HINFO: + /* lowercase text records */ + len -= 2; + if(len < (size_t)datstart[0]+1) + return; + lowercase_text_field(datstart); + len -= (size_t)datstart[0]+1; /* and skip the 1st */ + datstart += (size_t)datstart[0]+1; + if(len < (size_t)datstart[0]+1) + return; + lowercase_text_field(datstart); + return; + case LDNS_RR_TYPE_RT: + case LDNS_RR_TYPE_AFSDB: + case LDNS_RR_TYPE_KX: + case LDNS_RR_TYPE_MX: + /* skip fixed part */ + if(len < 2+2+1) /* rdlen, skiplen, 1byteroot */ + return; + datstart += 2; + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_SIG: + case LDNS_RR_TYPE_RRSIG: + /* skip fixed part */ + if(len < 2+18+1) + return; + datstart += 18; + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_PX: + /* skip, then two names after another */ + if(len < 2+2+1) + return; + datstart += 2; + query_dname_tolower(datstart); + query_dname_tolower(datstart + + dname_valid(datstart, len-2-2)); + return; + case LDNS_RR_TYPE_NAPTR: + if(len < 2+4) + return; + len -= 2+4; + datstart += 4; + if(len < (size_t)datstart[0]+1) /* skip text field */ + return; + len -= (size_t)datstart[0]+1; + datstart += (size_t)datstart[0]+1; + if(len < (size_t)datstart[0]+1) /* skip text field */ + return; + len -= (size_t)datstart[0]+1; + datstart += (size_t)datstart[0]+1; + if(len < (size_t)datstart[0]+1) /* skip text field */ + return; + len -= (size_t)datstart[0]+1; + datstart += (size_t)datstart[0]+1; + if(len < 1) /* check name is at least 1 byte*/ + return; + query_dname_tolower(datstart); + return; + case LDNS_RR_TYPE_SRV: + /* skip fixed part */ + if(len < 2+6+1) + return; + datstart += 6; + query_dname_tolower(datstart); + return; + /* A6 not supported */ + default: + /* nothing to do for unknown types */ + return; + } +} + +/** + * Create canonical form of rrset in the scratch buffer. + * @param buf: the buffer to use. + * @param k: the rrset to insert. + * @param sig: RRSIG rdata to include. + * @param siglen: RRSIG rdata len excluding signature field, but inclusive + * signer name length. + * @return false on alloc error. + */ +static int +rrset_canonical(ldns_buffer* buf, struct ub_packed_rrset_key* k, + uint8_t* sig, size_t siglen) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + size_t i; + uint8_t* can_owner = NULL; + size_t can_owner_len = 0; + /* sort RRs in place */ + canonical_sort(k); + + ldns_buffer_clear(buf); + ldns_buffer_write(buf, sig, siglen); + query_dname_tolower(sig+18); /* canonicalize signer name */ + for(i=0; icount; i++) { + /* determine canonical owner name */ + if(can_owner) + ldns_buffer_write(buf, can_owner, can_owner_len); + else insert_can_owner(buf, k, sig, &can_owner, + &can_owner_len); + ldns_buffer_write(buf, &k->rk.type, 2); + ldns_buffer_write(buf, &k->rk.rrset_class, 2); + ldns_buffer_write(buf, sig+4, 4); + ldns_buffer_write(buf, d->rr_data[i], d->rr_len[i]); + canonicalize_rdata(buf, k, d->rr_len[i]); + } + ldns_buffer_flip(buf); + return 1; +} + +/** check rrsig dates */ +static int +check_dates(struct val_env* ve, uint8_t* expi_p, uint8_t* incep_p) +{ + /* read out the dates */ + int32_t expi, incep, now; + memmove(&expi, expi_p, sizeof(expi)); + memmove(&incep, incep_p, sizeof(incep)); + expi = ntohl(expi); + incep = ntohl(incep); + + /* get current date */ + if(ve->date_override) + now = ve->date_override; + else now = (int32_t)time(0); + + /* check them */ + if(incep - expi > 0) { + verbose(VERB_ALGO, "verify: inception after expiration, " + "signature bad"); + return 0; + } + if(incep - now > 0) { + verbose(VERB_ALGO, "verify: signature bad, current time is" + " before inception date"); + return 0; + } + if(now - expi > 0) { + verbose(VERB_ALGO, "verify: signature expired"); + return 0; + } + return 1; + +} + enum sec_status dnskey_verify_rrset_sig(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, size_t sig_idx) { + uint8_t* sig; /* rdata */ + size_t siglen; + size_t rrnum = rrset_get_count(rrset); + uint8_t* signer; + size_t signer_len; + uint8_t* sigblock; /* signature rdata field */ + size_t sigblock_len; + uint16_t ktag; + rrset_get_rdata(rrset, rrnum + sig_idx, &sig, &siglen); + /* min length of rdatalen, fixed rrsig, root signer, 1 byte sig */ + if(siglen < 2+20) { + verbose(VERB_ALGO, "verify: signature too short"); + return sec_status_bogus; + } + + if(!(dnskey_get_flags(dnskey, dnskey_idx) & DNSKEY_BIT_ZSK)) { + verbose(VERB_ALGO, "verify: dnskey without ZSK flag"); + return sec_status_bogus; /* signer name invalid */ + } + /* verify as many fields in rrsig as possible */ + signer = sig+2+18; + signer_len = dname_valid(signer, siglen-2-18); + if(!signer_len) { + verbose(VERB_ALGO, "verify: malformed signer name"); + return sec_status_bogus; /* signer name invalid */ + } + sigblock = signer+signer_len; + if(siglen < 2+18+signer_len+1) { + verbose(VERB_ALGO, "verify: too short, no signature data"); + return sec_status_bogus; /* sig rdf is < 1 byte */ + } + sigblock_len = siglen - 2 - 18 - signer_len; + /* verify key dname == sig signer name */ + if(query_dname_compare(signer, dnskey->rk.dname) != 0) { + verbose(VERB_ALGO, "verify: wrong key for rrsig"); + return sec_status_bogus; + } + /* verify covered type */ + /* memcmp works because type is in network format for rrset */ + if(memcmp(sig+2, &rrset->rk.type, 2) != 0) { + verbose(VERB_ALGO, "verify: wrong type covered"); + return sec_status_bogus; + } /* verify keytag and sig algo (possibly again) */ + if((int)sig[2] != dnskey_get_algo(dnskey, dnskey_idx)) { + verbose(VERB_ALGO, "verify: wrong algorithm"); + return sec_status_bogus; + } + ktag = dnskey_calc_keytag(dnskey, dnskey_idx); + if(memcmp(sig+16, &ktag, 2) != 0) { + verbose(VERB_ALGO, "verify: wrong keytag"); + return sec_status_bogus; + } + /* verify labels is in a valid range */ + if((int)sig[3] > dname_signame_label_count(rrset->rk.dname)) { + verbose(VERB_ALGO, "verify: labelcount out of range"); + return sec_status_bogus; + } + /* original ttl, always ok */ + /* verify inception, expiration dates */ + if(!check_dates(ve, sig+8, sig+12)) { + return sec_status_bogus; + } /* create rrset canonical format in buffer, ready for signature */ + if(!rrset_canonical(env->scratch_buffer, rrset, sig+2, + 18 + signer_len)) { + log_err("verify: failed due to alloc error"); + return sec_status_unchecked; + } /* verify */ return sec_status_unchecked; diff --git a/validator/val_sigcrypt.h b/validator/val_sigcrypt.h index 43a83e18d..cbaff2604 100644 --- a/validator/val_sigcrypt.h +++ b/validator/val_sigcrypt.h @@ -122,6 +122,14 @@ int ds_get_key_algo(struct ub_packed_rrset_key* k, size_t idx); */ int dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx); +/** + * Get DNSKEY RR flags + * @param k: DNSKEY rrset. + * @param idx: which DNSKEY RR. + * @return flags or 0 if DNSKEY too short. + */ +uint16_t dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx); + /** * Verify rrset against dnskey rrset. * @param env: module environment, scratch space is used. diff --git a/validator/validator.c b/validator/validator.c index 28ed54bf3..5f28ca012 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -51,6 +51,7 @@ #include "util/log.h" #include "util/net_help.h" #include "util/region-allocator.h" +#include "util/config_file.h" /** apply config settings to validator */ static int @@ -72,6 +73,7 @@ val_apply_cfg(struct val_env* val_env, struct config_file* cfg) log_err("validator: error in trustanchors config"); return 0; } + val_env->date_override = cfg->val_date_override; return 1; } diff --git a/validator/validator.h b/validator/validator.h index bfa3a42c9..9aac905c6 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -65,6 +65,10 @@ struct val_env { /** key cache; these are validated keys. trusted keys only * end up here after being primed. */ struct key_cache* kcache; + + /** for debug testing a fixed validation date can be entered. + * if 0, current time is used for rrsig validation */ + int32_t date_override; }; /**