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