From b1d0ac45f16a7d6804d1d582ba5673b434d980e0 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Sat, 14 Mar 2015 10:43:03 -0700 Subject: [PATCH] Add support for bidirectional CHAP Signed-off-by: Ronnie Sahlberg --- README | 63 +++++++++++++++--- include/iscsi-private.h | 8 +++ include/iscsi.h | 10 +++ lib/connect.c | 3 + lib/init.c | 60 +++++++++++++++++ lib/libiscsi.def | 1 + lib/libiscsi.syms | 1 + lib/login.c | 139 ++++++++++++++++++++++++++++++++++++++-- 8 files changed, 270 insertions(+), 15 deletions(-) diff --git a/README b/README index 2f86e82..bff659e 100644 --- a/README +++ b/README @@ -33,21 +33,68 @@ To build RPMs run the following script from the libiscsi root directory iSCSI URL Format ================ iSCSI devices are specified by a URL format of the following form : - iscsi://[[%]@][:]// + iscsi://[[%]@][:]//[?[&]*] + +Arguments: +Username and password for bidirectional CHAP authentication: +target_user= +target_password= + + Example: iscsi://server/iqn.ronnie.test/1 -When using CHAP authentication, username and password can be specified as part of the URL + + +CHAP Authentication +=================== +CHAP authentication can be specified two ways. Either via the URL itself +or through environment variables. + +Note that when setting it via the URL, be careful so that username/password +will not be visible in logfiles or the process list. + +URL +--- +CHAP authentication via URL is specified by providing %@ +in the server part of the URL: + +Example: iscsi://ronnie%password@server/iqn.ronnie.test/1 -but this may make the user and password visible in log files as well as in ps aux output. -So it is also possible to provide either just the password or both the password and username -via environment variables. -The username and/or password can be set via -LIBISCSI_CHAP_USERNAME=ronnie -LIBISCSI_CHAP_PASSWORD=password + +Environment variables +--------------------- +Setting the CHAP authentication via environment variables: + LIBISCSI_CHAP_USERNAME=ronnie + LIBISCSI_CHAP_PASSWORD=password Example: LIBISCSI_CHAP_PASSWORD=password iscsi-inq iscsi://ronnie@10.1.1.27/iqn.ronnie.test/1 +Bidirectional CHAP Authentication +================================= +Bidirectional CHAP is when you not only authenticate the initiator to the target +but also authenticate the target back to the initiator. +This is only available if you also first specify normal authentication as per +the previous section. + +Bidirectional CHAP can be set either via URL arguments or via environment +variables. If specifying it via URL arguments, be careful so that you do +not leak the username/password via logfiles or the process list or similar. + +URL +--- +URL arguments contain the '&' character so make sure to escape them properly +if you pass them in via a commandline. + +Example: + iscsi://127.0.0.1/iqn.ronnie.test/1?target_user=target\&target_password=target + +Environment variables +--------------------- +Setting the CHAP authentication via environment variables: + LIBISCSI_CHAP_TARGET_USERNAME=target + LIBISCSI_CHAP_TARGET_PASSWORD=password + IPv6 support ============ diff --git a/include/iscsi-private.h b/include/iscsi-private.h index afa6ad3..ca2ee36 100644 --- a/include/iscsi-private.h +++ b/include/iscsi-private.h @@ -60,6 +60,9 @@ struct iscsi_in_pdu { void iscsi_free_iscsi_in_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in); void iscsi_free_iscsi_inqueue(struct iscsi_context *iscsi, struct iscsi_in_pdu *inqueue); +/* size of chap response field */ +#define CHAP_R_SIZE 16 + struct iscsi_context { char initiator_name[MAX_STRING_SIZE+1]; char target_name[MAX_STRING_SIZE+1]; @@ -73,6 +76,11 @@ struct iscsi_context { char passwd[MAX_STRING_SIZE+1]; char chap_c[MAX_STRING_SIZE+1]; + char target_user[MAX_STRING_SIZE+1]; + char target_passwd[MAX_STRING_SIZE+1]; + uint32_t target_chap_i; + unsigned char target_chap_r[CHAP_R_SIZE]; + char error_string[MAX_STRING_SIZE+1]; enum iscsi_session_type session_type; diff --git a/include/iscsi.h b/include/iscsi.h index 7a649ee..cf3309e 100644 --- a/include/iscsi.h +++ b/include/iscsi.h @@ -268,6 +268,16 @@ EXTERN int iscsi_set_header_digest(struct iscsi_context *iscsi, EXTERN int iscsi_set_initiator_username_pwd(struct iscsi_context *iscsi, const char *user, const char *passwd); +/* + * Specify the username and password to use for target chap authentication. + * Target/bidirectional CHAP is only supported if you also have normal + * CHAP authentication. + * You must configure CHAP first using iscsi_set_initiator_username_pwd() +`* before you can set up target authentication. + */ +EXTERN int iscsi_set_target_username_pwd(struct iscsi_context *iscsi, + const char *user, + const char *passwd); /* * check if the context is logged in or not diff --git a/lib/connect.c b/lib/connect.c index 204d0b2..1f79880 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -285,6 +285,9 @@ try_again: if (old_iscsi->user[0]) { iscsi_set_initiator_username_pwd(iscsi, old_iscsi->user, old_iscsi->passwd); } + if (old_iscsi->target_user[0]) { + iscsi_set_target_username_pwd(iscsi, old_iscsi->target_user, old_iscsi->target_passwd); + } iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); diff --git a/lib/init.c b/lib/init.c index 9a80b1a..395d317 100644 --- a/lib/init.c +++ b/lib/init.c @@ -474,9 +474,43 @@ iscsi_parse_url(struct iscsi_context *iscsi, const char *url, int full) strncpy(str,url + 8, MAX_STRING_SIZE); portal = str; + iscsi_set_target_username_pwd(iscsi, + getenv("LIBISCSI_CHAP_TARGET_USERNAME"), + getenv("LIBISCSI_CHAP_TARGET_PASSWORD")); + user = getenv("LIBISCSI_CHAP_USERNAME"); passwd = getenv("LIBISCSI_CHAP_PASSWORD"); + tmp = strchr(portal, '?'); + if (tmp) { + *tmp++ = 0; + while (tmp && *tmp) { + char *next = strchr(tmp, '&'); + char *key, *value; + + if (next != NULL) { + *next++ = 0; + } + key = tmp; + value = strchr(key, '='); + if (value != NULL) { + *value++ = 0; + } + if (!strcmp(key, "target_user")) { + if (value) { + strncpy(iscsi->target_user, + value, MAX_STRING_SIZE); + } + } else if (!strcmp(key, "target_password")) { + if (value) { + strncpy(iscsi->target_passwd, + value, MAX_STRING_SIZE); + } + } + tmp = next; + } + } + tmp = strchr(portal, '@'); if (tmp != NULL) { user = portal; @@ -553,9 +587,19 @@ iscsi_parse_url(struct iscsi_context *iscsi, const char *url, int full) strncpy(iscsi_url->portal,portal,MAX_STRING_SIZE); + if (!iscsi->target_user[0] || !iscsi->target_passwd[0]) { + iscsi->target_user[0] = 0; + iscsi->target_passwd[0] = 0; + } if (user != NULL && passwd != NULL) { strncpy(iscsi_url->user, user, MAX_STRING_SIZE); strncpy(iscsi_url->passwd, passwd, MAX_STRING_SIZE); + } else { + /* if we do not have normal CHAP, that means we do not have + * bidirectional either. + */ + iscsi->target_user[0] = 0; + iscsi->target_passwd[0] = 0; } if (full) { @@ -601,6 +645,22 @@ iscsi_set_initiator_username_pwd(struct iscsi_context *iscsi, return 0; } + +int +iscsi_set_target_username_pwd(struct iscsi_context *iscsi, + const char *user, const char *passwd) +{ + if (!user || !passwd) { + iscsi->target_user[0] = 0; + iscsi->target_passwd[0] = 0; + return 0; + } + strncpy(iscsi->target_user, user, MAX_STRING_SIZE); + strncpy(iscsi->target_passwd, passwd, MAX_STRING_SIZE); + return 0; +} + + int iscsi_set_immediate_data(struct iscsi_context *iscsi, enum iscsi_immediate_data immediate_data) { diff --git a/lib/libiscsi.def b/lib/libiscsi.def index 5388dc5..5d898da 100644 --- a/lib/libiscsi.def +++ b/lib/libiscsi.def @@ -97,6 +97,7 @@ iscsi_set_isid_oui iscsi_set_isid_random iscsi_set_isid_reserved iscsi_set_session_type +iscsi_set_target_username_pwd iscsi_set_targetname iscsi_set_tcp_keepalive iscsi_set_tcp_user_timeout diff --git a/lib/libiscsi.syms b/lib/libiscsi.syms index 98670e4..b806795 100644 --- a/lib/libiscsi.syms +++ b/lib/libiscsi.syms @@ -95,6 +95,7 @@ iscsi_set_isid_oui iscsi_set_isid_random iscsi_set_isid_reserved iscsi_set_session_type +iscsi_set_target_username_pwd iscsi_set_targetname iscsi_set_tcp_keepalive iscsi_set_tcp_user_timeout diff --git a/lib/login.c b/lib/login.c index ffbce98..33d20a2 100644 --- a/lib/login.c +++ b/lib/login.c @@ -637,13 +637,16 @@ static void gcry_md_close(gcry_md_hd_t h) } #endif +/* size of the challenge used for bidirectional chap */ +#define TARGET_CHAP_C_SIZE 32 + static int iscsi_login_add_chap_response(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) { char str[MAX_STRING_SIZE+1]; char * strp; unsigned char c, cc[2]; - unsigned char digest[16]; + unsigned char digest[CHAP_R_SIZE]; gcry_md_hd_t ctx; int i; @@ -652,14 +655,14 @@ iscsi_login_add_chap_response(struct iscsi_context *iscsi, struct iscsi_pdu *pdu return 0; } - gcry_md_open(&ctx, GCRY_MD_MD5, 0); - if (!ctx) { - iscsi_set_error(iscsi, "Cannot create MD5 algorithm"); + if (!iscsi->chap_c[0]) { + iscsi_set_error(iscsi, "No CHAP challenge found"); return -1; } - if (!iscsi->chap_c[0]) { - iscsi_set_error(iscsi, "No CHAP challenge found"); + gcry_md_open(&ctx, GCRY_MD_MD5, 0); + if (ctx == NULL) { + iscsi_set_error(iscsi, "Cannot create MD5 algorithm"); return -1; } gcry_md_putc(ctx, iscsi->chap_i); @@ -681,7 +684,7 @@ iscsi_login_add_chap_response(struct iscsi_context *iscsi, struct iscsi_pdu *pdu return -1; } - for (i=0; i<16; i++) { + for (i = 0; i < CHAP_R_SIZE; i++) { c = digest[i]; cc[0] = i2h((c >> 4)&0x0f); cc[1] = i2h((c )&0x0f); @@ -698,6 +701,63 @@ iscsi_login_add_chap_response(struct iscsi_context *iscsi, struct iscsi_pdu *pdu return -1; } + /* bidirectional chap */ + if (iscsi->target_user[0]) { + unsigned char target_chap_c[TARGET_CHAP_C_SIZE]; + + iscsi->target_chap_i++; + snprintf(str, MAX_STRING_SIZE, "CHAP_I=%d", + iscsi->target_chap_i); + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, + strlen(str) + 1) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add " + "data failed."); + return -1; + } + + for (i = 0; i < TARGET_CHAP_C_SIZE; i++) { + target_chap_c[i] = rand()&0xff; + } + strncpy(str, "CHAP_C=0x", MAX_STRING_SIZE); + if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, + strlen(str)) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + return -1; + } + for (i = 0; i < TARGET_CHAP_C_SIZE; i++) { + c = target_chap_c[i]; + cc[0] = i2h((c >> 4)&0x0f); + cc[1] = i2h((c )&0x0f); + if (iscsi_pdu_add_data(iscsi, pdu, &cc[0], 2) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add " + "data failed."); + return -1; + } + } + c = 0; + if (iscsi_pdu_add_data(iscsi, pdu, &c, 1) != 0) { + iscsi_set_error(iscsi, "Out-of-memory: pdu add data " + "failed."); + return -1; + } + + gcry_md_open(&ctx, GCRY_MD_MD5, 0); + if (ctx == NULL) { + iscsi_set_error(iscsi, "Cannot create MD5 algorithm"); + return -1; + } + gcry_md_putc(ctx, iscsi->target_chap_i); + gcry_md_write(ctx, (unsigned char *)iscsi->target_passwd, + strlen(iscsi->target_passwd)); + gcry_md_write(ctx, (unsigned char *)target_chap_c, + TARGET_CHAP_C_SIZE); + + memcpy(iscsi->target_chap_r, gcry_md_read(ctx, 0), + sizeof(iscsi->target_chap_r)); + gcry_md_close(ctx); + } + return 0; } @@ -969,12 +1029,24 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, uint32_t status; char *ptr = (char *)in->data; int size = in->data_pos; + int must_have_chap_n = 0; + int must_have_chap_r = 0; status = scsi_get_uint16(&in->hdr[36]); iscsi_adjust_statsn(iscsi, in); iscsi_adjust_maxexpcmdsn(iscsi, in); + /* Using bidirectional CHAP? Then we must see a chap_n and chap_r + * field in this PDU + */ + if ((in->hdr[1] & ISCSI_PDU_LOGIN_TRANSIT) + && (in->hdr[1] & ISCSI_PDU_LOGIN_CSG_FF) == ISCSI_PDU_LOGIN_CSG_SECNEG + && iscsi->target_user[0]) { + must_have_chap_n = 1; + must_have_chap_r = 1; + } + /* XXX here we should parse the data returned in case the target * renegotiated some some parameters. * we should also do proper handshaking if the target is not yet @@ -1070,6 +1142,43 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, iscsi->secneg_phase = ISCSI_LOGIN_SECNEG_PHASE_SEND_RESPONSE; } + if (!strncmp(ptr, "CHAP_N=", 7)) { + if (strcmp(iscsi->target_user, ptr + 7)) { + iscsi_set_error(iscsi, "Failed to log in to" + " target. Wrong CHAP targetname" + " received: %s", ptr + 7); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return 0; + } + must_have_chap_n = 0; + } + + if (!strncmp(ptr, "CHAP_R=0x", 9)) { + int i; + + if (len != 9 + 2 * CHAP_R_SIZE) { + iscsi_set_error(iscsi, "Wrong size of CHAP_R" + " received from target."); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return 0; + } + for (i = 0; i < CHAP_R_SIZE; i++) { + unsigned char c; + c = ((h2i(ptr[9 + 2 * i]) << 4) | h2i(ptr[9 + 2 * i + 1])); + if (c != iscsi->target_chap_r[i]) { + iscsi_set_error(iscsi, "Authentication " + "failed. Invalid CHAP_R " + "response from the target"); + pdu->callback(iscsi, SCSI_STATUS_ERROR, + NULL, pdu->private_data); + return 0; + } + } + must_have_chap_r = 0; + } + ptr += len + 1; size -= len + 1; } @@ -1089,6 +1198,22 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, return 0; } + if (must_have_chap_n) { + iscsi_set_error(iscsi, "Failed to log in to target. " + "It did not return CHAP_N during SECNEG."); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return 0; + } + + if (must_have_chap_r) { + iscsi_set_error(iscsi, "Failed to log in to target. " + "It did not return CHAP_R during SECNEG."); + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + return 0; + } + if (in->hdr[1] & ISCSI_PDU_LOGIN_TRANSIT) { iscsi->current_phase = (in->hdr[1] & ISCSI_PDU_LOGIN_NSG_FF) << 2; }