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

First functional DNSSEC - highly alpha.

parent c3e0b9b6
...@@ -56,6 +56,8 @@ static const struct { ...@@ -56,6 +56,8 @@ static const struct {
{ 38, "A6" }, { 38, "A6" },
{ 39, "DNAME" }, { 39, "DNAME" },
{ 41, "OPT" }, { 41, "OPT" },
{ 43, "DS" },
{ 46, "RRSIG" },
{ 48, "DNSKEY" }, { 48, "DNSKEY" },
{ 249, "TKEY" }, { 249, "TKEY" },
{ 250, "TSIG" }, { 250, "TSIG" },
...@@ -916,12 +918,19 @@ void cache_reload(void) ...@@ -916,12 +918,19 @@ void cache_reload(void)
struct name_list *nl; struct name_list *nl;
struct cname *a; struct cname *a;
struct interface_name *intr; struct interface_name *intr;
#ifdef HAVE_DNSSEC
struct dnskey *key;
#endif
cache_inserted = cache_live_freed = 0; cache_inserted = cache_live_freed = 0;
for (i=0; i<hash_size; i++) for (i=0; i<hash_size; i++)
for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp) 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; tmp = cache->hash_next;
if (cache->flags & (F_HOSTS | F_CONFIG)) if (cache->flags & (F_HOSTS | F_CONFIG))
{ {
...@@ -948,7 +957,7 @@ void cache_reload(void) ...@@ -948,7 +957,7 @@ void cache_reload(void)
if (hostname_isequal(a->target, intr->name) && if (hostname_isequal(a->target, intr->name) &&
((cache = whine_malloc(sizeof(struct crec))))) ((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->name.namep = a->alias;
cache->addr.cname.target.int_name = intr; cache->addr.cname.target.int_name = intr;
cache->addr.cname.uid = -1; cache->addr.cname.uid = -1;
...@@ -956,6 +965,20 @@ void cache_reload(void) ...@@ -956,6 +965,20 @@ void cache_reload(void)
add_hosts_cname(cache); /* handle chains */ 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 */ /* borrow the packet buffer for a temporary by-address hash */
memset(daemon->packet, 0, daemon->packet_buff_sz); memset(daemon->packet, 0, daemon->packet_buff_sz);
revhashsz = daemon->packet_buff_sz / sizeof(struct crec *); revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
...@@ -1197,16 +1220,13 @@ void dump_cache(time_t now) ...@@ -1197,16 +1220,13 @@ void dump_cache(time_t now)
for (i=0; i<hash_size; i++) for (i=0; i<hash_size; i++)
for (cache = hash_table[i]; cache; cache = cache->hash_next) for (cache = hash_table[i]; cache; cache = cache->hash_next)
{ {
char *a, *p = daemon->namebuff; char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
p += sprintf(p, "%-40.40s ", cache_get_name(cache)); *a = 0;
if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD)) if (strlen(n) == 0)
a = ""; n = "<Root>";
else if (cache->flags & F_CNAME) p += sprintf(p, "%-40.40s ", n);
{ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
a = "";
if (!is_outdated_cname_pointer(cache))
a = cache_get_cname_target(cache); a = cache_get_cname_target(cache);
}
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
else if (cache->flags & F_DNSKEY) else if (cache->flags & F_DNSKEY)
{ {
...@@ -1216,11 +1236,11 @@ void dump_cache(time_t now) ...@@ -1216,11 +1236,11 @@ void dump_cache(time_t now)
else if (cache->flags & F_DS) else if (cache->flags & F_DS)
{ {
a = daemon->addrbuff; a = daemon->addrbuff;
sprintf(a, "%5u %3u %3u %u", cache->addr.key.keytag, sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
cache->addr.key.algo, cache->addr.key.digest, cache->uid); cache->addr.key.algo, cache->addr.key.digest);
} }
#endif #endif
else else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
{ {
a = daemon->addrbuff; a = daemon->addrbuff;
if (cache->flags & F_IPV4) if (cache->flags & F_IPV4)
...@@ -1291,6 +1311,10 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1291,6 +1311,10 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
if (addr) if (addr)
{ {
if (flags & F_KEYTAG)
sprintf(daemon->addrbuff, arg, addr->addr.keytag);
else
{
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
addr, daemon->addrbuff, ADDRSTRLEN); addr, daemon->addrbuff, ADDRSTRLEN);
...@@ -1298,6 +1322,9 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1298,6 +1322,9 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
#endif #endif
} }
}
else
dest = arg;
if (flags & F_REVERSE) if (flags & F_REVERSE)
{ {
...@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg; source = arg;
else if (flags & F_UPSTREAM) else if (flags & F_UPSTREAM)
source = "reply"; source = "reply";
else if (flags & F_SECSTAT)
source = "validation";
else if (flags & F_AUTH) else if (flags & F_AUTH)
source = "auth"; source = "auth";
else if (flags & F_SERVER) else if (flags & F_SERVER)
...@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) ...@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source = arg; source = arg;
verb = "from"; verb = "from";
} }
else if (flags & F_DNSSEC)
{
source = arg;
verb = "to";
}
else else
source = "cached"; source = "cached";
...@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks) ...@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks)
keyblock_free = 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 #endif
...@@ -385,7 +385,12 @@ static char *compile_opts = ...@@ -385,7 +385,12 @@ static char *compile_opts =
#ifndef HAVE_AUTH #ifndef HAVE_AUTH
"no-" "no-"
#endif #endif
"auth"; "auth "
#ifndef HAVE_DNSSEC
"no-"
#endif
"DNSSEC";
#endif #endif
......
...@@ -242,6 +242,7 @@ struct all_addr { ...@@ -242,6 +242,7 @@ struct all_addr {
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
struct in6_addr addr6; struct in6_addr addr6;
#endif #endif
unsigned int keytag;
} addr; } addr;
}; };
...@@ -286,6 +287,12 @@ struct cname { ...@@ -286,6 +287,12 @@ struct cname {
struct cname *next; struct cname *next;
}; };
struct dnskey {
char *name, *key;
int keylen, algo, flags;
struct dnskey *next;
};
#define ADDRLIST_LITERAL 1 #define ADDRLIST_LITERAL 1
#define ADDRLIST_IPV6 2 #define ADDRLIST_IPV6 2
...@@ -360,7 +367,7 @@ struct crec { ...@@ -360,7 +367,7 @@ struct crec {
} key; } key;
} addr; } addr;
time_t ttd; /* time to die */ 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; int uid;
unsigned short flags; unsigned short flags;
union { union {
...@@ -395,6 +402,9 @@ struct crec { ...@@ -395,6 +402,9 @@ struct crec {
#define F_QUERY (1u<<19) #define F_QUERY (1u<<19)
#define F_NOERR (1u<<20) #define F_NOERR (1u<<20)
#define F_AUTH (1u<<21) #define F_AUTH (1u<<21)
#define F_DNSSEC (1u<<22)
#define F_KEYTAG (1u<<23)
#define F_SECSTAT (1u<<24)
/* composites */ /* composites */
#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */ #define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */
...@@ -896,6 +906,9 @@ extern struct daemon { ...@@ -896,6 +906,9 @@ extern struct daemon {
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
struct prefix_class *prefix_classes; struct prefix_class *prefix_classes;
#endif #endif
#ifdef HAVE_DNSSEC
struct dnskey *dnskeys;
#endif
/* globally used stuff for DNS */ /* globally used stuff for DNS */
char *packet; /* packet buffer */ char *packet; /* packet buffer */
...@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init); ...@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init);
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
struct blockdata *blockdata_alloc(char *data, size_t len); struct blockdata *blockdata_alloc(char *data, size_t len);
size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt); 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); void blockdata_free(struct blockdata *blocks);
#endif #endif
...@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, ...@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
unsigned long local_ttl); unsigned long local_ttl);
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
time_t now, char **ipsets, int is_sign, int checkrebind, 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, 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); 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, 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); ...@@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif #endif
/* dnssec.c */ /* 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_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 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 validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,
int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, 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 */ /* util.c */
void rand_init(void); void rand_init(void);
...@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t); ...@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t);
int prettyprint_addr(union mysockaddr *addr, char *buf); int prettyprint_addr(union mysockaddr *addr, char *buf);
int parse_hex(char *in, unsigned char *out, int maxlen, int parse_hex(char *in, unsigned char *out, int maxlen,
unsigned int *wildcard_mask, int *mac_type); 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, int memcmp_masked(unsigned char *a, unsigned char *b, int len,
unsigned int mask); unsigned int mask);
int expand_buf(struct iovec *iov, size_t size); int expand_buf(struct iovec *iov, size_t size);
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#ifndef DNSSEC_CRYPTO_H #ifndef DNSSEC_CRYPTO_H
#define DNSSEC_CRYPTO_H #define DNSSEC_CRYPTO_H
struct keydata; struct blockdata;
/* /*
* vtable for a signature verification algorithm. * vtable for a signature verification algorithm.
...@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx; ...@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx;
typedef struct typedef struct
{ {
int digest_algo; int digest_algo;
int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len); int (*verify)(VerifyAlgCtx *ctx, struct blockdata *key, unsigned key_len);
} VerifyAlg; } VerifyAlg;
struct VerifyAlgCtx struct VerifyAlgCtx
...@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a); ...@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a);
#define DIGESTALG_SHA512 257 #define DIGESTALG_SHA512 257
int digestalg_supported(int algo); 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_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); unsigned char *digestalg_final(void);
int digestalg_len(void); int digestalg_len(void);
......
...@@ -14,13 +14,16 @@ ...@@ -14,13 +14,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <string.h>
#include "dnsmasq.h" #include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h" #include "dnssec-crypto.h"
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/dsa.h> #include <openssl/dsa.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <string.h>
#define POOL_SIZE 1 #define POOL_SIZE 1
static union _Pool static union _Pool
...@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len) ...@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len)
printf("\n"); 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; size_t cnt;
BIGNUM temp; BIGNUM temp;
BN_init(ret); BN_init(ret);
cnt = keydata_walk(key_data, p, len); cnt = blockdata_walk(key_data, p, len);
BN_bin2bn(*p, cnt, ret); BN_bin2bn(*p, cnt, ret);
len -= cnt; len -= cnt;
*p += cnt; *p += cnt;
while (len > 0) while (len > 0)
{ {
if (!(cnt = keydata_walk(key_data, p, len))) if (!(cnt = blockdata_walk(key_data, p, len)))
return 0; return 0;
BN_lshift(ret, ret, cnt*8); BN_lshift(ret, ret, cnt*8);
BN_init(&temp); BN_init(&temp);
...@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char * ...@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char *
return 1; 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; unsigned char *p = key_data->key;
size_t exp_len, mod_len; size_t exp_len, mod_len;
...@@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, ...@@ -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); 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; unsigned char *p = key_data->key;
int T; int T;
...@@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct ...@@ -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); 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; int validated = 0;
...@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_ ...@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_
return validated; 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); 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); 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); 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); 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[] = static unsigned char asn1_signature[] =
{ {
...@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo) ...@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo)
int i; int i;
VerifyAlgCtx *ret = 0; VerifyAlgCtx *ret = 0;
if (!verifyalg_supported(algo))
return 0;
if (pool_used == (1<<POOL_SIZE)-1) if (pool_used == (1<<POOL_SIZE)-1)
ret = whine_malloc(valgctx_size[algo]); ret = whine_malloc(valgctx_size[algo]);
else else
...@@ -271,7 +271,7 @@ int digestalg_supported(int algo) ...@@ -271,7 +271,7 @@ int digestalg_supported(int algo)
algo == DIGESTALG_SHA512); algo == DIGESTALG_SHA512);
} }
int digestalg_begin(int algo) void digestalg_begin(int algo)
{ {
EVP_MD_CTX_init(&digctx); EVP_MD_CTX_init(&digctx);
if (algo == DIGESTALG_SHA1) if (algo == DIGESTALG_SHA1)
...@@ -282,9 +282,6 @@ int digestalg_begin(int algo) ...@@ -282,9 +282,6 @@ int digestalg_begin(int algo)
EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL); EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL);
else if (algo == DIGESTALG_MD5) else if (algo == DIGESTALG_MD5)
EVP_DigestInit_ex(&digctx, EVP_md5(), NULL); EVP_DigestInit_ex(&digctx, EVP_md5(), NULL);
else
return 0;
return 1;
} }
int digestalg_len() int digestalg_len()
...@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len) ...@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len)
EVP_DigestUpdate(&digctx, data, 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; size_t cnt; unsigned char *p = NULL;
while (len) while (len)
{ {
cnt = keydata_walk(&key, &p, len); cnt = blockdata_walk(&key, &p, len);
EVP_DigestUpdate(&digctx, p, cnt); EVP_DigestUpdate(&digctx, p, cnt);
p += cnt; p += cnt;
len -= cnt; len -= cnt;
...@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void) ...@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void)
return digest; return digest;
} }
#endif /* HAVE_DNSSEC */
/* 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-2014 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
...@@ -15,6 +16,9 @@ ...@@ -15,6 +16,9 @@
*/ */
#include "dnsmasq.h" #include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h" #include "dnssec-crypto.h"
#include <assert.h> #include <assert.h>
...@@ -28,8 +32,6 @@ ...@@ -28,8 +32,6 @@
#define SERIAL_LT -1 #define SERIAL_LT -1
#define SERIAL_GT 1 #define SERIAL_GT 1
static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen);
/* Implement RFC1982 wrapped compare for 32-bit numbers */ /* Implement RFC1982 wrapped compare for 32-bit numbers */
static int serial_compare_32(unsigned long s1, unsigned long s2) 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) ...@@ -45,36 +47,6 @@ static int serial_compare_32(unsigned long s1, unsigned long s2)
return SERIAL_UNDEF; 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. /* process_domain_name() - do operations with domain names in canonicalized wire format.
* *
...@@ -419,46 +391,55 @@ typedef struct PendingRRSIGValidation ...@@ -419,46 +391,55 @@ typedef struct PendingRRSIGValidation
int keytag; int keytag;
} PendingRRSIGValidation; } 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, /* Convert from presentation format to wire format, in place.
rather than tweaking with configure. */ Also map UC -> LC.
static char *my_strchrnul(char *str, char ch) 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) unsigned char *l, *p, term;
str++; int len;
return str;
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 */ /* Note: no compression allowed in input. */
static int convert_domain_to_wire(char *name, unsigned char* out) static void from_wire(char *name)
{ {
unsigned char len; unsigned char *l;
unsigned char *start = out; int len;
char *p;
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++; len = *l;
/* TODO: this will not be required anymore once we memmove(l, l+1, len);
remove all usages of extract_name() from DNSSEC code */ l[len] = '.';
if (ch >= 'A' && ch <= 'Z')
ch = ch - 'A' + 'a';
*out++ = ch;
}
} }
name = p+1;
}
while (*p);
*out++ = '\0'; *(l-1) = 0;
return out-start;
} }
...@@ -498,9 +479,19 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk ...@@ -498,9 +479,19 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk
return 1; 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; 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->qdcount = htons(1);
header->ancount = htons(0); header->ancount = htons(0);
...@@ -509,32 +500,35 @@ size_t dnssec_generate_query(struct dns_header *header, char *name, int class, i ...@@ -509,32 +500,35 @@ size_t dnssec_generate_query(struct dns_header *header, char *name, int class, i
header->hb3 = HB3_RD; header->hb3 = HB3_RD;
SET_OPCODE(header, QUERY); SET_OPCODE(header, QUERY);
header->hb4 = 0; header->hb4 = HB4_CD;
/* ID filled in later */ /* ID filled in later */
p = (unsigned char *)(header+1); p = (unsigned char *)(header+1);
p = do_rfc1035_name(p, name); p = do_rfc1035_name(p, name);
*p++ = 0;
PUTSHORT(type, p); PUTSHORT(type, p);
PUTSHORT(class, p); PUTSHORT(class, p);
return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ); 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. Put all DNSKEYs in the answer which are valid into the cache.
return codes: return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply. STAT_INSECURE bad packet, no DNSKEYs in reply.
STAT_SECURE At least one valid DNSKEY found and in cache. STAT_SECURE At least one valid DNSKEY found and in cache.
STAT_BOGUS At least one DNSKEY found, which fails validation. STAT_BOGUS No DNSKEYs found, which can be validated with DS,
STAT_NEED_DS DS records to validate a key not found, name in namebuff 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) 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; 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; struct blockdata *key;
if (ntohs(header->qdcount) != 1) 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 ...@@ -546,15 +540,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class) if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_INSECURE; 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(); 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 */ /* 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 */ return STAT_INSECURE; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
...@@ -562,64 +564,89 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -562,64 +564,89 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, 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 */ if (ADD_RDLEN(header, p, plen, rdlen))
p += rdlen;
continue; 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. */ /* length at least covers flags, protocol and algo now. */
GETSHORT(flags, p); GETSHORT(flags, p);
protocol = *p++; if (*p++ != 3)
return STAT_INSECURE;
algo = *p++; algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
/* See if we have cached a DS record which validates this key */ /* Put the key into the cache. Note that if the validation fails, we won't
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) call cache_end_insert() and this will never be committed. */
if (recp1->addr.key.algo == algo && is_supported_digest(recp1->addr.key.digest)) if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
break; (recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
/* DS record needed to validate key is missing, return name of DS in namebuff */
if (!recp1)
return STAT_NEED_DS;
else
{ {
int valid = 1; recp1->uid = rdlen - 4;
/* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest) recp1->addr.key.keydata = key;
and see if it equals digest stored in recp1 recp1->addr.key.algo = algo;
*/ recp1->addr.key.keytag = keytag;
if (!valid)
return STAT_BOGUS;
} }
if ((key = blockdata_alloc((char*)p, rdlen))) 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);
/* We've proved that the KEY is OK, store it in the cache */ from_wire(name);
if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
/* 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))
{ {
crecp->uid = rdlen; struct all_addr a;
crecp->addr.key.keydata = key; valid = 1;
crecp->addr.key.algo = algo; a.addr.keytag = keytag;
crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
gotone = 1; break;
} }
} }
} }
if (valid)
{
/* commit cache insert. */
cache_end_insert(); cache_end_insert();
return STAT_SECURE;
}
log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
return gotone ? STAT_SECURE : STAT_INSECURE; return STAT_BOGUS;
} }
/* 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
Leave name of DS query in name.
Put all DSs in the answer which are valid into the cache. Put all DSs in the answer which are valid into the cache.
return codes: 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_SECURE At least one valid DS found and in cache.
STAT_BOGUS At least one DS found, which fails validation. STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname 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 ...@@ -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) 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 *psave, *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1; struct crec *crecp;
int qtype, qclass, val, j, gotone; int qtype, qclass, val, j, gotone;
struct blockdata *key; struct blockdata *key;
...@@ -641,10 +668,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -641,10 +668,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class) if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
return STAT_INSECURE; 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. */ /* failed to validate or missing key. */
if (val != STAT_SECURE) if (val != STAT_SECURE)
...@@ -654,7 +684,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -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--) 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 */ /* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10))) 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 ...@@ -666,32 +696,42 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
/* check type, class and name, skip if not in DS rrset */ /* check type, class and name, skip if not in DS rrset */
if (qclass != class || qtype != T_DS || rc == 2) if (qclass == class && qtype == T_DS && rc == 1)
{ {
p += rdlen; if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
continue; return STAT_INSECURE; /* bad packet */
}
if ((key = blockdata_alloc((char*)p, rdlen))) psave = p;
{ GETSHORT(keytag, p);
algo = *p++;
digest = *p++;
/* We've proved that the DS is OK, store it in the cache */ /* 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.keydata = key;
crecp->addr.key.algo = algo; crecp->addr.key.algo = algo;
crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); crecp->addr.key.keytag = keytag;
gotone = 1;
} }
else
return STAT_INSECURE; /* cache problem */
p = psave;
} }
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
} }
cache_end_insert(); cache_end_insert();
return STAT_SECURE;
return gotone ? STAT_SECURE : STAT_INSECURE;
} }
...@@ -702,53 +742,87 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -702,53 +742,87 @@ 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_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong. STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) 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; unsigned char *p;
int rrsetidx, res, sigttl, sig_data_len, j; int rrsetidx, sigidx, res, rdlen, j;
struct crec *crecp; struct crec *crecp = NULL;
void *rrset[MAXRRSET]; /* TODO: max RRset size? */ void *rrset[MAXRRSET], *sigs[MAXRRSET]; /* TODO: max RRset size? */
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
if (!(p = skip_questions(header, plen))) if (!(p = skip_questions(header, plen)))
return STAT_INSECURE; return STAT_INSECURE;
/* look for an RRSIG record for this RRset and get pointers to each record */ /* 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--) j != 0; j--)
{ {
unsigned char *pstart = p; unsigned char *pstart;
int stype, sclass, sttl, rdlen; int stype, sclass, sttl;
if (!(res = extract_name(header, plen, &p, name, 0, 10))) if (!(res = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */ return STAT_INSECURE; /* bad packet */
pstart = p;
GETSHORT(stype, p); GETSHORT(stype, p);
GETSHORT(sclass, p); GETSHORT(sclass, p);
GETLONG(sttl, p); GETLONG(sttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
(void)sttl;
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */ return STAT_INSECURE; /* bad packet */
if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class) if (res == 1 && sclass == class)
continue; {
if (stype == type)
if (htons(stype) == type)
{ {
rrset[rrsetidx++] = pstart; rrset[rrsetidx++] = pstart;
if (rrsetidx == MAXRRSET) if (rrsetidx == MAXRRSET)
return STAT_INSECURE; /* RRSET too big TODO */ return STAT_INSECURE; /* RRSET too big TODO */
} }
if (htons(stype) == T_RRSIG) if (stype == T_RRSIG)
{ {
/* name matches, RRSIG for correct class */ sigs[sigidx++] = pstart;
/* enough data? */ if (sigidx == MAXRRSET)
if (rdlen < 18) return STAT_INSECURE; /* RRSET too big TODO */
}
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
}
/* RRset empty, no RRSIGs */
if (rrsetidx == 0 || sigidx == 0)
return STAT_INSECURE; return STAT_INSECURE;
/* Now try all the sigs to try and find one which validates */
for (j = 0; j <sigidx; j++)
{
unsigned char *psav;
int i, wire_len;
VerifyAlgCtx *alg;
u16 ntype, nclass;
u32 nsigttl;
p = sigs[j] + 8; /* skip type, class and ttl */
GETSHORT(rdlen, p);
if (rdlen < 18)
return STAT_INSECURE; /* bad packet */
psav = p;
GETSHORT(type_covered, p); GETSHORT(type_covered, p);
algo = *p++; algo = *p++;
labels = *p++; labels = *p++;
...@@ -758,7 +832,8 @@ int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class ...@@ -758,7 +832,8 @@ int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class
GETSHORT(key_tag, p); GETSHORT(key_tag, p);
if (type_covered != type || if (type_covered != type ||
!check_date_range(sig_inception, sig_expiration)) !check_date_range(sig_inception, sig_expiration) ||
!verifyalg_supported(algo))
{ {
/* covers wrong type or out of date - skip */ /* covers wrong type or out of date - skip */
p = psav; p = psav;
...@@ -770,460 +845,146 @@ int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class ...@@ -770,460 +845,146 @@ int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class
if (!extract_name(header, plen, &p, keyname, 1, 0)) if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_INSECURE; return STAT_INSECURE;
/* OK, we have the signature record, see if the /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
relevant DNSKEY is in the cache. */ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
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; 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;
}
}
/* Didn't find RRSIG or RRset is empty */
if (!sig || rrsetidx == 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. */ /* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header; rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = plen; rrset_canonical_order_ctx.pktlen = plen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
/* Now initialize the signature verification algorithm and process the whole alg = verifyalg_alloc(algo);
RRset */ alg->sig = p;
VerifyAlgCtx *alg = verifyalg_alloc(algo); alg->siglen = rdlen - (p - psav);
if (!alg)
return STAT_INSECURE;
alg->sig = sig; ntype = htons(type);
alg->siglen = sig_data_len; nclass = htons(class);
nsigttl = htonl(orig_ttl);
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_begin(alg->vtbl->digest_algo);
digestalg_add_data(sigrdata, 18+signer_name_rdlen); 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) for (i = 0; i < rrsetidx; ++i)
{ {
p = (unsigned char*)(rrset[i]); p = (unsigned char*)(rrset[i]);
digestalg_add_data(owner_wire, owner_wire_len); wire_len = to_wire(name);
digestalg_add_data(name, wire_len);
from_wire(name);
digestalg_add_data(&ntype, 2); digestalg_add_data(&ntype, 2);
digestalg_add_data(&nclass, 2); digestalg_add_data(&nclass, 2);
digestalg_add_data(&nsigttl, 4); digestalg_add_data(&nsigttl, 4);
p += 8; p += 8;
if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p)) if (!digestalg_add_rdata(type, header, plen, 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; 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)) memcpy(alg->digest, digestalg_final(), digestalg_len());
{
printf("ERROR: RRSIG outside date range\n");
return 0;
}
/* Iterate within the answer and find the RRsets matching the current RRsig */ if (key)
for (i = 0; i < count; ++i)
{ {
int qtype, qclass, rdlen; if (algo_in == algo && keytag_in == key_tag &&
if (!(res = extract_name(header, pktlen, &p, owner, 0, 10))) alg->vtbl->verify(alg, key, keylen))
return 0; return STAT_SECURE;
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;
} }
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;
} }
p += rdlen;
} }
/* Sort RRset records in canonical order. */ return STAT_BOGUS;
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); /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
sigclass = htons(sigclass); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
sigttl = htonl(sigttl); {
unsigned char *ans_start, *p1, *p2;
int type1, class1, rdlen1, type2, class2, rdlen2;
int i, j, rc;
/* TODO: we shouldn't need to convert this to wire here. Best solution would be: if (!(ans_start = skip_questions(header, plen)))
- Use process_name() instead of extract_name() everywhere in dnssec code return STAT_INSECURE;
- 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); for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
digestalg_add_data(sigrdata, 18+signer_name_rdlen);
for (i = 0; i < rrsetidx; ++i)
{ {
p = (unsigned char*)(rrset[i]); if (!extract_name(header, plen, &p1, name, 1, 10))
return STAT_INSECURE; /* bad packet */
digestalg_add_data(owner_wire, owner_wire_len);
digestalg_add_data(&sigtype, 2);
digestalg_add_data(&sigclass, 2);
digestalg_add_data(&sigttl, 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);
/* 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;
out->alg = alg;
out->keytag = keytag;
out->signer_name = signer_name;
return 1;
}
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);
}
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, /* Don't try and validate RRSIGs! */
unsigned char *reply, int count, char *owner, if (type1 != T_RRSIG)
int sigclass, int sigrdlen, unsigned char *sig) {
{ /* Check if we've done this RRset already */
PendingRRSIGValidation val; for (p2 = ans_start, j = 0; j < i; j++)
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
/* Initiate the RRSIG validation process. The pending state is returned into val. */ GETSHORT(type2, p2);
if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val)) GETSHORT(class2, p2);
return; p2 += 4; /* TTL */
GETSHORT(rdlen2, p2);
printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag); if (type2 == type1 && class2 == class1 && rc == 1)
break; /* Done it before: name, type, class all match. */
/* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */ if (!ADD_RDLEN(header, p2, plen, rdlen2))
char onekey = 0; return STAT_INSECURE;
struct crec *crecp = NULL; }
while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */
{
onekey = 1;
if (crecp->addr.key.keytag == val.keytag /* Not done, validate now */
&& crecp->addr.key.algo == verifyalg_algonum(val.alg)) if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
{ {
printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag); *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 (!onekey) if (!ADD_RDLEN(header, p1, plen, rdlen1))
{ return STAT_INSECURE;
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 */ /* 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) if (alg == 1)
{ {
/* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm. /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
See RFC4034, Appendix B.1 */ See RFC4034, Appendix B.1 */
return rdata[rdlen-3] * 256 + rdata[rdlen-2]; return key[keylen-4] * 256 + key[keylen-3];
} }
else else
{ {
unsigned long ac; unsigned long ac;
int i; int i;
ac = 0; ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg;
for (i = 0; i < rdlen; ++i) for (i = 0; i < keylen; ++i)
ac += (i & 1) ? rdata[i] : rdata[i] << 8; ac += (i & 1) ? key[i] : key[i] << 8;
ac += (ac >> 16) & 0xFFFF; ac += (ac >> 16) & 0xffff;
return ac & 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, #endif /* HAVE_DNSSEC */
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;
}
/* 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;
}
...@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, ...@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID)) 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 #endif
while (1) while (1)
...@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server ...@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE(header, NOERROR); 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); my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1; munged = 1;
...@@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now) ...@@ -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)) if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
{ {
int status; 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) 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) else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else 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 /* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */ answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_KEY) if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{ {
struct frec *new; struct frec *new;
if ((forward->stash = blockdata_alloc((char *)header, n)))
{
forward->stash_len = n;
if ((new = get_new_frec(now, NULL, 1))) if ((new = get_new_frec(now, NULL, 1)))
{
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 ((forward->stash = blockdata_alloc((char *)header, n)))
{ {
int fd; int fd;
new = forward; /* copy everything, then overwrite */ forward->stash_len = n;
new->dependent = forward; /* to find query awaiting new one. */ new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */ 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) if (status == STAT_NEED_KEY)
{ {
new->flags |= FREC_DNSKEY_QUERY; 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) else if (status == STAT_NEED_DS)
{ {
new->flags |= FREC_DS_QUERY; 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->crc = questions_crc(header, nn, daemon->namebuff);
new->new_id = get_id(new->crc); new->new_id = get_id(new->crc);
...@@ -740,8 +753,10 @@ void reply_query(int fd, int family, time_t now) ...@@ -740,8 +753,10 @@ void reply_query(int fd, int family, time_t now)
/* Send DNSSEC query to same server as original query */ /* 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; return;
} }
...@@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now) ...@@ -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. 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, Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
return it to the original requestor. */ return it to the original requestor. */
if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
{
while (forward->dependent) while (forward->dependent)
{ {
struct frec *prev = forward->dependent; struct frec *prev;
free_frec(forward);
forward = prev;
blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len;
if (status == STAT_SECURE) if (status == STAT_SECURE)
{ {
if (forward->flags & FREC_DNSKEY_QUERY) 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) else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_ds(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"));
} }
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 /* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */ answer, provided last DNSKEY is OK. */
if (status == STAT_SECURE) if (status == STAT_SECURE)
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);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
status = STAT_INSECURE;
}
}
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) if (status == STAT_SECURE)
cache_secure = 1; cache_secure = 1;
/* TODO return SERVFAIL here */ /* TODO return SERVFAIL here */
else if (status == STAT_BOGUS) else if (status == STAT_BOGUS)
no_cache_dnssec = 1; 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 #endif
...@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family) ...@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family)
return NULL; /* doom */ return NULL; /* doom */
} }
static void free_frec(struct frec *f) static void free_frec(struct frec *f)
{ {
if (f->rfd4 && --(f->rfd4->refcount) == 0) if (f->rfd4 && --(f->rfd4->refcount) == 0)
...@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f) ...@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f)
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (f->stash) if (f->stash)
{
blockdata_free(f->stash); blockdata_free(f->stash);
f->stash = NULL;
}
/* Anything we're waiting on is pointless now, too */ /* Anything we're waiting on is pointless now, too */
if (f->blocking_query) if (f->blocking_query)
......
...@@ -139,6 +139,7 @@ struct myoption { ...@@ -139,6 +139,7 @@ struct myoption {
#define LOPT_QUIET_DHCP6 327 #define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328 #define LOPT_QUIET_RA 328
#define LOPT_SEC_VALID 329 #define LOPT_SEC_VALID 329
#define LOPT_DNSKEY 330
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
...@@ -276,6 +277,7 @@ static const struct myoption opts[] = ...@@ -276,6 +277,7 @@ static const struct myoption opts[] =
{ "ipset", 1, 0, LOPT_IPSET }, { "ipset", 1, 0, LOPT_IPSET },
{ "synth-domain", 1, 0, LOPT_SYNTH }, { "synth-domain", 1, 0, LOPT_SYNTH },
{ "dnssec", 0, 0, LOPT_SEC_VALID }, { "dnssec", 0, 0, LOPT_SEC_VALID },
{ "dnskey", 1, 0, LOPT_DNSKEY },
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif #endif
...@@ -428,6 +430,7 @@ static struct { ...@@ -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 }, { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), 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 },
#endif #endif
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL }, { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
...@@ -3671,8 +3674,33 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ...@@ -3671,8 +3674,33 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
break; 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: 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 ...@@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return plen; /* Too big */ return plen; /* Too big */
} }
if (optno != 0)
{
PUTSHORT(optno, p); PUTSHORT(optno, p);
PUTSHORT(optlen, p); PUTSHORT(optlen, p);
memcpy(p, opt, optlen); memcpy(p, opt, optlen);
p += optlen; p += optlen;
}
PUTSHORT(p - datap, lenp); PUTSHORT(p - datap, lenp);
return p - (unsigned char *)header; return p - (unsigned char *)header;
...@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) ...@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way. expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */ 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, 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; unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
...@@ -920,6 +923,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t ...@@ -920,6 +923,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
unsigned long cttl = ULONG_MAX, attl; unsigned long cttl = ULONG_MAX, attl;
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
if (secure)
flags |= F_DNSSECOK;
namep = p; namep = p;
if (!extract_name(header, qlen, &p, name, 1, 4)) if (!extract_name(header, qlen, &p, name, 1, 4))
return 0; /* bad packet */ return 0; /* bad packet */
...@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
int dryrun = 0, sec_reqd = 0; int dryrun = 0, sec_reqd = 0;
int is_sign; int is_sign;
struct crec *crecp; 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; struct mx_srv_record *rec;
/* If there is an RFC2671 pseudoheader then it will be overwritten by /* 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, ...@@ -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))) if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
continue; continue;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
if (crecp->flags & F_NEG) if (crecp->flags & F_NEG)
{ {
ans = 1; ans = 1;
...@@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -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))) if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
break; break;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
if (crecp->flags & F_CNAME) if (crecp->flags & F_CNAME)
{ {
char *cname_target = cache_get_cname_target(crecp); 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, ...@@ -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)) && if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
(qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))) (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))))
{ {
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
ans = 1; ans = 1;
if (!dryrun) if (!dryrun)
{ {
...@@ -2047,6 +2065,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -2047,6 +2065,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (trunc) if (trunc)
header->hb3 |= HB3_TC; 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) if (nxdomain)
SET_RCODE(header, NXDOMAIN); SET_RCODE(header, NXDOMAIN);
else else
......
...@@ -109,8 +109,8 @@ static int check_name(char *in) ...@@ -109,8 +109,8 @@ static int check_name(char *in)
if (in[l-1] == '.') if (in[l-1] == '.')
{ {
if (l == 1) return 0;
in[l-1] = 0; in[l-1] = 0;
nowhite = 1;
} }
for (; (c = *in); in++) for (; (c = *in); in++)
...@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen, ...@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
return i; 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 */ /* return 0 for no match, or (no matched octets) + 1 */
int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask) 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