xref: /dflybsd-src/contrib/dhcpcd/src/auth.c (revision c80c9bba1b2fa2824af94c686145cb7eb7db2cd5)
18d36e1dfSRoy Marples /* SPDX-License-Identifier: BSD-2-Clause */
27827cba2SAaron LI /*
37827cba2SAaron LI  * dhcpcd - DHCP client daemon
4*80aa9461SRoy Marples  * Copyright (c) 2006-2023 Roy Marples <roy@marples.name>
57827cba2SAaron LI  * All rights reserved
67827cba2SAaron LI 
77827cba2SAaron LI  * Redistribution and use in source and binary forms, with or without
87827cba2SAaron LI  * modification, are permitted provided that the following conditions
97827cba2SAaron LI  * are met:
107827cba2SAaron LI  * 1. Redistributions of source code must retain the above copyright
117827cba2SAaron LI  *    notice, this list of conditions and the following disclaimer.
127827cba2SAaron LI  * 2. Redistributions in binary form must reproduce the above copyright
137827cba2SAaron LI  *    notice, this list of conditions and the following disclaimer in the
147827cba2SAaron LI  *    documentation and/or other materials provided with the distribution.
157827cba2SAaron LI  *
167827cba2SAaron LI  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
177827cba2SAaron LI  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
187827cba2SAaron LI  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
197827cba2SAaron LI  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
207827cba2SAaron LI  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
217827cba2SAaron LI  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
227827cba2SAaron LI  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
237827cba2SAaron LI  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
247827cba2SAaron LI  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
257827cba2SAaron LI  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
267827cba2SAaron LI  * SUCH DAMAGE.
277827cba2SAaron LI  */
287827cba2SAaron LI 
297827cba2SAaron LI #include <sys/file.h>
30acd7a309SRoy Marples #include <sys/stat.h>
31acd7a309SRoy Marples 
327827cba2SAaron LI #include <errno.h>
337827cba2SAaron LI #include <fcntl.h>
347827cba2SAaron LI #include <inttypes.h>
357827cba2SAaron LI #include <stddef.h>
368d36e1dfSRoy Marples #include <stdio.h>
377827cba2SAaron LI #include <stdlib.h>
387827cba2SAaron LI #include <string.h>
397827cba2SAaron LI #include <time.h>
407827cba2SAaron LI #include <unistd.h>
417827cba2SAaron LI 
427827cba2SAaron LI #include "config.h"
437827cba2SAaron LI #include "auth.h"
447827cba2SAaron LI #include "dhcp.h"
457827cba2SAaron LI #include "dhcp6.h"
467827cba2SAaron LI #include "dhcpcd.h"
47acd7a309SRoy Marples #include "privsep-root.h"
487827cba2SAaron LI 
497827cba2SAaron LI #ifdef HAVE_HMAC_H
507827cba2SAaron LI #include <hmac.h>
517827cba2SAaron LI #endif
527827cba2SAaron LI 
537827cba2SAaron LI #ifdef __sun
547827cba2SAaron LI #define htonll
557827cba2SAaron LI #define ntohll
567827cba2SAaron LI #endif
577827cba2SAaron LI 
587827cba2SAaron LI #ifndef htonll
597827cba2SAaron LI #if (BYTE_ORDER == LITTLE_ENDIAN)
607827cba2SAaron LI #define	htonll(x)	((uint64_t)htonl((uint32_t)((x) >> 32)) | \
617827cba2SAaron LI 			 (uint64_t)htonl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
627827cba2SAaron LI #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
637827cba2SAaron LI #define	htonll(x)	(x)
647827cba2SAaron LI #endif
657827cba2SAaron LI #endif  /* htonll */
667827cba2SAaron LI 
677827cba2SAaron LI #ifndef ntohll
687827cba2SAaron LI #if (BYTE_ORDER == LITTLE_ENDIAN)
697827cba2SAaron LI #define	ntohll(x)	((uint64_t)ntohl((uint32_t)((x) >> 32)) | \
707827cba2SAaron LI 			 (uint64_t)ntohl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
717827cba2SAaron LI #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
727827cba2SAaron LI #define	ntohll(x)	(x)
737827cba2SAaron LI #endif
747827cba2SAaron LI #endif  /* ntohll */
757827cba2SAaron LI 
767827cba2SAaron LI #define HMAC_LENGTH	16
777827cba2SAaron LI 
787827cba2SAaron LI void
dhcp_auth_reset(struct authstate * state)797827cba2SAaron LI dhcp_auth_reset(struct authstate *state)
807827cba2SAaron LI {
817827cba2SAaron LI 
827827cba2SAaron LI 	state->replay = 0;
837827cba2SAaron LI 	if (state->token) {
847827cba2SAaron LI 		free(state->token->key);
857827cba2SAaron LI 		free(state->token->realm);
867827cba2SAaron LI 		free(state->token);
877827cba2SAaron LI 		state->token = NULL;
887827cba2SAaron LI 	}
897827cba2SAaron LI 	if (state->reconf) {
907827cba2SAaron LI 		free(state->reconf->key);
917827cba2SAaron LI 		free(state->reconf->realm);
927827cba2SAaron LI 		free(state->reconf);
937827cba2SAaron LI 		state->reconf = NULL;
947827cba2SAaron LI 	}
957827cba2SAaron LI }
967827cba2SAaron LI 
977827cba2SAaron LI /*
987827cba2SAaron LI  * Authenticate a DHCP message.
997827cba2SAaron LI  * m and mlen refer to the whole message.
1007827cba2SAaron LI  * t is the DHCP type, pass it 4 or 6.
1017827cba2SAaron LI  * data and dlen refer to the authentication option within the message.
1027827cba2SAaron LI  */
1037827cba2SAaron LI const struct token *
dhcp_auth_validate(struct authstate * state,const struct auth * auth,const void * vm,size_t mlen,int mp,int mt,const void * vdata,size_t dlen)1047827cba2SAaron LI dhcp_auth_validate(struct authstate *state, const struct auth *auth,
1057827cba2SAaron LI     const void *vm, size_t mlen, int mp,  int mt,
1067827cba2SAaron LI     const void *vdata, size_t dlen)
1077827cba2SAaron LI {
1087827cba2SAaron LI 	const uint8_t *m, *data;
1097827cba2SAaron LI 	uint8_t protocol, algorithm, rdm, *mm, type;
1107827cba2SAaron LI 	uint64_t replay;
1117827cba2SAaron LI 	uint32_t secretid;
1127827cba2SAaron LI 	const uint8_t *d, *realm;
1137827cba2SAaron LI 	size_t realm_len;
1147827cba2SAaron LI 	const struct token *t;
1157827cba2SAaron LI 	time_t now;
1167827cba2SAaron LI 	uint8_t hmac_code[HMAC_LENGTH];
1177827cba2SAaron LI 
1187827cba2SAaron LI 	if (dlen < 3 + sizeof(replay)) {
1197827cba2SAaron LI 		errno = EINVAL;
1207827cba2SAaron LI 		return NULL;
1217827cba2SAaron LI 	}
1227827cba2SAaron LI 
1237827cba2SAaron LI 	m = vm;
1247827cba2SAaron LI 	data = vdata;
1258d36e1dfSRoy Marples 	/* Ensure that d is inside m which *may* not be the case for DHCPv4.
1268d36e1dfSRoy Marples 	 * This can occur if the authentication option is split using
1278d36e1dfSRoy Marples 	 * DHCP long option from RFC 3399. Section 9 which does infact note that
1288d36e1dfSRoy Marples 	 * implementations should take this into account.
1298d36e1dfSRoy Marples 	 * Fixing this would be problematic, patches welcome. */
1307827cba2SAaron LI 	if (data < m || data > m + mlen || data + dlen > m + mlen) {
1317827cba2SAaron LI 		errno = ERANGE;
1327827cba2SAaron LI 		return NULL;
1337827cba2SAaron LI 	}
1347827cba2SAaron LI 
1357827cba2SAaron LI 	d = data;
1367827cba2SAaron LI 	protocol = *d++;
1377827cba2SAaron LI 	algorithm = *d++;
1387827cba2SAaron LI 	rdm = *d++;
1397827cba2SAaron LI 	if (!(auth->options & DHCPCD_AUTH_SEND)) {
1407827cba2SAaron LI 		/* If we didn't send any authorisation, it can only be a
1417827cba2SAaron LI 		 * reconfigure key */
1427827cba2SAaron LI 		if (protocol != AUTH_PROTO_RECONFKEY) {
1437827cba2SAaron LI 			errno = EINVAL;
1447827cba2SAaron LI 			return NULL;
1457827cba2SAaron LI 		}
1467827cba2SAaron LI 	} else if (protocol != auth->protocol ||
1477827cba2SAaron LI 		    algorithm != auth->algorithm ||
1487827cba2SAaron LI 		    rdm != auth->rdm)
1497827cba2SAaron LI 	{
1507827cba2SAaron LI 		/* As we don't require authentication, we should still
1517827cba2SAaron LI 		 * accept a reconfigure key */
1527827cba2SAaron LI 		if (protocol != AUTH_PROTO_RECONFKEY ||
1537827cba2SAaron LI 		    auth->options & DHCPCD_AUTH_REQUIRE)
1547827cba2SAaron LI 		{
1557827cba2SAaron LI 			errno = EPERM;
1567827cba2SAaron LI 			return NULL;
1577827cba2SAaron LI 		}
1587827cba2SAaron LI 	}
1597827cba2SAaron LI 	dlen -= 3;
1607827cba2SAaron LI 
1617827cba2SAaron LI 	memcpy(&replay, d, sizeof(replay));
1627827cba2SAaron LI 	replay = ntohll(replay);
1637827cba2SAaron LI 	/*
1647827cba2SAaron LI 	 * Test for a replay attack.
1657827cba2SAaron LI 	 *
1667827cba2SAaron LI 	 * NOTE: Some servers always send a replay data value of zero.
1677827cba2SAaron LI 	 * This is strictly compliant with RFC 3315 and 3318 which say:
1687827cba2SAaron LI 	 * "If the RDM field contains 0x00, the replay detection field MUST be
1697827cba2SAaron LI 	 *    set to the value of a monotonically increasing counter."
1707827cba2SAaron LI 	 * An example of a monotonically increasing sequence is:
1717827cba2SAaron LI 	 * 1, 2, 2, 2, 2, 2, 2
1727827cba2SAaron LI 	 * Errata 3474 updates RFC 3318 to say:
1737827cba2SAaron LI 	 * "If the RDM field contains 0x00, the replay detection field MUST be
1747827cba2SAaron LI 	 *    set to the value of a strictly increasing counter."
1757827cba2SAaron LI 	 *
1767827cba2SAaron LI 	 * Taking the above into account, dhcpcd will only test for
1777827cba2SAaron LI 	 * strictly speaking replay attacks if it receives any non zero
1787827cba2SAaron LI 	 * replay data to validate against.
1797827cba2SAaron LI 	 */
1807827cba2SAaron LI 	if (state->token && state->replay != 0) {
1817827cba2SAaron LI 		if (state->replay == (replay ^ 0x8000000000000000ULL)) {
1827827cba2SAaron LI 			/* We don't know if the singular point is increasing
1837827cba2SAaron LI 			 * or decreasing. */
1847827cba2SAaron LI 			errno = EPERM;
1857827cba2SAaron LI 			return NULL;
1867827cba2SAaron LI 		}
1877827cba2SAaron LI 		if ((uint64_t)(replay - state->replay) <= 0) {
1887827cba2SAaron LI 			/* Replay attack detected */
1897827cba2SAaron LI 			errno = EPERM;
1907827cba2SAaron LI 			return NULL;
1917827cba2SAaron LI 		}
1927827cba2SAaron LI 	}
1937827cba2SAaron LI 	d+= sizeof(replay);
1947827cba2SAaron LI 	dlen -= sizeof(replay);
1957827cba2SAaron LI 
1967827cba2SAaron LI 	realm = NULL;
1977827cba2SAaron LI 	realm_len = 0;
1987827cba2SAaron LI 
1997827cba2SAaron LI 	/* Extract realm and secret.
2007827cba2SAaron LI 	 * Rest of data is MAC. */
2017827cba2SAaron LI 	switch (protocol) {
2027827cba2SAaron LI 	case AUTH_PROTO_TOKEN:
2037827cba2SAaron LI 		secretid = auth->token_rcv_secretid;
2047827cba2SAaron LI 		break;
2057827cba2SAaron LI 	case AUTH_PROTO_DELAYED:
2067827cba2SAaron LI 		if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
2077827cba2SAaron LI 			errno = EINVAL;
2087827cba2SAaron LI 			return NULL;
2097827cba2SAaron LI 		}
2107827cba2SAaron LI 		memcpy(&secretid, d, sizeof(secretid));
2117827cba2SAaron LI 		secretid = ntohl(secretid);
2127827cba2SAaron LI 		d += sizeof(secretid);
2137827cba2SAaron LI 		dlen -= sizeof(secretid);
2147827cba2SAaron LI 		break;
2157827cba2SAaron LI 	case AUTH_PROTO_DELAYEDREALM:
2167827cba2SAaron LI 		if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
2177827cba2SAaron LI 			errno = EINVAL;
2187827cba2SAaron LI 			return NULL;
2197827cba2SAaron LI 		}
2207827cba2SAaron LI 		realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code));
2217827cba2SAaron LI 		if (realm_len) {
2227827cba2SAaron LI 			realm = d;
2237827cba2SAaron LI 			d += realm_len;
2247827cba2SAaron LI 			dlen -= realm_len;
2257827cba2SAaron LI 		}
2267827cba2SAaron LI 		memcpy(&secretid, d, sizeof(secretid));
2277827cba2SAaron LI 		secretid = ntohl(secretid);
2287827cba2SAaron LI 		d += sizeof(secretid);
2297827cba2SAaron LI 		dlen -= sizeof(secretid);
2307827cba2SAaron LI 		break;
2317827cba2SAaron LI 	case AUTH_PROTO_RECONFKEY:
2327827cba2SAaron LI 		if (dlen != 1 + 16) {
2337827cba2SAaron LI 			errno = EINVAL;
2347827cba2SAaron LI 			return NULL;
2357827cba2SAaron LI 		}
2367827cba2SAaron LI 		type = *d++;
2377827cba2SAaron LI 		dlen--;
2387827cba2SAaron LI 		switch (type) {
2397827cba2SAaron LI 		case 1:
2407827cba2SAaron LI 			if ((mp == 4 && mt == DHCP_ACK) ||
2417827cba2SAaron LI 			    (mp == 6 && mt == DHCP6_REPLY))
2427827cba2SAaron LI 			{
2437827cba2SAaron LI 				if (state->reconf == NULL) {
2447827cba2SAaron LI 					state->reconf =
2457827cba2SAaron LI 					    malloc(sizeof(*state->reconf));
2467827cba2SAaron LI 					if (state->reconf == NULL)
2477827cba2SAaron LI 						return NULL;
2487827cba2SAaron LI 					state->reconf->key = malloc(16);
2497827cba2SAaron LI 					if (state->reconf->key == NULL) {
2507827cba2SAaron LI 						free(state->reconf);
2517827cba2SAaron LI 						state->reconf = NULL;
2527827cba2SAaron LI 						return NULL;
2537827cba2SAaron LI 					}
2547827cba2SAaron LI 					state->reconf->secretid = 0;
2557827cba2SAaron LI 					state->reconf->expire = 0;
2567827cba2SAaron LI 					state->reconf->realm = NULL;
2577827cba2SAaron LI 					state->reconf->realm_len = 0;
2587827cba2SAaron LI 					state->reconf->key_len = 16;
2597827cba2SAaron LI 				}
2607827cba2SAaron LI 				memcpy(state->reconf->key, d, 16);
2617827cba2SAaron LI 			} else {
2627827cba2SAaron LI 				errno = EINVAL;
2637827cba2SAaron LI 				return NULL;
2647827cba2SAaron LI 			}
2657827cba2SAaron LI 			if (state->reconf == NULL)
2667827cba2SAaron LI 				errno = ENOENT;
2677827cba2SAaron LI 			/* Free the old token so we log acceptance */
2687827cba2SAaron LI 			if (state->token) {
2697827cba2SAaron LI 				free(state->token);
2707827cba2SAaron LI 				state->token = NULL;
2717827cba2SAaron LI 			}
2727827cba2SAaron LI 			/* Nothing to validate, just accepting the key */
2737827cba2SAaron LI 			return state->reconf;
2747827cba2SAaron LI 		case 2:
2757827cba2SAaron LI 			if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
2767827cba2SAaron LI 			    (mp == 6 && mt == DHCP6_RECONFIGURE)))
2777827cba2SAaron LI 			{
2787827cba2SAaron LI 				errno = EINVAL;
2797827cba2SAaron LI 				return NULL;
2807827cba2SAaron LI 			}
2817827cba2SAaron LI 			if (state->reconf == NULL) {
2827827cba2SAaron LI 				errno = ENOENT;
2837827cba2SAaron LI 				return NULL;
2847827cba2SAaron LI 			}
2857827cba2SAaron LI 			t = state->reconf;
2867827cba2SAaron LI 			goto gottoken;
2877827cba2SAaron LI 		default:
2887827cba2SAaron LI 			errno = EINVAL;
2897827cba2SAaron LI 			return NULL;
2907827cba2SAaron LI 		}
2917827cba2SAaron LI 	default:
2927827cba2SAaron LI 		errno = ENOTSUP;
2937827cba2SAaron LI 		return NULL;
2947827cba2SAaron LI 	}
2957827cba2SAaron LI 
2967827cba2SAaron LI 	/* Find a token for the realm and secret */
2977827cba2SAaron LI 	TAILQ_FOREACH(t, &auth->tokens, next) {
2987827cba2SAaron LI 		if (t->secretid == secretid &&
2997827cba2SAaron LI 		    t->realm_len == realm_len &&
3007827cba2SAaron LI 		    (t->realm_len == 0 ||
3017827cba2SAaron LI 		    memcmp(t->realm, realm, t->realm_len) == 0))
3027827cba2SAaron LI 			break;
3037827cba2SAaron LI 	}
3047827cba2SAaron LI 	if (t == NULL) {
3057827cba2SAaron LI 		errno = ESRCH;
3067827cba2SAaron LI 		return NULL;
3077827cba2SAaron LI 	}
3087827cba2SAaron LI 	if (t->expire) {
3097827cba2SAaron LI 		if (time(&now) == -1)
3107827cba2SAaron LI 			return NULL;
3117827cba2SAaron LI 		if (t->expire < now) {
3127827cba2SAaron LI 			errno = EFAULT;
3137827cba2SAaron LI 			return NULL;
3147827cba2SAaron LI 		}
3157827cba2SAaron LI 	}
3167827cba2SAaron LI 
3177827cba2SAaron LI gottoken:
3187827cba2SAaron LI 	/* First message from the server */
3197827cba2SAaron LI 	if (state->token &&
3207827cba2SAaron LI 	    (state->token->secretid != t->secretid ||
3217827cba2SAaron LI 	    state->token->realm_len != t->realm_len ||
3227827cba2SAaron LI 	    memcmp(state->token->realm, t->realm, t->realm_len)))
3237827cba2SAaron LI 	{
3247827cba2SAaron LI 		errno = EPERM;
3257827cba2SAaron LI 		return NULL;
3267827cba2SAaron LI 	}
3277827cba2SAaron LI 
3287827cba2SAaron LI 	/* Special case as no hashing needs to be done. */
3297827cba2SAaron LI 	if (protocol == AUTH_PROTO_TOKEN) {
3307827cba2SAaron LI 		if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
3317827cba2SAaron LI 			errno = EPERM;
3327827cba2SAaron LI 			return NULL;
3337827cba2SAaron LI 		}
3347827cba2SAaron LI 		goto finish;
3357827cba2SAaron LI 	}
3367827cba2SAaron LI 
3377827cba2SAaron LI 	/* Make a duplicate of the message, but zero out the MAC part */
3387827cba2SAaron LI 	mm = malloc(mlen);
3397827cba2SAaron LI 	if (mm == NULL)
3407827cba2SAaron LI 		return NULL;
3417827cba2SAaron LI 	memcpy(mm, m, mlen);
3427827cba2SAaron LI 	memset(mm + (d - m), 0, dlen);
3437827cba2SAaron LI 
3447827cba2SAaron LI 	/* RFC3318, section 5.2 - zero giaddr and hops */
3457827cba2SAaron LI 	if (mp == 4) {
3467827cba2SAaron LI 		/* Assert the bootp structure is correct size. */
3477827cba2SAaron LI 		__CTASSERT(sizeof(struct bootp) == 300);
3487827cba2SAaron LI 
3497827cba2SAaron LI 		*(mm + offsetof(struct bootp, hops)) = '\0';
3507827cba2SAaron LI 		memset(mm + offsetof(struct bootp, giaddr), 0, 4);
3517827cba2SAaron LI 	}
3527827cba2SAaron LI 
3537827cba2SAaron LI 	memset(hmac_code, 0, sizeof(hmac_code));
3547827cba2SAaron LI 	switch (algorithm) {
3557827cba2SAaron LI 	case AUTH_ALG_HMAC_MD5:
3567827cba2SAaron LI 		hmac("md5", t->key, t->key_len, mm, mlen,
3577827cba2SAaron LI 		     hmac_code, sizeof(hmac_code));
3587827cba2SAaron LI 		break;
3597827cba2SAaron LI 	default:
3607827cba2SAaron LI 		errno = ENOSYS;
3617827cba2SAaron LI 		free(mm);
3627827cba2SAaron LI 		return NULL;
3637827cba2SAaron LI 	}
3647827cba2SAaron LI 
3657827cba2SAaron LI 	free(mm);
3668d36e1dfSRoy Marples 	if (!consttime_memequal(d, &hmac_code, dlen)) {
3677827cba2SAaron LI 		errno = EPERM;
3687827cba2SAaron LI 		return NULL;
3697827cba2SAaron LI 	}
3707827cba2SAaron LI 
3717827cba2SAaron LI finish:
3727827cba2SAaron LI 	/* If we got here then authentication passed */
3737827cba2SAaron LI 	state->replay = replay;
3747827cba2SAaron LI 	if (state->token == NULL) {
3757827cba2SAaron LI 		/* We cannot just save a pointer because a reconfigure will
3767827cba2SAaron LI 		 * recreate the token list. So we duplicate it. */
3777827cba2SAaron LI 		state->token = malloc(sizeof(*state->token));
3787827cba2SAaron LI 		if (state->token) {
3797827cba2SAaron LI 			state->token->secretid = t->secretid;
3807827cba2SAaron LI 			state->token->key = malloc(t->key_len);
3817827cba2SAaron LI 			if (state->token->key) {
3827827cba2SAaron LI 				state->token->key_len = t->key_len;
3837827cba2SAaron LI 				memcpy(state->token->key, t->key, t->key_len);
3847827cba2SAaron LI 			} else {
3857827cba2SAaron LI 				free(state->token);
3867827cba2SAaron LI 				state->token = NULL;
3877827cba2SAaron LI 				return NULL;
3887827cba2SAaron LI 			}
3897827cba2SAaron LI 			if (t->realm_len) {
3907827cba2SAaron LI 				state->token->realm = malloc(t->realm_len);
3917827cba2SAaron LI 				if (state->token->realm) {
3927827cba2SAaron LI 					state->token->realm_len = t->realm_len;
3937827cba2SAaron LI 					memcpy(state->token->realm, t->realm,
3947827cba2SAaron LI 					    t->realm_len);
3957827cba2SAaron LI 				} else {
3967827cba2SAaron LI 					free(state->token->key);
3977827cba2SAaron LI 					free(state->token);
3987827cba2SAaron LI 					state->token = NULL;
3997827cba2SAaron LI 					return NULL;
4007827cba2SAaron LI 				}
4017827cba2SAaron LI 			} else {
4027827cba2SAaron LI 				state->token->realm = NULL;
4037827cba2SAaron LI 				state->token->realm_len = 0;
4047827cba2SAaron LI 			}
4057827cba2SAaron LI 		}
4067827cba2SAaron LI 		/* If we cannot save the token, we must invalidate */
4077827cba2SAaron LI 		if (state->token == NULL)
4087827cba2SAaron LI 			return NULL;
4097827cba2SAaron LI 	}
4107827cba2SAaron LI 
4117827cba2SAaron LI 	return t;
4127827cba2SAaron LI }
4137827cba2SAaron LI 
414acd7a309SRoy Marples int
auth_get_rdm_monotonic(uint64_t * rdm)415acd7a309SRoy Marples auth_get_rdm_monotonic(uint64_t *rdm)
4167827cba2SAaron LI {
4177827cba2SAaron LI 	FILE *fp;
418acd7a309SRoy Marples 	int err;
4197827cba2SAaron LI #ifdef LOCK_EX
4207827cba2SAaron LI 	int flocked;
4217827cba2SAaron LI #endif
4227827cba2SAaron LI 
4237827cba2SAaron LI 	fp = fopen(RDM_MONOFILE, "r+");
4247827cba2SAaron LI 	if (fp == NULL) {
4257827cba2SAaron LI 		if (errno != ENOENT)
426acd7a309SRoy Marples 			return -1;
4277827cba2SAaron LI 		fp = fopen(RDM_MONOFILE, "w");
4287827cba2SAaron LI 		if (fp == NULL)
429acd7a309SRoy Marples 			return -1;
430acd7a309SRoy Marples 		if (chmod(RDM_MONOFILE, 0400) == -1) {
431acd7a309SRoy Marples 			fclose(fp);
432acd7a309SRoy Marples 			unlink(RDM_MONOFILE);
433acd7a309SRoy Marples 			return -1;
434acd7a309SRoy Marples 		}
4357827cba2SAaron LI #ifdef LOCK_EX
4367827cba2SAaron LI 		flocked = flock(fileno(fp), LOCK_EX);
4377827cba2SAaron LI #endif
438acd7a309SRoy Marples 		*rdm = 0;
4397827cba2SAaron LI 	} else {
4407827cba2SAaron LI #ifdef LOCK_EX
4417827cba2SAaron LI 		flocked = flock(fileno(fp), LOCK_EX);
4427827cba2SAaron LI #endif
443acd7a309SRoy Marples 		if (fscanf(fp, "0x%016" PRIu64, rdm) != 1) {
444acd7a309SRoy Marples 			fclose(fp);
445acd7a309SRoy Marples 			return -1;
446acd7a309SRoy Marples 		}
4477827cba2SAaron LI 	}
4487827cba2SAaron LI 
449acd7a309SRoy Marples 	(*rdm)++;
4507827cba2SAaron LI 	if (fseek(fp, 0, SEEK_SET) == -1 ||
4517827cba2SAaron LI 	    ftruncate(fileno(fp), 0) == -1 ||
452acd7a309SRoy Marples 	    fprintf(fp, "0x%016" PRIu64 "\n", *rdm) != 19 ||
4537827cba2SAaron LI 	    fflush(fp) == EOF)
454acd7a309SRoy Marples 		err = -1;
455acd7a309SRoy Marples 	else
456acd7a309SRoy Marples 		err = 0;
4577827cba2SAaron LI #ifdef LOCK_EX
4587827cba2SAaron LI 	if (flocked == 0)
4597827cba2SAaron LI 		flock(fileno(fp), LOCK_UN);
4607827cba2SAaron LI #endif
4617827cba2SAaron LI 	fclose(fp);
462acd7a309SRoy Marples 	return err;
4637827cba2SAaron LI }
4647827cba2SAaron LI 
4657827cba2SAaron LI #define	NTP_EPOCH	2208988800U	/* 1970 - 1900 in seconds */
4667827cba2SAaron LI #define	NTP_SCALE_FRAC	4294967295.0	/* max value of the fractional part */
4677827cba2SAaron LI static uint64_t
get_next_rdm_monotonic_clock(struct auth * auth)4687827cba2SAaron LI get_next_rdm_monotonic_clock(struct auth *auth)
4697827cba2SAaron LI {
4707827cba2SAaron LI 	struct timespec ts;
4717827cba2SAaron LI 	uint64_t secs, rdm;
4727827cba2SAaron LI 	double frac;
4737827cba2SAaron LI 
4747827cba2SAaron LI 	if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
4757827cba2SAaron LI 		return ++auth->last_replay; /* report error? */
4767827cba2SAaron LI 
4777827cba2SAaron LI 	secs = (uint64_t)ts.tv_sec + NTP_EPOCH;
4787827cba2SAaron LI 	frac = ((double)ts.tv_nsec / 1e9 * NTP_SCALE_FRAC);
4797827cba2SAaron LI 	rdm = (secs << 32) | (uint64_t)frac;
4807827cba2SAaron LI 	return rdm;
4817827cba2SAaron LI }
4827827cba2SAaron LI 
4837827cba2SAaron LI static uint64_t
get_next_rdm_monotonic(struct dhcpcd_ctx * ctx,struct auth * auth)484acd7a309SRoy Marples get_next_rdm_monotonic(struct dhcpcd_ctx *ctx, struct auth *auth)
4857827cba2SAaron LI {
486acd7a309SRoy Marples #ifndef PRIVSEP
487acd7a309SRoy Marples 	UNUSED(ctx);
488acd7a309SRoy Marples #endif
4897827cba2SAaron LI 
490acd7a309SRoy Marples 	if (auth->options & DHCPCD_AUTH_RDM_COUNTER) {
491acd7a309SRoy Marples 		uint64_t rdm;
492acd7a309SRoy Marples 		int err;
493acd7a309SRoy Marples 
494acd7a309SRoy Marples #ifdef PRIVSEP
495acd7a309SRoy Marples 		if (IN_PRIVSEP(ctx)) {
496acd7a309SRoy Marples 
497acd7a309SRoy Marples 			err = ps_root_getauthrdm(ctx, &rdm);
498acd7a309SRoy Marples 		} else
499acd7a309SRoy Marples #endif
500acd7a309SRoy Marples 			err = auth_get_rdm_monotonic(&rdm);
501acd7a309SRoy Marples 		if (err == -1)
502acd7a309SRoy Marples 			return ++auth->last_replay;
503acd7a309SRoy Marples 
504acd7a309SRoy Marples 		auth->last_replay = rdm;
505acd7a309SRoy Marples 		return rdm;
506acd7a309SRoy Marples 	}
5077827cba2SAaron LI 	return get_next_rdm_monotonic_clock(auth);
5087827cba2SAaron LI }
5097827cba2SAaron LI 
5107827cba2SAaron LI /*
5117827cba2SAaron LI  * Encode a DHCP message.
5127827cba2SAaron LI  * Either we know which token to use from the server response
5137827cba2SAaron LI  * or we are using a basic configuration token.
5147827cba2SAaron LI  * token is the token to encrypt with.
5157827cba2SAaron LI  * m and mlen refer to the whole message.
5167827cba2SAaron LI  * mp is the DHCP type, pass it 4 or 6.
5177827cba2SAaron LI  * mt is the DHCP message type.
5187827cba2SAaron LI  * data and dlen refer to the authentication option within the message.
5197827cba2SAaron LI  */
5207827cba2SAaron LI ssize_t
dhcp_auth_encode(struct dhcpcd_ctx * ctx,struct auth * auth,const struct token * t,void * vm,size_t mlen,int mp,int mt,void * vdata,size_t dlen)521acd7a309SRoy Marples dhcp_auth_encode(struct dhcpcd_ctx *ctx, struct auth *auth,
522acd7a309SRoy Marples     const struct token *t,
5237827cba2SAaron LI     void *vm, size_t mlen, int mp, int mt,
5247827cba2SAaron LI     void *vdata, size_t dlen)
5257827cba2SAaron LI {
5267827cba2SAaron LI 	uint64_t rdm;
5277827cba2SAaron LI 	uint8_t hmac_code[HMAC_LENGTH];
5287827cba2SAaron LI 	time_t now;
5297827cba2SAaron LI 	uint8_t hops, *p, *m, *data;
5307827cba2SAaron LI 	uint32_t giaddr, secretid;
5317827cba2SAaron LI 	bool auth_info;
5327827cba2SAaron LI 
5337827cba2SAaron LI 	/* Ignore the token argument given to us - always send using the
5347827cba2SAaron LI 	 * configured token. */
5357827cba2SAaron LI 	if (auth->protocol == AUTH_PROTO_TOKEN) {
5367827cba2SAaron LI 		TAILQ_FOREACH(t, &auth->tokens, next) {
5377827cba2SAaron LI 			if (t->secretid == auth->token_snd_secretid)
5387827cba2SAaron LI 				break;
5397827cba2SAaron LI 		}
5407827cba2SAaron LI 		if (t == NULL) {
5417827cba2SAaron LI 			errno = EINVAL;
5427827cba2SAaron LI 			return -1;
5437827cba2SAaron LI 		}
5447827cba2SAaron LI 		if (t->expire) {
5457827cba2SAaron LI 			if (time(&now) == -1)
5467827cba2SAaron LI 				return -1;
5477827cba2SAaron LI 			if (t->expire < now) {
5487827cba2SAaron LI 				errno = EPERM;
5497827cba2SAaron LI 				return -1;
5507827cba2SAaron LI 			}
5517827cba2SAaron LI 		}
5527827cba2SAaron LI 	}
5537827cba2SAaron LI 
5547827cba2SAaron LI 	switch(auth->protocol) {
5557827cba2SAaron LI 	case AUTH_PROTO_TOKEN:
5567827cba2SAaron LI 	case AUTH_PROTO_DELAYED:
5577827cba2SAaron LI 	case AUTH_PROTO_DELAYEDREALM:
5587827cba2SAaron LI 		/* We don't ever send a reconf key */
5597827cba2SAaron LI 		break;
5607827cba2SAaron LI 	default:
5617827cba2SAaron LI 		errno = ENOTSUP;
5627827cba2SAaron LI 		return -1;
5637827cba2SAaron LI 	}
5647827cba2SAaron LI 
5657827cba2SAaron LI 	switch(auth->algorithm) {
5667827cba2SAaron LI 	case AUTH_ALG_NONE:
5677827cba2SAaron LI 	case AUTH_ALG_HMAC_MD5:
5687827cba2SAaron LI 		break;
5697827cba2SAaron LI 	default:
5707827cba2SAaron LI 		errno = ENOTSUP;
5717827cba2SAaron LI 		return -1;
5727827cba2SAaron LI 	}
5737827cba2SAaron LI 
5747827cba2SAaron LI 	switch(auth->rdm) {
5757827cba2SAaron LI 	case AUTH_RDM_MONOTONIC:
5767827cba2SAaron LI 		break;
5777827cba2SAaron LI 	default:
5787827cba2SAaron LI 		errno = ENOTSUP;
5797827cba2SAaron LI 		return -1;
5807827cba2SAaron LI 	}
5817827cba2SAaron LI 
5827827cba2SAaron LI 	/* DISCOVER or INFORM messages don't write auth info */
5837827cba2SAaron LI 	if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
5847827cba2SAaron LI 	    (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
5857827cba2SAaron LI 		auth_info = false;
5867827cba2SAaron LI 	else
5877827cba2SAaron LI 		auth_info = true;
5887827cba2SAaron LI 
5897827cba2SAaron LI 	/* Work out the auth area size.
5907827cba2SAaron LI 	 * We only need to do this for DISCOVER messages */
5917827cba2SAaron LI 	if (vdata == NULL) {
5927827cba2SAaron LI 		dlen = 1 + 1 + 1 + 8;
5937827cba2SAaron LI 		switch(auth->protocol) {
5947827cba2SAaron LI 		case AUTH_PROTO_TOKEN:
5957827cba2SAaron LI 			dlen += t->key_len;
5967827cba2SAaron LI 			break;
5977827cba2SAaron LI 		case AUTH_PROTO_DELAYEDREALM:
5987827cba2SAaron LI 			if (auth_info && t)
5997827cba2SAaron LI 				dlen += t->realm_len;
6007827cba2SAaron LI 			/* FALLTHROUGH */
6017827cba2SAaron LI 		case AUTH_PROTO_DELAYED:
6027827cba2SAaron LI 			if (auth_info && t)
6037827cba2SAaron LI 				dlen += sizeof(t->secretid) + sizeof(hmac_code);
6047827cba2SAaron LI 			break;
6057827cba2SAaron LI 		}
6067827cba2SAaron LI 		return (ssize_t)dlen;
6077827cba2SAaron LI 	}
6087827cba2SAaron LI 
6097827cba2SAaron LI 	if (dlen < 1 + 1 + 1 + 8) {
6107827cba2SAaron LI 		errno = ENOBUFS;
6117827cba2SAaron LI 		return -1;
6127827cba2SAaron LI 	}
6137827cba2SAaron LI 
6147827cba2SAaron LI 	/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
6157827cba2SAaron LI 	m = vm;
6167827cba2SAaron LI 	data = vdata;
6177827cba2SAaron LI 	if (data < m || data > m + mlen || data + dlen > m + mlen) {
6187827cba2SAaron LI 		errno = ERANGE;
6197827cba2SAaron LI 		return -1;
6207827cba2SAaron LI 	}
6217827cba2SAaron LI 
6227827cba2SAaron LI 	/* Write out our option */
6237827cba2SAaron LI 	*data++ = auth->protocol;
6247827cba2SAaron LI 	*data++ = auth->algorithm;
6257827cba2SAaron LI 	/*
6267827cba2SAaron LI 	 * RFC 3315 21.4.4.1 says that SOLICIT in DELAYED authentication
6277827cba2SAaron LI 	 * should not set RDM or it's data.
6287827cba2SAaron LI 	 * An expired draft draft-ietf-dhc-dhcpv6-clarify-auth-01 suggets
6297827cba2SAaron LI 	 * this should not be set for INFORMATION REQ messages as well,
6307827cba2SAaron LI 	 * which is probably a good idea because both states start from zero.
6317827cba2SAaron LI 	 */
6327827cba2SAaron LI 	if (auth_info ||
6337827cba2SAaron LI 	    !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM)))
6347827cba2SAaron LI 	{
6357827cba2SAaron LI 		*data++ = auth->rdm;
6367827cba2SAaron LI 		switch (auth->rdm) {
6377827cba2SAaron LI 		case AUTH_RDM_MONOTONIC:
638acd7a309SRoy Marples 			rdm = get_next_rdm_monotonic(ctx, auth);
6397827cba2SAaron LI 			break;
6407827cba2SAaron LI 		default:
6417827cba2SAaron LI 			/* This block appeases gcc, clang doesn't need it */
642acd7a309SRoy Marples 			rdm = get_next_rdm_monotonic(ctx, auth);
6437827cba2SAaron LI 			break;
6447827cba2SAaron LI 		}
6457827cba2SAaron LI 		rdm = htonll(rdm);
6467827cba2SAaron LI 		memcpy(data, &rdm, 8);
6477827cba2SAaron LI 	} else {
6487827cba2SAaron LI 		*data++ = 0;		/* rdm */
6497827cba2SAaron LI 		memset(data, 0, 8);	/* replay detection data */
6507827cba2SAaron LI 	}
6517827cba2SAaron LI 	data += 8;
6527827cba2SAaron LI 	dlen -= 1 + 1 + 1 + 8;
6537827cba2SAaron LI 
6547827cba2SAaron LI 	/* Special case as no hashing needs to be done. */
6557827cba2SAaron LI 	if (auth->protocol == AUTH_PROTO_TOKEN) {
6567827cba2SAaron LI 		/* Should be impossible, but still */
6577827cba2SAaron LI 		if (t == NULL) {
6587827cba2SAaron LI 			errno = EINVAL;
6597827cba2SAaron LI 			return -1;
6607827cba2SAaron LI 		}
6617827cba2SAaron LI 		if (dlen < t->key_len) {
6627827cba2SAaron LI 			errno =	ENOBUFS;
6637827cba2SAaron LI 			return -1;
6647827cba2SAaron LI 		}
6657827cba2SAaron LI 		memcpy(data, t->key, t->key_len);
6667827cba2SAaron LI 		return (ssize_t)(dlen - t->key_len);
6677827cba2SAaron LI 	}
6687827cba2SAaron LI 
6697827cba2SAaron LI 	/* DISCOVER or INFORM messages don't write auth info */
6707827cba2SAaron LI 	if (!auth_info)
6717827cba2SAaron LI 		return (ssize_t)dlen;
6727827cba2SAaron LI 
6737827cba2SAaron LI 	/* Loading a saved lease without an authentication option */
6747827cba2SAaron LI 	if (t == NULL)
6757827cba2SAaron LI 		return 0;
6767827cba2SAaron LI 
6777827cba2SAaron LI 	/* Write out the Realm */
6787827cba2SAaron LI 	if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
6797827cba2SAaron LI 		if (dlen < t->realm_len) {
6807827cba2SAaron LI 			errno = ENOBUFS;
6817827cba2SAaron LI 			return -1;
6827827cba2SAaron LI 		}
6837827cba2SAaron LI 		memcpy(data, t->realm, t->realm_len);
6847827cba2SAaron LI 		data += t->realm_len;
6857827cba2SAaron LI 		dlen -= t->realm_len;
6867827cba2SAaron LI 	}
6877827cba2SAaron LI 
6887827cba2SAaron LI 	/* Write out the SecretID */
6897827cba2SAaron LI 	if (auth->protocol == AUTH_PROTO_DELAYED ||
6907827cba2SAaron LI 	    auth->protocol == AUTH_PROTO_DELAYEDREALM)
6917827cba2SAaron LI 	{
6927827cba2SAaron LI 		if (dlen < sizeof(t->secretid)) {
6937827cba2SAaron LI 			errno = ENOBUFS;
6947827cba2SAaron LI 			return -1;
6957827cba2SAaron LI 		}
6967827cba2SAaron LI 		secretid = htonl(t->secretid);
6977827cba2SAaron LI 		memcpy(data, &secretid, sizeof(secretid));
6987827cba2SAaron LI 		data += sizeof(secretid);
6997827cba2SAaron LI 		dlen -= sizeof(secretid);
7007827cba2SAaron LI 	}
7017827cba2SAaron LI 
7027827cba2SAaron LI 	/* Zero what's left, the MAC */
7037827cba2SAaron LI 	memset(data, 0, dlen);
7047827cba2SAaron LI 
7057827cba2SAaron LI 	/* RFC3318, section 5.2 - zero giaddr and hops */
7067827cba2SAaron LI 	if (mp == 4) {
7077827cba2SAaron LI 		p = m + offsetof(struct bootp, hops);
7087827cba2SAaron LI 		hops = *p;
7097827cba2SAaron LI 		*p = '\0';
7107827cba2SAaron LI 		p = m + offsetof(struct bootp, giaddr);
7117827cba2SAaron LI 		memcpy(&giaddr, p, sizeof(giaddr));
7127827cba2SAaron LI 		memset(p, 0, sizeof(giaddr));
7137827cba2SAaron LI 	} else {
7147827cba2SAaron LI 		/* appease GCC again */
7157827cba2SAaron LI 		hops = 0;
7167827cba2SAaron LI 		giaddr = 0;
7177827cba2SAaron LI 	}
7187827cba2SAaron LI 
7197827cba2SAaron LI 	/* Create our hash and write it out */
7207827cba2SAaron LI 	switch(auth->algorithm) {
7217827cba2SAaron LI 	case AUTH_ALG_HMAC_MD5:
7227827cba2SAaron LI 		hmac("md5", t->key, t->key_len, m, mlen,
7237827cba2SAaron LI 		     hmac_code, sizeof(hmac_code));
7247827cba2SAaron LI 		memcpy(data, hmac_code, sizeof(hmac_code));
7257827cba2SAaron LI 		break;
7267827cba2SAaron LI 	}
7277827cba2SAaron LI 
7287827cba2SAaron LI 	/* RFC3318, section 5.2 - restore giaddr and hops */
7297827cba2SAaron LI 	if (mp == 4) {
7307827cba2SAaron LI 		p = m + offsetof(struct bootp, hops);
7317827cba2SAaron LI 		*p = hops;
7327827cba2SAaron LI 		p = m + offsetof(struct bootp, giaddr);
7337827cba2SAaron LI 		memcpy(p, &giaddr, sizeof(giaddr));
7347827cba2SAaron LI 	}
7357827cba2SAaron LI 
7367827cba2SAaron LI 	/* Done! */
7377827cba2SAaron LI 	return (int)(dlen - sizeof(hmac_code)); /* should be zero */
7387827cba2SAaron LI }
739