source: rtems-libbsd/dhcpcd/auth.c @ f2ed769

4.1155-freebsd-126-freebsd-12freebsd-9.3
Last change on this file since f2ed769 was f2ed769, checked in by Sebastian Huber <sebastian.huber@…>, on 01/30/14 at 12:29:46

DHCPCD(8): Import

Import DHCPCD(8) from:

http://roy.marples.name/projects/dhcpcd/

The upstream sources can be obtained via:

fossil clone http://roy.marples.name/projects/dhcpcd

The imported version is 2014-01-29 19:46:44 [6b209507bb].

  • Property mode set to 100644
File size: 12.5 KB
Line 
1/*
2 * dhcpcd - DHCP client daemon
3 * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
4 * All rights reserved
5
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/file.h>
29#include <sys/queue.h>
30#include <errno.h>
31#include <fcntl.h>
32#include <inttypes.h>
33#include <stddef.h>
34#include <stdlib.h>
35#include <string.h>
36#include <syslog.h>
37#include <time.h>
38#include <unistd.h>
39
40#include "config.h"
41#include "auth.h"
42#include "crypt/crypt.h"
43#include "dhcp.h"
44#include "dhcp6.h"
45#include "dhcpcd.h"
46
47#ifndef htonll
48#if (BYTE_ORDER == LITTLE_ENDIAN)
49static inline uint64_t
50htonll(uint64_t x)
51{
52
53        return (uint64_t)htonl((uint32_t)(x >> 32)) |
54            (int64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
55}
56#else   /* (BYTE_ORDER == LITTLE_ENDIAN) */
57#define htonll(x) (x)
58#endif
59#endif  /* htonll */
60
61#ifndef ntohll
62#if (BYTE_ORDER == LITTLE_ENDIAN)
63static inline uint64_t
64ntohll(uint64_t x)
65{
66
67        return (uint64_t)ntohl((uint32_t)(x >> 32)) |
68            (int64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
69}
70#else   /* (BYTE_ORDER == LITTLE_ENDIAN) */
71#define ntohll(x) (x)
72#endif
73#endif  /* ntohll */
74
75#define HMAC_LENGTH     16
76
77/*
78 * Authenticate a DHCP message.
79 * m and mlen refer to the whole message.
80 * t is the DHCP type, pass it 4 or 6.
81 * data and dlen refer to the authentication option within the message.
82 */
83const struct token *
84dhcp_auth_validate(struct authstate *state, const struct auth *auth,
85    const uint8_t *m, unsigned int mlen, int mp,  int mt,
86    const uint8_t *data, unsigned int dlen)
87{
88        uint8_t protocol, algorithm, rdm, *mm, type;
89        uint64_t replay;
90        uint32_t secretid;
91        const uint8_t *d, *realm;
92        unsigned int realm_len;
93        const struct token *t;
94        time_t now;
95        uint8_t hmac[HMAC_LENGTH];
96
97        if (dlen < 3 + sizeof(replay)) {
98                errno = EINVAL;
99                return NULL;
100        }
101
102        /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
103        if (data < m || data > m + mlen || data + dlen > m + mlen) {
104                errno = ERANGE;
105                return NULL;
106        }
107
108        d = data;
109        protocol = *d++;
110        algorithm = *d++;
111        rdm = *d++;
112        if (!(auth->options & DHCPCD_AUTH_SEND)) {
113                /* If we didn't send any authorisation, it can only be a
114                 * reconfigure key */
115                if (protocol != AUTH_PROTO_RECONFKEY) {
116                        errno = EINVAL;
117                        return NULL;
118                }
119        } else if (protocol != auth->protocol ||
120                    algorithm != auth->algorithm ||
121                    rdm != auth->rdm)
122        {
123                errno = EPERM;
124                return NULL;
125        }
126
127        dlen -= 3;
128        memcpy(&replay, d, sizeof(replay));
129        replay = ntohll(replay);
130        d+= sizeof(replay);
131        dlen -= sizeof(replay);
132
133        if (state->token && replay - state->replay <= 0) {
134                /* Replay attack detected */
135                errno = EPERM;
136                return NULL;
137        }
138
139        realm = NULL;
140        realm_len = 0;
141
142        /* Extract realm and secret.
143         * Rest of data is MAC. */
144        switch (protocol) {
145        case AUTH_PROTO_TOKEN:
146                secretid = 0;
147                break;
148        case AUTH_PROTO_DELAYED:
149                if (dlen < sizeof(secretid) + sizeof(hmac)) {
150                        errno = EINVAL;
151                        return NULL;
152                }
153                memcpy(&secretid, d, sizeof(secretid));
154                d += sizeof(secretid);
155                dlen -= sizeof(secretid);
156                break;
157        case AUTH_PROTO_DELAYEDREALM:
158                if (dlen < sizeof(secretid) + sizeof(hmac)) {
159                        errno = EINVAL;
160                        return NULL;
161                }
162                realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
163                if (realm_len) {
164                        realm = d;
165                        d += realm_len;
166                        dlen -= realm_len;
167                }
168                memcpy(&secretid, d, sizeof(secretid));
169                d += sizeof(secretid);
170                dlen -= sizeof(secretid);
171                break;
172        case AUTH_PROTO_RECONFKEY:
173                if (dlen != 1 + 16) {
174                        errno = EINVAL;
175                        return NULL;
176                }
177                type = *d++;
178                dlen--;
179                switch (type) {
180                case 1:
181                        if ((mp == 4 && mt == DHCP_ACK) ||
182                            (mp == 6 && mt == DHCP6_REPLY))
183                        {
184                                if (state->reconf == NULL) {
185                                        state->reconf =
186                                            malloc(sizeof(*state->reconf));
187                                        if (state->reconf == NULL)
188                                                return NULL;
189                                        state->reconf->key = malloc(16);
190                                        if (state->reconf->key == NULL) {
191                                                free(state->reconf);
192                                                state->reconf = NULL;
193                                                return NULL;
194                                        }
195                                        state->reconf->secretid = 0;
196                                        state->reconf->expire = 0;
197                                        state->reconf->realm = NULL;
198                                        state->reconf->realm_len = 0;
199                                        state->reconf->key_len = 16;
200                                }
201                                memcpy(state->reconf->key, d, 16);
202                        } else {
203                                errno = EINVAL;
204                                return NULL;
205                        }
206                        if (state->reconf == NULL)
207                                errno = ENOENT;
208                        /* Nothing to validate, just accepting the key */
209                        return state->reconf;
210                case 2:
211                        if (state->reconf == NULL) {
212                                errno = ENOENT;
213                                return NULL;
214                        }
215                        t = state->reconf;
216                        goto gottoken;
217                default:
218                        errno = EINVAL;
219                        return NULL;
220                }
221        default:
222                errno = ENOTSUP;
223                return NULL;
224        }
225
226        /* Find a token for the realm and secret */
227        secretid = ntohl(secretid);
228        TAILQ_FOREACH(t, &auth->tokens, next) {
229                if (t->secretid == secretid &&
230                    t->realm_len == realm_len &&
231                    (t->realm_len == 0 ||
232                    memcmp(t->realm, realm, t->realm_len) == 0))
233                        break;
234        }
235        if (t == NULL) {
236                errno = ESRCH;
237                return NULL;
238        }
239        if (t->expire) {
240                if (time(&now) == -1)
241                        return NULL;
242                if (t->expire < now) {
243                        errno = EFAULT;
244                        return NULL;
245                }
246        }
247
248gottoken:
249        /* First message from the server */
250        if (state->token && state->token != t) {
251                errno = EPERM;
252                return NULL;
253        }
254
255        /* Special case as no hashing needs to be done. */
256        if (protocol == AUTH_PROTO_TOKEN) {
257                if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
258                        errno = EPERM;
259                        return NULL;
260                }
261                goto finish;
262        }
263
264        /* Make a duplicate of the message, but zero out the MAC part */
265        mm = malloc(mlen);
266        if (mm == NULL)
267                return NULL;
268        memcpy(mm, m, mlen);
269        memset(mm + (d - m), 0, dlen);
270
271        /* RFC3318, section 5.2 - zero giaddr and hops */
272        if (mp == 4) {
273                *(mm + offsetof(struct dhcp_message, hwopcount)) = '\0';
274                memset(mm + offsetof(struct dhcp_message, giaddr), 0, 4);
275        }
276
277        memset(hmac, 0, sizeof(hmac));
278        switch (algorithm) {
279        case AUTH_ALG_HMAC_MD5:
280                hmac_md5(mm, mlen, t->key, t->key_len, hmac);
281                break;
282        default:
283                errno = ENOSYS;
284                free(mm);
285                return NULL;
286        }
287
288        free(mm);
289        if (memcmp(d, &hmac, dlen)) {
290                errno = EPERM;
291                return NULL;
292        }
293
294finish:
295        /* If we got here then authentication passed */
296        state->replay = replay;
297        state->token = t;
298
299        return t;
300}
301
302static uint64_t last_rdm;
303static uint8_t last_rdm_set;
304static uint64_t
305get_next_rdm_monotonic(void)
306{
307        FILE *fp;
308        char *line, *ep;
309        uint64_t rdm;
310        int flocked;
311
312        fp = fopen(RDM_MONOFILE, "r+");
313        if (fp == NULL) {
314                if (errno != ENOENT)
315                        return ++last_rdm; /* report error? */
316                fp = fopen(RDM_MONOFILE, "w");
317                if (fp == NULL)
318                        return ++last_rdm; /* report error? */
319                flocked = flock(fileno(fp), LOCK_EX);
320                rdm = 0;
321        } else {
322                flocked = flock(fileno(fp), LOCK_EX);
323                line = get_line(fp);
324                if (line == NULL)
325                        rdm = 0; /* truncated? report error? */
326                else
327                        rdm = strtoull(line, &ep, 0);
328        }
329
330        rdm++;
331        if (fseek(fp, 0, SEEK_SET) == -1 ||
332            ftruncate(fileno(fp), 0) == -1 ||
333            fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19)
334        {
335                if (!last_rdm_set) {
336                        last_rdm = rdm;
337                        last_rdm_set = 1;
338                } else
339                        rdm = ++last_rdm;
340                /* report error? */
341        }
342        fflush(fp);
343        if (flocked == 0)
344                flock(fileno(fp), LOCK_UN);
345        fclose(fp);
346        return rdm;
347}
348
349
350/*
351 * Encode a DHCP message.
352 * Either we know which token to use from the server response
353 * or we are using a basic configuration token.
354 * token is the token to encrypt with.
355 * m and mlen refer to the whole message.
356 * mp is the DHCP type, pass it 4 or 6.
357 * mt is the DHCP message type.
358 * data and dlen refer to the authentication option within the message.
359 */
360int
361dhcp_auth_encode(const struct auth *auth, const struct token *t,
362    uint8_t *m, unsigned int mlen, int mp, int mt,
363    uint8_t *data, unsigned int dlen)
364{
365        uint64_t rdm;
366        uint8_t hmac[HMAC_LENGTH];
367        time_t now;
368        uint8_t hops, *p, info;
369        uint32_t giaddr, secretid;
370
371        if (auth->protocol == 0 && t == NULL) {
372                TAILQ_FOREACH(t, &auth->tokens, next) {
373                        if (t->secretid == 0 &&
374                            t->realm_len == 0)
375                        break;
376                }
377                if (t == NULL) {
378                        errno = EINVAL;
379                        return -1;
380                }
381                if (t->expire) {
382                        if (time(&now) == -1)
383                                return -1;
384                        if (t->expire < now) {
385                                errno = EPERM;
386                                return -1;
387                        }
388                }
389        }
390
391        switch(auth->protocol) {
392        case AUTH_PROTO_TOKEN:
393        case AUTH_PROTO_DELAYED:
394        case AUTH_PROTO_DELAYEDREALM:
395                /* We don't ever send a reconf key */
396                break;
397        default:
398                errno = ENOTSUP;
399                return -1;
400        }
401
402        switch(auth->algorithm) {
403        case AUTH_ALG_HMAC_MD5:
404                break;
405        default:
406                errno = ENOTSUP;
407                return -1;
408        }
409
410        switch(auth->rdm) {
411        case AUTH_RDM_MONOTONIC:
412                break;
413        default:
414                errno = ENOTSUP;
415                return -1;
416        }
417
418        /* DISCOVER or INFORM messages don't write auth info */
419        if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
420            (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
421                info = 0;
422        else
423                info = 1;
424
425        /* Work out the auth area size.
426         * We only need to do this for DISCOVER messages */
427        if (data == NULL) {
428                dlen = 1 + 1 + 1 + 8;
429                switch(auth->protocol) {
430                case AUTH_PROTO_TOKEN:
431                        dlen += t->key_len;
432                        break;
433                case AUTH_PROTO_DELAYEDREALM:
434                        if (info && t)
435                                dlen += t->realm_len;
436                        /* FALLTHROUGH */
437                case AUTH_PROTO_DELAYED:
438                        if (info && t)
439                                dlen += sizeof(t->secretid) + sizeof(hmac);
440                        break;
441                }
442                return dlen;
443        }
444
445        if (dlen < 1 + 1 + 1 + 8) {
446                errno = ENOBUFS;
447                return -1;
448        }
449
450        /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
451        if (data < m || data > m + mlen || data + dlen > m + mlen) {
452                errno = ERANGE;
453                return -1;
454        }
455
456        /* Write out our option */
457        *data++ = auth->protocol;
458        *data++ = auth->algorithm;
459        *data++ = auth->rdm;
460        switch (auth->rdm) {
461        case AUTH_RDM_MONOTONIC:
462                rdm = get_next_rdm_monotonic();
463                break;
464        default:
465                /* This block appeases gcc, clang doesn't need it */
466                rdm = get_next_rdm_monotonic();
467                break;
468        }
469        rdm = htonll(rdm);
470        memcpy(data, &rdm, 8);
471        data += 8;
472        dlen -= 1 + 1 + 1 + 8;
473
474        /* Special case as no hashing needs to be done. */
475        if (auth->protocol == AUTH_PROTO_TOKEN) {
476                /* Should be impossible, but still */
477                if (t == NULL) {
478                        errno = EINVAL;
479                        return -1;
480                }
481                if (dlen < t->key_len) {
482                        errno = ENOBUFS;
483                        return -1;
484                }
485                memcpy(data, t->key, t->key_len);
486                return dlen - t->key_len;
487        }
488
489        /* DISCOVER or INFORM messages don't write auth info */
490        if (!info)
491                return dlen;
492
493        /* Loading a saved lease without an authentication option */
494        if (t == NULL)
495                return 0;
496
497        /* Write out the Realm */
498        if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
499                if (dlen < t->realm_len) {
500                        errno = ENOBUFS;
501                        return -1;
502                }
503                memcpy(data, t->realm, t->realm_len);
504                data += t->realm_len;
505                dlen -= t->realm_len;
506        }
507
508        /* Write out the SecretID */
509        if (auth->protocol == AUTH_PROTO_DELAYED ||
510            auth->protocol == AUTH_PROTO_DELAYEDREALM)
511        {
512                if (dlen < sizeof(t->secretid)) {
513                        errno = ENOBUFS;
514                        return -1;
515                }
516                secretid = htonl(t->secretid);
517                memcpy(data, &secretid, sizeof(secretid));
518                data += sizeof(secretid);
519                dlen -= sizeof(secretid);
520        }
521
522        /* Zero what's left, the MAC */
523        memset(data, 0, dlen);
524
525        /* RFC3318, section 5.2 - zero giaddr and hops */
526        if (mp == 4) {
527                p = m + offsetof(struct dhcp_message, hwopcount);
528                hops = *p;
529                *p = '\0';
530                p = m + offsetof(struct dhcp_message, giaddr);
531                memcpy(&giaddr, p, sizeof(giaddr));
532                memset(p, 0, sizeof(giaddr));
533        } else {
534                /* appease GCC again */
535                hops = 0;
536                giaddr = 0;
537        }
538
539        /* Create our hash and write it out */
540        switch(auth->algorithm) {
541        case AUTH_ALG_HMAC_MD5:
542                hmac_md5(m, mlen, t->key, t->key_len, hmac);
543                memcpy(data, hmac, sizeof(hmac));
544                break;
545        }
546
547        /* RFC3318, section 5.2 - restore giaddr and hops */
548        if (mp == 4) {
549                p = m + offsetof(struct dhcp_message, hwopcount);
550                *p = hops;
551                p = m + offsetof(struct dhcp_message, giaddr);
552                memcpy(p, &giaddr, sizeof(giaddr));
553        }
554
555        /* Done! */
556        return dlen - sizeof(hmac); /* should be zero */
557}
Note: See TracBrowser for help on using the repository browser.