Commit 0fc2f313 authored by Simon Kelley's avatar Simon Kelley

First functional DNSSEC - highly alpha.

parent c3e0b9b6
......@@ -56,6 +56,8 @@ static const struct {
{ 38, "A6" },
{ 39, "DNAME" },
{ 41, "OPT" },
{ 43, "DS" },
{ 46, "RRSIG" },
{ 48, "DNSKEY" },
{ 249, "TKEY" },
{ 250, "TSIG" },
......@@ -916,12 +918,19 @@ void cache_reload(void)
struct name_list *nl;
struct cname *a;
struct interface_name *intr;
#ifdef HAVE_DNSSEC
struct dnskey *key;
#endif
cache_inserted = cache_live_freed = 0;
for (i=0; i<hash_size; i++)
for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
{
#ifdef HAVE_DNSSEC
if (cache->flags & (F_DNSKEY | F_DS))
blockdata_free(cache->addr.key.keydata);
#endif
tmp = cache->hash_next;
if (cache->flags & (F_HOSTS | F_CONFIG))
{
......@@ -948,13 +957,27 @@ void cache_reload(void)
if (hostname_isequal(a->target, intr->name) &&
((cache = whine_malloc(sizeof(struct crec)))))
{
cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG | F_DNSSECOK;
cache->name.namep = a->alias;
cache->addr.cname.target.int_name = intr;
cache->addr.cname.uid = -1;
cache_hash(cache);
add_hosts_cname(cache); /* handle chains */
}
#ifdef HAVE_DNSSEC
for (key = daemon->dnskeys; key; key = key->next)
if ((cache = whine_malloc(sizeof(struct crec))) &&
(cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
{
cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
cache->name.namep = key->name;
cache->uid = key->keylen;
cache->addr.key.algo = key->algo;
cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
cache_hash(cache);
}
#endif
/* borrow the packet buffer for a temporary by-address hash */
memset(daemon->packet, 0, daemon->packet_buff_sz);
......@@ -1197,16 +1220,13 @@ void dump_cache(time_t now)
for (i=0; i<hash_size; i++)
for (cache = hash_table[i]; cache; cache = cache->hash_next)
{
char *a, *p = daemon->namebuff;
p += sprintf(p, "%-40.40s ", cache_get_name(cache));
if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD))
a = "";
else if (cache->flags & F_CNAME)
{
a = "";
if (!is_outdated_cname_pointer(cache))
a = cache_get_cname_target(cache);
}
char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
*a = 0;
if (strlen(n) == 0)
n = "<Root>";
p += sprintf(p, "%-40.40s ", n);
if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
a = cache_get_cname_target(cache);
#ifdef HAVE_DNSSEC
else if (cache->flags & F_DNSKEY)
{
......@@ -1216,11 +1236,11 @@ void dump_cache(time_t now)
else if (cache->flags & F_DS)
{
a = daemon->addrbuff;
sprintf(a, "%5u %3u %3u %u", cache->addr.key.keytag,
cache->addr.key.algo, cache->addr.key.digest, cache->uid);
sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
cache->addr.key.algo, cache->addr.key.digest);
}
#endif
else
else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
{
a = daemon->addrbuff;
if (cache->flags & F_IPV4)
......@@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
if (addr)
{
if (flags & F_KEYTAG)
sprintf(daemon->addrbuff, arg, addr->addr.keytag);
else
{
#ifdef HAVE_IPV6
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
addr, daemon->addrbuff, ADDRSTRLEN);
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
addr, daemon->addrbuff, ADDRSTRLEN);
#else
strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
#endif
}
}
else
dest = arg;
if (flags & F_REVERSE)
{
......@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg;
else if (flags & F_UPSTREAM)
source = "reply";
else if (flags & F_SECSTAT)
source = "validation";
else if (flags & F_AUTH)
source = "auth";
else if (flags & F_SERVER)
......@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg;
verb = "from";
}
else if (flags & F_DNSSEC)
{
source = arg;
verb = "to";
}
else
source = "cached";
......@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks)
keyblock_free = blocks;
}
}
void blockdata_retrieve(struct blockdata *block, size_t len, void *data)
{
size_t blen;
struct blockdata *b;
for (b = block; len > 0 && b; b = b->next)
{
blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
memcpy(data, b->key, blen);
data += blen;
len -= blen;
}
}
#endif
......@@ -139,8 +139,8 @@ RESOLVFILE
/* #define HAVE_DBUS */
/* #define HAVE_IDN */
/* #define HAVE_CONNTRACK */
#define HAVE_DNSSEC
#define HAVE_OPENSSL
#define HAVE_DNSSEC
#define HAVE_OPENSSL
/* Default locations for important system files. */
......@@ -385,7 +385,12 @@ static char *compile_opts =
#ifndef HAVE_AUTH
"no-"
#endif
"auth";
"auth "
#ifndef HAVE_DNSSEC
"no-"
#endif
"DNSSEC";
#endif
......
......@@ -242,6 +242,7 @@ struct all_addr {
#ifdef HAVE_IPV6
struct in6_addr addr6;
#endif
unsigned int keytag;
} addr;
};
......@@ -286,6 +287,12 @@ struct cname {
struct cname *next;
};
struct dnskey {
char *name, *key;
int keylen, algo, flags;
struct dnskey *next;
};
#define ADDRLIST_LITERAL 1
#define ADDRLIST_IPV6 2
......@@ -360,7 +367,7 @@ struct crec {
} key;
} addr;
time_t ttd; /* time to die */
/* used as keylen if F_DS or F_DNSKEY, index to source for F_HOSTS */
/* used as keylen ifF_DNSKEY, index to source for F_HOSTS */
int uid;
unsigned short flags;
union {
......@@ -395,6 +402,9 @@ struct crec {
#define F_QUERY (1u<<19)
#define F_NOERR (1u<<20)
#define F_AUTH (1u<<21)
#define F_DNSSEC (1u<<22)
#define F_KEYTAG (1u<<23)
#define F_SECSTAT (1u<<24)
/* composites */
#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */
......@@ -896,6 +906,9 @@ extern struct daemon {
#ifdef OPTION6_PREFIX_CLASS
struct prefix_class *prefix_classes;
#endif
#ifdef HAVE_DNSSEC
struct dnskey *dnskeys;
#endif
/* globally used stuff for DNS */
char *packet; /* packet buffer */
......@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init);
#ifdef HAVE_DNSSEC
struct blockdata *blockdata_alloc(char *data, size_t len);
size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt);
void blockdata_retrieve(struct blockdata *block, size_t len, void *data);
void blockdata_free(struct blockdata *blocks);
#endif
......@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
unsigned long local_ttl);
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
time_t now, char **ipsets, int is_sign, int checkrebind,
int checking_disabled);
int no_cache, int secure);
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask, time_t now);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
......@@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
/* dnssec.c */
size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type);
size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, 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);
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname);
int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo, int keytag);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
/* util.c */
void rand_init(void);
......@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t);
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);
......
......@@ -17,7 +17,7 @@
#ifndef DNSSEC_CRYPTO_H
#define DNSSEC_CRYPTO_H
struct keydata;
struct blockdata;
/*
* vtable for a signature verification algorithm.
......@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx;
typedef struct
{
int digest_algo;
int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len);
int (*verify)(VerifyAlgCtx *ctx, struct blockdata *key, unsigned key_len);
} VerifyAlg;
struct VerifyAlgCtx
......@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a);
#define DIGESTALG_SHA512 257
int digestalg_supported(int algo);
int digestalg_begin(int algo);
void digestalg_begin(int algo);
void digestalg_add_data(void *data, unsigned len);
void digestalg_add_keydata(struct keydata *key, size_t len);
void digestalg_add_keydata(struct blockdata *key, size_t len);
unsigned char *digestalg_final(void);
int digestalg_len(void);
......
......@@ -14,13 +14,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h"
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/err.h>
#include <string.h>
#define POOL_SIZE 1
static union _Pool
......@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len)
printf("\n");
}
static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len)
static int keydata_to_bn(BIGNUM *ret, struct blockdata **key_data, unsigned char **p, unsigned len)
{
size_t cnt;
BIGNUM temp;
BN_init(ret);
cnt = keydata_walk(key_data, p, len);
cnt = blockdata_walk(key_data, p, len);
BN_bin2bn(*p, cnt, ret);
len -= cnt;
*p += cnt;
while (len > 0)
{
if (!(cnt = keydata_walk(key_data, p, len)))
if (!(cnt = blockdata_walk(key_data, p, len)))
return 0;
BN_lshift(ret, ret, cnt*8);
BN_init(&temp);
......@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char *
return 1;
}
static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, unsigned key_len)
static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct blockdata *key_data, unsigned key_len)
{
unsigned char *p = key_data->key;
size_t exp_len, mod_len;
......@@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data,
keydata_to_bn(mod, &key_data, &p, mod_len);
}
static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata *key_data, unsigned key_len)
static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct blockdata *key_data, unsigned key_len)
{
unsigned char *p = key_data->key;
int T;
......@@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct
keydata_to_bn(Y, &key_data, &p, 64+T*8);
}
static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len, int nid, int dlen)
static int rsa_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len, int nid, int dlen)
{
int validated = 0;
......@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_
return validated;
}
static int rsamd5_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
static int rsamd5_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{
return rsa_verify(ctx, key_data, key_len, NID_md5, 16);
}
static int rsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
static int rsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{
return rsa_verify(ctx, key_data, key_len, NID_sha1, 20);
}
static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
static int rsasha256_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{
return rsa_verify(ctx, key_data, key_len, NID_sha256, 32);
}
static int rsasha512_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
static int rsasha512_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{
return rsa_verify(ctx, key_data, key_len, NID_sha512, 64);
}
static int dsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
static int dsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
{
static unsigned char asn1_signature[] =
{
......@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo)
int i;
VerifyAlgCtx *ret = 0;
if (!verifyalg_supported(algo))
return 0;
if (pool_used == (1<<POOL_SIZE)-1)
ret = whine_malloc(valgctx_size[algo]);
else
......@@ -271,7 +271,7 @@ int digestalg_supported(int algo)
algo == DIGESTALG_SHA512);
}
int digestalg_begin(int algo)
void digestalg_begin(int algo)
{
EVP_MD_CTX_init(&digctx);
if (algo == DIGESTALG_SHA1)
......@@ -282,9 +282,6 @@ int digestalg_begin(int algo)
EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL);
else if (algo == DIGESTALG_MD5)
EVP_DigestInit_ex(&digctx, EVP_md5(), NULL);
else
return 0;
return 1;
}
int digestalg_len()
......@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len)
EVP_DigestUpdate(&digctx, data, len);
}
void digestalg_add_keydata(struct keydata *key, size_t len)
void digestalg_add_keydata(struct blockdata *key, size_t len)
{
size_t cnt; unsigned char *p = NULL;
while (len)
{
cnt = keydata_walk(&key, &p, len);
cnt = blockdata_walk(&key, &p, len);
EVP_DigestUpdate(&digctx, p, cnt);
p += cnt;
len -= cnt;
......@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void)
return digest;
}
#endif /* HAVE_DNSSEC */
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2014 Simon Kelley
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
......@@ -15,11 +16,14 @@
*/
#include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h"
#include <assert.h>
/* Maximum length in octects of a domain name, in wire format */
#define MAXCDNAME 256
#define MAXCDNAME 256
#define MAXRRSET 16
......@@ -28,8 +32,6 @@
#define SERIAL_LT -1
#define SERIAL_GT 1
static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen);
/* Implement RFC1982 wrapped compare for 32-bit numbers */
static int serial_compare_32(unsigned long s1, unsigned long s2)
{
......@@ -45,36 +47,6 @@ static int serial_compare_32(unsigned long s1, unsigned long s2)
return SERIAL_UNDEF;
}
/* Extract a DNS name from wire format, without handling compression. This is
faster than extract_name() and does not require access to the full dns
packet. */
static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
{
unsigned char *start=rr, *end = rr+maxlen;
int count;
while (rr < end && *rr != 0)
{
count = *rr++;
while (count-- > 0 && rr < end)
{
*buf = *rr++;
if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
return 0;
if (*buf >= 'A' && *buf <= 'Z')
*buf += 'a' - 'A';
buf++;
}
*buf++ = '.';
}
/* Remove trailing dot (if any) */
if (rr != start)
*(--buf) = 0;
if (rr == end)
return 0;
/* Trailing \0 in source data must be consumed */
return rr-start+1;
}
/* process_domain_name() - do operations with domain names in canonicalized wire format.
*
......@@ -419,46 +391,55 @@ typedef struct PendingRRSIGValidation
int keytag;
} PendingRRSIGValidation;
/* strchrnul - like strchr, but when character is not found, returns a pointer to the terminating \0.
This is an existing C GNU extension, but it's easier to reimplement it,
rather than tweaking with configure. */
static char *my_strchrnul(char *str, char ch)
/* Convert from presentation format to wire format, in place.
Also map UC -> LC.
Note that using extract_name to get presentation format
then calling to_wire() removes compression and maps case,
thus generating names in canonical form.
Calling to_wire followed by from_wire is almost an identity,
except that the UC remains mapped to LC.
*/
static int to_wire(char *name)
{
while (*str && *str != ch)
str++;
return str;
unsigned char *l, *p, term;
int len;
for (l = (unsigned char*)name; *l != 0; l = p)
{
for (p = l; *p != '.' && *p != 0; p++)
if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
term = *p;
if ((len = p - l) != 0)
memmove(l+1, l, len);
*l = len;
p++;
if (term == 0)
*p = 0;
}
return l + 1 - (unsigned char *)name;
}
/* Convert a domain name to wire format */
static int convert_domain_to_wire(char *name, unsigned char* out)
/* Note: no compression allowed in input. */
static void from_wire(char *name)
{
unsigned char len;
unsigned char *start = out;
char *p;
unsigned char *l;
int len;
do
for (l = (unsigned char *)name; *l != 0; l += len+1)
{
p = my_strchrnul(name, '.');
if ((len = p-name))
{
*out++ = len;
while (len--)
{
char ch = *name++;
/* TODO: this will not be required anymore once we
remove all usages of extract_name() from DNSSEC code */
if (ch >= 'A' && ch <= 'Z')
ch = ch - 'A' + 'a';
*out++ = ch;
}
}
name = p+1;
len = *l;
memmove(l, l+1, len);
l[len] = '.';
}
while (*p);
*out++ = '\0';
return out-start;
*(l-1) = 0;
}
......@@ -498,43 +479,56 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk
return 1;
}
size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type)
size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr)
{
unsigned char *p;
char types[20];
querystr("dnssec", types, type);
if (addr->sa.sa_family == AF_INET)
log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
#ifdef HAVE_IPV6
else
log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);
#endif
header->qdcount = htons(1);
header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
header->hb3 = HB3_RD;
header->hb3 = HB3_RD;
SET_OPCODE(header, QUERY);
header->hb4 = 0;
header->hb4 = HB4_CD;
/* ID filled in later */
p = (unsigned char *)(header+1);
p = do_rfc1035_name(p, name);
*p++ = 0;
PUTSHORT(type, p);
PUTSHORT(class, p);
return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ);
}
/* The DNS packet is expected to contain the answer to a DNSKEY query
/* The DNS packet is expected to contain the answer to a DNSKEY query.
Leave name of qury in name.
Put all DNSKEYs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply.
STAT_SECURE At least one valid DNSKEY found and in cache.
STAT_BOGUS At least one DNSKEY found, which fails validation.
STAT_NEED_DS DS records to validate a key not found, name in namebuff
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid.
STAT_NEED_DS DS records to validate a key not found, name in keyname
*/
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *p;
unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1;
int j, qtype, qclass, ttl, rdlen, flags, protocol, algo, gotone;
int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
struct blockdata *key;
if (ntohs(header->qdcount) != 1)
......@@ -546,15 +540,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class)
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_INSECURE;
/* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
{
strcpy(keyname, name);
return STAT_NEED_DS;
}
cache_start_insert();
for (gotone = 0, j = ntohs(header->ancount); j != 0; j--)
/* NOTE, we need to find ONE DNSKEY which matches the DS */
for (valid = 0, j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!extract_name(header, plen, &p, name, 1, 10))
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(qtype, p);
......@@ -562,64 +564,89 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qclass != class || qtype != T_DNSKEY || rdlen < 4)
if (qclass != class || qtype != T_DNSKEY || rc == 2)
{
/* skip all records other than DNSKEY */
p += rdlen;
continue;
if (ADD_RDLEN(header, p, plen, rdlen))
continue;
return STAT_INSECURE; /* bad packet */
}
crecp = cache_find_by_name(NULL, name, now, F_DS);
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
return STAT_INSECURE; /* bad packet */
psave = p;
/* length at least covers flags, protocol and algo now. */
GETSHORT(flags, p);
protocol = *p++;
if (*p++ != 3)
return STAT_INSECURE;
algo = *p++;
/* See if we have cached a DS record which validates this key */
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
if (recp1->addr.key.algo == algo && is_supported_digest(recp1->addr.key.digest))
break;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
/* DS record needed to validate key is missing, return name of DS in namebuff */
if (!recp1)
return STAT_NEED_DS;
else
/* Put the key into the cache. Note that if the validation fails, we won't
call cache_end_insert() and this will never be committed. */
if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
(recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
{
int valid = 1;
/* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest)
and see if it equals digest stored in recp1
*/
if (!valid)
return STAT_BOGUS;
recp1->uid = rdlen - 4;
recp1->addr.key.keydata = key;
recp1->addr.key.algo = algo;
recp1->addr.key.keytag = keytag;
}
if ((key = blockdata_alloc((char*)p, rdlen)))
{
/* We've proved that the KEY is OK, store it in the cache */
if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
{
crecp->uid = rdlen;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = algo;
crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen);
gotone = 1;
}
}
p = psave;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
/* Already determined that message is OK. Just loop stuffing cache */
if (valid || !key)
continue;
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
if (recp1->addr.key.algo == algo &&
recp1->addr.key.keytag == keytag &&
(flags & 0x100) && /* zone key flag */
digestalg_supported(recp1->addr.key.digest))
{
int wire_len = to_wire(name);
digestalg_begin(recp1->addr.key.digest);
digestalg_add_data(name, wire_len);
digestalg_add_data((char *)psave, rdlen);
from_wire(name);
/* TODO fragented digest */
if (memcmp(digestalg_final(), recp1->addr.key.keydata->key, digestalg_len()) == 0 &&
validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag))
{
struct all_addr a;
valid = 1;
a.addr.keytag = keytag;
log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
break;
}
}
}
cache_end_insert();
return gotone ? STAT_SECURE : STAT_INSECURE;
if (valid)
{
/* commit cache insert. */
cache_end_insert();
return STAT_SECURE;
}
log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
return STAT_BOGUS;
}
/* The DNS packet is expected to contain the answer to a DS query
Leave name of DS query in name.
Put all DSs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply.
STAT_INSECURE bad packet, no DS in reply.
STAT_SECURE At least one valid DS found and in cache.
STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
......@@ -627,8 +654,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
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);
struct crec *crecp, *recp1;
unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp;
int qtype, qclass, val, j, gotone;
struct blockdata *key;
......@@ -641,10 +668,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
return STAT_INSECURE;
val = validate_rrset(header, plen, class, T_DS, name, keyname);
val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0);
if (val == STAT_BOGUS)
log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
/* failed to validate or missing key. */
if (val != STAT_SECURE)
......@@ -654,7 +684,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
for (gotone = 0, j = ntohs(header->ancount); j != 0; j--)
{
int ttl, rdlen, rc, algo;
int ttl, rdlen, rc, algo, digest, keytag;
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
......@@ -666,32 +696,42 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETSHORT(rdlen, p);
/* check type, class and name, skip if not in DS rrset */
if (qclass != class || qtype != T_DS || rc == 2)
{
p += rdlen;
continue;
}
if ((key = blockdata_alloc((char*)p, rdlen)))
if (qclass == class && qtype == T_DS && rc == 1)
{
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
return STAT_INSECURE; /* bad packet */
psave = p;
GETSHORT(keytag, p);
algo = *p++;
digest = *p++;
/* We've proved that the DS is OK, store it in the cache */
if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
(crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
{
crecp->uid = rdlen;
struct all_addr a;
a.addr.keytag = keytag;
log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
crecp->addr.key.digest = digest;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = algo;
crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen);
gotone = 1;
crecp->addr.key.keytag = keytag;
}
else
return STAT_INSECURE; /* cache problem */
p = psave;
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
}
cache_end_insert();
return gotone ? STAT_SECURE : STAT_INSECURE;
return STAT_SECURE;
}
......@@ -702,528 +742,249 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
STAT_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
otherwise find the key in the cache.
*/
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname)
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
{
unsigned char *p, *psav, *sig;
int rrsetidx, res, sigttl, sig_data_len, j;
struct crec *crecp;
void *rrset[MAXRRSET]; /* TODO: max RRset size? */
unsigned char *p;
int rrsetidx, sigidx, res, rdlen, j;
struct crec *crecp = NULL;
void *rrset[MAXRRSET], *sigs[MAXRRSET]; /* TODO: max RRset size? */
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
if (!(p = skip_questions(header, plen)))
return STAT_INSECURE;
/* look for an RRSIG record for this RRset and get pointers to each record */
for (rrsetidx = 0, sig = NULL, j = ntohs(header->ancount) + ntohs(header->nscount);
for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);
j != 0; j--)
{
unsigned char *pstart = p;
int stype, sclass, sttl, rdlen;
unsigned char *pstart;
int stype, sclass, sttl;
if (!(res = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
pstart = p;
GETSHORT(stype, p);
GETSHORT(sclass, p);
GETLONG(sttl, p);
GETSHORT(rdlen, p);
(void)sttl;
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class)
continue;
if (htons(stype) == type)
{
rrset[rrsetidx++] = pstart;
if (rrsetidx == MAXRRSET)
return STAT_INSECURE; /* RRSET too big TODO */
}
if (htons(stype) == T_RRSIG)
if (res == 1 && sclass == class)
{
/* name matches, RRSIG for correct class */
/* enough data? */
if (rdlen < 18)
return STAT_INSECURE;
GETSHORT(type_covered, p);
algo = *p++;
labels = *p++;
GETLONG(orig_ttl, p);
GETLONG(sig_expiration, p);
GETLONG(sig_inception, p);
GETSHORT(key_tag, p);
if (type_covered != type ||
!check_date_range(sig_inception, sig_expiration))
if (stype == type)
{
/* covers wrong type or out of date - skip */
p = psav;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
continue;
rrset[rrsetidx++] = pstart;
if (rrsetidx == MAXRRSET)
return STAT_INSECURE; /* RRSET too big TODO */
}
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_INSECURE;
/* OK, we have the signature record, see if the
relevant DNSKEY is in the cache. */
for (crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY);
crecp;
crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag)
break;
/* No, abort for now whilst we get it */
if (!crecp)
return STAT_NEED_KEY;
/* Save point to signature data */
sig = p;
sig_data_len = rdlen - (p - psav);
sigttl = sttl;
/* next record */
p = psav;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
}
if (stype == T_RRSIG)
{
sigs[sigidx++] = pstart;
if (sigidx == MAXRRSET)
return STAT_INSECURE; /* RRSET too big TODO */
}
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
}
/* Didn't find RRSIG or RRset is empty */
if (!sig || rrsetidx == 0)
/* RRset empty, no RRSIGs */
if (rrsetidx == 0 || sigidx == 0)
return STAT_INSECURE;
/* OK, we have an RRSIG and an RRset and we have a the DNSKEY that validates them. */
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = plen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
/* Now initialize the signature verification algorithm and process the whole
RRset */
VerifyAlgCtx *alg = verifyalg_alloc(algo);
if (!alg)
return STAT_INSECURE;
alg->sig = sig;
alg->siglen = sig_data_len;
u16 ntype = htons(type);
u16 nclass = htons(class);
u32 nsigttl = htonl(sigttl);
/* TODO: we shouldn't need to convert this to wire here. Best solution would be:
- Use process_name() instead of extract_name() everywhere in dnssec code
- Convert from wire format to representation format only for querying/storing cache
*/
unsigned char owner_wire[MAXCDNAME];
int owner_wire_len = convert_domain_to_wire(name, owner_wire);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(sigrdata, 18+signer_name_rdlen);
for (i = 0; i < rrsetidx; ++i)
/* Now try all the sigs to try and find one which validates */
for (j = 0; j <sigidx; j++)
{
p = (unsigned char*)(rrset[i]);
unsigned char *psav;
int i, wire_len;
VerifyAlgCtx *alg;
u16 ntype, nclass;
u32 nsigttl;
p = sigs[j] + 8; /* skip type, class and ttl */
digestalg_add_data(owner_wire, owner_wire_len);
digestalg_add_data(&ntype, 2);
digestalg_add_data(&nclass, 2);
digestalg_add_data(&nsigttl, 4);
p += 8;
if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
return 0;
}
int digest_len = digestalg_len();
memcpy(alg->digest, digestalg_final(), digest_len);
if (alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp_uid))
return STAT_SECURE;
return STAT_INSECURE;
}
#if 0
static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig,
PendingRRSIGValidation *out)
{
int i, res;
int sigtype, sigalg, siglbl;
unsigned char *sigrdata = sig;
unsigned long sigttl, date_end, date_start;
unsigned char* p = reply;
char* signer_name = daemon->namebuff;
int signer_name_rdlen;
int keytag;
void *rrset[16]; /* TODO: max RRset size? */
int rrsetidx = 0;
if (sigrdlen < 18)
return 0;
GETSHORT(sigtype, sig);
sigalg = *sig++;
siglbl = *sig++;
GETLONG(sigttl, sig);
GETLONG(date_end, sig);
GETLONG(date_start, sig);
GETSHORT(keytag, sig);
sigrdlen -= 18;
if (!verifyalg_supported(sigalg))
{
printf("ERROR: RRSIG algorithm not supported: %d\n", sigalg);
return 0;
}
if (!check_date_range(date_start, date_end))
{
printf("ERROR: RRSIG outside date range\n");
return 0;
}
/* Iterate within the answer and find the RRsets matching the current RRsig */
for (i = 0; i < count; ++i)
{
int qtype, qclass, rdlen;
if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
return 0;
rrset[rrsetidx] = p;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
p += 4; /* skip ttl */
GETSHORT(rdlen, p);
if (res == 1 && qtype == sigtype && qclass == sigclass)
{
++rrsetidx;
if (rrsetidx == countof(rrset))
{
/* Internal buffer too small */
printf("internal buffer too small for this RRset\n");
return 0;
}
}
p += rdlen;
}
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = pktlen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
/* Skip through the signer name; we don't extract it right now because
we don't want to overwrite the single daemon->namebuff which contains
the owner name. We'll get to this later. */
if (!(p = skip_name(sig, header, pktlen, 0)))
return 0;
signer_name_rdlen = p - sig;
sig = p; sigrdlen -= signer_name_rdlen;
/* Now initialize the signature verification algorithm and process the whole
RRset */
VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
if (!alg)
return 0;
alg->sig = sig;
alg->siglen = sigrdlen;
sigtype = htons(sigtype);
sigclass = htons(sigclass);
sigttl = htonl(sigttl);
/* TODO: we shouldn't need to convert this to wire here. Best solution would be:
- Use process_name() instead of extract_name() everywhere in dnssec code
- Convert from wire format to representation format only for querying/storing cache
*/
unsigned char owner_wire[MAXCDNAME];
int owner_wire_len = convert_domain_to_wire(owner, owner_wire);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(sigrdata, 18+signer_name_rdlen);
for (i = 0; i < rrsetidx; ++i)
{
p = (unsigned char*)(rrset[i]);
digestalg_add_data(owner_wire, owner_wire_len);
digestalg_add_data(&sigtype, 2);
digestalg_add_data(&sigclass, 2);
digestalg_add_data(&sigttl, 4);
if (rdlen < 18)
return STAT_INSECURE; /* bad packet */
psav = p;
GETSHORT(type_covered, p);
algo = *p++;
labels = *p++;
GETLONG(orig_ttl, p);
GETLONG(sig_expiration, p);
GETLONG(sig_inception, p);
GETSHORT(key_tag, p);
if (type_covered != type ||
!check_date_range(sig_inception, sig_expiration) ||
!verifyalg_supported(algo))
{
/* covers wrong type or out of date - skip */
p = psav;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
continue;
}
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_INSECURE;
/* 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)))
return STAT_NEED_KEY;
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = plen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
alg = verifyalg_alloc(algo);
alg->sig = p;
alg->siglen = rdlen - (p - psav);
ntype = htons(type);
nclass = htons(class);
nsigttl = htonl(orig_ttl);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(psav, 18);
wire_len = to_wire(keyname);
digestalg_add_data(keyname, wire_len);
from_wire(keyname);
/* TODO wildcard rules : 4035 5.3.2 */
for (i = 0; i < rrsetidx; ++i)
{
p = (unsigned char*)(rrset[i]);
wire_len = to_wire(name);
digestalg_add_data(name, wire_len);
from_wire(name);
digestalg_add_data(&ntype, 2);
digestalg_add_data(&nclass, 2);
digestalg_add_data(&nsigttl, 4);
p += 8;
if (!digestalg_add_rdata(type, header, plen, p))
return STAT_INSECURE;
}
p += 8;
if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
return 0;
}
int digest_len = digestalg_len();
memcpy(alg->digest, digestalg_final(), digest_len);
/* We don't need the owner name anymore; now extract the signer name */
if (!extract_name_no_compression(sigrdata+18, signer_name_rdlen, signer_name))
return 0;
memcpy(alg->digest, digestalg_final(), digestalg_len());
out->alg = alg;
out->keytag = keytag;
out->signer_name = signer_name;
return 1;
}
if (key)
{
if (algo_in == algo && keytag_in == key_tag &&
alg->vtbl->verify(alg, key, keylen))
return STAT_SECURE;
}
else
{
/* iterate through all possible keys 4035 5.3.1 */
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag &&
alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid))
return STAT_SECURE;
}
}
static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
{
/* FIXME: keydata is non-contiguous */
return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
return STAT_BOGUS;
}
static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig)
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
{
PendingRRSIGValidation val;
/* Initiate the RRSIG validation process. The pending state is returned into val. */
if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val))
return;
unsigned char *ans_start, *p1, *p2;
int type1, class1, rdlen1, type2, class2, rdlen2;
int i, j, rc;
printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag);
/* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */
char onekey = 0;
struct crec *crecp = NULL;
while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */
if (!(ans_start = skip_questions(header, plen)))
return STAT_INSECURE;
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
onekey = 1;
if (crecp->addr.key.keytag == val.keytag
&& crecp->addr.key.algo == verifyalg_algonum(val.alg))
{
printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag);
if (!extract_name(header, plen, &p1, name, 1, 10))
return STAT_INSECURE; /* bad packet */
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
/* Don't try and validate RRSIGs! */
if (type1 != T_RRSIG)
{
/* Check if we've done this RRset already */
for (p2 = ans_start, j = 0; j < i; j++)
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
return STAT_INSECURE; /* 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_INSECURE;
}
/* Not done, validate now */
if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
{
*class = class1; /* Class for DS or DNSKEY */
return rc;
}
}
if (end_rrsig_validation(&val, crecp))
printf("Validation OK\n");
else
printf("ERROR: Validation FAILED (%s, keytag:%d, algo:%d)\n", owner, val.keytag, verifyalg_algonum(val.alg));
}
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_INSECURE;
}
if (!onekey)
{
printf("DNSKEY not found, need to fetch it\n");
/* TODO: store PendingRRSIGValidation in routing table,
fetch key (and make it go through dnssec_parskey), then complete validation. */
}
return STAT_SECURE;
}
#endif /* comment out */
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen)
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
{
if (alg == 1)
{
/* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
See RFC4034, Appendix B.1 */
return rdata[rdlen-3] * 256 + rdata[rdlen-2];
return key[keylen-4] * 256 + key[keylen-3];
}
else
{
unsigned long ac;
int i;
ac = 0;
for (i = 0; i < rdlen; ++i)
ac += (i & 1) ? rdata[i] : rdata[i] << 8;
ac += (ac >> 16) & 0xFFFF;
return ac & 0xFFFF;
}
}
/* Check if the DS record (from cache) points to the DNSKEY record (from cache) */
static int dnskey_ds_match(struct crec *dnskey, struct crec *ds)
{
if (dnskey->addr.key.keytag != ds->addr.key.keytag)
return 0;
if (dnskey->addr.key.algo != ds->addr.key.algo)
return 0;
unsigned char owner[MAXCDNAME]; /* TODO: user part of daemon->namebuff */
int owner_len = convert_domain_to_wire(cache_get_name(ds), owner);
size_t keylen = dnskey->uid;
int dig = ds->uid;
int digsize;
if (!digestalg_begin(dig))
return 0;
digsize = digestalg_len();
digestalg_add_data(owner, owner_len);
digestalg_add_data("\x01\x01\x03", 3);
digestalg_add_data(&ds->addr.key.algo, 1);
digestalg_add_keydata(dnskey->addr.key.keydata, keylen);
return (memcmp(digestalg_final(), ds->addr.key.keydata->key, digsize) == 0);
}
int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
int rdlen, unsigned char *rdata)
{
int flags, proto, alg;
struct blockdata *key; struct crec *crecp;
unsigned char *ordata = rdata; int ordlen = rdlen;
CHECKED_GETSHORT(flags, rdata, rdlen);
CHECKED_GETCHAR(proto, rdata, rdlen);
CHECKED_GETCHAR(alg, rdata, rdlen);
if (proto != 3)
return 0;
/* Skip non-signing keys (as specified in RFC4034 */
if (!(flags & 0x100))
return 0;
key = blockdata_alloc((char*)rdata, rdlen);
/* TODO: time(0) is correct here? */
crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
if (crecp)
{
/* TODO: improve union not to name "uid" this field */
crecp->uid = rdlen;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = alg;
crecp->addr.key.keytag = dnskey_keytag(alg, ordata, ordlen);
printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag);
}
else
{
blockdata_free(key);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf("DNSKEY: cache insertion failure\n");
return 0;
}
return 1;
}
int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
int rdlen, unsigned char *rdata)
{
int keytag, algo, dig;
struct blockdata *key; struct crec *crec_ds, *crec_key;
CHECKED_GETSHORT(keytag, rdata, rdlen);
CHECKED_GETCHAR(algo, rdata, rdlen);
CHECKED_GETCHAR(dig, rdata, rdlen);
if (!digestalg_supported(dig))
return 0;
key = blockdata_alloc((char*)rdata, rdlen);
/* TODO: time(0) is correct here? */
crec_ds = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DS);
if (!crec_ds)
{
blockdata_free(key);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf("DS: cache insertion failure\n");
return 0;
ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg;
for (i = 0; i < keylen; ++i)
ac += (i & 1) ? key[i] : key[i] << 8;
ac += (ac >> 16) & 0xffff;
return ac & 0xffff;
}
/* TODO: improve union not to name "uid" this field */
crec_ds->uid = dig;
crec_ds->addr.key.keydata = key;
crec_ds->addr.key.algo = algo;
crec_ds->addr.key.keytag = keytag;
printf("DS: storing key for %s (digest: %d)\n", owner, dig);
/* Now try to find a DNSKEY which matches this DS digest. */
printf("Looking for a DNSKEY matching DS %d...\n", keytag);
crec_key = NULL;
while ((crec_key = cache_find_by_name(crec_key, owner, time(0), F_DNSKEY))) /* TODO: time(0) */
{
if (dnskey_ds_match(crec_key, crec_ds))
{
/* TODO: create a link within the cache: ds => dnskey */
printf("MATCH FOUND for keytag %d\n", keytag);
return 1;
}
}
printf("ERROR: match not found for DS %d (owner: %s)\n", keytag, owner);
return 0;
}
int dnssec1_validate(struct dns_header *header, size_t pktlen)
{
unsigned char *p, *reply;
char *owner = daemon->namebuff;
int i, s, qtype, qclass, rdlen;
unsigned long ttl;
int slen[3] = { ntohs(header->ancount), ntohs(header->nscount), ntohs(header->arcount) };
if (slen[0] + slen[1] + slen[2] == 0)
return 0;
if (!(reply = p = skip_questions(header, pktlen)))
return 0;
/* First, process DNSKEY/DS records and add them to the cache. */
cache_start_insert();
for (i = 0; i < slen[0]; i++)
{
if (!extract_name(header, pktlen, &p, owner, 1, 10))
return 0;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qtype == T_DS)
{
printf("DS found\n");
dnssec_parseds(header, pktlen, owner, ttl, rdlen, p);
}
else if (qtype == T_DNSKEY)
{
printf("DNSKEY found\n");
dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p);
}
p += rdlen;
}
cache_end_insert();
/* After we have cached DNSKEY/DS records, start looking for RRSIGs.
We want to do this in a separate step because we want the cache
to be already populated with DNSKEYs before parsing signatures. */
p = reply;
for (s = 0; s < 3; ++s)
{
reply = p;
for (i = 0; i < slen[s]; i++)
{
if (!extract_name(header, pktlen, &p, owner, 1, 10))
return 0;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qtype == T_RRSIG)
{
printf("RRSIG found (owner: %s)\n", owner);
/* TODO: missing logic. We should only validate RRSIGs for which we
have a valid DNSKEY that is referenced by a DS record upstream.
There is a memory vs CPU conflict here; should we validate everything
to save memory and thus waste CPU, or better first acquire all information
(wasting memory) and then doing the minimum CPU computations required? */
dnssec_parserrsig(header, pktlen, reply, slen[s], owner, qclass, rdlen, p);
}
p += rdlen;
}
}
return 1;
}
#endif /* HAVE_DNSSEC */
......@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ);
{
plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ);
header->hb4 |= HB4_CD;
}
#endif
while (1)
......@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE(header, NOERROR);
}
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache))
if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure))
{
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
......@@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now)
if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
{
int status;
int class;
/* We've had a reply already, which we're validating. Ignore this duplicate */
if (forward->stash)
return;
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class);
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
/* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
struct frec *new;
if ((forward->stash = blockdata_alloc((char *)header, n)))
if ((new = get_new_frec(now, NULL, 1)))
{
forward->stash_len = n;
struct frec *next = new->next;
*new = *forward; /* copy everything, then overwrite */
new->next = next;
new->stash = NULL;
new->blocking_query = NULL;
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
if ((new = get_new_frec(now, NULL, 1)))
if ((forward->stash = blockdata_alloc((char *)header, n)))
{
int fd;
new = forward; /* copy everything, then overwrite */
forward->stash_len = n;
new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */
/* validate routines leave name of required record in daemon->namebuff */
/* validate routines leave name of required record in daemon->keyname */
if (status == STAT_NEED_KEY)
{
new->flags |= FREC_DNSKEY_QUERY;
nn = dnssec_generate_query(header, daemon->namebuff, class, T_DNSKEY);
nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DNSKEY, &server->addr);
}
else if (status == STAT_NEED_DS)
{
new->flags |= FREC_DS_QUERY;
nn = dnssec_generate_query(header, daemon->namebuff, class, T_DS);
nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DS, &server->addr);
}
new->crc = questions_crc(header, nn, daemon->namebuff);
new->new_id = get_id(new->crc);
......@@ -739,9 +752,11 @@ void reply_query(int fd, int family, time_t now)
}
/* Send DNSSEC query to same server as original query */
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
server->queries++;
}
}
return;
}
......@@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now)
and validate them with the new data. Failure to find needed data here is an internal error.
Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
return it to the original requestor. */
while (forward->dependent)
if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
{
struct frec *prev = forward->dependent;
free_frec(forward);
forward = prev;
blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len;
if (status == STAT_SECURE)
while (forward->dependent)
{
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
struct frec *prev;
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
if (status == STAT_SECURE)
{
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
}
prev = forward->dependent;
free_frec(forward);
forward = prev;
forward->blocking_query = NULL; /* already gone */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len;
}
/* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */
if (status == STAT_SECURE)
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
status = STAT_INSECURE;
}
}
/* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */
if (status == STAT_SECURE)
status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class);
log_query(F_KEYTAG | F_SECSTAT, "result", NULL,
status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
no_cache_dnssec = 0;
if (status == STAT_SECURE)
cache_secure = 1;
/* TODO return SERVFAIL here */
else if (status == STAT_BOGUS)
no_cache_dnssec = 1;
/* restore CD bit to the value in the query */
if (forward->flags & FREC_CHECKING_DISABLED)
header->hb4 |= HB4_CD;
else
header->hb4 &= ~HB4_CD;
}
#endif
......@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family)
return NULL; /* doom */
}
static void free_frec(struct frec *f)
{
if (f->rfd4 && --(f->rfd4->refcount) == 0)
......@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f)
#ifdef HAVE_DNSSEC
if (f->stash)
blockdata_free(f->stash);
{
blockdata_free(f->stash);
f->stash = NULL;
}
/* Anything we're waiting on is pointless now, too */
if (f->blocking_query)
......
......@@ -139,6 +139,7 @@ struct myoption {
#define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328
#define LOPT_SEC_VALID 329
#define LOPT_DNSKEY 330
#ifdef HAVE_GETOPT_LONG
......@@ -276,6 +277,7 @@ static const struct myoption opts[] =
{ "ipset", 1, 0, LOPT_IPSET },
{ "synth-domain", 1, 0, LOPT_SYNTH },
{ "dnssec", 0, 0, LOPT_SEC_VALID },
{ "dnskey", 1, 0, LOPT_DNSKEY },
#ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif
......@@ -428,6 +430,7 @@ static struct {
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
#ifdef HAVE_DNSSEC
{ 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 },
#endif
#ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
......@@ -3670,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon->host_records_tail = new;
break;
}
#ifdef HAVE_DNSSEC
case LOPT_DNSKEY:
{
struct dnskey *new = opt_malloc(sizeof(struct dnskey));
char *key64, *algo;
if (!(comma = split(arg)) || !(algo = split(comma)) || !(key64 = split(algo)) ||
!atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
!(new->name = canonicalise_opt(arg)))
ret_err(_("bad DNSKEY"));
/* Upper bound on length */
new->key = opt_malloc((3*strlen(key64)/4));
unhide_metas(key64);
if ((new->keylen = parse_base64(key64, new->key)) == -1)
ret_err(_("bad base64 in DNSKEY"));
new->next = daemon->dnskeys;
daemon->dnskeys = new;
break;
}
#endif
default:
ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)"));
ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
}
......
......@@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return plen; /* Too big */
}
PUTSHORT(optno, p);
PUTSHORT(optlen, p);
memcpy(p, opt, optlen);
p += optlen;
if (optno != 0)
{
PUTSHORT(optno, p);
PUTSHORT(optlen, p);
memcpy(p, opt, optlen);
p += optlen;
}
PUTSHORT(p - datap, lenp);
return p - (unsigned char *)header;
......@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec)
char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure)
{
unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
......@@ -919,6 +922,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
struct crec *cpp = NULL;
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
unsigned long cttl = ULONG_MAX, attl;
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
if (secure)
flags |= F_DNSSECOK;
namep = p;
if (!extract_name(header, qlen, &p, name, 1, 4))
......@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
int dryrun = 0, sec_reqd = 0;
int is_sign;
struct crec *crecp;
int nxdomain = 0, auth = 1, trunc = 0;
int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec;
/* If there is an RFC2671 pseudoheader then it will be overwritten by
......@@ -1621,6 +1630,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
continue;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
if (crecp->flags & F_NEG)
{
ans = 1;
......@@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
break;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
if (crecp->flags & F_CNAME)
{
char *cname_target = cache_get_cname_target(crecp);
......@@ -1868,6 +1883,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
(qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))))
{
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
ans = 1;
if (!dryrun)
{
......@@ -2046,7 +2064,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* truncation */
if (trunc)
header->hb3 |= HB3_TC;
header->hb4 &= ~HB4_AD;
if (option_bool(OPT_DNSSEC_VALID) || option_bool(OPT_DNSSEC_PROXY))
if (sec_data)
header->hb4 |= HB4_AD;
if (nxdomain)
SET_RCODE(header, NXDOMAIN);
else
......
......@@ -109,10 +109,10 @@ static int check_name(char *in)
if (in[l-1] == '.')
{
if (l == 1) return 0;
in[l-1] = 0;
nowhite = 1;
}
for (; (c = *in); in++)
{
if (c == '.')
......@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
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)
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment