1273459Strasz/*- 2332595Strasz * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3332595Strasz * 4273459Strasz * Copyright (c) 2014 The FreeBSD Foundation 5273459Strasz * All rights reserved. 6273459Strasz * 7273459Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 8273459Strasz * from the FreeBSD Foundation. 9273459Strasz * 10273459Strasz * Redistribution and use in source and binary forms, with or without 11273459Strasz * modification, are permitted provided that the following conditions 12273459Strasz * are met: 13273459Strasz * 1. Redistributions of source code must retain the above copyright 14273459Strasz * notice, this list of conditions and the following disclaimer. 15273459Strasz * 2. Redistributions in binary form must reproduce the above copyright 16273459Strasz * notice, this list of conditions and the following disclaimer in the 17273459Strasz * documentation and/or other materials provided with the distribution. 18273459Strasz * 19273459Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20273459Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21273459Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22273459Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23273459Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24273459Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25273459Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26273459Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27273459Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28273459Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29273459Strasz * SUCH DAMAGE. 30273459Strasz * 31273459Strasz */ 32273459Strasz 33273459Strasz#include <sys/cdefs.h> 34273459Strasz__FBSDID("$FreeBSD: stable/11/usr.sbin/ctld/chap.c 332595 2018-04-16 16:14:05Z trasz $"); 35273459Strasz 36273459Strasz#include <assert.h> 37285086Strasz#include <stdlib.h> 38273459Strasz#include <string.h> 39274328Smav#include <netinet/in.h> 40274328Smav#include <resolv.h> 41285086Strasz#include <md5.h> 42273459Strasz 43273459Strasz#include "ctld.h" 44273459Strasz 45273459Straszstatic void 46273459Straszchap_compute_md5(const char id, const char *secret, 47273459Strasz const void *challenge, size_t challenge_len, void *response, 48273459Strasz size_t response_len) 49273459Strasz{ 50273459Strasz MD5_CTX ctx; 51273459Strasz 52285086Strasz assert(response_len == CHAP_DIGEST_LEN); 53273459Strasz 54285086Strasz MD5Init(&ctx); 55285086Strasz MD5Update(&ctx, &id, sizeof(id)); 56285086Strasz MD5Update(&ctx, secret, strlen(secret)); 57285086Strasz MD5Update(&ctx, challenge, challenge_len); 58285086Strasz MD5Final(response, &ctx); 59273459Strasz} 60273459Strasz 61273459Straszstatic int 62273459Straszchap_hex2int(const char hex) 63273459Strasz{ 64273459Strasz switch (hex) { 65273459Strasz case '0': 66273459Strasz return (0x00); 67273459Strasz case '1': 68273459Strasz return (0x01); 69273459Strasz case '2': 70273459Strasz return (0x02); 71273459Strasz case '3': 72273459Strasz return (0x03); 73273459Strasz case '4': 74273459Strasz return (0x04); 75273459Strasz case '5': 76273459Strasz return (0x05); 77273459Strasz case '6': 78273459Strasz return (0x06); 79273459Strasz case '7': 80273459Strasz return (0x07); 81273459Strasz case '8': 82273459Strasz return (0x08); 83273459Strasz case '9': 84273459Strasz return (0x09); 85273459Strasz case 'a': 86273459Strasz case 'A': 87273459Strasz return (0x0a); 88273459Strasz case 'b': 89273459Strasz case 'B': 90273459Strasz return (0x0b); 91273459Strasz case 'c': 92273459Strasz case 'C': 93273459Strasz return (0x0c); 94273459Strasz case 'd': 95273459Strasz case 'D': 96273459Strasz return (0x0d); 97273459Strasz case 'e': 98273459Strasz case 'E': 99273459Strasz return (0x0e); 100273459Strasz case 'f': 101273459Strasz case 'F': 102273459Strasz return (0x0f); 103273459Strasz default: 104273459Strasz return (-1); 105273459Strasz } 106273459Strasz} 107273459Strasz 108274328Smavstatic int 109274328Smavchap_b642bin(const char *b64, void **binp, size_t *bin_lenp) 110274328Smav{ 111274328Smav char *bin; 112274328Smav int b64_len, bin_len; 113274328Smav 114274328Smav b64_len = strlen(b64); 115274328Smav bin_len = (b64_len + 3) / 4 * 3; 116274328Smav bin = calloc(bin_len, 1); 117274328Smav if (bin == NULL) 118274328Smav log_err(1, "calloc"); 119274328Smav 120274328Smav bin_len = b64_pton(b64, bin, bin_len); 121274328Smav if (bin_len < 0) { 122274328Smav log_warnx("malformed base64 variable"); 123274328Smav free(bin); 124274328Smav return (-1); 125274328Smav } 126274328Smav *binp = bin; 127274328Smav *bin_lenp = bin_len; 128274328Smav return (0); 129274328Smav} 130274328Smav 131273459Strasz/* 132273459Strasz * XXX: Review this _carefully_. 133273459Strasz */ 134273459Straszstatic int 135273459Straszchap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) 136273459Strasz{ 137273459Strasz int i, hex_len, nibble; 138273459Strasz bool lo = true; /* As opposed to 'hi'. */ 139273459Strasz char *bin; 140273459Strasz size_t bin_off, bin_len; 141273459Strasz 142274328Smav if (strncasecmp(hex, "0b", strlen("0b")) == 0) 143274328Smav return (chap_b642bin(hex + 2, binp, bin_lenp)); 144274328Smav 145273459Strasz if (strncasecmp(hex, "0x", strlen("0x")) != 0) { 146274328Smav log_warnx("malformed variable, should start with \"0x\"" 147274328Smav " or \"0b\""); 148273459Strasz return (-1); 149273459Strasz } 150273459Strasz 151273459Strasz hex += strlen("0x"); 152273459Strasz hex_len = strlen(hex); 153273459Strasz if (hex_len < 1) { 154273459Strasz log_warnx("malformed variable; doesn't contain anything " 155273459Strasz "but \"0x\""); 156273459Strasz return (-1); 157273459Strasz } 158273459Strasz 159273459Strasz bin_len = hex_len / 2 + hex_len % 2; 160273459Strasz bin = calloc(bin_len, 1); 161273459Strasz if (bin == NULL) 162273459Strasz log_err(1, "calloc"); 163273459Strasz 164273459Strasz bin_off = bin_len - 1; 165273459Strasz for (i = hex_len - 1; i >= 0; i--) { 166273459Strasz nibble = chap_hex2int(hex[i]); 167273459Strasz if (nibble < 0) { 168273459Strasz log_warnx("malformed variable, invalid char \"%c\"", 169273459Strasz hex[i]); 170273459Strasz free(bin); 171273459Strasz return (-1); 172273459Strasz } 173273459Strasz 174273459Strasz assert(bin_off < bin_len); 175273459Strasz if (lo) { 176273459Strasz bin[bin_off] = nibble; 177273459Strasz lo = false; 178273459Strasz } else { 179273459Strasz bin[bin_off] |= nibble << 4; 180273459Strasz bin_off--; 181273459Strasz lo = true; 182273459Strasz } 183273459Strasz } 184273459Strasz 185273459Strasz *binp = bin; 186273459Strasz *bin_lenp = bin_len; 187273459Strasz return (0); 188273459Strasz} 189273459Strasz 190274328Smav#ifdef USE_BASE64 191273459Straszstatic char * 192273459Straszchap_bin2hex(const char *bin, size_t bin_len) 193273459Strasz{ 194274328Smav unsigned char *b64, *tmp; 195274328Smav size_t b64_len; 196274328Smav 197274328Smav b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */ 198274328Smav b64 = malloc(b64_len); 199274328Smav if (b64 == NULL) 200274328Smav log_err(1, "malloc"); 201274328Smav 202274328Smav tmp = b64; 203274328Smav tmp += sprintf(tmp, "0b"); 204274328Smav b64_ntop(bin, bin_len, tmp, b64_len - 2); 205274328Smav 206274328Smav return (b64); 207274328Smav} 208274328Smav#else 209274328Smavstatic char * 210274328Smavchap_bin2hex(const char *bin, size_t bin_len) 211274328Smav{ 212273459Strasz unsigned char *hex, *tmp, ch; 213273459Strasz size_t hex_len; 214273459Strasz size_t i; 215273459Strasz 216273459Strasz hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ 217273459Strasz hex = malloc(hex_len); 218273459Strasz if (hex == NULL) 219273459Strasz log_err(1, "malloc"); 220273459Strasz 221273459Strasz tmp = hex; 222273459Strasz tmp += sprintf(tmp, "0x"); 223273459Strasz for (i = 0; i < bin_len; i++) { 224273459Strasz ch = bin[i]; 225273459Strasz tmp += sprintf(tmp, "%02x", ch); 226273459Strasz } 227273459Strasz 228273459Strasz return (hex); 229273459Strasz} 230274328Smav#endif /* !USE_BASE64 */ 231273459Strasz 232273459Straszstruct chap * 233273459Straszchap_new(void) 234273459Strasz{ 235273459Strasz struct chap *chap; 236273459Strasz 237307697Saraujo chap = calloc(1, sizeof(*chap)); 238273459Strasz if (chap == NULL) 239273459Strasz log_err(1, "calloc"); 240273459Strasz 241273459Strasz /* 242273459Strasz * Generate the challenge. 243273459Strasz */ 244285086Strasz arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge)); 245285086Strasz arc4random_buf(&chap->chap_id, sizeof(chap->chap_id)); 246273459Strasz 247273459Strasz return (chap); 248273459Strasz} 249273459Strasz 250273459Straszchar * 251273459Straszchap_get_id(const struct chap *chap) 252273459Strasz{ 253273459Strasz char *chap_i; 254273459Strasz int ret; 255273459Strasz 256273459Strasz ret = asprintf(&chap_i, "%d", chap->chap_id); 257273459Strasz if (ret < 0) 258273459Strasz log_err(1, "asprintf"); 259273459Strasz 260273459Strasz return (chap_i); 261273459Strasz} 262273459Strasz 263273459Straszchar * 264273459Straszchap_get_challenge(const struct chap *chap) 265273459Strasz{ 266273459Strasz char *chap_c; 267273459Strasz 268273459Strasz chap_c = chap_bin2hex(chap->chap_challenge, 269273459Strasz sizeof(chap->chap_challenge)); 270273459Strasz 271273459Strasz return (chap_c); 272273459Strasz} 273273459Strasz 274273459Straszstatic int 275273459Straszchap_receive_bin(struct chap *chap, void *response, size_t response_len) 276273459Strasz{ 277273459Strasz 278273459Strasz if (response_len != sizeof(chap->chap_response)) { 279273459Strasz log_debugx("got CHAP response with invalid length; " 280273459Strasz "got %zd, should be %zd", 281273459Strasz response_len, sizeof(chap->chap_response)); 282273459Strasz return (1); 283273459Strasz } 284273459Strasz 285273459Strasz memcpy(chap->chap_response, response, response_len); 286273459Strasz return (0); 287273459Strasz} 288273459Strasz 289273459Straszint 290273459Straszchap_receive(struct chap *chap, const char *response) 291273459Strasz{ 292273459Strasz void *response_bin; 293273459Strasz size_t response_bin_len; 294273459Strasz int error; 295273459Strasz 296273459Strasz error = chap_hex2bin(response, &response_bin, &response_bin_len); 297273459Strasz if (error != 0) { 298273459Strasz log_debugx("got incorrectly encoded CHAP response \"%s\"", 299273459Strasz response); 300273459Strasz return (1); 301273459Strasz } 302273459Strasz 303273459Strasz error = chap_receive_bin(chap, response_bin, response_bin_len); 304273459Strasz free(response_bin); 305273459Strasz 306273459Strasz return (error); 307273459Strasz} 308273459Strasz 309273459Straszint 310273459Straszchap_authenticate(struct chap *chap, const char *secret) 311273459Strasz{ 312285086Strasz char expected_response[CHAP_DIGEST_LEN]; 313273459Strasz 314273459Strasz chap_compute_md5(chap->chap_id, secret, 315273459Strasz chap->chap_challenge, sizeof(chap->chap_challenge), 316273459Strasz expected_response, sizeof(expected_response)); 317273459Strasz 318273459Strasz if (memcmp(chap->chap_response, 319273459Strasz expected_response, sizeof(expected_response)) != 0) { 320273459Strasz return (-1); 321273459Strasz } 322273459Strasz 323273459Strasz return (0); 324273459Strasz} 325273459Strasz 326273459Straszvoid 327273459Straszchap_delete(struct chap *chap) 328273459Strasz{ 329273459Strasz 330273459Strasz free(chap); 331273459Strasz} 332273459Strasz 333273459Straszstruct rchap * 334273459Straszrchap_new(const char *secret) 335273459Strasz{ 336273459Strasz struct rchap *rchap; 337273459Strasz 338307697Saraujo rchap = calloc(1, sizeof(*rchap)); 339273459Strasz if (rchap == NULL) 340273459Strasz log_err(1, "calloc"); 341273459Strasz 342273459Strasz rchap->rchap_secret = checked_strdup(secret); 343273459Strasz 344273459Strasz return (rchap); 345273459Strasz} 346273459Strasz 347273459Straszstatic void 348273459Straszrchap_receive_bin(struct rchap *rchap, const unsigned char id, 349273459Strasz const void *challenge, size_t challenge_len) 350273459Strasz{ 351273459Strasz 352273459Strasz rchap->rchap_id = id; 353273459Strasz rchap->rchap_challenge = calloc(challenge_len, 1); 354273459Strasz if (rchap->rchap_challenge == NULL) 355273459Strasz log_err(1, "calloc"); 356273459Strasz memcpy(rchap->rchap_challenge, challenge, challenge_len); 357273459Strasz rchap->rchap_challenge_len = challenge_len; 358273459Strasz} 359273459Strasz 360273459Straszint 361273459Straszrchap_receive(struct rchap *rchap, const char *id, const char *challenge) 362273459Strasz{ 363273459Strasz unsigned char id_bin; 364273459Strasz void *challenge_bin; 365273459Strasz size_t challenge_bin_len; 366273459Strasz 367273459Strasz int error; 368273459Strasz 369273459Strasz id_bin = strtoul(id, NULL, 10); 370273459Strasz 371273459Strasz error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); 372273459Strasz if (error != 0) { 373273459Strasz log_debugx("got incorrectly encoded CHAP challenge \"%s\"", 374273459Strasz challenge); 375273459Strasz return (1); 376273459Strasz } 377273459Strasz 378273459Strasz rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); 379273459Strasz free(challenge_bin); 380273459Strasz 381273459Strasz return (0); 382273459Strasz} 383273459Strasz 384273459Straszstatic void 385273459Straszrchap_get_response_bin(struct rchap *rchap, 386273459Strasz void **responsep, size_t *response_lenp) 387273459Strasz{ 388273459Strasz void *response_bin; 389285086Strasz size_t response_bin_len = CHAP_DIGEST_LEN; 390273459Strasz 391273459Strasz response_bin = calloc(response_bin_len, 1); 392273459Strasz if (response_bin == NULL) 393273459Strasz log_err(1, "calloc"); 394273459Strasz 395273459Strasz chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, 396273459Strasz rchap->rchap_challenge, rchap->rchap_challenge_len, 397273459Strasz response_bin, response_bin_len); 398273459Strasz 399273459Strasz *responsep = response_bin; 400273459Strasz *response_lenp = response_bin_len; 401273459Strasz} 402273459Strasz 403273459Straszchar * 404273459Straszrchap_get_response(struct rchap *rchap) 405273459Strasz{ 406273459Strasz void *response; 407273459Strasz size_t response_len; 408273459Strasz char *chap_r; 409273459Strasz 410273459Strasz rchap_get_response_bin(rchap, &response, &response_len); 411273459Strasz chap_r = chap_bin2hex(response, response_len); 412273459Strasz free(response); 413273459Strasz 414273459Strasz return (chap_r); 415273459Strasz} 416273459Strasz 417273459Straszvoid 418273459Straszrchap_delete(struct rchap *rchap) 419273459Strasz{ 420273459Strasz 421273459Strasz free(rchap->rchap_secret); 422273459Strasz free(rchap->rchap_challenge); 423273459Strasz free(rchap); 424273459Strasz} 425