xref: /minix3/external/bsd/dhcpcd/dist/auth.c (revision 9f20bfa6c4c442e2e798d91b11c2a5f8d6833a41)
1 #include <sys/cdefs.h>
2  __RCSID("$NetBSD: auth.c,v 1.10 2015/07/09 10:15:34 roy Exp $");
3 
4 /*
5  * dhcpcd - DHCP client daemon
6  * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
7  * All rights reserved
8 
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.
17  *
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.
29  */
30 
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>
40 
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"
47 
48 #ifdef __sun
49 #define htonll
50 #define ntohll
51 #endif
52 
53 #ifndef htonll
54 #if (BYTE_ORDER == LITTLE_ENDIAN)
55 static inline uint64_t
htonll(uint64_t x)56 htonll(uint64_t x)
57 {
58 
59 	return (uint64_t)htonl((uint32_t)(x >> 32)) |
60 	    (uint64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
61 }
62 #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
63 #define htonll(x) (x)
64 #endif
65 #endif  /* htonll */
66 
67 #ifndef ntohll
68 #if (BYTE_ORDER == LITTLE_ENDIAN)
69 static inline uint64_t
ntohll(uint64_t x)70 ntohll(uint64_t x)
71 {
72 
73 	return (uint64_t)ntohl((uint32_t)(x >> 32)) |
74 	    (uint64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
75 }
76 #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
77 #define ntohll(x) (x)
78 #endif
79 #endif  /* ntohll */
80 
81 #define HMAC_LENGTH	16
82 
83 void
dhcp_auth_reset(struct authstate * state)84 dhcp_auth_reset(struct authstate *state)
85 {
86 
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;
93 	}
94 	if (state->reconf) {
95 		free(state->reconf->key);
96 		free(state->reconf->realm);
97 		free(state->reconf);
98 		state->reconf = NULL;
99 	}
100 }
101 
102 /*
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.
107  */
108 const struct token *
dhcp_auth_validate(struct authstate * state,const struct auth * auth,const uint8_t * m,size_t mlen,int mp,int mt,const uint8_t * data,size_t dlen)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)
112 {
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];
121 
122 	if (dlen < 3 + sizeof(replay)) {
123 		errno = EINVAL;
124 		return NULL;
125 	}
126 
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;
131 	}
132 
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;
143 		}
144 	} else if (protocol != auth->protocol ||
145 		    algorithm != auth->algorithm ||
146 		    rdm != auth->rdm)
147 	{
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)
152 		{
153 			errno = EPERM;
154 			return NULL;
155 		}
156 	}
157 	dlen -= 3;
158 
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;
167 		}
168 		if ((uint64_t)(replay - state->replay) <= 0) {
169 			/* Replay attack detected */
170 			errno = EPERM;
171 			return NULL;
172 		}
173 	}
174 	d+= sizeof(replay);
175 	dlen -= sizeof(replay);
176 
177 	realm = NULL;
178 	realm_len = 0;
179 
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;
190 		}
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;
199 		}
200 		realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
201 		if (realm_len) {
202 			realm = d;
203 			d += realm_len;
204 			dlen -= realm_len;
205 		}
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;
214 		}
215 		type = *d++;
216 		dlen--;
217 		switch (type) {
218 		case 1:
219 			if ((mp == 4 && mt == DHCP_ACK) ||
220 			    (mp == 6 && mt == DHCP6_REPLY))
221 			{
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;
232 					}
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;
238 				}
239 				memcpy(state->reconf->key, d, 16);
240 			} else {
241 				errno = EINVAL;
242 				return NULL;
243 			}
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;
250 			}
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)))
256 			{
257 				errno = EINVAL;
258 				return NULL;
259 			}
260 			if (state->reconf == NULL) {
261 				errno = ENOENT;
262 				return NULL;
263 			}
264 			t = state->reconf;
265 			goto gottoken;
266 		default:
267 			errno = EINVAL;
268 			return NULL;
269 		}
270 	default:
271 		errno = ENOTSUP;
272 		return NULL;
273 	}
274 
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;
283 	}
284 	if (t == NULL) {
285 		errno = ESRCH;
286 		return NULL;
287 	}
288 	if (t->expire) {
289 		if (time(&now) == -1)
290 			return NULL;
291 		if (t->expire < now) {
292 			errno = EFAULT;
293 			return NULL;
294 		}
295 	}
296 
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)))
303 	{
304 		errno = EPERM;
305 		return NULL;
306 	}
307 
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;
313 		}
314 		goto finish;
315 	}
316 
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);
323 
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);
328 	}
329 
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;
339 	}
340 
341 	free(mm);
342 	if (memcmp(d, &hmac, dlen)) {
343 		errno = EPERM;
344 		return NULL;
345 	}
346 
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;
364 			}
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;
376 				}
377 			} else {
378 				state->token->realm = NULL;
379 				state->token->realm_len = 0;
380 			}
381 		}
382 		/* If we cannot save the token, we must invalidate */
383 		if (state->token == NULL)
384 			return NULL;
385 	}
386 
387 	return t;
388 }
389 
390 static uint64_t
get_next_rdm_monotonic_counter(struct auth * auth)391 get_next_rdm_monotonic_counter(struct auth *auth)
392 {
393 	FILE *fp;
394 	uint64_t rdm;
395 #ifdef LOCK_EX
396 	int flocked;
397 #endif
398 
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? */
416 	}
417 
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)
423 	{
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? */
430 	}
431 #ifdef LOCK_EX
432 	if (flocked == 0)
433 		flock(fileno(fp), LOCK_UN);
434 #endif
435 	fclose(fp);
436 	return rdm;
437 }
438 
439 #define JAN_1970       2208988800U    /* 1970 - 1900 in seconds */
440 static uint64_t
get_next_rdm_monotonic_clock(struct auth * auth)441 get_next_rdm_monotonic_clock(struct auth *auth)
442 {
443 	struct timespec ts;
444 	uint32_t pack[2];
445 	double frac;
446 	uint64_t rdm;
447 
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);
453 
454 	memcpy(&rdm, &pack, sizeof(rdm));
455 	return rdm;
456 }
457 
458 static uint64_t
get_next_rdm_monotonic(struct auth * auth)459 get_next_rdm_monotonic(struct auth *auth)
460 {
461 
462 	if (auth->options & DHCPCD_AUTH_RDM_COUNTER)
463 		return get_next_rdm_monotonic_counter(auth);
464 	return get_next_rdm_monotonic_clock(auth);
465 }
466 
467 /*
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.
476  */
477 ssize_t
dhcp_auth_encode(struct auth * auth,const struct token * t,uint8_t * m,size_t mlen,int mp,int mt,uint8_t * data,size_t dlen)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)
481 {
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;
487 
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;
493 		}
494 		if (t == NULL) {
495 			errno = EINVAL;
496 			return -1;
497 		}
498 		if (t->expire) {
499 			if (time(&now) == -1)
500 				return -1;
501 			if (t->expire < now) {
502 				errno = EPERM;
503 				return -1;
504 			}
505 		}
506 	}
507 
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;
517 	}
518 
519 	switch(auth->algorithm) {
520 	case AUTH_ALG_HMAC_MD5:
521 		break;
522 	default:
523 		errno = ENOTSUP;
524 		return -1;
525 	}
526 
527 	switch(auth->rdm) {
528 	case AUTH_RDM_MONOTONIC:
529 		break;
530 	default:
531 		errno = ENOTSUP;
532 		return -1;
533 	}
534 
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;
541 
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;
558 		}
559 		return (ssize_t)dlen;
560 	}
561 
562 	if (dlen < 1 + 1 + 1 + 8) {
563 		errno = ENOBUFS;
564 		return -1;
565 	}
566 
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;
571 	}
572 
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;
585 	}
586 	rdm = htonll(rdm);
587 	memcpy(data, &rdm, 8);
588 	data += 8;
589 	dlen -= 1 + 1 + 1 + 8;
590 
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;
597 		}
598 		if (dlen < t->key_len) {
599 			errno =	ENOBUFS;
600 			return -1;
601 		}
602 		memcpy(data, t->key, t->key_len);
603 		return (ssize_t)(dlen - t->key_len);
604 	}
605 
606 	/* DISCOVER or INFORM messages don't write auth info */
607 	if (!info)
608 		return (ssize_t)dlen;
609 
610 	/* Loading a saved lease without an authentication option */
611 	if (t == NULL)
612 		return 0;
613 
614 	/* Write out the Realm */
615 	if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
616 		if (dlen < t->realm_len) {
617 			errno = ENOBUFS;
618 			return -1;
619 		}
620 		memcpy(data, t->realm, t->realm_len);
621 		data += t->realm_len;
622 		dlen -= t->realm_len;
623 	}
624 
625 	/* Write out the SecretID */
626 	if (auth->protocol == AUTH_PROTO_DELAYED ||
627 	    auth->protocol == AUTH_PROTO_DELAYEDREALM)
628 	{
629 		if (dlen < sizeof(t->secretid)) {
630 			errno = ENOBUFS;
631 			return -1;
632 		}
633 		secretid = htonl(t->secretid);
634 		memcpy(data, &secretid, sizeof(secretid));
635 		data += sizeof(secretid);
636 		dlen -= sizeof(secretid);
637 	}
638 
639 	/* Zero what's left, the MAC */
640 	memset(data, 0, dlen);
641 
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;
654 	}
655 
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;
662 	}
663 
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));
670 	}
671 
672 	/* Done! */
673 	return (int)(dlen - sizeof(hmac)); /* should be zero */
674 }
675