Diff for /embedaddon/dnsmasq/src/dnssec.c between versions 1.1.1.2 and 1.1.1.4

version 1.1.1.2, 2016/11/02 09:57:01 version 1.1.1.4, 2023/09/27 11:02:07
Line 1 Line 1
 /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>  /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
           and Copyright (c) 2012-2016 Simon Kelley           and Copyright (c) 2012-2020 Simon Kelley
   
    This program is free software; you can redistribute it and/or modify     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     it under the terms of the GNU General Public License as published by
Line 19 Line 19
   
 #ifdef HAVE_DNSSEC  #ifdef HAVE_DNSSEC
   
 #include <nettle/rsa.h>  
 #include <nettle/dsa.h>  
 #ifndef NO_NETTLE_ECC  
 #  include <nettle/ecdsa.h>  
 #  include <nettle/ecc-curve.h>  
 #endif  
 #include <nettle/nettle-meta.h>  
 #include <nettle/bignum.h>  
   
 /* Nettle-3.0 moved to a new API for DSA. We use a name that's defined in the new API  
    to detect Nettle-3, and invoke the backwards compatibility mode. */  
 #ifdef dsa_params_init  
 #include <nettle/dsa-compat.h>  
 #endif  
   
 #define SERIAL_UNDEF  -100  #define SERIAL_UNDEF  -100
 #define SERIAL_EQ        0  #define SERIAL_EQ        0
 #define SERIAL_LT       -1  #define SERIAL_LT       -1
 #define SERIAL_GT        1  #define SERIAL_GT        1
   
 /* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */  
 static char *ds_digest_name(int digest)  
 {  
   switch (digest)  
     {  
     case 1: return "sha1";  
     case 2: return "sha256";  
     case 3: return "gosthash94";  
     case 4: return "sha384";  
     default: return NULL;  
     }  
 }  
    
 /* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */  
 static char *algo_digest_name(int algo)  
 {  
   switch (algo)  
     {  
     case 1: return "md5";  
     case 3: return "sha1";  
     case 5: return "sha1";  
     case 6: return "sha1";  
     case 7: return "sha1";  
     case 8: return "sha256";  
     case 10: return "sha512";  
     case 12: return "gosthash94";  
     case 13: return "sha256";  
     case 14: return "sha384";  
     default: return NULL;  
     }  
 }  
     
 /* http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */  
 static char *nsec3_digest_name(int digest)  
 {  
   switch (digest)  
     {  
     case 1: return "sha1";  
     default: return NULL;  
     }  
 }  
    
 /* Find pointer to correct hash function in nettle library */  
 static const struct nettle_hash *hash_find(char *name)  
 {  
   int i;  
     
   if (!name)  
     return NULL;  
     
   for (i = 0; nettle_hashes[i]; i++)  
     {  
       if (strcmp(nettle_hashes[i]->name, name) == 0)  
         return nettle_hashes[i];  
     }  
   
   return NULL;  
 }  
   
 /* expand ctx and digest memory allocations if necessary and init hash function */  
 static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp)  
 {  
   static void *ctx = NULL;  
   static unsigned char *digest = NULL;  
   static unsigned int ctx_sz = 0;  
   static unsigned int digest_sz = 0;  
   
   void *new;  
   
   if (ctx_sz < hash->context_size)  
     {  
       if (!(new = whine_malloc(hash->context_size)))  
         return 0;  
       if (ctx)  
         free(ctx);  
       ctx = new;  
       ctx_sz = hash->context_size;  
     }  
     
   if (digest_sz < hash->digest_size)  
     {  
       if (!(new = whine_malloc(hash->digest_size)))  
         return 0;  
       if (digest)  
         free(digest);  
       digest = new;  
       digest_sz = hash->digest_size;  
     }  
   
   *ctxp = ctx;  
   *digestp = digest;  
   
   hash->init(ctx);  
   
   return 1;  
 }  
     
 static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
                               unsigned char *digest, size_t digest_len, int algo)  
 {  
   unsigned char *p;  
   size_t exp_len;  
     
   static struct rsa_public_key *key = NULL;  
   static mpz_t sig_mpz;  
   
   (void)digest_len;  
     
   if (key == NULL)  
     {  
       if (!(key = whine_malloc(sizeof(struct rsa_public_key))))  
         return 0;  
         
       nettle_rsa_public_key_init(key);  
       mpz_init(sig_mpz);  
     }  
     
   if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL)))  
     return 0;  
     
   key_len--;  
   if ((exp_len = *p++) == 0)  
     {  
       GETSHORT(exp_len, p);  
       key_len -= 2;  
     }  
     
   if (exp_len >= key_len)  
     return 0;  
     
   key->size =  key_len - exp_len;  
   mpz_import(key->e, exp_len, 1, 1, 0, 0, p);  
   mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len);  
   
   mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig);  
     
   switch (algo)  
     {  
     case 1:  
       return nettle_rsa_md5_verify_digest(key, digest, sig_mpz);  
     case 5: case 7:  
       return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz);  
     case 8:  
       return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz);  
     case 10:  
       return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz);  
     }  
   
   return 0;  
 }    
   
 static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
                               unsigned char *digest, size_t digest_len, int algo)  
 {  
   unsigned char *p;  
   unsigned int t;  
     
   static struct dsa_public_key *key = NULL;  
   static struct dsa_signature *sig_struct;  
     
   (void)digest_len;  
   
   if (key == NULL)  
     {  
       if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) ||   
           !(key = whine_malloc(sizeof(struct dsa_public_key))))   
         return 0;  
         
       nettle_dsa_public_key_init(key);  
       nettle_dsa_signature_init(sig_struct);  
     }  
     
   if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL)))  
     return 0;  
     
   t = *p++;  
     
   if (key_len < (213 + (t * 24)))  
     return 0;  
     
   mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20;  
   mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);  
   mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);  
   mpz_import(key->y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);  
     
   mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1);  
   mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21);  
     
   (void)algo;  
     
   return nettle_dsa_sha1_verify_digest(key, digest, sig_struct);  
 }   
    
 #ifndef NO_NETTLE_ECC  
 static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len,   
                                 unsigned char *sig, size_t sig_len,  
                                 unsigned char *digest, size_t digest_len, int algo)  
 {  
   unsigned char *p;  
   unsigned int t;  
   struct ecc_point *key;  
   
   static struct ecc_point *key_256 = NULL, *key_384 = NULL;  
   static mpz_t x, y;  
   static struct dsa_signature *sig_struct;  
     
   if (!sig_struct)  
     {  
       if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))))  
         return 0;  
         
       nettle_dsa_signature_init(sig_struct);  
       mpz_init(x);  
       mpz_init(y);  
     }  
     
   switch (algo)  
     {  
     case 13:  
       if (!key_256)  
         {  
           if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))  
             return 0;  
             
           nettle_ecc_point_init(key_256, &nettle_secp_256r1);  
         }  
         
       key = key_256;  
       t = 32;  
       break;  
         
     case 14:  
       if (!key_384)  
         {  
           if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))  
             return 0;  
             
           nettle_ecc_point_init(key_384, &nettle_secp_384r1);  
         }  
         
       key = key_384;  
       t = 48;  
       break;  
           
     default:  
       return 0;  
     }  
     
   if (sig_len != 2*t || key_len != 2*t ||  
       !(p = blockdata_retrieve(key_data, key_len, NULL)))  
     return 0;  
     
   mpz_import(x, t , 1, 1, 0, 0, p);  
   mpz_import(y, t , 1, 1, 0, 0, p + t);  
   
   if (!ecc_point_set(key, x, y))  
     return 0;  
     
   mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig);  
   mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t);  
     
   return nettle_ecdsa_verify(key, digest_len, digest, sig_struct);  
 }   
 #endif   
   
 static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
                                     unsigned char *digest, size_t digest_len, int algo)  
 {  
       
   /* Enure at runtime that we have support for this digest */  
   if (!hash_find(algo_digest_name(algo)))  
     return NULL;  
     
   /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */  
   switch (algo)  
     {  
     case 1: case 5: case 7: case 8: case 10:  
       return dnsmasq_rsa_verify;  
         
     case 3: case 6:   
       return dnsmasq_dsa_verify;  
    
 #ifndef NO_NETTLE_ECC     
     case 13: case 14:  
       return dnsmasq_ecdsa_verify;  
 #endif  
     }  
     
   return NULL;  
 }  
   
 static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
                   unsigned char *digest, size_t digest_len, int algo)  
 {  
   
   int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
               unsigned char *digest, size_t digest_len, int algo);  
     
   func = verify_func(algo);  
     
   if (!func)  
     return 0;  
   
   return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo);  
 }  
   
 /* Convert from presentation format to wire format, in place.  /* Convert from presentation format to wire format, in place.
    Also map UC -> LC.     Also map UC -> LC.
    Note that using extract_name to get presentation format     Note that using extract_name to get presentation format
Line 358  static int verify(struct blockdata *key_data, unsigned Line 37  static int verify(struct blockdata *key_data, unsigned
    character. In theory, if all the characters in a name were /000 or     character. In theory, if all the characters in a name were /000 or
    '.' or NAME_ESCAPE then all would have to be escaped, so the      '.' or NAME_ESCAPE then all would have to be escaped, so the 
    presentation format would be twice as long as the spec (1024).      presentation format would be twice as long as the spec (1024). 
   The buffers are all delcared as 2049 (allowing for the trailing zero)    The buffers are all declared as 2049 (allowing for the trailing zero) 
    for this reason.     for this reason.
 */  */
 static int to_wire(char *name)  static int to_wire(char *name)
Line 424  static void from_wire(char *name) Line 103  static void from_wire(char *name)
 static int count_labels(char *name)  static int count_labels(char *name)
 {  {
   int i;    int i;
  char *p;
   
   if (*name == 0)    if (*name == 0)
     return 0;      return 0;
   
  for (i = 0; *name; name++)  for (p = name, i = 0; *p; p++)
    if (*name == '.')    if (*p == '.')
       i++;        i++;
   
  return i+1;  /* Don't count empty first label. */
   return *name == '.' ? i : i+1;
 }  }
   
 /* Implement RFC1982 wrapped compare for 32-bit numbers */  /* Implement RFC1982 wrapped compare for 32-bit numbers */
Line 475  int setup_timestamp(void) Line 156  int setup_timestamp(void)
       if (difftime(timestamp_time, time(0)) <=  0)        if (difftime(timestamp_time, time(0)) <=  0)
         {          {
           /* time already OK, update timestamp, and do key checking from the start. */            /* time already OK, update timestamp, and do key checking from the start. */
          if (utime(daemon->timestamp_file, NULL) == -1)          if (utimes(daemon->timestamp_file, NULL) == -1)
             my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));              my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
           daemon->back_to_the_future = 1;            daemon->back_to_the_future = 1;
           return 0;            return 0;
Line 489  int setup_timestamp(void) Line 170  int setup_timestamp(void)
       int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666);        int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666);
       if (fd != -1)        if (fd != -1)
         {          {
          struct utimbuf timbuf;          struct timeval tv[2];
   
           close(fd);            close(fd);
                       
          timestamp_time = timbuf.actime = timbuf.modtime = 1420070400; /* 1-1-2015 */          timestamp_time = 1420070400; /* 1-1-2015 */
          if (utime(daemon->timestamp_file, &timbuf) == 0)          tv[0].tv_sec = tv[1].tv_sec = timestamp_time;
           tv[0].tv_usec = tv[1].tv_usec = 0;
           if (utimes(daemon->timestamp_file, tv) == 0)
             goto check_and_exit;              goto check_and_exit;
         }          }
     }      }
Line 503  int setup_timestamp(void) Line 186  int setup_timestamp(void)
 }  }
   
 /* Check whether today/now is between date_start and date_end */  /* Check whether today/now is between date_start and date_end */
static int check_date_range(u32 date_start, u32 date_end)static int is_check_date(unsigned long curtime)
 {  {
   unsigned long curtime = time(0);  
    
   /* Checking timestamps may be temporarily disabled */    /* Checking timestamps may be temporarily disabled */
           
   /* If the current time if _before_ the timestamp    /* If the current time if _before_ the timestamp
Line 519  static int check_date_range(u32 date_start, u32 date_e Line 200  static int check_date_range(u32 date_start, u32 date_e
     {      {
       if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0)        if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0)
         {          {
          if (utime(daemon->timestamp_file, NULL) != 0)          if (utimes(daemon->timestamp_file, NULL) != 0)
             my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));              my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
                       
             my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSEC signature timestamps."));
           daemon->back_to_the_future = 1;            daemon->back_to_the_future = 1;
          set_option_bool(OPT_DNSSEC_TIME);          daemon->dnssec_no_time_check = 0;
           queue_event(EVENT_RELOAD); /* purge cache */            queue_event(EVENT_RELOAD); /* purge cache */
         }           } 
   
      if (daemon->back_to_the_future == 0)      return daemon->back_to_the_future;
        return 1; 
     }      }
  else if (option_bool(OPT_DNSSEC_TIME))  else
    return 1;    return !daemon->dnssec_no_time_check;
   
  /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ 
  return serial_compare_32(curtime, date_start) == SERIAL_GT 
    && serial_compare_32(curtime, date_end) == SERIAL_LT; 
 }  }
   
/* Return bytes of canonicalised rdata, when the return value is zero, the remaining /* Return bytes of canonicalised rrdata one by one.
   data, pointed to by *p, should be used raw. */   Init state->ip with the RR, and state->end with the end of same.
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,   Init state->op to NULL.
                     unsigned char **p, u16 **desc)   Init state->desc to RR descriptor.
    Init state->buff with a MAXDNAME * 2 buffer.
    
    After each call which returns 1, state->op points to the next byte of data.
    On returning 0, the end has been reached.
 */
 struct rdata_state {
   u16 *desc;
   size_t c;
   unsigned char *end, *ip, *op;
   char *buff;
 };
 
 static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
 {  {
  int d = **desc;  int d;
       
  /* No more data needs mangling */  if (state->op && state->c != 1)
  if (d == (u16)-1) 
     {      {
      /* If there's more data than we have space for, just return what fits,      state->op++;
         we'll get called again for more chunks */      state->c--;
      if (end - *p > bufflen)      return 1;
        { 
          memcpy(buff, *p, bufflen); 
          *p += bufflen; 
          return bufflen; 
        } 
       
      return 0; 
     }      }
 
  (*desc)++;  while (1)
      {
  if (d == 0 && extract_name(header, plen, p, buff, 1, 0))      d = *(state->desc);
    /* domain-name, canonicalise */ 
    return to_wire(buff); 
  else 
    {  
      /* plain data preceding a domain-name, don't run off the end of the data */ 
      if ((end - *p) < d) 
        d = end - *p; 
               
      if (d != 0)      if (d == (u16)-1)
         {          {
          memcpy(buff, *p, d);          /* all the bytes to the end. */
          *p += d;          if ((state->c = state->end - state->ip) != 0)
             {
               state->op = state->ip;
               state->ip = state->end;;
             }
           else
             return 0;
         }          }
         else
           {
             state->desc++;
             
             if (d == (u16)0)
               {
                 /* domain-name, canonicalise */
                 int len;
                 
                 if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
                     (len = to_wire(state->buff)) == 0)
                   continue;
                 
                 state->c = len;
                 state->op = (unsigned char *)state->buff;
               }
             else
               {
                 /* plain data preceding a domain-name, don't run off the end of the data */
                 if ((state->end - state->ip) < d)
                   d = state->end - state->ip;
                 
                 if (d == 0)
                   continue;
                     
                 state->op = state->ip;
                 state->c = d;
                 state->ip += d;
               }
           }
               
      return d;      return 1;
     }      }
 }  }
   
/* Bubble sort the RRset into the canonical order. /* Bubble sort the RRset into the canonical order. */
   Note that the byte-streams from two RRs may get unsynced: consider  
   RRs which have two domain-names at the start and then other data. 
   The domain-names may have different lengths in each RR, but sort equal 
   
   ------------static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
   |abcde|fghi|                      unsigned char **rrset, char *buff1, char *buff2)
   ------------ 
   |abcd|efghi| 
   ------------ 
 
   leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. 
*/ 
 
static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,  
                       unsigned char **rrset, char *buff1, char *buff2) 
 {  {
  int swap, quit, i;  int swap, i, j;
       
   do    do
     {      {
       for (swap = 0, i = 0; i < rrsetidx-1; i++)        for (swap = 0, i = 0; i < rrsetidx-1; i++)
         {          {
          int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;          int rdlen1, rdlen2;
          u16 *dp1, *dp2;          struct rdata_state state1, state2;
          unsigned char *end1, *end2;          
           /* Note that these have been determined to be OK previously,            /* Note that these have been determined to be OK previously,
              so we don't need to check for NULL return here. */               so we don't need to check for NULL return here. */
          unsigned char *p1 = skip_name(rrset[i], header, plen, 10);          state1.ip = skip_name(rrset[i], header, plen, 10);
          unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);          state2.ip = skip_name(rrset[i+1], header, plen, 10);
           state1.op = state2.op = NULL;
           state1.buff = buff1;
           state2.buff = buff2;
           state1.desc = state2.desc = rr_desc;
                       
          p1 += 8; /* skip class, type, ttl */          state1.ip += 8; /* skip class, type, ttl */
          GETSHORT(rdlen1, p1);          GETSHORT(rdlen1, state1.ip);
          end1 = p1 + rdlen1;          if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
             return rrsetidx; /* short packet */
           state1.end = state1.ip + rdlen1;
                       
          p2 += 8; /* skip class, type, ttl */          state2.ip += 8; /* skip class, type, ttl */
          GETSHORT(rdlen2, p2);          GETSHORT(rdlen2, state2.ip);
          end2 = p2 + rdlen2;           if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
                      return rrsetidx; /* short packet */
          dp1 = dp2 = rr_desc;          state2.end = state2.ip + rdlen2; 
          
          for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)          /* If the RR has no names in it then canonicalisation
              is the identity function and we can compare
              the RRs directly. If not we compare the 
              canonicalised RRs one byte at a time. */
           if (*rr_desc == (u16)-1)       
             {              {
              if (left1 != 0)              int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
                memmove(buff1, buff1 + len1 - left1, left1);              int cmp = memcmp(state1.ip, state2.ip, rdmin);
                               
              if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)              if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin))
                 {                  {
                   quit = 1;  
                   len1 = end1 - p1;  
                   memcpy(buff1 + left1, p1, len1);  
                 }  
               len1 += left1;  
                 
               if (left2 != 0)  
                 memmove(buff2, buff2 + len2 - left2, left2);  
                 
               if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)  
                 {  
                   quit = 1;  
                   len2 = end2 - p2;  
                   memcpy(buff2 + left2, p2, len2);  
                 }  
               len2 += left2;  
                  
               if (len1 > len2)  
                 left1 = len1 - len2, left2 = 0, len = len2;  
               else  
                 left2 = len2 - len1, left1 = 0, len = len1;  
                 
               rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);  
                 
               if (rc > 0 || (rc == 0 && quit && len1 > len2))  
                 {  
                   unsigned char *tmp = rrset[i+1];                    unsigned char *tmp = rrset[i+1];
                   rrset[i+1] = rrset[i];                    rrset[i+1] = rrset[i];
                   rrset[i] = tmp;                    rrset[i] = tmp;
                  swap = quit = 1;                  swap = 1;
                 }                  }
              else if (rc < 0)              else if (cmp == 0 && (rdlen1 == rdlen2))
                quit = 1;                {
                   /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
                   for (j = i+1; j < rrsetidx-1; j++)
                     rrset[j] = rrset[j+1];
                   rrsetidx--;
                   i--;
                 }
             }              }
             else
               /* Comparing canonicalised RRs, byte-at-a-time. */
               while (1)
                 {
                   int ok1, ok2;
                   
                   ok1 = get_rdata(header, plen, &state1);
                   ok2 = get_rdata(header, plen, &state2);
                   
                   if (!ok1 && !ok2)
                     {
                       /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
                       for (j = i+1; j < rrsetidx-1; j++)
                         rrset[j] = rrset[j+1];
                       rrsetidx--;
                       i--;
                       break;
                     }
                   else if (ok1 && (!ok2 || *state1.op > *state2.op)) 
                     {
                       unsigned char *tmp = rrset[i+1];
                       rrset[i+1] = rrset[i];
                       rrset[i] = tmp;
                       swap = 1;
                       break;
                     }
                   else if (ok2 && (!ok1 || *state2.op > *state1.op))
                     break;
                   
                   /* arrive here when bytes are equal, go round the loop again
                      and compare the next ones. */
                 }
         }          }
     } while (swap);      } while (swap);
   
     return rrsetidx;
 }  }
   
 static unsigned char **rrset = NULL, **sigs = NULL;  static unsigned char **rrset = NULL, **sigs = NULL;
   
/* Get pointers to RRset menbers and signature(s) for same./* Get pointers to RRset members and signature(s) for same.
    Check signatures, and return keyname associated in keyname. */     Check signatures, and return keyname associated in keyname. */
 static int explore_rrset(struct dns_header *header, size_t plen, int class, int type,   static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, 
                          char *name, char *keyname, int *sigcnt, int *rrcnt)                           char *name, char *keyname, int *sigcnt, int *rrcnt)
Line 680  static int explore_rrset(struct dns_header *header, si Line 403  static int explore_rrset(struct dns_header *header, si
   int gotkey = 0;    int gotkey = 0;
   
   if (!(p = skip_questions(header, plen)))    if (!(p = skip_questions(header, plen)))
    return STAT_BOGUS;    return 0;
   
    /* look for RRSIGs for this RRset and get pointers to each RR in the set. */     /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
   for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);     for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); 
Line 692  static int explore_rrset(struct dns_header *header, si Line 415  static int explore_rrset(struct dns_header *header, si
       pstart = p;        pstart = p;
               
       if (!(res = extract_name(header, plen, &p, name, 0, 10)))        if (!(res = extract_name(header, plen, &p, name, 0, 10)))
        return STAT_BOGUS; /* bad packet */        return 0; /* bad packet */
               
       GETSHORT(stype, p);        GETSHORT(stype, p);
       GETSHORT(sclass, p);        GETSHORT(sclass, p);
      p += 4; /* TTL */           
       
       pdata = p;        pdata = p;
   
         p += 4; /* TTL */
       GETSHORT(rdlen, p);        GETSHORT(rdlen, p);
               
       if (!CHECK_LEN(header, p, plen, rdlen))        if (!CHECK_LEN(header, p, plen, rdlen))
Line 762  static int explore_rrset(struct dns_header *header, si Line 485  static int explore_rrset(struct dns_header *header, si
                   sigs[sigidx++] = pdata;                    sigs[sigidx++] = pdata;
                 }                   } 
                               
              p = pdata + 2; /* restore for ADD_RDLEN */              p = pdata + 6; /* restore for ADD_RDLEN */
             }              }
         }          }
               
Line 791  static int explore_rrset(struct dns_header *header, si Line 514  static int explore_rrset(struct dns_header *header, si
    Name is unchanged on exit. keyname is used as workspace and trashed.     Name is unchanged on exit. keyname is used as workspace and trashed.
   
    Call explore_rrset first to find and count RRs and sigs.     Call explore_rrset first to find and count RRs and sigs.
   
      ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
 */  */
 static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,   static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, 
                          char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)                          char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
                           int algo_in, int keytag_in, unsigned long *ttl_out)
 {  {
   unsigned char *p;    unsigned char *p;
  int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag;  int rdlen, j, name_labels, algo, labels, key_tag;
   struct crec *crecp = NULL;    struct crec *crecp = NULL;
   u16 *rr_desc = rrfilter_desc(type);    u16 *rr_desc = rrfilter_desc(type);
  u32 sig_expiration, sig_inception  u32 sig_expiration, sig_inception;
;  int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
   
   unsigned long curtime = time(0);
   int time_check = is_check_date(curtime);
   
   if (wildcard_out)    if (wildcard_out)
     *wildcard_out = NULL;      *wildcard_out = NULL;
       
Line 809  static int validate_rrset(time_t now, struct dns_heade Line 539  static int validate_rrset(time_t now, struct dns_heade
   /* Sort RRset records into canonical order.     /* Sort RRset records into canonical order. 
      Note that at this point keyname and daemon->workspacename buffs are       Note that at this point keyname and daemon->workspacename buffs are
      unused, and used as workspace by the sort. */       unused, and used as workspace by the sort. */
  sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);  rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
                     
   /* Now try all the sigs to try and find one which validates */    /* Now try all the sigs to try and find one which validates */
   for (j = 0; j <sigidx; j++)    for (j = 0; j <sigidx; j++)
Line 819  static int validate_rrset(time_t now, struct dns_heade Line 549  static int validate_rrset(time_t now, struct dns_heade
       const struct nettle_hash *hash;        const struct nettle_hash *hash;
       void *ctx;        void *ctx;
       char *name_start;        char *name_start;
      u32 nsigttl;      u32 nsigttl, ttl, orig_ttl;
 
       failflags &= ~DNSSEC_FAIL_NOSIG;
               
       p = sigs[j];        p = sigs[j];
         GETLONG(ttl, p);
       GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */        GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
       psav = p;        psav = p;
               
Line 836  static int validate_rrset(time_t now, struct dns_heade Line 569  static int validate_rrset(time_t now, struct dns_heade
       if (!extract_name(header, plen, &p, keyname, 1, 0))        if (!extract_name(header, plen, &p, keyname, 1, 0))
         return STAT_BOGUS;          return STAT_BOGUS;
   
      if (!check_date_range(sig_inception, sig_expiration) ||      if (!time_check)
          labels > name_labels ||        failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
          !(hash = hash_find(algo_digest_name(algo))) ||      else
         {
           /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
           if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
             continue;
           else
             failflags &= ~DNSSEC_FAIL_NYV;
           
           if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
             continue;
           else
             failflags &= ~DNSSEC_FAIL_EXP;
         }
 
       if (!(hash = hash_find(algo_digest_name(algo))))
         continue;
       else
         failflags &= ~DNSSEC_FAIL_NOKEYSUP;
       
       if (labels > name_labels ||
           !hash_init(hash, &ctx, &digest))            !hash_init(hash, &ctx, &digest))
         continue;          continue;
               
       /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */        /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
       if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))        if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
         return STAT_NEED_KEY;          return STAT_NEED_KEY;
      
        if (ttl_out)
          {
            /* 4035 5.3.3 rules on TTLs */
            if (orig_ttl < ttl)
              ttl = orig_ttl;
            
            if (time_check && difftime(sig_expiration, curtime) < ttl)
              ttl = difftime(sig_expiration, curtime);
 
            *ttl_out = ttl;
          }
        
       sig = p;        sig = p;
       sig_len = rdlen - (p - psav);        sig_len = rdlen - (p - psav);
                               
Line 855  static int validate_rrset(time_t now, struct dns_heade Line 619  static int validate_rrset(time_t now, struct dns_heade
       wire_len = to_wire(keyname);        wire_len = to_wire(keyname);
       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);        hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
       from_wire(keyname);        from_wire(keyname);
   
   #define RRBUFLEN 128 /* Most RRs are smaller than this. */
               
       for (i = 0; i < rrsetidx; ++i)        for (i = 0; i < rrsetidx; ++i)
         {          {
          int seg;          int j;
          unsigned char *end, *cp;          struct rdata_state state;
          u16 len, *dp;          u16 len;
           unsigned char rrbuf[RRBUFLEN];
                       
           p = rrset[i];            p = rrset[i];
             
           if (!extract_name(header, plen, &p, name, 1, 10))             if (!extract_name(header, plen, &p, name, 1, 10)) 
             return STAT_BOGUS;              return STAT_BOGUS;
   
Line 871  static int validate_rrset(time_t now, struct dns_heade Line 639  static int validate_rrset(time_t now, struct dns_heade
           /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */            /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */
           if (labels < name_labels)            if (labels < name_labels)
             {              {
              int k;              for (j = name_labels - labels; j != 0; j--)
              for (k = name_labels - labels; k != 0; k--) 
                 {                  {
                   while (*name_start != '.' && *name_start != 0)                    while (*name_start != '.' && *name_start != 0)
                     name_start++;                      name_start++;
                  if (k != 1 && *name_start == '.')                  if (j != 1 && *name_start == '.')
                     name_start++;                      name_start++;
                 }                  }
                               
Line 891  static int validate_rrset(time_t now, struct dns_heade Line 658  static int validate_rrset(time_t now, struct dns_heade
           hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);            hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
           hash->update(ctx, 4, p); /* class and type */            hash->update(ctx, 4, p); /* class and type */
           hash->update(ctx, 4, (unsigned char *)&nsigttl);            hash->update(ctx, 4, (unsigned char *)&nsigttl);
          
          p += 8; /* skip class, type, ttl */          p += 8; /* skip type, class, ttl */
           GETSHORT(rdlen, p);            GETSHORT(rdlen, p);
           if (!CHECK_LEN(header, p, plen, rdlen))            if (!CHECK_LEN(header, p, plen, rdlen))
             return STAT_BOGUS;               return STAT_BOGUS; 
          
          end = p + rdlen;          /* Optimisation for RR types which need no cannonicalisation.
                       This includes DNSKEY DS NSEC and NSEC3, which are also long, so
          /* canonicalise rdata and calculate length of same, use name buffer as workspace.             it saves lots of calls to get_rdata, and avoids the pessimal
             Note that name buffer is twice MAXDNAME long in DNSSEC mode. */             segmented insertion, even with a small rrbuf[].
          cp = p;             
          dp = rr_desc;             If canonicalisation is not needed, a simple insertion into the hash works.
          for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);          */
          len += end - cp;          if (*rr_desc == (u16)-1)
          len = htons(len);            {
          hash->update(ctx, 2, (unsigned char *)&len);               len = htons(rdlen);
                        hash->update(ctx, 2, (unsigned char *)&len);
          /* Now canonicalise again and digest. */              hash->update(ctx, rdlen, p);
          cp = p;            }
          dp = rr_desc;          else
          while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))            {
            hash->update(ctx, seg, (unsigned char *)name);              /* canonicalise rdata and calculate length of same, use 
          if (cp != end)                 name buffer as workspace for get_rdata. */
            hash->update(ctx, end - cp, cp);              state.ip = p;
               state.op = NULL;
               state.desc = rr_desc;
               state.buff = name;
               state.end = p + rdlen;
               
               for (j = 0; get_rdata(header, plen, &state); j++)
                 if (j < RRBUFLEN)
                   rrbuf[j] = *state.op;
               
               len = htons((u16)j);
               hash->update(ctx, 2, (unsigned char *)&len); 
               
               /* If the RR is shorter than RRBUFLEN (most of them, in practice)
                  then we can just digest it now. If it exceeds RRBUFLEN we have to
                  go back to the start and do it in chunks. */
               if (j >= RRBUFLEN)
                 {
                   state.ip = p;
                   state.op = NULL;
                   state.desc = rr_desc;
                   
                   for (j = 0; get_rdata(header, plen, &state); j++)
                     {
                       rrbuf[j] = *state.op;
                       
                       if (j == RRBUFLEN - 1)
                         {
                           hash->update(ctx, RRBUFLEN, rrbuf);
                           j = -1;
                         }
                     }
                 }
               
               if (j != 0)
                 hash->update(ctx, j, rrbuf);
             }
         }          }
             
       hash->digest(ctx, hash->digest_size, digest);        hash->digest(ctx, hash->digest_size, digest);
               
       /* namebuff used for workspace above, restore to leave unchanged on exit */        /* namebuff used for workspace above, restore to leave unchanged on exit */
       p = (unsigned char*)(rrset[0]);        p = (unsigned char*)(rrset[0]);
      extract_name(header, plen, &p, name, 1, 0);      if (!extract_name(header, plen, &p, name, 1, 0))
         return STAT_BOGUS;
   
       if (key)        if (key)
         {          {
Line 941  static int validate_rrset(time_t now, struct dns_heade Line 745  static int validate_rrset(time_t now, struct dns_heade
         }          }
     }      }
   
  return STAT_BOGUS;  /* If we reach this point, no verifying key was found */
   return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY;
 }  }
     
   
Line 958  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 763  int dnssec_validate_by_ds(time_t now, struct dns_heade
 {  {
   unsigned char *psave, *p = (unsigned char *)(header+1);    unsigned char *psave, *p = (unsigned char *)(header+1);
   struct crec *crecp, *recp1;    struct crec *crecp, *recp1;
  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;  int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag;
   unsigned long ttl, sig_ttl;
   struct blockdata *key;    struct blockdata *key;
  struct all_addr a;  union all_addr a;
   int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY;
   
   if (ntohs(header->qdcount) != 1 ||    if (ntohs(header->qdcount) != 1 ||
         RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
       !extract_name(header, plen, &p, name, 1, 4))        !extract_name(header, plen, &p, name, 1, 4))
    return STAT_BOGUS;    return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
   
   GETSHORT(qtype, p);    GETSHORT(qtype, p);
   GETSHORT(qclass, p);    GETSHORT(qclass, p);
       
   if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)    if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
    return STAT_BOGUS;    return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
   
   /* See if we have cached a DS record which validates this key */    /* See if we have cached a DS record which validates this key */
   if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))    if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
Line 1004  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 812  int dnssec_validate_by_ds(time_t now, struct dns_heade
               
       GETSHORT(flags, p);        GETSHORT(flags, p);
       if (*p++ != 3)        if (*p++ != 3)
        return STAT_BOGUS;        return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
       algo = *p++;        algo = *p++;
       keytag = dnskey_keytag(algo, flags, p, rdlen - 4);        keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
       key = NULL;        key = NULL;
               
       /* key must have zone key flag set */        /* key must have zone key flag set */
       if (flags & 0x100)        if (flags & 0x100)
        key = blockdata_alloc((char*)p, rdlen - 4);        {
           key = blockdata_alloc((char*)p, rdlen - 4);
           failflags &= ~DNSSEC_FAIL_NOZONE;
         }
               
       p = psave;        p = psave;
               
Line 1032  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 843  int dnssec_validate_by_ds(time_t now, struct dns_heade
           unsigned char *digest, *ds_digest;            unsigned char *digest, *ds_digest;
           const struct nettle_hash *hash;            const struct nettle_hash *hash;
           int sigcnt, rrcnt;            int sigcnt, rrcnt;
          int wire_len;
           
           if (recp1->addr.ds.algo == algo &&             if (recp1->addr.ds.algo == algo && 
               recp1->addr.ds.keytag == keytag &&                recp1->addr.ds.keytag == keytag &&
              recp1->uid == (unsigned int)class &&              recp1->uid == (unsigned int)class)
              (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) && 
              hash_init(hash, &ctx, &digest)) 
             
             {              {
              int wire_len = to_wire(name);              failflags &= ~DNSSEC_FAIL_NOKEY;
                               
                 if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
                   continue;
                 else
                   failflags &= ~DNSSEC_FAIL_NODSSUP;
   
                 if (!hash_init(hash, &ctx, &digest))
                   continue;
                 
                 wire_len = to_wire(name);
                 
               /* Note that digest may be different between DSs, so                 /* Note that digest may be different between DSs, so 
                  we can't move this outside the loop. */                   we can't move this outside the loop. */
               hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);                hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
Line 1052  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 871  int dnssec_validate_by_ds(time_t now, struct dns_heade
                               
               if (!(recp1->flags & F_NEG) &&                if (!(recp1->flags & F_NEG) &&
                   recp1->addr.ds.keylen == (int)hash->digest_size &&                    recp1->addr.ds.keylen == (int)hash->digest_size &&
                  (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&                  (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
                   memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&                    memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
                   explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&                    explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
                  sigcnt != 0 && rrcnt != 0 &&                  rrcnt != 0)
                  validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,  
                                 NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) 
                 {                  {
                  valid = 1;                  if (sigcnt == 0)
                  break;                    continue;
                   else
                     failflags &= ~DNSSEC_FAIL_NOSIG;
                   
                   rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, 
                                       NULL, key, rdlen - 4, algo, keytag, &sig_ttl);
 
                   failflags &= rc;
                   
                   if (STAT_ISEQUAL(rc, STAT_SECURE))
                     {
                       valid = 1;
                       break;
                     }
                 }                  }
             }              }
         }          }
Line 1084  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 914  int dnssec_validate_by_ds(time_t now, struct dns_heade
           GETSHORT(qclass, p);            GETSHORT(qclass, p);
           GETLONG(ttl, p);            GETLONG(ttl, p);
           GETSHORT(rdlen, p);            GETSHORT(rdlen, p);
   
             /* TTL may be limited by sig. */
             if (sig_ttl < ttl)
               ttl = sig_ttl;
                           
           if (!CHECK_LEN(header, p, plen, rdlen))            if (!CHECK_LEN(header, p, plen, rdlen))
             return STAT_BOGUS; /* bad packet */              return STAT_BOGUS; /* bad packet */
Line 1103  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 937  int dnssec_validate_by_ds(time_t now, struct dns_heade
                   algo = *p++;                    algo = *p++;
                   keytag = dnskey_keytag(algo, flags, p, rdlen - 4);                    keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
                                       
                   /* Cache needs to known class for DNSSEC stuff */  
                   a.addr.dnssec.class = class;  
                     
                   if ((key = blockdata_alloc((char*)p, rdlen - 4)))                    if ((key = blockdata_alloc((char*)p, rdlen - 4)))
                     {                      {
                      if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))                      a.key.keylen = rdlen - 4;
                       a.key.keydata = key;
                       a.key.algo = algo;
                       a.key.keytag = keytag;
                       a.key.flags = flags;
                       
                       if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))
                         {                          {
                           blockdata_free(key);                            blockdata_free(key);
                           return STAT_BOGUS;                            return STAT_BOGUS;
                         }                          }
                       else                        else
                         {                          {
                          a.addr.log.keytag = keytag;                          a.log.keytag = keytag;
                          a.addr.log.algo = algo;                          a.log.algo = algo;
                          if (verify_func(algo))                          if (algo_digest_name(algo))
                            log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");                            log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", 0);
                           else                            else
                            log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");                            log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0);
                           
                          recp1->addr.key.keylen = rdlen - 4; 
                          recp1->addr.key.keydata = key; 
                          recp1->addr.key.algo = algo; 
                          recp1->addr.key.keytag = keytag; 
                          recp1->addr.key.flags = flags; 
                         }                          }
                     }                      }
                 }                  }
Line 1143  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 974  int dnssec_validate_by_ds(time_t now, struct dns_heade
       return STAT_OK;        return STAT_OK;
     }      }
   
  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0);
  return STAT_BOGUS;  return STAT_BOGUS | failflags;
 }  }
   
 /* The DNS packet is expected to contain the answer to a DS query  /* The DNS packet is expected to contain the answer to a DS query
   Put all DSs in the answer which are valid into the cache.   Put all DSs in the answer which are valid and have hash and signature algos
    we support into the cache.
    Also handles replies which prove that there's no DS at this location,      Also handles replies which prove that there's no DS at this location, 
    either because the zone is unsigned or this isn't a zone cut. These are     either because the zone is unsigned or this isn't a zone cut. These are
    cached too.     cached too.
      If none of the DS's are for supported algos, treat the answer as if 
      it's a proof of no DS at this location. RFC4035 para 5.2.
    return codes:     return codes:
    STAT_OK          At least one valid DS found and in cache.     STAT_OK          At least one valid DS found and in cache.
    STAT_BOGUS       no DS in reply or not signed, fails validation, bad packet.     STAT_BOGUS       no DS in reply or not signed, fails validation, bad packet.
Line 1162  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 996  int dnssec_validate_by_ds(time_t now, struct dns_heade
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)  int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {  {
   unsigned char *p = (unsigned char *)(header+1);    unsigned char *p = (unsigned char *)(header+1);
  int qtype, qclass, rc, i, neganswer, nons;  int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0, found_supported = 0;
  int aclass, atype, rdlen;  int aclass, atype, rdlen, flags;
   unsigned long ttl;    unsigned long ttl;
  struct all_addr a;  union all_addr a;
   
   if (ntohs(header->qdcount) != 1 ||    if (ntohs(header->qdcount) != 1 ||
       !(p = skip_name(p, header, plen, 4)))        !(p = skip_name(p, header, plen, 4)))
Line 1177  int dnssec_validate_ds(time_t now, struct dns_header * Line 1011  int dnssec_validate_ds(time_t now, struct dns_header *
   if (qtype != T_DS || qclass != class)    if (qtype != T_DS || qclass != class)
     rc = STAT_BOGUS;      rc = STAT_BOGUS;
   else    else
    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl);
  /* Note dnssec_validate_reply() will have cached positive answers */ 
       
  if (rc == STAT_INSECURE)  if (STAT_ISEQUAL(rc, STAT_INSECURE))
    rc = STAT_BOGUS;    {
       my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
       log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
       return STAT_BOGUS | DNSSEC_FAIL_INDET;
     }
   
   p = (unsigned char *)(header+1);    p = (unsigned char *)(header+1);
  extract_name(header, plen, &p, name, 1, 4);  if (!extract_name(header, plen, &p, name, 1, 4))
       return STAT_BOGUS;
 
   p += 4; /* qtype, qclass */    p += 4; /* qtype, qclass */
       
   /* If the key needed to validate the DS is on the same domain as the DS, we'll    /* If the key needed to validate the DS is on the same domain as the DS, we'll
      loop getting nowhere. Stop that now. This can happen of the DS answer comes       loop getting nowhere. Stop that now. This can happen of the DS answer comes
      from the DS's zone, and not the parent zone. */       from the DS's zone, and not the parent zone. */
  if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname)))  if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
     {      {
      log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");      log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
       return STAT_BOGUS;        return STAT_BOGUS;
     }      }
       
  if (rc != STAT_SECURE)  if (!STAT_ISEQUAL(rc, STAT_SECURE))
     return rc;      return rc;
         
   if (!neganswer)    if (!neganswer)
Line 1221  int dnssec_validate_ds(time_t now, struct dns_header * Line 1060  int dnssec_validate_ds(time_t now, struct dns_header *
               int algo, digest, keytag;                int algo, digest, keytag;
               unsigned char *psave = p;                unsigned char *psave = p;
               struct blockdata *key;                struct blockdata *key;
              struct crec *crecp;           
 
               if (rdlen < 4)                if (rdlen < 4)
                 return STAT_BOGUS; /* bad packet */                  return STAT_BOGUS; /* bad packet */
                               
Line 1230  int dnssec_validate_ds(time_t now, struct dns_header * Line 1068  int dnssec_validate_ds(time_t now, struct dns_header *
               algo = *p++;                algo = *p++;
               digest = *p++;                digest = *p++;
                               
              /* Cache needs to known class for DNSSEC stuff */              if (!ds_digest_name(digest) || !algo_digest_name(algo))
              a.addr.dnssec.class = class; 
               
              if ((key = blockdata_alloc((char*)p, rdlen - 4))) 
                 {                  {
                  if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))                  a.log.keytag = keytag;
                   a.log.algo = algo;
                   a.log.digest = digest;
                   log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)", 0);
                   neg_ttl = ttl;
                 } 
               else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
                 {
                   a.ds.digest = digest;
                   a.ds.keydata = key;
                   a.ds.algo = algo;
                   a.ds.keytag = keytag;
                   a.ds.keylen = rdlen - 4;
                   
                   if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))
                     {                      {
                       blockdata_free(key);                        blockdata_free(key);
                       return STAT_BOGUS;                        return STAT_BOGUS;
                     }                      }
                   else                    else
                     {                      {
                      a.addr.log.keytag = keytag;                      a.log.keytag = keytag;
                      a.addr.log.algo = algo;                      a.log.algo = algo;
                      a.addr.log.digest = digest;                      a.log.digest = digest;
                      if (hash_find(ds_digest_name(digest)) && verify_func(algo))                      log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu", 0);
                        log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu");                      found_supported = 1;
                      else 
                        log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)"); 
                       
                      crecp->addr.ds.digest = digest; 
                      crecp->addr.ds.keydata = key; 
                      crecp->addr.ds.algo = algo; 
                      crecp->addr.ds.keytag = keytag; 
                      crecp->addr.ds.keylen = rdlen - 4;  
                     }                       } 
                 }                  }
                               
               p = psave;                p = psave;
             }              }
   
           if (!ADD_RDLEN(header, p, plen, rdlen))            if (!ADD_RDLEN(header, p, plen, rdlen))
             return STAT_BOGUS; /* bad packet */              return STAT_BOGUS; /* bad packet */
         }          }
   
       cache_end_insert();        cache_end_insert();
   
         /* Fall through if no supported algo DS found. */
         if (found_supported)
           return STAT_OK;
     }      }
  else  
   flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
   
   if (neganswer)
     {      {
       int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;  
       unsigned long minttl = ULONG_MAX;  
         
       if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))  
         return STAT_BOGUS;  
         
       if (RCODE(header) == NXDOMAIN)        if (RCODE(header) == NXDOMAIN)
         flags |= F_NXDOMAIN;          flags |= F_NXDOMAIN;
               
Line 1282  int dnssec_validate_ds(time_t now, struct dns_header * Line 1124  int dnssec_validate_ds(time_t now, struct dns_header *
          to store presence/absence of NS. */           to store presence/absence of NS. */
       if (nons)        if (nons)
         flags &= ~F_DNSSECOK;          flags &= ~F_DNSSECOK;
         
       for (i = ntohs(header->nscount); i != 0; i--)  
         {  
           if (!(p = skip_name(p, header, plen, 0)))  
             return STAT_BOGUS;  
             
           GETSHORT(atype, p);   
           GETSHORT(aclass, p);  
           GETLONG(ttl, p);  
           GETSHORT(rdlen, p);  
             
           if (!CHECK_LEN(header, p, plen, rdlen))  
             return STAT_BOGUS; /* bad packet */  
             
           if (aclass != class || atype != T_SOA)  
             {  
               p += rdlen;  
               continue;  
             }  
             
           if (ttl < minttl)  
             minttl = ttl;  
             
           /* MNAME */  
           if (!(p = skip_name(p, header, plen, 0)))  
             return STAT_BOGUS;  
           /* RNAME */  
           if (!(p = skip_name(p, header, plen, 20)))  
             return STAT_BOGUS;  
           p += 16; /* SERIAL REFRESH RETRY EXPIRE */  
             
           GETLONG(ttl, p); /* minTTL */  
           if (ttl < minttl)  
             minttl = ttl;  
             
           break;  
         }  
         
       if (i != 0)  
         {  
           cache_start_insert();  
             
           a.addr.dnssec.class = class;  
           if (!cache_insert(name, &a, now, ttl, flags))  
             return STAT_BOGUS;  
             
           cache_end_insert();    
             
           log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");  
         }  
     }      }
     
     cache_start_insert();
     
     /* Use TTL from NSEC for negative cache entries */
     if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
       return STAT_BOGUS;
     
     cache_end_insert();  
     
     if (neganswer)
       log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS", 0);
               
   return STAT_OK;    return STAT_OK;
 }  }
Line 1402  static int hostname_cmp(const char *a, const char *b) Line 1205  static int hostname_cmp(const char *a, const char *b)
     }      }
 }  }
   
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
                                    char *workspace1, char *workspace2, char *name, int type, int *nons)                                    char *workspace1_in, char *workspace2, char *name, int type, int *nons)
 {  {
   int i, rc, rdlen;    int i, rc, rdlen;
   unsigned char *p, *psave;    unsigned char *p, *psave;
Line 1416  static int prove_non_existence_nsec(struct dns_header  Line 1219  static int prove_non_existence_nsec(struct dns_header 
   /* Find NSEC record that proves name doesn't exist */    /* Find NSEC record that proves name doesn't exist */
   for (i = 0; i < nsec_count; i++)    for (i = 0; i < nsec_count; i++)
     {      {
         char *workspace1 = workspace1_in;
         int sig_labels, name_labels;
   
       p = nsecs[i];        p = nsecs[i];
       if (!extract_name(header, plen, &p, workspace1, 1, 10))        if (!extract_name(header, plen, &p, workspace1, 1, 10))
         return 0;          return 0;
Line 1424  static int prove_non_existence_nsec(struct dns_header  Line 1230  static int prove_non_existence_nsec(struct dns_header 
       psave = p;        psave = p;
       if (!extract_name(header, plen, &p, workspace2, 1, 10))        if (!extract_name(header, plen, &p, workspace2, 1, 10))
         return 0;          return 0;
      
       /* If NSEC comes from wildcard expansion, use original wildcard
          as name for computation. */
       sig_labels = *labels[i];
       name_labels = count_labels(workspace1);
 
       if (sig_labels < name_labels)
         {
           int k;
           for (k = name_labels - sig_labels; k != 0; k--)
             {
               while (*workspace1 != '.' && *workspace1 != 0)
                 workspace1++;
               if (k != 1 && *workspace1 == '.')
                 workspace1++;
             }
           
           workspace1--;
           *workspace1 = '*';
         }
           
       rc = hostname_cmp(workspace1, name);        rc = hostname_cmp(workspace1, name);
               
       if (rc == 0)        if (rc == 0)
Line 1450  static int prove_non_existence_nsec(struct dns_header  Line 1276  static int prove_non_existence_nsec(struct dns_header 
                 return 0;                  return 0;
                               
               /* If the SOA bit is set for a DS record, then we have the                /* If the SOA bit is set for a DS record, then we have the
                 DS from the wrong side of the delegation. */                 DS from the wrong side of the delegation. For the root DS, 
              if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)                 this is expected. */
               if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
                 return 0;                  return 0;
             }              }
   
Line 1466  static int prove_non_existence_nsec(struct dns_header  Line 1293  static int prove_non_existence_nsec(struct dns_header 
                   if (offset < p[1] && (p[offset+2] & mask) != 0)                    if (offset < p[1] && (p[offset+2] & mask) != 0)
                     return 0;                      return 0;
                                       
                  break; /* finshed checking */                  break; /* finished checking */
                 }                  }
                               
               rdlen -= p[1];                rdlen -= p[1];
Line 1556  static int base32_decode(char *in, unsigned char *out) Line 1383  static int base32_decode(char *in, unsigned char *out)
 }  }
   
 static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,  static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
                                char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons)                                char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels)
 {  {
   int i, hash_len, salt_len, base32_len, rdlen, flags;    int i, hash_len, salt_len, base32_len, rdlen, flags;
   unsigned char *p, *psave;    unsigned char *p, *psave;
Line 1611  static int check_nsec3_coverage(struct dns_header *hea Line 1438  static int check_nsec3_coverage(struct dns_header *hea
                       return 0;                        return 0;
                                           
                     /* If the SOA bit is set for a DS record, then we have the                      /* If the SOA bit is set for a DS record, then we have the
                       DS from the wrong side of the delegation. */                       DS from the wrong side of the delegation. For the root DS, 
                    if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)                       this is expected.  */
                     if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
                       return 0;                        return 0;
                   }                    }
   
Line 1624  static int check_nsec3_coverage(struct dns_header *hea Line 1452  static int check_nsec3_coverage(struct dns_header *hea
                         if (offset < p[1] && (p[offset+2] & mask) != 0)                          if (offset < p[1] && (p[offset+2] & mask) != 0)
                           return 0;                            return 0;
                                                   
                        break; /* finshed checking */                        break; /* finished checking */
                       }                        }
                                           
                     rdlen -= p[1];                      rdlen -= p[1];
Line 1685  static int prove_non_existence_nsec3(struct dns_header Line 1513  static int prove_non_existence_nsec3(struct dns_header
       if (!(p = skip_name(nsecs[i], header, plen, 15)))        if (!(p = skip_name(nsecs[i], header, plen, 15)))
         return 0; /* bad packet */          return 0; /* bad packet */
               
      p += 10; /* type, class, TTL, rdlen */     p += 10; /* type, class, TTL, rdlen */
       algo = *p++;        algo = *p++;
               
       if ((hash = hash_find(nsec3_digest_name(algo))))        if ((hash = hash_find(nsec3_digest_name(algo))))
Line 1753  static int prove_non_existence_nsec3(struct dns_header Line 1581  static int prove_non_existence_nsec3(struct dns_header
   if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)    if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
     return 0;      return 0;
       
  if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons))  if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
     return 1;      return 1;
   
   /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"     /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" 
Line 1798  static int prove_non_existence_nsec3(struct dns_header Line 1626  static int prove_non_existence_nsec3(struct dns_header
   if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)    if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
     return 0;      return 0;
   
  if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))  if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
     return 0;      return 0;
       
   /* Finally, check that there's no seat of wildcard synthesis */    /* Finally, check that there's no seat of wildcard synthesis */
Line 1813  static int prove_non_existence_nsec3(struct dns_header Line 1641  static int prove_non_existence_nsec3(struct dns_header
       if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)        if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
         return 0;          return 0;
               
      if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))      if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
         return 0;          return 0;
     }      }
       
   return 1;    return 1;
 }  }
   
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl)
 {  {
  static unsigned char **nsecset = NULL;  static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
  static int nsecset_sz = 0;  static int nsecset_sz = 0, rrsig_labels_sz = 0;
       
   int type_found = 0;    int type_found = 0;
  unsigned char *p = skip_questions(header, plen);  unsigned char *auth_start, *p = skip_questions(header, plen);
   int type, class, rdlen, i, nsecs_found;    int type, class, rdlen, i, nsecs_found;
     unsigned long ttl;
       
   /* Move to NS section */    /* Move to NS section */
   if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))    if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
     return 0;      return 0;
   
     auth_start = p;
       
  for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)  for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++)
     {      {
       unsigned char *pstart = p;        unsigned char *pstart = p;
               
      if (!(p = skip_name(p, header, plen, 10)))      if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
         return 0;          return 0;
                
       GETSHORT(type, p);         GETSHORT(type, p); 
       GETSHORT(class, p);        GETSHORT(class, p);
      p += 4; /* TTL */      GETLONG(ttl, p);
       GETSHORT(rdlen, p);        GETSHORT(rdlen, p);
   
       if (class == qclass && (type == T_NSEC || type == T_NSEC3))        if (class == qclass && (type == T_NSEC || type == T_NSEC3))
         {          {
             if (nsec_ttl)
               {
                 /* Limit TTL with sig TTL */
                 if (daemon->rr_status[ntohs(header->ancount) + i] < ttl)
                   ttl = daemon->rr_status[ntohs(header->ancount) + i];
                 *nsec_ttl = ttl;
               }
             
           /* No mixed NSECing 'round here, thankyouverymuch */            /* No mixed NSECing 'round here, thankyouverymuch */
           if (type_found != 0 && type_found != type)            if (type_found != 0 && type_found != type)
             return 0;              return 0;
Line 1856  static int prove_non_existence(struct dns_header *head Line 1695  static int prove_non_existence(struct dns_header *head
           if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))            if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
             return 0;               return 0; 
                       
          nsecset[nsecs_found++] = pstart;          if (type == T_NSEC)
             {
               /* If we're looking for NSECs, find the corresponding SIGs, to 
                  extract the labels value, which we need in case the NSECs
                  are the result of wildcard expansion.
                  Note that the NSEC may not have been validated yet
                  so if there are multiple SIGs, make sure the label value
                  is the same in all, to avoid be duped by a rogue one.
                  If there are no SIGs, that's an error */
               unsigned char *p1 = auth_start;
               int res, j, rdlen1, type1, class1;
               
               if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
                 return 0;
               
               rrsig_labels[nsecs_found] = NULL;
               
               for (j = ntohs(header->nscount); j != 0; j--)
                 {
                   if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
                     return 0;
 
                    GETSHORT(type1, p1); 
                    GETSHORT(class1, p1);
                    p1 += 4; /* TTL */
                    GETSHORT(rdlen1, p1);
 
                    if (!CHECK_LEN(header, p1, plen, rdlen1))
                      return 0;
                    
                    if (res == 1 && class1 == qclass && type1 == T_RRSIG)
                      {
                        int type_covered;
                        unsigned char *psav = p1;
                        
                        if (rdlen1 < 18)
                          return 0; /* bad packet */
 
                        GETSHORT(type_covered, p1);
 
                        if (type_covered == T_NSEC)
                          {
                            p1++; /* algo */
                            
                            /* labels field must be the same in every SIG we find. */
                            if (!rrsig_labels[nsecs_found])
                              rrsig_labels[nsecs_found] = p1;
                            else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
                              return 0;
                            }
                        p1 = psav;
                      }
                    
                    if (!ADD_RDLEN(header, p1, plen, rdlen1))
                      return 0;
                 }
 
               /* Must have found at least one sig. */
               if (!rrsig_labels[nsecs_found])
                 return 0;
             }
 
           nsecset[nsecs_found++] = pstart;   
         }          }
               
       if (!ADD_RDLEN(header, p, plen, rdlen))        if (!ADD_RDLEN(header, p, plen, rdlen))
Line 1864  static int prove_non_existence(struct dns_header *head Line 1765  static int prove_non_existence(struct dns_header *head
     }      }
       
   if (type_found == T_NSEC)    if (type_found == T_NSEC)
    return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);    return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
   else if (type_found == T_NSEC3)    else if (type_found == T_NSEC3)
     return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);      return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
   else    else
Line 1909  static int zone_status(char *name, int class, char *ke Line 1810  static int zone_status(char *name, int class, char *ke
       if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))        if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
         return STAT_NEED_DS;          return STAT_NEED_DS;
               
       /* F_DNSSECOK misused in DS cache records to non-existance of NS record.       /* F_DNSSECOK misused in DS cache records to non-existence of NS record.
           F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,            F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
           but that's because there's no NS record either, ie this isn't the start            but that's because there's no NS record either, ie this isn't the start
           of a zone. We only prove that the DNS tree below a node is unsigned when            of a zone. We only prove that the DNS tree below a node is unsigned when
Line 1932  static int zone_status(char *name, int class, char *ke Line 1833  static int zone_status(char *name, int class, char *ke
           do             do 
             {              {
               if (crecp->uid == (unsigned int)class &&                if (crecp->uid == (unsigned int)class &&
                  hash_find(ds_digest_name(crecp->addr.ds.digest)) &&                  ds_digest_name(crecp->addr.ds.digest) &&
                  verify_func(crecp->addr.ds.algo))                  algo_digest_name(crecp->addr.ds.algo))
                 break;                  break;
             }              }
           while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));            while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
Line 1962  static int zone_status(char *name, int class, char *ke Line 1863  static int zone_status(char *name, int class, char *ke
    STAT_INSECURE at least one RRset not validated, because in unsigned zone.     STAT_INSECURE at least one RRset not validated, because in unsigned zone.
    STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.     STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)     STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
   STAT_NEED_DS  need DS to complete validation (name is returned in keyname)    STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
 
    daemon->rr_status points to a char array which corressponds to the RRs in the 
    answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't.
 
    When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
    Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
    is the nons argument is non-NULL.
 */  */
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,   int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, 
                          int *class, int check_unsigned, int *neganswer, int *nons)                          int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl)
 {  {
   static unsigned char **targets = NULL;    static unsigned char **targets = NULL;
   static int target_sz = 0;    static int target_sz = 0;
   
   unsigned char *ans_start, *p1, *p2;    unsigned char *ans_start, *p1, *p2;
  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;  int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
  int i, j, rc;  int i, j, rc = STAT_INSECURE;
   int secure = STAT_SECURE;
    
   /* extend rr_status if necessary */
   if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
     {
       unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64));
   
         if (!new)
           return STAT_BOGUS;
   
         free(daemon->rr_status);
         daemon->rr_status = new;
         daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64;
       }
     
     memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz);
     
   if (neganswer)    if (neganswer)
     *neganswer = 0;      *neganswer = 0;
       
Line 1987  int dnssec_validate_reply(time_t now, struct dns_heade Line 1911  int dnssec_validate_reply(time_t now, struct dns_heade
       
    /* Find all the targets we're looking for answers to.     /* Find all the targets we're looking for answers to.
      The zeroth array element is for the query, subsequent ones       The zeroth array element is for the query, subsequent ones
     for CNAME targets, unless the query is for a CNAME. */     for CNAME targets, unless the query is for a CNAME or ANY. */
   
   if (!expand_workspace(&targets, &target_sz, 0))    if (!expand_workspace(&targets, &target_sz, 0))
     return STAT_BOGUS;      return STAT_BOGUS;
Line 2006  int dnssec_validate_reply(time_t now, struct dns_heade Line 1930  int dnssec_validate_reply(time_t now, struct dns_heade
   if (qtype == T_RRSIG)    if (qtype == T_RRSIG)
     return STAT_INSECURE;      return STAT_INSECURE;
       
  if (qtype != T_CNAME)  if (qtype != T_CNAME && qtype != T_ANY)
     for (j = ntohs(header->ancount); j != 0; j--)       for (j = ntohs(header->ancount); j != 0; j--) 
       {        {
         if (!(p1 = skip_name(p1, header, plen, 10)))          if (!(p1 = skip_name(p1, header, plen, 10)))
Line 2030  int dnssec_validate_reply(time_t now, struct dns_heade Line 1954  int dnssec_validate_reply(time_t now, struct dns_heade
       
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)    for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {      {
         if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
           return STAT_BOGUS;
         
       if (!extract_name(header, plen, &p1, name, 1, 10))        if (!extract_name(header, plen, &p1, name, 1, 10))
         return STAT_BOGUS; /* bad packet */          return STAT_BOGUS; /* bad packet */
               
Line 2039  int dnssec_validate_reply(time_t now, struct dns_heade Line 1966  int dnssec_validate_reply(time_t now, struct dns_heade
       GETSHORT(rdlen1, p1);        GETSHORT(rdlen1, p1);
               
       /* Don't try and validate RRSIGs! */        /* Don't try and validate RRSIGs! */
      if (type1 != T_RRSIG)      if (type1 == T_RRSIG)
         continue;
       
       /* Check if we've done this RRset already */
       for (p2 = ans_start, j = 0; j < i; j++)
         {          {
          /* Check if we've done this RRset already */          if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
          for (p2 = ans_start, j = 0; j < i; j++)            return STAT_BOGUS; /* bad packet */
            { 
              if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) 
                return STAT_BOGUS; /* bad packet */ 
               
              GETSHORT(type2, p2); 
              GETSHORT(class2, p2); 
              p2 += 4; /* TTL */ 
              GETSHORT(rdlen2, p2); 
               
              if (type2 == type1 && class2 == class1 && rc == 1) 
                break; /* Done it before: name, type, class all match. */ 
               
              if (!ADD_RDLEN(header, p2, plen, rdlen2)) 
                return STAT_BOGUS; 
            } 
                       
             GETSHORT(type2, p2);
             GETSHORT(class2, p2);
             p2 += 4; /* TTL */
             GETSHORT(rdlen2, p2);
             
             if (type2 == type1 && class2 == class1 && rc == 1)
               break; /* Done it before: name, type, class all match. */
             
             if (!ADD_RDLEN(header, p2, plen, rdlen2))
               return STAT_BOGUS;
           }
         
         /* Done already: copy the validation status */
         if (j != i)
           daemon->rr_status[i] = daemon->rr_status[j];
         else
           {
           /* Not done, validate now */            /* Not done, validate now */
          if (j == i)          int sigcnt, rrcnt;
           char *wildname;
           
           if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
             return STAT_BOGUS;
           
           /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */
           if (sigcnt == 0)
             {              {
              int sigcnt, rrcnt;              /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
              char *wildname;              if (type1 == T_NSEC || type1 == T_NSEC3)
                              return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
              if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))              else if (nons && i >= ntohs(header->ancount))
                return STAT_BOGUS;                /* If we're validating a DS reply, rather than looking for the value of AD bit,
                   we only care that NSEC and NSEC3 RRs in the auth section are signed. 
              /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */                   Return SECURE even if others (SOA....) are not. */
              if (sigcnt == 0)                rc = STAT_SECURE;
               else
                 {                  {
                  if (check_unsigned)                  /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
                   if (check_unsigned && i < ntohs(header->ancount))
                     {                      {
                       rc = zone_status(name, class1, keyname, now);                        rc = zone_status(name, class1, keyname, now);
                      if (rc == STAT_SECURE)                      if (STAT_ISEQUAL(rc, STAT_SECURE))
                        rc = STAT_BOGUS;                        rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
                       if (class)                      
                         *class = class1; /* Class for NEED_DS or NEED_KEY */                      if (class)
                         *class = class1; /* Class for NEED_DS or NEED_KEY */
                     }                      }
                   else                     else 
                     rc = STAT_INSECURE;                       rc = STAT_INSECURE; 
                                       
                  return rc;                  if (!STAT_ISEQUAL(rc, STAT_INSECURE))
                     return rc;
                 }                  }
                          }
           else
             {
               /* explore_rrset() gives us key name from sigs in keyname.                /* explore_rrset() gives us key name from sigs in keyname.
                  Can't overwrite name here. */                   Can't overwrite name here. */
               strcpy(daemon->workspacename, keyname);                strcpy(daemon->workspacename, keyname);
               rc = zone_status(daemon->workspacename, class1, keyname, now);                rc = zone_status(daemon->workspacename, class1, keyname, now);
              
              if (rc != STAT_SECURE)              if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
                 {                  {
                   /* Zone is insecure, don't need to validate RRset */  
                   if (class)                    if (class)
                     *class = class1; /* Class for NEED_DS or NEED_KEY */                      *class = class1; /* Class for NEED_DS or NEED_KEY */
                   return rc;                    return rc;
                }                 }
                               
              rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);              /* Zone is insecure, don't need to validate RRset */
                            if (STAT_ISEQUAL(rc, STAT_SECURE))
              if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) 
                 {                  {
                  if (class)                  unsigned long sig_ttl;
                    *class = class1; /* Class for DS or DNSKEY */                  rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
                  return rc;                                      rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
                }                   
              else                   if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
                {                    {
                       if (class)
                         *class = class1; /* Class for DS or DNSKEY */
                       return rc;
                     } 
                   
                   /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */                    /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
                                   
                   /* Note that RR is validated */
                   daemon->rr_status[i] = sig_ttl;
                    
                   /* Note if we've validated either the answer to the question                    /* Note if we've validated either the answer to the question
                      or the target of a CNAME. Any not noted will need NSEC or                       or the target of a CNAME. Any not noted will need NSEC or
                      to be in unsigned space. */                       to be in unsigned space. */
   
                   for (j = 0; j <targetidx; j++)                    for (j = 0; j <targetidx; j++)
                     if ((p2 = targets[j]))                      if ((p2 = targets[j]))
                       {                        {
                        if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))                        int rc1;
                         if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
                           return STAT_BOGUS; /* bad packet */                            return STAT_BOGUS; /* bad packet */
                                                   
                        if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))                        if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
                           targets[j] = NULL;                            targets[j] = NULL;
                       }                        }
                                              
                   /* An attacker replay a wildcard answer with a different                  /* An attacker replay a wildcard answer with a different
                      answer and overlay a genuine RR. To prove this                     answer and overlay a genuine RR. To prove this
                      hasn't happened, the answer must prove that                     hasn't happened, the answer must prove that
                      the gennuine record doesn't exist. Check that here.                      the genuine record doesn't exist. Check that here. 
                      Note that we may not yet have validated the NSEC/NSEC3 RRsets.                      Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
                      That's not a problem since if the RRsets later fail                     That's not a problem since if the RRsets later fail
                      we'll return BOGUS then. */                     we'll return BOGUS then. */
                  if (rc == STAT_SECURE_WILDCARD && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))                  if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
                    return STAT_BOGUS;                      !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
                     return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
 
                   rc = STAT_SECURE;
                 }                  }
             }              }
         }          }
   
      if (!ADD_RDLEN(header, p1, plen, rdlen1))      if (STAT_ISEQUAL(rc, STAT_INSECURE))
        return STAT_BOGUS;        secure = STAT_INSECURE;
     }      }
   
   /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */    /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
Line 2147  int dnssec_validate_reply(time_t now, struct dns_heade Line 2102  int dnssec_validate_reply(time_t now, struct dns_heade
       {        {
         if (neganswer)          if (neganswer)
           *neganswer = 1;            *neganswer = 1;
        
         if (!extract_name(header, plen, &p2, name, 1, 10))          if (!extract_name(header, plen, &p2, name, 1, 10))
           return STAT_BOGUS; /* bad packet */            return STAT_BOGUS; /* bad packet */
                    
         /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */          /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
        
         /* For anything other than a DS record, this situation is OK if either          /* For anything other than a DS record, this situation is OK if either
            the answer is in an unsigned zone, or there's a NSEC records. */             the answer is in an unsigned zone, or there's a NSEC records. */
        if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons))        if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
           {            {
             /* Empty DS without NSECS */              /* Empty DS without NSECS */
             if (qtype == T_DS)              if (qtype == T_DS)
              return STAT_BOGUS;              return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
                           
            if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)            if (!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
               {                {
                 if (class)                  if (class)
                   *class = qclass; /* Class for NEED_DS or NEED_KEY */                    *class = qclass; /* Class for NEED_DS or NEED_KEY */
                 return rc;                  return rc;
               }                 } 
                           
            return STAT_BOGUS; /* signed zone, no NSECs */            return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
           }            }
       }        }
       
  return STAT_SECURE;  return secure;
 }  }
   
   
Line 2199  int dnskey_keytag(int alg, int flags, unsigned char *k Line 2154  int dnskey_keytag(int alg, int flags, unsigned char *k
 }  }
   
 size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class,   size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, 
                             int type, union mysockaddr *addr, int edns_pktsz)                             int type, int edns_pktsz)
 {  {
   unsigned char *p;    unsigned char *p;
   char *types = querystr("dnssec-query", type);  
   size_t ret;    size_t ret;
   
   if (addr->sa.sa_family == AF_INET)   
     log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);  
 #ifdef HAVE_IPV6  
   else  
     log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);  
 #endif  
     
   header->qdcount = htons(1);    header->qdcount = htons(1);
   header->ancount = htons(0);    header->ancount = htons(0);
   header->nscount = htons(0);    header->nscount = htons(0);
Line 2227  size_t dnssec_generate_query(struct dns_header *header Line 2174  size_t dnssec_generate_query(struct dns_header *header
   
   p = (unsigned char *)(header+1);    p = (unsigned char *)(header+1);
                   
  p = do_rfc1035_name(p, name);  p = do_rfc1035_name(p, name, NULL);
   *p++ = 0;    *p++ = 0;
   PUTSHORT(type, p);    PUTSHORT(type, p);
   PUTSHORT(class, p);    PUTSHORT(class, p);
Line 2240  size_t dnssec_generate_query(struct dns_header *header Line 2187  size_t dnssec_generate_query(struct dns_header *header
   return ret;    return ret;
 }  }
   
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)int errflags_to_ede(int status)
 {  {
  int q;  /* We can end up with more than one flag set for some errors,
  unsigned int len;     so this encodes a rough priority so the (eg) No sig is reported
  unsigned char *p = (unsigned char *)(header+1);     before no-unexpired-sig. */
  const struct nettle_hash *hash; 
  void *ctx; 
  unsigned char *digest; 
   
  if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) 
    return NULL; 
   
  for (q = ntohs(header->qdcount); q != 0; q--)  
    { 
      if (!extract_name(header, plen, &p, name, 1, 4)) 
        break; /* bad packet */ 
       
      len = to_wire(name); 
      hash->update(ctx, len, (unsigned char *)name); 
      /* CRC the class and type as well */ 
      hash->update(ctx, 4, p); 
   
      p += 4;  if (status & DNSSEC_FAIL_NYV)
      if (!CHECK_LEN(header, p, plen, 0))    return EDE_SIG_NYV;
        break; /* bad packet */  else if (status & DNSSEC_FAIL_EXP)
    }    return EDE_SIG_EXP;
    else if (status & DNSSEC_FAIL_NOKEYSUP)
  hash->digest(ctx, hash->digest_size, digest);    return EDE_USUPDNSKEY;
  return digest;  else if (status & DNSSEC_FAIL_NOZONE)
     return EDE_NO_ZONEKEY;
   else if (status & DNSSEC_FAIL_NOKEY)
     return EDE_NO_DNSKEY;
   else if (status & DNSSEC_FAIL_NODSSUP)
     return EDE_USUPDS;
   else if (status & DNSSEC_FAIL_NONSEC)
     return EDE_NO_NSEC;
   else if (status & DNSSEC_FAIL_INDET)
     return EDE_DNSSEC_IND;
   else if (status & DNSSEC_FAIL_NOSIG)
     return EDE_NO_RRSIG;
   else
     return EDE_UNSET;
 }  }
   
 #endif /* HAVE_DNSSEC */  #endif /* HAVE_DNSSEC */

Removed from v.1.1.1.2  
changed lines
  Added in v.1.1.1.4


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>