Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. =================================================================== RCS file: /ftp/cvs/cvsroot/src/external/bsd/dhcpcd/dist/src/auth.c,v rcsdiff: /ftp/cvs/cvsroot/src/external/bsd/dhcpcd/dist/src/auth.c,v: warning: Unknown phrases like `commitid ...;' are present. retrieving revision 1.1.1.5.4.1 retrieving revision 1.1.1.5.4.2 diff -u -p -r1.1.1.5.4.1 -r1.1.1.5.4.2 --- src/external/bsd/dhcpcd/dist/src/auth.c 2018/05/02 22:06:41 1.1.1.5.4.1 +++ src/external/bsd/dhcpcd/dist/src/auth.c 2018/07/27 10:43:20 1.1.1.5.4.2 @@ -0,0 +1,708 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2018 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "auth.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "dhcpcd.h" + +#ifdef HAVE_HMAC_H +#include +#endif + +#ifdef __sun +#define htonll +#define ntohll +#endif + +#ifndef htonll +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define htonll(x) ((uint64_t)htonl((uint32_t)((x) >> 32)) | \ + (uint64_t)htonl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32) +#else /* (BYTE_ORDER == LITTLE_ENDIAN) */ +#define htonll(x) (x) +#endif +#endif /* htonll */ + +#ifndef ntohll +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define ntohll(x) ((uint64_t)ntohl((uint32_t)((x) >> 32)) | \ + (uint64_t)ntohl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32) +#else /* (BYTE_ORDER == LITTLE_ENDIAN) */ +#define ntohll(x) (x) +#endif +#endif /* ntohll */ + +#define HMAC_LENGTH 16 + +void +dhcp_auth_reset(struct authstate *state) +{ + + state->replay = 0; + if (state->token) { + free(state->token->key); + free(state->token->realm); + free(state->token); + state->token = NULL; + } + if (state->reconf) { + free(state->reconf->key); + free(state->reconf->realm); + free(state->reconf); + state->reconf = NULL; + } +} + +/* + * Authenticate a DHCP message. + * m and mlen refer to the whole message. + * t is the DHCP type, pass it 4 or 6. + * data and dlen refer to the authentication option within the message. + */ +const struct token * +dhcp_auth_validate(struct authstate *state, const struct auth *auth, + const void *vm, size_t mlen, int mp, int mt, + const void *vdata, size_t dlen) +{ + const uint8_t *m, *data; + uint8_t protocol, algorithm, rdm, *mm, type; + uint64_t replay; + uint32_t secretid; + const uint8_t *d, *realm; + size_t realm_len; + const struct token *t; + time_t now; + uint8_t hmac_code[HMAC_LENGTH]; + + if (dlen < 3 + sizeof(replay)) { + errno = EINVAL; + return NULL; + } + + m = vm; + data = vdata; + /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */ + if (data < m || data > m + mlen || data + dlen > m + mlen) { + errno = ERANGE; + return NULL; + } + + d = data; + protocol = *d++; + algorithm = *d++; + rdm = *d++; + if (!(auth->options & DHCPCD_AUTH_SEND)) { + /* If we didn't send any authorisation, it can only be a + * reconfigure key */ + if (protocol != AUTH_PROTO_RECONFKEY) { + errno = EINVAL; + return NULL; + } + } else if (protocol != auth->protocol || + algorithm != auth->algorithm || + rdm != auth->rdm) + { + /* As we don't require authentication, we should still + * accept a reconfigure key */ + if (protocol != AUTH_PROTO_RECONFKEY || + auth->options & DHCPCD_AUTH_REQUIRE) + { + errno = EPERM; + return NULL; + } + } + dlen -= 3; + + memcpy(&replay, d, sizeof(replay)); + replay = ntohll(replay); + /* + * Test for a replay attack. + * + * NOTE: Some servers always send a replay data value of zero. + * This is strictly compliant with RFC 3315 and 3318 which say: + * "If the RDM field contains 0x00, the replay detection field MUST be + * set to the value of a monotonically increasing counter." + * An example of a monotonically increasing sequence is: + * 1, 2, 2, 2, 2, 2, 2 + * Errata 3474 updates RFC 3318 to say: + * "If the RDM field contains 0x00, the replay detection field MUST be + * set to the value of a strictly increasing counter." + * + * Taking the above into account, dhcpcd will only test for + * strictly speaking replay attacks if it receives any non zero + * replay data to validate against. + */ + if (state->token && state->replay != 0) { + if (state->replay == (replay ^ 0x8000000000000000ULL)) { + /* We don't know if the singular point is increasing + * or decreasing. */ + errno = EPERM; + return NULL; + } + if ((uint64_t)(replay - state->replay) <= 0) { + /* Replay attack detected */ + errno = EPERM; + return NULL; + } + } + d+= sizeof(replay); + dlen -= sizeof(replay); + + realm = NULL; + realm_len = 0; + + /* Extract realm and secret. + * Rest of data is MAC. */ + switch (protocol) { + case AUTH_PROTO_TOKEN: + secretid = auth->token_rcv_secretid; + break; + case AUTH_PROTO_DELAYED: + if (dlen < sizeof(secretid) + sizeof(hmac_code)) { + errno = EINVAL; + return NULL; + } + memcpy(&secretid, d, sizeof(secretid)); + secretid = ntohl(secretid); + d += sizeof(secretid); + dlen -= sizeof(secretid); + break; + case AUTH_PROTO_DELAYEDREALM: + if (dlen < sizeof(secretid) + sizeof(hmac_code)) { + errno = EINVAL; + return NULL; + } + realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code)); + if (realm_len) { + realm = d; + d += realm_len; + dlen -= realm_len; + } + memcpy(&secretid, d, sizeof(secretid)); + secretid = ntohl(secretid); + d += sizeof(secretid); + dlen -= sizeof(secretid); + break; + case AUTH_PROTO_RECONFKEY: + if (dlen != 1 + 16) { + errno = EINVAL; + return NULL; + } + type = *d++; + dlen--; + switch (type) { + case 1: + if ((mp == 4 && mt == DHCP_ACK) || + (mp == 6 && mt == DHCP6_REPLY)) + { + if (state->reconf == NULL) { + state->reconf = + malloc(sizeof(*state->reconf)); + if (state->reconf == NULL) + return NULL; + state->reconf->key = malloc(16); + if (state->reconf->key == NULL) { + free(state->reconf); + state->reconf = NULL; + return NULL; + } + state->reconf->secretid = 0; + state->reconf->expire = 0; + state->reconf->realm = NULL; + state->reconf->realm_len = 0; + state->reconf->key_len = 16; + } + memcpy(state->reconf->key, d, 16); + } else { + errno = EINVAL; + return NULL; + } + if (state->reconf == NULL) + errno = ENOENT; + /* Free the old token so we log acceptance */ + if (state->token) { + free(state->token); + state->token = NULL; + } + /* Nothing to validate, just accepting the key */ + return state->reconf; + case 2: + if (!((mp == 4 && mt == DHCP_FORCERENEW) || + (mp == 6 && mt == DHCP6_RECONFIGURE))) + { + errno = EINVAL; + return NULL; + } + if (state->reconf == NULL) { + errno = ENOENT; + return NULL; + } + t = state->reconf; + goto gottoken; + default: + errno = EINVAL; + return NULL; + } + default: + errno = ENOTSUP; + return NULL; + } + + /* Find a token for the realm and secret */ + TAILQ_FOREACH(t, &auth->tokens, next) { + if (t->secretid == secretid && + t->realm_len == realm_len && + (t->realm_len == 0 || + memcmp(t->realm, realm, t->realm_len) == 0)) + break; + } + if (t == NULL) { + errno = ESRCH; + return NULL; + } + if (t->expire) { + if (time(&now) == -1) + return NULL; + if (t->expire < now) { + errno = EFAULT; + return NULL; + } + } + +gottoken: + /* First message from the server */ + if (state->token && + (state->token->secretid != t->secretid || + state->token->realm_len != t->realm_len || + memcmp(state->token->realm, t->realm, t->realm_len))) + { + errno = EPERM; + return NULL; + } + + /* Special case as no hashing needs to be done. */ + if (protocol == AUTH_PROTO_TOKEN) { + if (dlen != t->key_len || memcmp(d, t->key, dlen)) { + errno = EPERM; + return NULL; + } + goto finish; + } + + /* Make a duplicate of the message, but zero out the MAC part */ + mm = malloc(mlen); + if (mm == NULL) + return NULL; + memcpy(mm, m, mlen); + memset(mm + (d - m), 0, dlen); + + /* RFC3318, section 5.2 - zero giaddr and hops */ + if (mp == 4) { + /* Assert the bootp structure is correct size. */ + __CTASSERT(sizeof(struct bootp) == 300); + + *(mm + offsetof(struct bootp, hops)) = '\0'; + memset(mm + offsetof(struct bootp, giaddr), 0, 4); + } + + memset(hmac_code, 0, sizeof(hmac_code)); + switch (algorithm) { + case AUTH_ALG_HMAC_MD5: + hmac("md5", t->key, t->key_len, mm, mlen, + hmac_code, sizeof(hmac_code)); + break; + default: + errno = ENOSYS; + free(mm); + return NULL; + } + + free(mm); + if (memcmp(d, &hmac_code, dlen)) { + errno = EPERM; + return NULL; + } + +finish: + /* If we got here then authentication passed */ + state->replay = replay; + if (state->token == NULL) { + /* We cannot just save a pointer because a reconfigure will + * recreate the token list. So we duplicate it. */ + state->token = malloc(sizeof(*state->token)); + if (state->token) { + state->token->secretid = t->secretid; + state->token->key = malloc(t->key_len); + if (state->token->key) { + state->token->key_len = t->key_len; + memcpy(state->token->key, t->key, t->key_len); + } else { + free(state->token); + state->token = NULL; + return NULL; + } + if (t->realm_len) { + state->token->realm = malloc(t->realm_len); + if (state->token->realm) { + state->token->realm_len = t->realm_len; + memcpy(state->token->realm, t->realm, + t->realm_len); + } else { + free(state->token->key); + free(state->token); + state->token = NULL; + return NULL; + } + } else { + state->token->realm = NULL; + state->token->realm_len = 0; + } + } + /* If we cannot save the token, we must invalidate */ + if (state->token == NULL) + return NULL; + } + + return t; +} + +static uint64_t +get_next_rdm_monotonic_counter(struct auth *auth) +{ + FILE *fp; + uint64_t rdm; +#ifdef LOCK_EX + int flocked; +#endif + + fp = fopen(RDM_MONOFILE, "r+"); + if (fp == NULL) { + if (errno != ENOENT) + return ++auth->last_replay; /* report error? */ + fp = fopen(RDM_MONOFILE, "w"); + if (fp == NULL) + return ++auth->last_replay; /* report error? */ +#ifdef LOCK_EX + flocked = flock(fileno(fp), LOCK_EX); +#endif + rdm = 0; + } else { +#ifdef LOCK_EX + flocked = flock(fileno(fp), LOCK_EX); +#endif + if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1) + rdm = 0; /* truncated? report error? */ + } + + rdm++; + if (fseek(fp, 0, SEEK_SET) == -1 || + ftruncate(fileno(fp), 0) == -1 || + fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 || + fflush(fp) == EOF) + { + if (!auth->last_replay_set) { + auth->last_replay = rdm; + auth->last_replay_set = 1; + } else + rdm = ++auth->last_replay; + /* report error? */ + } +#ifdef LOCK_EX + if (flocked == 0) + flock(fileno(fp), LOCK_UN); +#endif + fclose(fp); + return rdm; +} + +#define NTP_EPOCH 2208988800U /* 1970 - 1900 in seconds */ +#define NTP_SCALE_FRAC 4294967295.0 /* max value of the fractional part */ +static uint64_t +get_next_rdm_monotonic_clock(struct auth *auth) +{ + struct timespec ts; + uint64_t secs, rdm; + double frac; + + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) + return ++auth->last_replay; /* report error? */ + + secs = (uint64_t)ts.tv_sec + NTP_EPOCH; + frac = ((double)ts.tv_nsec / 1e9 * NTP_SCALE_FRAC); + rdm = (secs << 32) | (uint64_t)frac; + return rdm; +} + +static uint64_t +get_next_rdm_monotonic(struct auth *auth) +{ + + if (auth->options & DHCPCD_AUTH_RDM_COUNTER) + return get_next_rdm_monotonic_counter(auth); + return get_next_rdm_monotonic_clock(auth); +} + +/* + * Encode a DHCP message. + * Either we know which token to use from the server response + * or we are using a basic configuration token. + * token is the token to encrypt with. + * m and mlen refer to the whole message. + * mp is the DHCP type, pass it 4 or 6. + * mt is the DHCP message type. + * data and dlen refer to the authentication option within the message. + */ +ssize_t +dhcp_auth_encode(struct auth *auth, const struct token *t, + void *vm, size_t mlen, int mp, int mt, + void *vdata, size_t dlen) +{ + uint64_t rdm; + uint8_t hmac_code[HMAC_LENGTH]; + time_t now; + uint8_t hops, *p, *m, *data; + uint32_t giaddr, secretid; + bool auth_info; + + /* Ignore the token argument given to us - always send using the + * configured token. */ + if (auth->protocol == AUTH_PROTO_TOKEN) { + TAILQ_FOREACH(t, &auth->tokens, next) { + if (t->secretid == auth->token_snd_secretid) + break; + } + if (t == NULL) { + errno = EINVAL; + return -1; + } + if (t->expire) { + if (time(&now) == -1) + return -1; + if (t->expire < now) { + errno = EPERM; + return -1; + } + } + } + + switch(auth->protocol) { + case AUTH_PROTO_TOKEN: + case AUTH_PROTO_DELAYED: + case AUTH_PROTO_DELAYEDREALM: + /* We don't ever send a reconf key */ + break; + default: + errno = ENOTSUP; + return -1; + } + + switch(auth->algorithm) { + case AUTH_ALG_NONE: + case AUTH_ALG_HMAC_MD5: + break; + default: + errno = ENOTSUP; + return -1; + } + + switch(auth->rdm) { + case AUTH_RDM_MONOTONIC: + break; + default: + errno = ENOTSUP; + return -1; + } + + /* DISCOVER or INFORM messages don't write auth info */ + if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) || + (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ))) + auth_info = false; + else + auth_info = true; + + /* Work out the auth area size. + * We only need to do this for DISCOVER messages */ + if (vdata == NULL) { + dlen = 1 + 1 + 1 + 8; + switch(auth->protocol) { + case AUTH_PROTO_TOKEN: + dlen += t->key_len; + break; + case AUTH_PROTO_DELAYEDREALM: + if (auth_info && t) + dlen += t->realm_len; + /* FALLTHROUGH */ + case AUTH_PROTO_DELAYED: + if (auth_info && t) + dlen += sizeof(t->secretid) + sizeof(hmac_code); + break; + } + return (ssize_t)dlen; + } + + if (dlen < 1 + 1 + 1 + 8) { + errno = ENOBUFS; + return -1; + } + + /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */ + m = vm; + data = vdata; + if (data < m || data > m + mlen || data + dlen > m + mlen) { + errno = ERANGE; + return -1; + } + + /* Write out our option */ + *data++ = auth->protocol; + *data++ = auth->algorithm; + /* + * RFC 3315 21.4.4.1 says that SOLICIT in DELAYED authentication + * should not set RDM or it's data. + * An expired draft draft-ietf-dhc-dhcpv6-clarify-auth-01 suggets + * this should not be set for INFORMATION REQ messages as well, + * which is probably a good idea because both states start from zero. + */ + if (auth_info || + !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM))) + { + *data++ = auth->rdm; + switch (auth->rdm) { + case AUTH_RDM_MONOTONIC: + rdm = get_next_rdm_monotonic(auth); + break; + default: + /* This block appeases gcc, clang doesn't need it */ + rdm = get_next_rdm_monotonic(auth); + break; + } + rdm = htonll(rdm); + memcpy(data, &rdm, 8); + } else { + *data++ = 0; /* rdm */ + memset(data, 0, 8); /* replay detection data */ + } + data += 8; + dlen -= 1 + 1 + 1 + 8; + + /* Special case as no hashing needs to be done. */ + if (auth->protocol == AUTH_PROTO_TOKEN) { + /* Should be impossible, but still */ + if (t == NULL) { + errno = EINVAL; + return -1; + } + if (dlen < t->key_len) { + errno = ENOBUFS; + return -1; + } + memcpy(data, t->key, t->key_len); + return (ssize_t)(dlen - t->key_len); + } + + /* DISCOVER or INFORM messages don't write auth info */ + if (!auth_info) + return (ssize_t)dlen; + + /* Loading a saved lease without an authentication option */ + if (t == NULL) + return 0; + + /* Write out the Realm */ + if (auth->protocol == AUTH_PROTO_DELAYEDREALM) { + if (dlen < t->realm_len) { + errno = ENOBUFS; + return -1; + } + memcpy(data, t->realm, t->realm_len); + data += t->realm_len; + dlen -= t->realm_len; + } + + /* Write out the SecretID */ + if (auth->protocol == AUTH_PROTO_DELAYED || + auth->protocol == AUTH_PROTO_DELAYEDREALM) + { + if (dlen < sizeof(t->secretid)) { + errno = ENOBUFS; + return -1; + } + secretid = htonl(t->secretid); + memcpy(data, &secretid, sizeof(secretid)); + data += sizeof(secretid); + dlen -= sizeof(secretid); + } + + /* Zero what's left, the MAC */ + memset(data, 0, dlen); + + /* RFC3318, section 5.2 - zero giaddr and hops */ + if (mp == 4) { + p = m + offsetof(struct bootp, hops); + hops = *p; + *p = '\0'; + p = m + offsetof(struct bootp, giaddr); + memcpy(&giaddr, p, sizeof(giaddr)); + memset(p, 0, sizeof(giaddr)); + } else { + /* appease GCC again */ + hops = 0; + giaddr = 0; + } + + /* Create our hash and write it out */ + switch(auth->algorithm) { + case AUTH_ALG_HMAC_MD5: + hmac("md5", t->key, t->key_len, m, mlen, + hmac_code, sizeof(hmac_code)); + memcpy(data, hmac_code, sizeof(hmac_code)); + break; + } + + /* RFC3318, section 5.2 - restore giaddr and hops */ + if (mp == 4) { + p = m + offsetof(struct bootp, hops); + *p = hops; + p = m + offsetof(struct bootp, giaddr); + memcpy(p, &giaddr, sizeof(giaddr)); + } + + /* Done! */ + return (int)(dlen - sizeof(hmac_code)); /* should be zero */ +}