Remove building with NOCRYPTO option
[minix3.git] / external / bsd / dhcpcd / dist / auth.c
blob2e9f44cac784e0e519248f665e7b79a51ceb5c85
1 #include <sys/cdefs.h>
2 __RCSID("$NetBSD: auth.c,v 1.10 2015/07/09 10:15:34 roy Exp $");
4 /*
5 * dhcpcd - DHCP client daemon
6 * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
7 * All rights reserved
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
31 #include <sys/file.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <inttypes.h>
35 #include <stddef.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39 #include <unistd.h>
41 #include "config.h"
42 #include "auth.h"
43 #include "crypt/crypt.h"
44 #include "dhcp.h"
45 #include "dhcp6.h"
46 #include "dhcpcd.h"
48 #ifdef __sun
49 #define htonll
50 #define ntohll
51 #endif
53 #ifndef htonll
54 #if (BYTE_ORDER == LITTLE_ENDIAN)
55 static inline uint64_t
56 htonll(uint64_t x)
59 return (uint64_t)htonl((uint32_t)(x >> 32)) |
60 (uint64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
62 #else /* (BYTE_ORDER == LITTLE_ENDIAN) */
63 #define htonll(x) (x)
64 #endif
65 #endif /* htonll */
67 #ifndef ntohll
68 #if (BYTE_ORDER == LITTLE_ENDIAN)
69 static inline uint64_t
70 ntohll(uint64_t x)
73 return (uint64_t)ntohl((uint32_t)(x >> 32)) |
74 (uint64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
76 #else /* (BYTE_ORDER == LITTLE_ENDIAN) */
77 #define ntohll(x) (x)
78 #endif
79 #endif /* ntohll */
81 #define HMAC_LENGTH 16
83 void
84 dhcp_auth_reset(struct authstate *state)
87 state->replay = 0;
88 if (state->token) {
89 free(state->token->key);
90 free(state->token->realm);
91 free(state->token);
92 state->token = NULL;
94 if (state->reconf) {
95 free(state->reconf->key);
96 free(state->reconf->realm);
97 free(state->reconf);
98 state->reconf = NULL;
103 * Authenticate a DHCP message.
104 * m and mlen refer to the whole message.
105 * t is the DHCP type, pass it 4 or 6.
106 * data and dlen refer to the authentication option within the message.
108 const struct token *
109 dhcp_auth_validate(struct authstate *state, const struct auth *auth,
110 const uint8_t *m, size_t mlen, int mp, int mt,
111 const uint8_t *data, size_t dlen)
113 uint8_t protocol, algorithm, rdm, *mm, type;
114 uint64_t replay;
115 uint32_t secretid;
116 const uint8_t *d, *realm;
117 size_t realm_len;
118 const struct token *t;
119 time_t now;
120 uint8_t hmac[HMAC_LENGTH];
122 if (dlen < 3 + sizeof(replay)) {
123 errno = EINVAL;
124 return NULL;
127 /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
128 if (data < m || data > m + mlen || data + dlen > m + mlen) {
129 errno = ERANGE;
130 return NULL;
133 d = data;
134 protocol = *d++;
135 algorithm = *d++;
136 rdm = *d++;
137 if (!(auth->options & DHCPCD_AUTH_SEND)) {
138 /* If we didn't send any authorisation, it can only be a
139 * reconfigure key */
140 if (protocol != AUTH_PROTO_RECONFKEY) {
141 errno = EINVAL;
142 return NULL;
144 } else if (protocol != auth->protocol ||
145 algorithm != auth->algorithm ||
146 rdm != auth->rdm)
148 /* As we don't require authentication, we should still
149 * accept a reconfigure key */
150 if (protocol != AUTH_PROTO_RECONFKEY ||
151 auth->options & DHCPCD_AUTH_REQUIRE)
153 errno = EPERM;
154 return NULL;
157 dlen -= 3;
159 memcpy(&replay, d, sizeof(replay));
160 replay = ntohll(replay);
161 if (state->token) {
162 if (state->replay == (replay ^ 0x8000000000000000ULL)) {
163 /* We don't know if the singular point is increasing
164 * or decreasing. */
165 errno = EPERM;
166 return NULL;
168 if ((uint64_t)(replay - state->replay) <= 0) {
169 /* Replay attack detected */
170 errno = EPERM;
171 return NULL;
174 d+= sizeof(replay);
175 dlen -= sizeof(replay);
177 realm = NULL;
178 realm_len = 0;
180 /* Extract realm and secret.
181 * Rest of data is MAC. */
182 switch (protocol) {
183 case AUTH_PROTO_TOKEN:
184 secretid = 0;
185 break;
186 case AUTH_PROTO_DELAYED:
187 if (dlen < sizeof(secretid) + sizeof(hmac)) {
188 errno = EINVAL;
189 return NULL;
191 memcpy(&secretid, d, sizeof(secretid));
192 d += sizeof(secretid);
193 dlen -= sizeof(secretid);
194 break;
195 case AUTH_PROTO_DELAYEDREALM:
196 if (dlen < sizeof(secretid) + sizeof(hmac)) {
197 errno = EINVAL;
198 return NULL;
200 realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
201 if (realm_len) {
202 realm = d;
203 d += realm_len;
204 dlen -= realm_len;
206 memcpy(&secretid, d, sizeof(secretid));
207 d += sizeof(secretid);
208 dlen -= sizeof(secretid);
209 break;
210 case AUTH_PROTO_RECONFKEY:
211 if (dlen != 1 + 16) {
212 errno = EINVAL;
213 return NULL;
215 type = *d++;
216 dlen--;
217 switch (type) {
218 case 1:
219 if ((mp == 4 && mt == DHCP_ACK) ||
220 (mp == 6 && mt == DHCP6_REPLY))
222 if (state->reconf == NULL) {
223 state->reconf =
224 malloc(sizeof(*state->reconf));
225 if (state->reconf == NULL)
226 return NULL;
227 state->reconf->key = malloc(16);
228 if (state->reconf->key == NULL) {
229 free(state->reconf);
230 state->reconf = NULL;
231 return NULL;
233 state->reconf->secretid = 0;
234 state->reconf->expire = 0;
235 state->reconf->realm = NULL;
236 state->reconf->realm_len = 0;
237 state->reconf->key_len = 16;
239 memcpy(state->reconf->key, d, 16);
240 } else {
241 errno = EINVAL;
242 return NULL;
244 if (state->reconf == NULL)
245 errno = ENOENT;
246 /* Free the old token so we log acceptance */
247 if (state->token) {
248 free(state->token);
249 state->token = NULL;
251 /* Nothing to validate, just accepting the key */
252 return state->reconf;
253 case 2:
254 if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
255 (mp == 6 && mt == DHCP6_RECONFIGURE)))
257 errno = EINVAL;
258 return NULL;
260 if (state->reconf == NULL) {
261 errno = ENOENT;
262 return NULL;
264 t = state->reconf;
265 goto gottoken;
266 default:
267 errno = EINVAL;
268 return NULL;
270 default:
271 errno = ENOTSUP;
272 return NULL;
275 /* Find a token for the realm and secret */
276 secretid = ntohl(secretid);
277 TAILQ_FOREACH(t, &auth->tokens, next) {
278 if (t->secretid == secretid &&
279 t->realm_len == realm_len &&
280 (t->realm_len == 0 ||
281 memcmp(t->realm, realm, t->realm_len) == 0))
282 break;
284 if (t == NULL) {
285 errno = ESRCH;
286 return NULL;
288 if (t->expire) {
289 if (time(&now) == -1)
290 return NULL;
291 if (t->expire < now) {
292 errno = EFAULT;
293 return NULL;
297 gottoken:
298 /* First message from the server */
299 if (state->token &&
300 (state->token->secretid != t->secretid ||
301 state->token->realm_len != t->realm_len ||
302 memcmp(state->token->realm, t->realm, t->realm_len)))
304 errno = EPERM;
305 return NULL;
308 /* Special case as no hashing needs to be done. */
309 if (protocol == AUTH_PROTO_TOKEN) {
310 if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
311 errno = EPERM;
312 return NULL;
314 goto finish;
317 /* Make a duplicate of the message, but zero out the MAC part */
318 mm = malloc(mlen);
319 if (mm == NULL)
320 return NULL;
321 memcpy(mm, m, mlen);
322 memset(mm + (d - m), 0, dlen);
324 /* RFC3318, section 5.2 - zero giaddr and hops */
325 if (mp == 4) {
326 *(mm + offsetof(struct dhcp_message, hwopcount)) = '\0';
327 memset(mm + offsetof(struct dhcp_message, giaddr), 0, 4);
330 memset(hmac, 0, sizeof(hmac));
331 switch (algorithm) {
332 case AUTH_ALG_HMAC_MD5:
333 hmac_md5(mm, mlen, t->key, t->key_len, hmac);
334 break;
335 default:
336 errno = ENOSYS;
337 free(mm);
338 return NULL;
341 free(mm);
342 if (memcmp(d, &hmac, dlen)) {
343 errno = EPERM;
344 return NULL;
347 finish:
348 /* If we got here then authentication passed */
349 state->replay = replay;
350 if (state->token == NULL) {
351 /* We cannot just save a pointer because a reconfigure will
352 * recreate the token list. So we duplicate it. */
353 state->token = malloc(sizeof(*state->token));
354 if (state->token) {
355 state->token->secretid = t->secretid;
356 state->token->key = malloc(t->key_len);
357 if (state->token->key) {
358 state->token->key_len = t->key_len;
359 memcpy(state->token->key, t->key, t->key_len);
360 } else {
361 free(state->token);
362 state->token = NULL;
363 return NULL;
365 if (t->realm_len) {
366 state->token->realm = malloc(t->realm_len);
367 if (state->token->realm) {
368 state->token->realm_len = t->realm_len;
369 memcpy(state->token->realm, t->realm,
370 t->realm_len);
371 } else {
372 free(state->token->key);
373 free(state->token);
374 state->token = NULL;
375 return NULL;
377 } else {
378 state->token->realm = NULL;
379 state->token->realm_len = 0;
382 /* If we cannot save the token, we must invalidate */
383 if (state->token == NULL)
384 return NULL;
387 return t;
390 static uint64_t
391 get_next_rdm_monotonic_counter(struct auth *auth)
393 FILE *fp;
394 uint64_t rdm;
395 #ifdef LOCK_EX
396 int flocked;
397 #endif
399 fp = fopen(RDM_MONOFILE, "r+");
400 if (fp == NULL) {
401 if (errno != ENOENT)
402 return ++auth->last_replay; /* report error? */
403 fp = fopen(RDM_MONOFILE, "w");
404 if (fp == NULL)
405 return ++auth->last_replay; /* report error? */
406 #ifdef LOCK_EX
407 flocked = flock(fileno(fp), LOCK_EX);
408 #endif
409 rdm = 0;
410 } else {
411 #ifdef LOCK_EX
412 flocked = flock(fileno(fp), LOCK_EX);
413 #endif
414 if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1)
415 rdm = 0; /* truncated? report error? */
418 rdm++;
419 if (fseek(fp, 0, SEEK_SET) == -1 ||
420 ftruncate(fileno(fp), 0) == -1 ||
421 fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 ||
422 fflush(fp) == EOF)
424 if (!auth->last_replay_set) {
425 auth->last_replay = rdm;
426 auth->last_replay_set = 1;
427 } else
428 rdm = ++auth->last_replay;
429 /* report error? */
431 #ifdef LOCK_EX
432 if (flocked == 0)
433 flock(fileno(fp), LOCK_UN);
434 #endif
435 fclose(fp);
436 return rdm;
439 #define JAN_1970 2208988800U /* 1970 - 1900 in seconds */
440 static uint64_t
441 get_next_rdm_monotonic_clock(struct auth *auth)
443 struct timespec ts;
444 uint32_t pack[2];
445 double frac;
446 uint64_t rdm;
448 if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
449 return ++auth->last_replay; /* report error? */
450 pack[0] = htonl((uint32_t)ts.tv_sec + JAN_1970);
451 frac = ((double)ts.tv_nsec / 1e9 * 0x100000000ULL);
452 pack[1] = htonl((uint32_t)frac);
454 memcpy(&rdm, &pack, sizeof(rdm));
455 return rdm;
458 static uint64_t
459 get_next_rdm_monotonic(struct auth *auth)
462 if (auth->options & DHCPCD_AUTH_RDM_COUNTER)
463 return get_next_rdm_monotonic_counter(auth);
464 return get_next_rdm_monotonic_clock(auth);
468 * Encode a DHCP message.
469 * Either we know which token to use from the server response
470 * or we are using a basic configuration token.
471 * token is the token to encrypt with.
472 * m and mlen refer to the whole message.
473 * mp is the DHCP type, pass it 4 or 6.
474 * mt is the DHCP message type.
475 * data and dlen refer to the authentication option within the message.
477 ssize_t
478 dhcp_auth_encode(struct auth *auth, const struct token *t,
479 uint8_t *m, size_t mlen, int mp, int mt,
480 uint8_t *data, size_t dlen)
482 uint64_t rdm;
483 uint8_t hmac[HMAC_LENGTH];
484 time_t now;
485 uint8_t hops, *p, info;
486 uint32_t giaddr, secretid;
488 if (auth->protocol == 0 && t == NULL) {
489 TAILQ_FOREACH(t, &auth->tokens, next) {
490 if (t->secretid == 0 &&
491 t->realm_len == 0)
492 break;
494 if (t == NULL) {
495 errno = EINVAL;
496 return -1;
498 if (t->expire) {
499 if (time(&now) == -1)
500 return -1;
501 if (t->expire < now) {
502 errno = EPERM;
503 return -1;
508 switch(auth->protocol) {
509 case AUTH_PROTO_TOKEN:
510 case AUTH_PROTO_DELAYED:
511 case AUTH_PROTO_DELAYEDREALM:
512 /* We don't ever send a reconf key */
513 break;
514 default:
515 errno = ENOTSUP;
516 return -1;
519 switch(auth->algorithm) {
520 case AUTH_ALG_HMAC_MD5:
521 break;
522 default:
523 errno = ENOTSUP;
524 return -1;
527 switch(auth->rdm) {
528 case AUTH_RDM_MONOTONIC:
529 break;
530 default:
531 errno = ENOTSUP;
532 return -1;
535 /* DISCOVER or INFORM messages don't write auth info */
536 if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
537 (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
538 info = 0;
539 else
540 info = 1;
542 /* Work out the auth area size.
543 * We only need to do this for DISCOVER messages */
544 if (data == NULL) {
545 dlen = 1 + 1 + 1 + 8;
546 switch(auth->protocol) {
547 case AUTH_PROTO_TOKEN:
548 dlen += t->key_len;
549 break;
550 case AUTH_PROTO_DELAYEDREALM:
551 if (info && t)
552 dlen += t->realm_len;
553 /* FALLTHROUGH */
554 case AUTH_PROTO_DELAYED:
555 if (info && t)
556 dlen += sizeof(t->secretid) + sizeof(hmac);
557 break;
559 return (ssize_t)dlen;
562 if (dlen < 1 + 1 + 1 + 8) {
563 errno = ENOBUFS;
564 return -1;
567 /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
568 if (data < m || data > m + mlen || data + dlen > m + mlen) {
569 errno = ERANGE;
570 return -1;
573 /* Write out our option */
574 *data++ = auth->protocol;
575 *data++ = auth->algorithm;
576 *data++ = auth->rdm;
577 switch (auth->rdm) {
578 case AUTH_RDM_MONOTONIC:
579 rdm = get_next_rdm_monotonic(auth);
580 break;
581 default:
582 /* This block appeases gcc, clang doesn't need it */
583 rdm = get_next_rdm_monotonic(auth);
584 break;
586 rdm = htonll(rdm);
587 memcpy(data, &rdm, 8);
588 data += 8;
589 dlen -= 1 + 1 + 1 + 8;
591 /* Special case as no hashing needs to be done. */
592 if (auth->protocol == AUTH_PROTO_TOKEN) {
593 /* Should be impossible, but still */
594 if (t == NULL) {
595 errno = EINVAL;
596 return -1;
598 if (dlen < t->key_len) {
599 errno = ENOBUFS;
600 return -1;
602 memcpy(data, t->key, t->key_len);
603 return (ssize_t)(dlen - t->key_len);
606 /* DISCOVER or INFORM messages don't write auth info */
607 if (!info)
608 return (ssize_t)dlen;
610 /* Loading a saved lease without an authentication option */
611 if (t == NULL)
612 return 0;
614 /* Write out the Realm */
615 if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
616 if (dlen < t->realm_len) {
617 errno = ENOBUFS;
618 return -1;
620 memcpy(data, t->realm, t->realm_len);
621 data += t->realm_len;
622 dlen -= t->realm_len;
625 /* Write out the SecretID */
626 if (auth->protocol == AUTH_PROTO_DELAYED ||
627 auth->protocol == AUTH_PROTO_DELAYEDREALM)
629 if (dlen < sizeof(t->secretid)) {
630 errno = ENOBUFS;
631 return -1;
633 secretid = htonl(t->secretid);
634 memcpy(data, &secretid, sizeof(secretid));
635 data += sizeof(secretid);
636 dlen -= sizeof(secretid);
639 /* Zero what's left, the MAC */
640 memset(data, 0, dlen);
642 /* RFC3318, section 5.2 - zero giaddr and hops */
643 if (mp == 4) {
644 p = m + offsetof(struct dhcp_message, hwopcount);
645 hops = *p;
646 *p = '\0';
647 p = m + offsetof(struct dhcp_message, giaddr);
648 memcpy(&giaddr, p, sizeof(giaddr));
649 memset(p, 0, sizeof(giaddr));
650 } else {
651 /* appease GCC again */
652 hops = 0;
653 giaddr = 0;
656 /* Create our hash and write it out */
657 switch(auth->algorithm) {
658 case AUTH_ALG_HMAC_MD5:
659 hmac_md5(m, mlen, t->key, t->key_len, hmac);
660 memcpy(data, hmac, sizeof(hmac));
661 break;
664 /* RFC3318, section 5.2 - restore giaddr and hops */
665 if (mp == 4) {
666 p = m + offsetof(struct dhcp_message, hwopcount);
667 *p = hops;
668 p = m + offsetof(struct dhcp_message, giaddr);
669 memcpy(p, &giaddr, sizeof(giaddr));
672 /* Done! */
673 return (int)(dlen - sizeof(hmac)); /* should be zero */