]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
rrsig checks.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 9 Aug 2007 09:58:04 +0000 (09:58 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 9 Aug 2007 09:58:04 +0000 (09:58 +0000)
git-svn-id: file:///svn/unbound/trunk@502 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
testcode/unitdname.c
util/config_file.c
util/config_file.h
util/data/dname.c
util/data/dname.h
util/net_help.h
validator/val_sigcrypt.c
validator/val_sigcrypt.h
validator/validator.c
validator/validator.h

index e843be86d1af9edf50ea2c31cbdb582a5fc666c4..189acf1682c1dc7e781ae0e0b183d119b23c4d59 100644 (file)
@@ -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
index 03e3a6e6196f1d6a131f7d9f0edd53f3ef9ac128..25e33e707eac57e721f687525b99c7ee319dd2b3 100644 (file)
@@ -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);
 }
index 0647fcffbdd88bfa3ab15092752b26d7017f7eee..2be1ff78459a8812fb70ded0f5797d9132469dc7 100644 (file)
@@ -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:
index d91082e52bc8e7d144ea7402b11ce37714e4cbc2..d70f33c28198ceadfec6f642ec635646d71bdb45 100644 (file)
@@ -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;
 };
index f6ee7a677bc696a5e4d301d933e848a0b6cf3058..9d446b9c172ce9d50981e9735990bb09f4d403be 100644 (file)
@@ -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;
+}
index 3b673388ea2fb9add86c24d8d386369ad7d76b1b..57f2c2b277632464aca5bfe4b2f3bf6d466665a7 100644 (file)
@@ -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 */
index 7ee4460a65487b034ee2cc175c60fa242cac3b02..4847a55f8e10d2b3e21d92d2eacb8a6399e45e49 100644 (file)
 /** 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.
index 0808d1dd764a66a6c9bd7fafd22f5c16e17cf7c2..614310d7a5c59f24ff79cd393bedf349d2c06463 100644 (file)
  */
 #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; i<fqdn_labels-rrsig_labels; i++) {
+                       dname_remove_label(&nm, &len);  
+               }
+               *can_owner_len = len+2;
+               ldns_buffer_write(buf, (uint8_t*)"\001*", 2);
+               ldns_buffer_write(buf, nm, len);
+               query_dname_tolower(*can_owner);
+       }
+}
+
+/** 
+ * Lowercase a text rdata field in a buffer.
+ * @param p: pointer to start of text field (length byte).
+ */
+static void
+lowercase_text_field(uint8_t* p)
+{
+       int i, len = (int)*p;
+       p++;
+       for(i=0; i<len; i++) {
+               *p = (uint8_t)tolower((int)*p);
+               p++;
+       }
+}
+
+/**
+ * Canonicalize Rdata in buffer.
+ * @param buf: buffer at position just after the rdata.
+ * @param rrset: rrset with type.
+ * @param len: length of the rdata (including rdatalen uint16).
+ */
+static void
+canonicalize_rdata(ldns_buffer* buf, struct ub_packed_rrset_key* rrset,
+       size_t len)
+{
+       uint8_t* datstart = ldns_buffer_current(buf)-len+2;
+       switch(ntohs(rrset->rk.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; i<d->count; 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;
index 43a83e18d6aa769dafeb7fcbf02503d3213dfaa6..cbaff26047cbe36e402eb00794b2b013bc85e2ee 100644 (file)
@@ -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.
index 28ed54bf36f334b92e8f72d4f82f733865102b85..5f28ca0122b2e2fa06503c33111e9ce306eaf84c 100644 (file)
@@ -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;
 }
 
index bfa3a42c976fae5478f631e02f02edaf05d9e37f..9aac905c64e5806d06c62668dab74dd543cea12c 100644 (file)
@@ -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;
 };
 
 /**