From 53f84c7f620b63ff495eaa9acc50fe034e01103d Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 5 Apr 2012 02:43:39 +0200 Subject: [PATCH 001/105] Add compile-time macro for DNSSEC support. --- src/config.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.h b/src/config.h index 5224adf..fa20147 100644 --- a/src/config.h +++ b/src/config.h @@ -141,6 +141,7 @@ RESOLVFILE /* #define HAVE_DBUS */ /* #define HAVE_IDN */ /* #define HAVE_CONNTRACK */ +#define HAVE_DNSSEC /* Default locations for important system files. */ From 237724c0c715f9e6fad84fbd2c2855b6d978b8a8 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 5 Apr 2012 02:46:52 +0200 Subject: [PATCH 002/105] Rename existing DNSSEC macros into DNSSEC_PROXY. --- src/dnsmasq.h | 2 +- src/forward.c | 2 +- src/option.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index f37034d..92a6a43 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -213,7 +213,7 @@ struct event_desc { #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 diff --git a/src/forward.c b/src/forward.c index 6c9f646..7d106c9 100644 --- a/src/forward.c +++ b/src/forward.c @@ -476,7 +476,7 @@ static size_t process_reply(struct dns_header *header, time_t now, } /* RFC 4035 sect 4.6 para 3 */ - if (!is_sign && !option_bool(OPT_DNSSEC)) + if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) header->hb4 &= ~HB4_AD; if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) diff --git a/src/option.c b/src/option.c index b03388a..cff9ce1 100644 --- a/src/option.c +++ b/src/option.c @@ -109,7 +109,7 @@ struct myoption { #define LOPT_NO_REBIND 298 #define LOPT_LOC_REBND 299 #define LOPT_ADD_MAC 300 -#define LOPT_DNSSEC 301 +#define LOPT_SEC_PROXY 301 #define LOPT_INCR_ADDR 302 #define LOPT_CONNTRACK 303 #define LOPT_FQDN 304 @@ -250,7 +250,7 @@ static const struct myoption opts[] = { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES }, { "rebind-localhost-ok", 0, 0, LOPT_LOC_REBND }, { "add-mac", 0, 0, LOPT_ADD_MAC }, - { "proxy-dnssec", 0, 0, LOPT_DNSSEC }, + { "proxy-dnssec", 0, 0, LOPT_SEC_PROXY }, { "dhcp-sequential-ip", 0, 0, LOPT_INCR_ADDR }, { "conntrack", 0, 0, LOPT_CONNTRACK }, { "dhcp-client-update", 0, 0, LOPT_FQDN }, @@ -394,7 +394,7 @@ static struct { { LOPT_PXE_SERV, ARG_DUP, "", gettext_noop("Boot service for PXE menu."), NULL }, { 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_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL }, + { LOPT_SEC_PROXY, 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 }, From a669f012dd72e206fdcf42ae7e51c082a68039ab Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 5 Apr 2012 02:47:28 +0200 Subject: [PATCH 003/105] Add dnssec RR types --- src/dns-protocol.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 80eff72..3aa4fec 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 From 7dbe193beeb2f8fccb3a54eba72a0292195a0797 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 5 Apr 2012 02:50:13 +0200 Subject: [PATCH 004/105] Add run-time options to activate dnssec validation. --- src/dnsmasq.h | 3 ++- src/option.c | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 92a6a43..eed2146 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -222,7 +222,8 @@ struct event_desc { #define OPT_CLEVERBIND 39 #define OPT_TFTP 40 #define OPT_FAST_RA 41 -#define OPT_LAST 42 +#define OPT_DNSSEC_VALIDATE 42 +#define OPT_LAST 43 /* 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/option.c b/src/option.c index cff9ce1..ed015b6 100644 --- a/src/option.c +++ b/src/option.c @@ -129,11 +129,13 @@ struct myoption { #define LOPT_AUTHPEER 318 #define LOPT_IPSET 319 #define LOPT_SYNTH 320 +#define LOPT_SEC_VALID 321 #ifdef OPTION6_PREFIX_CLASS -#define LOPT_PREF_CLSS 321 +#define LOPT_PREF_CLSS 322 #endif #define LOPT_FAST_RA 322 + #ifdef HAVE_GETOPT_LONG static const struct option opts[] = #else @@ -267,6 +269,7 @@ static const struct myoption opts[] = { "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 @@ -402,6 +405,7 @@ static struct { { LOPT_FAST_RA, OPT_FAST_RA, NULL, gettext_noop("Always send frequent router-advertisements"), NULL }, { LOPT_DUID, ARG_ONE, ",", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, ",
", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, +<<<<<<< HEAD { LOPT_RR, ARG_DUP, ",,[]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, { LOPT_AUTHSERV, ARG_ONE, ",", gettext_noop("Export local names to global DNS"), NULL }, @@ -412,6 +416,10 @@ 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 sythesised names"), NULL }, + { LOPT_SEC_VALID, OPT_DNSSEC_VALIDATE, NULL, gettext_noop("Activate DNSSEC validation"), 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,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, #endif From f53c79c01be3fd64027283ea8568d99a811a526f Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sun, 22 Apr 2012 14:30:53 +0200 Subject: [PATCH 005/105] Externalize dns parsing functions. --- src/dnsmasq.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index eed2146..ce81eee 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -934,6 +934,9 @@ int is_name_synthetic(int flags, char *name, struct all_addr *addr); 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_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, @@ -966,6 +969,9 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp); size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr); #endif +/* dnssec.c */ +int dnssec_validate(struct dns_header *header, size_t plen); + /* util.c */ void rand_init(void); unsigned short rand16(void); From fa164d459f61118d1179380961a6e488743baaed Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sun, 22 Apr 2012 14:31:43 +0200 Subject: [PATCH 006/105] DNSSEC validation require EDNS0, force larger packet size. --- src/dnsmasq.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 71d4412..d8686a7 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -76,9 +76,10 @@ int main (int argc, char **argv) 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); From e292e93d358ba0780ecc1f3ce0682c8722930018 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sun, 22 Apr 2012 14:32:02 +0200 Subject: [PATCH 007/105] Initial dnssec structure. --- Makefile | 3 +- src/dnssec.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/forward.c | 7 +- 3 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/dnssec.c diff --git a/Makefile b/Makefile index fe63aee..2f853b9 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,8 @@ 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 hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h diff --git a/src/dnssec.c b/src/dnssec.c new file mode 100644 index 0000000..27cef65 --- /dev/null +++ b/src/dnssec.c @@ -0,0 +1,278 @@ + +#include "dnsmasq.h" +#include + +#define SERIAL_UNDEF -100 +#define SERIAL_EQ 0 +#define SERIAL_LT -1 +#define SERIAL_GT 1 + +#define countof(x) (long)(sizeof(x) / sizeof(x[0])) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* + * 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, push the data in; data is consumed immediately, so the buffer + * // can be freed or modified. + * alg->begin_data(); + * alg->add_data(buf1, 123); + * alg->add_data(buf2, 45); + * alg->add_data(buf3, 678); + * alg->end_data(); + * + * // Third, verify if we got the correct key for this signature. + * alg->verify(key1, 16); + * alg->verify(key2, 16); + */ +typedef struct +{ + int (*set_signature)(unsigned char *data, unsigned len); + void (*begin_data)(void); + void (*add_data)(void *data, unsigned len); + void (*end_data)(void); + int (*verify)(unsigned char *key, unsigned key_len); +} VerifyAlg; + +/* RFC4034, Appendix A.1: only algorithm 3 (DSA/SHA1) and 5 (RSA/SHA1) are + currently valid for zone-signing. */ +static const VerifyAlg valgs[6] = +{ + {0,0,0,0,0}, /* 0: reserved */ + {0,0,0,0,0}, /* 1: RSA/MD5 */ + {0,0,0,0,0}, /* 2: DH */ + {0,0,0,0,0}, /* 3: DSA/SHA1 */ + {0,0,0,0,0}, /* 4: ECC */ + {0,0,0,0,0}, /* 5: RSA/SHA1 */ +}; + +/* 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 (*buf >= 'A' && *buf <= 'Z') + *buf += 'a' - 'A'; + buf++; + } + *buf++ = '.'; + } + *buf = 0; + if (rr == end) + return 0; + return rr-start; +} + +/* 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 */ + if (serial_compare_32(curtime, date_start) != SERIAL_GT) + return 0; + if (serial_compare_32(curtime, date_end) != SERIAL_LT) + return 0; + return 1; +} + +/* 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. */ +static int rrset_canonical_order(const void *r1, const void *r2) +{ + int r1len, r2len, res; + const unsigned char *pr1=r1, *pr2=r2; + + pr1 += 8; pr2 += 8; + GETSHORT(r1len, pr1); GETSHORT(r2len, pr2); + + /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */ + res = memcmp(pr1, pr2, MIN(r1len, r2len)); + if (res == 0) + { + if (r1len < r2len) + return -1; + else + /* NOTE: 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; + } + + return res; +} + +static int validate_rrsig(struct dns_header *header, size_t pktlen, + unsigned char *reply, int count, char *owner, + int sigclass, int sigrdlen, unsigned char *sig) +{ + 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 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 (sigalg >= countof(valgs) || !valgs[sigalg].set_signature) + { + printf("RRSIG algorithm not supported: %d\n", sigalg); + return 0; + } + + if (!check_date_range(ntohl(date_start), ntohl(date_end))) + { + printf("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; + assert(rrsetidx < countof(rrset)); + /* TODO: here we should convert to lowercase domain names within + RDATA. We can do it in place. */ + } + p += rdlen; + } + + /* Sort RRset records in canonical order. */ + qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); + + /* Extract the signer name (we need to query DNSKEY of this name) */ + if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name))) + return 0; + sig += res; sigrdlen -= res; + + /* Now initialize the signature verification algorithm and process the whole + RRset */ + const VerifyAlg *alg = &valgs[sigalg]; + if (!alg->set_signature(sig, sigrdlen)) + return 0; + + alg->begin_data(); + alg->add_data(sigrdata, 18); + alg->add_data(signer_name, strlen(signer_name)-1); /* remove trailing dot */ + for (i = 0; i < rrsetidx; ++i) + { + int rdlen; + + alg->add_data(owner, strlen(owner)); + alg->add_data(&sigtype, 2); + alg->add_data(&sigclass, 2); + alg->add_data(&sigttl, 4); + + p = (unsigned char*)(rrset[i]); + p += 8; + GETSHORT(rdlen, p); + alg->add_data(p-2, rdlen+2); + } + alg->end_data(); + + /* TODO: now we need to fetch the DNSKEY of signer_name with the specified + keytag, and check whether it validates with the current algorithm. */ + /* + pseudo-code: + + char *key; int keylen; + if (!fetch_dnskey(signer_name, keytag, &key, &keylen)) + return 0; + return alg->verify(key, keylen); + */ + return 0; +} + + +int dnssec_validate(struct dns_header *header, size_t pktlen) +{ + unsigned char *p, *reply; + char *owner = daemon->namebuff; + int i, qtype, qclass, rdlen; + unsigned long ttl; + + if (header->ancount == 0) + return 0; + if (!(reply = p = skip_questions(header, pktlen))) + return 0; + for (i = 0; i < ntohs(header->ancount); 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\n"); + /* 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? */ + validate_rrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p); + } + p += rdlen; + } + return 1; +} diff --git a/src/forward.c b/src/forward.c index 7d106c9..f93cc54 100644 --- a/src/forward.c +++ b/src/forward.c @@ -491,7 +491,12 @@ static size_t process_reply(struct dns_header *header, time_t now, 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)) { From 970ce22b68a4be3db6c63a00ff4f13c57469030d Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sun, 22 Apr 2012 15:22:07 +0200 Subject: [PATCH 008/105] Augment verify algorithm table. --- src/dnssec.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 27cef65..0eae9ed 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -43,16 +43,25 @@ typedef struct int (*verify)(unsigned char *key, unsigned key_len); } VerifyAlg; -/* RFC4034, Appendix A.1: only algorithm 3 (DSA/SHA1) and 5 (RSA/SHA1) are - currently valid for zone-signing. */ -static const VerifyAlg valgs[6] = +/* Updated registry that merges various RFCs: + https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ +static const VerifyAlg valgs[] = { - {0,0,0,0,0}, /* 0: reserved */ - {0,0,0,0,0}, /* 1: RSA/MD5 */ - {0,0,0,0,0}, /* 2: DH */ - {0,0,0,0,0}, /* 3: DSA/SHA1 */ - {0,0,0,0,0}, /* 4: ECC */ - {0,0,0,0,0}, /* 5: RSA/SHA1 */ + {0,0,0,0,0}, /* 0: reserved */ + {0,0,0,0,0}, /* 1: RSAMD5 */ + {0,0,0,0,0}, /* 2: DH */ + {0,0,0,0,0}, /* 3: DSA */ + {0,0,0,0,0}, /* 4: ECC */ + {0,0,0,0,0}, /* 5: RSASHA1 */ + {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ + {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ + {0,0,0,0,0}, /* 8: RSASHA256 */ + {0,0,0,0,0}, /* 9: unassigned */ + {0,0,0,0,0}, /* 10: RSASHA512 */ + {0,0,0,0,0}, /* 11: unassigned */ + {0,0,0,0,0}, /* 12: ECC-GOST */ + {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */ + {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */ }; /* Implement RFC1982 wrapped compare for 32-bit numbers */ From c7a93f6e4e054dfd7882f90f28fe42e4e5d4f641 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sun, 22 Apr 2012 15:53:52 +0200 Subject: [PATCH 009/105] Skip trailing \0 in domain name. --- src/dnssec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dnssec.c b/src/dnssec.c index 0eae9ed..4cf9503 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -99,6 +99,7 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) } *buf++ = '.'; } + rr++; *buf = 0; if (rr == end) return 0; From b98f7715193be85454cbfe4eed7a3282788cef6e Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sun, 22 Apr 2012 15:59:27 +0200 Subject: [PATCH 010/105] Filter out invalid characters in domain names. --- src/dnssec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index 4cf9503..c5ba5f4 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -93,6 +93,8 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) while (count-- >= 0 && rr < end) { *buf = *rr++; + if (!isascii(*buf) || iscntrl(*buf) || *buf == '.') + return 0; if (*buf >= 'A' && *buf <= 'Z') *buf += 'a' - 'A'; buf++; From d322de06139eba5d25f829863a8ea682234d776e Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Mon, 23 Apr 2012 00:30:00 +0200 Subject: [PATCH 011/105] Further abstract API of verify crypto. --- src/dnssec-crypto.h | 56 +++++++++++++++++++++++++++++++++++++++ src/dnssec.c | 64 ++++++++++++--------------------------------- 2 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 src/dnssec-crypto.h diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h new file mode 100644 index 0000000..1c8b36d --- /dev/null +++ b/src/dnssec-crypto.h @@ -0,0 +1,56 @@ +#ifndef DNSSEC_CRYPTO_H +#define DNSSEC_CRYPTO_H + +/* + * 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, push the data in; data is consumed immediately, so the buffer + * // can be freed or modified. + * alg->begin_data(); + * alg->add_data(buf1, 123); + * alg->add_data(buf2, 45); + * alg->add_data(buf3, 678); + * alg->end_data(); + * + * // Third, verify if we got the correct key for this signature. + * alg->verify(key1, 16); + * alg->verify(key2, 16); + */ +typedef struct +{ + int (*set_signature)(unsigned char *data, unsigned len); + void (*begin_data)(void); + void (*add_data)(void *data, unsigned len); + void (*end_data)(void); + int (*verify)(unsigned char *key, unsigned key_len); +} VerifyAlg; + +#define DEFINE_VALG(alg) \ + void alg ## _set_signature(unsigned char *data, unsigned len); \ + void alg ## _begin_data(void); \ + void alg ## _add_data(void *data, unsigned len); \ + void alg ## _end_data(void); \ + int alg ## _verify(unsigned char *key, unsigned key_len) \ + /**/ + +#define VALG_VTABLE(alg) { \ + alg ## _set_signature, \ + alg ## _begin_data, \ + alg ## _add_data, \ + alg ## _end_data, \ + alg ## _verify \ + } /**/ + +/* Algorithm 5: RSASHA1 */ +DEFINE_VALG(rsasha1); + +#endif /* DNSSEC_CRYPTO_H */ diff --git a/src/dnssec.c b/src/dnssec.c index c5ba5f4..369ba11 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -1,5 +1,6 @@ #include "dnsmasq.h" +#include "dnssec-crypto.h" #include #define SERIAL_UNDEF -100 @@ -10,58 +11,25 @@ #define countof(x) (long)(sizeof(x) / sizeof(x[0])) #define MIN(a,b) ((a) < (b) ? (a) : (b)) -/* - * 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, push the data in; data is consumed immediately, so the buffer - * // can be freed or modified. - * alg->begin_data(); - * alg->add_data(buf1, 123); - * alg->add_data(buf2, 45); - * alg->add_data(buf3, 678); - * alg->end_data(); - * - * // Third, verify if we got the correct key for this signature. - * alg->verify(key1, 16); - * alg->verify(key2, 16); - */ -typedef struct -{ - int (*set_signature)(unsigned char *data, unsigned len); - void (*begin_data)(void); - void (*add_data)(void *data, unsigned len); - void (*end_data)(void); - int (*verify)(unsigned char *key, unsigned key_len); -} VerifyAlg; - /* Updated registry that merges various RFCs: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ static const VerifyAlg valgs[] = { - {0,0,0,0,0}, /* 0: reserved */ - {0,0,0,0,0}, /* 1: RSAMD5 */ - {0,0,0,0,0}, /* 2: DH */ - {0,0,0,0,0}, /* 3: DSA */ - {0,0,0,0,0}, /* 4: ECC */ - {0,0,0,0,0}, /* 5: RSASHA1 */ - {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ - {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ - {0,0,0,0,0}, /* 8: RSASHA256 */ - {0,0,0,0,0}, /* 9: unassigned */ - {0,0,0,0,0}, /* 10: RSASHA512 */ - {0,0,0,0,0}, /* 11: unassigned */ - {0,0,0,0,0}, /* 12: ECC-GOST */ - {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */ - {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */ + {0,0,0,0,0}, /* 0: reserved */ + {0,0,0,0,0}, /* 1: RSAMD5 */ + {0,0,0,0,0}, /* 2: DH */ + {0,0,0,0,0}, /* 3: DSA */ + {0,0,0,0,0}, /* 4: ECC */ + VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ + {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ + {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ + {0,0,0,0,0}, /* 8: RSASHA256 */ + {0,0,0,0,0}, /* 9: unassigned */ + {0,0,0,0,0}, /* 10: RSASHA512 */ + {0,0,0,0,0}, /* 11: unassigned */ + {0,0,0,0,0}, /* 12: ECC-GOST */ + {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */ + {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */ }; /* Implement RFC1982 wrapped compare for 32-bit numbers */ From 7e846b98580ba62220b24dcc9c7a0213f52dce6a Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Mon, 23 Apr 2012 00:30:38 +0200 Subject: [PATCH 012/105] Add openssl support to build machinery. --- Makefile | 6 ++++-- src/config.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 2f853b9..0d08b14 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,8 @@ ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFI 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)`\"' @@ -74,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)" \ - 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 clean : diff --git a/src/config.h b/src/config.h index fa20147..93dbf44 100644 --- a/src/config.h +++ b/src/config.h @@ -142,7 +142,7 @@ RESOLVFILE /* #define HAVE_IDN */ /* #define HAVE_CONNTRACK */ #define HAVE_DNSSEC - +#define HAVE_OPENSSL /* Default locations for important system files. */ From 9940aba9f6e40c8bf90a4006f97ac14e7051a68f Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Mon, 23 Apr 2012 00:32:01 +0200 Subject: [PATCH 013/105] Initial openssl RSASHA1 implementation (only SHA1 for now). --- Makefile | 3 ++- src/dnssec-openssl.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/dnssec-openssl.c diff --git a/Makefile b/Makefile index 0d08b14..16e85e1 100644 --- a/Makefile +++ b/Makefile @@ -67,8 +67,9 @@ 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 + domain.o dnssec.o dnssec-openssl.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c new file mode 100644 index 0000000..5c2536e --- /dev/null +++ b/src/dnssec-openssl.c @@ -0,0 +1,45 @@ +#include +#include + +struct rsasha1_state +{ + union + { + EVP_MD_CTX hash; + unsigned char digest[20]; + }; + unsigned char *sig; + unsigned siglen; + +} RSASHA1; + +int rsasha1_set_signature(unsigned char *data, unsigned len) +{ + RSASHA1.sig = data; + RSASHA1.siglen = len; + return 1; +} + +void rsasha1_begin_data(void) +{ + EVP_MD_CTX_init(&RSASHA1.hash); + EVP_DigestInit_ex(&RSASHA1.hash, EVP_sha1(), NULL); +} + +void rsasha1_add_data(void *data, unsigned len) +{ + EVP_DigestUpdate(&RSASHA1.hash, data, len); +} + +void rsasha1_end_data(void) +{ + unsigned char digest[20]; + EVP_DigestFinal(&RSASHA1.hash, digest, NULL); + memcpy(RSASHA1.digest, digest, 20); +} + +int rsasha1_verify(unsigned char *key, unsigned key_len) +{ + return 0; +} + From 382e38f4945316f11c814bcf9c625bf718856f82 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 24 Apr 2012 01:46:47 +0200 Subject: [PATCH 014/105] Specify the correct place where to canonicalize RR within RRset. --- src/dnssec.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 369ba11..ad34557 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -169,9 +169,12 @@ static int validate_rrsig(struct dns_header *header, size_t pktlen, if (res == 1 && qtype == sigtype && qclass == sigclass) { ++rrsetidx; - assert(rrsetidx < countof(rrset)); - /* TODO: here we should convert to lowercase domain names within - RDATA. We can do it in place. */ + if (rrsetidx == countof(rrset)) + { + /* Internal buffer too small */ + printf("internal buffer too small for this RRset\n"); + return 0; + } } p += rdlen; } @@ -205,6 +208,8 @@ static int validate_rrsig(struct dns_header *header, size_t pktlen, p = (unsigned char*)(rrset[i]); p += 8; GETSHORT(rdlen, p); + /* TODO: instead of a direct add_data(), we must call a RRtype-specific + function, that extract and canonicalizes domain names within RDATA. */ alg->add_data(p-2, rdlen+2); } alg->end_data(); From 6445c8ed73ee54b1654eb1da5a0fc468003b61e0 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 24 Apr 2012 02:02:29 +0200 Subject: [PATCH 015/105] Fix off-by-one in iteration. --- src/dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index ad34557..3fef946 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -58,7 +58,7 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) while (rr < end && *rr != 0) { count = *rr++; - while (count-- >= 0 && rr < end) + while (count-- > 0 && rr < end) { *buf = *rr++; if (!isascii(*buf) || iscntrl(*buf) || *buf == '.') From d31d057aa37cbff5b7ba0ebe9e6b2a2932b1e78c Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 24 Apr 2012 02:02:55 +0200 Subject: [PATCH 016/105] Remove useless endian-conversion after GETLONG(). --- src/dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 3fef946..5c6bda5 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -149,7 +149,7 @@ static int validate_rrsig(struct dns_header *header, size_t pktlen, return 0; } - if (!check_date_range(ntohl(date_start), ntohl(date_end))) + if (!check_date_range(date_start, date_end)) { printf("RRSIG outside date range\n"); return 0; From b573aebc0968ecd2736fc904c35c5a6630b06d46 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 24 Apr 2012 02:21:50 +0200 Subject: [PATCH 017/105] Add skeleton for RSASHA256. --- src/dnssec-crypto.h | 2 +- src/dnssec-openssl.c | 47 ++++++++++++++++++++++++++++++++++---------- src/dnssec.c | 2 +- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index 1c8b36d..c5e191b 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -50,7 +50,7 @@ typedef struct alg ## _verify \ } /**/ -/* Algorithm 5: RSASHA1 */ DEFINE_VALG(rsasha1); +DEFINE_VALG(rsasha256); #endif /* DNSSEC_CRYPTO_H */ diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 5c2536e..4dfb2ac 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -1,41 +1,63 @@ #include #include -struct rsasha1_state +struct rsasha_state { union { EVP_MD_CTX hash; - unsigned char digest[20]; + unsigned char digest[32]; }; unsigned char *sig; unsigned siglen; -} RSASHA1; +} RSASHA; int rsasha1_set_signature(unsigned char *data, unsigned len) { - RSASHA1.sig = data; - RSASHA1.siglen = len; + RSASHA.sig = data; + RSASHA.siglen = len; + return 1; +} + +int rsasha256_set_signature(unsigned char *data, unsigned len) +{ + RSASHA.sig = data; + RSASHA.siglen = len; return 1; } void rsasha1_begin_data(void) { - EVP_MD_CTX_init(&RSASHA1.hash); - EVP_DigestInit_ex(&RSASHA1.hash, EVP_sha1(), NULL); + EVP_MD_CTX_init(&RSASHA.hash); + EVP_DigestInit_ex(&RSASHA.hash, EVP_sha1(), NULL); +} +void rsasha256_begin_data(void) +{ + EVP_MD_CTX_init(&RSASHA.hash); + EVP_DigestInit_ex(&RSASHA.hash, EVP_sha256(), NULL); } void rsasha1_add_data(void *data, unsigned len) { - EVP_DigestUpdate(&RSASHA1.hash, data, len); + EVP_DigestUpdate(&RSASHA.hash, data, len); +} +void rsasha256_add_data(void *data, unsigned len) +{ + EVP_DigestUpdate(&RSASHA.hash, data, len); } void rsasha1_end_data(void) { unsigned char digest[20]; - EVP_DigestFinal(&RSASHA1.hash, digest, NULL); - memcpy(RSASHA1.digest, digest, 20); + EVP_DigestFinal(&RSASHA.hash, digest, NULL); + memcpy(RSASHA.digest, digest, 20); +} +void rsasha256_end_data(void) +{ + unsigned char digest[32]; + EVP_DigestFinal(&RSASHA.hash, digest, NULL); + memcpy(RSASHA.digest, digest, 32); } int rsasha1_verify(unsigned char *key, unsigned key_len) @@ -43,3 +65,8 @@ int rsasha1_verify(unsigned char *key, unsigned key_len) return 0; } +int rsasha256_verify(unsigned char *key, unsigned key_len) +{ + return 0; +} + diff --git a/src/dnssec.c b/src/dnssec.c index 5c6bda5..1d8727e 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -23,7 +23,7 @@ static const VerifyAlg valgs[] = VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ - {0,0,0,0,0}, /* 8: RSASHA256 */ + VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ {0,0,0,0,0}, /* 9: unassigned */ {0,0,0,0,0}, /* 10: RSASHA512 */ {0,0,0,0,0}, /* 11: unassigned */ From 0decc869ae17825675001565048074bb2364a579 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 24 Apr 2012 02:23:11 +0200 Subject: [PATCH 018/105] Fix rrset qsort comparison function. --- src/dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 1d8727e..1c7bba8 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -98,7 +98,7 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) static int rrset_canonical_order(const void *r1, const void *r2) { int r1len, r2len, res; - const unsigned char *pr1=r1, *pr2=r2; + const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2; pr1 += 8; pr2 += 8; GETSHORT(r1len, pr1); GETSHORT(r2len, pr2); From ba8badd6df39b9f73249bb34d80d1a1984bf555c Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:39:12 +0200 Subject: [PATCH 019/105] Fix bug in keydata_alloc() --- src/cache.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cache.c b/src/cache.c index 6fdeba2..8bd7bfd 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1333,6 +1333,8 @@ struct keydata *keydata_alloc(char *data, size_t len) { struct keydata *block, *ret = NULL; struct keydata **prev = &ret; + size_t blen; + while (len > 0) { if (keyblock_free) @@ -1350,9 +1352,10 @@ struct keydata *keydata_alloc(char *data, size_t len) 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; From 02f9b76418b74c56de96ba07b0999176de4a99b0 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:39:35 +0200 Subject: [PATCH 020/105] Rename key cache field. --- src/cache.c | 2 +- src/dnsmasq.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cache.c b/src/cache.c index 8bd7bfd..55dd2b9 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1184,7 +1184,7 @@ void dump_cache(time_t now) 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 diff --git a/src/dnsmasq.h b/src/dnsmasq.h index ce81eee..a59bb19 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -342,7 +342,7 @@ struct crec { 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 */ From 28c625572b412109ce4d8cc84bfb1234ef3270e9 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:40:13 +0200 Subject: [PATCH 021/105] Move general macros in dnsmasq.h --- src/dnsmasq.h | 3 +++ src/dnssec.c | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index a59bb19..6c62a52 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -56,6 +56,9 @@ typedef unsigned short u16; 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 diff --git a/src/dnssec.c b/src/dnssec.c index 1c7bba8..1e95479 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -8,9 +8,6 @@ #define SERIAL_LT -1 #define SERIAL_GT 1 -#define countof(x) (long)(sizeof(x) / sizeof(x[0])) -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - /* Updated registry that merges various RFCs: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ static const VerifyAlg valgs[] = From 366dfcb907644fd921f7acc42c4af2eed094bb0d Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:40:47 +0200 Subject: [PATCH 022/105] Explicitize the context of verification algorithm. --- src/dnssec-crypto.h | 37 ++++----- src/dnssec-openssl.c | 189 ++++++++++++++++++++++++++++++++++++------- src/dnssec.c | 21 ----- 3 files changed, 175 insertions(+), 72 deletions(-) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index c5e191b..3591556 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -25,32 +25,25 @@ * alg->verify(key1, 16); * alg->verify(key2, 16); */ + +typedef struct VerifyAlgCtx VerifyAlgCtx; + typedef struct { - int (*set_signature)(unsigned char *data, unsigned len); - void (*begin_data)(void); - void (*add_data)(void *data, unsigned len); - void (*end_data)(void); - int (*verify)(unsigned char *key, unsigned key_len); + int (*set_signature)(VerifyAlgCtx *ctx, unsigned char *data, unsigned len); + void (*begin_data)(VerifyAlgCtx *ctx); + void (*add_data)(VerifyAlgCtx *ctx, void *data, unsigned len); + void (*end_data)(VerifyAlgCtx *ctx); + int (*verify)(VerifyAlgCtx *ctx, unsigned char *key, unsigned key_len); } VerifyAlg; -#define DEFINE_VALG(alg) \ - void alg ## _set_signature(unsigned char *data, unsigned len); \ - void alg ## _begin_data(void); \ - void alg ## _add_data(void *data, unsigned len); \ - void alg ## _end_data(void); \ - int alg ## _verify(unsigned char *key, unsigned key_len) \ - /**/ +struct VerifyAlgCtx +{ + const VerifyAlg *vtbl; +}; -#define VALG_VTABLE(alg) { \ - alg ## _set_signature, \ - alg ## _begin_data, \ - alg ## _add_data, \ - alg ## _end_data, \ - alg ## _verify \ - } /**/ - -DEFINE_VALG(rsasha1); -DEFINE_VALG(rsasha256); +int verifyalg_supported(int algo); +VerifyAlgCtx* verifyalg_alloc(int algo); +void verifyalg_free(VerifyAlgCtx *a); #endif /* DNSSEC_CRYPTO_H */ diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 4dfb2ac..5cf2c41 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -1,72 +1,203 @@ #include +#include "dnsmasq.h" +#include "dnssec-crypto.h" #include -struct rsasha_state +typedef struct VACTX_rsasha1 { + VerifyAlgCtx base; + unsigned char *sig; + unsigned siglen; + union + { + EVP_MD_CTX hash; + unsigned char digest[20]; + }; +} VACTX_rsasha1; + +typedef struct VACTX_rsasha256 +{ + VerifyAlgCtx base; + unsigned char *sig; + unsigned siglen; union { EVP_MD_CTX hash; unsigned char digest[32]; }; - unsigned char *sig; - unsigned siglen; +} VACTX_rsasha256; -} RSASHA; - -int rsasha1_set_signature(unsigned char *data, unsigned len) +#define POOL_SIZE 1 +static union _Pool { - RSASHA.sig = data; - RSASHA.siglen = len; + VACTX_rsasha1 rsasha1; + VACTX_rsasha256 rsasha256; +} Pool[POOL_SIZE]; +static char pool_used = 0; + +static int rsasha1_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) +{ + VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; + ctx->sig = data; + ctx->siglen = len; return 1; } -int rsasha256_set_signature(unsigned char *data, unsigned len) +static int rsasha256_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) { - RSASHA.sig = data; - RSASHA.siglen = len; + VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; + ctx->sig = data; + ctx->siglen = len; return 1; } -void rsasha1_begin_data(void) +static void rsasha1_begin_data(VerifyAlgCtx *ctx_) { - EVP_MD_CTX_init(&RSASHA.hash); - EVP_DigestInit_ex(&RSASHA.hash, EVP_sha1(), NULL); + VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; + EVP_MD_CTX_init(&ctx->hash); + EVP_DigestInit_ex(&ctx->hash, EVP_sha1(), NULL); } -void rsasha256_begin_data(void) +static void rsasha256_begin_data(VerifyAlgCtx *ctx_) { - EVP_MD_CTX_init(&RSASHA.hash); - EVP_DigestInit_ex(&RSASHA.hash, EVP_sha256(), NULL); + VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; + EVP_MD_CTX_init(&ctx->hash); + EVP_DigestInit_ex(&ctx->hash, EVP_sha256(), NULL); } -void rsasha1_add_data(void *data, unsigned len) +static void rsasha1_add_data(VerifyAlgCtx *ctx_, void *data, unsigned len) { - EVP_DigestUpdate(&RSASHA.hash, data, len); + VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; + EVP_DigestUpdate(&ctx->hash, data, len); } -void rsasha256_add_data(void *data, unsigned len) +static void rsasha256_add_data(VerifyAlgCtx *ctx_, void *data, unsigned len) { - EVP_DigestUpdate(&RSASHA.hash, data, len); + VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; + EVP_DigestUpdate(&ctx->hash, data, len); } -void rsasha1_end_data(void) +static void rsasha1_end_data(VerifyAlgCtx *ctx_) { + VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; unsigned char digest[20]; - EVP_DigestFinal(&RSASHA.hash, digest, NULL); - memcpy(RSASHA.digest, digest, 20); + EVP_DigestFinal(&ctx->hash, digest, NULL); + memcpy(ctx->digest, digest, 20); } -void rsasha256_end_data(void) +static void rsasha256_end_data(VerifyAlgCtx *ctx_) { + VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; unsigned char digest[32]; - EVP_DigestFinal(&RSASHA.hash, digest, NULL); - memcpy(RSASHA.digest, digest, 32); + EVP_DigestFinal(&ctx->hash, digest, NULL); + memcpy(ctx->digest, digest, 32); } -int rsasha1_verify(unsigned char *key, unsigned key_len) +static int rsasha1_verify(VerifyAlgCtx *ctx_, unsigned char *key, unsigned key_len) { + VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; return 0; } -int rsasha256_verify(unsigned char *key, unsigned key_len) +static int rsasha256_verify(VerifyAlgCtx *ctx_, unsigned char *key, unsigned key_len) { + VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; return 0; } +#define DEFINE_VALG(alg) \ + int alg ## _set_signature(VerifyAlgCtx *ctx, unsigned char *data, unsigned len); \ + void alg ## _begin_data(VerifyAlgCtx *ctx); \ + void alg ## _add_data(VerifyAlgCtx *ctx, void *data, unsigned len); \ + void alg ## _end_data(VerifyAlgCtx *ctx); \ + int alg ## _verify(VerifyAlgCtx *ctx, unsigned char *key, unsigned key_len) \ + /**/ + +#define VALG_VTABLE(alg) { \ + alg ## _set_signature, \ + alg ## _begin_data, \ + alg ## _add_data, \ + alg ## _end_data, \ + alg ## _verify \ + } /**/ + +DEFINE_VALG(rsasha1); +DEFINE_VALG(rsasha256); + +/* Updated registry that merges various RFCs: + https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ +static const VerifyAlg valgs[] = +{ + {0,0,0,0,0}, /* 0: reserved */ + {0,0,0,0,0}, /* 1: RSAMD5 */ + {0,0,0,0,0}, /* 2: DH */ + {0,0,0,0,0}, /* 3: DSA */ + {0,0,0,0,0}, /* 4: ECC */ + VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ + {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ + {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ + VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ + {0,0,0,0,0}, /* 9: unassigned */ + {0,0,0,0,0}, /* 10: RSASHA512 */ + {0,0,0,0,0}, /* 11: unassigned */ + {0,0,0,0,0}, /* 12: ECC-GOST */ + {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */ + {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */ +}; + +static const int valgctx_size[] = +{ + 0, /* 0: reserved */ + 0, /* 1: RSAMD5 */ + 0, /* 2: DH */ + 0, /* 3: DSA */ + 0, /* 4: ECC */ + sizeof(VACTX_rsasha1), /* 5: RSASHA1 */ + 0, /* 6: DSA-NSEC3-SHA1 */ + 0, /* 7: RSASHA1-NSEC3-SHA1 */ + sizeof(VACTX_rsasha256), /* 8: RSASHA256 */ + 0, /* 9: unassigned */ + 0, /* 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<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); +} diff --git a/src/dnssec.c b/src/dnssec.c index 1e95479..f0e2251 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -8,27 +8,6 @@ #define SERIAL_LT -1 #define SERIAL_GT 1 -/* Updated registry that merges various RFCs: - https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ -static const VerifyAlg valgs[] = -{ - {0,0,0,0,0}, /* 0: reserved */ - {0,0,0,0,0}, /* 1: RSAMD5 */ - {0,0,0,0,0}, /* 2: DH */ - {0,0,0,0,0}, /* 3: DSA */ - {0,0,0,0,0}, /* 4: ECC */ - VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ - {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ - {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ - VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ - {0,0,0,0,0}, /* 9: unassigned */ - {0,0,0,0,0}, /* 10: RSASHA512 */ - {0,0,0,0,0}, /* 11: unassigned */ - {0,0,0,0,0}, /* 12: ECC-GOST */ - {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */ - {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */ -}; - /* Implement RFC1982 wrapped compare for 32-bit numbers */ static int serial_compare_32(unsigned long s1, unsigned long s2) { From adca3e9c4bc1552bb463dbb4222a112cd89a79fd Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:46:53 +0200 Subject: [PATCH 023/105] Refactor to use new VerifyAlg context, and start implementing logic for querying DNSKEYs. --- src/dnssec.c | 99 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index f0e2251..04b80cc 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -94,9 +94,17 @@ static int rrset_canonical_order(const void *r1, const void *r2) return res; } -static int validate_rrsig(struct dns_header *header, size_t pktlen, - unsigned char *reply, int count, char *owner, - int sigclass, int sigrdlen, unsigned char *sig) +typedef struct PendingRRSIGValidation +{ + VerifyAlgCtx *alg; + char *signer_name; + int keytag; +} PendingRRSIGValidation; + +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; @@ -119,7 +127,7 @@ static int validate_rrsig(struct dns_header *header, size_t pktlen, GETSHORT(keytag, sig); sigrdlen -= 18; - if (sigalg >= countof(valgs) || !valgs[sigalg].set_signature) + if (!verifyalg_supported(sigalg)) { printf("RRSIG algorithm not supported: %d\n", sigalg); return 0; @@ -165,42 +173,79 @@ static int validate_rrsig(struct dns_header *header, size_t pktlen, /* Now initialize the signature verification algorithm and process the whole RRset */ - const VerifyAlg *alg = &valgs[sigalg]; - if (!alg->set_signature(sig, sigrdlen)) + VerifyAlgCtx *alg = verifyalg_alloc(sigalg); + if (!alg) + return 0; + if (!alg->vtbl->set_signature(alg, sig, sigrdlen)) return 0; - alg->begin_data(); - alg->add_data(sigrdata, 18); - alg->add_data(signer_name, strlen(signer_name)-1); /* remove trailing dot */ + alg->vtbl->begin_data(alg); + alg->vtbl->add_data(alg, sigrdata, 18); + alg->vtbl->add_data(alg, signer_name, strlen(signer_name)-1); /* remove trailing dot */ for (i = 0; i < rrsetidx; ++i) { int rdlen; - alg->add_data(owner, strlen(owner)); - alg->add_data(&sigtype, 2); - alg->add_data(&sigclass, 2); - alg->add_data(&sigttl, 4); + alg->vtbl->add_data(alg, owner, strlen(owner)); + alg->vtbl->add_data(alg, &sigtype, 2); + alg->vtbl->add_data(alg, &sigclass, 2); + alg->vtbl->add_data(alg, &sigttl, 4); p = (unsigned char*)(rrset[i]); p += 8; GETSHORT(rdlen, p); /* TODO: instead of a direct add_data(), we must call a RRtype-specific function, that extract and canonicalizes domain names within RDATA. */ - alg->add_data(p-2, rdlen+2); + alg->vtbl->add_data(alg, p-2, rdlen+2); } - alg->end_data(); - - /* TODO: now we need to fetch the DNSKEY of signer_name with the specified - keytag, and check whether it validates with the current algorithm. */ - /* - pseudo-code: + alg->vtbl->end_data(alg); - char *key; int keylen; - if (!fetch_dnskey(signer_name, keytag, &key, &keylen)) - return 0; - return alg->verify(key, keylen); - */ - 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) +{ + +} + +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) + continue; + + printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag); + + if (end_rrsig_validation(&val, crecp)) + printf("Validation OK\n"); + else + printf("Validation FAILED\n"); + } + + if (!onekey) + { + printf("DNSKEY not found, need to fetch it"); + /* TODO: store PendingRRSIGValidation in routing table, + fetch key (and make it go through dnssec_parskey), then complete validation. */ + } } @@ -231,7 +276,7 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) 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? */ - validate_rrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p); + dnssec_parserrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p); } p += rdlen; } From ce2a0f5a6aa008e1094f7265b3df77a740dfb78f Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:47:56 +0200 Subject: [PATCH 024/105] Macros to simplify tentative parsing. --- src/dnssec.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index 04b80cc..c9f80ee 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -3,6 +3,25 @@ #include "dnssec-crypto.h" #include +#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 SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 From 2ef843dd16f3d065c9f10811f3f0097a6ca8f6d0 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:48:40 +0200 Subject: [PATCH 025/105] extract_name_no_compression: strip trailing dot. --- src/dnssec.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index c9f80ee..1784301 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -64,8 +64,10 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) } *buf++ = '.'; } + // Remove trailing dot (if any) + if (rr != start) + *(--buf) = 0; rr++; - *buf = 0; if (rr == end) return 0; return rr-start; @@ -200,7 +202,7 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, alg->vtbl->begin_data(alg); alg->vtbl->add_data(alg, sigrdata, 18); - alg->vtbl->add_data(alg, signer_name, strlen(signer_name)-1); /* remove trailing dot */ + alg->vtbl->add_data(alg, signer_name, strlen(signer_name)); for (i = 0; i < rrsetidx; ++i) { int rdlen; From 3471f18130362ebdd949d805cbf6a2a175a8a227 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 17:49:16 +0200 Subject: [PATCH 026/105] Start parsing DNSKEY records and insert them into cache. --- src/dnssec.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 1784301..ccafeff 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -269,6 +269,87 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, } } +/* Compute keytag (checksum to quickly index a key). See RFC4034 */ +static int dnskey_keytag(unsigned char *rdata, int rdlen) +{ + 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; +} + +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; + int explen, keytag; + unsigned long exp; + 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; + + switch (alg) + { + case 5: /* RSASHA1 */ + CHECKED_GETCHAR(explen, rdata, rdlen); + if (explen == 0) + { + printf("DNSKEY: RSASHA1: Unsupported huge exponents\n"); + return 0; + } + + if (rdlen < explen) + return 0; + printf("Alloc'ing: %d bytes\n", rdlen); + key = keydata_alloc(rdata, rdlen); + printf("Done\n"); + break; + + default: + printf("DNSKEY: Unsupported algorithm: %d\n", alg); + return 0; + } + + cache_start_insert(); + /* 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(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; + } + cache_end_insert(); + printf("DNSKEY record inserted\n"); + return 1; +} + +int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl, + int rdlen, unsigned char *rdata) +{ + return 0; +} int dnssec_validate(struct dns_header *header, size_t pktlen) { @@ -289,7 +370,17 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) GETSHORT(qclass, p); GETLONG(ttl, p); GETSHORT(rdlen, p); - if (qtype == T_RRSIG) + 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, qclass, rdlen, p); + } + else if (qtype == T_RRSIG) { printf("RRSIG found\n"); /* TODO: missing logic. We should only validate RRSIGs for which we From 6759b99e288fddeda8d30e2f22a13b950a9042df Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 18:03:24 +0200 Subject: [PATCH 027/105] Add function to extract algorithm number from context. --- src/dnssec-crypto.h | 1 + src/dnssec-openssl.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index 3591556..c89dd25 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -45,5 +45,6 @@ struct VerifyAlgCtx int verifyalg_supported(int algo); VerifyAlgCtx* verifyalg_alloc(int algo); void verifyalg_free(VerifyAlgCtx *a); +int verifyalg_algonum(VerifyAlgCtx *a); #endif /* DNSSEC_CRYPTO_H */ diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 5cf2c41..3151cdb 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -201,3 +201,11 @@ void verifyalg_free(VerifyAlgCtx *a) 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; +} From 47f99dd2b3277bc8e2fe210e779649772b00a2eb Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 18:03:52 +0200 Subject: [PATCH 028/105] Fix argument in dnssec_parsekey() call. --- src/dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index ccafeff..a248884 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -378,7 +378,7 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) else if (qtype == T_DNSKEY) { printf("DNSKEY found\n"); - dnssec_parsekey(header, pktlen, owner, qclass, rdlen, p); + dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p); } else if (qtype == T_RRSIG) { From e6c2a670fed9a6bfb89fbe469f04411704dd6b06 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 18:13:20 +0200 Subject: [PATCH 029/105] Before using a key for validation, also verify that algorithm matches. --- src/dnssec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index a248884..38507a3 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -252,6 +252,8 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, if (crecp->addr.key.keytag != val.keytag) continue; + if (crecp->addr.key.algo != verifyalg_algonum(val.alg)) + continue; printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag); From 4137b84e4ebfb3fc4d3682c34dac21ad131b6d60 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 18:13:41 +0200 Subject: [PATCH 030/105] Postpone RRSIG processing after all DNSKEY/DS have been parsed. --- src/dnssec.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 38507a3..cc12dc9 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -382,17 +382,33 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) printf("DNSKEY found\n"); dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p); } - else if (qtype == T_RRSIG) + p += rdlen; + } + + /* After we have parsed 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 (i = 0; i < ntohs(header->ancount); 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\n"); + printf("RRSIG found\n"); /* TODO: missing logic. We should only validate RRSIGs for which we - have a valid DNSKEY that is referenced by a DS record upstream. + 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, ntohs(header->ancount), owner, qclass, rdlen, p); - } + } p += rdlen; } + return 1; } From 0d829ebc6970813329f0a9728df307ab5b35f2be Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 18:17:50 +0200 Subject: [PATCH 031/105] Skip non-signing keys --- src/dnssec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index cc12dc9..6b73e3e 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -299,6 +299,9 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig if (proto != 3) return 0; + /* Skip non-signing keys (as specified in RFC4034 */ + if (!(flags & 0x100)) + return 0; switch (alg) { From ccca70cb33e3d980a9b714ea8d93ca9f722b9993 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 20:15:35 +0200 Subject: [PATCH 032/105] Change some logging messages. --- src/dnssec.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 6b73e3e..d4d202f 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -265,7 +265,7 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, if (!onekey) { - printf("DNSKEY not found, need to fetch it"); + 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. */ } @@ -315,9 +315,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig if (rdlen < explen) return 0; - printf("Alloc'ing: %d bytes\n", rdlen); key = keydata_alloc(rdata, rdlen); - printf("Done\n"); break; default: @@ -346,7 +344,6 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig return 0; } cache_end_insert(); - printf("DNSKEY record inserted\n"); return 1; } From d0edff7d6e38731e27a40cd570a811c3822937f2 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 20:16:22 +0200 Subject: [PATCH 033/105] Insert all DNSKEY/DS records into cache in one transaction. --- src/dnssec.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index d4d202f..9c45b9e 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -323,7 +323,6 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig return 0; } - cache_start_insert(); /* TODO: time(0) is correct here? */ crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY); if (crecp) @@ -343,7 +342,6 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig printf("DNSKEY: cache insertion failure\n"); return 0; } - cache_end_insert(); return 1; } @@ -364,6 +362,9 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) 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 < ntohs(header->ancount); i++) { if (!extract_name(header, pktlen, &p, owner, 1, 10)) @@ -384,8 +385,9 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) } p += rdlen; } + cache_end_insert(); - /* After we have parsed DNSKEY/DS records, start looking for RRSIGs. + /* 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; From 708bcd2dd394e1fb6fcd4e4acbc4d0fbdf954d37 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 20:19:07 +0200 Subject: [PATCH 034/105] Call valg verify functions (unimplemented for now) --- src/dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 9c45b9e..cb96eab 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -229,7 +229,7 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey) { - + 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, From 20bccd499f6b4c7def62f78a7eb2d9e58275fd22 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 25 Apr 2012 20:22:16 +0200 Subject: [PATCH 035/105] Rework the loop a little (no functionality changes) --- src/dnssec.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index cb96eab..f63871d 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -243,24 +243,24 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, 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 */ + + /* 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) - continue; - if (crecp->addr.key.algo != verifyalg_algonum(val.alg)) - continue; + 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); - printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag); - - if (end_rrsig_validation(&val, crecp)) - printf("Validation OK\n"); - else - printf("Validation FAILED\n"); + if (end_rrsig_validation(&val, crecp)) + printf("Validation OK\n"); + else + printf("Validation FAILED\n"); + } } if (!onekey) From 776fd04754056e9178187d5ea6ee9495e2c3f228 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 26 Apr 2012 14:37:10 +0200 Subject: [PATCH 036/105] Add cast to silence warning. --- src/dnssec-openssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 3151cdb..e4cc3ea 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -180,7 +180,7 @@ VerifyAlgCtx* verifyalg_alloc(int algo) for (i = 0; i < POOL_SIZE; ++i) if (!(pool_used & (1 << i))) { - ret = &Pool[i]; + ret = (VerifyAlgCtx*)&Pool[i]; pool_used |= 1 << i; break; } From a7338645d754c8adc8c7b74a83432b7654cc67a5 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 26 Apr 2012 14:37:22 +0200 Subject: [PATCH 037/105] Add a FIXME for missing logic. --- src/dnssec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dnssec.c b/src/dnssec.c index f63871d..87bfa7c 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -229,6 +229,7 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, 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); } From 458824dcb4095758b301f88724bc11f64b18210e Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:11:03 +0200 Subject: [PATCH 038/105] Helper function to walk through keydata chained blocks. --- src/cache.c | 17 +++++++++++++++++ src/dnsmasq.h | 1 + 2 files changed, 18 insertions(+) diff --git a/src/cache.c b/src/cache.c index 55dd2b9..9fd1cf2 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1364,6 +1364,23 @@ struct keydata *keydata_alloc(char *data, size_t len) return ret; } +size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) +{ + size_t ret; + + 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/dnsmasq.h b/src/dnsmasq.h index 6c62a52..4331066 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -925,6 +925,7 @@ char *cache_get_name(struct crec *crecp); 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 From 4c70046d9376a6b6caeb887e0cc6620974a885da Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:11:22 +0200 Subject: [PATCH 039/105] Move helper functions to common header file. --- src/dns-protocol.h | 17 +++++++++++++++++ src/dnssec.c | 19 ------------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 3aa4fec..77bcd58 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -117,3 +117,20 @@ 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) diff --git a/src/dnssec.c b/src/dnssec.c index 87bfa7c..ff556fe 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -3,25 +3,6 @@ #include "dnssec-crypto.h" #include -#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 SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 From 262ac85107bed292a75c454b3b5a3c36be129b65 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:13:34 +0200 Subject: [PATCH 040/105] verify() function must take a keydata chained buffer for input key. --- src/dnssec-crypto.h | 4 +++- src/dnssec-openssl.c | 6 +++--- src/dnssec.c | 20 +------------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index c89dd25..31b20ac 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -1,6 +1,8 @@ #ifndef DNSSEC_CRYPTO_H #define DNSSEC_CRYPTO_H +struct keydata; + /* * vtable for a signature verification algorithm. * @@ -34,7 +36,7 @@ typedef struct void (*begin_data)(VerifyAlgCtx *ctx); void (*add_data)(VerifyAlgCtx *ctx, void *data, unsigned len); void (*end_data)(VerifyAlgCtx *ctx); - int (*verify)(VerifyAlgCtx *ctx, unsigned char *key, unsigned key_len); + int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len); } VerifyAlg; struct VerifyAlgCtx diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index e4cc3ea..e74c9d2 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -90,13 +90,13 @@ static void rsasha256_end_data(VerifyAlgCtx *ctx_) memcpy(ctx->digest, digest, 32); } -static int rsasha1_verify(VerifyAlgCtx *ctx_, unsigned char *key, unsigned key_len) +static int rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) { VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; return 0; } -static int rsasha256_verify(VerifyAlgCtx *ctx_, unsigned char *key, unsigned key_len) +static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key, unsigned key_len) { VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; return 0; @@ -107,7 +107,7 @@ static int rsasha256_verify(VerifyAlgCtx *ctx_, unsigned char *key, unsigned key void alg ## _begin_data(VerifyAlgCtx *ctx); \ void alg ## _add_data(VerifyAlgCtx *ctx, void *data, unsigned len); \ void alg ## _end_data(VerifyAlgCtx *ctx); \ - int alg ## _verify(VerifyAlgCtx *ctx, unsigned char *key, unsigned key_len) \ + int alg ## _verify(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len) \ /**/ #define VALG_VTABLE(alg) { \ diff --git a/src/dnssec.c b/src/dnssec.c index ff556fe..a19d5e1 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -285,25 +285,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig if (!(flags & 0x100)) return 0; - switch (alg) - { - case 5: /* RSASHA1 */ - CHECKED_GETCHAR(explen, rdata, rdlen); - if (explen == 0) - { - printf("DNSKEY: RSASHA1: Unsupported huge exponents\n"); - return 0; - } - - if (rdlen < explen) - return 0; - key = keydata_alloc(rdata, rdlen); - break; - - default: - printf("DNSKEY: Unsupported algorithm: %d\n", alg); - return 0; - } + key = keydata_alloc(rdata, rdlen); /* TODO: time(0) is correct here? */ crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY); From 0360a524df748629d28b93d1845c128c1c8119b5 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:14:15 +0200 Subject: [PATCH 041/105] Implement RSA verification. --- src/dnssec-openssl.c | 56 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index e74c9d2..59504c4 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -2,6 +2,7 @@ #include "dnsmasq.h" #include "dnssec-crypto.h" #include +#include typedef struct VACTX_rsasha1 { @@ -90,10 +91,63 @@ static void rsasha256_end_data(VerifyAlgCtx *ctx_) memcpy(ctx->digest, digest, 32); } +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 rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) { VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; - return 0; + int validated = 0; + + printf("OpenSSL RSA verification\n"); + 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_sha1, ctx->digest, 20, ctx->sig, ctx->siglen, rsa)) + validated = 1; + + RSA_free(rsa); + return validated; } static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key, unsigned key_len) From 4b0eecbb4415039ddfa3d589c09083f4076797c2 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:18:52 +0200 Subject: [PATCH 042/105] Bugfix: rdata flags must go through hash function in network byte order. --- src/dnssec.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index a19d5e1..a0ca53d 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -181,6 +181,10 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, if (!alg->vtbl->set_signature(alg, sig, sigrdlen)) return 0; + sigtype = htons(sigtype); + sigclass = htons(sigclass); + sigttl = htonl(sigttl); + alg->vtbl->begin_data(alg); alg->vtbl->add_data(alg, sigrdata, 18); alg->vtbl->add_data(alg, signer_name, strlen(signer_name)); From 13e435ebcaa5170879218d1c8c28178c90fefacf Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:19:40 +0200 Subject: [PATCH 043/105] Bugfix: domain names must go through hash function in DNS format (but uncompressed!) --- src/dnssec.c | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index a0ca53d..fe93036 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -103,6 +103,27 @@ typedef struct PendingRRSIGValidation int keytag; } PendingRRSIGValidation; +/* Pass a domain name through a verification hash function. + + We must pass domain names in DNS wire format, but uncompressed. + This means that we cannot directly use raw data from the original + message since it might be compressed. */ +static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) +{ + unsigned char len; char *p; + + while ((p = strchr(name, '.'))) + { + len = p-name; + alg->vtbl->add_data(alg, &len, 1); + alg->vtbl->add_data(alg, name, len); + name = p+1; + } + len = strlen(name); + alg->vtbl->add_data(alg, &len, 1); + alg->vtbl->add_data(alg, name, len+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, @@ -114,6 +135,7 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, 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; @@ -169,10 +191,10 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); /* Extract the signer name (we need to query DNSKEY of this name) */ - if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name))) + if (!(signer_name_rdlen = extract_name_no_compression(sig, sigrdlen, signer_name))) return 0; - sig += res; sigrdlen -= res; - + sig += signer_name_rdlen; sigrdlen -= signer_name_rdlen; + /* Now initialize the signature verification algorithm and process the whole RRset */ VerifyAlgCtx *alg = verifyalg_alloc(sigalg); @@ -186,18 +208,17 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, sigttl = htonl(sigttl); alg->vtbl->begin_data(alg); - alg->vtbl->add_data(alg, sigrdata, 18); - alg->vtbl->add_data(alg, signer_name, strlen(signer_name)); + alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen); for (i = 0; i < rrsetidx; ++i) { int rdlen; - - alg->vtbl->add_data(alg, owner, strlen(owner)); + p = (unsigned char*)(rrset[i]); + + verifyalg_add_data_domain(alg, owner); alg->vtbl->add_data(alg, &sigtype, 2); alg->vtbl->add_data(alg, &sigclass, 2); alg->vtbl->add_data(alg, &sigttl, 4); - p = (unsigned char*)(rrset[i]); p += 8; GETSHORT(rdlen, p); /* TODO: instead of a direct add_data(), we must call a RRtype-specific From 4e076d746f2574ce3ce82950ce90c0187a9dc612 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Fri, 27 Apr 2012 03:24:12 +0200 Subject: [PATCH 044/105] Debug function. --- src/dnssec-openssl.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 59504c4..5a0869f 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -36,6 +36,16 @@ static union _Pool } 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 rsasha1_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) { VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; From 32f82c62c86c905cab78e07e77325f3c592787ef Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 01:01:16 +0200 Subject: [PATCH 045/105] Export skip_name function. --- src/dnsmasq.h | 1 + src/rfc1035.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 4331066..429e95f 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -940,6 +940,7 @@ 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); diff --git a/src/rfc1035.c b/src/rfc1035.c index 60ed068..a5a769a 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -274,7 +274,7 @@ int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) 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) { From 79333a249888f930c51d88804a3484a1d5a54620 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 01:03:10 +0200 Subject: [PATCH 046/105] Fix a bug in extract_name_no_compression. When the maxlen was exactly equal to the length of the string, the function was returning 0 because the end-of-buffer check was misplaced. --- src/dnssec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index fe93036..8b4efaf 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -42,16 +42,16 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) if (*buf >= 'A' && *buf <= 'Z') *buf += 'a' - 'A'; buf++; - } + } *buf++ = '.'; } // Remove trailing dot (if any) if (rr != start) *(--buf) = 0; - rr++; if (rr == end) return 0; - return rr-start; + // Trailing \0 in source data must be consumed + return rr-start+1; } /* Check whether today/now is between date_start and date_end */ From 00b963ab720d13214d2e20534a632dc75f00414f Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 01:03:22 +0200 Subject: [PATCH 047/105] Improve logging message. --- src/dnssec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 8b4efaf..679b77d 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -390,7 +390,7 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) GETSHORT(rdlen, p); if (qtype == T_RRSIG) { - printf("RRSIG found\n"); + 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 From 50a96b62f1384cf45ead6fc529eded48e6528ef2 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 01:04:56 +0200 Subject: [PATCH 048/105] Fix a validation bug when owner != signer. Since owner and signer are both domain names and share the same buffer in memory (daemon->namebuff), we need to go through a little hoop to make sure one doesn't step on the other's toes. We don't really need to extract the signer name until we have finished calculating the hash of the RRset, so we postpone its extraction. --- src/dnssec.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 679b77d..2991fe2 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -190,10 +190,13 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, /* Sort RRset records in canonical order. */ qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); - /* Extract the signer name (we need to query DNSKEY of this name) */ - if (!(signer_name_rdlen = extract_name_no_compression(sig, sigrdlen, signer_name))) + /* 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; - sig += signer_name_rdlen; sigrdlen -= signer_name_rdlen; + signer_name_rdlen = p - sig; + sig = p; sigrdlen -= signer_name_rdlen; /* Now initialize the signature verification algorithm and process the whole RRset */ @@ -227,6 +230,10 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, } alg->vtbl->end_data(alg); + /* 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; From 28f04fd647dd27d1b59211ab1bc1e91a316e8e22 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 03:46:59 +0200 Subject: [PATCH 049/105] Remove unused variable. --- src/cache.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cache.c b/src/cache.c index 9fd1cf2..a589079 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1366,8 +1366,6 @@ struct keydata *keydata_alloc(char *data, size_t len) size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) { - size_t ret; - if (*p == NULL) *p = (*key)->key; else if (*p == (*key)->key + KEYBLOCK_LEN) From dd090561bf90da65c32ad44e3211e27144138278 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 03:47:10 +0200 Subject: [PATCH 050/105] Convert to C-style comments. --- src/dnssec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 2991fe2..6e93dae 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -45,12 +45,12 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) } *buf++ = '.'; } - // Remove trailing dot (if any) + /* Remove trailing dot (if any) */ if (rr != start) *(--buf) = 0; if (rr == end) return 0; - // Trailing \0 in source data must be consumed + /* Trailing \0 in source data must be consumed */ return rr-start+1; } From a55ce08cc0a162e7632bb99dc62bda027f5c5d0d Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 03:48:09 +0200 Subject: [PATCH 051/105] Silence a few warnings. --- src/dnssec-openssl.c | 1 + src/dnssec.c | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 5a0869f..7c39fd4 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -163,6 +163,7 @@ static int rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key, unsigned key_len) { VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; + (void)ctx; return 0; } diff --git a/src/dnssec.c b/src/dnssec.c index 6e93dae..f17c158 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -261,7 +261,7 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, /* 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) */ + while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */ { onekey = 1; @@ -303,8 +303,6 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig { int flags, proto, alg; struct keydata *key; struct crec *crecp; - int explen, keytag; - unsigned long exp; unsigned char *ordata = rdata; int ordlen = rdlen; CHECKED_GETSHORT(flags, rdata, rdlen); @@ -317,7 +315,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig if (!(flags & 0x100)) return 0; - key = keydata_alloc(rdata, rdlen); + key = keydata_alloc((char*)rdata, rdlen); /* TODO: time(0) is correct here? */ crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY); From 0852d76b5812f05722b75d578a927c0dd7c537c2 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 03:49:24 +0200 Subject: [PATCH 052/105] Start implementing canonicalization of RDATA wire formats. --- src/dnssec.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index f17c158..99760b1 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -124,6 +124,98 @@ static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) alg->vtbl->add_data(alg, name, len+1); } +/* Pass a DNS domain name in wire format through a hash function. Returns the + total number of bytes passed through the function or 0 in case of errors. + Updates the rdata pointer moving it further within the RR. + + If alg is NULL, go in dry run mode (still correctly updates rdata and return + the correct total). + + The function canonicalizes the domain name (RFC 4034, §6.2), which basically + means conversion to lower case, and uncompression. */ +static int verifyalg_add_data_wire_domain(VerifyAlgCtx *alg, struct dns_header *header, size_t pktlen, + unsigned char** rdata) +{ + int hops = 0, total = 0; + unsigned char label_type; + unsigned char *end = (unsigned char *)header + pktlen; + unsigned char count; unsigned char *p = *rdata; + + while (1) + { + if (p >= end) + return 0; + if (!(count = *p++)) + break; + label_type = count & 0xC0; + if (label_type == 0xC0) + { + if (p >= end) + return 0; + p = (unsigned char*)header + (count & 0x3F) * 256 + *p; + if (hops == 0) + *rdata = p; + if (++hops == 256) + return 0; + } + else if (label_type == 0x00) + { + if (p+count-1 >= end) + return 0; + if (alg) + { + alg->vtbl->add_data(alg, &count, 1); + /* TODO: missing conversion to lower-case and alphabet check */ + alg->vtbl->add_data(alg, p, count); + } + total += count+1; + p += count; + } + else + return 0; /* unsupported label_type */ + } + + if (hops == 0) + *rdata = p; + if (alg) + alg->vtbl->add_data(alg, &count, 1); + return total+1; +} + +/* Pass a resource record's rdata field through a verification hash function. + + 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 verifyalg_add_rdata(VerifyAlgCtx *alg, int sigtype, struct dns_header *header, size_t pktlen, + unsigned char *rdata) +{ + unsigned char *p; + int res; unsigned short rdlen; + + GETSHORT(rdlen, rdata); + p = rdata; + + switch (sigtype) + { + /* TODO: missing lots of RR types, see RFC4034, §6.2 */ + case T_CNAME: + if (!(res = verifyalg_add_data_wire_domain(NULL, header, pktlen, &p))) + return 0; + if (p - rdata > rdlen) + return 0; + rdlen = htons(res); + alg->vtbl->add_data(alg, &rdlen, 2); + verifyalg_add_data_wire_domain(alg, header, pktlen, &rdata); + break; + + default: + alg->vtbl->add_data(alg, rdata-2, rdlen+2); + break; + } + 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, @@ -214,7 +306,6 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen); for (i = 0; i < rrsetidx; ++i) { - int rdlen; p = (unsigned char*)(rrset[i]); verifyalg_add_data_domain(alg, owner); @@ -223,10 +314,8 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, alg->vtbl->add_data(alg, &sigttl, 4); p += 8; - GETSHORT(rdlen, p); - /* TODO: instead of a direct add_data(), we must call a RRtype-specific - function, that extract and canonicalizes domain names within RDATA. */ - alg->vtbl->add_data(alg, p-2, rdlen+2); + if (!verifyalg_add_rdata(alg, ntohs(sigtype), header, pktlen, p)) + return 0; } alg->vtbl->end_data(alg); From 41de7442d25c601f505c1061ba2566c5c1239aed Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 03:59:49 +0200 Subject: [PATCH 053/105] Reformat some code (no semantic difference). --- src/dnssec.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 99760b1..f011adb 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -62,11 +62,8 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) unsigned long curtime = time(0); /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ - if (serial_compare_32(curtime, date_start) != SERIAL_GT) - return 0; - if (serial_compare_32(curtime, date_end) != SERIAL_LT) - return 0; - return 1; + 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 From e83297d0f60d0b04c173c23250d0f98ca3ddc2ca Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 12:20:53 +0200 Subject: [PATCH 054/105] RSASHA1-NSEC3-SHA1 is equivalent to RSASHA1 for the purpose of RRSIG validation. --- src/dnssec-openssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 7c39fd4..e24941e 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -197,7 +197,7 @@ static const VerifyAlg valgs[] = {0,0,0,0,0}, /* 4: ECC */ VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ - {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */ + VALG_VTABLE(rsasha1), /* 7: RSASHA1-NSEC3-SHA1 */ VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ {0,0,0,0,0}, /* 9: unassigned */ {0,0,0,0,0}, /* 10: RSASHA512 */ @@ -216,7 +216,7 @@ static const int valgctx_size[] = 0, /* 4: ECC */ sizeof(VACTX_rsasha1), /* 5: RSASHA1 */ 0, /* 6: DSA-NSEC3-SHA1 */ - 0, /* 7: RSASHA1-NSEC3-SHA1 */ + sizeof(VACTX_rsasha1), /* 7: RSASHA1-NSEC3-SHA1 */ sizeof(VACTX_rsasha256), /* 8: RSASHA256 */ 0, /* 9: unassigned */ 0, /* 10: RSASHA512 */ From 23c21766814cf049658c1beb8b7c3685fa29e56f Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 12:22:41 +0200 Subject: [PATCH 055/105] Process RRSIGs also in authority and additional sections. --- src/dnssec.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index f011adb..2cc7a88 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -435,17 +435,18 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) { unsigned char *p, *reply; char *owner = daemon->namebuff; - int i, qtype, qclass, rdlen; + int i, s, qtype, qclass, rdlen; unsigned long ttl; + int slen[3] = { ntohs(header->ancount), ntohs(header->nscount), ntohs(header->arcount) }; - if (header->ancount == 0) + 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 < ntohs(header->ancount); i++) + for (i = 0; i < slen[0]; i++) { if (!extract_name(header, pktlen, &p, owner, 1, 10)) return 0; @@ -471,25 +472,29 @@ int dnssec_validate(struct dns_header *header, size_t pktlen) 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 (i = 0; i < ntohs(header->ancount); i++) + for (s = 0; s < 3; ++s) { - 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) + reply = p; + for (i = 0; i < slen[s]; i++) { - 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, ntohs(header->ancount), owner, qclass, rdlen, p); + 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; } - p += rdlen; } return 1; From d1ca25ca7ec1ed3a428ac65269a8ba6f1f5007b2 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 12:23:04 +0200 Subject: [PATCH 056/105] Canonicalize NS records. --- src/dnssec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dnssec.c b/src/dnssec.c index 2cc7a88..6c11738 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -195,6 +195,7 @@ static int verifyalg_add_rdata(VerifyAlgCtx *alg, int sigtype, struct dns_header switch (sigtype) { /* TODO: missing lots of RR types, see RFC4034, §6.2 */ + case T_NS: case T_CNAME: if (!(res = verifyalg_add_data_wire_domain(NULL, header, pktlen, &p))) return 0; From 02bff4f10933ce7f417e1d7b8918f9dc7a88b593 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 12:59:05 +0200 Subject: [PATCH 057/105] Implement RSASHA256. --- src/dnssec-openssl.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index e24941e..b9771dd 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -147,11 +147,9 @@ static int rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; int validated = 0; - printf("OpenSSL RSA verification\n"); 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_sha1, ctx->digest, 20, ctx->sig, ctx->siglen, rsa)) validated = 1; @@ -160,11 +158,20 @@ static int rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned return validated; } -static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key, unsigned key_len) +static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) { VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; - (void)ctx; - return 0; + 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_sha256, ctx->digest, 32, ctx->sig, ctx->siglen, rsa)) + validated = 1; + + RSA_free(rsa); + return validated; } #define DEFINE_VALG(alg) \ From 7f0485cf5344f112c9b4e986fa894263002f1f94 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 28 Apr 2012 12:59:49 +0200 Subject: [PATCH 058/105] verifyalg_add_data_domain: fix for root domain (""). --- src/dnssec.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 6c11738..0858b0b 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -100,6 +100,17 @@ typedef struct PendingRRSIGValidation int keytag; } PendingRRSIGValidation; +/* strchrnul - like strchr, but when character is not found, returns a pointer to the terminating \0. + + This is an existing C GNU extension, but it's easier to reimplement it, + rather than tweaking with configure. */ +static char *strchrnul(char *str, char ch) +{ + while (*str && *str != ch) + str++; + return str; +} + /* Pass a domain name through a verification hash function. We must pass domain names in DNS wire format, but uncompressed. @@ -109,16 +120,19 @@ static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) { unsigned char len; char *p; - while ((p = strchr(name, '.'))) + do { - len = p-name; - alg->vtbl->add_data(alg, &len, 1); - alg->vtbl->add_data(alg, name, len); + p = strchrnul(name, '.'); + if ((len = p-name)) + { + alg->vtbl->add_data(alg, &len, 1); + alg->vtbl->add_data(alg, name, len); + } name = p+1; } - len = strlen(name); - alg->vtbl->add_data(alg, &len, 1); - alg->vtbl->add_data(alg, name, len+1); + while (*p); + + alg->vtbl->add_data(alg, "\0", 1); } /* Pass a DNS domain name in wire format through a hash function. Returns the From 6299ffbe60ae614377b44184978da5f23e1f1699 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 1 May 2012 18:27:52 +0200 Subject: [PATCH 059/105] Start refactoring for correct handling of domain wire-format. Introduce utility functions and RDATA meta-description. --- src/dnssec.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index 0858b0b..a106fa5 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -3,6 +3,9 @@ #include "dnssec-crypto.h" #include +/* 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 @@ -54,6 +57,187 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) 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 PDN_EXTRACT 0 +#define PDN_COMPARE 1 +#define PDN_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) + { + if (p >= end) + return 0; + 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 + *p; + } + 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 }, +}; + + /* Check whether today/now is between date_start and date_end */ static int check_date_range(unsigned long date_start, unsigned long date_end) { From 0ca895f585305fbb20edfa8a6f58ec7bbf009d11 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Tue, 1 May 2012 18:28:43 +0200 Subject: [PATCH 060/105] Fix rrset_canonical_order() to correct handle canonicalization of domain names in RDATA. --- src/dnssec.c | 72 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index a106fa5..8851492 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -250,31 +250,68 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) && 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) { - int r1len, r2len, res; - const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2; + size_t r1len, r2len; + int rrtype, i; + unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2; + unsigned char tmp1[MAXCDNAME], tmp2[MAXCDNAME]; /* TODO: use part of daemon->namebuff */ - pr1 += 8; pr2 += 8; + #define ORDER(buf1,len1, buf2,len2) \ + do { \ + int res = memcmp(buf1, buf2, MIN(len1,len2)); \ + if (res != 0) return res; \ + if (len1 < len2) return -1; \ + if (len1 > len2) return 1; \ + } while (0) + + GETSHORT(rrtype, pr1); + pr1 += 6; pr2 += 8; GETSHORT(r1len, pr1); GETSHORT(r2len, pr2); - /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */ - res = memcmp(pr1, pr2, MIN(r1len, r2len)); - if (res == 0) - { - if (r1len < r2len) - return -1; - else - /* NOTE: 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; - } - - return res; + if (rrtype < countof(rdata_description)) + for (i = 0; rdata_description[rrtype][i] != RDESC_END; ++i) + { + int d = rdata_description[rrtype][i]; + if (d == RDESC_DOMAIN) + { + int dl1 = process_domain_name(rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen, + &pr1, &r1len, tmp1, PWN_EXTRACT); + int dl2 = process_domain_name(rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen, + &pr2, &r2len, tmp2, PWN_EXTRACT); + /* TODO: how do we handle errors, that is dl1==0 or dl2==0 ? */ + assert(dl1 != 0); + assert(dl2 != 0); + ORDER(tmp1, dl1, tmp2, dl2); + } + else + { + ORDER(pr1, d, pr2, d); + pr1 += d; pr2 += d; + r1len -= d; r2len -= d; + } + } + + /* Order the rest of the record. */ + ORDER(pr1, r1len, pr2, r2len); + + /* If we reached this point, the two RRs are identical. + 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; + + #undef ORDER } typedef struct PendingRRSIGValidation @@ -319,6 +356,7 @@ static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) alg->vtbl->add_data(alg, "\0", 1); } + /* Pass a DNS domain name in wire format through a hash function. Returns the total number of bytes passed through the function or 0 in case of errors. Updates the rdata pointer moving it further within the RR. @@ -476,6 +514,8 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, } /* 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 From ec2962eacbaddebbc03a511a5646bc9f9272185c Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 00:15:26 +0200 Subject: [PATCH 061/105] Fix the macro names. --- src/dnssec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 8851492..6bb7a05 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -100,9 +100,9 @@ static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) * larger, reflecting the total number of octects composing the domain name. * */ -#define PDN_EXTRACT 0 -#define PDN_COMPARE 1 -#define PDN_ORDER 2 +#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) From 0db0e0c2163395949d878e689f62e3fc804c0090 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 00:16:08 +0200 Subject: [PATCH 062/105] Fix a bug in rdlen update while decompressing a name --- src/dnssec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 6bb7a05..6cf115c 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -141,8 +141,10 @@ static int process_domain_name(struct dns_header *header, size_t pktlen, label_type = count & 0xC0; if (label_type == 0xC0) { + int l2; if (p >= end) return 0; + l2 = *p++; if (hops == 0) { if (p - *rdata > *rdlen) @@ -152,7 +154,7 @@ static int process_domain_name(struct dns_header *header, size_t pktlen, } if (++hops == 256) return 0; - p = (unsigned char*)header + (count & 0x3F) * 256 + *p; + p = (unsigned char*)header + (count & 0x3F) * 256 + l2; } else if (label_type == 0x00) { From 4885d57c58e6cec9b33db4ee5192c025133b1dc0 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 00:30:56 +0200 Subject: [PATCH 063/105] Add rdata canonicalization functions. --- src/dnssec.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index 6cf115c..b5a94f2 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -240,6 +240,93 @@ static const int rdata_description[][8] = }; +/* 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) { From da23c4f96006b04d8d1203aca7c32eb61c0f6a33 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 00:31:19 +0200 Subject: [PATCH 064/105] Simplify rrset_canonical_order() with new canonicalization functions. --- src/dnssec.c | 56 +++++++++++++++++----------------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index b5a94f2..5411d3b 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -353,54 +353,34 @@ struct { static int rrset_canonical_order(const void *r1, const void *r2) { size_t r1len, r2len; - int rrtype, i; + int rrtype; unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2; unsigned char tmp1[MAXCDNAME], tmp2[MAXCDNAME]; /* TODO: use part of daemon->namebuff */ - #define ORDER(buf1,len1, buf2,len2) \ - do { \ - int res = memcmp(buf1, buf2, MIN(len1,len2)); \ - if (res != 0) return res; \ - if (len1 < len2) return -1; \ - if (len1 > len2) return 1; \ - } while (0) - GETSHORT(rrtype, pr1); pr1 += 6; pr2 += 8; - GETSHORT(r1len, pr1); GETSHORT(r2len, pr2); - if (rrtype < countof(rdata_description)) - for (i = 0; rdata_description[rrtype][i] != RDESC_END; ++i) - { - int d = rdata_description[rrtype][i]; - if (d == RDESC_DOMAIN) - { - int dl1 = process_domain_name(rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen, - &pr1, &r1len, tmp1, PWN_EXTRACT); - int dl2 = process_domain_name(rrset_canonical_order_ctx.header, rrset_canonical_order_ctx.pktlen, - &pr2, &r2len, tmp2, PWN_EXTRACT); - /* TODO: how do we handle errors, that is dl1==0 or dl2==0 ? */ - assert(dl1 != 0); - assert(dl2 != 0); - ORDER(tmp1, dl1, tmp2, dl2); - } - else - { - ORDER(pr1, d, pr2, d); - pr1 += d; pr2 += d; - r1len -= d; r2len -= d; - } - } + 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; + } - /* Order the rest of the record. */ - ORDER(pr1, r1len, pr2, r2len); - - /* If we reached this point, the two RRs are identical. + /* 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; - - #undef ORDER } typedef struct PendingRRSIGValidation From f119ed382ec3f27299e37a5c993c2cf8a123c198 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 00:31:55 +0200 Subject: [PATCH 065/105] Simplify verifyalg_add_rdata() with new canonicalization functions. --- src/dnssec.c | 100 ++++++++++++--------------------------------------- 1 file changed, 22 insertions(+), 78 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 5411d3b..47bbbc4 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -426,64 +426,6 @@ static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) } -/* Pass a DNS domain name in wire format through a hash function. Returns the - total number of bytes passed through the function or 0 in case of errors. - Updates the rdata pointer moving it further within the RR. - - If alg is NULL, go in dry run mode (still correctly updates rdata and return - the correct total). - - The function canonicalizes the domain name (RFC 4034, §6.2), which basically - means conversion to lower case, and uncompression. */ -static int verifyalg_add_data_wire_domain(VerifyAlgCtx *alg, struct dns_header *header, size_t pktlen, - unsigned char** rdata) -{ - int hops = 0, total = 0; - unsigned char label_type; - unsigned char *end = (unsigned char *)header + pktlen; - unsigned char count; unsigned char *p = *rdata; - - while (1) - { - if (p >= end) - return 0; - if (!(count = *p++)) - break; - label_type = count & 0xC0; - if (label_type == 0xC0) - { - if (p >= end) - return 0; - p = (unsigned char*)header + (count & 0x3F) * 256 + *p; - if (hops == 0) - *rdata = p; - if (++hops == 256) - return 0; - } - else if (label_type == 0x00) - { - if (p+count-1 >= end) - return 0; - if (alg) - { - alg->vtbl->add_data(alg, &count, 1); - /* TODO: missing conversion to lower-case and alphabet check */ - alg->vtbl->add_data(alg, p, count); - } - total += count+1; - p += count; - } - else - return 0; /* unsupported label_type */ - } - - if (hops == 0) - *rdata = p; - if (alg) - alg->vtbl->add_data(alg, &count, 1); - return total+1; -} - /* Pass a resource record's rdata field through a verification hash function. We must pass the record in DNS wire format, but if the record contains domain names, @@ -491,30 +433,32 @@ static int verifyalg_add_data_wire_domain(VerifyAlgCtx *alg, struct dns_header * static int verifyalg_add_rdata(VerifyAlgCtx *alg, int sigtype, struct dns_header *header, size_t pktlen, unsigned char *rdata) { + size_t len; unsigned char *p; - int res; unsigned short rdlen; + unsigned short total; + unsigned char tmpbuf[MAXDNAME]; /* TODO: reuse part of daemon->namebuff */ + RDataCForm cf1, cf2; - GETSHORT(rdlen, rdata); - p = rdata; + /* Initialize two iterations over the canonical form*/ + rdata_cform_init(&cf1, header, pktlen, rdata, sigtype, tmpbuf); + cf2 = cf1; - switch (sigtype) - { - /* TODO: missing lots of RR types, see RFC4034, §6.2 */ - case T_NS: - case T_CNAME: - if (!(res = verifyalg_add_data_wire_domain(NULL, header, pktlen, &p))) - return 0; - if (p - rdata > rdlen) - return 0; - rdlen = htons(res); - alg->vtbl->add_data(alg, &rdlen, 2); - verifyalg_add_data_wire_domain(alg, header, pktlen, &rdata); - break; + /* 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); + alg->vtbl->add_data(alg, &total, 2); + + while ((p = rdata_cform_next(&cf2, &len))) + alg->vtbl->add_data(alg, p, len); - default: - alg->vtbl->add_data(alg, rdata-2, rdlen+2); - break; - } return 1; } From 785ee80b9395b457c9331e1d2f2e98d804f1771e Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 01:14:26 +0200 Subject: [PATCH 066/105] Describe SOA rdata section. --- src/dnssec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dnssec.c b/src/dnssec.c index 47bbbc4..a9a236b 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -237,6 +237,7 @@ static const int rdata_description[][8] = /* 3: .. */ { RDESC_END }, /* 4: .. */ { RDESC_END }, /* 5: CNAME */ { RDESC_DOMAIN, RDESC_END }, + /* 6: SOA */ { RDESC_DOMAIN, RDESC_DOMAIN, RDESC_END }, }; From 0937692dc6b4c091b99455300bee1892e0a6ab85 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 01:22:48 +0200 Subject: [PATCH 067/105] Add rdata description for MX. --- src/dnssec.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index a9a236b..72fd07d 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -238,6 +238,15 @@ static const int rdata_description[][8] = /* 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 }, }; From 32b826e2a004bc050e4210ce6206fce8c353f6f3 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 03:28:41 +0200 Subject: [PATCH 068/105] Genericize verifyalg_add_data_domain() (rename to convert_domain_to_wire()). --- src/dnssec.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 72fd07d..aa25a51 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -411,28 +411,28 @@ static char *strchrnul(char *str, char ch) return str; } -/* Pass a domain name through a verification hash function. - - We must pass domain names in DNS wire format, but uncompressed. - This means that we cannot directly use raw data from the original - message since it might be compressed. */ -static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) +/* Convert a domain name to wire format */ +static int convert_domain_to_wire(char *name, unsigned char* out) { - unsigned char len; char *p; + unsigned char len; + unsigned char *start = out; + char *p; do { p = strchrnul(name, '.'); if ((len = p-name)) { - alg->vtbl->add_data(alg, &len, 1); - alg->vtbl->add_data(alg, name, len); + *out++ = len; + memcpy(out, name, len); + out += len; } name = p+1; } while (*p); - alg->vtbl->add_data(alg, "\0", 1); + *out++ = '\0'; + return out-start; } @@ -561,13 +561,20 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, 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); + alg->vtbl->begin_data(alg); alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen); for (i = 0; i < rrsetidx; ++i) { p = (unsigned char*)(rrset[i]); - verifyalg_add_data_domain(alg, owner); + alg->vtbl->add_data(alg, owner_wire, owner_wire_len); alg->vtbl->add_data(alg, &sigtype, 2); alg->vtbl->add_data(alg, &sigclass, 2); alg->vtbl->add_data(alg, &sigttl, 4); From f5adbb90a13f6fe8d289b543203734cae3425e76 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 03:28:59 +0200 Subject: [PATCH 069/105] Implement digest algorithm support. --- src/dnssec-crypto.h | 7 +++++++ src/dnssec-openssl.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index 31b20ac..33be969 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -49,4 +49,11 @@ VerifyAlgCtx* verifyalg_alloc(int algo); void verifyalg_free(VerifyAlgCtx *a); int verifyalg_algonum(VerifyAlgCtx *a); +/* Functions to calculate the digest of a key */ +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); +int digestalg_final(struct keydata *digest); + #endif /* DNSSEC_CRYPTO_H */ diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index b9771dd..d35fc19 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -281,3 +281,47 @@ int verifyalg_algonum(VerifyAlgCtx *a) return -1; return num; } + +static EVP_MD_CTX digctx; + +int digestalg_supported(int algo) +{ + return (algo == 1 || algo == 2); +} + +int digestalg_begin(int algo) +{ + EVP_MD_CTX_init(&digctx); + if (algo == 1) + EVP_DigestInit_ex(&digctx, EVP_sha1(), NULL); + else if (algo == 2) + EVP_DigestInit_ex(&digctx, EVP_sha256(), NULL); + else + return 0; + return 1; +} + +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; + } +} + +int digestalg_final(struct keydata *expected) +{ + unsigned char digest[32]; + EVP_DigestFinal(&digctx, digest, NULL); + /* FIXME: why EVP_MD_CTX_size() crashes? */ + return (memcmp(digest, expected->key, 20) == 0); +} From 0304d28f7ec8ebabf8b8d01030fbd10233a1fc73 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 03:29:52 +0200 Subject: [PATCH 070/105] Parse and match DS records. --- src/dnssec.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/dnssec.c b/src/dnssec.c index aa25a51..826f1ba 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -653,6 +653,28 @@ static int dnskey_keytag(unsigned char *rdata, int rdlen) 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; + + if (!digestalg_begin(dig)) + return 0; + 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 digestalg_final(ds->addr.key.keydata); +} + int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl, int rdlen, unsigned char *rdata) { @@ -697,6 +719,48 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig 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 0; } From b58fb39f24e2e5476be071434c3af7dff10762cc Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 03:53:46 +0200 Subject: [PATCH 071/105] Since extract_name() does not convert to lowercase, do it temporarly within convert_domain_to_wire(). --- src/dnssec.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 826f1ba..b927320 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -424,8 +424,15 @@ static int convert_domain_to_wire(char *name, unsigned char* out) if ((len = p-name)) { *out++ = len; - memcpy(out, name, len); - 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; } From ed1fc985951ad5126bd0ff94721d102886c8f692 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 15:47:28 +0200 Subject: [PATCH 072/105] Untangle digestalg from verifyalg; better separation, less code duplication. --- src/dnssec-crypto.h | 27 ++++++----- src/dnssec-openssl.c | 112 ++++++++++++++++--------------------------- src/dnssec.c | 28 ++++++----- 3 files changed, 73 insertions(+), 94 deletions(-) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index 33be969..3daac0b 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -15,13 +15,13 @@ struct keydata; * // nor consumed, so the pointer must stay valid. * alg->set_signature(sig, 16); * - * // Second, push the data in; data is consumed immediately, so the buffer - * // can be freed or modified. - * alg->begin_data(); - * alg->add_data(buf1, 123); - * alg->add_data(buf2, 45); - * alg->add_data(buf3, 678); - * alg->end_data(); + * // 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); @@ -33,9 +33,8 @@ typedef struct VerifyAlgCtx VerifyAlgCtx; typedef struct { int (*set_signature)(VerifyAlgCtx *ctx, unsigned char *data, unsigned len); - void (*begin_data)(VerifyAlgCtx *ctx); - void (*add_data)(VerifyAlgCtx *ctx, void *data, unsigned len); - void (*end_data)(VerifyAlgCtx *ctx); + int (*get_digestalgo)(VerifyAlgCtx *ctx); + void (*set_digest)(VerifyAlgCtx *ctx, unsigned char *digest); int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len); } VerifyAlg; @@ -50,10 +49,16 @@ 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 + 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); -int digestalg_final(struct keydata *digest); +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 index d35fc19..1099bf8 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -9,11 +9,7 @@ typedef struct VACTX_rsasha1 VerifyAlgCtx base; unsigned char *sig; unsigned siglen; - union - { - EVP_MD_CTX hash; - unsigned char digest[20]; - }; + unsigned char digest[20]; } VACTX_rsasha1; typedef struct VACTX_rsasha256 @@ -21,11 +17,7 @@ typedef struct VACTX_rsasha256 VerifyAlgCtx base; unsigned char *sig; unsigned siglen; - union - { - EVP_MD_CTX hash; - unsigned char digest[32]; - }; + unsigned char digest[32]; } VACTX_rsasha256; #define POOL_SIZE 1 @@ -62,43 +54,26 @@ static int rsasha256_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsi return 1; } -static void rsasha1_begin_data(VerifyAlgCtx *ctx_) +static int rsasha1_get_digestalgo(VerifyAlgCtx *ctx_) { - VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; - EVP_MD_CTX_init(&ctx->hash); - EVP_DigestInit_ex(&ctx->hash, EVP_sha1(), NULL); + (void)ctx_; + return DIGESTALG_SHA1; } -static void rsasha256_begin_data(VerifyAlgCtx *ctx_) +static int rsasha256_get_digestalgo(VerifyAlgCtx *ctx_) { - VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; - EVP_MD_CTX_init(&ctx->hash); - EVP_DigestInit_ex(&ctx->hash, EVP_sha256(), NULL); + (void)ctx_; + return DIGESTALG_SHA256; } -static void rsasha1_add_data(VerifyAlgCtx *ctx_, void *data, unsigned len) +static void rsasha1_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) { VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; - EVP_DigestUpdate(&ctx->hash, data, len); + memcpy(ctx->digest, digest, sizeof(ctx->digest)); } -static void rsasha256_add_data(VerifyAlgCtx *ctx_, void *data, unsigned len) +static void rsasha256_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) { VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; - EVP_DigestUpdate(&ctx->hash, data, len); -} - -static void rsasha1_end_data(VerifyAlgCtx *ctx_) -{ - VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; - unsigned char digest[20]; - EVP_DigestFinal(&ctx->hash, digest, NULL); - memcpy(ctx->digest, digest, 20); -} -static void rsasha256_end_data(VerifyAlgCtx *ctx_) -{ - VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; - unsigned char digest[32]; - EVP_DigestFinal(&ctx->hash, digest, NULL); - memcpy(ctx->digest, digest, 32); + memcpy(ctx->digest, digest, sizeof(ctx->digest)); } static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len) @@ -174,44 +149,36 @@ static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsign return validated; } -#define DEFINE_VALG(alg) \ - int alg ## _set_signature(VerifyAlgCtx *ctx, unsigned char *data, unsigned len); \ - void alg ## _begin_data(VerifyAlgCtx *ctx); \ - void alg ## _add_data(VerifyAlgCtx *ctx, void *data, unsigned len); \ - void alg ## _end_data(VerifyAlgCtx *ctx); \ - int alg ## _verify(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len) \ - /**/ +#define VALG_UNSUPPORTED() { \ + 0,0,0,0 \ + } /**/ #define VALG_VTABLE(alg) { \ alg ## _set_signature, \ - alg ## _begin_data, \ - alg ## _add_data, \ - alg ## _end_data, \ + alg ## _get_digestalgo, \ + alg ## _set_digest, \ alg ## _verify \ } /**/ -DEFINE_VALG(rsasha1); -DEFINE_VALG(rsasha256); - /* Updated registry that merges various RFCs: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ static const VerifyAlg valgs[] = { - {0,0,0,0,0}, /* 0: reserved */ - {0,0,0,0,0}, /* 1: RSAMD5 */ - {0,0,0,0,0}, /* 2: DH */ - {0,0,0,0,0}, /* 3: DSA */ - {0,0,0,0,0}, /* 4: ECC */ - VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ - {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */ - VALG_VTABLE(rsasha1), /* 7: RSASHA1-NSEC3-SHA1 */ - VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ - {0,0,0,0,0}, /* 9: unassigned */ - {0,0,0,0,0}, /* 10: RSASHA512 */ - {0,0,0,0,0}, /* 11: unassigned */ - {0,0,0,0,0}, /* 12: ECC-GOST */ - {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */ - {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */ + VALG_UNSUPPORTED(), /* 0: reserved */ + VALG_UNSUPPORTED(), /* 1: RSAMD5 */ + VALG_UNSUPPORTED(), /* 2: DH */ + VALG_UNSUPPORTED(), /* 3: DSA */ + VALG_UNSUPPORTED(), /* 4: ECC */ + VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ + VALG_UNSUPPORTED(), /* 6: DSA-NSEC3-SHA1 */ + VALG_VTABLE(rsasha1), /* 7: RSASHA1-NSEC3-SHA1 */ + VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ + VALG_UNSUPPORTED(), /* 9: unassigned */ + VALG_UNSUPPORTED(), /* 10: RSASHA512 */ + VALG_UNSUPPORTED(), /* 11: unassigned */ + VALG_UNSUPPORTED(), /* 12: ECC-GOST */ + VALG_UNSUPPORTED(), /* 13: ECDSAP256SHA256 */ + VALG_UNSUPPORTED(), /* 14: ECDSAP384SHA384 */ }; static const int valgctx_size[] = @@ -286,7 +253,7 @@ static EVP_MD_CTX digctx; int digestalg_supported(int algo) { - return (algo == 1 || algo == 2); + return (algo == DIGESTALG_SHA1 || algo == DIGESTALG_SHA256); } int digestalg_begin(int algo) @@ -301,6 +268,11 @@ int digestalg_begin(int algo) return 1; } +int digestalg_len() +{ + return EVP_MD_CTX_size(&digctx); +} + void digestalg_add_data(void *data, unsigned len) { EVP_DigestUpdate(&digctx, data, len); @@ -318,10 +290,10 @@ void digestalg_add_keydata(struct keydata *key, size_t len) } } -int digestalg_final(struct keydata *expected) +unsigned char* digestalg_final(void) { - unsigned char digest[32]; + static unsigned char digest[32]; EVP_DigestFinal(&digctx, digest, NULL); - /* FIXME: why EVP_MD_CTX_size() crashes? */ - return (memcmp(digest, expected->key, 20) == 0); + return digest; } + diff --git a/src/dnssec.c b/src/dnssec.c index b927320..71fca24 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -443,11 +443,11 @@ static int convert_domain_to_wire(char *name, unsigned char* out) } -/* Pass a resource record's rdata field through a verification hash function. +/* 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 verifyalg_add_rdata(VerifyAlgCtx *alg, int sigtype, struct dns_header *header, size_t pktlen, +static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pktlen, unsigned char *rdata) { size_t len; @@ -471,10 +471,10 @@ static int verifyalg_add_rdata(VerifyAlgCtx *alg, int sigtype, struct dns_header /* Iteration 2: process the canonical record through the hash function */ total = htons(total); - alg->vtbl->add_data(alg, &total, 2); + digestalg_add_data(&total, 2); while ((p = rdata_cform_next(&cf2, &len))) - alg->vtbl->add_data(alg, p, len); + digestalg_add_data(p, len); return 1; } @@ -575,22 +575,22 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, unsigned char owner_wire[MAXCDNAME]; int owner_wire_len = convert_domain_to_wire(owner, owner_wire); - alg->vtbl->begin_data(alg); - alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen); + digestalg_begin(alg->vtbl->get_digestalgo(alg)); + digestalg_add_data(sigrdata, 18+signer_name_rdlen); for (i = 0; i < rrsetidx; ++i) { p = (unsigned char*)(rrset[i]); - alg->vtbl->add_data(alg, owner_wire, owner_wire_len); - alg->vtbl->add_data(alg, &sigtype, 2); - alg->vtbl->add_data(alg, &sigclass, 2); - alg->vtbl->add_data(alg, &sigttl, 4); + 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 (!verifyalg_add_rdata(alg, ntohs(sigtype), header, pktlen, p)) + if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p)) return 0; } - alg->vtbl->end_data(alg); + alg->vtbl->set_digest(alg, digestalg_final()); /* 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)) @@ -672,14 +672,16 @@ static int dnskey_ds_match(struct crec *dnskey, struct crec *ds) 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 digestalg_final(ds->addr.key.keydata); + 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, From 1f0dc5835b4ca0c43bcd9d3f487c51acfae3aaf9 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 17:03:07 +0200 Subject: [PATCH 073/105] Implement DSA-SHA1 verification algorithm. --- src/dnssec-openssl.c | 81 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 1099bf8..f7cb880 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -3,6 +3,8 @@ #include "dnssec-crypto.h" #include #include +#include +#include typedef struct VACTX_rsasha1 { @@ -20,11 +22,21 @@ typedef struct VACTX_rsasha256 unsigned char digest[32]; } VACTX_rsasha256; +typedef struct VACTX_dsasha1 +{ + VerifyAlgCtx base; + unsigned char *sig; + unsigned siglen; + unsigned char digest[20]; +} VACTX_dsasha1; + + #define POOL_SIZE 1 static union _Pool { VACTX_rsasha1 rsasha1; VACTX_rsasha256 rsasha256; + VACTX_dsasha1 dsasha1; } Pool[POOL_SIZE]; static char pool_used = 0; @@ -54,6 +66,14 @@ static int rsasha256_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsi return 1; } +static int dsasha1_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) +{ + VACTX_dsasha1 *ctx = (VACTX_dsasha1 *)ctx_; + ctx->sig = data; + ctx->siglen = len; + return 1; +} + static int rsasha1_get_digestalgo(VerifyAlgCtx *ctx_) { (void)ctx_; @@ -64,6 +84,11 @@ static int rsasha256_get_digestalgo(VerifyAlgCtx *ctx_) (void)ctx_; return DIGESTALG_SHA256; } +static int dsasha1_get_digestalgo(VerifyAlgCtx *ctx_) +{ + (void)ctx_; + return DIGESTALG_SHA1; +} static void rsasha1_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) { @@ -75,6 +100,11 @@ static void rsasha256_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; memcpy(ctx->digest, digest, sizeof(ctx->digest)); } +static void dsasha1_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) +{ + VACTX_dsasha1 *ctx = (VACTX_dsasha1 *)ctx_; + memcpy(ctx->digest, digest, sizeof(ctx->digest)); +} static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len) { @@ -117,6 +147,19 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, keydata_to_bn(mod, &key_data, &p, mod_len); } +static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata *key_data, unsigned key_len) +{ + 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 rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) { VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; @@ -149,6 +192,40 @@ static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsign return validated; } +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 + }; + VACTX_dsasha1 *ctx = (VACTX_dsasha1 *)ctx_; + 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,0,0 \ } /**/ @@ -167,7 +244,7 @@ static const VerifyAlg valgs[] = VALG_UNSUPPORTED(), /* 0: reserved */ VALG_UNSUPPORTED(), /* 1: RSAMD5 */ VALG_UNSUPPORTED(), /* 2: DH */ - VALG_UNSUPPORTED(), /* 3: DSA */ + VALG_VTABLE(dsasha1), /* 3: DSA */ VALG_UNSUPPORTED(), /* 4: ECC */ VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ VALG_UNSUPPORTED(), /* 6: DSA-NSEC3-SHA1 */ @@ -186,7 +263,7 @@ static const int valgctx_size[] = 0, /* 0: reserved */ 0, /* 1: RSAMD5 */ 0, /* 2: DH */ - 0, /* 3: DSA */ + sizeof(VACTX_dsasha1), /* 3: DSA */ 0, /* 4: ECC */ sizeof(VACTX_rsasha1), /* 5: RSASHA1 */ 0, /* 6: DSA-NSEC3-SHA1 */ From 3af1ea8cbc120c7237f1e2ed11d7c6028edce7a6 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 19:06:47 +0200 Subject: [PATCH 074/105] Simplify abstraction of verification algorithms (it was too flexible) --- src/dnssec-crypto.h | 8 ++- src/dnssec-openssl.c | 159 +++++++++++-------------------------------- src/dnssec.c | 9 +-- 3 files changed, 51 insertions(+), 125 deletions(-) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index 3daac0b..b1c7ef8 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -32,15 +32,16 @@ typedef struct VerifyAlgCtx VerifyAlgCtx; typedef struct { - int (*set_signature)(VerifyAlgCtx *ctx, unsigned char *data, unsigned len); - int (*get_digestalgo)(VerifyAlgCtx *ctx); - void (*set_digest)(VerifyAlgCtx *ctx, unsigned char *digest); + 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[32]; }; int verifyalg_supported(int algo); @@ -53,6 +54,7 @@ int verifyalg_algonum(VerifyAlgCtx *a); /* RFC4034 digest algorithms */ #define DIGESTALG_SHA1 1 #define DIGESTALG_SHA256 2 +#define DIGESTALG_MD5 256 int digestalg_supported(int algo); int digestalg_begin(int algo); diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index f7cb880..dc37294 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -6,37 +6,10 @@ #include #include -typedef struct VACTX_rsasha1 -{ - VerifyAlgCtx base; - unsigned char *sig; - unsigned siglen; - unsigned char digest[20]; -} VACTX_rsasha1; - -typedef struct VACTX_rsasha256 -{ - VerifyAlgCtx base; - unsigned char *sig; - unsigned siglen; - unsigned char digest[32]; -} VACTX_rsasha256; - -typedef struct VACTX_dsasha1 -{ - VerifyAlgCtx base; - unsigned char *sig; - unsigned siglen; - unsigned char digest[20]; -} VACTX_dsasha1; - - #define POOL_SIZE 1 static union _Pool { - VACTX_rsasha1 rsasha1; - VACTX_rsasha256 rsasha256; - VACTX_dsasha1 dsasha1; + VerifyAlgCtx ctx; } Pool[POOL_SIZE]; static char pool_used = 0; @@ -50,62 +23,6 @@ static void print_hex(unsigned char *data, unsigned len) printf("\n"); } -static int rsasha1_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) -{ - VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; - ctx->sig = data; - ctx->siglen = len; - return 1; -} - -static int rsasha256_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) -{ - VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; - ctx->sig = data; - ctx->siglen = len; - return 1; -} - -static int dsasha1_set_signature(VerifyAlgCtx *ctx_, unsigned char *data, unsigned len) -{ - VACTX_dsasha1 *ctx = (VACTX_dsasha1 *)ctx_; - ctx->sig = data; - ctx->siglen = len; - return 1; -} - -static int rsasha1_get_digestalgo(VerifyAlgCtx *ctx_) -{ - (void)ctx_; - return DIGESTALG_SHA1; -} -static int rsasha256_get_digestalgo(VerifyAlgCtx *ctx_) -{ - (void)ctx_; - return DIGESTALG_SHA256; -} -static int dsasha1_get_digestalgo(VerifyAlgCtx *ctx_) -{ - (void)ctx_; - return DIGESTALG_SHA1; -} - -static void rsasha1_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) -{ - VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; - memcpy(ctx->digest, digest, sizeof(ctx->digest)); -} -static void rsasha256_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) -{ - VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; - memcpy(ctx->digest, digest, sizeof(ctx->digest)); -} -static void dsasha1_set_digest(VerifyAlgCtx *ctx_, unsigned char *digest) -{ - VACTX_dsasha1 *ctx = (VACTX_dsasha1 *)ctx_; - memcpy(ctx->digest, digest, sizeof(ctx->digest)); -} - static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len) { size_t cnt; @@ -160,9 +77,13 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata_to_bn(Y, &key_data, &p, 64+T*8); } -static int rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) +static int rsamd5_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) +{ + return 0; +} + +static int rsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) { - VACTX_rsasha1 *ctx = (VACTX_rsasha1 *)ctx_; int validated = 0; RSA *rsa = RSA_new(); @@ -176,9 +97,8 @@ static int rsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned return validated; } -static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) +static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) { - VACTX_rsasha256 *ctx = (VACTX_rsasha256 *)ctx_; int validated = 0; RSA *rsa = RSA_new(); @@ -192,7 +112,7 @@ static int rsasha256_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsign return validated; } -static int dsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned key_len) +static int dsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) { static unsigned char asn1_signature[] = { @@ -202,7 +122,6 @@ static int dsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned 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 }; - VACTX_dsasha1 *ctx = (VACTX_dsasha1 *)ctx_; int validated = 0; /* A DSA signature is made of 2 bignums (R & S). We could parse them manually with BN_bin2bn(), @@ -227,13 +146,11 @@ static int dsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned } #define VALG_UNSUPPORTED() { \ - 0,0,0,0 \ + 0,0 \ } /**/ -#define VALG_VTABLE(alg) { \ - alg ## _set_signature, \ - alg ## _get_digestalgo, \ - alg ## _set_digest, \ +#define VALG_VTABLE(alg, digest) { \ + digest, \ alg ## _verify \ } /**/ @@ -241,34 +158,36 @@ static int dsasha1_verify(VerifyAlgCtx *ctx_, struct keydata *key_data, unsigned https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */ static const VerifyAlg valgs[] = { - VALG_UNSUPPORTED(), /* 0: reserved */ - VALG_UNSUPPORTED(), /* 1: RSAMD5 */ - VALG_UNSUPPORTED(), /* 2: DH */ - VALG_VTABLE(dsasha1), /* 3: DSA */ - VALG_UNSUPPORTED(), /* 4: ECC */ - VALG_VTABLE(rsasha1), /* 5: RSASHA1 */ - VALG_UNSUPPORTED(), /* 6: DSA-NSEC3-SHA1 */ - VALG_VTABLE(rsasha1), /* 7: RSASHA1-NSEC3-SHA1 */ - VALG_VTABLE(rsasha256), /* 8: RSASHA256 */ - VALG_UNSUPPORTED(), /* 9: unassigned */ - VALG_UNSUPPORTED(), /* 10: RSASHA512 */ - VALG_UNSUPPORTED(), /* 11: unassigned */ - VALG_UNSUPPORTED(), /* 12: ECC-GOST */ - VALG_UNSUPPORTED(), /* 13: ECDSAP256SHA256 */ - VALG_UNSUPPORTED(), /* 14: ECDSAP384SHA384 */ + 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_UNSUPPORTED(), /* 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_UNSUPPORTED(), /* 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 */ - 0, /* 1: RSAMD5 */ + sizeof(VerifyAlgCtx), /* 1: RSAMD5 */ 0, /* 2: DH */ - sizeof(VACTX_dsasha1), /* 3: DSA */ + sizeof(VerifyAlgCtx), /* 3: DSA */ 0, /* 4: ECC */ - sizeof(VACTX_rsasha1), /* 5: RSASHA1 */ + sizeof(VerifyAlgCtx), /* 5: RSASHA1 */ 0, /* 6: DSA-NSEC3-SHA1 */ - sizeof(VACTX_rsasha1), /* 7: RSASHA1-NSEC3-SHA1 */ - sizeof(VACTX_rsasha256), /* 8: RSASHA256 */ + sizeof(VerifyAlgCtx), /* 7: RSASHA1-NSEC3-SHA1 */ + sizeof(VerifyAlgCtx), /* 8: RSASHA256 */ 0, /* 9: unassigned */ 0, /* 10: RSASHA512 */ 0, /* 11: unassigned */ @@ -330,16 +249,20 @@ static EVP_MD_CTX digctx; int digestalg_supported(int algo) { - return (algo == DIGESTALG_SHA1 || algo == DIGESTALG_SHA256); + return (algo == DIGESTALG_SHA1 || + algo == DIGESTALG_SHA256 || + algo == DIGESTALG_MD5); } int digestalg_begin(int algo) { EVP_MD_CTX_init(&digctx); - if (algo == 1) + if (algo == DIGESTALG_SHA1) EVP_DigestInit_ex(&digctx, EVP_sha1(), NULL); - else if (algo == 2) + else if (algo == DIGESTALG_SHA256) EVP_DigestInit_ex(&digctx, EVP_sha256(), NULL); + else if (algo == DIGESTALG_MD5) + EVP_DigestInit_ex(&digctx, EVP_md5(), NULL); else return 0; return 1; diff --git a/src/dnssec.c b/src/dnssec.c index 71fca24..b2abb94 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -561,8 +561,8 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, VerifyAlgCtx *alg = verifyalg_alloc(sigalg); if (!alg) return 0; - if (!alg->vtbl->set_signature(alg, sig, sigrdlen)) - return 0; + alg->sig = sig; + alg->siglen = sigrdlen; sigtype = htons(sigtype); sigclass = htons(sigclass); @@ -575,7 +575,7 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, unsigned char owner_wire[MAXCDNAME]; int owner_wire_len = convert_domain_to_wire(owner, owner_wire); - digestalg_begin(alg->vtbl->get_digestalgo(alg)); + digestalg_begin(alg->vtbl->digest_algo); digestalg_add_data(sigrdata, 18+signer_name_rdlen); for (i = 0; i < rrsetidx; ++i) { @@ -590,7 +590,8 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p)) return 0; } - alg->vtbl->set_digest(alg, digestalg_final()); + 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)) From 75ffc9bf1501390b3a10a48b39582959d055ca77 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 19:58:06 +0200 Subject: [PATCH 075/105] Implement RSA-MD5. --- src/dnssec-openssl.c | 41 ++++++++++++++++++----------------------- src/dnssec.c | 27 ++++++++++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index dc37294..06ac557 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -77,39 +77,34 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata_to_bn(Y, &key_data, &p, 64+T*8); } +static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len, int nid, int dlen) +{ + 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 0; + return rsa_verify(ctx, key_data, key_len, NID_md5, 16); } static int rsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) { - 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_sha1, ctx->digest, 20, ctx->sig, ctx->siglen, rsa)) - validated = 1; - - RSA_free(rsa); - return validated; + return rsa_verify(ctx, key_data, key_len, NID_sha1, 20); } static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) { - 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_sha256, ctx->digest, 32, ctx->sig, ctx->siglen, rsa)) - validated = 1; - - RSA_free(rsa); - return validated; + return rsa_verify(ctx, key_data, key_len, NID_sha256, 32); } static int dsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) diff --git a/src/dnssec.c b/src/dnssec.c index b2abb94..3ad438d 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -649,16 +649,25 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, } /* Compute keytag (checksum to quickly index a key). See RFC4034 */ -static int dnskey_keytag(unsigned char *rdata, int rdlen) +static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen) { - unsigned long ac; - int i; + 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; + 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) */ @@ -712,7 +721,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig crecp->uid = rdlen; crecp->addr.key.keydata = key; crecp->addr.key.algo = alg; - crecp->addr.key.keytag = dnskey_keytag(ordata, ordlen); + crecp->addr.key.keytag = dnskey_keytag(alg, ordata, ordlen); printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag); } else From ccd1d32c3a425658b7725f98685be56705cebbce Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 19:59:16 +0200 Subject: [PATCH 076/105] Make testsuite errors greppable. --- src/dnssec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index 3ad438d..1fc121c 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -636,7 +636,7 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, if (end_rrsig_validation(&val, crecp)) printf("Validation OK\n"); else - printf("Validation FAILED\n"); + printf("ERROR: Validation FAILED (%s, keytag:%d, algo:%d)\n", owner, val.keytag, verifyalg_algonum(val.alg)); } } @@ -777,9 +777,11 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign { /* 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; } From 5c328419345fb88e065ac55655f2d961bd1f60bf Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 20:10:55 +0200 Subject: [PATCH 077/105] Implement RSA-SHA512. --- src/dnssec-crypto.h | 3 ++- src/dnssec-openssl.c | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index b1c7ef8..d2f1308 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -41,7 +41,7 @@ struct VerifyAlgCtx const VerifyAlg *vtbl; unsigned char *sig; size_t siglen; - unsigned char digest[32]; + unsigned char digest[64]; /* TODO: if memory problems, use VLA */ }; int verifyalg_supported(int algo); @@ -55,6 +55,7 @@ int verifyalg_algonum(VerifyAlgCtx *a); #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); diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 06ac557..1159e2a 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -107,6 +107,11 @@ static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigne 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[] = @@ -159,11 +164,11 @@ static const VerifyAlg valgs[] = VALG_VTABLE(dsasha1, DIGESTALG_SHA1), /* 3: DSA */ VALG_UNSUPPORTED(), /* 4: ECC */ VALG_VTABLE(rsasha1, DIGESTALG_SHA1), /* 5: RSASHA1 */ - VALG_UNSUPPORTED(), /* 6: DSA-NSEC3-SHA1 */ + 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_UNSUPPORTED(), /* 10: RSASHA512 */ + VALG_VTABLE(rsasha512, DIGESTALG_SHA512), /* 10: RSASHA512 */ VALG_UNSUPPORTED(), /* 11: unassigned */ VALG_UNSUPPORTED(), /* 12: ECC-GOST */ VALG_UNSUPPORTED(), /* 13: ECDSAP256SHA256 */ @@ -184,7 +189,7 @@ static const int valgctx_size[] = sizeof(VerifyAlgCtx), /* 7: RSASHA1-NSEC3-SHA1 */ sizeof(VerifyAlgCtx), /* 8: RSASHA256 */ 0, /* 9: unassigned */ - 0, /* 10: RSASHA512 */ + sizeof(VerifyAlgCtx), /* 10: RSASHA512 */ 0, /* 11: unassigned */ 0, /* 12: ECC-GOST */ 0, /* 13: ECDSAP256SHA256 */ @@ -246,7 +251,8 @@ int digestalg_supported(int algo) { return (algo == DIGESTALG_SHA1 || algo == DIGESTALG_SHA256 || - algo == DIGESTALG_MD5); + algo == DIGESTALG_MD5 || + algo == DIGESTALG_SHA512); } int digestalg_begin(int algo) @@ -256,6 +262,8 @@ int digestalg_begin(int algo) 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 From 4b5287005f96646e824335dc7859b76278dfa61a Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Wed, 2 May 2012 20:11:07 +0200 Subject: [PATCH 078/105] Again make errors greppable. --- src/dnssec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 1fc121c..30b5716 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -509,13 +509,13 @@ static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, if (!verifyalg_supported(sigalg)) { - printf("RRSIG algorithm not supported: %d\n", sigalg); + printf("ERROR: RRSIG algorithm not supported: %d\n", sigalg); return 0; } if (!check_date_range(date_start, date_end)) { - printf("RRSIG outside date range\n"); + printf("ERROR: RRSIG outside date range\n"); return 0; } From 4f9aefc753247c9c0e206b8b22563506212c21f5 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 3 May 2012 15:17:27 +0100 Subject: [PATCH 079/105] Don't fight over namespace with re-implementation of strchrnul() --- src/dnssec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index 30b5716..c9276ba 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -404,7 +404,7 @@ typedef struct PendingRRSIGValidation This is an existing C GNU extension, but it's easier to reimplement it, rather than tweaking with configure. */ -static char *strchrnul(char *str, char ch) +static char *my_strchrnul(char *str, char ch) { while (*str && *str != ch) str++; @@ -420,7 +420,7 @@ static int convert_domain_to_wire(char *name, unsigned char* out) do { - p = strchrnul(name, '.'); + p = my_strchrnul(name, '.'); if ((len = p-name)) { *out++ = len; From 4631dbf68c89ebd1f8597c6ee8ac364bdcafb694 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Thu, 3 May 2012 18:09:14 +0200 Subject: [PATCH 080/105] DSA-NSEC3-SHA1 is an alias of DSA for signature verification. --- src/dnssec-openssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 1159e2a..2f1c838 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -185,7 +185,7 @@ static const int valgctx_size[] = sizeof(VerifyAlgCtx), /* 3: DSA */ 0, /* 4: ECC */ sizeof(VerifyAlgCtx), /* 5: RSASHA1 */ - 0, /* 6: DSA-NSEC3-SHA1 */ + sizeof(VerifyAlgCtx), /* 6: DSA-NSEC3-SHA1 */ sizeof(VerifyAlgCtx), /* 7: RSASHA1-NSEC3-SHA1 */ sizeof(VerifyAlgCtx), /* 8: RSASHA256 */ 0, /* 9: unassigned */ From 8d41ebd8a32c217dfc644592adbbaad6fdd2fa74 Mon Sep 17 00:00:00 2001 From: Giovanni Bajo Date: Sat, 5 May 2012 00:48:12 +0200 Subject: [PATCH 081/105] Add copyright banners --- src/dnssec-crypto.h | 16 ++++++++++++++++ src/dnssec-openssl.c | 16 ++++++++++++++++ src/dnssec.c | 15 +++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index d2f1308..1717db6 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -1,3 +1,19 @@ +/* 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 diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 2f1c838..4bf7e73 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -1,3 +1,19 @@ +/* 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 #include "dnsmasq.h" #include "dnssec-crypto.h" diff --git a/src/dnssec.c b/src/dnssec.c index c9276ba..222be3f 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -1,3 +1,18 @@ +/* dnssec.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" #include "dnssec-crypto.h" From 687bac22dbae4c4f36f9021407d110336e7a3463 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 29 May 2013 14:17:15 +0100 Subject: [PATCH 082/105] Tidy rebase --- Makefile | 1 - bld/Android.mk | 3 ++- src/option.c | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 16e85e1..ab9c179 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,6 @@ 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 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 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ 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/option.c b/src/option.c index ed015b6..bcc72f6 100644 --- a/src/option.c +++ b/src/option.c @@ -405,7 +405,6 @@ static struct { { LOPT_FAST_RA, OPT_FAST_RA, NULL, gettext_noop("Always send frequent router-advertisements"), NULL }, { LOPT_DUID, ARG_ONE, ",", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, ",
", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, -<<<<<<< HEAD { LOPT_RR, ARG_DUP, ",,[]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, { LOPT_AUTHSERV, ARG_ONE, ",", gettext_noop("Export local names to global DNS"), NULL }, @@ -416,7 +415,6 @@ 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 sythesised names"), NULL }, - { LOPT_SEC_VALID, OPT_DNSSEC_VALIDATE, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, #ifdef HAVE_DNSSEC { LOPT_SEC_VALID, OPT_DNSSEC_VALIDATE, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, #endif From 3a2371527ff3383526e9c1f38d4a08010009ffd6 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 12 Dec 2013 12:15:50 +0000 Subject: [PATCH 083/105] Commit to allow master merge. --- src/dnsmasq.c | 6 +- src/dnsmasq.h | 34 ++++++++--- src/dnssec.c | 14 ++--- src/forward.c | 165 +++++++++++++++++++++++++++++++++++++++++++------- src/option.c | 2 +- src/rfc1035.c | 40 ++++++++---- 6 files changed, 209 insertions(+), 52 deletions(-) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index f9c2c30..8dbe275 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -81,10 +81,10 @@ int main (int argc, char **argv) umask(022); /* known umask, create leases and pid files as 0644 */ read_opts(argc, argv, compile_opts); - if (option_bool(OPT_DNSSEC_VALIDATE)) + if (option_bool(OPT_DNSSEC_VALID)) if (daemon->doctors) exit(1); /* TODO */ if (daemon->edns_pktsz < PACKETSZ) - daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALIDATE) ? EDNS_PKTSZ : PACKETSZ; + daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? 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); @@ -1302,7 +1302,7 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp) /* will we be able to get memory? */ if (daemon->port != 0) - get_new_frec(now, &wait); + get_new_frec(now, &wait, 0); for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) { diff --git a/src/dnsmasq.h b/src/dnsmasq.h index f96a94c..8783676 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -228,7 +228,7 @@ struct event_desc { #define OPT_QUIET_DHCP 42 #define OPT_QUIET_DHCP6 43 #define OPT_QUIET_RA 44 -#define OPT_DNSSEC_VALIDATE 45 +#define OPT_DNSSEC_VALID 45 #define OPT_LAST 46 /* extra flags for my_syslog, we use a couple of facilities since they are known @@ -352,7 +352,7 @@ struct crec { int uid; /* -1 if union is interface-name */ } cname; struct { - struct keydata *keydata; + struct blockdata *keydata; unsigned char algo; unsigned char digest; /* DS only */ unsigned short keytag; @@ -499,9 +499,20 @@ struct hostsfile { int index; /* matches to cache entries for logging */ }; + +/* DNSSEC status values. */ +#define STAT_SECURE 1 +#define STAT_INSECURE 2 +#define STAT_BOGUS 3 +#define STAT_NEED_DS 4 +#define STAT_NEED_KEY 5 + #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 #define FREC_HAS_SUBNET 4 +#define FREC_DNSSEC_QUERY 8 +#define FREC_DNSKEY_QUERY 16 +#define FREC_DS_QUERY 32 struct frec { union mysockaddr source; @@ -516,6 +527,12 @@ struct frec { int fd, forwardall, flags; unsigned int crc; time_t time; +#ifdef HAVE_DNSSEC + struct blockdata *stash; /* Saved reply, whilst we validate */ + size_t stash_len; + struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ + struct frec *blocking_query; /* Query which is blocking us. */ +#endif struct frec *next; }; @@ -954,9 +971,9 @@ char *cache_get_name(struct crec *crecp); char *cache_get_cname_target(struct crec *crecp); 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); +struct blockdata *blockdata_alloc(char *data, size_t len); +size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt); +void blockdata_free(struct blockdata *blocks); #endif /* domain.c */ @@ -992,6 +1009,9 @@ size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen); size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3); size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source); +#ifdef HAVE_DNSSEC +size_t add_do_bit(struct dns_header *header, size_t plen, char *limit); +#endif int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, unsigned long ttl, @@ -1010,7 +1030,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -int dnssec_validate(struct dns_header *header, size_t plen); +int dnssec_validate(int flags, struct dns_header *header, size_t plen); /* util.c */ void rand_init(void); @@ -1072,7 +1092,7 @@ void receive_query(struct listener *listen, time_t now); unsigned char *tcp_request(int confd, time_t now, union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); void server_gone(struct server *server); -struct frec *get_new_frec(time_t now, int *wait); +struct frec *get_new_frec(time_t now, int *wait, int force); int send_from(int fd, int nowild, char *packet, size_t len, union mysockaddr *to, struct all_addr *source, unsigned int iface); diff --git a/src/dnssec.c b/src/dnssec.c index 222be3f..a1f7856 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -713,7 +713,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig int rdlen, unsigned char *rdata) { int flags, proto, alg; - struct keydata *key; struct crec *crecp; + struct blockdata *key; struct crec *crecp; unsigned char *ordata = rdata; int ordlen = rdlen; CHECKED_GETSHORT(flags, rdata, rdlen); @@ -726,7 +726,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig if (!(flags & 0x100)) return 0; - key = keydata_alloc((char*)rdata, rdlen); + key = blockdata_alloc((char*)rdata, rdlen); /* TODO: time(0) is correct here? */ crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY); @@ -741,7 +741,7 @@ int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsig } else { - keydata_free(key); + 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"); @@ -754,7 +754,7 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign int rdlen, unsigned char *rdata) { int keytag, algo, dig; - struct keydata *key; struct crec *crec_ds, *crec_key; + struct blockdata *key; struct crec *crec_ds, *crec_key; CHECKED_GETSHORT(keytag, rdata, rdlen); CHECKED_GETCHAR(algo, rdata, rdlen); @@ -763,13 +763,13 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign if (!digestalg_supported(dig)) return 0; - key = keydata_alloc((char*)rdata, rdlen); + 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) { - keydata_free(key); + 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"); @@ -800,7 +800,7 @@ int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsign return 0; } -int dnssec_validate(struct dns_header *header, size_t pktlen) +int dnssec1_validate(struct dns_header *header, size_t pktlen) { unsigned char *p, *reply; char *owner = daemon->namebuff; diff --git a/src/forward.c b/src/forward.c index 1c66acf..2b93af1 100644 --- a/src/forward.c +++ b/src/forward.c @@ -270,7 +270,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - if (!flags && !(forward = get_new_frec(now, NULL))) + if (!flags && !(forward = get_new_frec(now, NULL, 0))) /* table full - server failure. */ flags = F_NEG; @@ -342,6 +342,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } } +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ); +#endif + while (1) { /* only send to servers dealing with our domain. @@ -447,12 +452,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, - int checking_disabled, int check_subnet, union mysockaddr *query_source) + int no_cache, int cache_secure, int check_subnet, union mysockaddr *query_source) { unsigned char *pheader, *sizep; char **sets = 0; int munged = 0, is_sign; size_t plen; + int squash_ad = 0; #ifdef HAVE_IPSET /* Similar algorithm to search_servers. */ @@ -495,11 +501,21 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } } - /* RFC 4035 sect 4.6 para 3 */ if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) - header->hb4 &= ~HB4_AD; + squash_ad = 1; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + squash_ad = no_cache; + if (cache_secure) + header->hb4 |= HB4_AD; +#endif + + if (squash_ad) + header->hb4 &= ~HB4_AD; + if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) return n; @@ -513,11 +529,6 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server 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)) { @@ -539,7 +550,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server SET_RCODE(header, NOERROR); } - if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, checking_disabled)) + if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache)) { my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; @@ -597,9 +608,7 @@ void reply_query(int fd, int family, time_t now) n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) return; - - server = forward->sentto; - + if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) && !option_bool(OPT_ORDER) && forward->forwardall == 0) @@ -624,6 +633,8 @@ void reply_query(int fd, int family, time_t now) } } } + + server = forward->sentto; if ((forward->sentto->flags & SERV_TYPE) == 0) { @@ -645,7 +656,7 @@ void reply_query(int fd, int family, time_t now) if (!option_bool(OPT_ALL_SERVERS)) daemon->last_server = server; } - + /* If the answer is an error, keep the forward record in place in case we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when @@ -653,12 +664,106 @@ void reply_query(int fd, int family, time_t now) if (forward->forwardall == 0 || --forward->forwardall == 1 || (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) { - int check_rebind = !(forward->flags & FREC_NOREBIND); + int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0; - if (!option_bool(OPT_NO_REBIND)) - check_rebind = 0; + if (option_bool(OPT_NO_REBIND)) + check_rebind = !(forward->flags & FREC_NOREBIND); - if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED, + /* Don't cache replies where DNSSEC validation was turned off, either + the upstream server told us so, or the original query specified it. */ + if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED)) + no_cache_dnssec = 1; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) + { + int status = dnssec_validate(forward->flags, header, n); + + /* Can't validate, as we're missing key data. Put this + answer aside, whilst we get that. */ + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + { + struct frec *new; + if ((forward->stash = blockdata_alloc((char *)header, n))) + { + forward->stash_len = n; + + /* Now formulate a query for the missing data. */ + nn = dnssec_generate_query(header, status); + new = get_new_frec(now, NULL, 1); + + if (new) + { + int fd; + + new = forward; /* copy everything, then overwrite */ + new->dependent = forward; /* to find query awaiting new one. */ + forward->blocking_query = new; /* for garbage cleaning */ + new->flags |= FREC_DNSSEC_QUERY; + if (status == STAT_NEED_KEY) + new->flags |= FREC_DNSKEY_QUERY; /* So we verify differently */ + else if (status == STAT_NEED_DS) + new->flags |= FREC_DS_QUERY; + new->crc = questions_crc(header, nn, daemon->namebuff); + new->new_id = get_id(new->crc); + + /* Don't resend this. */ + daemon->srv_save = NULL; + + if (server->sfd) + fd = server->sfd->fd; + else +#ifdef HAVE_IPV6 + /* Note that we use the same random port for the DNSSEC stuff */ + if (server->addr.sa.sa_family == AF_INET6) + { + fd = new->rfd6->fd; + new->rfd6->refcount++; + } + else +#endif + { + fd = new->rfd4->fd; + new->rfd4->refcount++; + } + + /* 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()); + } + } + return; + } + + /* Ok, we reached far enough up the chain-of-trust that we can validate something. + Now wind back down, pulling back answers which wouldn't previously validate + and validate them with the new data. Failure to find needed data here is an internal error. + Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates, + return it to the original requestor. */ + while (forward->flags & FREC_DNSSEC_QUERY) + { + if (status == STAT_SECURE) + extract_dnssec_replies(); + free_frec(forward); + forward = forward->dependent; + blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header); + n = forward->stash_len; + if (status == STAT_SECURE) + { + status = dnssec_validate(forward->flags, header, n); + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); + } + } + + if (status == STAT_SECURE) + cache_secure = 1; + /* TODO return SERVFAIL here */ + else if (status == STAT_BOGUS) + no_cache_dnssec = 1; + } +#endif + + if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, forward->flags & FREC_HAS_SUBNET, &forward->source))) { header->id = htons(forward->orig_id); @@ -1129,7 +1234,7 @@ unsigned char *tcp_request(int confd, time_t now, if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) m = process_reply(header, now, last_server, (unsigned int)m, option_bool(OPT_NO_REBIND) && !norebind, checking_disabled, - check_subnet, &peer_addr); + 0, check_subnet, &peer_addr); /* TODO - cache secure */ break; } @@ -1163,6 +1268,9 @@ static struct frec *allocate_frec(time_t now) f->flags = 0; #ifdef HAVE_IPV6 f->rfd6 = NULL; +#endif +#ifdef HAVE_DNSSEC + f->blocking_query = NULL; #endif daemon->frec_list = f; } @@ -1221,13 +1329,26 @@ static void free_frec(struct frec *f) f->rfd6 = NULL; #endif + +#ifdef HAVE_DNSSEC + if (f->stash) + blockdata_free(f->stash); + + /* Anything we're waiting on is pointless now, too */ + if (f->blocking_query) + free_frec(f->blocking_query); + f->blocking_query = NULL; + +#endif } /* if wait==NULL return a free or older than TIMEOUT record. else return *wait zero if one available, or *wait is delay to when the oldest in-use record will expire. Impose an absolute - limit of 4*TIMEOUT before we wipe things (for random sockets) */ -struct frec *get_new_frec(time_t now, int *wait) + limit of 4*TIMEOUT before we wipe things (for random sockets). + If force is set, always return a result, even if we have + to allocate above the limit. */ +struct frec *get_new_frec(time_t now, int *wait, int force) { struct frec *f, *oldest, *target; int count; @@ -1276,7 +1397,7 @@ struct frec *get_new_frec(time_t now, int *wait) } /* none available, calculate time 'till oldest record expires */ - if (count > daemon->ftabsize) + if (!force && count > daemon->ftabsize) { static time_t last_log = 0; diff --git a/src/option.c b/src/option.c index 0ca00b9..8427497 100644 --- a/src/option.c +++ b/src/option.c @@ -427,7 +427,7 @@ static struct { { 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_VALIDATE, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, + { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, #endif #ifdef OPTION6_PREFIX_CLASS { LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, diff --git a/src/rfc1035.c b/src/rfc1035.c index 7188e4e..7321d96 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -515,7 +515,7 @@ struct macparm { }; static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, - int optno, unsigned char *opt, size_t optlen) + int optno, unsigned char *opt, size_t optlen, int set_do) { unsigned char *lenp, *datap, *p; int rdlen; @@ -531,7 +531,8 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned *p++ = 0; /* empty name */ PUTSHORT(T_OPT, p); PUTSHORT(daemon->edns_pktsz, p); /* max packet length */ - PUTLONG(0, p); /* extended RCODE */ + PUTSHORT(0, p); /* extended RCODE and version */ + PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */ lenp = p; PUTSHORT(0, p); /* RDLEN */ rdlen = 0; @@ -543,7 +544,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned else { int i, is_sign; - unsigned short code, len; + unsigned short code, len, flags; if (ntohs(header->arcount) != 1 || !(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) || @@ -551,14 +552,24 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned (!(p = skip_name(p, header, plen, 10)))) return plen; - p += 8; /* skip UDP length and RCODE */ - + p += 6; /* skip UDP length and RCODE */ + GETSHORT(flags, p); + if (set_do) + { + p -=2; + PUTSHORT(flags | 0x8000, p); + } + lenp = p; GETSHORT(rdlen, p); if (!CHECK_LEN(header, p, plen, rdlen)) return plen; /* bad packet */ datap = p; + /* no option to add */ + if (optno == 0) + return plen; + /* check if option already there */ for (i = 0; i + 4 < rdlen; i += len + 4) { @@ -602,7 +613,7 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p if (!match) return 1; /* continue */ - parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen); + parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0); return 0; /* done */ } @@ -681,9 +692,16 @@ size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, unio struct subnet_opt opt; len = calc_subnet_opt(&opt, source); - return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len); + return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0); } - + +#ifdef HAVE_DNSSEC +size_t add_do_bit(struct dns_header *header, size_t plen, char *limit) +{ + return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1); +} +#endif + int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer) { /* Section 9.2, Check that subnet option in reply matches. */ @@ -878,7 +896,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) expired and cleaned out that way. Return 1 if we reject an address because it look like part of dns-rebinding attack. */ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, - char **ipsets, int is_sign, int check_rebind, int checking_disabled) + char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec) { unsigned char *p, *p1, *endrr, *namep; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; @@ -1118,15 +1136,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t } /* Don't put stuff from a truncated packet into the cache. - Don't cache replies where DNSSEC validation was turned off, either - the upstream server told us so, or the original query specified it. Don't cache replies from non-recursive nameservers, since we may get a reply containing a CNAME but not its target, even though the target does exist. */ if (!(header->hb3 & HB3_TC) && !(header->hb4 & HB4_CD) && (header->hb4 & HB4_RA) && - !checking_disabled) + !no_cache_dnssec) cache_end_insert(); return 0; From 9d633048fe8045d3240029c0cb6180834b80c9a3 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Fri, 13 Dec 2013 15:36:55 +0000 Subject: [PATCH 084/105] Saving progress --- src/dns-protocol.h | 2 ++ src/dnsmasq.h | 5 ++-- src/forward.c | 60 +++++++++++++++++++++++++++------------------- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 023be5f..07cc768 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -82,6 +82,8 @@ struct dns_header { #define HB4_RCODE 0x0f #define OPCODE(x) (((x)->hb3 & HB3_OPCODE) >> 3) +#define SET_OPCODE(x, code) (x)->hb3 = ((x)->hb3 & ~HB3_OPCODE) | code + #define RCODE(x) ((x)->hb4 & HB4_RCODE) #define SET_RCODE(x, code) (x)->hb4 = ((x)->hb4 & ~HB4_RCODE) | code diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 7991dd0..bde72e2 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -511,9 +511,8 @@ struct hostsfile { #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 #define FREC_HAS_SUBNET 4 -#define FREC_DNSSEC_QUERY 8 -#define FREC_DNSKEY_QUERY 16 -#define FREC_DS_QUERY 32 +#define FREC_DNSKEY_QUERY 8 +#define FREC_DS_QUERY 16 struct frec { union mysockaddr source; diff --git a/src/forward.c b/src/forward.c index ca4c118..97f8800 100644 --- a/src/forward.c +++ b/src/forward.c @@ -677,7 +677,16 @@ void reply_query(int fd, int family, time_t now) #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { - int status = dnssec_validate(forward->flags, header, n); + int status; + char rrbitmap[256/8]; + int class; + + if (forward->flags && FREC_DNSSKEY_QUERY) + status = dnssec_validate_by_ds(header, n, daemon->namebuff, &class); + else if (forward->flags && FREC_DS_QUERY) + status = dnssec_validate_dnskey(header, n, daemon->namebuff, &class); + else + status = dnssec_validate_reply(&rrbitmap, header, n, daemon->namebuff, &class); /* Can't validate, as we're missing key data. Put this answer aside, whilst we get that. */ @@ -687,26 +696,29 @@ void reply_query(int fd, int family, time_t now) if ((forward->stash = blockdata_alloc((char *)header, n))) { forward->stash_len = n; - - /* Now formulate a query for the missing data. */ - nn = dnssec_generate_query(header, status); - new = get_new_frec(now, NULL, 1); - - if (new) + + if ((new = get_new_frec(now, NULL, 1))) { int fd; - + new = forward; /* copy everything, then overwrite */ new->dependent = forward; /* to find query awaiting new one. */ forward->blocking_query = new; /* for garbage cleaning */ - new->flags |= FREC_DNSSEC_QUERY; + /* validate routines leave name of required record in daemon->namebuff */ if (status == STAT_NEED_KEY) - new->flags |= FREC_DNSKEY_QUERY; /* So we verify differently */ + { + new->flags |= FREC_DNSKEY_QUERY; + nn = dnssec_generate_query(header, daemon->namebuff, class, T_DNSKEY); + } 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); + } new->crc = questions_crc(header, nn, daemon->namebuff); new->new_id = get_id(new->crc); - + header->id = htons(new->id); + /* Don't resend this. */ daemon->srv_save = NULL; @@ -714,19 +726,19 @@ void reply_query(int fd, int family, time_t now) fd = server->sfd->fd; else #ifdef HAVE_IPV6 - /* Note that we use the same random port for the DNSSEC stuff */ - if (server->addr.sa.sa_family == AF_INET6) - { - fd = new->rfd6->fd; - new->rfd6->refcount++; - } - else + /* Note that we use the same random port for the DNSSEC stuff */ + if (server->addr.sa.sa_family == AF_INET6) + { + fd = new->rfd6->fd; + new->rfd6->refcount++; + } + else #endif - { - fd = new->rfd4->fd; - new->rfd4->refcount++; - } - + { + fd = new->rfd4->fd; + new->rfd4->refcount++; + } + /* 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()); } From c3e0b9b6e75001ea2d24a4f67537ed77d0f0210c Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 31 Dec 2013 13:50:39 +0000 Subject: [PATCH 085/105] backup --- src/cache.c | 20 +-- src/dns-protocol.h | 6 + src/dnsmasq.c | 6 + src/dnsmasq.h | 14 +- src/dnssec.c | 359 +++++++++++++++++++++++++++++++++++++++++++++ src/forward.c | 35 +++-- src/rfc1035.c | 7 - 7 files changed, 413 insertions(+), 34 deletions(-) diff --git a/src/cache.c b/src/cache.c index 1338681..cfbeae3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -26,7 +26,7 @@ static union bigname *big_free = NULL; static int bignames_left, hash_size; static int uid = 1; #ifdef HAVE_DNSSEC -static struct keydata *keyblock_free = NULL; +static struct blockdata *keyblock_free = NULL; #endif /* type->string mapping: this is also used by the name-hash function as a mixing table. */ @@ -198,7 +198,7 @@ static void cache_free(struct crec *crecp) } #ifdef HAVE_DNSSEC else if (crecp->flags & (F_DNSKEY | F_DS)) - keydata_free(crecp->addr.key.keydata); + blockdata_free(crecp->addr.key.keydata); #endif } @@ -1361,10 +1361,10 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) } #ifdef HAVE_DNSSEC -struct keydata *keydata_alloc(char *data, size_t len) +struct blockdata *blockdata_alloc(char *data, size_t len) { - struct keydata *block, *ret = NULL; - struct keydata **prev = &ret; + struct blockdata *block, *ret = NULL; + struct blockdata **prev = &ret; size_t blen; while (len > 0) @@ -1375,12 +1375,12 @@ struct keydata *keydata_alloc(char *data, size_t len) keyblock_free = block->next; } else - block = whine_malloc(sizeof(struct keydata)); + block = whine_malloc(sizeof(struct blockdata)); if (!block) { /* failed to alloc, free partial chain */ - keydata_free(ret); + blockdata_free(ret); return NULL; } @@ -1396,7 +1396,7 @@ struct keydata *keydata_alloc(char *data, size_t len) return ret; } -size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) +size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt) { if (*p == NULL) *p = (*key)->key; @@ -1411,9 +1411,9 @@ size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) return MIN(cnt, (*key)->key + KEYBLOCK_LEN - (*p)); } -void keydata_free(struct keydata *blocks) +void blockdata_free(struct blockdata *blocks) { - struct keydata *tmp; + struct blockdata *tmp; if (blocks) { diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 07cc768..efecb1c 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -140,3 +140,9 @@ struct dns_header { GETLONG(var, ptr); \ (len) -= 4; \ } while (0) + +#define CHECK_LEN(header, pp, plen, len) \ + ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) + +#define ADD_RDLEN(header, pp, plen, len) \ + (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index f5ce7df..3e5f51e 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -81,8 +81,14 @@ int main (int argc, char **argv) umask(022); /* known umask, create leases and pid files as 0644 */ read_opts(argc, argv, compile_opts); + +#ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) if (daemon->doctors) exit(1); /* TODO */ + + daemon->keyname = safe_malloc(MAXDNAME); +#endif + if (daemon->edns_pktsz < PACKETSZ) daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? EDNS_PKTSZ : PACKETSZ; daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 241f9a5..8a3541a 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -335,8 +335,8 @@ union bigname { union bigname *next; /* freelist */ }; -struct keydata { - struct keydata *next; +struct blockdata { + struct blockdata *next; unsigned char key[KEYBLOCK_LEN]; }; @@ -528,6 +528,7 @@ struct frec { unsigned int crc; time_t time; #ifdef HAVE_DNSSEC + int class; struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ @@ -900,6 +901,9 @@ extern struct daemon { char *packet; /* packet buffer */ int packet_buff_sz; /* size of above */ char *namebuff; /* MAXDNAME size buffer */ +#ifdef HAVE_DNSSEC + char *keyname; /* MAXDNAME size buffer */ +#endif unsigned int local_answer, queries_forwarded, auth_answer; struct frec *frec_list; struct serverfd *sfds; @@ -1030,7 +1034,11 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -int dnssec_validate(int flags, struct dns_header *header, size_t plen); +size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type); +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); +int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname); +int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class); /* util.c */ void rand_init(void); diff --git a/src/dnssec.c b/src/dnssec.c index a1f7856..4a91137 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -21,11 +21,15 @@ /* Maximum length in octects of a domain name, in wire format */ #define MAXCDNAME 256 +#define MAXRRSET 16 + #define SERIAL_UNDEF -100 #define SERIAL_EQ 0 #define SERIAL_LT -1 #define SERIAL_GT 1 +static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen); + /* Implement RFC1982 wrapped compare for 32-bit numbers */ static int serial_compare_32(unsigned long s1, unsigned long s2) { @@ -494,7 +498,359 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk return 1; } +size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type) +{ + unsigned char *p; + header->qdcount = htons(1); + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + + header->hb3 = HB3_RD; + SET_OPCODE(header, QUERY); + header->hb4 = 0; + + /* ID filled in later */ + + p = (unsigned char *)(header+1); + + p = do_rfc1035_name(p, name); + PUTSHORT(type, p); + PUTSHORT(class, p); + + return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ); +} + +/* The DNS packet is expected to contain the answer to a DNSKEY query + Put all DNSKEYs in the answer which are valid into the cache. + return codes: + STAT_INSECURE bad packet, no DNSKEYs in reply. + STAT_SECURE At least one valid DNSKEY found and in cache. + STAT_BOGUS At least one DNSKEY found, which fails validation. + STAT_NEED_DS DS records to validate a key not found, name in namebuff +*/ +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +{ + unsigned char *p; + struct crec *crecp, *recp1; + int j, qtype, qclass, ttl, rdlen, flags, protocol, algo, gotone; + struct blockdata *key; + + if (ntohs(header->qdcount) != 1) + return STAT_INSECURE; + + if (!extract_name(header, plen, &p, name, 1, 4)) + return STAT_INSECURE; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qtype != T_DNSKEY || qclass != class) + return STAT_INSECURE; + + cache_start_insert(); + + for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) + { + /* Ensure we have type, class TTL and length */ + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (qclass != class || qtype != T_DNSKEY || rdlen < 4) + { + /* skip all records other than DNSKEY */ + p += rdlen; + continue; + } + + crecp = cache_find_by_name(NULL, name, now, F_DS); + + /* length at least covers flags, protocol and algo now. */ + GETSHORT(flags, p); + protocol = *p++; + algo = *p++; + + /* See if we have cached a DS record which validates this key */ + for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) + if (recp1->addr.key.algo == algo && is_supported_digest(recp1->addr.key.digest)) + break; + + /* DS record needed to validate key is missing, return name of DS in namebuff */ + if (!recp1) + return STAT_NEED_DS; + else + { + int valid = 1; + /* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest) + and see if it equals digest stored in recp1 + */ + + if (!valid) + return STAT_BOGUS; + } + + if ((key = blockdata_alloc((char*)p, rdlen))) + { + + /* We've proved that the KEY is OK, store it in the cache */ + if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY))) + { + crecp->uid = rdlen; + crecp->addr.key.keydata = key; + crecp->addr.key.algo = algo; + crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); + gotone = 1; + } + } + + } + + cache_end_insert(); + + + return gotone ? STAT_SECURE : STAT_INSECURE; +} +/* The DNS packet is expected to contain the answer to a DS query + Put all DSs in the answer which are valid into the cache. + return codes: + STAT_INSECURE bad packet, no DNSKEYs in reply. + STAT_SECURE At least one valid DS found and in cache. + STAT_BOGUS At least one DS found, which fails validation. + STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname +*/ + +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +{ + unsigned char *p = (unsigned char *)(header+1); + struct crec *crecp, *recp1; + int qtype, qclass, val, j, gotone; + struct blockdata *key; + + if (ntohs(header->qdcount) != 1) + return STAT_INSECURE; + + if (!extract_name(header, plen, &p, name, 1, 4)) + return STAT_INSECURE; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qtype != T_DS || qclass != class) + return STAT_INSECURE; + + val = validate_rrset(header, plen, class, T_DS, name, keyname); + + /* failed to validate or missing key. */ + if (val != STAT_SECURE) + return val; + + cache_start_insert(); + + for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) + { + int ttl, rdlen, rc, algo; + + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + /* check type, class and name, skip if not in DS rrset */ + if (qclass != class || qtype != T_DS || rc == 2) + { + p += rdlen; + continue; + } + + if ((key = blockdata_alloc((char*)p, rdlen))) + { + + /* 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))) + { + crecp->uid = rdlen; + crecp->addr.key.keydata = key; + crecp->addr.key.algo = algo; + crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); + gotone = 1; + } + } + + } + + cache_end_insert(); + + + return gotone ? STAT_SECURE : STAT_INSECURE; +} + + + +/* Validate a single RRset (class, type, name) in the supplied DNS reply + Return code: + STAT_SECURE if it validates. + STAT_INSECURE can't validate (no RRSIG, bad packet). + STAT_BOGUS signature is wrong. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) +*/ +int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname) +{ + unsigned char *p, *psav, *sig; + int rrsetidx, res, sigttl, sig_data_len, j; + struct crec *crecp; + void *rrset[MAXRRSET]; /* TODO: max RRset size? */ + int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; + + if (!(p = skip_questions(header, plen))) + return STAT_INSECURE; + + /* look for an RRSIG record for this RRset and get pointers to each record */ + for (rrsetidx = 0, sig = NULL, j = ntohs(header->ancount) + ntohs(header->nscount); + j != 0; j--) + { + unsigned char *pstart = p; + int stype, sclass, sttl, rdlen; + + if (!(res = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(stype, p); + GETSHORT(sclass, p); + GETLONG(sttl, p); + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_INSECURE; /* bad packet */ + + if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class) + continue; + + if (htons(stype) == type) + { + rrset[rrsetidx++] = pstart; + if (rrsetidx == MAXRRSET) + return STAT_INSECURE; /* RRSET too big TODO */ + } + + if (htons(stype) == T_RRSIG) + { + /* name matches, RRSIG for correct class */ + /* enough data? */ + if (rdlen < 18) + return STAT_INSECURE; + + GETSHORT(type_covered, p); + algo = *p++; + labels = *p++; + GETLONG(orig_ttl, p); + GETLONG(sig_expiration, p); + GETLONG(sig_inception, p); + GETSHORT(key_tag, p); + + if (type_covered != type || + !check_date_range(sig_inception, sig_expiration)) + { + /* covers wrong type or out of date - skip */ + p = psav; + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; + continue; + } + + if (!extract_name(header, plen, &p, keyname, 1, 0)) + return STAT_INSECURE; + + /* OK, we have the signature record, see if the + relevant DNSKEY is in the cache. */ + for (crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY); + crecp; + crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag) + break; + + /* No, abort for now whilst we get it */ + if (!crecp) + return STAT_NEED_KEY; + + /* Save point to signature data */ + sig = p; + sig_data_len = rdlen - (p - psav); + sigttl = sttl; + + /* next record */ + p = psav; + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; + } + } + + /* 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. */ + rrset_canonical_order_ctx.header = header; + rrset_canonical_order_ctx.pktlen = plen; + qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); + + /* Now initialize the signature verification algorithm and process the whole + RRset */ + VerifyAlgCtx *alg = verifyalg_alloc(algo); + if (!alg) + return STAT_INSECURE; + + alg->sig = sig; + alg->siglen = sig_data_len; + + u16 ntype = htons(type); + u16 nclass = htons(class); + u32 nsigttl = htonl(sigttl); + + /* TODO: we shouldn't need to convert this to wire here. Best solution would be: + - Use process_name() instead of extract_name() everywhere in dnssec code + - Convert from wire format to representation format only for querying/storing cache + */ + unsigned char owner_wire[MAXCDNAME]; + int owner_wire_len = convert_domain_to_wire(name, owner_wire); + + digestalg_begin(alg->vtbl->digest_algo); + digestalg_add_data(sigrdata, 18+signer_name_rdlen); + for (i = 0; i < rrsetidx; ++i) + { + p = (unsigned char*)(rrset[i]); + + digestalg_add_data(owner_wire, owner_wire_len); + digestalg_add_data(&ntype, 2); + digestalg_add_data(&nclass, 2); + digestalg_add_data(&nsigttl, 4); + + p += 8; + if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p)) + return 0; + } + int digest_len = digestalg_len(); + memcpy(alg->digest, digestalg_final(), digest_len); + + if (alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp_uid)) + return STAT_SECURE; + + return STAT_INSECURE; +} + + +#if 0 static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, unsigned char *reply, int count, char *owner, int sigclass, int sigrdlen, unsigned char *sig, @@ -624,6 +980,7 @@ static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_d 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) @@ -663,6 +1020,8 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, } } +#endif /* comment out */ + /* Compute keytag (checksum to quickly index a key). See RFC4034 */ static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen) { diff --git a/src/forward.c b/src/forward.c index 97f8800..7e422e3 100644 --- a/src/forward.c +++ b/src/forward.c @@ -678,15 +678,14 @@ void reply_query(int fd, int family, time_t now) if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { int status; - char rrbitmap[256/8]; int class; - if (forward->flags && FREC_DNSSKEY_QUERY) - status = dnssec_validate_by_ds(header, n, daemon->namebuff, &class); - else if (forward->flags && FREC_DS_QUERY) - status = dnssec_validate_dnskey(header, n, daemon->namebuff, &class); + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else - status = dnssec_validate_reply(&rrbitmap, header, n, daemon->namebuff, &class); + status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); /* Can't validate, as we're missing key data. Put this answer aside, whilst we get that. */ @@ -717,7 +716,7 @@ void reply_query(int fd, int family, time_t now) } new->crc = questions_crc(header, nn, daemon->namebuff); new->new_id = get_id(new->crc); - header->id = htons(new->id); + header->id = htons(new->new_id); /* Don't resend this. */ daemon->srv_save = NULL; @@ -751,22 +750,30 @@ void reply_query(int fd, int family, time_t now) and validate them with the new data. Failure to find needed data here is an internal error. Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ - while (forward->flags & FREC_DNSSEC_QUERY) + while (forward->dependent) { - if (status == STAT_SECURE) - extract_dnssec_replies(); + struct frec *prev = forward->dependent; free_frec(forward); - forward = forward->dependent; + forward = prev; blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header); n = forward->stash_len; if (status == STAT_SECURE) { - status = dnssec_validate(forward->flags, header, n); - if (status == STAT_NEED_DS || status == STAT_NEED_KEY) - my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); } } + /* All DNSKEY and DS records done and in cache, now finally validate original + answer, provided last DNSKEY is OK. */ + if (status == STAT_SECURE) + status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); + if (status == STAT_SECURE) cache_secure = 1; /* TODO return SERVFAIL here */ diff --git a/src/rfc1035.c b/src/rfc1035.c index ab0b239..e547782 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -16,13 +16,6 @@ #include "dnsmasq.h" - -#define CHECK_LEN(header, pp, plen, len) \ - ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) - -#define ADD_RDLEN(header, pp, plen, len) \ - (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) - int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, char *name, int isExtract, int extrabytes) { From 0fc2f31368017b8a5d1ac80b8ae3efb4dacc24ad Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 10:26:58 +0000 Subject: [PATCH 086/105] First functional DNSSEC - highly alpha. --- src/cache.c | 83 +++- src/config.h | 11 +- src/dnsmasq.h | 29 +- src/dnssec-crypto.h | 8 +- src/dnssec-openssl.c | 40 +- src/dnssec.c | 975 ++++++++++++++++--------------------------- src/forward.c | 104 +++-- src/option.c | 32 +- src/rfc1035.c | 38 +- src/util.c | 64 ++- 10 files changed, 683 insertions(+), 701 deletions(-) diff --git a/src/cache.c b/src/cache.c index cfbeae3..ee27e4e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -56,6 +56,8 @@ static const struct { { 38, "A6" }, { 39, "DNAME" }, { 41, "OPT" }, + { 43, "DS" }, + { 46, "RRSIG" }, { 48, "DNSKEY" }, { 249, "TKEY" }, { 250, "TSIG" }, @@ -916,12 +918,19 @@ void cache_reload(void) struct name_list *nl; struct cname *a; struct interface_name *intr; +#ifdef HAVE_DNSSEC + struct dnskey *key; +#endif cache_inserted = cache_live_freed = 0; for (i=0; iflags & (F_DNSKEY | F_DS)) + blockdata_free(cache->addr.key.keydata); +#endif tmp = cache->hash_next; if (cache->flags & (F_HOSTS | F_CONFIG)) { @@ -948,13 +957,27 @@ void cache_reload(void) if (hostname_isequal(a->target, intr->name) && ((cache = whine_malloc(sizeof(struct crec))))) { - cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; + cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG | F_DNSSECOK; cache->name.namep = a->alias; cache->addr.cname.target.int_name = intr; cache->addr.cname.uid = -1; cache_hash(cache); add_hosts_cname(cache); /* handle chains */ } + +#ifdef HAVE_DNSSEC + for (key = daemon->dnskeys; key; key = key->next) + if ((cache = whine_malloc(sizeof(struct crec))) && + (cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen))) + { + cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP; + cache->name.namep = key->name; + cache->uid = key->keylen; + cache->addr.key.algo = key->algo; + cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen); + cache_hash(cache); + } +#endif /* borrow the packet buffer for a temporary by-address hash */ memset(daemon->packet, 0, daemon->packet_buff_sz); @@ -1197,16 +1220,13 @@ void dump_cache(time_t now) for (i=0; ihash_next) { - char *a, *p = daemon->namebuff; - p += sprintf(p, "%-40.40s ", cache_get_name(cache)); - if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD)) - a = ""; - else if (cache->flags & F_CNAME) - { - a = ""; - if (!is_outdated_cname_pointer(cache)) - a = cache_get_cname_target(cache); - } + char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache); + *a = 0; + if (strlen(n) == 0) + n = ""; + p += sprintf(p, "%-40.40s ", n); + if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache)) + a = cache_get_cname_target(cache); #ifdef HAVE_DNSSEC else if (cache->flags & F_DNSKEY) { @@ -1216,11 +1236,11 @@ void dump_cache(time_t now) else if (cache->flags & F_DS) { a = daemon->addrbuff; - sprintf(a, "%5u %3u %3u %u", cache->addr.key.keytag, - cache->addr.key.algo, cache->addr.key.digest, cache->uid); + sprintf(a, "%5u %3u %3u", cache->addr.key.keytag, + cache->addr.key.algo, cache->addr.key.digest); } #endif - else + else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD)) { a = daemon->addrbuff; if (cache->flags & F_IPV4) @@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) if (addr) { + if (flags & F_KEYTAG) + sprintf(daemon->addrbuff, arg, addr->addr.keytag); + else + { #ifdef HAVE_IPV6 - inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, - addr, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, + addr, daemon->addrbuff, ADDRSTRLEN); #else - strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); + strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); #endif + } } + else + dest = arg; if (flags & F_REVERSE) { @@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) source = arg; else if (flags & F_UPSTREAM) source = "reply"; + else if (flags & F_SECSTAT) + source = "validation"; else if (flags & F_AUTH) source = "auth"; else if (flags & F_SERVER) @@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) source = arg; verb = "from"; } + else if (flags & F_DNSSEC) + { + source = arg; + verb = "to"; + } else source = "cached"; @@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks) keyblock_free = blocks; } } + +void blockdata_retrieve(struct blockdata *block, size_t len, void *data) +{ + size_t blen; + struct blockdata *b; + + for (b = block; len > 0 && b; b = b->next) + { + blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; + memcpy(data, b->key, blen); + data += blen; + len -= blen; + } +} + #endif diff --git a/src/config.h b/src/config.h index 079c3d9..969c2b5 100644 --- a/src/config.h +++ b/src/config.h @@ -139,8 +139,8 @@ RESOLVFILE /* #define HAVE_DBUS */ /* #define HAVE_IDN */ /* #define HAVE_CONNTRACK */ -#define HAVE_DNSSEC -#define HAVE_OPENSSL +#define HAVE_DNSSEC +#define HAVE_OPENSSL /* Default locations for important system files. */ @@ -385,7 +385,12 @@ static char *compile_opts = #ifndef HAVE_AUTH "no-" #endif - "auth"; +"auth " +#ifndef HAVE_DNSSEC +"no-" +#endif +"DNSSEC"; + #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 8a3541a..044c865 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -242,6 +242,7 @@ struct all_addr { #ifdef HAVE_IPV6 struct in6_addr addr6; #endif + unsigned int keytag; } addr; }; @@ -286,6 +287,12 @@ struct cname { struct cname *next; }; +struct dnskey { + char *name, *key; + int keylen, algo, flags; + struct dnskey *next; +}; + #define ADDRLIST_LITERAL 1 #define ADDRLIST_IPV6 2 @@ -360,7 +367,7 @@ struct crec { } key; } addr; time_t ttd; /* time to die */ - /* used as keylen if F_DS or F_DNSKEY, index to source for F_HOSTS */ + /* used as keylen ifF_DNSKEY, index to source for F_HOSTS */ int uid; unsigned short flags; union { @@ -395,6 +402,9 @@ struct crec { #define F_QUERY (1u<<19) #define F_NOERR (1u<<20) #define F_AUTH (1u<<21) +#define F_DNSSEC (1u<<22) +#define F_KEYTAG (1u<<23) +#define F_SECSTAT (1u<<24) /* composites */ #define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */ @@ -896,6 +906,9 @@ extern struct daemon { #ifdef OPTION6_PREFIX_CLASS struct prefix_class *prefix_classes; #endif +#ifdef HAVE_DNSSEC + struct dnskey *dnskeys; +#endif /* globally used stuff for DNS */ char *packet; /* packet buffer */ @@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init); #ifdef HAVE_DNSSEC struct blockdata *blockdata_alloc(char *data, size_t len); size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt); +void blockdata_retrieve(struct blockdata *block, size_t len, void *data); void blockdata_free(struct blockdata *blocks); #endif @@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, unsigned long local_ttl); int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, time_t now, char **ipsets, int is_sign, int checkrebind, - int checking_disabled); + int no_cache, int secure); size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct in_addr local_addr, struct in_addr local_netmask, time_t now); int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, @@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type); +size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); -int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname); -int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class); +int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, + int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo, int keytag); +int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class); +int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); /* util.c */ void rand_init(void); @@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t); int prettyprint_addr(union mysockaddr *addr, char *buf); int parse_hex(char *in, unsigned char *out, int maxlen, unsigned int *wildcard_mask, int *mac_type); +#ifdef HAVE_DNSSEC +int parse_base64(char *in, char *out); +#endif int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask); int expand_buf(struct iovec *iov, size_t size); diff --git a/src/dnssec-crypto.h b/src/dnssec-crypto.h index 1717db6..77c5bc5 100644 --- a/src/dnssec-crypto.h +++ b/src/dnssec-crypto.h @@ -17,7 +17,7 @@ #ifndef DNSSEC_CRYPTO_H #define DNSSEC_CRYPTO_H -struct keydata; +struct blockdata; /* * vtable for a signature verification algorithm. @@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx; typedef struct { int digest_algo; - int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len); + int (*verify)(VerifyAlgCtx *ctx, struct blockdata *key, unsigned key_len); } VerifyAlg; struct VerifyAlgCtx @@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a); #define DIGESTALG_SHA512 257 int digestalg_supported(int algo); -int digestalg_begin(int algo); +void digestalg_begin(int algo); void digestalg_add_data(void *data, unsigned len); -void digestalg_add_keydata(struct keydata *key, size_t len); +void digestalg_add_keydata(struct blockdata *key, size_t len); unsigned char *digestalg_final(void); int digestalg_len(void); diff --git a/src/dnssec-openssl.c b/src/dnssec-openssl.c index 4bf7e73..2e25f82 100644 --- a/src/dnssec-openssl.c +++ b/src/dnssec-openssl.c @@ -14,13 +14,16 @@ along with this program. If not, see . */ -#include #include "dnsmasq.h" + +#ifdef HAVE_DNSSEC + #include "dnssec-crypto.h" #include #include #include #include +#include #define POOL_SIZE 1 static union _Pool @@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len) printf("\n"); } -static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len) +static int keydata_to_bn(BIGNUM *ret, struct blockdata **key_data, unsigned char **p, unsigned len) { size_t cnt; BIGNUM temp; BN_init(ret); - cnt = keydata_walk(key_data, p, len); + cnt = blockdata_walk(key_data, p, len); BN_bin2bn(*p, cnt, ret); len -= cnt; *p += cnt; while (len > 0) { - if (!(cnt = keydata_walk(key_data, p, len))) + if (!(cnt = blockdata_walk(key_data, p, len))) return 0; BN_lshift(ret, ret, cnt*8); BN_init(&temp); @@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char * return 1; } -static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, unsigned key_len) +static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct blockdata *key_data, unsigned key_len) { unsigned char *p = key_data->key; size_t exp_len, mod_len; @@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, keydata_to_bn(mod, &key_data, &p, mod_len); } -static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata *key_data, unsigned key_len) +static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct blockdata *key_data, unsigned key_len) { unsigned char *p = key_data->key; int T; @@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata_to_bn(Y, &key_data, &p, 64+T*8); } -static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len, int nid, int dlen) +static int rsa_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len, int nid, int dlen) { int validated = 0; @@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_ return validated; } -static int rsamd5_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) +static int rsamd5_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) { return rsa_verify(ctx, key_data, key_len, NID_md5, 16); } -static int rsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) +static int rsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) { return rsa_verify(ctx, key_data, key_len, NID_sha1, 20); } -static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) +static int rsasha256_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) { return rsa_verify(ctx, key_data, key_len, NID_sha256, 32); } -static int rsasha512_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) +static int rsasha512_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) { return rsa_verify(ctx, key_data, key_len, NID_sha512, 64); } -static int dsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len) +static int dsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len) { static unsigned char asn1_signature[] = { @@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo) int i; VerifyAlgCtx *ret = 0; - if (!verifyalg_supported(algo)) - return 0; - if (pool_used == (1< + and Copyright (c) 2012-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,11 +16,14 @@ */ #include "dnsmasq.h" + +#ifdef HAVE_DNSSEC + #include "dnssec-crypto.h" #include /* Maximum length in octects of a domain name, in wire format */ -#define MAXCDNAME 256 +#define MAXCDNAME 256 #define MAXRRSET 16 @@ -28,8 +32,6 @@ #define SERIAL_LT -1 #define SERIAL_GT 1 -static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen); - /* Implement RFC1982 wrapped compare for 32-bit numbers */ static int serial_compare_32(unsigned long s1, unsigned long s2) { @@ -45,36 +47,6 @@ static int serial_compare_32(unsigned long s1, unsigned long s2) return SERIAL_UNDEF; } -/* Extract a DNS name from wire format, without handling compression. This is - faster than extract_name() and does not require access to the full dns - packet. */ -static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) -{ - unsigned char *start=rr, *end = rr+maxlen; - int count; - - while (rr < end && *rr != 0) - { - count = *rr++; - while (count-- > 0 && rr < end) - { - *buf = *rr++; - if (!isascii(*buf) || iscntrl(*buf) || *buf == '.') - return 0; - if (*buf >= 'A' && *buf <= 'Z') - *buf += 'a' - 'A'; - buf++; - } - *buf++ = '.'; - } - /* Remove trailing dot (if any) */ - if (rr != start) - *(--buf) = 0; - if (rr == end) - return 0; - /* Trailing \0 in source data must be consumed */ - return rr-start+1; -} /* process_domain_name() - do operations with domain names in canonicalized wire format. * @@ -419,46 +391,55 @@ typedef struct PendingRRSIGValidation int keytag; } PendingRRSIGValidation; -/* strchrnul - like strchr, but when character is not found, returns a pointer to the terminating \0. - This is an existing C GNU extension, but it's easier to reimplement it, - rather than tweaking with configure. */ -static char *my_strchrnul(char *str, char ch) +/* Convert from presentation format to wire format, in place. + Also map UC -> LC. + Note that using extract_name to get presentation format + then calling to_wire() removes compression and maps case, + thus generating names in canonical form. + Calling to_wire followed by from_wire is almost an identity, + except that the UC remains mapped to LC. +*/ +static int to_wire(char *name) { - while (*str && *str != ch) - str++; - return str; + unsigned char *l, *p, term; + int len; + + for (l = (unsigned char*)name; *l != 0; l = p) + { + for (p = l; *p != '.' && *p != 0; p++) + if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + + term = *p; + + if ((len = p - l) != 0) + memmove(l+1, l, len); + *l = len; + + p++; + + if (term == 0) + *p = 0; + } + + return l + 1 - (unsigned char *)name; } -/* Convert a domain name to wire format */ -static int convert_domain_to_wire(char *name, unsigned char* out) +/* Note: no compression allowed in input. */ +static void from_wire(char *name) { - unsigned char len; - unsigned char *start = out; - char *p; + unsigned char *l; + int len; - do + for (l = (unsigned char *)name; *l != 0; l += len+1) { - p = my_strchrnul(name, '.'); - if ((len = p-name)) - { - *out++ = len; - while (len--) - { - char ch = *name++; - /* TODO: this will not be required anymore once we - remove all usages of extract_name() from DNSSEC code */ - if (ch >= 'A' && ch <= 'Z') - ch = ch - 'A' + 'a'; - *out++ = ch; - } - } - name = p+1; + len = *l; + memmove(l, l+1, len); + l[len] = '.'; } - while (*p); - *out++ = '\0'; - return out-start; + *(l-1) = 0; } @@ -498,43 +479,56 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk return 1; } -size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type) +size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr) { unsigned char *p; + char types[20]; + + querystr("dnssec", types, type); + if (addr->sa.sa_family == AF_INET) + log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); +#ifdef HAVE_IPV6 + else + log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); +#endif + header->qdcount = htons(1); header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); - header->hb3 = HB3_RD; + header->hb3 = HB3_RD; SET_OPCODE(header, QUERY); - header->hb4 = 0; + header->hb4 = HB4_CD; /* ID filled in later */ p = (unsigned char *)(header+1); p = do_rfc1035_name(p, name); + *p++ = 0; PUTSHORT(type, p); PUTSHORT(class, p); return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ); } -/* The DNS packet is expected to contain the answer to a DNSKEY query +/* The DNS packet is expected to contain the answer to a DNSKEY query. + Leave name of qury in name. Put all DNSKEYs in the answer which are valid into the cache. return codes: STAT_INSECURE bad packet, no DNSKEYs in reply. STAT_SECURE At least one valid DNSKEY found and in cache. - STAT_BOGUS At least one DNSKEY found, which fails validation. - STAT_NEED_DS DS records to validate a key not found, name in namebuff + STAT_BOGUS No DNSKEYs found, which can be validated with DS, + or self-sign for DNSKEY RRset is not valid. + STAT_NEED_DS DS records to validate a key not found, name in keyname */ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) { - unsigned char *p; + unsigned char *psave, *p = (unsigned char *)(header+1); struct crec *crecp, *recp1; - int j, qtype, qclass, ttl, rdlen, flags, protocol, algo, gotone; + int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; struct blockdata *key; if (ntohs(header->qdcount) != 1) @@ -546,15 +540,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch GETSHORT(qtype, p); GETSHORT(qclass, p); - if (qtype != T_DNSKEY || qclass != class) + if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) return STAT_INSECURE; + /* See if we have cached a DS record which validates this key */ + if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) + { + strcpy(keyname, name); + return STAT_NEED_DS; + } + cache_start_insert(); - for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) + /* NOTE, we need to find ONE DNSKEY which matches the DS */ + for (valid = 0, j = ntohs(header->ancount); j != 0; j--) { /* Ensure we have type, class TTL and length */ - if (!extract_name(header, plen, &p, name, 1, 10)) + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) return STAT_INSECURE; /* bad packet */ GETSHORT(qtype, p); @@ -562,64 +564,89 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch GETLONG(ttl, p); GETSHORT(rdlen, p); - if (qclass != class || qtype != T_DNSKEY || rdlen < 4) + if (qclass != class || qtype != T_DNSKEY || rc == 2) { - /* skip all records other than DNSKEY */ - p += rdlen; - continue; + if (ADD_RDLEN(header, p, plen, rdlen)) + continue; + + return STAT_INSECURE; /* bad packet */ } - crecp = cache_find_by_name(NULL, name, now, F_DS); + if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) + return STAT_INSECURE; /* bad packet */ + + psave = p; /* length at least covers flags, protocol and algo now. */ GETSHORT(flags, p); - protocol = *p++; + if (*p++ != 3) + return STAT_INSECURE; algo = *p++; - - /* See if we have cached a DS record which validates this key */ + keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + + /* Put the key into the cache. Note that if the validation fails, we won't + call cache_end_insert() and this will never be committed. */ + if ((key = blockdata_alloc((char*)p, rdlen - 4)) && + (recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY))) + { + recp1->uid = rdlen - 4; + recp1->addr.key.keydata = key; + recp1->addr.key.algo = algo; + recp1->addr.key.keytag = keytag; + } + + 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 && is_supported_digest(recp1->addr.key.digest)) - break; - - /* DS record needed to validate key is missing, return name of DS in namebuff */ - if (!recp1) - return STAT_NEED_DS; - else - { - int valid = 1; - /* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest) - and see if it equals digest stored in recp1 - */ - - if (!valid) - return STAT_BOGUS; - } - - if ((key = blockdata_alloc((char*)p, rdlen))) - { - - /* We've proved that the KEY is OK, store it in the cache */ - if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY))) - { - crecp->uid = rdlen; - crecp->addr.key.keydata = key; - crecp->addr.key.algo = algo; - crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); - gotone = 1; - } - } - - } - - cache_end_insert(); + if (recp1->addr.key.algo == algo && + recp1->addr.key.keytag == keytag && + (flags & 0x100) && /* zone key flag */ + digestalg_supported(recp1->addr.key.digest)) + { + int wire_len = to_wire(name); + + digestalg_begin(recp1->addr.key.digest); + digestalg_add_data(name, wire_len); + digestalg_add_data((char *)psave, rdlen); + + from_wire(name); - - return gotone ? STAT_SECURE : STAT_INSECURE; + /* TODO fragented digest */ + if (memcmp(digestalg_final(), recp1->addr.key.keydata->key, digestalg_len()) == 0 && + validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag)) + { + struct all_addr a; + valid = 1; + a.addr.keytag = keytag; + log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); + break; + } + } + } + + if (valid) + { + /* commit cache insert. */ + cache_end_insert(); + return STAT_SECURE; + } + + log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); + return STAT_BOGUS; } + + /* The DNS packet is expected to contain the answer to a DS query + Leave name of DS query in name. Put all DSs in the answer which are valid into the cache. return codes: - STAT_INSECURE bad packet, no DNSKEYs in reply. + STAT_INSECURE bad packet, no DS in reply. STAT_SECURE At least one valid DS found and in cache. STAT_BOGUS At least one DS found, which fails validation. STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname @@ -627,8 +654,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) { - unsigned char *p = (unsigned char *)(header+1); - struct crec *crecp, *recp1; + unsigned char *psave, *p = (unsigned char *)(header+1); + struct crec *crecp; int qtype, qclass, val, j, gotone; struct blockdata *key; @@ -641,10 +668,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char GETSHORT(qtype, p); GETSHORT(qclass, p); - if (qtype != T_DS || qclass != class) + if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0) return STAT_INSECURE; - - val = validate_rrset(header, plen, class, T_DS, name, keyname); + + val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0); + + if (val == STAT_BOGUS) + log_query(F_UPSTREAM, name, NULL, "BOGUS DS"); /* failed to validate or missing key. */ if (val != STAT_SECURE) @@ -654,7 +684,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) { - int ttl, rdlen, rc, algo; + int ttl, rdlen, rc, algo, digest, keytag; /* Ensure we have type, class TTL and length */ if (!(rc = extract_name(header, plen, &p, name, 0, 10))) @@ -666,32 +696,42 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char GETSHORT(rdlen, p); /* check type, class and name, skip if not in DS rrset */ - if (qclass != class || qtype != T_DS || rc == 2) - { - p += rdlen; - continue; - } - - if ((key = blockdata_alloc((char*)p, rdlen))) + if (qclass == class && qtype == T_DS && rc == 1) { + if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) + return STAT_INSECURE; /* bad packet */ + + psave = p; + GETSHORT(keytag, p); + algo = *p++; + digest = *p++; /* We've proved that the DS is OK, store it in the cache */ - if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS))) + if ((key = blockdata_alloc((char*)p, rdlen - 4)) && + (crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS))) { - crecp->uid = rdlen; + struct all_addr a; + a.addr.keytag = keytag; + log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); + crecp->addr.key.digest = digest; crecp->addr.key.keydata = key; crecp->addr.key.algo = algo; - crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen); - gotone = 1; + crecp->addr.key.keytag = keytag; } + else + return STAT_INSECURE; /* cache problem */ + + p = psave; } - + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; /* bad packet */ + } cache_end_insert(); - - return gotone ? STAT_SECURE : STAT_INSECURE; + return STAT_SECURE; } @@ -702,528 +742,249 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char STAT_INSECURE can't validate (no RRSIG, bad packet). STAT_BOGUS signature is wrong. STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + + if key is non-NULL, use that key, which has the algo and tag given in the params of those names, + otherwise find the key in the cache. */ -int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname) +int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, + int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) { - unsigned char *p, *psav, *sig; - int rrsetidx, res, sigttl, sig_data_len, j; - struct crec *crecp; - void *rrset[MAXRRSET]; /* TODO: max RRset size? */ + unsigned char *p; + int rrsetidx, sigidx, res, rdlen, j; + struct crec *crecp = NULL; + void *rrset[MAXRRSET], *sigs[MAXRRSET]; /* TODO: max RRset size? */ int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; if (!(p = skip_questions(header, plen))) return STAT_INSECURE; /* look for an RRSIG record for this RRset and get pointers to each record */ - for (rrsetidx = 0, sig = NULL, j = ntohs(header->ancount) + ntohs(header->nscount); + for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); j != 0; j--) { - unsigned char *pstart = p; - int stype, sclass, sttl, rdlen; + unsigned char *pstart; + int stype, sclass, sttl; if (!(res = extract_name(header, plen, &p, name, 0, 10))) return STAT_INSECURE; /* bad packet */ + pstart = p; + GETSHORT(stype, p); GETSHORT(sclass, p); GETLONG(sttl, p); GETSHORT(rdlen, p); + + (void)sttl; if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_INSECURE; /* bad packet */ - if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class) - continue; - - if (htons(stype) == type) + if (res == 1 && sclass == class) { - rrset[rrsetidx++] = pstart; - if (rrsetidx == MAXRRSET) - return STAT_INSECURE; /* RRSET too big TODO */ - } - - if (htons(stype) == T_RRSIG) - { - /* name matches, RRSIG for correct class */ - /* enough data? */ - if (rdlen < 18) - return STAT_INSECURE; - - GETSHORT(type_covered, p); - algo = *p++; - labels = *p++; - GETLONG(orig_ttl, p); - GETLONG(sig_expiration, p); - GETLONG(sig_inception, p); - GETSHORT(key_tag, p); - - if (type_covered != type || - !check_date_range(sig_inception, sig_expiration)) + if (stype == type) { - /* covers wrong type or out of date - skip */ - p = psav; - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_INSECURE; - continue; + rrset[rrsetidx++] = pstart; + if (rrsetidx == MAXRRSET) + return STAT_INSECURE; /* RRSET too big TODO */ } - if (!extract_name(header, plen, &p, keyname, 1, 0)) - return STAT_INSECURE; - - /* OK, we have the signature record, see if the - relevant DNSKEY is in the cache. */ - for (crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY); - crecp; - crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) - if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag) - break; - - /* No, abort for now whilst we get it */ - if (!crecp) - return STAT_NEED_KEY; - - /* Save point to signature data */ - sig = p; - sig_data_len = rdlen - (p - psav); - sigttl = sttl; - - /* next record */ + if (stype == T_RRSIG) + { + sigs[sigidx++] = pstart; + if (sigidx == MAXRRSET) + return STAT_INSECURE; /* RRSET too big TODO */ + } + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; + } + + /* RRset empty, no RRSIGs */ + if (rrsetidx == 0 || sigidx == 0) + return STAT_INSECURE; + + /* Now try all the sigs to try and find one which validates */ + for (j = 0; j sig = sig; - alg->siglen = sig_data_len; - - u16 ntype = htons(type); - u16 nclass = htons(class); - u32 nsigttl = htonl(sigttl); - - /* TODO: we shouldn't need to convert this to wire here. Best solution would be: - - Use process_name() instead of extract_name() everywhere in dnssec code - - Convert from wire format to representation format only for querying/storing cache - */ - unsigned char owner_wire[MAXCDNAME]; - int owner_wire_len = convert_domain_to_wire(name, owner_wire); - - digestalg_begin(alg->vtbl->digest_algo); - digestalg_add_data(sigrdata, 18+signer_name_rdlen); - for (i = 0; i < rrsetidx; ++i) - { - p = (unsigned char*)(rrset[i]); + continue; + } - digestalg_add_data(owner_wire, owner_wire_len); - digestalg_add_data(&ntype, 2); - digestalg_add_data(&nclass, 2); - digestalg_add_data(&nsigttl, 4); + if (!extract_name(header, plen, &p, keyname, 1, 0)) + return STAT_INSECURE; + + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ + if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) + return STAT_NEED_KEY; + + /* Sort RRset records in canonical order. */ + rrset_canonical_order_ctx.header = header; + rrset_canonical_order_ctx.pktlen = plen; + qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); + + alg = verifyalg_alloc(algo); + alg->sig = p; + alg->siglen = rdlen - (p - psav); + + ntype = htons(type); + nclass = htons(class); + nsigttl = htonl(orig_ttl); + + digestalg_begin(alg->vtbl->digest_algo); + digestalg_add_data(psav, 18); + wire_len = to_wire(keyname); + digestalg_add_data(keyname, wire_len); + from_wire(keyname); + + /* TODO wildcard rules : 4035 5.3.2 */ + for (i = 0; i < rrsetidx; ++i) + { + p = (unsigned char*)(rrset[i]); + + wire_len = to_wire(name); + digestalg_add_data(name, wire_len); + from_wire(name); + digestalg_add_data(&ntype, 2); + digestalg_add_data(&nclass, 2); + digestalg_add_data(&nsigttl, 4); + + p += 8; + if (!digestalg_add_rdata(type, header, plen, p)) + return STAT_INSECURE; + } - p += 8; - if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p)) - return 0; + memcpy(alg->digest, digestalg_final(), digestalg_len()); + + if (key) + { + if (algo_in == algo && keytag_in == key_tag && + alg->vtbl->verify(alg, key, keylen)) + return STAT_SECURE; + } + else + { + /* iterate through all possible keys 4035 5.3.1 */ + for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && + alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid)) + return STAT_SECURE; + } } - 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_BOGUS; } + - -#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) +/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ +int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class) { - 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)) + unsigned char *ans_start, *p1, *p2; + int type1, class1, rdlen1, type2, class2, rdlen2; + int i, j, rc; + + if (!(ans_start = skip_questions(header, plen))) + return STAT_INSECURE; + + for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) { - printf("ERROR: RRSIG algorithm not supported: %d\n", sigalg); - return 0; + if (!extract_name(header, plen, &p1, name, 1, 10)) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + + /* Don't try and validate RRSIGs! */ + if (type1 != T_RRSIG) + { + /* Check if we've done this RRset already */ + for (p2 = ans_start, j = 0; j < i; j++) + { + if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(type2, p2); + GETSHORT(class2, p2); + p2 += 4; /* TTL */ + GETSHORT(rdlen2, p2); + + if (type2 == type1 && class2 == class1 && rc == 1) + break; /* Done it before: name, type, class all match. */ + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_INSECURE; + } + + /* Not done, validate now */ + if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE) + { + *class = class1; /* Class for DS or DNSKEY */ + return rc; + } + } + + if (!ADD_RDLEN(header, p1, plen, rdlen1)) + return STAT_INSECURE; } - 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; + return STAT_SECURE; } -static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey) -{ - /* FIXME: keydata is non-contiguous */ - return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid); -} - - -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. */ - } -} - -#endif /* comment out */ /* Compute keytag (checksum to quickly index a key). See RFC4034 */ -static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen) +int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) { if (alg == 1) { /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm. See RFC4034, Appendix B.1 */ - return rdata[rdlen-3] * 256 + rdata[rdlen-2]; + return key[keylen-4] * 256 + key[keylen-3]; } else { unsigned long ac; int i; - ac = 0; - for (i = 0; i < rdlen; ++i) - ac += (i & 1) ? rdata[i] : rdata[i] << 8; - ac += (ac >> 16) & 0xFFFF; - return ac & 0xFFFF; + ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg; + for (i = 0; i < keylen; ++i) + ac += (i & 1) ? key[i] : key[i] << 8; + ac += (ac >> 16) & 0xffff; + return ac & 0xffff; } } -/* Check if the DS record (from cache) points to the DNSKEY record (from cache) */ -static int dnskey_ds_match(struct crec *dnskey, struct crec *ds) -{ - if (dnskey->addr.key.keytag != ds->addr.key.keytag) - return 0; - if (dnskey->addr.key.algo != ds->addr.key.algo) - return 0; - unsigned char owner[MAXCDNAME]; /* TODO: user part of daemon->namebuff */ - int owner_len = convert_domain_to_wire(cache_get_name(ds), owner); - size_t keylen = dnskey->uid; - int dig = ds->uid; - int digsize; - - if (!digestalg_begin(dig)) - return 0; - digsize = digestalg_len(); - digestalg_add_data(owner, owner_len); - digestalg_add_data("\x01\x01\x03", 3); - digestalg_add_data(&ds->addr.key.algo, 1); - digestalg_add_keydata(dnskey->addr.key.keydata, keylen); - return (memcmp(digestalg_final(), ds->addr.key.keydata->key, digsize) == 0); -} - -int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl, - int rdlen, unsigned char *rdata) -{ - int flags, proto, alg; - struct blockdata *key; struct crec *crecp; - unsigned char *ordata = rdata; int ordlen = rdlen; - - CHECKED_GETSHORT(flags, rdata, rdlen); - CHECKED_GETCHAR(proto, rdata, rdlen); - CHECKED_GETCHAR(alg, rdata, rdlen); - - if (proto != 3) - return 0; - /* Skip non-signing keys (as specified in RFC4034 */ - if (!(flags & 0x100)) - return 0; - - key = blockdata_alloc((char*)rdata, rdlen); - - /* TODO: time(0) is correct here? */ - crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY); - if (crecp) - { - /* TODO: improve union not to name "uid" this field */ - crecp->uid = rdlen; - crecp->addr.key.keydata = key; - crecp->addr.key.algo = alg; - crecp->addr.key.keytag = dnskey_keytag(alg, ordata, ordlen); - printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag); - } - else - { - blockdata_free(key); - /* TODO: if insertion really might fail, verify we don't depend on cache - insertion success for validation workflow correctness */ - printf("DNSKEY: cache insertion failure\n"); - return 0; - } - return 1; -} - -int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl, - int rdlen, unsigned char *rdata) -{ - int keytag, algo, dig; - struct blockdata *key; struct crec *crec_ds, *crec_key; - - CHECKED_GETSHORT(keytag, rdata, rdlen); - CHECKED_GETCHAR(algo, rdata, rdlen); - CHECKED_GETCHAR(dig, rdata, rdlen); - - if (!digestalg_supported(dig)) - return 0; - - key = blockdata_alloc((char*)rdata, rdlen); - - /* TODO: time(0) is correct here? */ - crec_ds = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DS); - if (!crec_ds) - { - blockdata_free(key); - /* TODO: if insertion really might fail, verify we don't depend on cache - insertion success for validation workflow correctness */ - printf("DS: cache insertion failure\n"); - return 0; - } - - /* TODO: improve union not to name "uid" this field */ - crec_ds->uid = dig; - crec_ds->addr.key.keydata = key; - crec_ds->addr.key.algo = algo; - crec_ds->addr.key.keytag = keytag; - printf("DS: storing key for %s (digest: %d)\n", owner, dig); - - /* Now try to find a DNSKEY which matches this DS digest. */ - printf("Looking for a DNSKEY matching DS %d...\n", keytag); - crec_key = NULL; - while ((crec_key = cache_find_by_name(crec_key, owner, time(0), F_DNSKEY))) /* TODO: time(0) */ - { - if (dnskey_ds_match(crec_key, crec_ds)) - { - /* TODO: create a link within the cache: ds => dnskey */ - printf("MATCH FOUND for keytag %d\n", keytag); - return 1; - } - } - - printf("ERROR: match not found for DS %d (owner: %s)\n", keytag, owner); - return 0; -} - -int dnssec1_validate(struct dns_header *header, size_t pktlen) -{ - unsigned char *p, *reply; - char *owner = daemon->namebuff; - int i, s, qtype, qclass, rdlen; - unsigned long ttl; - int slen[3] = { ntohs(header->ancount), ntohs(header->nscount), ntohs(header->arcount) }; - - if (slen[0] + slen[1] + slen[2] == 0) - return 0; - if (!(reply = p = skip_questions(header, pktlen))) - return 0; - - /* First, process DNSKEY/DS records and add them to the cache. */ - cache_start_insert(); - for (i = 0; i < slen[0]; i++) - { - if (!extract_name(header, pktlen, &p, owner, 1, 10)) - return 0; - GETSHORT(qtype, p); - GETSHORT(qclass, p); - GETLONG(ttl, p); - GETSHORT(rdlen, p); - if (qtype == T_DS) - { - printf("DS found\n"); - dnssec_parseds(header, pktlen, owner, ttl, rdlen, p); - } - else if (qtype == T_DNSKEY) - { - printf("DNSKEY found\n"); - dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p); - } - p += rdlen; - } - cache_end_insert(); - - /* After we have cached DNSKEY/DS records, start looking for RRSIGs. - We want to do this in a separate step because we want the cache - to be already populated with DNSKEYs before parsing signatures. */ - p = reply; - for (s = 0; s < 3; ++s) - { - reply = p; - for (i = 0; i < slen[s]; i++) - { - if (!extract_name(header, pktlen, &p, owner, 1, 10)) - return 0; - GETSHORT(qtype, p); - GETSHORT(qclass, p); - GETLONG(ttl, p); - GETSHORT(rdlen, p); - if (qtype == T_RRSIG) - { - printf("RRSIG found (owner: %s)\n", owner); - /* TODO: missing logic. We should only validate RRSIGs for which we - have a valid DNSKEY that is referenced by a DS record upstream. - There is a memory vs CPU conflict here; should we validate everything - to save memory and thus waste CPU, or better first acquire all information - (wasting memory) and then doing the minimum CPU computations required? */ - dnssec_parserrsig(header, pktlen, reply, slen[s], owner, qclass, rdlen, p); - } - p += rdlen; - } - } - - return 1; -} +#endif /* HAVE_DNSSEC */ diff --git a/src/forward.c b/src/forward.c index 7e422e3..64f9bac 100644 --- a/src/forward.c +++ b/src/forward.c @@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) - plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ); + { + plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ); + header->hb4 |= HB4_CD; + } #endif while (1) @@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server SET_RCODE(header, NOERROR); } - if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache)) + if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure)) { my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; @@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now) if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) { int status; - int class; + + /* We've had a reply already, which we're validating. Ignore this duplicate */ + if (forward->stash) + return; if (forward->flags & FREC_DNSKEY_QUERY) - status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else if (forward->flags & FREC_DS_QUERY) status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else - status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class); /* Can't validate, as we're missing key data. Put this answer aside, whilst we get that. */ if (status == STAT_NEED_DS || status == STAT_NEED_KEY) { struct frec *new; - if ((forward->stash = blockdata_alloc((char *)header, n))) + + if ((new = get_new_frec(now, NULL, 1))) { - forward->stash_len = n; + struct frec *next = new->next; + *new = *forward; /* copy everything, then overwrite */ + new->next = next; + new->stash = NULL; + new->blocking_query = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); - if ((new = get_new_frec(now, NULL, 1))) + if ((forward->stash = blockdata_alloc((char *)header, n))) { int fd; - new = forward; /* copy everything, then overwrite */ + forward->stash_len = n; + new->dependent = forward; /* to find query awaiting new one. */ forward->blocking_query = new; /* for garbage cleaning */ - /* validate routines leave name of required record in daemon->namebuff */ + /* validate routines leave name of required record in daemon->keyname */ if (status == STAT_NEED_KEY) { new->flags |= FREC_DNSKEY_QUERY; - nn = dnssec_generate_query(header, daemon->namebuff, class, T_DNSKEY); + nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DNSKEY, &server->addr); } else if (status == STAT_NEED_DS) { new->flags |= FREC_DS_QUERY; - nn = dnssec_generate_query(header, daemon->namebuff, class, T_DS); + nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DS, &server->addr); } new->crc = questions_crc(header, nn, daemon->namebuff); new->new_id = get_id(new->crc); @@ -739,9 +752,11 @@ void reply_query(int fd, int family, time_t now) } /* Send DNSSEC query to same server as original query */ - while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send()); + while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send()); + server->queries++; } } + return; } @@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now) and validate them with the new data. Failure to find needed data here is an internal error. Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ - while (forward->dependent) + if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) { - struct frec *prev = forward->dependent; - free_frec(forward); - forward = prev; - blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header); - n = forward->stash_len; - if (status == STAT_SECURE) + while (forward->dependent) { - if (forward->flags & FREC_DNSKEY_QUERY) - status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); - else if (forward->flags & FREC_DS_QUERY) - status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + struct frec *prev; - if (status == STAT_NEED_DS || status == STAT_NEED_KEY) - my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); + if (status == STAT_SECURE) + { + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + } + + prev = forward->dependent; + free_frec(forward); + forward = prev; + forward->blocking_query = NULL; /* already gone */ + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + n = forward->stash_len; + } + + /* All DNSKEY and DS records done and in cache, now finally validate original + answer, provided last DNSKEY is OK. */ + if (status == STAT_SECURE) + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class); + + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + { + my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); + status = STAT_INSECURE; } } - - /* All DNSKEY and DS records done and in cache, now finally validate original - answer, provided last DNSKEY is OK. */ - if (status == STAT_SECURE) - status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class); + + log_query(F_KEYTAG | F_SECSTAT, "result", NULL, + status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + no_cache_dnssec = 0; if (status == STAT_SECURE) cache_secure = 1; /* TODO return SERVFAIL here */ else if (status == STAT_BOGUS) no_cache_dnssec = 1; + + /* restore CD bit to the value in the query */ + if (forward->flags & FREC_CHECKING_DISABLED) + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; } #endif @@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family) return NULL; /* doom */ } - static void free_frec(struct frec *f) { if (f->rfd4 && --(f->rfd4->refcount) == 0) @@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f) #ifdef HAVE_DNSSEC if (f->stash) - blockdata_free(f->stash); + { + blockdata_free(f->stash); + f->stash = NULL; + } /* Anything we're waiting on is pointless now, too */ if (f->blocking_query) diff --git a/src/option.c b/src/option.c index cadabd5..2933e9f 100644 --- a/src/option.c +++ b/src/option.c @@ -139,6 +139,7 @@ struct myoption { #define LOPT_QUIET_DHCP6 327 #define LOPT_QUIET_RA 328 #define LOPT_SEC_VALID 329 +#define LOPT_DNSKEY 330 #ifdef HAVE_GETOPT_LONG @@ -276,6 +277,7 @@ static const struct myoption opts[] = { "ipset", 1, 0, LOPT_IPSET }, { "synth-domain", 1, 0, LOPT_SYNTH }, { "dnssec", 0, 0, LOPT_SEC_VALID }, + { "dnskey", 1, 0, LOPT_DNSKEY }, #ifdef OPTION6_PREFIX_CLASS { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, #endif @@ -428,6 +430,7 @@ static struct { { LOPT_SYNTH, ARG_DUP, ",,[]", 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 #ifdef OPTION6_PREFIX_CLASS { LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, @@ -3670,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma daemon->host_records_tail = new; break; } - + +#ifdef HAVE_DNSSEC + case LOPT_DNSKEY: + { + struct dnskey *new = opt_malloc(sizeof(struct dnskey)); + char *key64, *algo; + + if (!(comma = split(arg)) || !(algo = split(comma)) || !(key64 = split(algo)) || + !atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) || + !(new->name = canonicalise_opt(arg))) + ret_err(_("bad DNSKEY")); + + + /* Upper bound on length */ + new->key = opt_malloc((3*strlen(key64)/4)); + unhide_metas(key64); + if ((new->keylen = parse_base64(key64, new->key)) == -1) + ret_err(_("bad base64 in DNSKEY")); + + new->next = daemon->dnskeys; + daemon->dnskeys = new; + + break; + } +#endif + default: - ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)")); + ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)")); } diff --git a/src/rfc1035.c b/src/rfc1035.c index e547782..a05060a 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned return plen; /* Too big */ } - PUTSHORT(optno, p); - PUTSHORT(optlen, p); - memcpy(p, opt, optlen); - p += optlen; + if (optno != 0) + { + PUTSHORT(optno, p); + PUTSHORT(optlen, p); + memcpy(p, opt, optlen); + p += optlen; + } PUTSHORT(p - datap, lenp); return p - (unsigned char *)header; @@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) expired and cleaned out that way. Return 1 if we reject an address because it look like part of dns-rebinding attack. */ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, - char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec) + char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure) { unsigned char *p, *p1, *endrr, *namep; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; @@ -919,6 +922,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t struct crec *cpp = NULL; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; unsigned long cttl = ULONG_MAX, attl; + + if (RCODE(header) == NXDOMAIN) + flags |= F_NXDOMAIN; + + if (secure) + flags |= F_DNSSECOK; namep = p; if (!extract_name(header, qlen, &p, name, 1, 4)) @@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, int dryrun = 0, sec_reqd = 0; int is_sign; struct crec *crecp; - int nxdomain = 0, auth = 1, trunc = 0; + int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; /* If there is an RFC2671 pseudoheader then it will be overwritten by @@ -1621,6 +1630,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) continue; + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + if (crecp->flags & F_NEG) { ans = 1; @@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) break; + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + if (crecp->flags & F_CNAME) { char *cname_target = cache_get_cname_target(crecp); @@ -1868,6 +1883,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) && (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))) { + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + ans = 1; if (!dryrun) { @@ -2046,7 +2064,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, /* truncation */ if (trunc) header->hb3 |= HB3_TC; - + + header->hb4 &= ~HB4_AD; + + if (option_bool(OPT_DNSSEC_VALID) || option_bool(OPT_DNSSEC_PROXY)) + if (sec_data) + header->hb4 |= HB4_AD; + if (nxdomain) SET_RCODE(header, NXDOMAIN); else diff --git a/src/util.c b/src/util.c index 096297d..c210add 100644 --- a/src/util.c +++ b/src/util.c @@ -109,10 +109,10 @@ static int check_name(char *in) if (in[l-1] == '.') { - if (l == 1) return 0; in[l-1] = 0; + nowhite = 1; } - + for (; (c = *in); in++) { if (c == '.') @@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen, return i; } +#ifdef HAVE_DNSSEC +static int charval(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + if (c >= 'a' && c <= 'z') + return c - 'a' + 26; + + if (c >= '0' && c <= '9') + return c - '0' + 52; + + if (c == '+') + return 62; + + if (c == '/') + return 63; + + if (c == '=') + return -1; + + return -2; +} + +int parse_base64(char *in, char *out) +{ + char *p = out; + int i, val[4]; + + while (*in) + { + for (i = 0; i < 4; i++) + { + while (*in == ' ') + in++; + if (*in == 0) + return -1; + if ((val[i] = charval(*in++)) == -2) + return -1; + } + + while (*in == ' ') + in++; + + if (val[1] == -1) + return -1; /* too much padding */ + + *p++ = (val[0] << 2) | (val[1] >> 4); + + if (val[2] != -1) + *p++ = (val[1] << 4) | ( val[2] >> 2); + + if (val[3] != -1) + *p++ = (val[2] << 6) | val[3]; + } + + return p - out; +} +#endif + /* return 0 for no match, or (no matched octets) + 1 */ int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask) { From 65d1e3bb9b9878735541f7daf6f9013042cb7d0c Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 11:00:01 +0000 Subject: [PATCH 087/105] Tweak libraries and make DNSSEC compile optional. --- Makefile | 8 ++++---- src/config.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 61c22c9..25ae31f 100644 --- a/Makefile +++ b/Makefile @@ -59,8 +59,8 @@ ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFI 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` +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` version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' @@ -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) $(ssl_cflags)" \ - build_libs="$(dbus_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(ssl_libs)" \ + 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)" \ -f $(top)/Makefile dnsmasq mostly_clean : diff --git a/src/config.h b/src/config.h index 969c2b5..d4978b6 100644 --- a/src/config.h +++ b/src/config.h @@ -139,8 +139,8 @@ RESOLVFILE /* #define HAVE_DBUS */ /* #define HAVE_IDN */ /* #define HAVE_CONNTRACK */ -#define HAVE_DNSSEC -#define HAVE_OPENSSL +/* #define HAVE_DNSSEC */ + /* Default locations for important system files. */ From 871417d45d4a4e061d9755720e5f0a0b9e3ff725 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 11:22:32 +0000 Subject: [PATCH 088/105] Handle truncated replies in DNSSEC validation. --- src/forward.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/forward.c b/src/forward.c index 64f9bac..f254fe1 100644 --- a/src/forward.c +++ b/src/forward.c @@ -686,7 +686,19 @@ void reply_query(int fd, int family, time_t now) if (forward->stash) return; - if (forward->flags & FREC_DNSKEY_QUERY) + if (header->hb3 & HB3_TC) + { + /* Truncated answer can't be validated. + The client will retry over TCP, but if this is an answer to a + DNSSEC-generated query, we have a problem. Should really re-send + over TCP. No-one with any sense will make a DNSKEY or DS RRset + exceed 4096, so this may not be a real problem. Just log + for now. */ + if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) + my_syslog(LOG_ERR, _("Reply to DNSSEC query truncated - validation fails.")); + status = STAT_INSECURE; + } + else if (forward->flags & FREC_DNSKEY_QUERY) status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); else if (forward->flags & FREC_DS_QUERY) status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); From 60b68069cf8bde6f2201ece5735e1c8cf68378f3 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 12:10:28 +0000 Subject: [PATCH 089/105] Rationalise DNS packet-buffer size calculations. --- src/dnsmasq.c | 7 ++++++- src/dnsmasq.h | 2 +- src/dnssec.c | 4 ++-- src/forward.c | 18 ++++++++++-------- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 3e5f51e..27928fe 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -90,7 +90,12 @@ int main (int argc, char **argv) #endif if (daemon->edns_pktsz < PACKETSZ) - daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? EDNS_PKTSZ : PACKETSZ; + daemon->edns_pktsz = PACKETSZ; +#ifdef HAVE_DNSSEC + /* Enforce min packet big enough for DNSSEC */ + if (option_bool(OPT_DNSSEC_VALID) && daemon->edns_pktsz < EDNS_PKTSZ) + daemon->edns_pktsz = EDNS_PKTSZ; +#endif 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 044c865..7f6dd7e 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1048,7 +1048,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ -size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr); +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, diff --git a/src/dnssec.c b/src/dnssec.c index 2e82b16..f0f709d 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -479,7 +479,7 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk return 1; } -size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr) +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr) { unsigned char *p; char types[20]; @@ -511,7 +511,7 @@ size_t dnssec_generate_query(struct dns_header *header, char *name, int class, i PUTSHORT(type, 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, end); } /* The DNS packet is expected to contain the answer to a DNSKEY query. diff --git a/src/forward.c b/src/forward.c index f254fe1..9ae64d6 100644 --- a/src/forward.c +++ b/src/forward.c @@ -330,11 +330,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, int forwarded = 0; if (option_bool(OPT_ADD_MAC)) - plen = add_mac(header, plen, ((char *) header) + PACKETSZ, &forward->source); + plen = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); if (option_bool(OPT_CLIENT_SUBNET)) { - size_t new = add_source_addr(header, plen, ((char *) header) + PACKETSZ, &forward->source); + size_t new = add_source_addr(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); if (new != plen) { plen = new; @@ -345,7 +345,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) { - plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ); + plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz); header->hb4 |= HB4_CD; } #endif @@ -585,7 +585,7 @@ void reply_query(int fd, int family, time_t now) union mysockaddr serveraddr; struct frec *forward; socklen_t addrlen = sizeof(serveraddr); - ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); + ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen); size_t nn; struct server *server; @@ -732,12 +732,14 @@ void reply_query(int fd, int family, time_t now) if (status == STAT_NEED_KEY) { new->flags |= FREC_DNSKEY_QUERY; - nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DNSKEY, &server->addr); + nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz, + daemon->keyname, forward->class, T_DNSKEY, &server->addr); } else if (status == STAT_NEED_DS) { new->flags |= FREC_DS_QUERY; - nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DS, &server->addr); + nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz, + daemon->keyname, forward->class, T_DS, &server->addr); } new->crc = questions_crc(header, nn, daemon->namebuff); new->new_id = get_id(new->crc); @@ -1064,7 +1066,7 @@ void receive_query(struct listener *listen, time_t now) #ifdef HAVE_AUTH if (auth_dns) { - m = answer_auth(header, ((char *) header) + PACKETSZ, (size_t)n, now, &source_addr, local_auth); + m = answer_auth(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, now, &source_addr, local_auth); if (m >= 1) { send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), @@ -1075,7 +1077,7 @@ void receive_query(struct listener *listen, time_t now) else #endif { - m = answer_request(header, ((char *) header) + PACKETSZ, (size_t)n, + m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, dst_addr_4, netmask, now); if (m >= 1) From 3ddacb86e9cc4fca9c315138edeb4d04103647bf Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 14:32:03 +0000 Subject: [PATCH 090/105] Ensure cache is big enough to do DNSSEC. --- src/dnsmasq.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 27928fe..c77f355 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -82,13 +82,6 @@ int main (int argc, char **argv) read_opts(argc, argv, compile_opts); -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID)) - if (daemon->doctors) exit(1); /* TODO */ - - daemon->keyname = safe_malloc(MAXDNAME); -#endif - if (daemon->edns_pktsz < PACKETSZ) daemon->edns_pktsz = PACKETSZ; #ifdef HAVE_DNSSEC @@ -96,12 +89,17 @@ int main (int argc, char **argv) if (option_bool(OPT_DNSSEC_VALID) && daemon->edns_pktsz < EDNS_PKTSZ) daemon->edns_pktsz = EDNS_PKTSZ; #endif + daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? daemon->edns_pktsz : DNSMASQ_PACKETSZ; daemon->packet = safe_malloc(daemon->packet_buff_sz); - + daemon->addrbuff = safe_malloc(ADDRSTRLEN); - + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + daemon->keyname = safe_malloc(MAXDNAME); +#endif #ifdef HAVE_DHCP if (!daemon->lease_file) @@ -143,6 +141,11 @@ int main (int argc, char **argv) } #endif +#ifdef HAVE_DNSSEC + if (daemon->cachesize Date: Wed, 8 Jan 2014 15:53:35 +0000 Subject: [PATCH 091/105] DNSSEC for TCP queries. --- src/forward.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/src/forward.c b/src/forward.c index 9ae64d6..1106f40 100644 --- a/src/forward.c +++ b/src/forward.c @@ -1094,6 +1094,66 @@ void receive_query(struct listener *listen, time_t now) } } +#ifdef HAVE_DNSSEC +static int tcp_key_recurse(time_t now, int status, int class, char *keyname, struct server *server) +{ + /* Recurse up the key heirarchy */ + size_t n; + unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); + unsigned char *payload = &packet[2]; + struct dns_header *header = (struct dns_header *)payload; + u16 *length = (u16 *)packet; + int new_status; + unsigned char c1, c2; + + n = dnssec_generate_query(header, ((char *) header) + 65536, keyname, class, + status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr); + + *length = htons(n); + + if (!read_write(server->tcpfd, packet, n + sizeof(u16), 0) || + !read_write(server->tcpfd, &c1, 1, 1) || + !read_write(server->tcpfd, &c2, 1, 1) || + !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) + { + close(server->tcpfd); + server->tcpfd = -1; + new_status = STAT_INSECURE; + } + else + { + n = (c1 << 8) | c2; + + if (status == STAT_NEED_KEY) + new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class); + else + new_status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, class); + + if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) + { + if ((new_status = tcp_key_recurse(now, new_status, class, daemon->keyname, server) == STAT_SECURE)) + { + if (status == STAT_NEED_KEY) + new_status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, class); + else + new_status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, class); + + if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) + { + my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); + status = STAT_INSECURE; + } + } + } + } + + free(packet); + + return new_status; +} +#endif + + /* The daemon forks before calling this: it should deal with one connection, blocking as neccessary, and then return. Note, need to be a bit careful about resources for debug mode, when the fork is suppressed: that's @@ -1106,7 +1166,7 @@ unsigned char *tcp_request(int confd, time_t now, #ifdef HAVE_AUTH int local_auth = 0; #endif - int checking_disabled, check_subnet; + int checking_disabled, check_subnet, no_cache_dnssec = 0, cache_secure = 0; size_t m; unsigned short qtype; unsigned int gotname; @@ -1139,7 +1199,8 @@ unsigned char *tcp_request(int confd, time_t now, check_subnet = 0; /* save state of "cd" flag in query */ - checking_disabled = header->hb4 & HB4_CD; + if ((checking_disabled = header->hb4 & HB4_CD)) + no_cache_dnssec = 1; /* RFC 4035: sect 4.6 para 2 */ header->hb4 &= ~HB4_AD; @@ -1259,6 +1320,14 @@ unsigned char *tcp_request(int confd, time_t now, continue; } +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + size = add_do_bit(header, size, ((char *) header) + 65536); + header->hb4 |= HB4_CD; + } +#endif + #ifdef HAVE_CONNTRACK /* Copy connection mark of incoming query to outgoing connection. */ if (option_bool(OPT_CONNTRACK)) @@ -1282,7 +1351,8 @@ unsigned char *tcp_request(int confd, time_t now, if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) || !read_write(last_server->tcpfd, &c1, 1, 1) || - !read_write(last_server->tcpfd, &c2, 1, 1)) + !read_write(last_server->tcpfd, &c2, 1, 1) || + !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1)) { close(last_server->tcpfd); last_server->tcpfd = -1; @@ -1290,8 +1360,6 @@ unsigned char *tcp_request(int confd, time_t now, } m = (c1 << 8) | c2; - if (!read_write(last_server->tcpfd, payload, m, 1)) - return packet; if (!gotname) strcpy(daemon->namebuff, "query"); @@ -1303,6 +1371,36 @@ unsigned char *tcp_request(int confd, time_t now, log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); #endif + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled) + { + int class, status; + + status = dnssec_validate_reply(now, header, m, daemon->namebuff, daemon->keyname, &class); + + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + { + if ((status = tcp_key_recurse(now, status, class, daemon->keyname, last_server)) == STAT_SECURE) + status = dnssec_validate_reply(now, header, m, daemon->namebuff, daemon->keyname, &class); + } + + log_query(F_KEYTAG | F_SECSTAT, "result", NULL, + status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS) + no_cache_dnssec = 1; + + if (status == STAT_SECURE) + cache_secure = 1; + } +#endif + + /* restore CD bit to the value in the query */ + if (checking_disabled) + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; /* There's no point in updating the cache, since this process will exit and lose the information after a few queries. We make this call for the alias and @@ -1312,8 +1410,8 @@ unsigned char *tcp_request(int confd, time_t now, sending replies containing questions and bogus answers. */ if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) m = process_reply(header, now, last_server, (unsigned int)m, - option_bool(OPT_NO_REBIND) && !norebind, checking_disabled, - 0, check_subnet, &peer_addr); /* TODO - cache secure */ + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, + cache_secure, check_subnet, &peer_addr); break; } From f1668d2786556628572e6c9d4d77c1ac2bf44240 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 16:53:27 +0000 Subject: [PATCH 092/105] New source port for DNSSEC-originated queries. --- src/forward.c | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/forward.c b/src/forward.c index 1106f40..39b0c82 100644 --- a/src/forward.c +++ b/src/forward.c @@ -718,6 +718,10 @@ void reply_query(int fd, int family, time_t now) new->next = next; new->stash = NULL; new->blocking_query = NULL; + new->rfd4 = NULL; +#ifdef HAVE_IPV6 + new->rfd6 = NULL; +#endif new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); if ((forward->stash = blockdata_alloc((char *)header, n))) @@ -751,23 +755,27 @@ void reply_query(int fd, int family, time_t now) if (server->sfd) fd = server->sfd->fd; else + { + fd = -1; #ifdef HAVE_IPV6 - /* Note that we use the same random port for the DNSSEC stuff */ - if (server->addr.sa.sa_family == AF_INET6) - { - fd = new->rfd6->fd; - new->rfd6->refcount++; - } - else + if (server->addr.sa.sa_family == AF_INET6) + { + if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) + fd = new->rfd6->fd; + } + else #endif - { - fd = new->rfd4->fd; - new->rfd4->refcount++; - } + { + if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) + fd = new->rfd4->fd; + } + } - /* 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()); - server->queries++; + if (fd != -1) + { + while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send()); + server->queries++; + } } } @@ -1142,7 +1150,7 @@ static int tcp_key_recurse(time_t now, int status, int class, char *keyname, str { my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); status = STAT_INSECURE; - } + } } } } From c47e3ba4466fe5ae48da3095eab6ae47d36701d2 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 17:07:54 +0000 Subject: [PATCH 093/105] Update copyright for 2014. --- Makefile | 2 +- src/auth.c | 2 +- src/bpf.c | 2 +- src/cache.c | 2 +- src/config.h | 2 +- src/conntrack.c | 2 +- src/dbus.c | 2 +- src/dhcp-common.c | 2 +- src/dhcp-protocol.h | 2 +- src/dhcp.c | 2 +- src/dhcp6-protocol.h | 2 +- src/dhcp6.c | 2 +- src/dns-protocol.h | 2 +- src/dnsmasq.c | 2 +- src/dnsmasq.h | 4 ++-- src/domain.c | 2 +- src/forward.c | 2 +- src/helper.c | 2 +- src/lease.c | 2 +- src/log.c | 2 +- src/netlink.c | 2 +- src/network.c | 2 +- src/option.c | 2 +- src/outpacket.c | 2 +- src/radv-protocol.h | 2 +- src/radv.c | 2 +- src/rfc1035.c | 2 +- src/rfc2131.c | 2 +- src/rfc3315.c | 2 +- src/slaac.c | 2 +- src/tftp.c | 2 +- src/util.c | 2 +- 32 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 25ae31f..39aeaf4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# dnsmasq is Copyright (c) 2000-2013 Simon Kelley +# dnsmasq is Copyright (c) 2000-2014 Simon Kelley # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/auth.c b/src/auth.c index d31ed60..d6fdef6 100644 --- a/src/auth.c +++ b/src/auth.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bpf.c b/src/bpf.c index a3c5ad4..f9c6063 100644 --- a/src/bpf.c +++ b/src/bpf.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/cache.c b/src/cache.c index ee27e4e..3504183 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/config.h b/src/config.h index d4978b6..80b154c 100644 --- a/src/config.h +++ b/src/config.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/conntrack.c b/src/conntrack.c index f8105b2..6a5133a 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dbus.c b/src/dbus.c index da28a28..5e9fdc9 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 3f9979e..9d13ac8 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h index a2750de..4c09614 100644 --- a/src/dhcp-protocol.h +++ b/src/dhcp-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dhcp.c b/src/dhcp.c index 67a82b5..92ea92b 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h index f8e98ec..5927dc3 100644 --- a/src/dhcp6-protocol.h +++ b/src/dhcp6-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dhcp6.c b/src/dhcp6.c index 9d4f450..d9cd690 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dns-protocol.h b/src/dns-protocol.h index efecb1c..8518dd0 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dnsmasq.c b/src/dnsmasq.c index c77f355..a264f77 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 7f6dd7e..7b106eb 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,7 +14,7 @@ along with this program. If not, see . */ -#define COPYRIGHT "Copyright (c) 2000-2013 Simon Kelley" +#define COPYRIGHT "Copyright (c) 2000-2014 Simon Kelley" #ifndef NO_LARGEFILE /* Ensure we can use files >2GB (log files may grow this big) */ diff --git a/src/domain.c b/src/domain.c index 3df4ab8..fdd5e4f 100644 --- a/src/domain.c +++ b/src/domain.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/forward.c b/src/forward.c index 39b0c82..5c2d1a0 100644 --- a/src/forward.c +++ b/src/forward.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/helper.c b/src/helper.c index 24c2afd..4be53c3 100644 --- a/src/helper.c +++ b/src/helper.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/lease.c b/src/lease.c index 7e00804..d8a53d3 100644 --- a/src/lease.c +++ b/src/lease.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/log.c b/src/log.c index f532f93..8083a86 100644 --- a/src/log.c +++ b/src/log.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/netlink.c b/src/netlink.c index 3be94ee..c9d5e22 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/network.c b/src/network.c index fd49b5c..f068454 100644 --- a/src/network.c +++ b/src/network.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/option.c b/src/option.c index 2933e9f..e4885ee 100644 --- a/src/option.c +++ b/src/option.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/outpacket.c b/src/outpacket.c index 9d64c01..dce68f7 100644 --- a/src/outpacket.c +++ b/src/outpacket.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/radv-protocol.h b/src/radv-protocol.h index 8d5b153..9e53d8e 100644 --- a/src/radv-protocol.h +++ b/src/radv-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/radv.c b/src/radv.c index f01b136..9838bb6 100644 --- a/src/radv.c +++ b/src/radv.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/rfc1035.c b/src/rfc1035.c index a05060a..b4f0de7 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/rfc2131.c b/src/rfc2131.c index 6f685c1..12dd284 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/rfc3315.c b/src/rfc3315.c index 8a2660f..a18c549 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/slaac.c b/src/slaac.c index 7eb4236..351d680 100644 --- a/src/slaac.c +++ b/src/slaac.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tftp.c b/src/tftp.c index 07b9e2e..a527911 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/util.c b/src/util.c index c210add..d5839fa 100644 --- a/src/util.c +++ b/src/util.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 98c098bfc74ec502497909d3c9f699a445a4b6ae Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 17:31:16 +0000 Subject: [PATCH 094/105] Move blockdata to it's own file. --- Makefile | 2 +- bld/Android.mk | 2 +- src/blockdata.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cache.c | 84 +---------------------------------------- src/dnsmasq.h | 2 + 5 files changed, 104 insertions(+), 85 deletions(-) create mode 100644 src/blockdata.c diff --git a/Makefile b/Makefile index 39aeaf4..92158e0 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ 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 + domain.o dnssec.o dnssec-openssl.o blockdata.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h diff --git a/bld/Android.mk b/bld/Android.mk index 5eb4749..309d178 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.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 \ - dnssec.c dnssec-openssl.c + dnssec.c dnssec-openssl.c blockdata.c LOCAL_MODULE := dnsmasq diff --git a/src/blockdata.c b/src/blockdata.c new file mode 100644 index 0000000..be697aa --- /dev/null +++ b/src/blockdata.c @@ -0,0 +1,99 @@ +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 + +static struct blockdata *keyblock_free = NULL; + +struct blockdata *blockdata_alloc(char *data, size_t len) +{ + struct blockdata *block, *ret = NULL; + struct blockdata **prev = &ret; + size_t blen; + + while (len > 0) + { + if (keyblock_free) + { + block = keyblock_free; + keyblock_free = block->next; + } + else + block = whine_malloc(sizeof(struct blockdata)); + + if (!block) + { + /* failed to alloc, free partial chain */ + blockdata_free(ret); + return NULL; + } + + blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; + memcpy(block->key, data, blen); + data += blen; + len -= blen; + *prev = block; + prev = &block->next; + block->next = NULL; + } + + 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; + + if (blocks) + { + for (tmp = blocks; tmp->next; tmp = tmp->next); + tmp->next = keyblock_free; + 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 diff --git a/src/cache.c b/src/cache.c index 3504183..42039ec 100644 --- a/src/cache.c +++ b/src/cache.c @@ -25,9 +25,6 @@ static int cache_inserted = 0, cache_live_freed = 0, insert_error; static union bigname *big_free = NULL; static int bignames_left, hash_size; static int uid = 1; -#ifdef HAVE_DNSSEC -static struct blockdata *keyblock_free = NULL; -#endif /* type->string mapping: this is also used by the name-hash function as a mixing table. */ static const struct { @@ -1394,83 +1391,4 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest); } -#ifdef HAVE_DNSSEC -struct blockdata *blockdata_alloc(char *data, size_t len) -{ - struct blockdata *block, *ret = NULL; - struct blockdata **prev = &ret; - size_t blen; - - while (len > 0) - { - if (keyblock_free) - { - block = keyblock_free; - keyblock_free = block->next; - } - else - block = whine_malloc(sizeof(struct blockdata)); - - if (!block) - { - /* failed to alloc, free partial chain */ - blockdata_free(ret); - return NULL; - } - - blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; - memcpy(block->key, data, blen); - data += blen; - len -= blen; - *prev = block; - prev = &block->next; - block->next = NULL; - } - - 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, (*key)->key + KEYBLOCK_LEN - (*p)); -} - -void blockdata_free(struct blockdata *blocks) -{ - struct blockdata *tmp; - - if (blocks) - { - for (tmp = blocks; tmp->next; tmp = tmp->next); - tmp->next = keyblock_free; - 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 - - + diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 7b106eb..c31ac64 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -987,6 +987,8 @@ void dump_cache(time_t now); char *cache_get_name(struct crec *crecp); char *cache_get_cname_target(struct crec *crecp); struct crec *cache_enumerate(int init); + +/* blockdata.c */ #ifdef HAVE_DNSSEC struct blockdata *blockdata_alloc(char *data, size_t len); size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt); From c2207688c0e6ca00966201921c1b374ab8937aaf Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 18:04:20 +0000 Subject: [PATCH 095/105] Memory stats for DNSSEC. --- src/blockdata.c | 24 +++++++++++++++++++----- src/cache.c | 3 +++ src/dnsmasq.h | 1 + 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/blockdata.c b/src/blockdata.c index be697aa..ba2e406 100644 --- a/src/blockdata.c +++ b/src/blockdata.c @@ -19,6 +19,13 @@ #ifdef HAVE_DNSSEC static struct blockdata *keyblock_free = NULL; +static unsigned int blockdata_count = 0, blockdata_hwm = 0; + +void blockdata_report(void) +{ + my_syslog(LOG_INFO, _("DNSSEC memory in use %u, max %u"), + blockdata_count * KEYBLOCK_LEN, blockdata_hwm * KEYBLOCK_LEN); +} struct blockdata *blockdata_alloc(char *data, size_t len) { @@ -32,10 +39,15 @@ struct blockdata *blockdata_alloc(char *data, size_t len) { block = keyblock_free; keyblock_free = block->next; + blockdata_count++; } - else - block = whine_malloc(sizeof(struct blockdata)); - + else if ((block = whine_malloc(sizeof(struct blockdata)))) + { + blockdata_count++; + if (blockdata_hwm < blockdata_count) + blockdata_hwm = blockdata_count; + } + if (!block) { /* failed to alloc, free partial chain */ @@ -76,9 +88,11 @@ void blockdata_free(struct blockdata *blocks) if (blocks) { - for (tmp = blocks; tmp->next; tmp = tmp->next); + for (tmp = blocks; tmp->next; tmp = tmp->next) + blockdata_count--; tmp->next = keyblock_free; - keyblock_free = blocks; + keyblock_free = blocks; + blockdata_count--; } } diff --git a/src/cache.c b/src/cache.c index 42039ec..1ff783c 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1184,6 +1184,9 @@ void dump_cache(time_t now) #ifdef HAVE_AUTH my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->auth_answer); #endif +#ifdef HAVE_DNSSEC + blockdata_report(); +#endif /* sum counts from different records for same server */ for (serv = daemon->servers; serv; serv = serv->next) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index c31ac64..6a087e4 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -990,6 +990,7 @@ struct crec *cache_enumerate(int init); /* blockdata.c */ #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); void blockdata_retrieve(struct blockdata *block, size_t len, void *data); From 795501bc86a316d9b064caef7117b7784c27530a Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 18:11:55 +0000 Subject: [PATCH 096/105] AD bit handling when doing validation. --- src/forward.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/forward.c b/src/forward.c index 5c2d1a0..8167229 100644 --- a/src/forward.c +++ b/src/forward.c @@ -461,7 +461,6 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server char **sets = 0; int munged = 0, is_sign; size_t plen; - int squash_ad = 0; #ifdef HAVE_IPSET /* Similar algorithm to search_servers. */ @@ -506,19 +505,16 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* RFC 4035 sect 4.6 para 3 */ if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) - squash_ad = 1; + header->hb4 &= ~HB4_AD; #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) - squash_ad = no_cache; - + header->hb4 &= ~HB4_AD; + if (cache_secure) header->hb4 |= HB4_AD; #endif - if (squash_ad) - header->hb4 &= ~HB4_AD; - if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) return n; From 0435d041ead233634c447c50bff4413aae4363b4 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 18:22:37 +0000 Subject: [PATCH 097/105] AD into cache fixes. --- src/rfc1035.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/rfc1035.c b/src/rfc1035.c index b4f0de7..7d48910 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -921,14 +921,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t int found = 0, cname_count = 5; struct crec *cpp = NULL; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; + int secflag = secure ? F_DNSSECOK : 0; unsigned long cttl = ULONG_MAX, attl; - if (RCODE(header) == NXDOMAIN) - flags |= F_NXDOMAIN; - - if (secure) - flags |= F_DNSSECOK; - namep = p; if (!extract_name(header, qlen, &p, name, 1, 4)) return 0; /* bad packet */ @@ -989,7 +984,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t goto cname_loop; } - cache_insert(name, &addr, now, cttl, name_encoding | F_REVERSE); + cache_insert(name, &addr, now, cttl, name_encoding | secflag | F_REVERSE); found = 1; } @@ -1007,7 +1002,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t ttl = find_soa(header, qlen, NULL); } if (ttl) - cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags); + cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag); } } else @@ -1057,7 +1052,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t { if (!cname_count--) return 0; /* looped CNAMES */ - newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD); + newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD | secflag); if (newc) { newc->addr.cname.target.cache = NULL; @@ -1100,7 +1095,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t } #endif - newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD); + newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD | secflag); if (newc && cpp) { cpp->addr.cname.target.cache = newc; @@ -1126,7 +1121,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t pointing at this, inherit its TTL */ if (ttl || cpp) { - newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags); + newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag); if (newc && cpp) { cpp->addr.cname.target.cache = newc; From b6e9e7c32d0e225021a03fbe0c31391d58cac2af Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 8 Jan 2014 21:21:20 +0000 Subject: [PATCH 098/105] Handle digest lengths greater than 1 block. --- src/blockdata.c | 10 ++++++++-- src/dnsmasq.h | 2 +- src/dnssec.c | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/blockdata.c b/src/blockdata.c index ba2e406..aef249e 100644 --- a/src/blockdata.c +++ b/src/blockdata.c @@ -96,18 +96,24 @@ void blockdata_free(struct blockdata *blocks) } } -void blockdata_retrieve(struct blockdata *block, size_t len, void *data) +/* copy blocks into data[], return 1 if data[] unchanged by so doing */ +int blockdata_retrieve(struct blockdata *block, size_t len, void *data) { size_t blen; struct blockdata *b; + int match = 1; for (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; len -= blen; } + + return match; } - + #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 6a087e4..230f35b 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -993,7 +993,7 @@ struct crec *cache_enumerate(int init); 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); -void blockdata_retrieve(struct blockdata *block, size_t len, void *data); +int blockdata_retrieve(struct blockdata *block, size_t len, void *data); void blockdata_free(struct blockdata *blocks); #endif diff --git a/src/dnssec.c b/src/dnssec.c index f0f709d..f9d3aab 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -617,8 +617,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch from_wire(name); - /* TODO fragented digest */ - if (memcmp(digestalg_final(), recp1->addr.key.keydata->key, digestalg_len()) == 0 && + 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; @@ -717,6 +717,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char crecp->addr.key.keydata = key; crecp->addr.key.algo = algo; crecp->addr.key.keytag = keytag; + crecp->uid = rdlen - 4; } else return STAT_INSECURE; /* cache problem */ From b8071a849ada5dea89587ffc82c08be369aada51 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 9 Jan 2014 09:41:33 +0000 Subject: [PATCH 099/105] Tweak blockdata accounting. --- src/blockdata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockdata.c b/src/blockdata.c index aef249e..f9155d9 100644 --- a/src/blockdata.c +++ b/src/blockdata.c @@ -24,7 +24,7 @@ static unsigned int blockdata_count = 0, blockdata_hwm = 0; void blockdata_report(void) { my_syslog(LOG_INFO, _("DNSSEC memory in use %u, max %u"), - blockdata_count * KEYBLOCK_LEN, blockdata_hwm * KEYBLOCK_LEN); + blockdata_count * sizeof(struct blockdata), blockdata_hwm * sizeof(struct blockdata)); } struct blockdata *blockdata_alloc(char *data, size_t len) From 5f8e58f49bf6798f8bf341573dd95c6a12e3e0cf Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 9 Jan 2014 17:31:19 +0000 Subject: [PATCH 100/105] DNSSEC consolidation. --- src/dns-protocol.h | 15 +- src/dnsmasq.h | 2 - src/dnssec.c | 1073 +++++++++++++++++++------------------------- src/rfc1035.c | 2 +- 4 files changed, 471 insertions(+), 621 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 8518dd0..90257ad 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -39,16 +39,29 @@ #define C_ANY 255 /* wildcard match */ #define T_A 1 -#define T_NS 2 +#define T_NS 2 +#define T_MD 3 +#define T_MF 4 #define T_CNAME 5 #define T_SOA 6 +#define T_MB 7 +#define T_MG 8 +#define T_MR 9 #define T_PTR 12 +#define T_MINFO 14 #define T_MX 15 #define T_TXT 16 +#define T_RP 17 +#define T_AFSDB 18 +#define T_RT 21 #define T_SIG 24 +#define T_PX 26 #define T_AAAA 28 +#define T_NXT 30 #define T_SRV 33 #define T_NAPTR 35 +#define T_KX 36 +#define T_DNAME 39 #define T_OPT 41 #define T_DS 43 #define T_RRSIG 46 diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 230f35b..ca4fa61 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1054,8 +1054,6 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); -int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, - int type, char *name, char *keyname, 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); diff --git a/src/dnssec.c b/src/dnssec.c index f9d3aab..daf1dd3 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -20,378 +20,12 @@ #ifdef HAVE_DNSSEC #include "dnssec-crypto.h" -#include - -/* Maximum length in octects of a domain name, in wire format */ -#define MAXCDNAME 256 - -#define MAXRRSET 16 #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; -} - - -/* 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; - - /* Convert from presentation format to wire format, in place. Also map UC -> LC. Note that using extract_name to get presentation format @@ -442,80 +76,423 @@ static void from_wire(char *name) *(l-1) = 0; } - -/* 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) +/* Implement RFC1982 wrapped compare for 32-bit numbers */ +static int serial_compare_32(unsigned long s1, unsigned long s2) { - size_t len; - unsigned char *p; - unsigned short total; - unsigned char tmpbuf[MAXDNAME]; /* TODO: reuse part of daemon->namebuff */ - RDataCForm cf1, cf2; + if (s1 == s2) + return SERIAL_EQ; - /* Initialize two iterations over the canonical form*/ - rdata_cform_init(&cf1, header, pktlen, rdata, sigtype, tmpbuf); - cf2 = cf1; + 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; +} - /* 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)) +/* Check whether today/now is between date_start and date_end */ +static int check_date_range(unsigned long date_start, unsigned long date_end) +{ + 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; +} + +static u16 *get_desc(int type) +{ + /* List of RRtypes which include domains in the data. + 0 -> domain + integer -> no of plain bytes + -1 -> end + + zero is not a valid RRtype, so the final entry is returned for + anything which needs no mangling. + */ + + static u16 rr_desc[] = + { + T_NS, 0, -1, + T_MD, 0, -1, + T_MF, 0, -1, + T_CNAME, 0, -1, + T_SOA, 0, 0, -1, + T_MB, 0, -1, + T_MG, 0, -1, + T_MR, 0, -1, + T_PTR, 0, -1, + T_MINFO, 0, 0, -1, + T_MX, 2, 0, -1, + T_RP, 0, 0, -1, + T_AFSDB, 2, 0, -1, + T_RT, 2, 0, -1, + T_SIG, 18, 0, -1, + T_PX, 2, 0, 0, -1, + T_NXT, 0, -1, + T_KX, 2, 0, -1, + T_SRV, 6, 0, -1, + T_DNAME, 0, -1, + T_RRSIG, 18, 0, -1, + T_NSEC, 0, -1, + 0, -1 /* wildcard/catchall */ + }; + + u16 *p = rr_desc; + + while (*p != type && *p != 0) + while (*p++ != (u16)-1); + + return p+1; +} + +/* Return bytes of canonicalised rdata, when the return value is zero, the remaining + data, pointed to by *p, should be used raw. */ +static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, + unsigned char **p, u16 **desc) +{ + int d = **desc; + + (*desc)++; + + /* No more data needs mangling */ + if (d == (u16)-1) 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; -} - -size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr) -{ - unsigned char *p; - char types[20]; - querystr("dnssec", types, type); - - if (addr->sa.sa_family == AF_INET) - log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); -#ifdef HAVE_IPV6 + if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) + /* domain-name, canonicalise */ + return to_wire(buff); else - log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); -#endif - - header->qdcount = htons(1); - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - - header->hb3 = HB3_RD; - SET_OPCODE(header, QUERY); - header->hb4 = HB4_CD; - - /* ID filled in later */ - - p = (unsigned char *)(header+1); - - p = do_rfc1035_name(p, name); - *p++ = 0; - PUTSHORT(type, p); - PUTSHORT(class, p); - - return add_do_bit(header, p - (unsigned char *)header, end); + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((end - *p) < d) + d = end - *p; + + if (d != 0) + { + memcpy(buff, *p, d); + *p += d; + } + + return d; + } } + +/* Bubble sort the RRset into the canonical order. + Note that the byte-streams from two RRs may get unsynced: consider + RRs which have two domain-names at the start and then other data. + The domain-names may have different lengths in each RR, but sort equal + + ------------ + |abcde|fghi| + ------------ + |abcd|efghi| + ------------ + + leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. +*/ + +static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) +{ + int swap, quit, i; + do + { + for (swap = 0, i = 0; i < rrsetidx-1; i++) + { + int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; + u16 *dp1, *dp2; + unsigned char *end1, *end2; + unsigned char *p1 = skip_name(rrset[i], header, plen, 10); + unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); + + p1 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, p1); + end1 = p1 + rdlen1; + + p2 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, p2); + end2 = p2 + rdlen2; + + dp1 = dp2 = rr_desc; + + for (quit = 0, left1 = 0, left2 = 0; !quit;) + { + if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0) + { + quit = 1; + len1 = end1 - p1; + memcpy(buff1 + left1, p1, len1); + } + len1 += left1; + + if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0) + { + quit = 1; + len2 = end2 - p2; + memcpy(buff2 + left2, p2, len2); + } + len2 += left2; + + if (len1 > len2) + { + left1 = len1 - len2; + left2 = 0; + len = len2; + } + else + { + left2 = len2 - len1; + left1 = 0; + len = len1; + } + + rc = memcmp(buff1, buff2, len); + + if (rc == 1 || (rc == 0 && quit && len2 > len1)) + { + unsigned char *tmp = rrset[i+1]; + rrset[i+1] = rrset[i]; + rrset[i] = tmp; + swap = quit = 1; + } + } + } + } while (swap); +} + +/* Validate a single RRset (class, type, name) in the supplied DNS reply + Return code: + STAT_SECURE if it validates. + STAT_INSECURE can't validate (no RRSIG, bad packet). + STAT_BOGUS signature is wrong. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + + if key is non-NULL, use that key, which has the algo and tag given in the params of those names, + otherwise find the key in the cache. +*/ +static 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) +{ + static unsigned char **rrset = NULL, **sigs = NULL; + static int rrset_sz = 0, sig_sz = 0; + + unsigned char *p; + int rrsetidx, sigidx, res, rdlen, j; + struct crec *crecp = NULL; + int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; + u16 *rr_desc = get_desc(type); + + if (!(p = skip_questions(header, plen))) + return STAT_INSECURE; + + /* look for an RRSIG record for this RRset and get pointers to each record */ + for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); + j != 0; j--) + { + unsigned char *pstart, *pdata; + int stype, sclass, sttl; + + pstart = p; + + if (!(res = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(stype, p); + GETSHORT(sclass, p); + GETLONG(sttl, p); + + pdata = p; + + GETSHORT(rdlen, p); + + (void)sttl; + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_INSECURE; /* bad packet */ + + if (res == 1 && sclass == class) + { + if (stype == type) + { + if (rrsetidx == rrset_sz) + { + unsigned char **new; + + /* expand */ + if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **)))) + return STAT_INSECURE; + + if (rrset) + { + memcpy(new, rrset, rrset_sz * sizeof(unsigned char **)); + free(rrset); + } + + rrset = new; + rrset_sz += 5; + } + rrset[rrsetidx++] = pstart; + } + + if (stype == T_RRSIG) + { + if (sigidx == sig_sz) + { + unsigned char **new; + + /* expand */ + if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **)))) + return STAT_INSECURE; + + if (sigs) + { + memcpy(new, sigs, sig_sz * sizeof(unsigned char **)); + free(sigs); + } + + sigs = new; + sig_sz += 5; + } + sigs[sigidx++] = pdata; + } + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_INSECURE; + } + + /* RRset empty, no RRSIGs */ + if (rrsetidx == 0 || sigidx == 0) + return STAT_INSECURE; + + /* Sort RRset records into canonical order. + Note that at this point keyname and name buffs are + unused, and used as workspace by the sort. */ + sort_rrset(header, plen, rr_desc, rrsetidx, rrset, name, keyname); + + /* Now try all the sigs to try and find one which validates */ + for (j = 0; j sig = p; + alg->siglen = rdlen - (p - psav); + + nsigttl = htonl(orig_ttl); + + digestalg_begin(alg->vtbl->digest_algo); + digestalg_add_data(psav, 18); + wire_len = to_wire(keyname); + digestalg_add_data(keyname, wire_len); + from_wire(keyname); + + /* TODO wildcard rules : 4035 5.3.2 */ + for (i = 0; i < rrsetidx; ++i) + { + int seg; + unsigned char *end, *cp; + u16 len, *dp; + + p = rrset[i]; + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_INSECURE; + wire_len = to_wire(name); + digestalg_add_data(name, wire_len); + from_wire(name); /* leave name unchanged on exit */ + digestalg_add_data(p, 4); /* class and type */ + digestalg_add_data(&nsigttl, 4); + + p += 8; /* skip class, type, ttl */ + GETSHORT(rdlen, p); + end = p + rdlen; + + /* canonicalise rdata and calculate length of same, use name buffer as workspace */ + cp = p; + dp = rr_desc; + 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); + + /* 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); + if (cp != end) + digestalg_add_data(cp, end - cp); + + /* namebuff used for workspace, above, restore for next loop + and to leave unchanged on exit */ + p = (unsigned char*)(rrset[i]); + 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)) + return STAT_SECURE; + } + else + { + /* iterate through all possible keys 4035 5.3.1 */ + for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && + alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid)) + return STAT_SECURE; + } + } + + return STAT_BOGUS; +} + /* The DNS packet is expected to contain the answer to a DNSKEY query. - Leave name of qury in name. + Leave name of query in name. Put all DNSKEYs in the answer which are valid into the cache. return codes: STAT_INSECURE bad packet, no DNSKEYs in reply. @@ -531,12 +508,13 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; struct blockdata *key; - if (ntohs(header->qdcount) != 1) - return STAT_INSECURE; - - if (!extract_name(header, plen, &p, name, 1, 4)) - return STAT_INSECURE; - + if (ntohs(header->qdcount) != 1 || + !extract_name(header, plen, &p, name, 1, 4)) + { + strcpy(name, ""); + return STAT_INSECURE; + } + GETSHORT(qtype, p); GETSHORT(qclass, p); @@ -641,7 +619,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch return STAT_BOGUS; } - /* The DNS packet is expected to contain the answer to a DS query Leave name of DS query in name. Put all DSs in the answer which are valid into the cache. @@ -659,12 +636,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char int qtype, qclass, val, j, gotone; struct blockdata *key; - if (ntohs(header->qdcount) != 1) - return STAT_INSECURE; - - if (!extract_name(header, plen, &p, name, 1, 4)) - return STAT_INSECURE; - + if (ntohs(header->qdcount) != 1 || + !extract_name(header, plen, &p, name, 1, 4)) + { + strcpy(name, ""); + return STAT_INSECURE; + } + GETSHORT(qtype, p); GETSHORT(qclass, p); @@ -735,179 +713,6 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char return STAT_SECURE; } - - -/* Validate a single RRset (class, type, name) in the supplied DNS reply - Return code: - STAT_SECURE if it validates. - STAT_INSECURE can't validate (no RRSIG, bad packet). - STAT_BOGUS signature is wrong. - STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) - - if key is non-NULL, use that key, which has the algo and tag given in the params of those names, - otherwise find the key in the cache. -*/ -int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, - int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) -{ - unsigned char *p; - int rrsetidx, sigidx, res, rdlen, j; - struct crec *crecp = NULL; - void *rrset[MAXRRSET], *sigs[MAXRRSET]; /* TODO: max RRset size? */ - int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; - - if (!(p = skip_questions(header, plen))) - return STAT_INSECURE; - - /* look for an RRSIG record for this RRset and get pointers to each record */ - for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); - j != 0; j--) - { - unsigned char *pstart; - int stype, sclass, sttl; - - if (!(res = extract_name(header, plen, &p, name, 0, 10))) - return STAT_INSECURE; /* bad packet */ - - pstart = p; - - GETSHORT(stype, p); - GETSHORT(sclass, p); - GETLONG(sttl, p); - GETSHORT(rdlen, p); - - (void)sttl; - - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_INSECURE; /* bad packet */ - - if (res == 1 && sclass == class) - { - if (stype == type) - { - rrset[rrsetidx++] = pstart; - if (rrsetidx == MAXRRSET) - return STAT_INSECURE; /* RRSET too big TODO */ - } - - if (stype == T_RRSIG) - { - sigs[sigidx++] = pstart; - if (sigidx == MAXRRSET) - return STAT_INSECURE; /* RRSET too big TODO */ - } - } - - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_INSECURE; - } - - /* RRset empty, no RRSIGs */ - if (rrsetidx == 0 || sigidx == 0) - return STAT_INSECURE; - - /* Now try all the sigs to try and find one which validates */ - for (j = 0; j sig = p; - alg->siglen = rdlen - (p - psav); - - ntype = htons(type); - nclass = htons(class); - nsigttl = htonl(orig_ttl); - - digestalg_begin(alg->vtbl->digest_algo); - digestalg_add_data(psav, 18); - wire_len = to_wire(keyname); - digestalg_add_data(keyname, wire_len); - from_wire(keyname); - - /* TODO wildcard rules : 4035 5.3.2 */ - for (i = 0; i < rrsetidx; ++i) - { - p = (unsigned char*)(rrset[i]); - - wire_len = to_wire(name); - digestalg_add_data(name, wire_len); - from_wire(name); - digestalg_add_data(&ntype, 2); - digestalg_add_data(&nclass, 2); - digestalg_add_data(&nsigttl, 4); - - p += 8; - if (!digestalg_add_rdata(type, header, plen, p)) - return STAT_INSECURE; - } - - memcpy(alg->digest, digestalg_final(), digestalg_len()); - - if (key) - { - if (algo_in == algo && keytag_in == key_tag && - alg->vtbl->verify(alg, key, keylen)) - return STAT_SECURE; - } - else - { - /* iterate through all possible keys 4035 5.3.1 */ - for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) - if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag && - alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid)) - return STAT_SECURE; - } - } - - return STAT_BOGUS; -} - - /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class) { @@ -987,5 +792,39 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) } } +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr) +{ + unsigned char *p; + char types[20]; + + querystr("dnssec", types, type); + if (addr->sa.sa_family == AF_INET) + log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); +#ifdef HAVE_IPV6 + else + log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); +#endif + + header->qdcount = htons(1); + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + + header->hb3 = HB3_RD; + SET_OPCODE(header, QUERY); + header->hb4 = HB4_CD; + + /* ID filled in later */ + + p = (unsigned char *)(header+1); + + p = do_rfc1035_name(p, name); + *p++ = 0; + PUTSHORT(type, p); + PUTSHORT(class, p); + + return add_do_bit(header, p - (unsigned char *)header, end); +} + #endif /* HAVE_DNSSEC */ diff --git a/src/rfc1035.c b/src/rfc1035.c index 7d48910..0b254e3 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -493,7 +493,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t else if (is_sign && i == arcount - 1 && class == C_ANY && - (type == T_SIG || type == T_TSIG)) + type == T_TSIG) *is_sign = 1; } From 5ada8885075dae927d47eaf02442b6973b53c61a Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 9 Jan 2014 22:25:03 +0000 Subject: [PATCH 101/105] RFC 4035 5.3.2 wildcard label rules. --- src/dnssec.c | 138 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 56 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index daf1dd3..d832137 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -76,6 +76,21 @@ static void from_wire(char *name) *(l-1) = 0; } +/* Input in presentation format */ +static int count_labels(char *name) +{ + int i; + + if (*name == 0) + return 0; + + for (i = 0; *name; name++) + if (*name == '.') + i++; + + return i+1; +} + /* Implement RFC1982 wrapped compare for 32-bit numbers */ static int serial_compare_32(unsigned long s1, unsigned long s2) { @@ -280,7 +295,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in static int rrset_sz = 0, sig_sz = 0; unsigned char *p; - int rrsetidx, sigidx, res, rdlen, j; + int rrsetidx, sigidx, res, rdlen, j, name_labels; struct crec *crecp = NULL; int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; u16 *rr_desc = get_desc(type); @@ -288,12 +303,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (!(p = skip_questions(header, plen))) return STAT_INSECURE; - /* look for an RRSIG record for this RRset and get pointers to each record */ + name_labels = count_labels(name); /* For 4035 5.3.2 check */ + + /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); j != 0; j--) { unsigned char *pstart, *pdata; - int stype, sclass, sttl; + int stype, sclass; pstart = p; @@ -302,17 +319,12 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in GETSHORT(stype, p); GETSHORT(sclass, p); - GETLONG(sttl, p); + p += 4; /* TTL */ pdata = p; GETSHORT(rdlen, p); - (void)sttl; - - if (!CHECK_LEN(header, p, plen, rdlen)) - return STAT_INSECURE; /* bad packet */ - if (res == 1 && sclass == class) { if (stype == type) @@ -339,24 +351,42 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (stype == T_RRSIG) { - if (sigidx == sig_sz) - { - unsigned char **new; - - /* expand */ - if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **)))) - return STAT_INSECURE; - - if (sigs) - { - memcpy(new, sigs, sig_sz * sizeof(unsigned char **)); - free(sigs); - } - - sigs = new; - sig_sz += 5; - } - sigs[sigidx++] = pdata; + if (rdlen < 18) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(type_covered, p); + algo = *p++; + labels = *p++; + p += 4; /* orig_ttl */ + GETLONG(sig_expiration, p); + GETLONG(sig_inception, p); + p = pdata + 2; /* restore for ADD_RDLEN */ + + if (type_covered == type && + check_date_range(sig_inception, sig_expiration) && + verifyalg_supported(algo) && + labels <= name_labels) + { + if (sigidx == sig_sz) + { + unsigned char **new; + + /* expand */ + if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **)))) + return STAT_INSECURE; + + if (sigs) + { + memcpy(new, sigs, sig_sz * sizeof(unsigned char **)); + free(sigs); + } + + sigs = new; + sig_sz += 5; + } + + sigs[sigidx++] = pdata; + } } } @@ -382,33 +412,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in u32 nsigttl; p = sigs[j]; - - GETSHORT(rdlen, p); - - if (rdlen < 18) - return STAT_INSECURE; /* bad packet */ - + GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */ psav = p; - GETSHORT(type_covered, p); + p += 2; /* type_covered - already checked */ algo = *p++; labels = *p++; GETLONG(orig_ttl, p); - GETLONG(sig_expiration, p); - GETLONG(sig_inception, p); + p += 8; /* sig_expiration and sig_inception */ GETSHORT(key_tag, p); - if (type_covered != type || - !check_date_range(sig_inception, sig_expiration) || - !verifyalg_supported(algo)) - { - /* covers wrong type or out of date - skip */ - p = psav; - if (!ADD_RDLEN(header, p, plen, rdlen)) - return STAT_INSECURE; - continue; - } - if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_INSECURE; @@ -428,24 +441,38 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in digestalg_add_data(keyname, wire_len); from_wire(keyname); - /* TODO wildcard rules : 4035 5.3.2 */ for (i = 0; i < rrsetidx; ++i) { int seg; unsigned char *end, *cp; + char *name_start = name; u16 len, *dp; p = rrset[i]; if (!extract_name(header, plen, &p, name, 1, 10)) return STAT_INSECURE; - wire_len = to_wire(name); - digestalg_add_data(name, wire_len); - from_wire(name); /* leave name unchanged on exit */ + + /* if more labels than in RRsig name, hash *. 4035 5.3.2 */ + if (labels < name_labels) + { + int k; + for (k = name_labels - labels; k != 0; k--) + while (*name_start != '.' && *name_start != 0) + name_start++; + name_start--; + *name_start = '*'; + } + + 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); p += 8; /* skip class, type, ttl */ GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_INSECURE; + end = p + rdlen; /* canonicalise rdata and calculate length of same, use name buffer as workspace */ @@ -463,13 +490,12 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in digestalg_add_data(name, seg); if (cp != end) digestalg_add_data(cp, end - cp); - - /* namebuff used for workspace, above, restore for next loop - and to leave unchanged on exit */ - p = (unsigned char*)(rrset[i]); - extract_name(header, plen, &p, name, 1, 0); } + /* 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) From 1486a9c7f27fbeb2fa131ef3274e34fa1d7098f2 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Fri, 10 Jan 2014 11:39:14 +0000 Subject: [PATCH 102/105] Furthet tweak to RRset sort. --- src/dnssec.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/dnssec.c b/src/dnssec.c index d832137..712696f 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -233,8 +233,11 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int dp1 = dp2 = rr_desc; - for (quit = 0, left1 = 0, left2 = 0; !quit;) + for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) { + if (left1 != 0) + memmove(buff1, buff1 + len1 - left1, left1); + if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0) { quit = 1; @@ -243,6 +246,9 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int } len1 += left1; + if (left2 != 0) + memmove(buff2, buff2 + len2 - left2, left2); + if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0) { quit = 1; @@ -252,21 +258,13 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int len2 += left2; if (len1 > len2) - { - left1 = len1 - len2; - left2 = 0; - len = len2; - } + left1 = len1 - len2, left2 = 0, len = len2; else - { - left2 = len2 - len1; - left1 = 0; - len = len1; - } + left2 = len2 - len1, left1 = 0, len = len1; rc = memcmp(buff1, buff2, len); - if (rc == 1 || (rc == 0 && quit && len2 > len1)) + if (rc == 1 || (rc == 0 && quit && len1 > len2)) { unsigned char *tmp = rrset[i+1]; rrset[i+1] = rrset[i]; From 4f04476e3b25d4e25cbfef17f29abdedf2b56574 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Fri, 10 Jan 2014 12:20:38 +0000 Subject: [PATCH 103/105] Set AD bit for address replies from /etc/hosts &c --- src/cache.c | 14 +++++++------- src/config.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cache.c b/src/cache.c index 1ff783c..fbdcae7 100644 --- a/src/cache.c +++ b/src/cache.c @@ -692,7 +692,7 @@ static void add_hosts_cname(struct crec *target) if (hostname_isequal(cache_get_name(target), a->target) && (crec = whine_malloc(sizeof(struct crec)))) { - crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME; + crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME | F_DNSSECOK; crec->name.namep = a->alias; crec->addr.cname.target.cache = target; crec->addr.cname.uid = target->uid; @@ -829,14 +829,14 @@ static int read_hostsfile(char *filename, int index, int cache_size, struct crec if (inet_pton(AF_INET, token, &addr) > 0) { - flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; + flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_DNSSECOK; addrlen = INADDRSZ; domain_suffix = get_domain(addr.addr.addr4); } #ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, token, &addr) > 0) { - flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; + flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_DNSSECOK; addrlen = IN6ADDRSZ; domain_suffix = get_domain6(&addr.addr.addr6); } @@ -990,7 +990,7 @@ void cache_reload(void) (cache = whine_malloc(sizeof(struct crec)))) { cache->name.namep = nl->name; - cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG; + cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG | F_DNSSECOK; add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, 0, (struct crec **)daemon->packet, revhashsz); } #ifdef HAVE_IPV6 @@ -998,7 +998,7 @@ void cache_reload(void) (cache = whine_malloc(sizeof(struct crec)))) { cache->name.namep = nl->name; - cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG; + cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG | F_DNSSECOK; add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, 0, (struct crec **)daemon->packet, revhashsz); } #endif @@ -1068,7 +1068,7 @@ static void add_dhcp_cname(struct crec *target, time_t ttd) if (aliasc) { - aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG; + aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG | F_DNSSECOK; if (ttd == 0) aliasc->flags |= F_IMMORTAL; else @@ -1156,7 +1156,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, if (crec) /* malloc may fail */ { - crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD; + crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD | F_DNSSECOK; if (ttd == 0) crec->flags |= F_IMMORTAL; else diff --git a/src/config.h b/src/config.h index 80b154c..c9870d4 100644 --- a/src/config.h +++ b/src/config.h @@ -18,7 +18,7 @@ #define MAX_PROCS 20 /* max no children for TCP requests */ #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ #define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */ -#define KEYBLOCK_LEN 140 /* choose to mininise fragmentation when storing DNSSEC keys */ +#define KEYBLOCK_LEN 35 /* choose to mininise fragmentation when storing DNSSEC keys */ #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define FORWARD_TEST 50 /* try all servers every 50 queries */ #define FORWARD_TIME 20 /* or 20 seconds */ From ae76242fdf9ab5ec1c1c81a64030daf85e36582f Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Fri, 10 Jan 2014 18:15:16 +0000 Subject: [PATCH 104/105] Fix missing RA RDNS option with --dhcp-option=option6:23,[::] --- CHANGELOG | 4 ++++ src/radv.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 785eea4..165ec4f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,10 @@ version 2.69 Fix infinite loop associated with some --bogus-nxdomain configs. Thanks fogobogo for the bug report. + Fix missing RA RDNS option with configuration like + --dhcp-option=option6:23,[::] Thanks to Tsachi Kimeldorfer + for spotting the problem. + version 2.68 Use random addresses for DHCPv6 temporary address diff --git a/src/radv.c b/src/radv.c index 9838bb6..84c1aab 100644 --- a/src/radv.c +++ b/src/radv.c @@ -343,7 +343,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de struct in6_addr *a = (struct in6_addr *)opt_cfg->val; done_dns = 1; - if (opt_cfg->len == 0 || (IN6_IS_ADDR_UNSPECIFIED(a) && parm.pref_time != 0)) + if (opt_cfg->len == 0 || (IN6_IS_ADDR_UNSPECIFIED(a) && parm.pref_time == 0)) continue; put_opt6_char(ICMP6_OPT_RDNSS); From c3a04081ff5e8a400df21096791e67e368114c73 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Sat, 11 Jan 2014 22:18:19 +0000 Subject: [PATCH 105/105] [fd00::} and [fe80::] special addresses in DHCPv6 options. --- CHANGELOG | 6 +++ man/dnsmasq.8 | 12 ++++-- src/dhcp6.c | 11 ++++- src/dnsmasq.h | 4 +- src/ip6addr.h | 34 ++++++++++++++++ src/radv.c | 108 ++++++++++++++++++++++++++++++++++++++------------ src/rfc3315.c | 76 +++++++++++++++++++++++------------ 7 files changed, 194 insertions(+), 57 deletions(-) create mode 100644 src/ip6addr.h diff --git a/CHANGELOG b/CHANGELOG index 165ec4f..b218821 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,12 @@ version 2.69 --dhcp-option=option6:23,[::] Thanks to Tsachi Kimeldorfer for spotting the problem. + Add [fd00::] and [fe80::] as special addresses in DHCPv6 + options, analogous to [::]. [fd00::] is replaced with the + actual ULA of the interface on the machine running + dnsmasq, [fe80::] with the link-local address. + Thanks to Tsachi Kimeldorfer for championing this. + version 2.68 Use random addresses for DHCPv6 temporary address diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index e85d272..42d3d5c 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -917,9 +917,11 @@ and to set the time-server address to 192.168.0.4, do .B --dhcp-option = 42,192.168.0.4 or .B --dhcp-option = option:ntp-server, 192.168.0.4 -The special address 0.0.0.0 (or [::] for DHCPv6) is taken to mean "the address of the -machine running dnsmasq". Data types allowed are comma separated -dotted-quad IP addresses, a decimal number, colon-separated hex digits +The special address 0.0.0.0 is taken to mean "the address of the +machine running dnsmasq". + +Data types allowed are comma separated +dotted-quad IPv4 addresses, []-wrapped IPv6 addresses, a decimal number, colon-separated hex digits and a text string. If the optional tags are given then this option is only sent when all the tags are matched. @@ -935,7 +937,9 @@ keyword, followed by the option number or option name. The IPv6 option name space is disjoint from the IPv4 option name space. IPv6 addresses in options must be bracketed with square brackets, eg. .B --dhcp-option=option6:ntp-server,[1234::56] - +For IPv6, [::] means "the global address of +the machine running dnsmasq", whilst [fd00::] is replaced with the +ULA, if it exists, and [fe80::] with the link-local address. Be careful: no checking is done that the correct type of data for the option number is sent, it is quite possible to diff --git a/src/dhcp6.c b/src/dhcp6.c index d9cd690..3a83c18 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -23,7 +23,7 @@ struct iface_param { struct dhcp_context *current; struct dhcp_relay *relay; - struct in6_addr fallback, relay_local; + struct in6_addr fallback, relay_local, ll_addr, ula_addr; int ind, addr_match; }; @@ -158,6 +158,8 @@ void dhcp6_packet(time_t now) parm.ind = if_index; parm.addr_match = 0; memset(&parm.fallback, 0, IN6ADDRSZ); + memset(&parm.ll_addr, 0, IN6ADDRSZ); + memset(&parm.ula_addr, 0, IN6ADDRSZ); for (context = daemon->dhcp6; context; context = context->next) if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0) @@ -210,7 +212,7 @@ void dhcp6_packet(time_t now) lease_prune(NULL, now); /* lose any expired leases */ port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, - sz, &from.sin6_addr, now); + &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now); lease_update_file(now); lease_update_dns(0); @@ -309,6 +311,11 @@ static int complete_context6(struct in6_addr *local, int prefix, if (if_index == param->ind) { + if (IN6_IS_ADDR_LINKLOCAL(local)) + param->ll_addr = *local; + else if (IN6_IS_ADDR_ULA(local)) + param->ula_addr = *local; + if (!IN6_IS_ADDR_LOOPBACK(local) && !IN6_IS_ADDR_LINKLOCAL(local) && !IN6_IS_ADDR_MULTICAST(local)) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index ca4fa61..0082510 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -50,6 +50,7 @@ #include #include "config.h" +#include "ip6addr.h" typedef unsigned char u8; typedef unsigned short u16; @@ -1314,7 +1315,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, /* rfc3315.c */ #ifdef HAVE_DHCP6 unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, - struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now); + struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr, + size_t sz, struct in6_addr *client_addr, time_t now); void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id); unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface); diff --git a/src/ip6addr.h b/src/ip6addr.h new file mode 100644 index 0000000..c7dcb39 --- /dev/null +++ b/src/ip6addr.h @@ -0,0 +1,34 @@ +/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 . +*/ + + + +#define IN6_IS_ADDR_ULA(a) \ + ((((__const uint32_t *) (a))[0] & htonl (0xff000000)) \ + == htonl (0xfd000000)) + +#define IN6_IS_ADDR_ULA_ZERO(a) \ + (((__const uint32_t *) (a))[0] == htonl (0xfd000000) \ + && ((__const uint32_t *) (a))[1] == 0 \ + && ((__const uint32_t *) (a))[2] == 0 \ + && ((__const uint32_t *) (a))[3] == 0) + +#define IN6_IS_ADDR_LINK_LOCAL_ZERO(a) \ + (((__const uint32_t *) (a))[0] == htonl (0xfe800000) \ + && ((__const uint32_t *) (a))[1] == 0 \ + && ((__const uint32_t *) (a))[2] == 0 \ + && ((__const uint32_t *) (a))[3] == 0) + diff --git a/src/radv.c b/src/radv.c index 84c1aab..76027cd 100644 --- a/src/radv.c +++ b/src/radv.c @@ -31,8 +31,8 @@ struct ra_param { int ind, managed, other, found_context, first; char *if_name; struct dhcp_netid *tags; - struct in6_addr link_local, link_global; - unsigned int pref_time, adv_interval; + struct in6_addr link_local, link_global, ula; + unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval; }; struct search_param { @@ -206,6 +206,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de struct dhcp_opt *opt_cfg; struct ra_interface *ra_param = find_iface_param(iface_name); int done_dns = 0, old_prefix = 0; + unsigned int min_pref_time; #ifdef HAVE_LINUX_NETWORK FILE *f; #endif @@ -228,7 +229,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de parm.if_name = iface_name; parm.first = 1; parm.now = now; - parm.pref_time = 0; + parm.glob_pref_time = parm.link_pref_time = parm.ula_pref_time = 0; parm.adv_interval = calc_interval(ra_param); /* set tag with name == interface */ @@ -245,6 +246,18 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de if (!iface_enumerate(AF_INET6, &parm, add_prefixes)) return; + /* Find smallest preferred time within address classes, + to use as lifetime for options. This is a rather arbitrary choice. */ + min_pref_time = 0xffffffff; + if (parm.glob_pref_time != 0 && parm.glob_pref_time < min_pref_time) + min_pref_time = parm.glob_pref_time; + + if (parm.ula_pref_time != 0 && parm.ula_pref_time < min_pref_time) + min_pref_time = parm.ula_pref_time; + + if (parm.link_pref_time != 0 && parm.link_pref_time < min_pref_time) + min_pref_time = parm.link_pref_time; + /* Look for constructed contexts associated with addresses which have gone, and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */ for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) @@ -340,22 +353,48 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de if (opt_cfg->opt == OPTION6_DNS_SERVER) { - struct in6_addr *a = (struct in6_addr *)opt_cfg->val; - + struct in6_addr *a; + int len; + done_dns = 1; - if (opt_cfg->len == 0 || (IN6_IS_ADDR_UNSPECIFIED(a) && parm.pref_time == 0)) + + if (opt_cfg->len == 0) continue; - put_opt6_char(ICMP6_OPT_RDNSS); - put_opt6_char((opt_cfg->len/8) + 1); - put_opt6_short(0); - put_opt6_long(parm.pref_time); - /* zero means "self" */ - for (i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++) - if (IN6_IS_ADDR_UNSPECIFIED(a)) - put_opt6(&parm.link_global, IN6ADDRSZ); - else - put_opt6(a, IN6ADDRSZ); + /* reduce len for any addresses we can't substitute */ + for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, i = 0; + i < opt_cfg->len; i += IN6ADDRSZ, a++) + if ((IN6_IS_ADDR_UNSPECIFIED(a) && parm.glob_pref_time == 0) || + (IN6_IS_ADDR_ULA_ZERO(a) && parm.ula_pref_time == 0) || + (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && parm.link_pref_time == 0)) + len -= IN6ADDRSZ; + + if (len != 0) + { + put_opt6_char(ICMP6_OPT_RDNSS); + put_opt6_char((len/8) + 1); + put_opt6_short(0); + put_opt6_long(min_pref_time); + + for (a = (struct in6_addr *)opt_cfg->val, i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++) + if (IN6_IS_ADDR_UNSPECIFIED(a)) + { + if (parm.glob_pref_time != 0) + put_opt6(&parm.link_global, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_ULA_ZERO(a)) + { + if (parm.ula_pref_time != 0) + put_opt6(&parm.ula, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a)) + { + if (parm.link_pref_time != 0) + put_opt6(&parm.link_local, IN6ADDRSZ); + } + else + put_opt6(a, IN6ADDRSZ); + } } if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0) @@ -365,7 +404,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de put_opt6_char(ICMP6_OPT_DNSSL); put_opt6_char(len + 1); put_opt6_short(0); - put_opt6_long(parm.pref_time); + put_opt6_long(min_pref_time); put_opt6(opt_cfg->val, opt_cfg->len); /* pad */ @@ -374,13 +413,13 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de } } - if (daemon->port == NAMESERVER_PORT && !done_dns && parm.pref_time != 0) + if (daemon->port == NAMESERVER_PORT && !done_dns && parm.link_pref_time != 0) { /* default == us, as long as we are supplying DNS service. */ put_opt6_char(ICMP6_OPT_RDNSS); put_opt6_char(3); put_opt6_short(0); - put_opt6_long(parm.pref_time); + put_opt6_long(min_pref_time); put_opt6(&parm.link_local, IN6ADDRSZ); } @@ -426,7 +465,16 @@ static int add_prefixes(struct in6_addr *local, int prefix, if (if_index == param->ind) { if (IN6_IS_ADDR_LINKLOCAL(local)) - param->link_local = *local; + { + /* Can there be more than one LL address? + Select the one with the longest preferred time + if there is. */ + if (preferred > param->link_pref_time) + { + param->link_pref_time = preferred; + param->link_local = *local; + } + } else if (!IN6_IS_ADDR_LOOPBACK(local) && !IN6_IS_ADDR_MULTICAST(local)) { @@ -516,11 +564,22 @@ static int add_prefixes(struct in6_addr *local, int prefix, /* configured time is ceiling */ if (!constructed || preferred > time) preferred = time; - - if (preferred > param->pref_time) + + if (IN6_IS_ADDR_ULA(local)) { - param->pref_time = preferred; - param->link_global = *local; + if (preferred > param->ula_pref_time) + { + param->ula_pref_time = preferred; + param->ula = *local; + } + } + else + { + if (preferred > param->glob_pref_time) + { + param->glob_pref_time = preferred; + param->link_global = *local; + } } if (real_prefix != 0) @@ -546,7 +605,6 @@ static int add_prefixes(struct in6_addr *local, int prefix, if (!option_bool(OPT_QUIET_RA)) my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff); } - } } } diff --git a/src/rfc3315.c b/src/rfc3315.c index a18c549..364277d 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -24,7 +24,7 @@ struct state { int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate; char *client_hostname, *hostname, *domain, *send_domain; struct dhcp_context *context; - struct in6_addr *link_address, *fallback; + struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr; unsigned int xid, fqdn_flags; char *iface_name; void *packet_options, *end; @@ -73,7 +73,8 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, - struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now) + struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr, + size_t sz, struct in6_addr *client_addr, time_t now) { struct dhcp_vendor *vendor; int msg_type; @@ -93,6 +94,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if state.interface = interface; state.iface_name = iface_name; state.fallback = fallback; + state.ll_addr = ll_addr; + state.ula_addr = ula_addr; state.mac_len = 0; state.tags = NULL; state.link_address = NULL; @@ -1269,36 +1272,59 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) continue; } - if (opt_cfg->opt == OPTION6_DNS_SERVER) - { - done_dns = 1; - if (opt_cfg->len == 0) - continue; - } - if (opt_cfg->opt == OPTION6_REFRESH_TIME) done_refresh = 1; - o = new_opt6(opt_cfg->opt); if (opt_cfg->flags & DHOPT_ADDR6) { - int j; - struct in6_addr *a = (struct in6_addr *)opt_cfg->val; - for (j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++) - { - /* zero means "self" (but not in vendorclass options.) */ - if (IN6_IS_ADDR_UNSPECIFIED(a)) - { - if (!add_local_addrs(state->context)) - put_opt6(state->fallback, IN6ADDRSZ); + int len, j; + struct in6_addr *a; + + if (opt_cfg->opt == OPTION6_DNS_SERVER) + done_dns = 1; + + for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0; + j < opt_cfg->len; j += IN6ADDRSZ, a++) + if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) || + (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ll_addr))) + len -= IN6ADDRSZ; + + if (len != 0) + { + + o = new_opt6(opt_cfg->opt); + + for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++) + { + if (IN6_IS_ADDR_UNSPECIFIED(a)) + { + if (!add_local_addrs(state->context)) + put_opt6(state->fallback, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_ULA_ZERO(a)) + { + if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) + put_opt6(state->ula_addr, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a)) + { + if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)) + put_opt6(state->ll_addr, IN6ADDRSZ); + } + else + put_opt6(a, IN6ADDRSZ); } - else - put_opt6(a, IN6ADDRSZ); - } + + end_opt6(o); + } + } + else + { + o = new_opt6(opt_cfg->opt); + if (opt_cfg->val) + put_opt6(opt_cfg->val, opt_cfg->len); + end_opt6(o); } - else if (opt_cfg->val) - put_opt6(opt_cfg->val, opt_cfg->len); - end_opt6(o); } if (daemon->port == NAMESERVER_PORT && !done_dns)