Add support for bidirectional CHAP

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
This commit is contained in:
Ronnie Sahlberg
2015-03-14 10:43:03 -07:00
parent b03959dbb5
commit b1d0ac45f1
8 changed files with 270 additions and 15 deletions

63
README
View File

@@ -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://[<username>[%<password>]@]<host>[:<port>]/<target-iqn>/<lun>
iscsi://[<username>[%<password>]@]<host>[:<port>]/<target-iqn>/<lun>[?<argument>[&<argument>]*]
Arguments:
Username and password for bidirectional CHAP authentication:
target_user=<account>
target_password=<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 <username>%<password>@
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
============

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}