1/*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2014 The FreeBSD Foundation 5 * 6 * This software was developed by Edward Tomasz Napierala under sponsorship 7 * from the FreeBSD Foundation. 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 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD$"); 34 35#include <assert.h> 36#include <stdlib.h> 37#include <string.h> 38#include <netinet/in.h> 39#include <resolv.h> 40#include <md5.h> 41 42#include "ctld.h" 43 44static void 45chap_compute_md5(const char id, const char *secret, 46 const void *challenge, size_t challenge_len, void *response, 47 size_t response_len) 48{ 49 MD5_CTX ctx; 50 51 assert(response_len == CHAP_DIGEST_LEN); 52 53 MD5Init(&ctx); 54 MD5Update(&ctx, &id, sizeof(id)); 55 MD5Update(&ctx, secret, strlen(secret)); 56 MD5Update(&ctx, challenge, challenge_len); 57 MD5Final(response, &ctx); 58} 59 60static int 61chap_hex2int(const char hex) 62{ 63 switch (hex) { 64 case '0': 65 return (0x00); 66 case '1': 67 return (0x01); 68 case '2': 69 return (0x02); 70 case '3': 71 return (0x03); 72 case '4': 73 return (0x04); 74 case '5': 75 return (0x05); 76 case '6': 77 return (0x06); 78 case '7': 79 return (0x07); 80 case '8': 81 return (0x08); 82 case '9': 83 return (0x09); 84 case 'a': 85 case 'A': 86 return (0x0a); 87 case 'b': 88 case 'B': 89 return (0x0b); 90 case 'c': 91 case 'C': 92 return (0x0c); 93 case 'd': 94 case 'D': 95 return (0x0d); 96 case 'e': 97 case 'E': 98 return (0x0e); 99 case 'f': 100 case 'F': 101 return (0x0f); 102 default: 103 return (-1); 104 } 105} 106 107static int 108chap_b642bin(const char *b64, void **binp, size_t *bin_lenp) 109{ 110 char *bin; 111 int b64_len, bin_len; 112 113 b64_len = strlen(b64); 114 bin_len = (b64_len + 3) / 4 * 3; 115 bin = calloc(bin_len, 1); 116 if (bin == NULL) 117 log_err(1, "calloc"); 118 119 bin_len = b64_pton(b64, bin, bin_len); 120 if (bin_len < 0) { 121 log_warnx("malformed base64 variable"); 122 free(bin); 123 return (-1); 124 } 125 *binp = bin; 126 *bin_lenp = bin_len; 127 return (0); 128} 129 130/* 131 * XXX: Review this _carefully_. 132 */ 133static int 134chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) 135{ 136 int i, hex_len, nibble; 137 bool lo = true; /* As opposed to 'hi'. */ 138 char *bin; 139 size_t bin_off, bin_len; 140 141 if (strncasecmp(hex, "0b", strlen("0b")) == 0) 142 return (chap_b642bin(hex + 2, binp, bin_lenp)); 143 144 if (strncasecmp(hex, "0x", strlen("0x")) != 0) { 145 log_warnx("malformed variable, should start with \"0x\"" 146 " or \"0b\""); 147 return (-1); 148 } 149 150 hex += strlen("0x"); 151 hex_len = strlen(hex); 152 if (hex_len < 1) { 153 log_warnx("malformed variable; doesn't contain anything " 154 "but \"0x\""); 155 return (-1); 156 } 157 158 bin_len = hex_len / 2 + hex_len % 2; 159 bin = calloc(bin_len, 1); 160 if (bin == NULL) 161 log_err(1, "calloc"); 162 163 bin_off = bin_len - 1; 164 for (i = hex_len - 1; i >= 0; i--) { 165 nibble = chap_hex2int(hex[i]); 166 if (nibble < 0) { 167 log_warnx("malformed variable, invalid char \"%c\"", 168 hex[i]); 169 free(bin); 170 return (-1); 171 } 172 173 assert(bin_off < bin_len); 174 if (lo) { 175 bin[bin_off] = nibble; 176 lo = false; 177 } else { 178 bin[bin_off] |= nibble << 4; 179 bin_off--; 180 lo = true; 181 } 182 } 183 184 *binp = bin; 185 *bin_lenp = bin_len; 186 return (0); 187} 188 189#ifdef USE_BASE64 190static char * 191chap_bin2hex(const char *bin, size_t bin_len) 192{ 193 unsigned char *b64, *tmp; 194 size_t b64_len; 195 196 b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */ 197 b64 = malloc(b64_len); 198 if (b64 == NULL) 199 log_err(1, "malloc"); 200 201 tmp = b64; 202 tmp += sprintf(tmp, "0b"); 203 b64_ntop(bin, bin_len, tmp, b64_len - 2); 204 205 return (b64); 206} 207#else 208static char * 209chap_bin2hex(const char *bin, size_t bin_len) 210{ 211 unsigned char *hex, *tmp, ch; 212 size_t hex_len; 213 size_t i; 214 215 hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ 216 hex = malloc(hex_len); 217 if (hex == NULL) 218 log_err(1, "malloc"); 219 220 tmp = hex; 221 tmp += sprintf(tmp, "0x"); 222 for (i = 0; i < bin_len; i++) { 223 ch = bin[i]; 224 tmp += sprintf(tmp, "%02x", ch); 225 } 226 227 return (hex); 228} 229#endif /* !USE_BASE64 */ 230 231struct chap * 232chap_new(void) 233{ 234 struct chap *chap; 235 236 chap = calloc(1, sizeof(*chap)); 237 if (chap == NULL) 238 log_err(1, "calloc"); 239 240 /* 241 * Generate the challenge. 242 */ 243 arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge)); 244 arc4random_buf(&chap->chap_id, sizeof(chap->chap_id)); 245 246 return (chap); 247} 248 249char * 250chap_get_id(const struct chap *chap) 251{ 252 char *chap_i; 253 int ret; 254 255 ret = asprintf(&chap_i, "%d", chap->chap_id); 256 if (ret < 0) 257 log_err(1, "asprintf"); 258 259 return (chap_i); 260} 261 262char * 263chap_get_challenge(const struct chap *chap) 264{ 265 char *chap_c; 266 267 chap_c = chap_bin2hex(chap->chap_challenge, 268 sizeof(chap->chap_challenge)); 269 270 return (chap_c); 271} 272 273static int 274chap_receive_bin(struct chap *chap, void *response, size_t response_len) 275{ 276 277 if (response_len != sizeof(chap->chap_response)) { 278 log_debugx("got CHAP response with invalid length; " 279 "got %zd, should be %zd", 280 response_len, sizeof(chap->chap_response)); 281 return (1); 282 } 283 284 memcpy(chap->chap_response, response, response_len); 285 return (0); 286} 287 288int 289chap_receive(struct chap *chap, const char *response) 290{ 291 void *response_bin; 292 size_t response_bin_len; 293 int error; 294 295 error = chap_hex2bin(response, &response_bin, &response_bin_len); 296 if (error != 0) { 297 log_debugx("got incorrectly encoded CHAP response \"%s\"", 298 response); 299 return (1); 300 } 301 302 error = chap_receive_bin(chap, response_bin, response_bin_len); 303 free(response_bin); 304 305 return (error); 306} 307 308int 309chap_authenticate(struct chap *chap, const char *secret) 310{ 311 char expected_response[CHAP_DIGEST_LEN]; 312 313 chap_compute_md5(chap->chap_id, secret, 314 chap->chap_challenge, sizeof(chap->chap_challenge), 315 expected_response, sizeof(expected_response)); 316 317 if (memcmp(chap->chap_response, 318 expected_response, sizeof(expected_response)) != 0) { 319 return (-1); 320 } 321 322 return (0); 323} 324 325void 326chap_delete(struct chap *chap) 327{ 328 329 free(chap); 330} 331 332struct rchap * 333rchap_new(const char *secret) 334{ 335 struct rchap *rchap; 336 337 rchap = calloc(1, sizeof(*rchap)); 338 if (rchap == NULL) 339 log_err(1, "calloc"); 340 341 rchap->rchap_secret = checked_strdup(secret); 342 343 return (rchap); 344} 345 346static void 347rchap_receive_bin(struct rchap *rchap, const unsigned char id, 348 const void *challenge, size_t challenge_len) 349{ 350 351 rchap->rchap_id = id; 352 rchap->rchap_challenge = calloc(challenge_len, 1); 353 if (rchap->rchap_challenge == NULL) 354 log_err(1, "calloc"); 355 memcpy(rchap->rchap_challenge, challenge, challenge_len); 356 rchap->rchap_challenge_len = challenge_len; 357} 358 359int 360rchap_receive(struct rchap *rchap, const char *id, const char *challenge) 361{ 362 unsigned char id_bin; 363 void *challenge_bin; 364 size_t challenge_bin_len; 365 366 int error; 367 368 id_bin = strtoul(id, NULL, 10); 369 370 error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); 371 if (error != 0) { 372 log_debugx("got incorrectly encoded CHAP challenge \"%s\"", 373 challenge); 374 return (1); 375 } 376 377 rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); 378 free(challenge_bin); 379 380 return (0); 381} 382 383static void 384rchap_get_response_bin(struct rchap *rchap, 385 void **responsep, size_t *response_lenp) 386{ 387 void *response_bin; 388 size_t response_bin_len = CHAP_DIGEST_LEN; 389 390 response_bin = calloc(response_bin_len, 1); 391 if (response_bin == NULL) 392 log_err(1, "calloc"); 393 394 chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, 395 rchap->rchap_challenge, rchap->rchap_challenge_len, 396 response_bin, response_bin_len); 397 398 *responsep = response_bin; 399 *response_lenp = response_bin_len; 400} 401 402char * 403rchap_get_response(struct rchap *rchap) 404{ 405 void *response; 406 size_t response_len; 407 char *chap_r; 408 409 rchap_get_response_bin(rchap, &response, &response_len); 410 chap_r = chap_bin2hex(response, response_len); 411 free(response); 412 413 return (chap_r); 414} 415 416void 417rchap_delete(struct rchap *rchap) 418{ 419 420 free(rchap->rchap_secret); 421 free(rchap->rchap_challenge); 422 free(rchap); 423} 424