Use DS records as trust anchors, not DNSKEYs.

This allows us to query for the root zone DNSKEY RRset and validate
it, thus automatically handling KSK rollover.
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 687d921..f3c5de1 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -13,7 +13,10 @@
 cache or forwards them to a real, recursive, DNS server. It loads the
 contents of /etc/hosts so that local hostnames
 which do not appear in the global DNS can be resolved and also answers
-DNS queries for DHCP configured hosts. It can also act as the authoritative DNS server for one or more domains, allowing local names to appear in the global DNS.
+DNS queries for DHCP configured hosts. It can also act as the
+authoritative DNS server for one or more domains, allowing local names
+to appear in the global DNS. It can be configured to do DNSSEC
+validation.
 .PP
 The dnsmasq DHCP server supports static address assignments and multiple
 networks. It automatically
@@ -587,12 +590,15 @@
 clients unable to do validation, use of the AD bit set by dnsmasq is useful, provided that the network between 
 the dnsmasq server and the client is trusted. Dnsmasq must be compiled with HAVE_DNSSEC enabled, and DNSSEC
 trust anchors provided, see 
-.B --dnsskey.
+.B --trust-anchor.
 Because the DNSSEC validation process uses the cache, it is not permitted to reduce the cache size below the default when DNSSEC is enabled.
 .TP
-.B --dnskey=[<class>],<domain>,<flags>,<algorithm>,<base64-key>
-Provide DNSKEY records to act a trust anchors for DNSSEC validation. Typically these will be the keys for root zone,
-but trust anchors for limited domains are also possible. 
+.B --trust-anchor=[<class>],<domain>,<key-tag>,<algorithm>,<digest-type>,<digest>
+Provide DS records to act a trust anchors for DNSSEC
+validation. Typically these will be the DS record(s) for Zone Signing
+key(s) of the root zone,
+but trust anchors for limited domains are also possible. The current
+root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml 
 .TP
 .B --proxy-dnssec
 Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it.  This is an 
@@ -601,7 +607,10 @@
 .TP
 .B --dnssec-debug
 Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, 
-and don't convert BOGUS replies to SERVFAIL responses. 
+and don't convert replies which do not validate to responses with
+a return code of SERVFAIL. Note that
+setting this may affect DNS behaviour in bad ways, it is not an
+extra-logging flag and should not be set in production.
 .TP
 .B --auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....]]
 Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain
diff --git a/src/cache.c b/src/cache.c
index 9407636..93865d9 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -985,7 +985,7 @@
   struct cname *a;
   struct interface_name *intr;
 #ifdef HAVE_DNSSEC
-  struct dnskey *key;
+  struct ds_config *ds;
 #endif
 
   cache_inserted = cache_live_freed = 0;
@@ -1031,17 +1031,17 @@
 	}
 
 #ifdef HAVE_DNSSEC
-  for (key = daemon->dnskeys; key; key = key->next)
+  for (ds = daemon->ds; ds; ds = ds->next)
     if ((cache = whine_malloc(sizeof(struct crec))) &&
-	(cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
+	(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
       {
-	cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
-	cache->name.namep = key->name;
-	cache->addr.key.keylen = key->keylen;
-	cache->addr.key.algo = key->algo;
-	cache->addr.key.flags = key->flags;
-	cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
-	cache->uid = key->class;
+	cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
+	cache->name.namep = ds->name;
+	cache->addr.ds.keylen = ds->digestlen;
+	cache->addr.ds.algo = ds->algo;
+	cache->addr.ds.keytag = ds->keytag;
+	cache->addr.ds.digest = ds->digest_type;
+	cache->uid = ds->class;
 	cache_hash(cache);
       }
 #endif
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 6c19c05..3c8a847 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -144,7 +144,7 @@
   if (option_bool(OPT_DNSSEC_VALID))
     {
 #ifdef HAVE_DNSSEC
-      if (!daemon->dnskeys)
+      if (!daemon->ds)
 	die(_("No trust anchors provided for DNSSEC"), NULL, EC_BADCONF);
       
       if (daemon->cachesize < CACHESIZ)
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 1f42818..29ae295 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -295,10 +295,10 @@
   struct cname *next;
 }; 
 
-struct dnskey {
-  char *name, *key;
-  int keylen, class, algo, flags;
-  struct dnskey *next;
+struct ds_config {
+  char *name, *digest;
+  int digestlen, class, algo, keytag, digest_type;
+  struct ds_config *next;
 };
 
 #define ADDRLIST_LITERAL 1
@@ -930,7 +930,7 @@
   struct prefix_class *prefix_classes;
 #endif
 #ifdef HAVE_DNSSEC
-  struct dnskey *dnskeys;
+  struct ds_config *ds;
 #endif
 
   /* globally used stuff for DNS */
@@ -1107,9 +1107,6 @@
 int prettyprint_addr(union mysockaddr *addr, char *buf);
 int parse_hex(char *in, unsigned char *out, int maxlen, 
 	      unsigned int *wildcard_mask, int *mac_type);
-#ifdef HAVE_DNSSEC
-int parse_base64(char *in, char *out);
-#endif
 int memcmp_masked(unsigned char *a, unsigned char *b, int len, 
 		  unsigned int mask);
 int expand_buf(struct iovec *iov, size_t size);
diff --git a/src/option.c b/src/option.c
index 9a23043..f7f98d5 100644
--- a/src/option.c
+++ b/src/option.c
@@ -139,7 +139,7 @@
 #define LOPT_QUIET_DHCP6  327
 #define LOPT_QUIET_RA     328
 #define LOPT_SEC_VALID    329
-#define LOPT_DNSKEY       330
+#define LOPT_TRUST_ANCHOR 330
 #define LOPT_DNSSEC_DEBUG 331
 
 #ifdef HAVE_GETOPT_LONG
@@ -277,7 +277,7 @@
     { "ipset", 1, 0, LOPT_IPSET },
     { "synth-domain", 1, 0, LOPT_SYNTH },
     { "dnssec", 0, 0, LOPT_SEC_VALID },
-    { "dnskey", 1, 0, LOPT_DNSKEY },
+    { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
     { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
 #ifdef OPTION6_PREFIX_CLASS 
     { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
@@ -430,7 +430,7 @@
   { LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
   { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
   { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
-  { LOPT_DNSKEY, ARG_DUP, "<domain>,<algo>,<key>", gettext_noop("Specify trust anchor DNSKEY"), NULL },
+  { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
   { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
 #ifdef OPTION6_PREFIX_CLASS 
   { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
@@ -590,6 +590,16 @@
 
   return 1;
 }
+
+static int atoi_check8(char *a, int *res)
+{
+  if (!(atoi_check(a, res)) ||
+      *res < 0 ||
+      *res > 0xff)
+    return 0;
+
+  return 1;
+}
 	
 static void add_txt(char *name, char *txt)
 {
@@ -3675,10 +3685,11 @@
       }
 
 #ifdef HAVE_DNSSEC
-    case LOPT_DNSKEY:
+    case LOPT_TRUST_ANCHOR:
       {
-	struct dnskey *new = opt_malloc(sizeof(struct dnskey));
-      	char *key64, *algo = NULL;
+	struct ds_config *new = opt_malloc(sizeof(struct ds_config));
+      	char *cp, *cp1, *keyhex, *digest, *algo = NULL;
+	int len;
 	
 	new->class = C_IN;
 
@@ -3700,20 +3711,30 @@
 	      }
 	  }
 		  
-       	if (!comma || !algo || !(key64 = split(algo)) ||
-	    !atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
+       	if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
+	    !atoi_check16(comma, &new->keytag) || 
+	    !atoi_check8(algo, &new->algo) ||
+	    !atoi_check8(digest, &new->digest_type) ||
 	    !(new->name = canonicalise_opt(arg)))
-	  ret_err(_("bad DNSKEY"));
+	  ret_err(_("bad trust anchor"));
 	    
 	/* Upper bound on length */
-	new->key = opt_malloc((3*strlen(key64)/4)+1);
-	unhide_metas(key64);
-	if ((new->keylen = parse_base64(key64, new->key)) == -1)
-	  ret_err(_("bad base64 in DNSKEY"));
+	len = (2*strlen(keyhex))+1;
+	new->digest = opt_malloc(len);
+	unhide_metas(keyhex);
+	/* 4034: "Whitespace is allowed within digits" */
+	for (cp = keyhex; *cp; )
+	  if (isspace(*cp))
+	    for (cp1 = cp; *cp1; cp1++)
+	      *cp1 = *(cp1+1);
+	  else
+	    cp++;
+	if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
+	  ret_err(_("bad HEX in trust anchor"));
 	
-	new->next = daemon->dnskeys;
-	daemon->dnskeys = new;
-
+	new->next = daemon->ds;
+	daemon->ds = new;
+	
 	break;
       }
 #endif
diff --git a/src/rfc1035.c b/src/rfc1035.c
index c58b9ff..b8e0f18 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1599,20 +1599,17 @@
 		  while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
 		    if (crecp->uid == qclass)
 		      {
-			if (!(crecp->flags & F_CONFIG)) /* Don't return configured keys - send upstream instead */
-			  {
-			    gotone = 1;
-			    if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
-			      {			     			      
-				struct all_addr a;
-				a.addr.keytag =  crecp->addr.key.keytag;
-				log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u");
-				if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
-							crec_ttl(crecp, now), &nameoffset,
-							T_DNSKEY, qclass, "sbbt", 
-							crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata))
-				  anscount++;
-			      }
+			gotone = 1;
+			if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
+			  {			     			      
+			    struct all_addr a;
+			    a.addr.keytag =  crecp->addr.key.keytag;
+			    log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u");
+			    if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+						    crec_ttl(crecp, now), &nameoffset,
+						    T_DNSKEY, qclass, "sbbt", 
+						    crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata))
+			      anscount++;
 			  }
 		      }
 		}
diff --git a/src/util.c b/src/util.c
index d5839fa..7c46d40 100644
--- a/src/util.c
+++ b/src/util.c
@@ -482,66 +482,6 @@
   return i;
 }
 
-#ifdef HAVE_DNSSEC
-static int charval(char c)
-{
-  if (c >= 'A' && c <= 'Z')
-    return c - 'A';
-  
-  if (c >= 'a' && c <= 'z')
-    return c - 'a' + 26;
-
-  if (c >= '0' && c <= '9')
-    return c - '0' + 52;
-
-  if (c == '+')
-    return 62;
-  
-  if (c == '/')
-    return 63;
-
-  if (c == '=')
-    return -1;
-
-  return -2;
-}
-
-int parse_base64(char *in, char *out)
-{
-  char *p = out;
-  int i, val[4];
-
-  while (*in)
-    {
-      for (i = 0; i < 4; i++)
-	{ 
-	  while (*in == ' ') 
-	    in++;
-	  if (*in == 0)
-	    return -1;
-	  if ((val[i] = charval(*in++)) == -2)
-	    return -1;
-	}
-          
-      while (*in == ' ') 
-	in++;
-      
-      if (val[1] == -1)
-	return -1; /* too much padding */
-      
-      *p++ = (val[0] << 2) | (val[1] >> 4);
-      
-      if (val[2] != -1)
-	*p++ = (val[1] << 4) | ( val[2] >> 2);
-
-      if (val[3] != -1)
-	*p++ = (val[2] << 6) | val[3];
-    }
-
-  return p - out;
-}
-#endif
-
 /* return 0 for no match, or (no matched octets) + 1 */
 int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
 {
diff --git a/trust-anchors.conf b/trust-anchors.conf
index bf3969d..afda518 100644
--- a/trust-anchors.conf
+++ b/trust-anchors.conf
@@ -1,8 +1,9 @@
-# The root DNSSEC trust anchors, valid as at 30/01/2014
+# The root DNSSEC trust anchor, valid as at 30/01/2014
 
+# Note that this is a DS record (ie a hash of the root Zone Signing Key) 
+# If was downloaded from https://data.iana.org/root-anchors/root-anchors.xml
 
-dnskey=.,257,8,AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0=
-dnskey=.,256,8,AwEAAb8sU6pbYMWRbkRnEuEZw9NSir707TkOcF+UL1XiK4NDJOvXRyX1 95Am5dQ7bRnnuySZ3daf37vvjUUhuIWUAQ4stht8nJfYxVQXDYjSpGH5 I6Hf/0CZEoNP6cNvrQ7AFmKkmv00xWExKQjbvnRPI4bqpMwtHVzn6Wyb BZ6kuqED
-dnskey=.,256,8,AwEAAYRU41/8smgAvuSojEP4jaj5Yll7WPaUKpYvnz2pnX2VIvRn4jsy Jns80bloenG6X9ebJVy2CFtZQLKHP8DcKmIFotdgs2HolyocY1am/+33 4RtzusM2ojkhjn1FRGtuSE9s2TSz1ISv0yVnFyu+EP/ZkiWnDfWeVrJI SEWBEr4V
+trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
+