1 /* SPDX-License-Identifier: BSD-2-Clause */
3 * dhcpcd - DHCP client daemon
4 * Copyright (c) 2006-2019 Roy Marples <roy@marples.name>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56 #if (BYTE_ORDER == LITTLE_ENDIAN)
57 #define htonll(x) ((uint64_t)htonl((uint32_t)((x) >> 32)) | \
58 (uint64_t)htonl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
59 #else /* (BYTE_ORDER == LITTLE_ENDIAN) */
65 #if (BYTE_ORDER == LITTLE_ENDIAN)
66 #define ntohll(x) ((uint64_t)ntohl((uint32_t)((x) >> 32)) | \
67 (uint64_t)ntohl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
68 #else /* (BYTE_ORDER == LITTLE_ENDIAN) */
73 #define HMAC_LENGTH 16
76 dhcp_auth_reset(struct authstate *state)
81 free(state->token->key);
82 free(state->token->realm);
87 free(state->reconf->key);
88 free(state->reconf->realm);
95 * Authenticate a DHCP message.
96 * m and mlen refer to the whole message.
97 * t is the DHCP type, pass it 4 or 6.
98 * data and dlen refer to the authentication option within the message.
101 dhcp_auth_validate(struct authstate *state, const struct auth *auth,
102 const void *vm, size_t mlen, int mp, int mt,
103 const void *vdata, size_t dlen)
105 const uint8_t *m, *data;
106 uint8_t protocol, algorithm, rdm, *mm, type;
109 const uint8_t *d, *realm;
111 const struct token *t;
113 uint8_t hmac_code[HMAC_LENGTH];
115 if (dlen < 3 + sizeof(replay)) {
122 /* Ensure that d is inside m which *may* not be the case for DHCPv4.
123 * This can occur if the authentication option is split using
124 * DHCP long option from RFC 3399. Section 9 which does infact note that
125 * implementations should take this into account.
126 * Fixing this would be problematic, patches welcome. */
127 if (data < m || data > m + mlen || data + dlen > m + mlen) {
136 if (!(auth->options & DHCPCD_AUTH_SEND)) {
137 /* If we didn't send any authorisation, it can only be a
139 if (protocol != AUTH_PROTO_RECONFKEY) {
143 } else if (protocol != auth->protocol ||
144 algorithm != auth->algorithm ||
147 /* As we don't require authentication, we should still
148 * accept a reconfigure key */
149 if (protocol != AUTH_PROTO_RECONFKEY ||
150 auth->options & DHCPCD_AUTH_REQUIRE)
158 memcpy(&replay, d, sizeof(replay));
159 replay = ntohll(replay);
161 * Test for a replay attack.
163 * NOTE: Some servers always send a replay data value of zero.
164 * This is strictly compliant with RFC 3315 and 3318 which say:
165 * "If the RDM field contains 0x00, the replay detection field MUST be
166 * set to the value of a monotonically increasing counter."
167 * An example of a monotonically increasing sequence is:
168 * 1, 2, 2, 2, 2, 2, 2
169 * Errata 3474 updates RFC 3318 to say:
170 * "If the RDM field contains 0x00, the replay detection field MUST be
171 * set to the value of a strictly increasing counter."
173 * Taking the above into account, dhcpcd will only test for
174 * strictly speaking replay attacks if it receives any non zero
175 * replay data to validate against.
177 if (state->token && state->replay != 0) {
178 if (state->replay == (replay ^ 0x8000000000000000ULL)) {
179 /* We don't know if the singular point is increasing
184 if ((uint64_t)(replay - state->replay) <= 0) {
185 /* Replay attack detected */
191 dlen -= sizeof(replay);
196 /* Extract realm and secret.
197 * Rest of data is MAC. */
199 case AUTH_PROTO_TOKEN:
200 secretid = auth->token_rcv_secretid;
202 case AUTH_PROTO_DELAYED:
203 if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
207 memcpy(&secretid, d, sizeof(secretid));
208 secretid = ntohl(secretid);
209 d += sizeof(secretid);
210 dlen -= sizeof(secretid);
212 case AUTH_PROTO_DELAYEDREALM:
213 if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
217 realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code));
223 memcpy(&secretid, d, sizeof(secretid));
224 secretid = ntohl(secretid);
225 d += sizeof(secretid);
226 dlen -= sizeof(secretid);
228 case AUTH_PROTO_RECONFKEY:
229 if (dlen != 1 + 16) {
237 if ((mp == 4 && mt == DHCP_ACK) ||
238 (mp == 6 && mt == DHCP6_REPLY))
240 if (state->reconf == NULL) {
242 malloc(sizeof(*state->reconf));
243 if (state->reconf == NULL)
245 state->reconf->key = malloc(16);
246 if (state->reconf->key == NULL) {
248 state->reconf = NULL;
251 state->reconf->secretid = 0;
252 state->reconf->expire = 0;
253 state->reconf->realm = NULL;
254 state->reconf->realm_len = 0;
255 state->reconf->key_len = 16;
257 memcpy(state->reconf->key, d, 16);
262 if (state->reconf == NULL)
264 /* Free the old token so we log acceptance */
269 /* Nothing to validate, just accepting the key */
270 return state->reconf;
272 if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
273 (mp == 6 && mt == DHCP6_RECONFIGURE)))
278 if (state->reconf == NULL) {
293 /* Find a token for the realm and secret */
294 TAILQ_FOREACH(t, &auth->tokens, next) {
295 if (t->secretid == secretid &&
296 t->realm_len == realm_len &&
297 (t->realm_len == 0 ||
298 memcmp(t->realm, realm, t->realm_len) == 0))
306 if (time(&now) == -1)
308 if (t->expire < now) {
315 /* First message from the server */
317 (state->token->secretid != t->secretid ||
318 state->token->realm_len != t->realm_len ||
319 memcmp(state->token->realm, t->realm, t->realm_len)))
325 /* Special case as no hashing needs to be done. */
326 if (protocol == AUTH_PROTO_TOKEN) {
327 if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
334 /* Make a duplicate of the message, but zero out the MAC part */
339 memset(mm + (d - m), 0, dlen);
341 /* RFC3318, section 5.2 - zero giaddr and hops */
343 /* Assert the bootp structure is correct size. */
344 __CTASSERT(sizeof(struct bootp) == 300);
346 *(mm + offsetof(struct bootp, hops)) = '\0';
347 memset(mm + offsetof(struct bootp, giaddr), 0, 4);
350 memset(hmac_code, 0, sizeof(hmac_code));
352 case AUTH_ALG_HMAC_MD5:
353 hmac("md5", t->key, t->key_len, mm, mlen,
354 hmac_code, sizeof(hmac_code));
363 if (!consttime_memequal(d, &hmac_code, dlen)) {
369 /* If we got here then authentication passed */
370 state->replay = replay;
371 if (state->token == NULL) {
372 /* We cannot just save a pointer because a reconfigure will
373 * recreate the token list. So we duplicate it. */
374 state->token = malloc(sizeof(*state->token));
376 state->token->secretid = t->secretid;
377 state->token->key = malloc(t->key_len);
378 if (state->token->key) {
379 state->token->key_len = t->key_len;
380 memcpy(state->token->key, t->key, t->key_len);
387 state->token->realm = malloc(t->realm_len);
388 if (state->token->realm) {
389 state->token->realm_len = t->realm_len;
390 memcpy(state->token->realm, t->realm,
393 free(state->token->key);
399 state->token->realm = NULL;
400 state->token->realm_len = 0;
403 /* If we cannot save the token, we must invalidate */
404 if (state->token == NULL)
412 get_next_rdm_monotonic_counter(struct auth *auth)
420 fp = fopen(RDM_MONOFILE, "r+");
423 return ++auth->last_replay; /* report error? */
424 fp = fopen(RDM_MONOFILE, "w");
426 return ++auth->last_replay; /* report error? */
428 flocked = flock(fileno(fp), LOCK_EX);
433 flocked = flock(fileno(fp), LOCK_EX);
435 if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1)
436 rdm = 0; /* truncated? report error? */
440 if (fseek(fp, 0, SEEK_SET) == -1 ||
441 ftruncate(fileno(fp), 0) == -1 ||
442 fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 ||
445 if (!auth->last_replay_set) {
446 auth->last_replay = rdm;
447 auth->last_replay_set = 1;
449 rdm = ++auth->last_replay;
454 flock(fileno(fp), LOCK_UN);
460 #define NTP_EPOCH 2208988800U /* 1970 - 1900 in seconds */
461 #define NTP_SCALE_FRAC 4294967295.0 /* max value of the fractional part */
463 get_next_rdm_monotonic_clock(struct auth *auth)
469 if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
470 return ++auth->last_replay; /* report error? */
472 secs = (uint64_t)ts.tv_sec + NTP_EPOCH;
473 frac = ((double)ts.tv_nsec / 1e9 * NTP_SCALE_FRAC);
474 rdm = (secs << 32) | (uint64_t)frac;
479 get_next_rdm_monotonic(struct auth *auth)
482 if (auth->options & DHCPCD_AUTH_RDM_COUNTER)
483 return get_next_rdm_monotonic_counter(auth);
484 return get_next_rdm_monotonic_clock(auth);
488 * Encode a DHCP message.
489 * Either we know which token to use from the server response
490 * or we are using a basic configuration token.
491 * token is the token to encrypt with.
492 * m and mlen refer to the whole message.
493 * mp is the DHCP type, pass it 4 or 6.
494 * mt is the DHCP message type.
495 * data and dlen refer to the authentication option within the message.
498 dhcp_auth_encode(struct auth *auth, const struct token *t,
499 void *vm, size_t mlen, int mp, int mt,
500 void *vdata, size_t dlen)
503 uint8_t hmac_code[HMAC_LENGTH];
505 uint8_t hops, *p, *m, *data;
506 uint32_t giaddr, secretid;
509 /* Ignore the token argument given to us - always send using the
510 * configured token. */
511 if (auth->protocol == AUTH_PROTO_TOKEN) {
512 TAILQ_FOREACH(t, &auth->tokens, next) {
513 if (t->secretid == auth->token_snd_secretid)
521 if (time(&now) == -1)
523 if (t->expire < now) {
530 switch(auth->protocol) {
531 case AUTH_PROTO_TOKEN:
532 case AUTH_PROTO_DELAYED:
533 case AUTH_PROTO_DELAYEDREALM:
534 /* We don't ever send a reconf key */
541 switch(auth->algorithm) {
543 case AUTH_ALG_HMAC_MD5:
551 case AUTH_RDM_MONOTONIC:
558 /* DISCOVER or INFORM messages don't write auth info */
559 if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
560 (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
565 /* Work out the auth area size.
566 * We only need to do this for DISCOVER messages */
568 dlen = 1 + 1 + 1 + 8;
569 switch(auth->protocol) {
570 case AUTH_PROTO_TOKEN:
573 case AUTH_PROTO_DELAYEDREALM:
575 dlen += t->realm_len;
577 case AUTH_PROTO_DELAYED:
579 dlen += sizeof(t->secretid) + sizeof(hmac_code);
582 return (ssize_t)dlen;
585 if (dlen < 1 + 1 + 1 + 8) {
590 /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
593 if (data < m || data > m + mlen || data + dlen > m + mlen) {
598 /* Write out our option */
599 *data++ = auth->protocol;
600 *data++ = auth->algorithm;
602 * RFC 3315 21.4.4.1 says that SOLICIT in DELAYED authentication
603 * should not set RDM or it's data.
604 * An expired draft draft-ietf-dhc-dhcpv6-clarify-auth-01 suggets
605 * this should not be set for INFORMATION REQ messages as well,
606 * which is probably a good idea because both states start from zero.
609 !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM)))
613 case AUTH_RDM_MONOTONIC:
614 rdm = get_next_rdm_monotonic(auth);
617 /* This block appeases gcc, clang doesn't need it */
618 rdm = get_next_rdm_monotonic(auth);
622 memcpy(data, &rdm, 8);
624 *data++ = 0; /* rdm */
625 memset(data, 0, 8); /* replay detection data */
628 dlen -= 1 + 1 + 1 + 8;
630 /* Special case as no hashing needs to be done. */
631 if (auth->protocol == AUTH_PROTO_TOKEN) {
632 /* Should be impossible, but still */
637 if (dlen < t->key_len) {
641 memcpy(data, t->key, t->key_len);
642 return (ssize_t)(dlen - t->key_len);
645 /* DISCOVER or INFORM messages don't write auth info */
647 return (ssize_t)dlen;
649 /* Loading a saved lease without an authentication option */
653 /* Write out the Realm */
654 if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
655 if (dlen < t->realm_len) {
659 memcpy(data, t->realm, t->realm_len);
660 data += t->realm_len;
661 dlen -= t->realm_len;
664 /* Write out the SecretID */
665 if (auth->protocol == AUTH_PROTO_DELAYED ||
666 auth->protocol == AUTH_PROTO_DELAYEDREALM)
668 if (dlen < sizeof(t->secretid)) {
672 secretid = htonl(t->secretid);
673 memcpy(data, &secretid, sizeof(secretid));
674 data += sizeof(secretid);
675 dlen -= sizeof(secretid);
678 /* Zero what's left, the MAC */
679 memset(data, 0, dlen);
681 /* RFC3318, section 5.2 - zero giaddr and hops */
683 p = m + offsetof(struct bootp, hops);
686 p = m + offsetof(struct bootp, giaddr);
687 memcpy(&giaddr, p, sizeof(giaddr));
688 memset(p, 0, sizeof(giaddr));
690 /* appease GCC again */
695 /* Create our hash and write it out */
696 switch(auth->algorithm) {
697 case AUTH_ALG_HMAC_MD5:
698 hmac("md5", t->key, t->key_len, m, mlen,
699 hmac_code, sizeof(hmac_code));
700 memcpy(data, hmac_code, sizeof(hmac_code));
704 /* RFC3318, section 5.2 - restore giaddr and hops */
706 p = m + offsetof(struct bootp, hops);
708 p = m + offsetof(struct bootp, giaddr);
709 memcpy(p, &giaddr, sizeof(giaddr));
713 return (int)(dlen - sizeof(hmac_code)); /* should be zero */