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