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