From 86bec2d399df0bc5052de7b1c969a5860c8186d4 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 13 Jan 2014 21:31:20 +0000 Subject: [PATCH 1/3] Swap crypto library from openSSL to nettle. --- Makefile | 28 ++-- src/blockdata.c | 47 ++++--- src/dns-protocol.h | 18 --- src/dnsmasq.h | 3 +- src/dnssec-crypto.h | 83 ------------ src/dnssec-openssl.c | 316 ------------------------------------------- src/dnssec.c | 303 ++++++++++++++++++++++++++++++++++------- src/option.c | 2 +- 8 files changed, 296 insertions(+), 504 deletions(-) delete mode 100644 src/dnssec-crypto.h delete mode 100644 src/dnssec-openssl.c diff --git a/Makefile b/Makefile index 92158e0..fb8e9eb 100644 --- a/Makefile +++ b/Makefile @@ -51,24 +51,24 @@ top!=pwd # GNU make way. top?=$(CURDIR) -dbus_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` -dbus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` -idn_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --cflags libidn` -idn_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --libs libidn` -ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --cflags libnetfilter_conntrack` -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` -sec_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags libcrypto` -sec_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs libcrypto` -sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi` +dbus_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` +dbus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` +idn_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --cflags libidn` +idn_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --libs libidn` +ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --cflags libnetfilter_conntrack` +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` +nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags nettle hogweed` +nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed` +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 dnssec.o dnssec-openssl.o blockdata.o + domain.o dnssec.o blockdata.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h @@ -76,8 +76,8 @@ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ all : $(BUILDDIR) @cd $(BUILDDIR) && $(MAKE) \ top="$(top)" \ - build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(sec_cflags)" \ - build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(sec_libs)" \ + build_cflags="$(version) $(dbus_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags)" \ + build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs)" \ -f $(top)/Makefile dnsmasq mostly_clean : diff --git a/src/blockdata.c b/src/blockdata.c index f9155d9..1da78b7 100644 --- a/src/blockdata.c +++ b/src/blockdata.c @@ -67,21 +67,6 @@ struct blockdata *blockdata_alloc(char *data, size_t len) return ret; } -size_t blockdata_walk(struct blockdata **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, (size_t)((*key)->key + KEYBLOCK_LEN - (*p))); -} - void blockdata_free(struct blockdata *blocks) { struct blockdata *tmp; @@ -96,24 +81,38 @@ void blockdata_free(struct blockdata *blocks) } } -/* copy blocks into data[], return 1 if data[] unchanged by so doing */ -int blockdata_retrieve(struct blockdata *block, size_t len, void *data) +/* if data == NULL, return pointer to static block of sufficient size */ +void *blockdata_retrieve(struct blockdata *block, size_t len, void *data) { size_t blen; struct blockdata *b; - int match = 1; + void *new, *d; - for (b = block; len > 0 && b; b = b->next) + static unsigned int buff_len = 0; + static unsigned char *buff = NULL; + + if (!data) + { + if (len > buff_len) + { + if (!(new = whine_malloc(len))) + return NULL; + if (buff) + free(buff); + buff = new; + } + data = buff; + } + + for (d = data, b = block; len > 0 && b; b = b->next) { blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; - if (memcmp(data, b->key, blen) != 0) - match = 0; - memcpy(data, b->key, blen); - data += blen; + memcpy(d, b->key, blen); + d += blen; len -= blen; } - return match; + return data; } #endif diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 90257ad..2149e72 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -136,24 +136,6 @@ struct dns_header { (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) - #define CHECK_LEN(header, pp, plen, len) \ ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 0082510..4d91d92 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -993,8 +993,7 @@ struct crec *cache_enumerate(int init); #ifdef HAVE_DNSSEC void blockdata_report(void); struct blockdata *blockdata_alloc(char *data, size_t len); -size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt); -int blockdata_retrieve(struct blockdata *block, size_t len, void *data); +void *blockdata_retrieve(struct blockdata *block, size_t len, void *data); void blockdata_free(struct blockdata *blocks); #endif diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h deleted file mode 100644 index 77c5bc5..0000000 --- a/src/dnssec-crypto.h +++ /dev/null @@ -1,83 +0,0 @@ -/* dnssec-crypto.h is Copyright (c) 2012 Giovanni Bajo - - 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 . -*/ - -#ifndef DNSSEC_CRYPTO_H -#define DNSSEC_CRYPTO_H - -struct blockdata; - -/* - * 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 blockdata *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); -void digestalg_begin(int algo); -void digestalg_add_data(void *data, unsigned len); -void digestalg_add_keydata(struct blockdata *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 deleted file mode 100644 index 2e25f82..0000000 --- a/src/dnssec-openssl.c +++ /dev/null @@ -1,316 +0,0 @@ -/* dnssec-openssl.c is Copyright (c) 2012 Giovanni Bajo - - 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 . -*/ - -#include "dnsmasq.h" - -#ifdef HAVE_DNSSEC - -#include "dnssec-crypto.h" -#include -#include -#include -#include -#include - -#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 blockdata **key_data, unsigned char **p, unsigned len) -{ - size_t cnt; - BIGNUM temp; - - BN_init(ret); - - cnt = blockdata_walk(key_data, p, len); - BN_bin2bn(*p, cnt, ret); - len -= cnt; - *p += cnt; - while (len > 0) - { - if (!(cnt = blockdata_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 blockdata *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 blockdata *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 blockdata *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 blockdata *key_data, unsigned key_len) -{ - return rsa_verify(ctx, key_data, key_len, NID_md5, 16); -} - -static int rsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) -{ - return rsa_verify(ctx, key_data, key_len, NID_sha1, 20); -} - -static int rsasha256_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) -{ - return rsa_verify(ctx, key_data, key_len, NID_sha256, 32); -} - -static int rsasha512_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) -{ - return rsa_verify(ctx, key_data, key_len, NID_sha512, 64); -} - -static int dsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *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 (pool_used == (1<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); -} - -void 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); -} - -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 blockdata *key, size_t len) -{ - size_t cnt; unsigned char *p = NULL; - while (len) - { - cnt = blockdata_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; -} - -#endif /* HAVE_DNSSEC */ diff --git a/src/dnssec.c b/src/dnssec.c index 712696f..940115f 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -19,13 +19,210 @@ #ifdef HAVE_DNSSEC -#include "dnssec-crypto.h" +#include +#include +#include +#include #define SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 #define SERIAL_GT 1 +/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +static char *ds_digest_name(int digest) +{ + switch (digest) + { + case 1: return "sha1"; + case 2: return "sha256"; + case 3: return "gosthash94"; + case 4: return "sha384"; + default: return NULL; + } +} + +/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +static char *algo_digest_name(int algo) +{ + switch (algo) + { + case 1: return "md5"; + case 3: return "sha1"; + case 5: return "sha1"; + case 6: return "sha1"; + case 7: return "sha1"; + case 8: return "sha256"; + case 10: return "sha512"; + case 12: return "gosthash94"; + case 13: return "sha256"; + case 14: return "sha384"; + default: return NULL; + } +} + +/* Find pointer to correct hash function in nettle library */ +static const struct nettle_hash *hash_find(char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; nettle_hashes[i]; i++) + { + if (strcmp(nettle_hashes[i]->name, name) == 0) + return nettle_hashes[i]; + } + + return NULL; +} + +/* expand ctx and digest memory allocations if necessary and init hash function */ +static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) +{ + static void *ctx = NULL; + static unsigned char *digest = NULL; + static unsigned int ctx_sz = 0; + static unsigned int digest_sz = 0; + + void *new; + + if (ctx_sz < hash->context_size) + { + if (!(new = whine_malloc(hash->context_size))) + return 0; + if (ctx) + free(ctx); + ctx = new; + ctx_sz = hash->context_size; + } + + if (digest_sz < hash->digest_size) + { + if (!(new = whine_malloc(hash->digest_size))) + return 0; + if (digest) + free(digest); + digest = new; + digest_sz = hash->digest_size; + } + + *ctxp = ctx; + *digestp = digest; + + hash->init(ctx); + + return 1; +} + +static int rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, int algo) +{ + unsigned char *p; + size_t exp_len; + + static struct rsa_public_key *key = NULL; + static mpz_t sig_mpz; + + if (key == NULL) + { + if (!(key = whine_malloc(sizeof(struct rsa_public_key)))) + return 0; + + nettle_rsa_public_key_init(key); + mpz_init(sig_mpz); + } + + if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + key_len--; + if ((exp_len = *p++) == 0) + { + GETSHORT(exp_len, p); + key_len -= 2; + } + + if (exp_len >= key_len) + return 0; + + key->size = key_len - exp_len; + mpz_import(key->e, exp_len, 1, 1, 0, 0, p); + mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len); + + mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig); + + switch (algo) + { + case 1: + return nettle_rsa_md5_verify_digest(key, digest, sig_mpz); + case 5: case 7: + return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz); + case 8: + return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz); + case 10: + return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz); + } + + return 0; +} + +static int dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, int algo) +{ + unsigned char *p; + unsigned int t; + + static struct dsa_public_key *key = NULL; + static struct dsa_signature *sig_struct; + + if (key == NULL) + { + if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || + !(key = whine_malloc(sizeof(struct dsa_public_key)))) + return 0; + + nettle_dsa_public_key_init(key); + nettle_dsa_signature_init(sig_struct); + } + + if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + t = *p++; + + if (key_len < (213 + (t * 24))) + return 0; + + mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20; + mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + mpz_import(key->y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + + mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1); + mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21); + + (void)algo; + + return nettle_dsa_sha1_verify_digest(key, digest, sig_struct); +} + +static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, int algo) +{ + switch (algo) + { + case 1: case 5: case 7: case 8: case 10: + return rsa_verify(key_data, key_len, sig, sig_len, digest, algo); + + case 3: case 6: + return dsa_verify(key_data, key_len, sig, sig_len, digest, algo); + } + + return 0; +} + /* Convert from presentation format to wire format, in place. Also map UC -> LC. Note that using extract_name to get presentation format @@ -362,7 +559,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (type_covered == type && check_date_range(sig_inception, sig_expiration) && - verifyalg_supported(algo) && + hash_find(algo_digest_name(algo)) && labels <= name_labels) { if (sigidx == sig_sz) @@ -404,9 +601,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in /* Now try all the sigs to try and find one which validates */ for (j = 0; j sig = p; - alg->siglen = rdlen - (p - psav); + sig = p; + sig_len = rdlen - (p - psav); + + if (!(hash = hash_find(algo_digest_name(algo))) || + !hash_init(hash, &ctx, &digest)) + continue; nsigttl = htonl(orig_ttl); - digestalg_begin(alg->vtbl->digest_algo); - digestalg_add_data(psav, 18); + hash->update(ctx, 18, psav); wire_len = to_wire(keyname); - digestalg_add_data(keyname, wire_len); + hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); from_wire(keyname); for (i = 0; i < rrsetidx; ++i) @@ -462,9 +663,9 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in } wire_len = to_wire(name_start); - digestalg_add_data(name_start, wire_len); - digestalg_add_data(p, 4); /* class and type */ - digestalg_add_data(&nsigttl, 4); + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start); + hash->update(ctx, 4, p); /* class and type */ + hash->update(ctx, 4, (unsigned char *)&nsigttl); p += 8; /* skip class, type, ttl */ GETSHORT(rdlen, p); @@ -479,27 +680,27 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg); len += end - cp; len = htons(len); - digestalg_add_data(&len, 2); + hash->update(ctx, 2, (unsigned char *)&len); /* Now canonicalise again and digest. */ cp = p; dp = rr_desc; while ((seg = get_rdata(header, plen, end, name, &cp, &dp))) - digestalg_add_data(name, seg); + hash->update(ctx, seg, (unsigned char *)name); if (cp != end) - digestalg_add_data(cp, end - cp); + hash->update(ctx, end - cp, cp); } - + + hash->digest(ctx, hash->digest_size, digest); + /* namebuff used for workspace above, restore to leave unchanged on exit */ p = (unsigned char*)(rrset[0]); extract_name(header, plen, &p, name, 1, 0); - memcpy(alg->digest, digestalg_final(), digestalg_len()); - if (key) { if (algo_in == algo && keytag_in == key_tag && - alg->vtbl->verify(alg, key, keylen)) + verify(key, keylen, sig, sig_len, digest, algo)) return STAT_SECURE; } else @@ -507,7 +708,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in /* 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)) + verify(crecp->addr.key.keydata, crecp->uid, sig, sig_len, digest, algo)) return STAT_SECURE; } } @@ -579,7 +780,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch psave = p; - /* length at least covers flags, protocol and algo now. */ GETSHORT(flags, p); if (*p++ != 3) return STAT_INSECURE; @@ -606,30 +806,41 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch 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); + { + void *ctx; + unsigned char *digest, *ds_digest; + const struct nettle_hash *hash; + + if (recp1->addr.key.algo == algo && + recp1->addr.key.keytag == keytag && + (flags & 0x100) && /* zone key flag */ + (hash = hash_find(ds_digest_name(recp1->addr.key.digest))) && + hash_init(hash, &ctx, &digest)) - digestalg_begin(recp1->addr.key.digest); - digestalg_add_data(name, wire_len); - digestalg_add_data((char *)psave, rdlen); - - from_wire(name); - - if (recp1->uid == digestalg_len() && - blockdata_retrieve(recp1->addr.key.keydata, recp1->uid, digestalg_final()) && - validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag)) - { - struct all_addr a; - valid = 1; - a.addr.keytag = keytag; - log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); - break; - } - } + { + int wire_len = to_wire(name); + + /* Note that digest may be different between DSs, so + we can't move this outside the loop. */ + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); + hash->update(ctx, (unsigned int)rdlen, psave); + hash->digest(ctx, hash->digest_size, digest); + + from_wire(name); + + if (recp1->uid == (int)hash->digest_size && + (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->uid, NULL)) && + memcmp (ds_digest, digest, recp1->uid) == 0 && + validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag)) + { + struct all_addr a; + valid = 1; + a.addr.keytag = keytag; + log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); + break; + } + } + } } if (valid) diff --git a/src/option.c b/src/option.c index e4885ee..912876f 100644 --- a/src/option.c +++ b/src/option.c @@ -3687,7 +3687,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* Upper bound on length */ - new->key = opt_malloc((3*strlen(key64)/4)); + new->key = opt_malloc((3*strlen(key64)/4)+1); unhide_metas(key64); if ((new->keylen = parse_base64(key64, new->key)) == -1) ret_err(_("bad base64 in DNSKEY")); From cc111e0bab09b50376b263645c11e8c52367baf3 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 13 Jan 2014 21:38:19 +0000 Subject: [PATCH 2/3] Add ip6addr.h to Makefile list. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fb8e9eb..45bd2ae 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ domain.o dnssec.o blockdata.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ - dns-protocol.h radv-protocol.h + dns-protocol.h radv-protocol.h ip6addr.h all : $(BUILDDIR) @cd $(BUILDDIR) && $(MAKE) \ From a25720a34a3dae8ab67a237a7f732ccf635e661f Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 14 Jan 2014 23:13:55 +0000 Subject: [PATCH 3/3] protocol handling for DNSSEC --- src/dnsmasq.h | 3 ++- src/forward.c | 28 ++++++++++++++++++++++++++-- src/option.c | 6 +++--- src/rfc1035.c | 51 +++++++++++++++++++++++++++++++++------------------ 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 4d91d92..7ffef8f 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -230,7 +230,8 @@ struct event_desc { #define OPT_QUIET_DHCP6 43 #define OPT_QUIET_RA 44 #define OPT_DNSSEC_VALID 45 -#define OPT_LAST 46 +#define OPT_DNSSEC_PERMISS 46 +#define OPT_LAST 47 /* 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. */ diff --git a/src/forward.c b/src/forward.c index 8167229..0dd66f0 100644 --- a/src/forward.c +++ b/src/forward.c @@ -511,7 +511,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server if (option_bool(OPT_DNSSEC_VALID)) header->hb4 &= ~HB4_AD; - if (cache_secure) + if (!(header->hb4 & HB4_CD) && cache_secure) header->hb4 |= HB4_AD; #endif @@ -556,6 +556,31 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } } +#ifdef HAVE_DNSSEC + if (no_cache && !(header->hb4 & HB4_CD)) + { + if (option_bool(OPT_DNSSEC_PERMISS)) + { + unsigned short type; + char types[20]; + + if (extract_request(header, (size_t)n, daemon->namebuff, &type)) + { + querystr("", types, type); + my_syslog(LOG_WARNING, _("DNSSEC validation failed: query %s%s"), daemon->namebuff, types); + } + else + my_syslog(LOG_WARNING, _("DNSSEC validation failed for unknown query")); + } + else + { + /* Bogus reply, turn into SERVFAIL */ + SET_RCODE(header, SERVFAIL); + munged = 1; + } + } +#endif + /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ @@ -824,7 +849,6 @@ void reply_query(int fd, int family, time_t now) if (status == STAT_SECURE) cache_secure = 1; - /* TODO return SERVFAIL here */ else if (status == STAT_BOGUS) no_cache_dnssec = 1; diff --git a/src/option.c b/src/option.c index 912876f..760fd62 100644 --- a/src/option.c +++ b/src/option.c @@ -140,7 +140,7 @@ struct myoption { #define LOPT_QUIET_RA 328 #define LOPT_SEC_VALID 329 #define LOPT_DNSKEY 330 - +#define LOPT_DNSSEC_PERM 331 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -278,6 +278,7 @@ static const struct myoption opts[] = { "synth-domain", 1, 0, LOPT_SYNTH }, { "dnssec", 0, 0, LOPT_SEC_VALID }, { "dnskey", 1, 0, LOPT_DNSKEY }, + { "dnssec-permissive", 0, 0, LOPT_DNSSEC_PERM }, #ifdef OPTION6_PREFIX_CLASS { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, #endif @@ -428,10 +429,9 @@ static struct { { LOPT_AUTHPEER, ARG_DUP, "[,...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, { LOPT_IPSET, ARG_DUP, "//[,...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, { LOPT_SYNTH, ARG_DUP, ",,[]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, -#ifdef HAVE_DNSSEC { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, { LOPT_DNSKEY, ARG_DUP, ",,", gettext_noop("Specify trust anchor DNSKEY"), NULL }, -#endif + { LOPT_DNSSEC_PERM, OPT_DNSSEC_PERMISS, NULL, gettext_noop("Do NOT return SERVFAIL whne DNSSEC validation fails."), NULL }, #ifdef OPTION6_PREFIX_CLASS { LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, #endif diff --git a/src/rfc1035.c b/src/rfc1035.c index 0b254e3..6827544 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -511,14 +511,17 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned int optno, unsigned char *opt, size_t optlen, int set_do) { unsigned char *lenp, *datap, *p; - int rdlen; + int rdlen, is_sign; - if (ntohs(header->arcount) == 0) + if (!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign))) { + if (is_sign) + return plen; + /* We are adding the pseudoheader */ if (!(p = skip_questions(header, plen)) || !(p = skip_section(p, - ntohs(header->ancount) + ntohs(header->nscount), + ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), header, plen))) return plen; *p++ = 0; /* empty name */ @@ -531,16 +534,16 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned rdlen = 0; if (((ssize_t)optlen) > (limit - (p + 4))) return plen; /* Too big */ - header->arcount = htons(1); + header->arcount = htons(ntohs(header->arcount) + 1); datap = p; } else { - int i, is_sign; + int i; unsigned short code, len, flags; + /* Must be at the end, if exists */ if (ntohs(header->arcount) != 1 || - !(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) || is_sign || (!(p = skip_name(p, header, plen, 10)))) return plen; @@ -1147,7 +1150,6 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t /* If the packet holds exactly one query return F_IPV4 or F_IPV6 and leave the name from the query in name */ - unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep) { unsigned char *p = (unsigned char *)(header+1); @@ -1447,23 +1449,30 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, int nameoffset; unsigned short flag; int q, ans, anscount = 0, addncount = 0; - int dryrun = 0, sec_reqd = 0; + int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0; int is_sign; struct crec *crecp; int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; + size_t len; + /* Don't return AD set even for local data if checking disabled. */ + if (header->hb4 & HB4_CD) + sec_data = 0; + /* If there is an RFC2671 pseudoheader then it will be overwritten by partial replies, so we have to do a dry run to see if we can answer the query. We check to see if the do bit is set, if so we always forward rather than answering from the cache, which doesn't include - security information. */ + security information, unless we're in DNSSEC validation mode. */ if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign)) { unsigned short udpsz, flags; unsigned char *psave = pheader; + have_pseudoheader = 1; + GETSHORT(udpsz, pheader); pheader += 2; /* ext_rcode */ GETSHORT(flags, pheader); @@ -1637,7 +1646,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!dryrun) log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL); } - else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID)) { ans = 1; if (!(crecp->flags & (F_HOSTS | F_DHCP))) @@ -1834,7 +1843,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!dryrun) log_query(crecp->flags, name, NULL, NULL); } - else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID)) { /* If we are returning local answers depending on network, filter here. */ @@ -2060,12 +2069,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (trunc) header->hb3 |= HB3_TC; - header->hb4 &= ~HB4_AD; - - if (option_bool(OPT_DNSSEC_VALID) || option_bool(OPT_DNSSEC_PROXY)) - if (sec_data) - header->hb4 |= HB4_AD; - if (nxdomain) SET_RCODE(header, NXDOMAIN); else @@ -2073,6 +2076,18 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, header->ancount = htons(anscount); header->nscount = htons(0); header->arcount = htons(addncount); - return ansp - (unsigned char *)header; + + header->hb4 &= ~HB4_AD; + len = ansp - (unsigned char *)header; + + if (have_pseudoheader) + { + len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd); + if (sec_reqd && sec_data) + header->hb4 |= HB4_AD; + + } + + return len ; }