--- /dev/null Fri Apr 4 13:31:04 2008 +++ new/src/sun_nws/comstar/port_providers/iscsit/src/iscsit_auth.c Fri Apr 4 13:31:04 2008 @@ -0,0 +1,757 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at src/sun_nws/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at src/sun_nws/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)iscsit_auth.c 1.7 08/03/23 SMI" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* networking stuff */ +#include /* networking stuff */ +#include /* offsetof */ +#include /* ASSERT3U */ +#include + +#include +#include +#include +#include +#include +#include +#include + +static kv_status_t +iscsit_select_auth(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_propose_chap(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_select_alg(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_n(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_r(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_i(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_c(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_auth_propose(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_auth_expect_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_expect_r(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_done(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_auth_gen_challenge(iscsit_conn_t *ict); + +static kv_status_t +iscsit_auth_gen_response(iscsit_conn_t *ict); + +typedef struct { + iscsit_auth_phase_t phase; + iscsikey_id_t kv_id; + iscsit_auth_handler_t handler; +} auth_phase_entry_t; + +/* + * This table defines all authentication phases which have valid + * handler. The entries which have a non-zero key index are for + * a key/value pair handling when a key/value is being received, + * the rest of entries are for target checking the authentication + * phase after all key/value pair(s) are handled. + */ +static const auth_phase_entry_t apet[] = { + /* by key */ + { AP_AM_UNDECIDED, KI_AUTH_METHOD, iscsit_select_auth }, + { AP_AM_PROPOSED, KI_CHAP_A, auth_propose_chap }, + + { AP_CHAP_A_WAITING, KI_CHAP_A, auth_chap_select_alg }, + { AP_CHAP_R_WAITING, KI_CHAP_N, auth_chap_recv_n }, + { AP_CHAP_R_WAITING, KI_CHAP_R, auth_chap_recv_r }, + { AP_CHAP_R_WAITING, KI_CHAP_I, auth_chap_recv_i }, + { AP_CHAP_R_WAITING, KI_CHAP_C, auth_chap_recv_c }, + { AP_CHAP_R_RCVD, KI_CHAP_N, auth_chap_recv_n }, + { AP_CHAP_R_RCVD, KI_CHAP_R, auth_chap_recv_r }, + { AP_CHAP_R_RCVD, KI_CHAP_I, auth_chap_recv_i }, + { AP_CHAP_R_RCVD, KI_CHAP_C, auth_chap_recv_c }, + + /* by target */ + { AP_AM_UNDECIDED, 0, iscsit_auth_propose }, + { AP_AM_DECIDED, 0, iscsit_auth_expect_key }, + + { AP_CHAP_A_RCVD, 0, auth_chap_expect_r }, + { AP_CHAP_R_RCVD, 0, auth_chap_done } +}; + +typedef struct { + iscsit_auth_method_t am_id; + char *am_name; +} auth_id_name_t; + +/* + * a table of mapping from the authentication index to name. + */ +static const auth_id_name_t aint[] = { + { AM_CHAP, "CHAP" }, + { AM_NONE, "None" }, + /* { AM_KRB5, "KRB5" }, */ /* Not supported */ + /* { AM_SPKM1, "SPKM1" }, */ /* Not supported */ + /* { AM_SPKM2, "SPKM2" }, */ /* Not supported */ + /* { AM_SRP, "SRP" }, */ /* Not supported */ +}; + +#define ARRAY_LENGTH(ARRAY) (sizeof (ARRAY) / sizeof (ARRAY[0])) + +/* + * get the authentication method name for the method id. + */ +static const char * +am_id_to_name(int id) +{ + int i; + const auth_id_name_t *p; + i = 0; + while (i < ARRAY_LENGTH(aint)) { + p = &(aint[i]); + if (id == p->am_id) { + return (p->am_name); + } + i ++; + } + + return (NULL); +} + +/* + * Look for an apporiate function handler which is defined for + * current authentication phase and matches the key which is + * being handled. The key index is passed in as zero when it + * is looking for an handler for checking the authentication phase + * after all security keys are handled. + */ +iscsit_auth_handler_t +iscsit_auth_get_handler(iscsit_auth_client_t *client, iscsikey_id_t kv_id) +{ + iscsit_auth_phase_t phase = client->phase; + int i; + const auth_phase_entry_t *p; + + i = 0; + p = NULL; + while (i < ARRAY_LENGTH(apet)) { + p = &(apet[i]); + if (phase == p->phase && + kv_id == p->kv_id) { + return (p->handler); + } + i ++; + } + + /* No handler can be found, it must be an invalid requst. */ + return (NULL); +} + +/* + * Select an authentication method from a list of values proposed + * by initiator. After a valid method is selected, shift the + * authentication phase to AP_AM_DECIDED. + */ +static kv_status_t +iscsit_select_auth(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_sess_t *isp = ict->ict_sess; + sess_auth_t *auth = &isp->ist_auth; + int *am_list = &auth->authMethodValidList[0]; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc; + nvpair_t *am_choice; + char *am; + const char *am_name; + const char *text; + int am_id; + int i; + + client->phase = AP_AM_DECIDED; + + /* select a valid authentication method */ + am_choice = iscsi_get_next_listvalue(nvp, NULL); + while (am_choice != NULL) { + nvrc = nvpair_value_string(am_choice, &am); + ASSERT(nvrc == 0); + + i = 0; + am_id = am_list[i]; + while (am_id != 0) { + am_name = am_id_to_name(am_id); + if (strcasecmp(am, am_name) == 0) { + text = am; + goto am_decided; + } + i ++; + am_id = am_list[i]; + } + am_choice = iscsi_get_next_listvalue(nvp, am_choice); + } + + /* none of authentication method is valid */ + am_id = 0; + text = ISCSI_TEXT_REJECT; + +am_decided: + client->negotiatedMethod = am_id; + /* add the selected method to the response nvlist */ + nvrc = nvlist_add_string(lsm->icl_response_nvlist, + ikvx->ik_key_name, text); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +/* + * Initiator chooses to use CHAP after target proposed a list of + * authentication method. Set the authentication method to CHAP and + * continue on chap authentication phase. + */ +static kv_status_t +auth_propose_chap(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + client->negotiatedMethod = AM_CHAP; + client->phase = AP_AM_DECIDED; + + return (auth_chap_select_alg(ict, nvp, ikvx)); +} + +/* + * Select a CHAP algorithm from a list of values proposed by + * initiator and shift the authentication phase to AP_CHAP_A_RCVD. + */ +static kv_status_t +auth_chap_select_alg(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc, rc; + kv_status_t kvrc; + nvpair_t *alg_choice; + char *alg_string; + uint64_t alg; + const char *text; + + client->phase = AP_CHAP_A_RCVD; + + alg_choice = iscsi_get_next_listvalue(nvp, NULL); + while (alg_choice != NULL) { + nvrc = nvpair_value_string(alg_choice, &alg_string); + ASSERT(nvrc == 0); + rc = ddi_strtoul(alg_string, NULL, 0, (unsigned long *)&alg); + if (rc == 0 && alg == 5) { + /* only MD5 is supported */ + text = alg_string; + goto alg_selected; + } + + alg_choice = iscsi_get_next_listvalue(nvp, alg_choice); + } + + /* none of algorithm is selected */ + alg = 0; + text = ISCSI_TEXT_REJECT; + +alg_selected: + /* save the selected algorithm or zero for none is selected */ + client_set_numeric_data( + &client->recvKeyBlock, + AKT_CHAP_A, + (uint32_t)alg); + + /* add the selected algorithm to the response nvlist */ + nvrc = nvlist_add_string(lsm->icl_response_nvlist, + ikvx->ik_key_name, text); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + if (kvrc == 0 && alg != 0) { + kvrc = iscsit_auth_gen_challenge(ict); + } + + return (kvrc); +} + +/* + * Validate and save the the chap name which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: the CHAP_N, CHAP_R, optionally CHAP_I and CHAP_C key/value + * pairs need to be received in one packet, we handle each of them + * separately, in order to track the authentication phase, we set + * the authentication phase to AP_CHAP_R_RCVD once one of them is + * handled. So both of AP_CHAP_R_WAITING and AP_CHAP_R_RCVD phases + * are valid for these keys. The function auth_chap_done is going + * to detect if any of these keys is missing. + */ +static kv_status_t +auth_chap_recv_n(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + char *chap_name; + + nvrc = nvpair_value_string(nvp, &chap_name); + ASSERT(nvrc == 0); + + client_set_string_data(&client->recvKeyBlock, + AKT_CHAP_N, + chap_name); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Validate and save the the chap response which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: see function auth_chap_recv_n. + */ +static kv_status_t +auth_chap_recv_r(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + unsigned char *chap_resp; + uint_t len; + + nvrc = nvpair_value_byte_array(nvp, &chap_resp, &len); + ASSERT(nvrc == 0); + + client_set_binary_data(&client->recvKeyBlock, + AKT_CHAP_R, + chap_resp, len); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Validate and save the the chap identifier which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: see function auth_chap_recv_n. + */ +static kv_status_t +auth_chap_recv_i(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + uint64_t chap_id; + + nvrc = nvpair_value_uint64(nvp, &chap_id); + ASSERT(nvrc == 0); + + client_set_numeric_data(&client->recvKeyBlock, + AKT_CHAP_I, + chap_id); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Validate and save the the chap challenge which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: see function auth_chap_recv_n. + */ +static kv_status_t +auth_chap_recv_c(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + unsigned char *chap_challenge; + uint_t len; + + nvrc = nvpair_value_byte_array(nvp, &chap_challenge, &len); + ASSERT(nvrc == 0); + + client_set_binary_data( + &client->recvKeyBlock, + AKT_CHAP_C, + chap_challenge, len); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Shift the authentication phase to AP_CHAP_R_WAITING after target + * has successfully selected a chap algorithm. + */ +static kv_status_t +auth_chap_expect_r(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + uint32_t alg; + + client_get_numeric_data(&client->recvKeyBlock, + AKT_CHAP_A, + &alg); + + if (alg != 0) { + client->phase = AP_CHAP_R_WAITING; + } else { + /* none of proposed algorithm is supported or understood. */ + client->phase = AP_CHAP_A_WAITING; + } + + return (KV_HANDLED); +} + +/* + * Initiator does not propose security negotiation, target needs to + * verify if we can bypass the security negotiation phase or propose + * a security negotiation for the initiator. + */ +static kv_status_t +iscsit_auth_propose(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_sess_t *isp = ict->ict_sess; + sess_auth_t *auth = &isp->ist_auth; + int *am_list = &auth->authMethodValidList[0]; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + int nvrc; + kv_status_t kvrc; + const char *am_name; + + /* XXX check access */ + if (am_list[0] == AM_NONE || am_list[0] == 0) { + lsm->icl_auth_pass = 1; + } + + if (lsm->icl_auth_pass == 0) { + /* + * It should be noted that the negotiation might also + * be directed by the target if the initiator does + * support security, but is not ready to direct the + * negotiation (propose options). + * - RFC3720 section 5.3.2. + */ + am_name = am_id_to_name(am_list[0]); + nvrc = nvlist_add_string( + lsm->icl_response_nvlist, + "AuthMethod", am_name); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + + client->phase = AP_AM_PROPOSED; + } else { + kvrc = KV_HANDLED; + + client->phase = AP_DONE; + } + + return (kvrc); +} + +/* + * Shift the authentication phase according to the authentication + * method once it is selected. + */ +static kv_status_t +iscsit_auth_expect_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + if (client->negotiatedMethod != 0) { + /* Shift security negotiation phase. */ + switch (client->negotiatedMethod) { + case AM_CHAP: + client->phase = AP_CHAP_A_WAITING; + break; + case AM_NONE: + client->phase = AP_DONE; + lsm->icl_auth_pass = 1; + break; + default: + ASSERT(0); + break; + } + } else { + /* None of proposed method is supported or understood. */ + client->phase = AP_AM_UNDECIDED; + } + + return (KV_HANDLED); +} + +/* + * The last step of the chap authentication. We will validate the + * chap parameters we received and authenticate the client here. + */ +static kv_status_t +auth_chap_done(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc = KV_HANDLED; + + iscsit_sess_t *isp = ict->ict_sess; + sess_auth_t *sess_auth = &isp->ist_auth; + char *username; + char *username_in; + uint8_t *password_in; + int password_in_length; + + uint32_t chap_id; + unsigned char *chap_challenge; + unsigned int challenge_len; + char *chap_name; + unsigned char *chap_resp; + unsigned int resp_len; + + int bi_auth; + + username = sess_auth->username; + username_in = sess_auth->username_in; + password_in = sess_auth->password_in; + password_in_length = sess_auth->password_length_in; + /* + * ASSERT(username[0] != 0); + * ASSERT(username_in[0] != 0); + * ASSERT(password_in[0] != 0); + * ASSERT(password_in_length != 0); + */ + + /* + * We must sent the chap identifier and challenger. + */ + ASSERT(client_auth_key_present(&client->sendKeyBlock, AKT_CHAP_I) && + client_auth_key_present(&client->sendKeyBlock, AKT_CHAP_C)); + + /* + * Check if we have received a valid list of response keys. + */ + if (!client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_N) || + !client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_R) || + ((bi_auth = + client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_I)) ^ + client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_C))) { + return (KV_MISSING_FIELDS); + } + + client->phase = AP_DONE; + + client_get_string_data(&client->recvKeyBlock, + AKT_CHAP_N, + &chap_name); + + /* check username */ + if (strcmp(username_in, chap_name) != 0) { + return (KV_AUTH_FAILED); + } + + client_get_numeric_data(&client->sendKeyBlock, + AKT_CHAP_I, + &chap_id); + + client_get_binary_data(&client->sendKeyBlock, + AKT_CHAP_C, + &chap_challenge, &challenge_len); + + client_get_binary_data(&client->recvKeyBlock, + AKT_CHAP_R, + &chap_resp, &resp_len); + + if (client_verify_chap_resp( + username, username_in, + password_in, password_in_length, + chap_id, + chap_challenge, challenge_len, + chap_resp, resp_len) != ISCSI_AUTH_PASSED) { + return (KV_AUTH_FAILED); + } + + /* bi-direction authentication is required */ + if (bi_auth != 0) { + kvrc = iscsit_auth_gen_response(ict); + } + + lsm->icl_auth_pass = 1; + + return (kvrc); +} + +static kv_status_t +iscsit_auth_gen_challenge(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc; + + unsigned char idData[1]; + unsigned char *bin; + int len; + + auth_random_set_data(idData, 1); + client_set_numeric_data(&client->sendKeyBlock, + AKT_CHAP_I, + idData[0]); + + /* send chap identifier */ + nvrc = nvlist_add_uint64( + lsm->icl_response_nvlist, + "CHAP_I", idData[0]); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + if (kvrc != 0) { + return (kvrc); + } + + bin = &(client->auth_send_binary_block.largeBinary[0]); + len = iscsiAuthChapResponseLength; + auth_random_set_data(bin, len); + client_set_binary_data(&client->sendKeyBlock, + AKT_CHAP_C, + bin, len); + + /* send chap challenge */ + nvrc = nvlist_add_byte_array( + lsm->icl_response_nvlist, + "CHAP_C", bin, len); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +static kv_status_t +iscsit_auth_gen_response(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc; + + iscsit_sess_t *isp = ict->ict_sess; + sess_auth_t *sess_auth = &isp->ist_auth; + char *username; + uint8_t *password; + int password_length; + + uint32_t chap_id; + unsigned char *chap_challenge; + unsigned int challenge_len; + uchar_t resp[iscsiAuthChapResponseLength]; + + username = sess_auth->username; + password = sess_auth->password; + password_length = sess_auth->password_length; + + /* + * ASSERT(username[0] != 0); + * ASSERT(password[0] != 0); + * ASSERT(password_length != 0); + */ + client_get_numeric_data(&client->recvKeyBlock, + AKT_CHAP_I, + &chap_id); + + client_get_binary_data(&client->recvKeyBlock, + AKT_CHAP_C, + &chap_challenge, &challenge_len); + + client_compute_chap_resp( + &resp[0], + chap_id, + password, password_length, + chap_challenge, challenge_len); + + nvrc = nvlist_add_string( + lsm->icl_response_nvlist, + "CHAP_N", username); + + if (nvrc == 0) { + nvrc = nvlist_add_byte_array( + lsm->icl_response_nvlist, + "CHAP_R", resp, sizeof (resp)); + } + kvrc = iscsi_nvstat_to_kvstat(nvrc); + + return (kvrc); +}