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