]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Implement RFC 1321 MD5 Message-Digest if not provided in libc.
authorRoy Marples <roy@marples.name>
Sat, 25 Jan 2014 01:35:53 +0000 (01:35 +0000)
committerRoy Marples <roy@marples.name>
Sat, 25 Jan 2014 01:35:53 +0000 (01:35 +0000)
Implement RFC 2104 HMAC Keyed Hashing.

Implement RFC 3118 Authentication for DHCP Messages
and RFC 3315 Authentication options.

24 files changed:
Makefile
arp.c
auth.c [new file with mode: 0644]
auth.h [new file with mode: 0644]
configure
crypt/crypt.h [new file with mode: 0644]
crypt/hmac_md5.c [new file with mode: 0644]
crypt/md5.c [new file with mode: 0644]
crypt/md5.h [new file with mode: 0644]
defs.h
dhcp.c
dhcp.h
dhcp6.c
dhcp6.h
dhcpcd-definitions.conf
dhcpcd.8.in
dhcpcd.c
dhcpcd.conf.5.in
if-options.c
if-options.h
test/Makefile [new file with mode: 0644]
test/test.c [new file with mode: 0644]
test/test.h [new file with mode: 0644]
test/test_hmac_md5.c [new file with mode: 0644]

index 4d831ab49407a96c1a2a5b6a0a5700f522fc638a..b6fe6dea2c97bd8764bd5b53d6bb9f4d8036fa40 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,15 @@ MKDIRS=
 include config.mk
 CFLAGS+=       -std=${CSTD}
 
+SRCS+=         ${DHCPCD_SRCS}
+
+.PATH: ./crypt
+
+VPATH= . ./crypt
+
+CPPFLAGS+=     -I./crypt
+SRCS+=         auth.c hmac_md5.c ${MD5_SRC}
+
 OBJS+=         ${SRCS:.c=.o} ${COMPAT_SRCS:.c=.o}
 
 SCRIPT=                ${LIBEXECDIR}/dhcpcd-run-hooks
@@ -58,7 +67,7 @@ HOST_SH?=     /bin/sh
 
 CLEANFILES+=   *.tar.bz2
 
-.PHONY:                import import-bsd dev
+.PHONY:                import import-bsd dev test
 
 .SUFFIXES:     .in
 
@@ -95,6 +104,9 @@ depend: .depend
 ${PROG}: ${DEPEND} ${OBJS}
        ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
 
+test:
+       cd $@; ${MAKE} $@; ./$@
+
 _embeddedinstall: dhcpcd-definitions.conf
        ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR}
        ${INSTALL} -m ${CONFMODE} dhcpcd-definitions.conf ${DESTDIR}${SCRIPTSDIR}
@@ -126,7 +138,7 @@ install: proginstall _maninstall _confinstall
 
 clean:
        rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
-       for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done
+       for x in ${SUBDIRS} test; do cd $$x; ${MAKE} $@; cd ..; done
 
 distclean: clean
        rm -f .depend config.h config.mk
diff --git a/arp.c b/arp.c
index 056e6858b3a99aa9e5cd4a7e9b51b1c64267d265..ea8a73c7e06426c6d80a8c4083fb23c93772fd19 100644 (file)
--- a/arp.c
+++ b/arp.c
@@ -231,7 +231,7 @@ arp_announce(void *arg)
        if (++state->claims < ANNOUNCE_NUM)
                syslog(LOG_DEBUG,
                    "%s: sending ARP announce (%d of %d), "
-                   "next in %d.00 seconds",
+                   "next in %d.0 seconds",
                    ifp->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
        else
                syslog(LOG_DEBUG,
@@ -317,7 +317,7 @@ arp_probe(void *arg)
                        eloop_timeout_add_tv(&tv, dhcp_bind, ifp);
        }
        syslog(LOG_DEBUG,
-           "%s: sending ARP probe (%d of %d), next in %0.2f seconds",
+           "%s: sending ARP probe (%d of %d), next in %0.1f seconds",
            ifp->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM,
            timeval_to_double(&tv));
        if (arp_send(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1)
diff --git a/auth.c b/auth.c
new file mode 100644 (file)
index 0000000..e088bd3
--- /dev/null
+++ b/auth.c
@@ -0,0 +1,548 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/file.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "config.h"
+#include "auth.h"
+#include "crypt/crypt.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "dhcpcd.h"
+
+#ifndef htonll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static inline uint64_t
+htonll(uint64_t x)
+{
+
+       return (uint64_t)htonl((uint32_t)(x >> 32)) |
+           (int64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
+}
+#else  /* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define htonll(x) (x)
+#endif
+#endif  /* htonll */
+
+#ifndef ntohll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static inline uint64_t
+ntohll(uint64_t x)
+{
+
+       return (uint64_t)ntohl((uint32_t)(x >> 32)) |
+           (int64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
+}
+#else  /* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define ntohll(x) (x)
+#endif
+#endif  /* ntohll */
+
+#define HMAC_LENGTH    16
+
+/*
+ * Authenticate a DHCP message.
+ * m and mlen refer to the whole message.
+ * t is the DHCP type, pass it 4 or 6.
+ * data and dlen refer to the authentication option within the message.
+ */
+const struct token *
+dhcp_auth_validate(struct authstate *state, const struct auth *auth,
+    const uint8_t *m, unsigned int mlen, int mp,  int mt,
+    const uint8_t *data, unsigned int dlen)
+{
+       uint8_t protocol, algorithm, rdm, *mm, type;
+       uint64_t replay;
+       uint32_t secretid;
+       const uint8_t *d, *realm;
+       unsigned int realm_len;
+       const struct token *t;
+       time_t now;
+       uint8_t hmac[HMAC_LENGTH];
+
+       if (dlen < 3 + sizeof(replay)) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
+       if (data < m || data > m + mlen || data + dlen > m + mlen) {
+               errno = ERANGE;
+               return NULL;
+       }
+
+       d = data;
+       protocol = *d++;
+       algorithm = *d++;
+       rdm = *d++;
+       if (!(auth->options & DHCPCD_AUTH_SEND)) {
+               /* If we didn't send any authorisation, it can only be a
+                * reconfigure key */
+               if (protocol != AUTH_PROTO_RECONFKEY) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+       } else if (protocol != auth->protocol ||
+                   algorithm != auth->algorithm ||
+                   rdm != auth->rdm)
+       {
+               errno = EPERM;
+               return NULL;
+       }
+
+       dlen -= 3;
+       memcpy(&replay, d, sizeof(replay));
+       replay = ntohll(replay);
+       d+= sizeof(replay);
+       dlen -= sizeof(replay);
+
+       if (state->token && replay - state->replay <= 0) {
+               /* Replay attack detected */
+               errno = EPERM;
+               return NULL;
+       }
+
+       realm = NULL;
+       realm_len = 0;
+
+       /* Extract realm and secret.
+        * Rest of data is MAC. */
+       switch (protocol) {
+       case AUTH_PROTO_TOKEN:
+               secretid = 0;
+               break;
+       case AUTH_PROTO_DELAYED:
+               if (dlen < sizeof(secretid) + sizeof(hmac)) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+               memcpy(&secretid, d, sizeof(secretid));
+               d += sizeof(secretid);
+               dlen -= sizeof(secretid);
+               break;
+       case AUTH_PROTO_DELAYEDREALM:
+               if (dlen < sizeof(secretid) + sizeof(hmac)) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+               realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
+               if (realm_len) {
+                       realm = d;
+                       d += realm_len;
+                       dlen -= realm_len;
+               }
+               memcpy(&secretid, d, sizeof(secretid));
+               d += sizeof(secretid);
+               dlen -= sizeof(secretid);
+               break;
+       case AUTH_PROTO_RECONFKEY:
+               if (dlen != 1 + 16) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+               type = *d++;
+               dlen--;
+               switch (type) {
+               case 1:
+                       if ((mp == 4 && mt == DHCP_ACK) ||
+                           (mp == 6 && mt == DHCP6_REPLY))
+                       {
+                               if (state->reconf == NULL) {
+                                       state->reconf =
+                                           malloc(sizeof(*state->reconf));
+                                       if (state->reconf == NULL)
+                                               return NULL;
+                                       state->reconf->key = malloc(16);
+                                       if (state->reconf->key == NULL) {
+                                               free(state->reconf);
+                                               state->reconf = NULL;
+                                               return NULL;
+                                       }
+                                       state->reconf->secretid = 0;
+                                       state->reconf->expire = 0;
+                                       state->reconf->realm = NULL;
+                                       state->reconf->realm_len = 0;
+                                       state->reconf->key_len = 16;
+                               }
+                               memcpy(state->reconf->key, d, 16);
+                       } else {
+                               errno = EINVAL;
+                               return NULL;
+                       }
+                       if (state->reconf == NULL)
+                               errno = ENOENT;
+                       /* Nothing to validate, just accepting the key */
+                       return state->reconf;
+               case 2:
+                       if (state->reconf == NULL) {
+                               errno = ENOENT;
+                               return NULL;
+                       }
+                       t = state->reconf;
+                       goto gottoken;
+               default:
+                       errno = EINVAL;
+                       return NULL;
+               }
+       default:
+               errno = ENOTSUP;
+               return NULL;
+       }
+
+       /* Find a token for the realm and secret */
+       secretid = ntohl(secretid);
+       TAILQ_FOREACH(t, &auth->tokens, next) {
+               if (t->secretid == secretid &&
+                   t->realm_len == realm_len &&
+                   (t->realm_len == 0 ||
+                   memcmp(t->realm, realm, t->realm_len) == 0))
+                       break;
+       }
+       if (t == NULL) {
+               errno = ESRCH;
+               return NULL;
+       }
+       if (t->expire) {
+               if (time(&now) == -1)
+                       return NULL;
+               if (t->expire < now) {
+                       errno = EFAULT;
+                       return NULL;
+               }
+       }
+
+gottoken:
+       /* First message from the server */
+       if (state->token && state->token != t) {
+               errno = EPERM;
+               return NULL;
+       }
+
+       /* Special case as no hashing needs to be done. */
+       if (protocol == AUTH_PROTO_TOKEN) {
+               if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
+                       errno = EPERM;
+                       return NULL;
+               }
+               goto finish;
+       }
+
+       /* Make a duplicate of the message, but zero out the MAC part */
+       mm = malloc(mlen);
+       if (mm == NULL)
+               return NULL;
+       memcpy(mm, m, mlen);
+       memset(mm + (d - m), 0, dlen);
+
+       /* RFC3318, section 5.2 - zero giaddr and hops */
+       if (mp == 4) {
+               *(mm + offsetof(struct dhcp_message, hwopcount)) = '\0';
+               memset(mm + offsetof(struct dhcp_message, giaddr), 0, 4);
+       }
+
+       memset(hmac, 0, sizeof(hmac));
+       switch (algorithm) {
+       case AUTH_ALG_HMAC_MD5:
+               hmac_md5(mm, mlen, t->key, t->key_len, hmac);
+               break;
+       default:
+               errno = ENOSYS;
+               free(mm);
+               return NULL;
+       }
+
+       free(mm);
+       if (memcmp(d, &hmac, dlen)) {
+               errno = EPERM;
+               return NULL;
+       }
+
+finish:
+       /* If we got here then authentication passed */
+       state->replay = replay;
+       state->token = t;
+
+       return t;
+}
+
+static uint64_t last_rdm;
+static uint8_t last_rdm_set;
+static uint64_t
+get_next_rdm_monotonic(void)
+{
+       FILE *fp;
+       char *line, *ep;
+       uint64_t rdm;
+       int flocked;
+
+       fp = fopen(RDM_MONOFILE, "r+");
+       if (fp == NULL) {
+               if (errno != ENOENT)
+                       return ++last_rdm; /* report error? */
+               fp = fopen(RDM_MONOFILE, "w");
+               if (fp == NULL)
+                       return ++last_rdm; /* report error? */
+               flocked = flock(fileno(fp), LOCK_EX);
+               rdm = 0;
+       } else {
+               flocked = flock(fileno(fp), LOCK_EX);
+               line = get_line(fp);
+               if (line == NULL)
+                       rdm = 0; /* truncated? report error? */
+               else
+                       rdm = strtoull(line, &ep, 0);
+       }
+
+       rdm++;
+       fseek(fp, 0, SEEK_SET);
+       if (fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19) {
+               if (!last_rdm_set) {
+                       last_rdm = rdm;
+                       last_rdm_set = 1;
+               } else
+                       rdm = ++last_rdm;
+               /* report error? */
+       }
+       fflush(fp);
+       if (flocked == 0)
+               flock(fileno(fp), LOCK_UN);
+       fclose(fp);
+       return rdm;
+}
+
+
+/*
+ * Encode a DHCP message.
+ * Either we know which token to use from the server response
+ * or we are using a basic configuration token.
+ * token is the token to encrypt with.
+ * m and mlen refer to the whole message.
+ * mp is the DHCP type, pass it 4 or 6.
+ * mt is the DHCP message type.
+ * data and dlen refer to the authentication option within the message.
+ */
+int
+dhcp_auth_encode(const struct auth *auth, const struct token *t,
+    uint8_t *m, unsigned int mlen, int mp, int mt,
+    uint8_t *data, unsigned int dlen)
+{
+       uint64_t rdm;
+       uint8_t hmac[HMAC_LENGTH];
+       time_t now;
+       uint8_t hops, *p;
+       uint32_t giaddr, secretid;
+
+       if (auth->protocol == 0 && t == NULL) {
+               TAILQ_FOREACH(t, &auth->tokens, next) {
+                       if (t->secretid == 0 &&
+                           t->realm_len == 0)
+                       break;
+               }
+               if (t == NULL) {
+                       errno = EINVAL;
+                       return -1;
+               }
+               if (t->expire) {
+                       if (time(&now) == -1)
+                               return -1;
+                       if (t->expire < now) {
+                               errno = EPERM;
+                               return -1;
+                       }
+               }
+       }
+
+       switch(auth->protocol) {
+       case AUTH_PROTO_TOKEN:
+       case AUTH_PROTO_DELAYED:
+       case AUTH_PROTO_DELAYEDREALM:
+               /* We don't ever send a reconf key */
+               break;
+       default:
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       switch(auth->algorithm) {
+       case AUTH_ALG_HMAC_MD5:
+               break;
+       default:
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       switch(auth->rdm) {
+       case AUTH_RDM_MONOTONIC:
+               break;
+       default:
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       /* Work out the auth area size.
+        * We only need to do this for DISCOVER messages */
+       if (data == NULL) {
+               dlen = 1 + 1 + 1 + 8;
+               switch(auth->protocol) {
+               case AUTH_PROTO_TOKEN:
+                       dlen += t->key_len;
+                       break;
+               case AUTH_PROTO_DELAYEDREALM:
+                       if (t)
+                               dlen += t->realm_len;
+                       /* FALLTHROUGH */
+               case AUTH_PROTO_DELAYED:
+                       if (t)
+                               dlen += sizeof(t->secretid) + sizeof(hmac);
+                       break;
+               }
+               return dlen;
+       }
+
+       if (dlen < 1 + 1 + 1 + 8) {
+               errno = ENOBUFS;
+               return -1;
+       }
+
+       /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
+       if (data < m || data > m + mlen || data + dlen > m + mlen) {
+               errno = ERANGE;
+               return -1;
+       }
+
+       /* Write out our option */
+       *data++ = auth->protocol;
+       *data++ = auth->algorithm;
+       *data++ = auth->rdm;
+       switch (auth->rdm) {
+       case AUTH_RDM_MONOTONIC:
+               rdm = get_next_rdm_monotonic();
+               break;
+       default:
+               /* This block appeases gcc, clang doesn't need it */
+               rdm = get_next_rdm_monotonic();
+               break;
+       }
+       rdm = htonll(rdm);
+       memcpy(data, &rdm, 8);
+       data += 8;
+       dlen -= 1 + 1 + 1 + 8;
+
+       /* Special case as no hashing needs to be done. */
+       if (auth->protocol == AUTH_PROTO_TOKEN) {
+               /* Should be impossible, but still */
+               if (t == NULL) {
+                       errno = EINVAL;
+                       return -1;
+               }
+               if (dlen < t->key_len) {
+                       errno = ENOBUFS;
+                       return -1;
+               }
+               memcpy(data, t->key, t->key_len);
+               return dlen - t->key_len;
+       }
+
+       /* DISCOVER or INFORM messages don't write auth info */
+       if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
+           (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
+               return dlen;
+
+       /* Loading a saved lease without an authentication option */
+       if (t == NULL)
+               return 0;
+
+       /* Write out the Realm */
+       if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
+               if (dlen < t->realm_len) {
+                       errno = ENOBUFS;
+                       return -1;
+               }
+               memcpy(data, t->realm, t->realm_len);
+               data += t->realm_len;
+               dlen -= t->realm_len;
+       }
+
+       /* Write out the SecretID */
+       if (auth->protocol == AUTH_PROTO_DELAYED ||
+           auth->protocol == AUTH_PROTO_DELAYEDREALM)
+       {
+               if (dlen < sizeof(t->secretid)) {
+                       errno = ENOBUFS;
+                       return -1;
+               }
+               secretid = htonl(t->secretid);
+               memcpy(data, &secretid, sizeof(secretid));
+               data += sizeof(secretid);
+               dlen -= sizeof(secretid);
+       }
+
+       /* Zero what's left, the MAC */
+       memset(data, 0, dlen);
+
+       /* RFC3318, section 5.2 - zero giaddr and hops */
+       if (mp == 4) {
+               p = m + offsetof(struct dhcp_message, hwopcount);
+               hops = *p;
+               *p = '\0';
+               p = m + offsetof(struct dhcp_message, giaddr);
+               memcpy(&giaddr, p, sizeof(giaddr));
+               memset(p, 0, sizeof(giaddr));
+       } else {
+               /* appease GCC again */
+               hops = 0;
+               giaddr = 0;
+       }
+
+       /* Create our hash and write it out */
+       switch(auth->algorithm) {
+       case AUTH_ALG_HMAC_MD5:
+               hmac_md5(m, mlen, t->key, t->key_len, hmac);
+               memcpy(data, hmac, sizeof(hmac));
+               break;
+       }
+
+       /* RFC3318, section 5.2 - restore giaddr and hops */
+       if (mp == 4) {
+               p = m + offsetof(struct dhcp_message, hwopcount);
+               *p = hops;
+               p = m + offsetof(struct dhcp_message, giaddr);
+               memcpy(p, &giaddr, sizeof(giaddr));
+       }
+
+       /* Done! */
+       return dlen - sizeof(hmac); /* should be zero */
+}
diff --git a/auth.h b/auth.h
new file mode 100644 (file)
index 0000000..cd5f544
--- /dev/null
+++ b/auth.h
@@ -0,0 +1,79 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef AUTH_H
+#define AUTH_H
+
+#include <sys/queue.h>
+
+#define DHCPCD_AUTH_SEND       (1 << 0)
+#define DHCPCD_AUTH_REQUIRE    (1 << 1)
+
+#define AUTH_PROTO_TOKEN       0
+#define AUTH_PROTO_DELAYED     1
+#define AUTH_PROTO_DELAYEDREALM        2
+#define AUTH_PROTO_RECONFKEY   3
+
+#define AUTH_ALG_HMAC_MD5      1
+
+#define AUTH_RDM_MONOTONIC     0
+
+struct token {
+       TAILQ_ENTRY(token) next;
+       uint32_t secretid;
+       unsigned int realm_len;
+       unsigned char *realm;
+       unsigned int key_len;
+       unsigned char *key;
+       time_t expire;
+};
+
+TAILQ_HEAD(token_head, token);
+
+struct auth {
+       int options;
+       uint8_t protocol;
+       uint8_t algorithm;
+       uint8_t rdm;
+       struct token_head tokens;
+};
+
+struct authstate {
+       uint64_t replay;
+       const struct token *token;
+       struct token *reconf;
+};
+
+const struct token * dhcp_auth_validate(struct authstate *,
+    const struct auth *,
+    const uint8_t *, unsigned int, int, int,
+    const uint8_t *, unsigned int);
+
+int dhcp_auth_encode(const struct auth *, const struct token *,
+    uint8_t *, unsigned int, int, int,
+    uint8_t *, unsigned int);
+#endif
index 73392d9e41da90e5a558cef1b49202f1116abac9..cfb65ff0e1415ffbdf88979d91e62eb77277e3fd 100755 (executable)
--- a/configure
+++ b/configure
@@ -64,6 +64,7 @@ for x do
         --without-posix_spawn) POSIX_SPAWN=no;;
        --without-pollts) POLLTS=no;;
        --with-pollts) POLLTS=$var;;
+       --without-md5) MD5=no;;
        --without-dev) DEV=no;;
        --without-udev) UDEV=no;;
        --serviceexists) SERVICEEXISTS=$var;;
@@ -319,7 +320,7 @@ fi
 
 if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then
        echo "dhcpcd-definitions.conf will be embedded in dhcpcd itself"
-       echo "SRCS+=            dhcpcd-embedded.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     dhcpcd-embedded.c" >>$CONFIG_MK
 else
        echo "dhcpcd-definitions.conf will be installed to $LIBEXECDIR"
        echo "CFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK
@@ -335,36 +336,36 @@ case "$OS" in
 linux)
        echo "CPPFLAGS+=        -D_BSD_SOURCE -D_XOPEN_SOURCE=700" >>$CONFIG_MK
        if [ -z "$INET" -o "$INET" = yes ]; then
-               echo "SRCS+=            lpf.c" >>$CONFIG_MK
+               echo "DHCPCD_SRCS+=     lpf.c" >>$CONFIG_MK
        fi
-       echo "SRCS+=            if-linux.c if-linux-wireless.c" >>$CONFIG_MK
-       echo "SRCS+=            platform-linux.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     if-linux.c if-linux-wireless.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     platform-linux.c" >>$CONFIG_MK
        echo "LDADD+=           -lrt -ldl" >>$CONFIG_MK
        ;;
 kfreebsd)
        echo "CPPFLAGS+=        -D_GNU_SOURCE" >>$CONFIG_MK
        if [ -z "$INET" -o "$INET" = yes ]; then
-               echo "SRCS+=            bpf.c" >>$CONFIG_MK
+               echo "DHCPCD_SRCS+=             bpf.c" >>$CONFIG_MK
        fi
-       echo "SRCS+=            if-bsd.c platform-bsd.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     if-bsd.c platform-bsd.c" >>$CONFIG_MK
        echo "COMPAT_SRCS+=     compat/linkaddr.c" >>$CONFIG_MK
        echo "LDADD+=           -lrt -ldl" >>$CONFIG_MK
        ;;
 *)
        if [ -z "$INET" -o "$INET" = yes ]; then
-               echo "SRCS+=            bpf.c" >>$CONFIG_MK
+               echo "DHCPCD_SRCS+=             bpf.c" >>$CONFIG_MK
        fi
-       echo "SRCS+=            if-bsd.c platform-bsd.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     if-bsd.c platform-bsd.c" >>$CONFIG_MK
        ;;
 esac
 
 if [ -z "$INET" -o "$INET" = yes ]; then
        echo "CPPFLAGS+=        -DINET" >>$CONFIG_MK
-       echo "SRCS+=            arp.c dhcp.c ipv4.c ipv4ll.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     arp.c dhcp.c ipv4.c ipv4ll.c" >>$CONFIG_MK
 fi
 if [ -z "$INET6" -o "$INET6" = yes ]; then
        echo "CPPFLAGS+=        -DINET6" >>$CONFIG_MK
-       echo "SRCS+=            ipv6.c ipv6nd.c dhcp6.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     ipv6.c ipv6nd.c dhcp6.c" >>$CONFIG_MK
 fi
 
 # NetBSD: Even if we build for $PREFIX, the clueless user might move us to /
@@ -624,6 +625,32 @@ pselect)
        ;;
 esac
 
+if [ -z "$MD5" ]; then
+       printf "Testing for MD5Init ... "
+       cat <<EOF >_md5.c
+#include <md5.h>
+#include <stdlib.h>
+int main(void) {
+       MD5_CTX context;
+       MD5Init(&context);
+       return 0;
+}
+EOF
+       if $XCC _md5.c -o _md5 2>/dev/null; then
+               MD5=yes
+       else
+               MD5=no
+       fi
+       echo "$MD5"
+       rm -f _md5.c _md5
+fi
+if [ "$MD5" = no ]; then
+       echo "MD5_SRC=  md5.c" >>$CONFIG_MK
+else
+       echo "MD5_SRC=" >>$CONFIG_MK
+       echo "CPPFLAGS+=        -DHAVE_MD5H" >>$CONFIG_H
+fi
+
 if [ "$DEV" != no -a "$UDEV" != no ]; then
        printf "Checking for libudev ... "
        LIBUDEV_CFLAGS=$(pkg-config --cflags libudev 2>/dev/null)
@@ -678,7 +705,7 @@ elif [ "$DEV" != no -a "$UDEV" != no ]; then
 fi
 
 if [ "$DEV" = yes ]; then
-       echo "SRCS+=            dev.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     dev.c" >>$CONFIG_MK
        echo "CPPFLAGS+=        -DPLUGIN_DEV" >>$CONFIG_MK
        echo "MKDIRS+=  dev" >>$CONFIG_MK
 fi
@@ -772,3 +799,5 @@ echo "   RUNDIR =           $RUNDIR"
 echo "   MANDIR =              $MANDIR"
 echo "   HOOKSCRIPTS = $HOOKS"
 echo
+
+rm -f dhcpcd tests/test
diff --git a/crypt/crypt.h b/crypt/crypt.h
new file mode 100644 (file)
index 0000000..0a3932e
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef CRYPT_H
+#define CRYPT_H
+
+void hmac_md5(const uint8_t *, int, const uint8_t *, int, uint8_t *);
+
+#endif
diff --git a/crypt/hmac_md5.c b/crypt/hmac_md5.c
new file mode 100644 (file)
index 0000000..06ea465
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "crypt.h"
+
+#ifdef HAVE_MD5_H
+#include <md5.h>
+#else
+#include "md5.h"
+#endif
+
+#define HMAC_PAD_LEN   64
+#define IPAD           0x36
+#define OPAD           0x5C
+
+/* hmac_md5 as per RFC3118 */
+void
+hmac_md5(const uint8_t *text, int text_len,
+    const uint8_t *key, int key_len,
+    uint8_t *digest)
+{
+       uint8_t k_ipad[HMAC_PAD_LEN], k_opad[HMAC_PAD_LEN];
+       uint8_t tk[MD5_DIGEST_LENGTH];
+       int i;
+       MD5_CTX context;
+
+       /* Ensure key is no bigger than HMAC_PAD_LEN */
+       if (key_len > HMAC_PAD_LEN) {
+               MD5Init(&context);
+               MD5Update(&context, key, key_len);
+               MD5Final(tk, &context);
+               key = tk;
+               key_len = MD5_DIGEST_LENGTH;
+       }
+
+       /* store key in pads */
+       memcpy(k_ipad, key, key_len);
+       memcpy(k_opad, key, key_len);
+       memset(k_ipad + key_len, 0, sizeof(k_ipad) - key_len);
+       memset(k_opad + key_len, 0, sizeof(k_opad) - key_len);
+
+       /* XOR key with ipad and opad values */
+       for (i = 0; i < HMAC_PAD_LEN; i++) {
+               k_ipad[i] ^= IPAD;
+               k_opad[i] ^= OPAD;
+       }
+
+       /* inner MD5 */
+       MD5Init(&context);
+       MD5Update(&context, k_ipad, HMAC_PAD_LEN);
+       MD5Update(&context, text, text_len);
+       MD5Final(digest, &context);
+
+       /* outer MD5 */
+       MD5Init(&context);
+       MD5Update(&context, k_opad, HMAC_PAD_LEN);
+       MD5Update(&context, digest, MD5_DIGEST_LENGTH);
+       MD5Final(digest, &context);
+}
diff --git a/crypt/md5.c b/crypt/md5.c
new file mode 100644 (file)
index 0000000..9530b88
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include <sys/param.h>
+#include <inttypes.h>
+
+#include <string.h>
+
+#include "md5.h"
+
+#define PUT_64BIT_LE(cp, value) do {                                   \
+       (cp)[7] = (value) >> 56;                                        \
+       (cp)[6] = (value) >> 48;                                        \
+       (cp)[5] = (value) >> 40;                                        \
+       (cp)[4] = (value) >> 32;                                        \
+       (cp)[3] = (value) >> 24;                                        \
+       (cp)[2] = (value) >> 16;                                        \
+       (cp)[1] = (value) >> 8;                                         \
+       (cp)[0] = (value); } while (0)
+
+#define PUT_32BIT_LE(cp, value) do {                                   \
+       (cp)[3] = (value) >> 24;                                        \
+       (cp)[2] = (value) >> 16;                                        \
+       (cp)[1] = (value) >> 8;                                         \
+       (cp)[0] = (value); } while (0)
+
+static uint8_t PADDING[MD5_BLOCK_LENGTH] = {
+       0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(MD5_CTX *ctx)
+{
+       ctx->count = 0;
+       ctx->state[0] = 0x67452301;
+       ctx->state[1] = 0xefcdab89;
+       ctx->state[2] = 0x98badcfe;
+       ctx->state[3] = 0x10325476;
+}
+
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+       ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void
+MD5Transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH])
+{
+       uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4];
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+       memcpy(in, block, sizeof(in));
+#else
+       for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) {
+               in[a] = (uint32_t)(
+                   (uint32_t)(block[a * 4 + 0]) |
+                   (uint32_t)(block[a * 4 + 1]) <<  8 |
+                   (uint32_t)(block[a * 4 + 2]) << 16 |
+                   (uint32_t)(block[a * 4 + 3]) << 24);
+       }
+#endif
+
+       a = state[0];
+       b = state[1];
+       c = state[2];
+       d = state[3];
+
+       MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478,  7);
+       MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12);
+       MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17);
+       MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22);
+       MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf,  7);
+       MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12);
+       MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17);
+       MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22);
+       MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8,  7);
+       MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12);
+       MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+       MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+       MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122,  7);
+       MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+       MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+       MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+       MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562,  5);
+       MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340,  9);
+       MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+       MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20);
+       MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d,  5);
+       MD5STEP(F2, d, a, b, c, in[10] + 0x02441453,  9);
+       MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+       MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20);
+       MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6,  5);
+       MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6,  9);
+       MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14);
+       MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20);
+       MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905,  5);
+       MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8,  9);
+       MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14);
+       MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+       MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942,  4);
+       MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11);
+       MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+       MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+       MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44,  4);
+       MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11);
+       MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16);
+       MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+       MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6,  4);
+       MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11);
+       MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16);
+       MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23);
+       MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039,  4);
+       MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+       MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+       MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23);
+
+       MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244,  6);
+       MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10);
+       MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+       MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21);
+       MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3,  6);
+       MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10);
+       MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+       MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21);
+       MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f,  6);
+       MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+       MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15);
+       MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+       MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82,  6);
+       MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+       MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15);
+       MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21);
+
+       state[0] += a;
+       state[1] += b;
+       state[2] += c;
+       state[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len)
+{
+       size_t have, need;
+
+       /* Check how many bytes we already have and how many more we need. */
+       have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
+       need = MD5_BLOCK_LENGTH - have;
+
+       /* Update bitcount */
+       ctx->count += (uint64_t)len << 3;
+
+       if (len >= need) {
+               if (have != 0) {
+                       memcpy(ctx->buffer + have, input, need);
+                       MD5Transform(ctx->state, ctx->buffer);
+                       input += need;
+                       len -= need;
+                       have = 0;
+               }
+
+               /* Process data in MD5_BLOCK_LENGTH-byte chunks. */
+               while (len >= MD5_BLOCK_LENGTH) {
+                       MD5Transform(ctx->state, input);
+                       input += MD5_BLOCK_LENGTH;
+                       len -= MD5_BLOCK_LENGTH;
+               }
+       }
+
+       /* Handle any remaining bytes of data. */
+       if (len != 0)
+               memcpy(ctx->buffer + have, input, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx)
+{
+       uint8_t count[8];
+       size_t padlen;
+       int i;
+
+       /* Convert count to 8 bytes in little endian order. */
+       PUT_64BIT_LE(count, ctx->count);
+
+       /* Pad out to 56 mod 64. */
+       padlen = MD5_BLOCK_LENGTH -
+           ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
+       if (padlen < 1 + 8)
+               padlen += MD5_BLOCK_LENGTH;
+       MD5Update(ctx, PADDING, padlen - 8);            /* padlen - 8 <= 64 */
+       MD5Update(ctx, count, 8);
+
+       if (digest != NULL) {
+               for (i = 0; i < 4; i++)
+                       PUT_32BIT_LE(digest + i * 4, ctx->state[i]);
+       }
+       memset(ctx, 0, sizeof(*ctx));   /* in case it's sensitive */
+}
+
+
diff --git a/crypt/md5.h b/crypt/md5.h
new file mode 100644 (file)
index 0000000..402309c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#ifndef MD5_H_
+#define MD5_H_
+
+#define MD5_DIGEST_LENGTH      16
+#define MD5_BLOCK_LENGTH       64
+
+typedef struct MD5Context {
+       uint32_t state[4];      /* state (ABCD) */
+       uint64_t count;         /* number of bits, modulo 2^64 (lsb first) */
+       unsigned char buffer[MD5_BLOCK_LENGTH]; /* input buffer */
+} MD5_CTX;
+
+void   MD5Init(MD5_CTX *);
+void   MD5Update(MD5_CTX *, const unsigned char *, size_t);
+void   MD5Final(unsigned char[MD5_DIGEST_LENGTH], MD5_CTX *);
+#endif
diff --git a/defs.h b/defs.h
index cfc0164d047b99b10e670ad4929c43c4f4fc13ae..f477272e0883f4b9849bc72ba1ce29192ca9ebff 100644 (file)
--- a/defs.h
+++ b/defs.h
@@ -54,5 +54,8 @@
 #ifndef CONTROLSOCKET
 # define CONTROLSOCKET         RUNDIR "/" PACKAGE ".sock"
 #endif
+#ifndef RDM_MONOFILE
+# define RDM_MONOFILE          DBDIR "/" PACKAGE "-rdm.monotonic"
+#endif
 
 #endif
diff --git a/dhcp.c b/dhcp.c
index aa237690c65553bafe58cf806cd04598536b96f5..e0edf86d4296d7bebeee3c3bd379e2e6409940c2 100644 (file)
--- a/dhcp.c
+++ b/dhcp.c
@@ -673,11 +673,12 @@ make_message(struct dhcp_message **message,
     uint8_t type)
 {
        struct dhcp_message *dhcp;
-       uint8_t *m, *lp, *p;
+       uint8_t *m, *lp, *p, *auth;
        uint8_t *n_params = NULL;
        uint32_t ul;
        uint16_t sz;
        size_t len, i;
+       int auth_len;
        const struct dhcp_opt *opt;
        const struct if_options *ifo = iface->options;
        const struct dhcp_state *state = D_CSTATE(iface);
@@ -916,6 +917,27 @@ make_message(struct dhcp_message **message,
                }
                *n_params = p - n_params - 1;
        }
+
+       /* silence GCC */
+       auth_len = 0;
+       auth = NULL;
+
+       if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+               auth_len = dhcp_auth_encode(&ifo->auth, state->auth.token,
+                   NULL, 0, 4, type, NULL, 0);
+               if (auth_len > 0) {
+                       len = (p + auth_len) - m;
+                       if (auth_len > 255 || len > sizeof(*dhcp))
+                               goto toobig;
+                       *p++ = DHO_AUTHENTICATION;
+                       *p++ = (uint8_t)auth_len;
+                       auth = p;
+                       p += auth_len;
+               } else if (auth_len == -1)
+                       syslog(LOG_ERR, "%s: dhcp_auth_encode: %m",
+                           iface->name);
+       }
+
        *p++ = DHO_END;
 
 #ifdef BOOTP_MESSAGE_LENTH_MIN
@@ -926,8 +948,13 @@ make_message(struct dhcp_message **message,
                *p++ = DHO_PAD;
 #endif
 
+       len = p - m;
+       if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len > 0)
+               dhcp_auth_encode(&ifo->auth, state->auth.token,
+                   m, len, 4, type, auth, auth_len);
+
        *message = dhcp;
-       return p - m;
+       return len;
 
 toobig:
        syslog(LOG_ERR, "%s: DHCP messge too big", iface->name);
@@ -978,12 +1005,15 @@ write_lease(const struct interface *ifp, const struct dhcp_message *dhcp)
 }
 
 struct dhcp_message *
-read_lease(const struct interface *ifp)
+read_lease(struct interface *ifp)
 {
        int fd;
        struct dhcp_message *dhcp;
-       const struct dhcp_state *state = D_CSTATE(ifp);
+       struct dhcp_state *state = D_STATE(ifp);
        ssize_t bytes;
+       const uint8_t *auth;
+       uint8_t type;
+       int auth_len;
 
        fd = open(state->leasefile, O_RDONLY);
        if (fd == -1) {
@@ -1003,8 +1033,28 @@ read_lease(const struct interface *ifp)
        close(fd);
        if (bytes < 0) {
                free(dhcp);
-               dhcp = NULL;
+               return NULL;
        }
+
+       /* We may have found a BOOTP server */
+       if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1)
+               type = 0;
+       /* Authenticate the message */
+       auth = get_option(dhcp, DHO_AUTHENTICATION, &auth_len);
+       if (auth) {
+               if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+                   (uint8_t *)dhcp, sizeof(*dhcp), 4, type,
+                   auth, auth_len) == NULL)
+               {
+                       syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m",
+                           ifp->name);
+                       free(dhcp);
+                       return NULL;
+               }
+               syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+                   ifp->name, state->auth.token->secretid);
+       }
+
        return dhcp;
 }
 
@@ -1453,7 +1503,7 @@ send_message(struct interface *iface, int type,
                tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
                timernorm(&tv);
                syslog(LOG_DEBUG,
-                   "%s: sending %s (xid 0x%x), next in %0.2f seconds",
+                   "%s: sending %s (xid 0x%x), next in %0.1f seconds",
                    iface->name, get_dhcp_op(type), state->xid,
                    timeval_to_double(&tv));
        }
@@ -1989,6 +2039,10 @@ dhcp_drop(struct interface *ifp, const char *reason)
        state->old = NULL;
        state->lease.addr.s_addr = 0;
        ifp->options->options &= ~ DHCPCD_CSR_WARNED;
+       state->auth.token = NULL;
+       state->auth.replay = 0;
+       free(state->auth.reconf);
+       state->auth.reconf = NULL;
 }
 
 static void
@@ -2066,16 +2120,38 @@ dhcp_handledhcp(struct interface *iface, struct dhcp_message **dhcpp,
        struct dhcp_message *dhcp = *dhcpp;
        struct dhcp_lease *lease = &state->lease;
        uint8_t type, tmp;
+       const uint8_t *auth;
        struct in_addr addr;
        size_t i;
-
-       /* reset the message counter */
-       state->interval = 0;
+       int auth_len;
 
        /* We may have found a BOOTP server */
        if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1)
                type = 0;
 
+       /* Authenticate the message */
+       auth = get_option(dhcp, DHO_AUTHENTICATION, &auth_len);
+       if (auth) {
+               if (dhcp_auth_validate(&state->auth, &ifo->auth,
+                   (uint8_t *)*dhcpp, sizeof(**dhcpp), 4, type,
+                   auth, auth_len) == NULL)
+               {
+                       syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m",
+                           iface->name);
+                       log_dhcp(LOG_ERR, "authentication failed",
+                           iface, dhcp, from);
+                       return;
+               }
+               syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+                   iface->name, state->auth.token->secretid);
+       } else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+               log_dhcp(LOG_ERR, "missing authentiation", iface, dhcp, from);
+               return;
+       }
+
+       /* reset the message counter */
+       state->interval = 0;
+
        if (type == DHCP_NAK) {
                /* For NAK, only check if we require the ServerID */
                if (has_option_mask(ifo->requiremask, DHO_SERVERID) &&
diff --git a/dhcp.h b/dhcp.h
index 8aed958424d614d8a83ddbeb69c410811a63c34d..b1777f85620ffbabc3f91a439c03ca638d14fc01 100644 (file)
--- a/dhcp.h
+++ b/dhcp.h
@@ -1,6 +1,6 @@
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2013 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -34,6 +34,7 @@
 #include <limits.h>
 #include <stdint.h>
 
+#include "auth.h"
 #include "dhcp-common.h"
 
 /* UDP port numbers for DHCP */
@@ -105,6 +106,7 @@ enum DHO {
        DHO_USERCLASS              = 77,  /* RFC 3004 */
        DHO_RAPIDCOMMIT            = 80,  /* RFC 4039 */
        DHO_FQDN                   = 81,
+       DHO_AUTHENTICATION         = 90,  /* RFC 3118 */
        DHO_VIVCO                  = 124, /* RFC 3925 */
        DHO_VIVSO                  = 125, /* RFC 3925 */
        DHO_DNSSEARCH              = 119, /* RFC 3397 */
@@ -228,6 +230,8 @@ struct dhcp_state {
        time_t start_uptime;
 
        unsigned char *clientid;
+
+       struct authstate auth;
 };
 
 #define D_STATE(ifp)                                                          \
@@ -267,7 +271,7 @@ ssize_t make_message(struct dhcp_message **, const struct interface *,
 int valid_dhcp_packet(unsigned char *);
 
 ssize_t write_lease(const struct interface *, const struct dhcp_message *);
-struct dhcp_message *read_lease(const struct interface *);
+struct dhcp_message *read_lease(struct interface *);
 void get_lease(struct dhcp_lease *, const struct dhcp_message *);
 
 void dhcp_handleifa(int, struct interface *,
diff --git a/dhcp6.c b/dhcp6.c
index 8d4a22a3d2c84eec75ae0308e9c09872330e66cd..65039c84b6124a5ac3cf905f5e4b531d51ea88a4 100644 (file)
--- a/dhcp6.c
+++ b/dhcp6.c
@@ -356,8 +356,9 @@ dhcp6_makemessage(struct interface *ifp)
        const struct dhcp6_option *si, *unicast;
        ssize_t len, ml;
        size_t l;
+       int auth_len;
        uint8_t u8;
-       uint16_t *u16, n_options;
+       uint16_t *u16, n_options, type;
        const struct if_options *ifo;
        const struct dhcp_opt *opt;
        uint8_t IA, *p;
@@ -474,48 +475,55 @@ dhcp6_makemessage(struct interface *ifp)
                m = state->new;
                ml = state->new_len;
        }
-
-       state->send = malloc(len);
-       if (state->send == NULL)
-               return -1;
-
-       state->send_len = len;
        unicast = NULL;
        /* Depending on state, get the unicast address */
        switch(state->state) {
                break;
        case DH6S_INIT: /* FALLTHROUGH */
        case DH6S_DISCOVER:
-               state->send->type = DHCP6_SOLICIT;
+               type = DHCP6_SOLICIT;
                break;
        case DH6S_REQUEST:
-               state->send->type = DHCP6_REQUEST;
+               type = DHCP6_REQUEST;
                unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
                break;
        case DH6S_CONFIRM:
-               state->send->type = DHCP6_CONFIRM;
+               type = DHCP6_CONFIRM;
                break;
        case DH6S_REBIND:
-               state->send->type = DHCP6_REBIND;
+               type = DHCP6_REBIND;
                break;
        case DH6S_RENEW:
-               state->send->type = DHCP6_RENEW;
+               type = DHCP6_RENEW;
                unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
                break;
        case DH6S_INFORM:
-               state->send->type = DHCP6_INFORMATION_REQ;
+               type = DHCP6_INFORMATION_REQ;
                break;
        case DH6S_RELEASE:
-               state->send->type = DHCP6_RELEASE;
+               type = DHCP6_RELEASE;
                unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
                break;
        default:
                errno = EINVAL;
-               free(state->send);
-               state->send = NULL;
                return -1;
        }
 
+       if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+               auth_len = dhcp_auth_encode(&ifo->auth, state->auth.token,
+                   NULL, 0, 6, type, NULL, 0);
+               if (auth_len > 0)
+                       len += sizeof(*o) + auth_len;
+       } else
+               auth_len = 0; /* appease GCC */
+
+       state->send = malloc(len);
+       if (state->send == NULL)
+               return -1;
+
+       state->send_len = len;
+       state->send->type = type;
+
        /* If we found a unicast option, copy it to our state for sending */
        if (unicast && ntohs(unicast->len) == sizeof(state->unicast.s6_addr))
                memcpy(&state->unicast.s6_addr, D6_COPTION_DATA(unicast),
@@ -656,6 +664,23 @@ dhcp6_makemessage(struct interface *ifp)
                }
        }
 
+       /* This has to be the last option */
+       if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len > 0) {
+               o = D6_NEXT_OPTION(o);
+               o->code = htons(D6_OPTION_AUTH);
+               o->len = htons(auth_len);
+               if (dhcp_auth_encode(&ifo->auth, state->auth.token,
+                   (uint8_t *)state->send, state->send_len,
+                   6, state->send->type,
+                   D6_OPTION_DATA(o), auth_len) == -1) 
+               {
+                       printf ("oh dear\n");
+                       free(state->send);
+                       state->send = NULL;
+                       return -1;
+               }
+       }
+
        return 0;
 }
 
@@ -796,7 +821,7 @@ dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *))
 logsend:
                syslog(LOG_DEBUG,
                    "%s: %s %s (xid 0x%02x%02x%02x),"
-                   " next in %0.2f seconds",
+                   " next in %0.1f seconds",
                    ifp->name,
                    broad_uni,
                    dhcp6_get_op(state->send->type),
@@ -1052,6 +1077,7 @@ dhcp6_startrequest(struct interface *ifp)
                syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name);
                return;
        }
+
        dhcp6_sendrequest(ifp);
 }
 
@@ -1069,6 +1095,7 @@ dhcp6_startconfirm(struct interface *ifp)
        state->MRT = CNF_MAX_RT;
        state->MRC = 0;
 
+       syslog(LOG_INFO, "%s: confirming prior DHCPv6 lease", ifp->name);
        if (dhcp6_makemessage(ifp) == -1) {
                syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name);
                return;
@@ -1592,6 +1619,7 @@ dhcp6_readlease(struct interface *ifp)
        int fd;
        ssize_t bytes;
        struct timeval now;
+       const struct dhcp6_option *o;
 
        state = D6_STATE(ifp);
        if (stat(state->leasefile, &st) == -1) {
@@ -1634,6 +1662,27 @@ dhcp6_readlease(struct interface *ifp)
                }
        }
 
+       /* Authenticate the message */
+       o = dhcp6_getmoption(D6_OPTION_AUTH, state->new, state->new_len);
+       if (o) {
+               if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+                   (uint8_t *)state->new, state->new_len, 6, state->new->type,
+                   D6_COPTION_DATA(o), ntohs(o->len)) == NULL)
+               {
+                       syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m",
+                           ifp->name);
+                       syslog(LOG_ERR, "%s: authentication failed",
+                           ifp->name);
+                       goto ex;
+               }
+               syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+                   ifp->name, state->auth.token->secretid);
+       } else if (ifp->options->auth.options & DHCPCD_AUTH_REQUIRE) {
+               syslog(LOG_ERR, "%s: authentication now required", ifp->name);
+               goto ex;
+       } else
+               syslog(LOG_ERR, "eg");
+
        return fd;
 
 ex:
@@ -2065,6 +2114,26 @@ dhcp6_handledata(__unused void *arg)
                }
        }
 
+       /* Authenticate the message */
+       o = dhcp6_getmoption(D6_OPTION_AUTH, r, len);
+       if (o) {
+               if (dhcp_auth_validate(&state->auth, &ifo->auth,
+                   (uint8_t *)r, len, 6, r->type,
+                   D6_COPTION_DATA(o), ntohs(o->len)) == NULL)
+               {
+                       syslog(LOG_DEBUG, "dhcp_auth_validate: %m");
+                       syslog(LOG_ERR, "%s: authentication failed from %s",
+                           ifp->name, sfrom);
+                       return;
+               }
+               syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+                   ifp->name, state->auth.token->secretid);
+       } else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+               syslog(LOG_ERR, "%s: missing authentiation from %s",
+                   ifp->name, sfrom);
+               return;
+       }
+
        op = dhcp6_get_op(r->type);
        switch(r->type) {
        case DHCP6_REPLY:
@@ -2498,6 +2567,7 @@ dhcp6_freedrop(struct interface *ifp, int drop, const char *reason)
                free(state->recv);
                free(state->new);
                free(state->old);
+               free(state->auth.reconf);
                free(state);
                ifp->if_data[IF_DATA_DHCP6] = NULL;
        }
diff --git a/dhcp6.h b/dhcp6.h
index 120500c2f1afd4def315b5170cb6a3b90cb3b17e..dc2b527fc6aed6bb6b98366bef1d7af340dcf7f2 100644 (file)
--- a/dhcp6.h
+++ b/dhcp6.h
@@ -61,6 +61,7 @@
 #define D6_OPTION_IA_ADDR              5
 #define D6_OPTION_PREFERENCE           7
 #define D6_OPTION_ELAPSED              8
+#define D6_OPTION_AUTH                 11
 #define D6_OPTION_UNICAST              12
 #define D6_OPTION_STATUS_CODE          13
 #define D6_OPTION_RAPID_COMMIT         14
@@ -197,6 +198,8 @@ struct dhcp6_state {
        uint8_t sla_set;
        char leasefile[PATH_MAX];
        const char *reason;
+
+       struct authstate auth;
 };
 
 #define D6_STATE(ifp)                                                         \
index 9fdfb14618d13a6d9db1a7a7191db9746ace16e4..842608610ec989f66745f24679307b66ab365ef9 100644 (file)
@@ -111,6 +111,14 @@ define 87  string                  nds_context
 define 88      domain                  bcms_controller_names
 define 89      array ipaddress         bcms_controller_address
 
+# DHCP Authentication, RFC3118
+define 90      embed                   auth
+embed          byte                    protocol
+embed          byte                    algorithm
+embed          byte                    rdm
+embed          binhex:8                replay
+embed          binhex                  information
+
 # DHCP Leasequery, RFC4388
 define 91      uint32                  client_last_transaction_time
 define 92      array ipaddress         associated_ip
@@ -190,7 +198,8 @@ define6 9   binhex                  dhcp_relay_msg
 define6 11     embed                   auth
 embed          byte                    protocol
 embed          byte                    algorithm
-embed          binhex:8                replay_detection
+embed          byte                    rdm
+embed          binhex:8                replay
 embed          binhex                  information
 
 define6 12     ip6address              unicast
index 6c724ce9d453d68f965e3daba368f0a7e2271dcd..5a1c25034193c2474d5377620cd53ebddd55bd02 100644 (file)
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd January 18, 2014
+.Dd January 24, 2014
 .Dt DHCPCD 8
 .Os
 .Sh NAME
@@ -629,6 +629,10 @@ option described above.
 The actual DHCP message send by the server.
 We use this when reading the last
 lease and use the files mtime as when it was issued.
+.It Pa @DBDIR@/dhcpcd-rdm.monotonic
+Stores the monotonic counter used in the
+.Ar replay
+field in Authentication Options.
 .It Pa /var/run/dhcpcd.pid
 Stores the PID of
 .Nm
@@ -647,12 +651,26 @@ running on the
 .Xr dhcpcd-run-hooks 8 ,
 .Xr resolvconf 8
 .Sh STANDARDS
-RFC\ 951 RFC\ 1534 RFC\ 2131, RFC\ 2132, RFC\ 2855, RFC\ 3004, RFC\ 3315,
-RFC\ 3361, RFC\ 3633, RFC\ 3396, RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925,
-RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702,
-RFC\ 4074, RFC\ 4861, RFC\ 4833, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106.
+RFC\ 951, RFC\ 1534, RFC\ 2104, RFC\ 2131, RFC\ 2132, RFC\ 2855, RFC\ 3004,
+RFC\ 3118, RFC\ 3315, RFC\ 3361, RFC\ 3633, RFC\ 3396, RFC\ 3397, RFC\ 3442,
+RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361,
+RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833, RFC\ 5227, RFC\ 5942,
+RFC\ 5969, RFC\ 6106.
 .Sh AUTHORS
 .An Roy Marples Aq Mt roy@marples.name
 .Sh BUGS
 Please report them to
 .Lk http://roy.marples.name/projects/dhcpcd
+.Pp
+If authentication is used and the
+.Pa @DBDIR@/dhcpcd-rdm.monotonic
+file is removed or altered then the DHCP server will need it's notion
+of the last replay value
+.Nm
+sent reset.
+We could change this to use a NTP time stamp instead, but it's
+more likely the RTC on this host is broken which would cause the same result.
+.Pp
+WIDE DHCPv6 server sometimes fails to authenticate a
+.Nm
+message.
index d6dfa067a8199b449101f9754e0b2d9ab0d99850..70210f118fcd7294c3494e6d639809b613726438 100644 (file)
--- a/dhcpcd.c
+++ b/dhcpcd.c
@@ -460,6 +460,11 @@ configure_interface1(struct interface *ifp)
                }
        }
 #endif
+
+       /* If we are not sending an authentication option, don't require it */
+       if (!(ifo->auth.options & DHCPCD_AUTH_SEND))
+               ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
+
 }
 
 int
index 8834befa2c05eedb51cd5e33dfd38fb904124f8b..a901aa0dab816f102ab440ea69b78703ac50061b 100644 (file)
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd January 18, 2014
+.Dd January 24, 2014
 .Dt DHCPCD.CONF 5
 .Os
 .Sh NAME
@@ -69,6 +69,21 @@ Example:
 .Pp
 .D1 profile 192.168.0.1
 .D1 static ip_address=192.168.0.10/24
+.It Ic authprotocol Ar protocol Ar algorithm Ar rdm
+Authenticate DHCP messages.
+See the Supported Protocols section.
+.It Ic authtoken Ar secretid Ar realm Ar expire Ar key
+Define a shared key for use in authentication.
+.Ar realm can be "" to for use with the
+.Ar delayed
+prptocol.
+.Ar expire
+is the date the token expires and should be formatted "yyy-mm-dd HH:MM".
+You can use the keyword
+.Ar forever
+or
+.Ar 0
+which means the token never expires.
 .It Ic background
 Background immediately.
 This is useful for startup scripts which don't disable link messages for
@@ -302,6 +317,8 @@ alongside.
 .It Ic noarp
 Don't send any ARP requests.
 This also disables IPv4LL.
+.It Ic noauthrequired
+Don't require authentication even though we requested it.
 .It Ic nodev
 Don't load
 .Pa /dev
@@ -629,6 +646,40 @@ References an option from the global definition
 .D1 embed uint32 enterprise_number
 .D1 # Options defined for the enterprise number
 .D1 encap 1 ipaddress ipaddress
+.Ss Supported protocols
+.Bl -tag -width -indent
+.It Ic token
+Sends and expects the token with the secretid 0 in each message.
+.It Ic delayedrealm
+Delayed Authentication.
+.Nm dhcpcd
+will send an authentication option with no key or MAC.
+The server will see this option, and select a key for
+.Nm , writing the
+.Ar realm
+and
+.Ar secretid
+in it.
+.Nm dhcpcd
+will then look for a non-expired token with a matching realm and secretid.
+This token is used to authenicate all other messages.
+.It Ic delayed
+Same as above, but without a realm.
+.El
+.Ss Supported algorithms
+If none specified,
+.Ic hmac-md5
+is the default.
+.Bl -tag -width -indent
+.It Ic hmac-md5
+.El
+.Ss Supported Replay Detection Mechanisms
+If none specified,
+.Ic monotonic
+is the default.
+.Bl -tag -width -indent
+.It Ic monotonic
+.El
 .Sh SEE ALSO
 .Xr fnmatch 3 ,
 .Xr if_nametoindex 3 ,
index 2ec5778e5989cff9b09625bdd1f0bc668f166ce0..d30220e74ac43534a00ea0f184f9671f99383e36 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <sys/param.h>
 #include <sys/types.h>
+#include <sys/queue.h>
 
 #include <arpa/inet.h>
 
@@ -80,6 +81,9 @@ unsigned long long options = 0;
 #define O_ENCAP                        O_BASE + 22
 #define O_VENDOPT              O_BASE + 23
 #define O_VENDCLASS            O_BASE + 24
+#define O_AUTHPROTOCOL         O_BASE + 25
+#define O_AUTHTOKEN            O_BASE + 26
+#define O_AUTHNOTREQUIRED      O_BASE + 27
 
 char *dev_load;
 
@@ -155,6 +159,9 @@ const struct option cf_options[] = {
        {"encap",           required_argument, NULL, O_ENCAP},
        {"vendopt",         required_argument, NULL, O_VENDOPT},
        {"vendclass",       required_argument, NULL, O_VENDCLASS},
+       {"authprotocol",    required_argument, NULL, O_AUTHPROTOCOL},
+       {"authtoken",       required_argument, NULL, O_AUTHTOKEN},
+       {"noauthrequired",  no_argument,       NULL, O_AUTHNOTREQUIRED},
        {NULL,              0,                 NULL, '\0'}
 };
 
@@ -362,7 +369,7 @@ parse_string_hwaddr(char *sbuf, ssize_t slen, const char *str, int clid)
 }
 
 static int
-parse_iaid(uint8_t *iaid, const char *arg, size_t len)
+parse_iaid1(uint8_t *iaid, const char *arg, size_t len, int n)
 {
        unsigned long l;
        size_t s;
@@ -372,7 +379,10 @@ parse_iaid(uint8_t *iaid, const char *arg, size_t len)
        errno = 0;
        l = strtoul(arg, &np, 0);
        if (l <= (unsigned long)UINT32_MAX && errno == 0 && *np == '\0') {
-               u32 = htonl(l);
+               if (n)
+                       u32 = htonl(l);
+               else
+                       u32 = l;
                memcpy(iaid, &u32, sizeof(u32));
                return 0;
        }
@@ -390,6 +400,20 @@ parse_iaid(uint8_t *iaid, const char *arg, size_t len)
        return 0;
 }
 
+static int
+parse_iaid(uint8_t *iaid, const char *arg, size_t len)
+{
+
+       return parse_iaid1(iaid, arg, len, 1);
+}
+
+static int
+parse_uint32(uint32_t *i, const char *arg)
+{
+
+       return parse_iaid1((uint8_t *)i, arg, sizeof(uint32_t), 0);
+}
+
 static char **
 splitv(int *argc, char **argv, const char *arg)
 {
@@ -549,6 +573,28 @@ strskipwhite(const char *s)
        return UNCONST(s);
 }
 
+/* Find the end pointer of a string. */
+static char *
+strend(const char *s)
+{
+
+       s = strskipwhite(s);
+       if (s == NULL)
+               return NULL;
+       if (*s != '"')
+               return strchr(s, ' ');
+       s++;
+       for (; *s != '"' ; s++) {
+               if (*s == '\0')
+                       return NULL;
+               if (*s == '\\') {
+                       if (*(++s) == '\0')
+                               return NULL;
+               }
+       }
+       return UNCONST(++s);
+}
+
 static int
 parse_option(const char *ifname, struct if_options *ifo,
     int opt, const char *arg)
@@ -565,6 +611,7 @@ parse_option(const char *ifname, struct if_options *ifo,
        struct dhcp_opt **dop, *ndop;
        size_t *dop_len, dl;
        struct vivco *vivco;
+       struct token *token;
 #ifdef INET6
        size_t sl;
        struct if_ia *ia;
@@ -1521,6 +1568,135 @@ parse_option(const char *ifname, struct if_options *ifo,
                vivco->len = s;
                vivco->data = (uint8_t *)np;
                break;
+       case O_AUTHPROTOCOL:
+               fp = strwhite(arg);
+               if (fp)
+                       *fp++ = '\0';
+               if (strcasecmp(arg, "token") == 0)
+                       ifo->auth.protocol = AUTH_PROTO_TOKEN;
+               else if (strcasecmp(arg, "delayed") == 0)
+                       ifo->auth.protocol = AUTH_PROTO_DELAYED;
+               else if (strcasecmp(arg, "delayedrealm") == 0)
+                       ifo->auth.protocol = AUTH_PROTO_DELAYEDREALM;
+               else {
+                       syslog(LOG_ERR, "%s: unsupported protocol", arg);
+                       return -1;
+               }
+               arg = strskipwhite(fp);
+               fp = strwhite(arg);
+               if (arg == NULL) {
+                       ifo->auth.options |= DHCPCD_AUTH_SEND;
+                       ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+                       ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+                       break;
+               }
+               if (fp)
+                       *fp++ = '\0';
+               if (strcasecmp(arg, "hmacmd5") == 0 ||
+                   strcasecmp(arg, "hmac-md5") == 0)
+                       ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+               else {
+                       syslog(LOG_ERR, "%s: unsupported algorithm", arg);
+                       return 1;
+               }
+               arg = fp;
+               if (arg == NULL) {
+                       ifo->auth.options |= DHCPCD_AUTH_SEND;
+                       ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+                       break;
+               }
+               if (strcasecmp(arg, "monotonic") == 0)
+                       ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+               else {
+                       syslog(LOG_ERR, "%s: unsupported RDM", arg);
+                       return -1;
+               }
+               break;
+       case O_AUTHTOKEN:
+               fp = strwhite(arg);
+               if (fp == NULL) {
+                       syslog(LOG_ERR, "authtoken requires a realm");
+                       return -1;
+               }
+               *fp++ = '\0';
+               token = malloc(sizeof(*token));
+               if (token == NULL) {
+                       syslog(LOG_ERR, "%s: %m", __func__);
+                       return -1;
+               }
+               if (parse_uint32(&token->secretid, arg) == -1) {
+                       syslog(LOG_ERR, "%s: not a number", arg);
+                       free(token);
+                       return -1;
+               }
+               arg = fp;
+               fp = strend(arg);
+               if (fp == NULL) {
+                       syslog(LOG_ERR, "authtoken requies an a key");
+                       free(token);
+                       return -1;
+               }
+               *fp++ = '\0';
+               token->realm_len = parse_string(NULL, 0, arg);
+               if (token->realm_len) {
+                       token->realm = malloc(token->realm_len);
+                       if (token->realm == NULL) {
+                               free(token);
+                               syslog(LOG_ERR, "%s: %m", __func__);
+                               return -1;
+                       }
+                       parse_string((char *)token->realm, token->realm_len,
+                           arg);
+               }
+               arg = fp;
+               fp = strend(arg);
+               if (fp == NULL) {
+                       syslog(LOG_ERR, "authtoken requies an an expiry date");
+                       free(token->realm);
+                       free(token);
+                       return -1;
+               }
+               *fp++ = '\0';
+               if (*arg == '"') {
+                       arg++;
+                       np = strchr(arg, '"');
+                       if (np)
+                               *np = '\0';
+               }
+               if (strcmp(arg, "0") == 0 || strcasecmp(arg, "forever") == 0)
+                       token->expire =0;
+               else {
+                       struct tm tm;
+
+                       memset(&tm, 0, sizeof(tm));
+                       if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) {
+                               syslog(LOG_ERR, "%s: invalid date time", arg);
+                               free(token->realm);
+                               free(token);
+                               return -1;
+                       }
+                       if ((token->expire = mktime(&tm)) == (time_t)-1) {
+                               syslog(LOG_ERR, "%s: mktime: %m", __func__);
+                               free(token->realm);
+                               free(token);
+                               return -1;
+                       }
+               }
+               arg = fp;
+               token->key_len = parse_string(NULL, 0, arg);
+               if (token->key_len == 0) {
+                       syslog(LOG_ERR, "authtoken needs a key");
+                       free(token->realm);
+                       free(token);
+                       return -1;
+               }
+               token->key = malloc(token->key_len);
+               parse_string((char *)token->key, token->key_len, arg);
+               TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next);
+               break;
+       case O_AUTHNOTREQUIRED:
+               ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
+               break;
        default:
                return 0;
        }
@@ -1607,6 +1783,8 @@ read_config(const char *file,
        ifo->timeout = DEFAULT_TIMEOUT;
        ifo->reboot = DEFAULT_REBOOT;
        ifo->metric = -1;
+       ifo->auth.options |= DHCPCD_AUTH_REQUIRE;
+       TAILQ_INIT(&ifo->auth.tokens);
        strlcpy(ifo->script, SCRIPT, sizeof(ifo->script));
 
        ifo->vendorclassid[0] = strlen(vendor);
@@ -1802,6 +1980,7 @@ free_options(struct if_options *ifo)
        size_t i;
        struct dhcp_opt *opt;
        struct vivco *vo;
+       struct token *token;
 
        if (ifo) {
                if (ifo->environ) {
@@ -1848,6 +2027,13 @@ free_options(struct if_options *ifo)
 #endif
                free(ifo->ia);
 
+               while ((token = TAILQ_FIRST(&ifo->auth.tokens))) {
+                       TAILQ_REMOVE(&ifo->auth.tokens, token, next);
+                       if (token->realm_len)
+                               free(token->realm);
+                       free(token->key);
+                       free(token);
+               }
                free(ifo);
        }
 }
index e50f9867d00693d17d7586000ab8d9efdf2cc94d..c0a3e80c10c44702be7312941d371f39f7b852fb 100644 (file)
@@ -36,6 +36,8 @@
 #include <limits.h>
 #include <stdint.h>
 
+#include "auth.h"
+
 /* Don't set any optional arguments here so we retain POSIX
  * compatibility with getopt */
 #define IF_OPTS "46bc:de:f:gh:i:kl:m:no:pqr:s:t:u:v:wxy:z:ABC:DEF:GHI:JKLO:Q:S:TUVW:X:Z:"
@@ -175,6 +177,8 @@ struct if_options {
        size_t vivco_len;
        struct dhcp_opt *vivso_override;
        size_t vivso_override_len;
+
+       struct auth auth;
 };
 
 extern unsigned long long options;
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..5f3881c
--- /dev/null
@@ -0,0 +1,33 @@
+include ../config.mk
+
+PROG=          test
+SRCS=          test.c
+SRCS+=         test_hmac_md5.c hmac_md5.c ${MD5_SRC}
+
+CFLAGS?=       -O2
+CSTD?=         c99
+CFLAGS+=       -std=${CSTD}
+
+CPPFLAGS+=     -I../crypt
+
+.PATH:         ../crypt
+
+VPATH=         . ../crypt
+
+OBJS+=         ${SRCS:.c=.o}
+
+.c.o:
+       ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@
+
+all: ${PROG}
+
+clean:
+       rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
+
+.depend: ${SRCS} ${COMPAT_SRCS}
+       ${CC} ${CPPFLAGS} -MM ${SRCS} ${COMPAT_SRCS} > .depend
+
+depend: .depend
+
+${PROG}: ${DEPEND} ${OBJS}
+       ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
diff --git a/test/test.c b/test/test.c
new file mode 100644 (file)
index 0000000..fd8d5b4
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "test.h"
+
+int main(void)
+{
+       int r = 0;
+
+       if (test_hmac_md5())
+               r = -1;
+
+       return r;
+}
diff --git a/test/test.h b/test/test.h
new file mode 100644 (file)
index 0000000..0ca6182
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef TEST_H
+
+int test_hmac_md5(void);
+
+#endif
diff --git a/test/test_hmac_md5.c b/test/test_hmac_md5.c
new file mode 100644 (file)
index 0000000..1da71a2
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include "../crypt/crypt.h"
+#include "test.h"
+
+/* RFC2202 MD5 implementation */
+
+static void
+print_hmac(uint8_t *hmac)
+{
+       int i;
+
+       printf("digest = 0x");
+       for (i = 0; i < 16; i++)
+               printf("%02x", *hmac++);
+       printf("\n");
+}
+
+static void
+hmac_md5_test1(void)
+{
+       uint8_t hmac[16];
+       const uint8_t text[] = "Hi There";
+       uint8_t key[16];
+       int i;
+
+       printf ("HMAC MD5 Test 1:\t\t");
+       for (i = 0; i < 16; i++)
+               key[i] = 0x0b;
+       hmac_md5(text, 8, key, 16, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x9294727a3638bb1c13f48ef8158bfc9d\n");
+}
+
+static void
+hmac_md5_test2(void)
+{
+       uint8_t hmac[16];
+       const uint8_t text[] = "what do ya want for nothing?";
+       const uint8_t key[] = "Jefe";
+
+       printf("HMAC MD5 Test 2:\t\t");
+       hmac_md5(text, 28, key, 4, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x750c783e6ab0b503eaa863e10a5db738\n");
+}
+
+static void
+hmac_md5_test3(void)
+{
+       uint8_t hmac[16];
+       uint8_t text[50];
+       uint8_t key[16];
+       int i;
+
+       printf ("HMAC MD5 Test 3:\t\t");
+       for (i = 0; i < 50; i++)
+               text[i] = 0xdd;
+       for (i = 0; i < 16; i++)
+               key[i] = 0xaa;
+       hmac_md5(text, 50, key, 16, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x56be34521d144c88dbb8c733f0e8b3f6\n");
+}
+
+static void
+hmac_md5_test4(void)
+{
+       uint8_t hmac[16];
+       uint8_t text[50];
+       uint8_t key[25];
+       int i;
+
+       printf ("HMAC MD5 Test 4:\t\t");
+       for (i = 0; i < 50; i++)
+               text[i] = 0xcd;
+       for (i = 0; i < 25; i++)
+               key[i] = i + 1;
+       hmac_md5(text, 50, key, 25, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x697eaf0aca3a3aea3a75164746ffaa79\n");
+}
+
+static void
+hmac_md5_test5(void)
+{
+       uint8_t hmac[16];
+       const uint8_t text[] = "Test With Truncation";
+       uint8_t key[16];
+       int i;
+
+       printf ("HMAC MD5 Test 5:\t\t");
+       for (i = 0; i < 16; i++)
+               key[i] = 0x0c;
+       hmac_md5(text, 20, key, 16, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x56461ef2342edc00f9bab995690efd4c\n");
+}
+
+static void
+hmac_md5_test6(void)
+{
+       uint8_t hmac[16];
+       const uint8_t text[] = "Test Using Larger Than Block-Size Key - Hash Key First";
+       uint8_t key[80];
+       int i;
+
+       printf ("HMAC MD5 Test 6:\t\t");
+       for (i = 0; i < 80; i++)
+               key[i] = 0xaa;
+       hmac_md5(text, 54, key, 80, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd\n");
+}
+
+static void
+hmac_md5_test7(void)
+{
+       uint8_t hmac[16];
+       const uint8_t text[] = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data";
+       uint8_t key[80];
+       int i;
+
+       printf ("HMAC MD5 Test 7:\t\t");
+       for (i = 0; i < 80; i++)
+               key[i] = 0xaa;
+       hmac_md5(text, 73, key, 80, hmac);
+       print_hmac(hmac);
+       printf("\t\texpected result:\t 0x6f630fad67cda0ee1fb1f562db3aa53e\n");
+}
+
+int test_hmac_md5(void)
+{
+
+       printf ("Starting HMAC MD5 tests...\n\n");
+       hmac_md5_test1();
+       hmac_md5_test2();
+       hmac_md5_test3();
+       hmac_md5_test4();
+       hmac_md5_test5();
+       hmac_md5_test6();
+       hmac_md5_test7();
+       printf("\nConfirm above results visually against RFC 2202.\n");
+       return 0;
+}