From 45078155e3aea5ffb03d0b1d61f76a8f1dd89bf7 Mon Sep 17 00:00:00 2001 From: Edward Tomasz Napierala Date: Wed, 22 Oct 2014 08:59:23 +0000 Subject: [PATCH] Untangle iSCSI authentication code by splitting off the CHAP implementation. Reviewed by: mav@ MFC after: 1 month Sponsored by: The FreeBSD Foundation --- usr.sbin/ctld/Makefile | 2 +- usr.sbin/ctld/chap.c | 386 +++++++++++++++++++++++++++++++++++++++ usr.sbin/ctld/ctld.h | 30 +++ usr.sbin/ctld/login.c | 226 +++-------------------- usr.sbin/iscsid/Makefile | 2 +- usr.sbin/iscsid/chap.c | 386 +++++++++++++++++++++++++++++++++++++++ usr.sbin/iscsid/iscsid.h | 33 +++- usr.sbin/iscsid/login.c | 221 ++++------------------ 8 files changed, 893 insertions(+), 393 deletions(-) create mode 100644 usr.sbin/ctld/chap.c create mode 100644 usr.sbin/iscsid/chap.c diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile index 2b998d8131ec..09b89cb08215 100644 --- a/usr.sbin/ctld/Makefile +++ b/usr.sbin/ctld/Makefile @@ -1,7 +1,7 @@ # $FreeBSD$ PROG= ctld -SRCS= ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h +SRCS= chap.c ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h CFLAGS+= -I${.CURDIR} CFLAGS+= -I${.CURDIR}/../../sys CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl diff --git a/usr.sbin/ctld/chap.c b/usr.sbin/ctld/chap.c new file mode 100644 index 000000000000..635ab8cb0249 --- /dev/null +++ b/usr.sbin/ctld/chap.c @@ -0,0 +1,386 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include "ctld.h" + +static void +chap_compute_md5(const char id, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + int rv; + + assert(response_len == MD5_DIGEST_LENGTH); + + MD5_Init(&ctx); + MD5_Update(&ctx, &id, sizeof(id)); + MD5_Update(&ctx, secret, strlen(secret)); + MD5_Update(&ctx, challenge, challenge_len); + rv = MD5_Final(response, &ctx); + if (rv != 1) + log_errx(1, "MD5_Final"); +} + +static int +chap_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = chap_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + free(bin); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +chap_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +struct chap * +chap_new(void) +{ + struct chap *chap; + int rv; + + chap = calloc(sizeof(*chap), 1); + if (chap == NULL) + log_err(1, "calloc"); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(chap->chap_challenge, sizeof(chap->chap_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&chap->chap_id, sizeof(chap->chap_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + return (chap); +} + +char * +chap_get_id(const struct chap *chap) +{ + char *chap_i; + int ret; + + ret = asprintf(&chap_i, "%d", chap->chap_id); + if (ret < 0) + log_err(1, "asprintf"); + + return (chap_i); +} + +char * +chap_get_challenge(const struct chap *chap) +{ + char *chap_c; + + chap_c = chap_bin2hex(chap->chap_challenge, + sizeof(chap->chap_challenge)); + + return (chap_c); +} + +static int +chap_receive_bin(struct chap *chap, void *response, size_t response_len) +{ + + if (response_len != sizeof(chap->chap_response)) { + log_debugx("got CHAP response with invalid length; " + "got %zd, should be %zd", + response_len, sizeof(chap->chap_response)); + return (1); + } + + memcpy(chap->chap_response, response, response_len); + return (0); +} + +int +chap_receive(struct chap *chap, const char *response) +{ + void *response_bin; + size_t response_bin_len; + int error; + + error = chap_hex2bin(response, &response_bin, &response_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP response \"%s\"", + response); + return (1); + } + + error = chap_receive_bin(chap, response_bin, response_bin_len); + free(response_bin); + + return (error); +} + +int +chap_authenticate(struct chap *chap, const char *secret) +{ + char expected_response[MD5_DIGEST_LENGTH]; + + chap_compute_md5(chap->chap_id, secret, + chap->chap_challenge, sizeof(chap->chap_challenge), + expected_response, sizeof(expected_response)); + + if (memcmp(chap->chap_response, + expected_response, sizeof(expected_response)) != 0) { + return (-1); + } + + return (0); +} + +void +chap_delete(struct chap *chap) +{ + + free(chap); +} + +struct rchap * +rchap_new(const char *secret) +{ + struct rchap *rchap; + + rchap = calloc(sizeof(*rchap), 1); + if (rchap == NULL) + log_err(1, "calloc"); + + rchap->rchap_secret = checked_strdup(secret); + + return (rchap); +} + +static void +rchap_receive_bin(struct rchap *rchap, const unsigned char id, + const void *challenge, size_t challenge_len) +{ + + rchap->rchap_id = id; + rchap->rchap_challenge = calloc(challenge_len, 1); + if (rchap->rchap_challenge == NULL) + log_err(1, "calloc"); + memcpy(rchap->rchap_challenge, challenge, challenge_len); + rchap->rchap_challenge_len = challenge_len; +} + +int +rchap_receive(struct rchap *rchap, const char *id, const char *challenge) +{ + unsigned char id_bin; + void *challenge_bin; + size_t challenge_bin_len; + + int error; + + id_bin = strtoul(id, NULL, 10); + + error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP challenge \"%s\"", + challenge); + return (1); + } + + rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); + free(challenge_bin); + + return (0); +} + +static void +rchap_get_response_bin(struct rchap *rchap, + void **responsep, size_t *response_lenp) +{ + void *response_bin; + size_t response_bin_len = MD5_DIGEST_LENGTH; + + response_bin = calloc(response_bin_len, 1); + if (response_bin == NULL) + log_err(1, "calloc"); + + chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, + rchap->rchap_challenge, rchap->rchap_challenge_len, + response_bin, response_bin_len); + + *responsep = response_bin; + *response_lenp = response_bin_len; +} + +char * +rchap_get_response(struct rchap *rchap) +{ + void *response; + size_t response_len; + char *chap_r; + + rchap_get_response_bin(rchap, &response, &response_len); + chap_r = chap_bin2hex(response, response_len); + free(response); + + return (chap_r); +} + +void +rchap_delete(struct rchap *rchap) +{ + + free(rchap->rchap_secret); + free(rchap->rchap_challenge); + free(rchap); +} diff --git a/usr.sbin/ctld/ctld.h b/usr.sbin/ctld/ctld.h index 98d612f3fe2a..e0eb9139db2a 100644 --- a/usr.sbin/ctld/ctld.h +++ b/usr.sbin/ctld/ctld.h @@ -39,6 +39,7 @@ #include #include #include +#include #define DEFAULT_CONFIG_PATH "/etc/ctl.conf" #define DEFAULT_PIDFILE "/var/run/ctld.pid" @@ -207,6 +208,35 @@ struct keys { size_t keys_data_len; }; +#define CHAP_CHALLENGE_LEN 1024 + +struct chap { + unsigned char chap_id; + char chap_challenge[CHAP_CHALLENGE_LEN]; + char chap_response[MD5_DIGEST_LENGTH]; +}; + +struct rchap { + char *rchap_secret; + unsigned char rchap_id; + void *rchap_challenge; + size_t rchap_challenge_len; +}; + +struct chap *chap_new(void); +char *chap_get_id(const struct chap *chap); +char *chap_get_challenge(const struct chap *chap); +int chap_receive(struct chap *chap, const char *response); +int chap_authenticate(struct chap *chap, + const char *secret); +void chap_delete(struct chap *chap); + +struct rchap *rchap_new(const char *secret); +int rchap_receive(struct rchap *rchap, + const char *id, const char *challenge); +char *rchap_get_response(struct rchap *rchap); +void rchap_delete(struct rchap *rchap); + struct conf *conf_new(void); struct conf *conf_new_from_file(const char *path); struct conf *conf_new_from_kernel(void); diff --git a/usr.sbin/ctld/login.c b/usr.sbin/ctld/login.c index 209582bed99e..d6fd1ffa3c45 100644 --- a/usr.sbin/ctld/login.c +++ b/usr.sbin/ctld/login.c @@ -39,9 +39,6 @@ __FBSDID("$FreeBSD$"); #include #include #include -#include -#include -#include #include "ctld.h" #include "iscsi_proto.h" @@ -229,150 +226,6 @@ login_list_prefers(const char *list, return (-1); } -static int -login_hex2int(const char hex) -{ - switch (hex) { - case '0': - return (0x00); - case '1': - return (0x01); - case '2': - return (0x02); - case '3': - return (0x03); - case '4': - return (0x04); - case '5': - return (0x05); - case '6': - return (0x06); - case '7': - return (0x07); - case '8': - return (0x08); - case '9': - return (0x09); - case 'a': - case 'A': - return (0x0a); - case 'b': - case 'B': - return (0x0b); - case 'c': - case 'C': - return (0x0c); - case 'd': - case 'D': - return (0x0d); - case 'e': - case 'E': - return (0x0e); - case 'f': - case 'F': - return (0x0f); - default: - return (-1); - } -} - -/* - * XXX: Review this _carefully_. - */ -static int -login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) -{ - int i, hex_len, nibble; - bool lo = true; /* As opposed to 'hi'. */ - char *bin; - size_t bin_off, bin_len; - - if (strncasecmp(hex, "0x", strlen("0x")) != 0) { - log_warnx("malformed variable, should start with \"0x\""); - return (-1); - } - - hex += strlen("0x"); - hex_len = strlen(hex); - if (hex_len < 1) { - log_warnx("malformed variable; doesn't contain anything " - "but \"0x\""); - return (-1); - } - - bin_len = hex_len / 2 + hex_len % 2; - bin = calloc(bin_len, 1); - if (bin == NULL) - log_err(1, "calloc"); - - bin_off = bin_len - 1; - for (i = hex_len - 1; i >= 0; i--) { - nibble = login_hex2int(hex[i]); - if (nibble < 0) { - log_warnx("malformed variable, invalid char \"%c\"", - hex[i]); - return (-1); - } - - assert(bin_off < bin_len); - if (lo) { - bin[bin_off] = nibble; - lo = false; - } else { - bin[bin_off] |= nibble << 4; - bin_off--; - lo = true; - } - } - - *binp = bin; - *bin_lenp = bin_len; - return (0); -} - -static char * -login_bin2hex(const char *bin, size_t bin_len) -{ - unsigned char *hex, *tmp, ch; - size_t hex_len; - size_t i; - - hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ - hex = malloc(hex_len); - if (hex == NULL) - log_err(1, "malloc"); - - tmp = hex; - tmp += sprintf(tmp, "0x"); - for (i = 0; i < bin_len; i++) { - ch = bin[i]; - tmp += sprintf(tmp, "%02x", ch); - } - - return (hex); -} - -static void -login_compute_md5(const char id, const char *secret, - const void *challenge, size_t challenge_len, void *response, - size_t response_len) -{ - MD5_CTX ctx; - int rv; - - assert(response_len == MD5_DIGEST_LENGTH); - - MD5_Init(&ctx); - MD5_Update(&ctx, &id, sizeof(id)); - MD5_Update(&ctx, secret, strlen(secret)); - MD5_Update(&ctx, challenge, challenge_len); - rv = MD5_Final(response, &ctx); - if (rv != 1) - log_errx(1, "MD5_Final"); -} - -#define LOGIN_CHALLENGE_LEN 1024 - static struct pdu * login_receive_chap_a(struct connection *conn) { @@ -400,21 +253,21 @@ login_receive_chap_a(struct connection *conn) } static void -login_send_chap_c(struct pdu *request, const unsigned char id, - const void *challenge, const size_t challenge_len) +login_send_chap_c(struct pdu *request, struct chap *chap) { struct pdu *response; struct keys *response_keys; - char *chap_c, chap_i[4]; + char *chap_c, *chap_i; - chap_c = login_bin2hex(challenge, challenge_len); - snprintf(chap_i, sizeof(chap_i), "%d", id); + chap_c = chap_get_challenge(chap); + chap_i = chap_get_id(chap); response = login_new_response(request); response_keys = keys_new(); keys_add(response_keys, "CHAP_A", "5"); keys_add(response_keys, "CHAP_I", chap_i); keys_add(response_keys, "CHAP_C", chap_c); + free(chap_i); free(chap_c); keys_save(response_keys, response); pdu_send(response); @@ -423,15 +276,12 @@ login_send_chap_c(struct pdu *request, const unsigned char id, } static struct pdu * -login_receive_chap_r(struct connection *conn, - struct auth_group *ag, const unsigned char id, const void *challenge, - const size_t challenge_len, const struct auth **cap) +login_receive_chap_r(struct connection *conn, struct auth_group *ag, + struct chap *chap, const struct auth **authp) { struct pdu *request; struct keys *request_keys; const char *chap_n, *chap_r; - char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; - size_t response_bin_len; const struct auth *auth; int error; @@ -449,7 +299,7 @@ login_receive_chap_r(struct connection *conn, login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU without CHAP_R"); } - error = login_hex2bin(chap_r, &response_bin, &response_bin_len); + error = chap_receive(chap, chap_r); if (error != 0) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU with malformed CHAP_R"); @@ -469,21 +319,17 @@ login_receive_chap_r(struct connection *conn, assert(auth->a_secret != NULL); assert(strlen(auth->a_secret) > 0); - login_compute_md5(id, auth->a_secret, challenge, - challenge_len, expected_response_bin, - sizeof(expected_response_bin)); - if (memcmp(response_bin, expected_response_bin, - sizeof(expected_response_bin)) != 0) { + error = chap_authenticate(chap, auth->a_secret); + if (error != 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "CHAP authentication failed for user \"%s\"", auth->a_user); } keys_delete(request_keys); - free(response_bin); - *cap = auth; + *authp = auth; return (request); } @@ -494,10 +340,9 @@ login_send_chap_success(struct pdu *request, struct pdu *response; struct keys *request_keys, *response_keys; struct iscsi_bhs_login_response *bhslr2; + struct rchap *rchap; const char *chap_i, *chap_c; - char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; - size_t challenge_len; - unsigned char id; + char *chap_r; int error; response = login_new_response(request); @@ -530,21 +375,18 @@ login_send_chap_success(struct pdu *request, "is not set", auth->a_user); } - id = strtoul(chap_i, NULL, 10); - error = login_hex2bin(chap_c, &challenge, &challenge_len); + log_debugx("performing mutual authentication as user \"%s\"", + auth->a_mutual_user); + + rchap = rchap_new(auth->a_mutual_secret); + error = rchap_receive(rchap, chap_i, chap_c); if (error != 0) { login_send_error(request, 0x02, 0x07); log_errx(1, "received CHAP Login PDU with malformed " - "CHAP_C"); + "CHAP_I or CHAP_C"); } - - log_debugx("performing mutual authentication as user \"%s\"", - auth->a_mutual_user); - login_compute_md5(id, auth->a_mutual_secret, challenge, - challenge_len, response_bin, sizeof(response_bin)); - - chap_r = login_bin2hex(response_bin, - sizeof(response_bin)); + chap_r = rchap_get_response(rchap); + rchap_delete(rchap); response_keys = keys_new(); keys_add(response_keys, "CHAP_N", auth->a_mutual_user); keys_add(response_keys, "CHAP_R", chap_r); @@ -564,10 +406,8 @@ static void login_chap(struct connection *conn, struct auth_group *ag) { const struct auth *auth; + struct chap *chap; struct pdu *request; - char challenge_bin[LOGIN_CHALLENGE_LEN]; - unsigned char id; - int rv; /* * Receive CHAP_A PDU. @@ -578,34 +418,21 @@ login_chap(struct connection *conn, struct auth_group *ag) /* * Generate the challenge. */ - rv = RAND_bytes(challenge_bin, sizeof(challenge_bin)); - if (rv != 1) { - login_send_error(request, 0x03, 0x02); - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - rv = RAND_bytes(&id, sizeof(id)); - if (rv != 1) { - login_send_error(request, 0x03, 0x02); - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } + chap = chap_new(); /* * Send the challenge. */ log_debugx("sending CHAP_C, binary challenge size is %zd bytes", - sizeof(challenge_bin)); - login_send_chap_c(request, id, challenge_bin, - sizeof(challenge_bin)); + sizeof(chap->chap_challenge)); + login_send_chap_c(request, chap); pdu_delete(request); /* * Receive CHAP_N/CHAP_R PDU and authenticate. */ log_debugx("waiting for CHAP_N/CHAP_R"); - request = login_receive_chap_r(conn, ag, id, challenge_bin, - sizeof(challenge_bin), &auth); + request = login_receive_chap_r(conn, ag, chap, &auth); /* * Yay, authentication succeeded! @@ -614,6 +441,7 @@ login_chap(struct connection *conn, struct auth_group *ag) "transitioning to Negotiation Phase", auth->a_user); login_send_chap_success(request, auth); pdu_delete(request); + chap_delete(chap); } static void diff --git a/usr.sbin/iscsid/Makefile b/usr.sbin/iscsid/Makefile index e60010e7c5d1..5b79e307becd 100644 --- a/usr.sbin/iscsid/Makefile +++ b/usr.sbin/iscsid/Makefile @@ -1,7 +1,7 @@ # $FreeBSD$ PROG= iscsid -SRCS= discovery.c iscsid.c keys.c log.c login.c pdu.c +SRCS= chap.c discovery.c iscsid.c keys.c log.c login.c pdu.c CFLAGS+= -I${.CURDIR} CFLAGS+= -I${.CURDIR}/../../sys/cam CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi diff --git a/usr.sbin/iscsid/chap.c b/usr.sbin/iscsid/chap.c new file mode 100644 index 000000000000..abc9a18d7e1e --- /dev/null +++ b/usr.sbin/iscsid/chap.c @@ -0,0 +1,386 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include "iscsid.h" + +static void +chap_compute_md5(const char id, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + int rv; + + assert(response_len == MD5_DIGEST_LENGTH); + + MD5_Init(&ctx); + MD5_Update(&ctx, &id, sizeof(id)); + MD5_Update(&ctx, secret, strlen(secret)); + MD5_Update(&ctx, challenge, challenge_len); + rv = MD5_Final(response, &ctx); + if (rv != 1) + log_errx(1, "MD5_Final"); +} + +static int +chap_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = chap_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + free(bin); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +chap_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +struct chap * +chap_new(void) +{ + struct chap *chap; + int rv; + + chap = calloc(sizeof(*chap), 1); + if (chap == NULL) + log_err(1, "calloc"); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(chap->chap_challenge, sizeof(chap->chap_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&chap->chap_id, sizeof(chap->chap_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + return (chap); +} + +char * +chap_get_id(const struct chap *chap) +{ + char *chap_i; + int ret; + + ret = asprintf(&chap_i, "%d", chap->chap_id); + if (ret < 0) + log_err(1, "asprintf"); + + return (chap_i); +} + +char * +chap_get_challenge(const struct chap *chap) +{ + char *chap_c; + + chap_c = chap_bin2hex(chap->chap_challenge, + sizeof(chap->chap_challenge)); + + return (chap_c); +} + +static int +chap_receive_bin(struct chap *chap, void *response, size_t response_len) +{ + + if (response_len != sizeof(chap->chap_response)) { + log_debugx("got CHAP response with invalid length; " + "got %zd, should be %zd", + response_len, sizeof(chap->chap_response)); + return (1); + } + + memcpy(chap->chap_response, response, response_len); + return (0); +} + +int +chap_receive(struct chap *chap, const char *response) +{ + void *response_bin; + size_t response_bin_len; + int error; + + error = chap_hex2bin(response, &response_bin, &response_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP response \"%s\"", + response); + return (1); + } + + error = chap_receive_bin(chap, response_bin, response_bin_len); + free(response_bin); + + return (error); +} + +int +chap_authenticate(struct chap *chap, const char *secret) +{ + char expected_response[MD5_DIGEST_LENGTH]; + + chap_compute_md5(chap->chap_id, secret, + chap->chap_challenge, sizeof(chap->chap_challenge), + expected_response, sizeof(expected_response)); + + if (memcmp(chap->chap_response, + expected_response, sizeof(expected_response)) != 0) { + return (-1); + } + + return (0); +} + +void +chap_delete(struct chap *chap) +{ + + free(chap); +} + +struct rchap * +rchap_new(const char *secret) +{ + struct rchap *rchap; + + rchap = calloc(sizeof(*rchap), 1); + if (rchap == NULL) + log_err(1, "calloc"); + + rchap->rchap_secret = checked_strdup(secret); + + return (rchap); +} + +static void +rchap_receive_bin(struct rchap *rchap, const unsigned char id, + const void *challenge, size_t challenge_len) +{ + + rchap->rchap_id = id; + rchap->rchap_challenge = calloc(challenge_len, 1); + if (rchap->rchap_challenge == NULL) + log_err(1, "calloc"); + memcpy(rchap->rchap_challenge, challenge, challenge_len); + rchap->rchap_challenge_len = challenge_len; +} + +int +rchap_receive(struct rchap *rchap, const char *id, const char *challenge) +{ + unsigned char id_bin; + void *challenge_bin; + size_t challenge_bin_len; + + int error; + + id_bin = strtoul(id, NULL, 10); + + error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); + if (error != 0) { + log_debugx("got incorrectly encoded CHAP challenge \"%s\"", + challenge); + return (1); + } + + rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len); + free(challenge_bin); + + return (0); +} + +static void +rchap_get_response_bin(struct rchap *rchap, + void **responsep, size_t *response_lenp) +{ + void *response_bin; + size_t response_bin_len = MD5_DIGEST_LENGTH; + + response_bin = calloc(response_bin_len, 1); + if (response_bin == NULL) + log_err(1, "calloc"); + + chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, + rchap->rchap_challenge, rchap->rchap_challenge_len, + response_bin, response_bin_len); + + *responsep = response_bin; + *response_lenp = response_bin_len; +} + +char * +rchap_get_response(struct rchap *rchap) +{ + void *response; + size_t response_len; + char *chap_r; + + rchap_get_response_bin(rchap, &response, &response_len); + chap_r = chap_bin2hex(response, response_len); + free(response); + + return (chap_r); +} + +void +rchap_delete(struct rchap *rchap) +{ + + free(rchap->rchap_secret); + free(rchap->rchap_challenge); + free(rchap); +} diff --git a/usr.sbin/iscsid/iscsid.h b/usr.sbin/iscsid/iscsid.h index 962355947a4f..b05c222ce9cb 100644 --- a/usr.sbin/iscsid/iscsid.h +++ b/usr.sbin/iscsid/iscsid.h @@ -34,6 +34,7 @@ #include #include +#include #include @@ -60,8 +61,7 @@ struct connection { size_t conn_max_data_segment_length; size_t conn_max_burst_length; size_t conn_first_burst_length; - char conn_mutual_challenge[CONN_MUTUAL_CHALLENGE_LEN]; - unsigned char conn_mutual_id; + struct chap *conn_mutual_chap; }; struct pdu { @@ -80,6 +80,35 @@ struct keys { size_t keys_data_len; }; +#define CHAP_CHALLENGE_LEN 1024 + +struct chap { + unsigned char chap_id; + char chap_challenge[CHAP_CHALLENGE_LEN]; + char chap_response[MD5_DIGEST_LENGTH]; +}; + +struct rchap { + char *rchap_secret; + unsigned char rchap_id; + void *rchap_challenge; + size_t rchap_challenge_len; +}; + +struct chap *chap_new(void); +char *chap_get_id(const struct chap *chap); +char *chap_get_challenge(const struct chap *chap); +int chap_receive(struct chap *chap, const char *response); +int chap_authenticate(struct chap *chap, + const char *secret); +void chap_delete(struct chap *chap); + +struct rchap *rchap_new(const char *secret); +int rchap_receive(struct rchap *rchap, + const char *id, const char *challenge); +char *rchap_get_response(struct rchap *rchap); +void rchap_delete(struct rchap *rchap); + struct keys *keys_new(void); void keys_delete(struct keys *key); void keys_load(struct keys *keys, const struct pdu *pdu); diff --git a/usr.sbin/iscsid/login.c b/usr.sbin/iscsid/login.c index c0bef632292f..19ab844c0ff8 100644 --- a/usr.sbin/iscsid/login.c +++ b/usr.sbin/iscsid/login.c @@ -39,9 +39,6 @@ __FBSDID("$FreeBSD$"); #include #include #include -#include -#include -#include #include "iscsid.h" #include "iscsi_proto.h" @@ -329,148 +326,6 @@ login_list_prefers(const char *list, return (-1); } -static int -login_hex2int(const char hex) -{ - switch (hex) { - case '0': - return (0x00); - case '1': - return (0x01); - case '2': - return (0x02); - case '3': - return (0x03); - case '4': - return (0x04); - case '5': - return (0x05); - case '6': - return (0x06); - case '7': - return (0x07); - case '8': - return (0x08); - case '9': - return (0x09); - case 'a': - case 'A': - return (0x0a); - case 'b': - case 'B': - return (0x0b); - case 'c': - case 'C': - return (0x0c); - case 'd': - case 'D': - return (0x0d); - case 'e': - case 'E': - return (0x0e); - case 'f': - case 'F': - return (0x0f); - default: - return (-1); - } -} - -/* - * XXX: Review this _carefully_. - */ -static int -login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) -{ - int i, hex_len, nibble; - bool lo = true; /* As opposed to 'hi'. */ - char *bin; - size_t bin_off, bin_len; - - if (strncasecmp(hex, "0x", strlen("0x")) != 0) { - log_warnx("malformed variable, should start with \"0x\""); - return (-1); - } - - hex += strlen("0x"); - hex_len = strlen(hex); - if (hex_len < 1) { - log_warnx("malformed variable; doesn't contain anything " - "but \"0x\""); - return (-1); - } - - bin_len = hex_len / 2 + hex_len % 2; - bin = calloc(bin_len, 1); - if (bin == NULL) - log_err(1, "calloc"); - - bin_off = bin_len - 1; - for (i = hex_len - 1; i >= 0; i--) { - nibble = login_hex2int(hex[i]); - if (nibble < 0) { - log_warnx("malformed variable, invalid char \"%c\"", - hex[i]); - return (-1); - } - - assert(bin_off < bin_len); - if (lo) { - bin[bin_off] = nibble; - lo = false; - } else { - bin[bin_off] |= nibble << 4; - bin_off--; - lo = true; - } - } - - *binp = bin; - *bin_lenp = bin_len; - return (0); -} - -static char * -login_bin2hex(const char *bin, size_t bin_len) -{ - unsigned char *hex, *tmp, ch; - size_t hex_len; - size_t i; - - hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ - hex = malloc(hex_len); - if (hex == NULL) - log_err(1, "malloc"); - - tmp = hex; - tmp += sprintf(tmp, "0x"); - for (i = 0; i < bin_len; i++) { - ch = bin[i]; - tmp += sprintf(tmp, "%02x", ch); - } - - return (hex); -} - -static void -login_compute_md5(const char id, const char *secret, - const void *challenge, size_t challenge_len, void *response, - size_t response_len) -{ - MD5_CTX ctx; - int rv; - - assert(response_len == MD5_DIGEST_LENGTH); - - MD5_Init(&ctx); - MD5_Update(&ctx, &id, sizeof(id)); - MD5_Update(&ctx, secret, strlen(secret)); - MD5_Update(&ctx, challenge, challenge_len); - rv = MD5_Final(response, &ctx); - if (rv != 1) - log_errx(1, "MD5_Final"); -} - static void login_negotiate_key(struct connection *conn, const char *name, const char *value) @@ -687,12 +542,11 @@ login_send_chap_r(struct pdu *response) struct connection *conn; struct pdu *request; struct keys *request_keys, *response_keys; + struct rchap *rchap; const char *chap_a, *chap_c, *chap_i; - char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; - size_t challenge_len; - int error, rv; - unsigned char id; - char *mutual_chap_c, mutual_chap_i[4]; + char *chap_r; + int error; + char *mutual_chap_c, *mutual_chap_i; /* * As in the rest of the initiator, 'request' means @@ -720,17 +574,19 @@ login_send_chap_r(struct pdu *response) if (chap_i == NULL) log_errx(1, "received CHAP packet without CHAP_I"); - if (strcmp(chap_a, "5") != 0) + if (strcmp(chap_a, "5") != 0) { log_errx(1, "received CHAP packet " "with unsupported CHAP_A \"%s\"", chap_a); - id = strtoul(chap_i, NULL, 10); - error = login_hex2bin(chap_c, &challenge, &challenge_len); - if (error != 0) - log_errx(1, "received CHAP packet with malformed CHAP_C"); - login_compute_md5(id, conn->conn_conf.isc_secret, - challenge, challenge_len, response_bin, sizeof(response_bin)); - free(challenge); - chap_r = login_bin2hex(response_bin, sizeof(response_bin)); + } + + rchap = rchap_new(conn->conn_conf.isc_secret); + error = rchap_receive(rchap, chap_i, chap_c); + if (error != 0) { + log_errx(1, "received CHAP packet " + "with malformed CHAP_I or CHAP_C"); + } + chap_r = rchap_get_response(rchap); + rchap_delete(rchap); keys_delete(response_keys); @@ -747,26 +603,15 @@ login_send_chap_r(struct pdu *response) if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_debugx("requesting mutual authentication; " "binary challenge size is %zd bytes", - sizeof(conn->conn_mutual_challenge)); + sizeof(conn->conn_mutual_chap->chap_challenge)); - rv = RAND_bytes(conn->conn_mutual_challenge, - sizeof(conn->conn_mutual_challenge)); - if (rv != 1) { - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - rv = RAND_bytes(&conn->conn_mutual_id, - sizeof(conn->conn_mutual_id)); - if (rv != 1) { - log_errx(1, "RAND_bytes failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge, - sizeof(conn->conn_mutual_challenge)); - snprintf(mutual_chap_i, sizeof(mutual_chap_i), - "%d", conn->conn_mutual_id); + assert(conn->conn_mutual_chap == NULL); + conn->conn_mutual_chap = chap_new(); + mutual_chap_i = chap_get_id(conn->conn_mutual_chap); + mutual_chap_c = chap_get_challenge(conn->conn_mutual_chap); keys_add(request_keys, "CHAP_I", mutual_chap_i); keys_add(request_keys, "CHAP_C", mutual_chap_c); + free(mutual_chap_i); free(mutual_chap_c); } @@ -782,8 +627,6 @@ login_verify_mutual(const struct pdu *response) struct connection *conn; struct keys *response_keys; const char *chap_n, *chap_r; - char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; - size_t response_bin_len; int error; conn = response->pdu_connection; @@ -797,28 +640,26 @@ login_verify_mutual(const struct pdu *response) chap_r = keys_find(response_keys, "CHAP_R"); if (chap_r == NULL) log_errx(1, "received CHAP Response PDU without CHAP_R"); - error = login_hex2bin(chap_r, &response_bin, &response_bin_len); - if (error != 0) - log_errx(1, "received CHAP Response PDU with malformed CHAP_R"); + + error = chap_receive(conn->conn_mutual_chap, chap_r); + if (error != 0) + log_errx(1, "received CHAP Response PDU with invalid CHAP_R"); if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) { fail(conn, "Mutual CHAP failed"); log_errx(1, "mutual CHAP authentication failed: wrong user"); } - login_compute_md5(conn->conn_mutual_id, - conn->conn_conf.isc_mutual_secret, conn->conn_mutual_challenge, - sizeof(conn->conn_mutual_challenge), expected_response_bin, - sizeof(expected_response_bin)); - - if (memcmp(response_bin, expected_response_bin, - sizeof(expected_response_bin)) != 0) { + error = chap_authenticate(conn->conn_mutual_chap, + conn->conn_conf.isc_mutual_secret); + if (error != 0) { fail(conn, "Mutual CHAP failed"); log_errx(1, "mutual CHAP authentication failed: wrong secret"); } - keys_delete(response_keys); - free(response_bin); + keys_delete(response_keys); + chap_delete(conn->conn_mutual_chap); + conn->conn_mutual_chap = NULL; log_debugx("mutual CHAP authentication succeeded"); }