1273459Strasz/*- 2273459Strasz * Copyright (c) 2014 The FreeBSD Foundation 3273459Strasz * All rights reserved. 4273459Strasz * 5273459Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 6273459Strasz * from the FreeBSD Foundation. 7273459Strasz * 8273459Strasz * Redistribution and use in source and binary forms, with or without 9273459Strasz * modification, are permitted provided that the following conditions 10273459Strasz * are met: 11273459Strasz * 1. Redistributions of source code must retain the above copyright 12273459Strasz * notice, this list of conditions and the following disclaimer. 13273459Strasz * 2. Redistributions in binary form must reproduce the above copyright 14273459Strasz * notice, this list of conditions and the following disclaimer in the 15273459Strasz * documentation and/or other materials provided with the distribution. 16273459Strasz * 17273459Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18273459Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19273459Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20273459Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21273459Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22273459Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23273459Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24273459Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25273459Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26273459Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27273459Strasz * SUCH DAMAGE. 28273459Strasz * 29273459Strasz */ 30273459Strasz 31273459Strasz#include <sys/cdefs.h> 32273459Strasz__FBSDID("$FreeBSD: releng/10.3/usr.sbin/ctld/chap.c 286219 2015-08-03 07:20:33Z trasz $"); 33273459Strasz 34273459Strasz#include <assert.h> 35286219Strasz#include <stdlib.h> 36273459Strasz#include <string.h> 37274909Smav#include <netinet/in.h> 38274909Smav#include <resolv.h> 39286219Strasz#include <md5.h> 40273459Strasz 41273459Strasz#include "ctld.h" 42273459Strasz 43273459Straszstatic void 44273459Straszchap_compute_md5(const char id, const char *secret, 45273459Strasz const void *challenge, size_t challenge_len, void *response, 46273459Strasz size_t response_len) 47273459Strasz{ 48273459Strasz MD5_CTX ctx; 49273459Strasz 50286219Strasz assert(response_len == CHAP_DIGEST_LEN); 51273459Strasz 52286219Strasz MD5Init(&ctx); 53286219Strasz MD5Update(&ctx, &id, sizeof(id)); 54286219Strasz MD5Update(&ctx, secret, strlen(secret)); 55286219Strasz MD5Update(&ctx, challenge, challenge_len); 56286219Strasz MD5Final(response, &ctx); 57273459Strasz} 58273459Strasz 59273459Straszstatic int 60273459Straszchap_hex2int(const char hex) 61273459Strasz{ 62273459Strasz switch (hex) { 63273459Strasz case '0': 64273459Strasz return (0x00); 65273459Strasz case '1': 66273459Strasz return (0x01); 67273459Strasz case '2': 68273459Strasz return (0x02); 69273459Strasz case '3': 70273459Strasz return (0x03); 71273459Strasz case '4': 72273459Strasz return (0x04); 73273459Strasz case '5': 74273459Strasz return (0x05); 75273459Strasz case '6': 76273459Strasz return (0x06); 77273459Strasz case '7': 78273459Strasz return (0x07); 79273459Strasz case '8': 80273459Strasz return (0x08); 81273459Strasz case '9': 82273459Strasz return (0x09); 83273459Strasz case 'a': 84273459Strasz case 'A': 85273459Strasz return (0x0a); 86273459Strasz case 'b': 87273459Strasz case 'B': 88273459Strasz return (0x0b); 89273459Strasz case 'c': 90273459Strasz case 'C': 91273459Strasz return (0x0c); 92273459Strasz case 'd': 93273459Strasz case 'D': 94273459Strasz return (0x0d); 95273459Strasz case 'e': 96273459Strasz case 'E': 97273459Strasz return (0x0e); 98273459Strasz case 'f': 99273459Strasz case 'F': 100273459Strasz return (0x0f); 101273459Strasz default: 102273459Strasz return (-1); 103273459Strasz } 104273459Strasz} 105273459Strasz 106274909Smavstatic int 107274909Smavchap_b642bin(const char *b64, void **binp, size_t *bin_lenp) 108274909Smav{ 109274909Smav char *bin; 110274909Smav int b64_len, bin_len; 111274909Smav 112274909Smav b64_len = strlen(b64); 113274909Smav bin_len = (b64_len + 3) / 4 * 3; 114274909Smav bin = calloc(bin_len, 1); 115274909Smav if (bin == NULL) 116274909Smav log_err(1, "calloc"); 117274909Smav 118274909Smav bin_len = b64_pton(b64, bin, bin_len); 119274909Smav if (bin_len < 0) { 120274909Smav log_warnx("malformed base64 variable"); 121274909Smav free(bin); 122274909Smav return (-1); 123274909Smav } 124274909Smav *binp = bin; 125274909Smav *bin_lenp = bin_len; 126274909Smav return (0); 127274909Smav} 128274909Smav 129273459Strasz/* 130273459Strasz * XXX: Review this _carefully_. 131273459Strasz */ 132273459Straszstatic int 133273459Straszchap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) 134273459Strasz{ 135273459Strasz int i, hex_len, nibble; 136273459Strasz bool lo = true; /* As opposed to 'hi'. */ 137273459Strasz char *bin; 138273459Strasz size_t bin_off, bin_len; 139273459Strasz 140274909Smav if (strncasecmp(hex, "0b", strlen("0b")) == 0) 141274909Smav return (chap_b642bin(hex + 2, binp, bin_lenp)); 142274909Smav 143273459Strasz if (strncasecmp(hex, "0x", strlen("0x")) != 0) { 144274909Smav log_warnx("malformed variable, should start with \"0x\"" 145274909Smav " or \"0b\""); 146273459Strasz return (-1); 147273459Strasz } 148273459Strasz 149273459Strasz hex += strlen("0x"); 150273459Strasz hex_len = strlen(hex); 151273459Strasz if (hex_len < 1) { 152273459Strasz log_warnx("malformed variable; doesn't contain anything " 153273459Strasz "but \"0x\""); 154273459Strasz return (-1); 155273459Strasz } 156273459Strasz 157273459Strasz bin_len = hex_len / 2 + hex_len % 2; 158273459Strasz bin = calloc(bin_len, 1); 159273459Strasz if (bin == NULL) 160273459Strasz log_err(1, "calloc"); 161273459Strasz 162273459Strasz bin_off = bin_len - 1; 163273459Strasz for (i = hex_len - 1; i >= 0; i--) { 164273459Strasz nibble = chap_hex2int(hex[i]); 165273459Strasz if (nibble < 0) { 166273459Strasz log_warnx("malformed variable, invalid char \"%c\"", 167273459Strasz hex[i]); 168273459Strasz free(bin); 169273459Strasz return (-1); 170273459Strasz } 171273459Strasz 172273459Strasz assert(bin_off < bin_len); 173273459Strasz if (lo) { 174273459Strasz bin[bin_off] = nibble; 175273459Strasz lo = false; 176273459Strasz } else { 177273459Strasz bin[bin_off] |= nibble << 4; 178273459Strasz bin_off--; 179273459Strasz lo = true; 180273459Strasz } 181273459Strasz } 182273459Strasz 183273459Strasz *binp = bin; 184273459Strasz *bin_lenp = bin_len; 185273459Strasz return (0); 186273459Strasz} 187273459Strasz 188274909Smav#ifdef USE_BASE64 189273459Straszstatic char * 190273459Straszchap_bin2hex(const char *bin, size_t bin_len) 191273459Strasz{ 192274909Smav unsigned char *b64, *tmp; 193274909Smav size_t b64_len; 194274909Smav 195274909Smav b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */ 196274909Smav b64 = malloc(b64_len); 197274909Smav if (b64 == NULL) 198274909Smav log_err(1, "malloc"); 199274909Smav 200274909Smav tmp = b64; 201274909Smav tmp += sprintf(tmp, "0b"); 202274909Smav b64_ntop(bin, bin_len, tmp, b64_len - 2); 203274909Smav 204274909Smav return (b64); 205274909Smav} 206274909Smav#else 207274909Smavstatic char * 208274909Smavchap_bin2hex(const char *bin, size_t bin_len) 209274909Smav{ 210273459Strasz unsigned char *hex, *tmp, ch; 211273459Strasz size_t hex_len; 212273459Strasz size_t i; 213273459Strasz 214273459Strasz hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ 215273459Strasz hex = malloc(hex_len); 216273459Strasz if (hex == NULL) 217273459Strasz log_err(1, "malloc"); 218273459Strasz 219273459Strasz tmp = hex; 220273459Strasz tmp += sprintf(tmp, "0x"); 221273459Strasz for (i = 0; i < bin_len; i++) { 222273459Strasz ch = bin[i]; 223273459Strasz tmp += sprintf(tmp, "%02x", ch); 224273459Strasz } 225273459Strasz 226273459Strasz return (hex); 227273459Strasz} 228274909Smav#endif /* !USE_BASE64 */ 229273459Strasz 230273459Straszstruct chap * 231273459Straszchap_new(void) 232273459Strasz{ 233273459Strasz struct chap *chap; 234273459Strasz 235273459Strasz chap = calloc(sizeof(*chap), 1); 236273459Strasz if (chap == NULL) 237273459Strasz log_err(1, "calloc"); 238273459Strasz 239273459Strasz /* 240273459Strasz * Generate the challenge. 241273459Strasz */ 242286219Strasz arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge)); 243286219Strasz arc4random_buf(&chap->chap_id, sizeof(chap->chap_id)); 244273459Strasz 245273459Strasz return (chap); 246273459Strasz} 247273459Strasz 248273459Straszchar * 249273459Straszchap_get_id(const struct chap *chap) 250273459Strasz{ 251273459Strasz char *chap_i; 252273459Strasz int ret; 253273459Strasz 254273459Strasz ret = asprintf(&chap_i, "%d", chap->chap_id); 255273459Strasz if (ret < 0) 256273459Strasz log_err(1, "asprintf"); 257273459Strasz 258273459Strasz return (chap_i); 259273459Strasz} 260273459Strasz 261273459Straszchar * 262273459Straszchap_get_challenge(const struct chap *chap) 263273459Strasz{ 264273459Strasz char *chap_c; 265273459Strasz 266273459Strasz chap_c = chap_bin2hex(chap->chap_challenge, 267273459Strasz sizeof(chap->chap_challenge)); 268273459Strasz 269273459Strasz return (chap_c); 270273459Strasz} 271273459Strasz 272273459Straszstatic int 273273459Straszchap_receive_bin(struct chap *chap, void *response, size_t response_len) 274273459Strasz{ 275273459Strasz 276273459Strasz if (response_len != sizeof(chap->chap_response)) { 277273459Strasz log_debugx("got CHAP response with invalid length; " 278273459Strasz "got %zd, should be %zd", 279273459Strasz response_len, sizeof(chap->chap_response)); 280273459Strasz return (1); 281273459Strasz } 282273459Strasz 283273459Strasz memcpy(chap->chap_response, response, response_len); 284273459Strasz return (0); 285273459Strasz} 286273459Strasz 287273459Straszint 288273459Straszchap_receive(struct chap *chap, const char *response) 289273459Strasz{ 290273459Strasz void *response_bin; 291273459Strasz size_t response_bin_len; 292273459Strasz int error; 293273459Strasz 294273459Strasz error = chap_hex2bin(response, &response_bin, &response_bin_len); 295273459Strasz if (error != 0) { 296273459Strasz log_debugx("got incorrectly encoded CHAP response \"%s\"", 297273459Strasz response); 298273459Strasz return (1); 299273459Strasz } 300273459Strasz 301273459Strasz error = chap_receive_bin(chap, response_bin, response_bin_len); 302273459Strasz free(response_bin); 303273459Strasz 304273459Strasz return (error); 305273459Strasz} 306273459Strasz 307273459Straszint 308273459Straszchap_authenticate(struct chap *chap, const char *secret) 309273459Strasz{ 310286219Strasz char expected_response[CHAP_DIGEST_LEN]; 311273459Strasz 312273459Strasz chap_compute_md5(chap->chap_id, secret, 313273459Strasz chap->chap_challenge, sizeof(chap->chap_challenge), 314273459Strasz expected_response, sizeof(expected_response)); 315273459Strasz 316273459Strasz if (memcmp(chap->chap_response, 317273459Strasz expected_response, sizeof(expected_response)) != 0) { 318273459Strasz return (-1); 319273459Strasz } 320273459Strasz 321273459Strasz return (0); 322273459Strasz} 323273459Strasz 324273459Straszvoid 325273459Straszchap_delete(struct chap *chap) 326273459Strasz{ 327273459Strasz 328273459Strasz free(chap); 329273459Strasz} 330273459Strasz 331273459Straszstruct rchap * 332273459Straszrchap_new(const char *secret) 333273459Strasz{ 334273459Strasz struct rchap *rchap; 335273459Strasz 336273459Strasz rchap = calloc(sizeof(*rchap), 1); 337273459Strasz if (rchap == NULL) 338273459Strasz log_err(1, "calloc"); 339273459Strasz 340273459Strasz rchap->rchap_secret = checked_strdup(secret); 341273459Strasz 342273459Strasz return (rchap); 343273459Strasz} 344273459Strasz 345273459Straszstatic void 346273459Straszrchap_receive_bin(struct rchap *rchap, const unsigned char id, 347273459Strasz const void *challenge, size_t challenge_len) 348273459Strasz{ 349273459Strasz 350273459Strasz rchap->rchap_id = id; 351273459Strasz rchap->rchap_challenge = calloc(challenge_len, 1); 352273459Strasz if (rchap->rchap_challenge == NULL) 353273459Strasz log_err(1, "calloc"); 354273459Strasz memcpy(rchap->rchap_challenge, challenge, challenge_len); 355273459Strasz rchap->rchap_challenge_len = challenge_len; 356273459Strasz} 357273459Strasz 358273459Straszint 359273459Straszrchap_receive(struct rchap *rchap, const char *id, const char *challenge) 360273459Strasz{ 361273459Strasz unsigned char id_bin; 362273459Strasz void *challenge_bin; 363273459Strasz size_t challenge_bin_len; 364273459Strasz 365273459Strasz int error; 366273459Strasz 367273459Strasz id_bin = strtoul(id, NULL, 10); 368273459Strasz 369273459Strasz error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); 370273459Strasz if (error != 0) { 371273459Strasz log_debugx("got incorrectly encoded CHAP challenge \"%s\"", 372273459Strasz challenge); 373273459Strasz return (1); 374273459Strasz } 375273459Strasz 376273459Strasz rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); 377273459Strasz free(challenge_bin); 378273459Strasz 379273459Strasz return (0); 380273459Strasz} 381273459Strasz 382273459Straszstatic void 383273459Straszrchap_get_response_bin(struct rchap *rchap, 384273459Strasz void **responsep, size_t *response_lenp) 385273459Strasz{ 386273459Strasz void *response_bin; 387286219Strasz size_t response_bin_len = CHAP_DIGEST_LEN; 388273459Strasz 389273459Strasz response_bin = calloc(response_bin_len, 1); 390273459Strasz if (response_bin == NULL) 391273459Strasz log_err(1, "calloc"); 392273459Strasz 393273459Strasz chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, 394273459Strasz rchap->rchap_challenge, rchap->rchap_challenge_len, 395273459Strasz response_bin, response_bin_len); 396273459Strasz 397273459Strasz *responsep = response_bin; 398273459Strasz *response_lenp = response_bin_len; 399273459Strasz} 400273459Strasz 401273459Straszchar * 402273459Straszrchap_get_response(struct rchap *rchap) 403273459Strasz{ 404273459Strasz void *response; 405273459Strasz size_t response_len; 406273459Strasz char *chap_r; 407273459Strasz 408273459Strasz rchap_get_response_bin(rchap, &response, &response_len); 409273459Strasz chap_r = chap_bin2hex(response, response_len); 410273459Strasz free(response); 411273459Strasz 412273459Strasz return (chap_r); 413273459Strasz} 414273459Strasz 415273459Straszvoid 416273459Straszrchap_delete(struct rchap *rchap) 417273459Strasz{ 418273459Strasz 419273459Strasz free(rchap->rchap_secret); 420273459Strasz free(rchap->rchap_challenge); 421273459Strasz free(rchap); 422273459Strasz} 423