Merge branch 'master' into dnssec
Conflicts:
src/dnsmasq.h
src/forward.c
src/option.c
diff --git a/Makefile b/Makefile
index be809d5..61c22c9 100644
--- a/Makefile
+++ b/Makefile
@@ -59,13 +59,16 @@
ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack`
lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.1`
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.1`
+ssl_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_OPENSSL $(PKG_CONFIG) --cflags openssl`
+ssl_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_OPENSSL $(PKG_CONFIG) --libs openssl`
sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
objs = cache.o rfc1035.o util.o option.o forward.o network.o \
dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
- dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o domain.o
+ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
+ domain.o dnssec.o dnssec-openssl.o
hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
dns-protocol.h radv-protocol.h
@@ -73,8 +76,8 @@
all : $(BUILDDIR)
@cd $(BUILDDIR) && $(MAKE) \
top="$(top)" \
- build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags)" \
- build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs)" \
+ build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(ssl_cflags)" \
+ build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(ssl_libs)" \
-f $(top)/Makefile dnsmasq
mostly_clean :
diff --git a/bld/Android.mk b/bld/Android.mk
index 46e4d03..5eb4749 100644
--- a/bld/Android.mk
+++ b/bld/Android.mk
@@ -8,7 +8,8 @@
netlink.c network.c option.c rfc1035.c \
rfc2131.c tftp.c util.c conntrack.c \
dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
- radv.c slaac.c auth.c ipset.c domain.c
+ radv.c slaac.c auth.c ipset.c domain.c \
+ dnssec.c dnssec-openssl.c
LOCAL_MODULE := dnsmasq
diff --git a/src/cache.c b/src/cache.c
index d99aba6..fd0e02a 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1216,7 +1216,7 @@
else if (cache->flags & F_DS)
{
a = daemon->addrbuff;
- sprintf(a, "%5u %3u %3u %u", cache->addr.key.flags_or_keyid,
+ sprintf(a, "%5u %3u %3u %u", cache->addr.key.keytag,
cache->addr.key.algo, cache->addr.key.digest, cache->uid);
}
#endif
@@ -1365,6 +1365,8 @@
{
struct keydata *block, *ret = NULL;
struct keydata **prev = &ret;
+ size_t blen;
+
while (len > 0)
{
if (keyblock_free)
@@ -1382,9 +1384,10 @@
return NULL;
}
- memcpy(block->key, data, len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len);
- data += KEYBLOCK_LEN;
- len -= KEYBLOCK_LEN;
+ blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+ memcpy(block->key, data, blen);
+ data += blen;
+ len -= blen;
*prev = block;
prev = &block->next;
block->next = NULL;
@@ -1393,6 +1396,21 @@
return ret;
}
+size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt)
+{
+ if (*p == NULL)
+ *p = (*key)->key;
+ else if (*p == (*key)->key + KEYBLOCK_LEN)
+ {
+ *key = (*key)->next;
+ if (*key == NULL)
+ return 0;
+ *p = (*key)->key;
+ }
+
+ return MIN(cnt, (*key)->key + KEYBLOCK_LEN - (*p));
+}
+
void keydata_free(struct keydata *blocks)
{
struct keydata *tmp;
diff --git a/src/config.h b/src/config.h
index 0edc4af..4bfa0b7 100644
--- a/src/config.h
+++ b/src/config.h
@@ -139,7 +139,8 @@
/* #define HAVE_DBUS */
/* #define HAVE_IDN */
/* #define HAVE_CONNTRACK */
-
+#define HAVE_DNSSEC
+#define HAVE_OPENSSL
/* Default locations for important system files. */
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 2f144a8..023be5f 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -50,6 +50,10 @@
#define T_SRV 33
#define T_NAPTR 35
#define T_OPT 41
+#define T_DS 43
+#define T_RRSIG 46
+#define T_NSEC 47
+#define T_DNSKEY 48
#define T_TKEY 249
#define T_TSIG 250
#define T_AXFR 252
@@ -117,3 +121,20 @@
(cp) += 4; \
}
+#define CHECKED_GETCHAR(var, ptr, len) do { \
+ if ((len) < 1) return 0; \
+ var = *ptr++; \
+ (len) -= 1; \
+ } while (0)
+
+#define CHECKED_GETSHORT(var, ptr, len) do { \
+ if ((len) < 2) return 0; \
+ GETSHORT(var, ptr); \
+ (len) -= 2; \
+ } while (0)
+
+#define CHECKED_GETLONG(var, ptr, len) do { \
+ if ((len) < 4) return 0; \
+ GETLONG(var, ptr); \
+ (len) -= 4; \
+ } while (0)
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index a2b37dc..964c81f 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -81,9 +81,10 @@
umask(022); /* known umask, create leases and pid files as 0644 */
read_opts(argc, argv, compile_opts);
-
+ if (option_bool(OPT_DNSSEC_VALIDATE))
+ if (daemon->doctors) exit(1); /* TODO */
if (daemon->edns_pktsz < PACKETSZ)
- daemon->edns_pktsz = PACKETSZ;
+ daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALIDATE) ? EDNS_PKTSZ : PACKETSZ;
daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ?
daemon->edns_pktsz : DNSMASQ_PACKETSZ;
daemon->packet = safe_malloc(daemon->packet_buff_sz);
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 4d368c5..90173ac 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -56,6 +56,9 @@
typedef unsigned int u32;
typedef unsigned long long u64;
+#define countof(x) (long)(sizeof(x) / sizeof(x[0]))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
#include "dns-protocol.h"
#include "dhcp-protocol.h"
#ifdef HAVE_DHCP6
@@ -213,7 +216,7 @@
#define OPT_NO_OVERRIDE 30
#define OPT_NO_REBIND 31
#define OPT_ADD_MAC 32
-#define OPT_DNSSEC 33
+#define OPT_DNSSEC_PROXY 33
#define OPT_CONSEC_ADDR 34
#define OPT_CONNTRACK 35
#define OPT_FQDN_UPDATE 36
@@ -225,7 +228,8 @@
#define OPT_QUIET_DHCP 42
#define OPT_QUIET_DHCP6 43
#define OPT_QUIET_RA 44
-#define OPT_LAST 45
+#define OPT_DNSSEC_VALIDATE 45
+#define OPT_LAST 46
/* extra flags for my_syslog, we use a couple of facilities since they are known
not to occupy the same bits as priorities, no matter how syslog.h is set up. */
@@ -347,7 +351,7 @@
struct keydata *keydata;
unsigned char algo;
unsigned char digest; /* DS only */
- unsigned short flags_or_keyid; /* flags for DNSKEY, keyid for DS */
+ unsigned short keytag;
} key;
} addr;
time_t ttd; /* time to die */
@@ -948,6 +952,7 @@
struct crec *cache_enumerate(int init);
#ifdef HAVE_DNSSEC
struct keydata *keydata_alloc(char *data, size_t len);
+size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt);
void keydata_free(struct keydata *blocks);
#endif
@@ -960,6 +965,10 @@
int is_rev_synth(int flag, struct all_addr *addr, char *name);
/* rfc1035.c */
+int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
+ char *name, int isExtract, int extrabytes);
+unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
+unsigned char *skip_questions(struct dns_header *header, size_t plen);
unsigned int extract_request(struct dns_header *header, size_t qlen,
char *name, unsigned short *typep);
size_t setup_reply(struct dns_header *header, size_t qlen,
@@ -997,6 +1006,9 @@
int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
+/* dnssec.c */
+int dnssec_validate(struct dns_header *header, size_t plen);
+
/* util.c */
void rand_init(void);
unsigned short rand16(void);
diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h
new file mode 100644
index 0000000..1717db6
--- /dev/null
+++ b/src/dnssec-crypto.h
@@ -0,0 +1,83 @@
+/* dnssec-crypto.h is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
+
+ 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
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DNSSEC_CRYPTO_H
+#define DNSSEC_CRYPTO_H
+
+struct keydata;
+
+/*
+ * vtable for a signature verification algorithm.
+ *
+ * Each algorithm verifies that a certain signature over a (possibly non-contigous)
+ * array of data has been made with the specified key.
+ *
+ * Sample of usage:
+ *
+ * // First, set the signature we need to check. Notice: data is not copied
+ * // nor consumed, so the pointer must stay valid.
+ * alg->set_signature(sig, 16);
+ *
+ * // Second, get push the data through the corresponding digest algorithm;
+ * // data is consumed immediately, so the buffers can be freed or modified.
+ * digestalg_begin(alg->get_digestalgo());
+ * digestalg_add_data(buf1, 123);
+ * digestalg_add_data(buf2, 45);
+ * digestalg_add_data(buf3, 678);
+ * alg->set_digest(digestalg_final());
+ *
+ * // Third, verify if we got the correct key for this signature.
+ * alg->verify(key1, 16);
+ * alg->verify(key2, 16);
+ */
+
+typedef struct VerifyAlgCtx VerifyAlgCtx;
+
+typedef struct
+{
+ int digest_algo;
+ int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len);
+} VerifyAlg;
+
+struct VerifyAlgCtx
+{
+ const VerifyAlg *vtbl;
+ unsigned char *sig;
+ size_t siglen;
+ unsigned char digest[64]; /* TODO: if memory problems, use VLA */
+};
+
+int verifyalg_supported(int algo);
+VerifyAlgCtx* verifyalg_alloc(int algo);
+void verifyalg_free(VerifyAlgCtx *a);
+int verifyalg_algonum(VerifyAlgCtx *a);
+
+/* Functions to calculate the digest of a key */
+
+/* RFC4034 digest algorithms */
+#define DIGESTALG_SHA1 1
+#define DIGESTALG_SHA256 2
+#define DIGESTALG_MD5 256
+#define DIGESTALG_SHA512 257
+
+int digestalg_supported(int algo);
+int digestalg_begin(int algo);
+void digestalg_add_data(void *data, unsigned len);
+void digestalg_add_keydata(struct keydata *key, size_t len);
+unsigned char *digestalg_final(void);
+int digestalg_len(void);
+
+#endif /* DNSSEC_CRYPTO_H */
diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c
new file mode 100644
index 0000000..4bf7e73
--- /dev/null
+++ b/src/dnssec-openssl.c
@@ -0,0 +1,318 @@
+/* dnssec-openssl.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
+
+ 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
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include "dnsmasq.h"
+#include "dnssec-crypto.h"
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/dsa.h>
+#include <openssl/err.h>
+
+#define POOL_SIZE 1
+static union _Pool
+{
+ VerifyAlgCtx ctx;
+} Pool[POOL_SIZE];
+static char pool_used = 0;
+
+static void print_hex(unsigned char *data, unsigned len)
+{
+ while (len > 0)
+ {
+ printf("%02x", *data++);
+ --len;
+ }
+ printf("\n");
+}
+
+static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len)
+{
+ size_t cnt;
+ BIGNUM temp;
+
+ BN_init(ret);
+
+ cnt = keydata_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)))
+ return 0;
+ BN_lshift(ret, ret, cnt*8);
+ BN_init(&temp);
+ BN_bin2bn(*p, cnt, &temp);
+ BN_add(ret, ret, &temp);
+ len -= cnt;
+ *p += cnt;
+ }
+ return 1;
+}
+
+static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, unsigned key_len)
+{
+ unsigned char *p = key_data->key;
+ size_t exp_len, mod_len;
+
+ CHECKED_GETCHAR(exp_len, p, key_len);
+ if (exp_len == 0)
+ CHECKED_GETSHORT(exp_len, p, key_len);
+ if (exp_len >= key_len)
+ return 0;
+ mod_len = key_len - exp_len;
+
+ return keydata_to_bn(exp, &key_data, &p, exp_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)
+{
+ unsigned char *p = key_data->key;
+ int T;
+
+ CHECKED_GETCHAR(T, p, key_len);
+ return
+ keydata_to_bn(Q, &key_data, &p, 20) &&
+ keydata_to_bn(P, &key_data, &p, 64+T*8) &&
+ keydata_to_bn(G, &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)
+{
+ int validated = 0;
+
+ RSA *rsa = RSA_new();
+ rsa->e = BN_new();
+ rsa->n = BN_new();
+ if (rsasha1_parse_key(rsa->e, rsa->n, key_data, key_len)
+ && RSA_verify(nid, ctx->digest, dlen, ctx->sig, ctx->siglen, rsa))
+ validated = 1;
+
+ RSA_free(rsa);
+ return validated;
+}
+
+static int rsamd5_verify(VerifyAlgCtx *ctx, struct keydata *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)
+{
+ return rsa_verify(ctx, key_data, key_len, NID_sha1, 20);
+}
+
+static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *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)
+{
+ 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 unsigned char asn1_signature[] =
+ {
+ 0x30, 0x2E, // sequence
+ 0x02, 21, // large integer (21 bytes)
+ 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // R
+ 0x02, 21, // large integer (21 bytes)
+ 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // S
+ };
+ int validated = 0;
+
+ /* A DSA signature is made of 2 bignums (R & S). We could parse them manually with BN_bin2bn(),
+ but OpenSSL does not have an API to verify a DSA signature given R and S, and insists
+ in having a ASN.1 BER sequence (as per RFC3279).
+ We prepare a hard-coded ASN.1 sequence, and just fill in the R&S numbers in it. */
+ memcpy(asn1_signature+5, ctx->sig+1, 20);
+ memcpy(asn1_signature+28, ctx->sig+21, 20);
+
+ DSA *dsa = DSA_new();
+ dsa->q = BN_new();
+ dsa->p = BN_new();
+ dsa->g = BN_new();
+ dsa->pub_key = BN_new();
+
+ if (dsasha1_parse_key(dsa->q, dsa->p, dsa->g, dsa->pub_key, key_data, key_len)
+ && DSA_verify(0, ctx->digest, 20, asn1_signature, countof(asn1_signature), dsa) > 0)
+ validated = 1;
+
+ DSA_free(dsa);
+ return validated;
+}
+
+#define VALG_UNSUPPORTED() { \
+ 0,0 \
+ } /**/
+
+#define VALG_VTABLE(alg, digest) { \
+ digest, \
+ alg ## _verify \
+ } /**/
+
+/* Updated registry that merges various RFCs:
+ https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */
+static const VerifyAlg valgs[] =
+{
+ VALG_UNSUPPORTED(), /* 0: reserved */
+ VALG_VTABLE(rsamd5, DIGESTALG_MD5), /* 1: RSAMD5 */
+ VALG_UNSUPPORTED(), /* 2: DH */
+ VALG_VTABLE(dsasha1, DIGESTALG_SHA1), /* 3: DSA */
+ VALG_UNSUPPORTED(), /* 4: ECC */
+ VALG_VTABLE(rsasha1, DIGESTALG_SHA1), /* 5: RSASHA1 */
+ VALG_VTABLE(dsasha1, DIGESTALG_SHA1), /* 6: DSA-NSEC3-SHA1 */
+ VALG_VTABLE(rsasha1, DIGESTALG_SHA1), /* 7: RSASHA1-NSEC3-SHA1 */
+ VALG_VTABLE(rsasha256, DIGESTALG_SHA256), /* 8: RSASHA256 */
+ VALG_UNSUPPORTED(), /* 9: unassigned */
+ VALG_VTABLE(rsasha512, DIGESTALG_SHA512), /* 10: RSASHA512 */
+ VALG_UNSUPPORTED(), /* 11: unassigned */
+ VALG_UNSUPPORTED(), /* 12: ECC-GOST */
+ VALG_UNSUPPORTED(), /* 13: ECDSAP256SHA256 */
+ VALG_UNSUPPORTED(), /* 14: ECDSAP384SHA384 */
+};
+
+/* TODO: remove if we don't need this anymore
+ (to be rechecked if we ever remove OpenSSL) */
+static const int valgctx_size[] =
+{
+ 0, /* 0: reserved */
+ sizeof(VerifyAlgCtx), /* 1: RSAMD5 */
+ 0, /* 2: DH */
+ sizeof(VerifyAlgCtx), /* 3: DSA */
+ 0, /* 4: ECC */
+ sizeof(VerifyAlgCtx), /* 5: RSASHA1 */
+ sizeof(VerifyAlgCtx), /* 6: DSA-NSEC3-SHA1 */
+ sizeof(VerifyAlgCtx), /* 7: RSASHA1-NSEC3-SHA1 */
+ sizeof(VerifyAlgCtx), /* 8: RSASHA256 */
+ 0, /* 9: unassigned */
+ sizeof(VerifyAlgCtx), /* 10: RSASHA512 */
+ 0, /* 11: unassigned */
+ 0, /* 12: ECC-GOST */
+ 0, /* 13: ECDSAP256SHA256 */
+ 0, /* 14: ECDSAP384SHA384 */
+};
+
+int verifyalg_supported(int algo)
+{
+ return (algo < countof(valgctx_size) && valgctx_size[algo] != 0);
+}
+
+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
+ for (i = 0; i < POOL_SIZE; ++i)
+ if (!(pool_used & (1 << i)))
+ {
+ ret = (VerifyAlgCtx*)&Pool[i];
+ pool_used |= 1 << i;
+ break;
+ }
+
+ if (ret)
+ ret->vtbl = &valgs[algo];
+ return ret;
+}
+
+void verifyalg_free(VerifyAlgCtx *a)
+{
+ int pool_idx = ((char*)a - (char*)&Pool[0]) / sizeof(Pool[0]);
+ if (pool_idx < 0 || pool_idx >= POOL_SIZE)
+ {
+ free(a);
+ return;
+ }
+
+ pool_used &= ~(1 << pool_idx);
+}
+
+int verifyalg_algonum(VerifyAlgCtx *a)
+{
+ int num = a->vtbl - valgs;
+ if (num < 0 || num >= countof(valgs))
+ return -1;
+ return num;
+}
+
+static EVP_MD_CTX digctx;
+
+int digestalg_supported(int algo)
+{
+ return (algo == DIGESTALG_SHA1 ||
+ algo == DIGESTALG_SHA256 ||
+ algo == DIGESTALG_MD5 ||
+ algo == DIGESTALG_SHA512);
+}
+
+int digestalg_begin(int algo)
+{
+ EVP_MD_CTX_init(&digctx);
+ if (algo == DIGESTALG_SHA1)
+ EVP_DigestInit_ex(&digctx, EVP_sha1(), NULL);
+ else if (algo == DIGESTALG_SHA256)
+ EVP_DigestInit_ex(&digctx, EVP_sha256(), NULL);
+ else if (algo == DIGESTALG_SHA512)
+ 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()
+{
+ return EVP_MD_CTX_size(&digctx);
+}
+
+void digestalg_add_data(void *data, unsigned len)
+{
+ EVP_DigestUpdate(&digctx, data, len);
+}
+
+void digestalg_add_keydata(struct keydata *key, size_t len)
+{
+ size_t cnt; unsigned char *p = NULL;
+ while (len)
+ {
+ cnt = keydata_walk(&key, &p, len);
+ EVP_DigestUpdate(&digctx, p, cnt);
+ p += cnt;
+ len -= cnt;
+ }
+}
+
+unsigned char* digestalg_final(void)
+{
+ static unsigned char digest[32];
+ EVP_DigestFinal(&digctx, digest, NULL);
+ return digest;
+}
+
diff --git a/src/dnssec.c b/src/dnssec.c
new file mode 100644
index 0000000..222be3f
--- /dev/null
+++ b/src/dnssec.c
@@ -0,0 +1,870 @@
+/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
+
+ 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
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+#include "dnssec-crypto.h"
+#include <assert.h>
+
+/* Maximum length in octects of a domain name, in wire format */
+#define MAXCDNAME 256
+
+#define SERIAL_UNDEF -100
+#define SERIAL_EQ 0
+#define SERIAL_LT -1
+#define SERIAL_GT 1
+
+/* Implement RFC1982 wrapped compare for 32-bit numbers */
+static int serial_compare_32(unsigned long s1, unsigned long s2)
+{
+ if (s1 == s2)
+ return SERIAL_EQ;
+
+ if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
+ (s1 > s2 && (s1 - s2) > (1UL<<31)))
+ return SERIAL_LT;
+ if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
+ (s1 > s2 && (s1 - s2) < (1UL<<31)))
+ return SERIAL_GT;
+ 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.
+ *
+ * Handling domain names in wire format can be done with buffers as large as MAXCDNAME (256),
+ * while the representation format (as created by, eg., extract_name) requires MAXDNAME (1024).
+ *
+ * With "canonicalized wire format", we mean the standard DNS wire format, eg:
+ *
+ * <3>www<7>example<3>org<0>
+ *
+ * with all ÅSCII letters converted to lowercase, and no wire-level compression.
+ *
+ * The function works with two different buffers:
+ * - Input buffer: 'rdata' is a pointer to the actual wire data, and 'rdlen' is
+ * the total length till the end of the rdata or DNS packet section. Both
+ * variables are updated after processing the domain name, so that rdata points
+ * after it, and rdlen is decreased by the amount of the processed octects.
+ * - Output buffer: 'out' points to it. In some cases, this buffer can be prefilled
+ * and used as additional input (see below).
+ *
+ * The argument "action" decides what to do with the submitted domain name:
+ *
+ * PDN_EXTRACT:
+ * Extract the domain name from input buffer into the output buffer, possibly uncompressing it.
+ * Return the length of the domain name in the output buffer in octects, or zero if error.
+ *
+ * PDN_COMPARE:
+ * Compare the domain name in the input buffer and the one in the output buffer (ignoring
+ * differences in compression). Returns 0 in case of error, a positive number
+ * if they are equal, or a negative number if they are different. This function always
+ * consumes the whole name in the input buffer (there is no early exit).
+ *
+ * PDN_ORDER:
+ * Order between the domain name in the input buffer and the domain name in the output buffer.
+ * Returns 0 if the names are equal, 1 if input > output, or -1 if input < output. This
+ * function early-exits when it finds a difference, so rdata might not be fully updated.
+ *
+ * Notice: because of compression, rdata/rdlen might be updated with a different quantity than
+ * the returned number of octects. For instance, if we extract a compressed domain name, rdata/rdlen
+ * might be updated only by 2 bytes (that is, rdata is incresed by 2, and rdlen decreased by 2),
+ * because it then reuses existing data elsewhere in the DNS packet, while the return value might be
+ * larger, reflecting the total number of octects composing the domain name.
+ *
+ */
+#define PWN_EXTRACT 0
+#define PWN_COMPARE 1
+#define PWN_ORDER 2
+static int process_domain_name(struct dns_header *header, size_t pktlen,
+ unsigned char** rdata, size_t* rdlen,
+ unsigned char *out, int action)
+{
+ int hops = 0, total = 0, i;
+ unsigned char label_type;
+ unsigned char *end = (unsigned char *)header + pktlen;
+ unsigned char count; unsigned char *p = *rdata;
+ int nonequal = 0;
+
+#define PROCESS(ch) \
+ do { \
+ if (action == PWN_EXTRACT) \
+ *out++ = ch; \
+ else if (action == PWN_COMPARE) \
+ { \
+ if (*out++ != ch) \
+ nonequal = 1; \
+ } \
+ else if (action == PWN_ORDER) \
+ { \
+ char _ch = *out++; \
+ if (ch < _ch) \
+ return -1; \
+ else if (_ch > ch) \
+ return 1; \
+ } \
+ } while (0)
+
+ while (1)
+ {
+ if (p >= end)
+ return 0;
+ if (!(count = *p++))
+ break;
+ label_type = count & 0xC0;
+ if (label_type == 0xC0)
+ {
+ int l2;
+ if (p >= end)
+ return 0;
+ l2 = *p++;
+ if (hops == 0)
+ {
+ if (p - *rdata > *rdlen)
+ return 0;
+ *rdlen -= p - *rdata;
+ *rdata = p;
+ }
+ if (++hops == 256)
+ return 0;
+ p = (unsigned char*)header + (count & 0x3F) * 256 + l2;
+ }
+ else if (label_type == 0x00)
+ {
+ if (p+count-1 >= end)
+ return 0;
+ total += count+1;
+ if (total >= MAXCDNAME)
+ return 0;
+ PROCESS(count);
+ for (i = 0; i < count; ++i)
+ {
+ unsigned char ch = *p++;
+ if (ch >= 'A' && ch <= 'Z')
+ ch += 'a' - 'A';
+ PROCESS(ch);
+ }
+ }
+ else
+ return 0; /* unsupported label_type */
+ }
+
+ if (hops == 0)
+ {
+ if (p - *rdata > *rdlen)
+ return 0;
+ *rdlen -= p - *rdata;
+ *rdata = p;
+ }
+ ++total;
+ if (total >= MAXCDNAME)
+ return 0;
+ PROCESS(0);
+
+ /* If we arrived here without early-exit, they're equal */
+ if (action == PWN_ORDER)
+ return 0;
+ return nonequal ? -total : total;
+
+ #undef PROCESS
+}
+
+
+/* RDATA meta-description.
+ *
+ * RFC4034 §6.2 introduces the concept of a "canonical form of a RR". This canonical
+ * form is used in two important points within the DNSSEC protocol/algorithm:
+ *
+ * 1) When computing the hash for verifying the RRSIG signature, we need to do it on
+ * the canonical form.
+ * 2) When ordering a RRset in canonical order (§6.3), we need to lexicographically sort
+ * the RRs in canonical form.
+ *
+ * The canonical form of a RR is specifically tricky because it also affects the RDATA,
+ * which is different for each RR type; in fact, RFC4034 says that "domain names in
+ * RDATA must be canonicalized" (= uncompressed and lower-cased).
+ *
+ * To handle this correctly, we then need a way to describe how the RDATA section is
+ * composed for each RR type; we don't need to describe every field, but just to specify
+ * where domain names are. The following array contains this description, and it is
+ * used by rrset_canonical_order() and verifyalg_add_rdata(), to adjust their behaviour
+ * for each RR type.
+ *
+ * The format of the description is very easy, for instance:
+ *
+ * { 12, RDESC_DOMAIN, RDESC_DOMAIN, 4, RDESC_DOMAIN, RDESC_END }
+ *
+ * This means that this (ficticious) RR type has a RDATA section containing 12 octects
+ * (we don't care what they contain), followed by 2 domain names, followed by 4 octects,
+ * followed by 1 domain name, and then followed by an unspecificied number of octects (0
+ * or more).
+ */
+
+#define RDESC_DOMAIN -1
+#define RDESC_END 0
+static const int rdata_description[][8] =
+{
+ /**/ { RDESC_END },
+ /* 1: A */ { RDESC_END },
+ /* 2: NS */ { RDESC_DOMAIN, RDESC_END },
+ /* 3: .. */ { RDESC_END },
+ /* 4: .. */ { RDESC_END },
+ /* 5: CNAME */ { RDESC_DOMAIN, RDESC_END },
+ /* 6: SOA */ { RDESC_DOMAIN, RDESC_DOMAIN, RDESC_END },
+ /* 7: */ { RDESC_END },
+ /* 8: */ { RDESC_END },
+ /* 9: */ { RDESC_END },
+ /* 10: */ { RDESC_END },
+ /* 11: */ { RDESC_END },
+ /* 12: */ { RDESC_END },
+ /* 13: */ { RDESC_END },
+ /* 14: */ { RDESC_END },
+ /* 15: MX */ { 2, RDESC_DOMAIN, RDESC_END },
+};
+
+
+/* On-the-fly rdata canonicalization
+ *
+ * This set of functions allow the user to iterate over the rdata section of a RR
+ * while canonicalizing it on-the-fly. This is a great memory saving since the user
+ * doesn't need to allocate memory for a copy of the whole rdata section.
+ *
+ * Sample usage:
+ *
+ * RDataCFrom cf;
+ * rdata_cfrom_init(
+ * &cf,
+ * header, pktlen, // dns_header
+ * rdata, // pointer to rdata section
+ * rrtype, // RR tyep
+ * tmpbuf); // temporary buf (MAXCDNAME)
+ *
+ * while ((p = rdata_cfrom_next(&cf, &len))
+ * {
+ * // Process p[0..len]
+ * }
+ *
+ * if (rdata_cfrom_error(&cf))
+ * // error occurred while parsing
+ *
+ */
+typedef struct
+{
+ struct dns_header *header;
+ size_t pktlen;
+ unsigned char *rdata;
+ unsigned char *tmpbuf;
+ size_t rdlen;
+ int rrtype;
+ int cnt;
+} RDataCForm;
+
+static void rdata_cform_init(RDataCForm *ctx, struct dns_header *header, size_t pktlen,
+ unsigned char *rdata, int rrtype, unsigned char *tmpbuf)
+{
+ if (rrtype >= countof(rdata_description))
+ rrtype = 0;
+ ctx->header = header;
+ ctx->pktlen = pktlen;
+ ctx->rdata = rdata;
+ ctx->rrtype = rrtype;
+ ctx->tmpbuf = tmpbuf;
+ ctx->cnt = -1;
+ GETSHORT(ctx->rdlen, ctx->rdata);
+}
+
+static int rdata_cform_error(RDataCForm *ctx)
+{
+ return ctx->cnt == -2;
+}
+
+static unsigned char *rdata_cform_next(RDataCForm *ctx, size_t *len)
+{
+ if (ctx->cnt != -1 && rdata_description[ctx->rrtype][ctx->cnt] == RDESC_END)
+ return NULL;
+
+ int d = rdata_description[ctx->rrtype][++ctx->cnt];
+ if (d == RDESC_DOMAIN)
+ {
+ *len = process_domain_name(ctx->header, ctx->pktlen, &ctx->rdata, &ctx->rdlen, ctx->tmpbuf, PWN_EXTRACT);
+ if (!*len)
+ {
+ ctx->cnt = -2;
+ return NULL;
+ }
+ return ctx->tmpbuf;
+ }
+ else if (d == RDESC_END)
+ {
+ *len = ctx->rdlen;
+ return ctx->rdata;
+ }
+ else
+ {
+ unsigned char *ret = ctx->rdata;
+ ctx->rdlen -= d;
+ ctx->rdata += d;
+ *len = d;
+ return ret;
+ }
+}
+
+
+/* Check whether today/now is between date_start and date_end */
+static int check_date_range(unsigned long date_start, unsigned long date_end)
+{
+ /* TODO: double-check that time(0) is the correct time we are looking for */
+ /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */
+ unsigned long curtime = time(0);
+
+ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
+ return serial_compare_32(curtime, date_start) == SERIAL_GT
+ && serial_compare_32(curtime, date_end) == SERIAL_LT;
+}
+
+
+/* Sort RRs within a RRset in canonical order, according to RFC4034, §6.3
+ Notice that the RRDATA sections have been already normalized, so a memcpy
+ is sufficient.
+ NOTE: r1/r2 point immediately after the owner name. */
+
+struct {
+ struct dns_header *header;
+ size_t pktlen;
+} rrset_canonical_order_ctx;
+
+static int rrset_canonical_order(const void *r1, const void *r2)
+{
+ size_t r1len, r2len;
+ int rrtype;
+ unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
+ unsigned char tmp1[MAXCDNAME], tmp2[MAXCDNAME]; /* TODO: use part of daemon->namebuff */
+
+ GETSHORT(rrtype, pr1);
+ pr1 += 6; pr2 += 8;
+
+ RDataCForm cf1, cf2;
+ rdata_cform_init(&cf1, rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen,
+ pr1, rrtype, tmp1);
+ rdata_cform_init(&cf2, rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen,
+ pr2, rrtype, tmp2);
+ while ((pr1 = rdata_cform_next(&cf1, &r1len)) &&
+ (pr2 = rdata_cform_next(&cf2, &r2len)))
+ {
+ int res = memcmp(pr1, pr2, MIN(r1len,r2len));
+ if (res != 0)
+ return res;
+ if (r1len < r2len)
+ return -1;
+ if (r2len > r1len)
+ return 1;
+ }
+
+ /* If we reached this point, the two RRs are identical (or an error occurred).
+ RFC2181 says that an RRset is not allowed to contain duplicate
+ records. If it happens, it is a protocol error and anything goes. */
+ return 1;
+}
+
+typedef struct PendingRRSIGValidation
+{
+ VerifyAlgCtx *alg;
+ char *signer_name;
+ 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)
+{
+ while (*str && *str != ch)
+ str++;
+ return str;
+}
+
+/* Convert a domain name to wire format */
+static int convert_domain_to_wire(char *name, unsigned char* out)
+{
+ unsigned char len;
+ unsigned char *start = out;
+ char *p;
+
+ do
+ {
+ 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;
+ }
+ while (*p);
+
+ *out++ = '\0';
+ return out-start;
+}
+
+
+/* Pass a resource record's rdata field through the currently-initailized digest algorithm.
+
+ We must pass the record in DNS wire format, but if the record contains domain names,
+ they must be uncompressed. This makes things very tricky, because */
+static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pktlen,
+ unsigned char *rdata)
+{
+ size_t len;
+ unsigned char *p;
+ unsigned short total;
+ unsigned char tmpbuf[MAXDNAME]; /* TODO: reuse part of daemon->namebuff */
+ RDataCForm cf1, cf2;
+
+ /* Initialize two iterations over the canonical form*/
+ rdata_cform_init(&cf1, header, pktlen, rdata, sigtype, tmpbuf);
+ cf2 = cf1;
+
+ /* Iteration 1: go through the canonical record and count the total octects.
+ This number might be different from the non-canonical rdata length
+ because of domain names compression. */
+ total = 0;
+ while ((p = rdata_cform_next(&cf1, &len)))
+ total += len;
+ if (rdata_cform_error(&cf1))
+ return 0;
+
+ /* Iteration 2: process the canonical record through the hash function */
+ total = htons(total);
+ digestalg_add_data(&total, 2);
+
+ while ((p = rdata_cform_next(&cf2, &len)))
+ digestalg_add_data(p, len);
+
+ return 1;
+}
+
+
+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);
+
+ 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);
+}
+
+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)
+{
+ 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;
+
+ 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) */
+ {
+ 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 (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)
+ {
+ 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. */
+ }
+}
+
+/* Compute keytag (checksum to quickly index a key). See RFC4034 */
+static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen)
+{
+ 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];
+ }
+ 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 keydata *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 = keydata_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
+ {
+ keydata_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 keydata *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 = keydata_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)
+ {
+ keydata_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 dnssec_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;
+}
diff --git a/src/forward.c b/src/forward.c
index 7ed8880..1c66acf 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -497,8 +497,8 @@
/* RFC 4035 sect 4.6 para 3 */
- if (!is_sign && !option_bool(OPT_DNSSEC))
- header->hb4 &= ~HB4_AD;
+ if (!is_sign && !option_bool(OPT_DNSSEC_PROXY))
+ header->hb4 &= ~HB4_AD;
if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN))
return n;
@@ -512,7 +512,12 @@
if (!option_bool(OPT_LOG))
server->flags |= SERV_WARNED_RECURSIVE;
}
-
+
+#ifdef HAVE_DNSSEC
+ printf("validate\n");
+ dnssec_validate(header, n);
+#endif
+
if (daemon->bogus_addr && RCODE(header) != NXDOMAIN &&
check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now))
{
diff --git a/src/option.c b/src/option.c
index 0ba2664..defe014 100644
--- a/src/option.c
+++ b/src/option.c
@@ -138,6 +138,7 @@
#define LOPT_QUIET_DHCP 326
#define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328
+#define LOPT_SEC_VALID 329
#ifdef HAVE_GETOPT_LONG
@@ -274,6 +275,7 @@
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
{ "ipset", 1, 0, LOPT_IPSET },
{ "synth-domain", 1, 0, LOPT_SYNTH },
+ { "dnssec", 0, 0, LOPT_SEC_VALID },
#ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif
@@ -407,7 +409,7 @@
{ LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
{ LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
{ LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add requestor's IP subnet to forwarded DNS queries."), NULL },
- { LOPT_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
+ { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
{ LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
{ LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
{ LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL },
@@ -424,6 +426,9 @@
{ LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
{ LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
+#ifdef HAVE_DNSSEC
+ { LOPT_SEC_VALID, OPT_DNSSEC_VALIDATE, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
+#endif
#ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
#endif
diff --git a/src/rfc1035.c b/src/rfc1035.c
index fc6d09c..932470f 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -274,7 +274,7 @@
return 0;
}
-static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes)
+unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes)
{
while(1)
{