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