Import dhcpcd-8.0.4 to vendor branch.
[dragonfly.git] / contrib / dhcpcd / src / auth.c
blob8a83530842556ec8ee3a1cfe69299bda2331f4a1
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3 * dhcpcd - DHCP client daemon
4 * Copyright (c) 2006-2019 Roy Marples <roy@marples.name>
5 * All rights reserved
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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
26 * SUCH DAMAGE.
29 #include <sys/file.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <inttypes.h>
33 #include <stddef.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38 #include <unistd.h>
40 #include "config.h"
41 #include "auth.h"
42 #include "dhcp.h"
43 #include "dhcp6.h"
44 #include "dhcpcd.h"
46 #ifdef HAVE_HMAC_H
47 #include <hmac.h>
48 #endif
50 #ifdef __sun
51 #define htonll
52 #define ntohll
53 #endif
55 #ifndef htonll
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) */
60 #define htonll(x) (x)
61 #endif
62 #endif /* htonll */
64 #ifndef ntohll
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) */
69 #define ntohll(x) (x)
70 #endif
71 #endif /* ntohll */
73 #define HMAC_LENGTH 16
75 void
76 dhcp_auth_reset(struct authstate *state)
79 state->replay = 0;
80 if (state->token) {
81 free(state->token->key);
82 free(state->token->realm);
83 free(state->token);
84 state->token = NULL;
86 if (state->reconf) {
87 free(state->reconf->key);
88 free(state->reconf->realm);
89 free(state->reconf);
90 state->reconf = NULL;
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.
100 const struct token *
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;
107 uint64_t replay;
108 uint32_t secretid;
109 const uint8_t *d, *realm;
110 size_t realm_len;
111 const struct token *t;
112 time_t now;
113 uint8_t hmac_code[HMAC_LENGTH];
115 if (dlen < 3 + sizeof(replay)) {
116 errno = EINVAL;
117 return NULL;
120 m = vm;
121 data = vdata;
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) {
128 errno = ERANGE;
129 return NULL;
132 d = data;
133 protocol = *d++;
134 algorithm = *d++;
135 rdm = *d++;
136 if (!(auth->options & DHCPCD_AUTH_SEND)) {
137 /* If we didn't send any authorisation, it can only be a
138 * reconfigure key */
139 if (protocol != AUTH_PROTO_RECONFKEY) {
140 errno = EINVAL;
141 return NULL;
143 } else if (protocol != auth->protocol ||
144 algorithm != auth->algorithm ||
145 rdm != auth->rdm)
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)
152 errno = EPERM;
153 return NULL;
156 dlen -= 3;
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
180 * or decreasing. */
181 errno = EPERM;
182 return NULL;
184 if ((uint64_t)(replay - state->replay) <= 0) {
185 /* Replay attack detected */
186 errno = EPERM;
187 return NULL;
190 d+= sizeof(replay);
191 dlen -= sizeof(replay);
193 realm = NULL;
194 realm_len = 0;
196 /* Extract realm and secret.
197 * Rest of data is MAC. */
198 switch (protocol) {
199 case AUTH_PROTO_TOKEN:
200 secretid = auth->token_rcv_secretid;
201 break;
202 case AUTH_PROTO_DELAYED:
203 if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
204 errno = EINVAL;
205 return NULL;
207 memcpy(&secretid, d, sizeof(secretid));
208 secretid = ntohl(secretid);
209 d += sizeof(secretid);
210 dlen -= sizeof(secretid);
211 break;
212 case AUTH_PROTO_DELAYEDREALM:
213 if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
214 errno = EINVAL;
215 return NULL;
217 realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code));
218 if (realm_len) {
219 realm = d;
220 d += realm_len;
221 dlen -= realm_len;
223 memcpy(&secretid, d, sizeof(secretid));
224 secretid = ntohl(secretid);
225 d += sizeof(secretid);
226 dlen -= sizeof(secretid);
227 break;
228 case AUTH_PROTO_RECONFKEY:
229 if (dlen != 1 + 16) {
230 errno = EINVAL;
231 return NULL;
233 type = *d++;
234 dlen--;
235 switch (type) {
236 case 1:
237 if ((mp == 4 && mt == DHCP_ACK) ||
238 (mp == 6 && mt == DHCP6_REPLY))
240 if (state->reconf == NULL) {
241 state->reconf =
242 malloc(sizeof(*state->reconf));
243 if (state->reconf == NULL)
244 return NULL;
245 state->reconf->key = malloc(16);
246 if (state->reconf->key == NULL) {
247 free(state->reconf);
248 state->reconf = NULL;
249 return 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);
258 } else {
259 errno = EINVAL;
260 return NULL;
262 if (state->reconf == NULL)
263 errno = ENOENT;
264 /* Free the old token so we log acceptance */
265 if (state->token) {
266 free(state->token);
267 state->token = NULL;
269 /* Nothing to validate, just accepting the key */
270 return state->reconf;
271 case 2:
272 if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
273 (mp == 6 && mt == DHCP6_RECONFIGURE)))
275 errno = EINVAL;
276 return NULL;
278 if (state->reconf == NULL) {
279 errno = ENOENT;
280 return NULL;
282 t = state->reconf;
283 goto gottoken;
284 default:
285 errno = EINVAL;
286 return NULL;
288 default:
289 errno = ENOTSUP;
290 return 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))
299 break;
301 if (t == NULL) {
302 errno = ESRCH;
303 return NULL;
305 if (t->expire) {
306 if (time(&now) == -1)
307 return NULL;
308 if (t->expire < now) {
309 errno = EFAULT;
310 return NULL;
314 gottoken:
315 /* First message from the server */
316 if (state->token &&
317 (state->token->secretid != t->secretid ||
318 state->token->realm_len != t->realm_len ||
319 memcmp(state->token->realm, t->realm, t->realm_len)))
321 errno = EPERM;
322 return NULL;
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)) {
328 errno = EPERM;
329 return NULL;
331 goto finish;
334 /* Make a duplicate of the message, but zero out the MAC part */
335 mm = malloc(mlen);
336 if (mm == NULL)
337 return NULL;
338 memcpy(mm, m, mlen);
339 memset(mm + (d - m), 0, dlen);
341 /* RFC3318, section 5.2 - zero giaddr and hops */
342 if (mp == 4) {
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));
351 switch (algorithm) {
352 case AUTH_ALG_HMAC_MD5:
353 hmac("md5", t->key, t->key_len, mm, mlen,
354 hmac_code, sizeof(hmac_code));
355 break;
356 default:
357 errno = ENOSYS;
358 free(mm);
359 return NULL;
362 free(mm);
363 if (!consttime_memequal(d, &hmac_code, dlen)) {
364 errno = EPERM;
365 return NULL;
368 finish:
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));
375 if (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);
381 } else {
382 free(state->token);
383 state->token = NULL;
384 return NULL;
386 if (t->realm_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,
391 t->realm_len);
392 } else {
393 free(state->token->key);
394 free(state->token);
395 state->token = NULL;
396 return NULL;
398 } else {
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)
405 return NULL;
408 return t;
411 static uint64_t
412 get_next_rdm_monotonic_counter(struct auth *auth)
414 FILE *fp;
415 uint64_t rdm;
416 #ifdef LOCK_EX
417 int flocked;
418 #endif
420 fp = fopen(RDM_MONOFILE, "r+");
421 if (fp == NULL) {
422 if (errno != ENOENT)
423 return ++auth->last_replay; /* report error? */
424 fp = fopen(RDM_MONOFILE, "w");
425 if (fp == NULL)
426 return ++auth->last_replay; /* report error? */
427 #ifdef LOCK_EX
428 flocked = flock(fileno(fp), LOCK_EX);
429 #endif
430 rdm = 0;
431 } else {
432 #ifdef LOCK_EX
433 flocked = flock(fileno(fp), LOCK_EX);
434 #endif
435 if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1)
436 rdm = 0; /* truncated? report error? */
439 rdm++;
440 if (fseek(fp, 0, SEEK_SET) == -1 ||
441 ftruncate(fileno(fp), 0) == -1 ||
442 fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 ||
443 fflush(fp) == EOF)
445 if (!auth->last_replay_set) {
446 auth->last_replay = rdm;
447 auth->last_replay_set = 1;
448 } else
449 rdm = ++auth->last_replay;
450 /* report error? */
452 #ifdef LOCK_EX
453 if (flocked == 0)
454 flock(fileno(fp), LOCK_UN);
455 #endif
456 fclose(fp);
457 return rdm;
460 #define NTP_EPOCH 2208988800U /* 1970 - 1900 in seconds */
461 #define NTP_SCALE_FRAC 4294967295.0 /* max value of the fractional part */
462 static uint64_t
463 get_next_rdm_monotonic_clock(struct auth *auth)
465 struct timespec ts;
466 uint64_t secs, rdm;
467 double frac;
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;
475 return rdm;
478 static uint64_t
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.
497 ssize_t
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)
502 uint64_t rdm;
503 uint8_t hmac_code[HMAC_LENGTH];
504 time_t now;
505 uint8_t hops, *p, *m, *data;
506 uint32_t giaddr, secretid;
507 bool auth_info;
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)
514 break;
516 if (t == NULL) {
517 errno = EINVAL;
518 return -1;
520 if (t->expire) {
521 if (time(&now) == -1)
522 return -1;
523 if (t->expire < now) {
524 errno = EPERM;
525 return -1;
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 */
535 break;
536 default:
537 errno = ENOTSUP;
538 return -1;
541 switch(auth->algorithm) {
542 case AUTH_ALG_NONE:
543 case AUTH_ALG_HMAC_MD5:
544 break;
545 default:
546 errno = ENOTSUP;
547 return -1;
550 switch(auth->rdm) {
551 case AUTH_RDM_MONOTONIC:
552 break;
553 default:
554 errno = ENOTSUP;
555 return -1;
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)))
561 auth_info = false;
562 else
563 auth_info = true;
565 /* Work out the auth area size.
566 * We only need to do this for DISCOVER messages */
567 if (vdata == NULL) {
568 dlen = 1 + 1 + 1 + 8;
569 switch(auth->protocol) {
570 case AUTH_PROTO_TOKEN:
571 dlen += t->key_len;
572 break;
573 case AUTH_PROTO_DELAYEDREALM:
574 if (auth_info && t)
575 dlen += t->realm_len;
576 /* FALLTHROUGH */
577 case AUTH_PROTO_DELAYED:
578 if (auth_info && t)
579 dlen += sizeof(t->secretid) + sizeof(hmac_code);
580 break;
582 return (ssize_t)dlen;
585 if (dlen < 1 + 1 + 1 + 8) {
586 errno = ENOBUFS;
587 return -1;
590 /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
591 m = vm;
592 data = vdata;
593 if (data < m || data > m + mlen || data + dlen > m + mlen) {
594 errno = ERANGE;
595 return -1;
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.
608 if (auth_info ||
609 !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM)))
611 *data++ = auth->rdm;
612 switch (auth->rdm) {
613 case AUTH_RDM_MONOTONIC:
614 rdm = get_next_rdm_monotonic(auth);
615 break;
616 default:
617 /* This block appeases gcc, clang doesn't need it */
618 rdm = get_next_rdm_monotonic(auth);
619 break;
621 rdm = htonll(rdm);
622 memcpy(data, &rdm, 8);
623 } else {
624 *data++ = 0; /* rdm */
625 memset(data, 0, 8); /* replay detection data */
627 data += 8;
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 */
633 if (t == NULL) {
634 errno = EINVAL;
635 return -1;
637 if (dlen < t->key_len) {
638 errno = ENOBUFS;
639 return -1;
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 */
646 if (!auth_info)
647 return (ssize_t)dlen;
649 /* Loading a saved lease without an authentication option */
650 if (t == NULL)
651 return 0;
653 /* Write out the Realm */
654 if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
655 if (dlen < t->realm_len) {
656 errno = ENOBUFS;
657 return -1;
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)) {
669 errno = ENOBUFS;
670 return -1;
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 */
682 if (mp == 4) {
683 p = m + offsetof(struct bootp, hops);
684 hops = *p;
685 *p = '\0';
686 p = m + offsetof(struct bootp, giaddr);
687 memcpy(&giaddr, p, sizeof(giaddr));
688 memset(p, 0, sizeof(giaddr));
689 } else {
690 /* appease GCC again */
691 hops = 0;
692 giaddr = 0;
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));
701 break;
704 /* RFC3318, section 5.2 - restore giaddr and hops */
705 if (mp == 4) {
706 p = m + offsetof(struct bootp, hops);
707 *p = hops;
708 p = m + offsetof(struct bootp, giaddr);
709 memcpy(p, &giaddr, sizeof(giaddr));
712 /* Done! */
713 return (int)(dlen - sizeof(hmac_code)); /* should be zero */