/** @file | |
This file is for Challenge-Handshake Authentication Protocol (CHAP) | |
Configuration. | |
Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) Microsoft Corporation | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "IScsiImpl.h" | |
// | |
// Supported CHAP hash algorithms, mapped to sets of BaseCryptLib APIs and | |
// macros. CHAP_HASH structures at lower subscripts in the array are preferred | |
// by the initiator. | |
// | |
STATIC CONST CHAP_HASH mChapHash[] = { | |
{ | |
ISCSI_CHAP_ALGORITHM_SHA256, | |
SHA256_DIGEST_SIZE, | |
Sha256GetContextSize, | |
Sha256Init, | |
Sha256Update, | |
Sha256Final | |
}, | |
#ifdef ENABLE_MD5_DEPRECATED_INTERFACES | |
// | |
// Keep the deprecated MD5 entry at the end of the array (making MD5 the | |
// least preferred choice of the initiator). | |
// | |
{ | |
ISCSI_CHAP_ALGORITHM_MD5, | |
MD5_DIGEST_SIZE, | |
Md5GetContextSize, | |
Md5Init, | |
Md5Update, | |
Md5Final | |
}, | |
#endif // ENABLE_MD5_DEPRECATED_INTERFACES | |
}; | |
// | |
// Ordered list of mChapHash[*].Algorithm values. It is formatted for the | |
// CHAP_A=<A1,A2...> value string, by the IScsiCHAPInitHashList() function. It | |
// is sent by the initiator in ISCSI_CHAP_STEP_ONE. | |
// | |
STATIC CHAR8 mChapHashListString[ | |
3 + // UINT8 identifier in | |
// decimal | |
(1 + 3) * (ARRAY_SIZE (mChapHash) - 1) + // comma prepended for | |
// entries after the | |
// first | |
1 + // extra character for | |
// AsciiSPrint() | |
// truncation check | |
1 // terminating NUL | |
]; | |
/** | |
Initiator calculates its own expected hash value. | |
@param[in] ChapIdentifier iSCSI CHAP identifier sent by authenticator. | |
@param[in] ChapSecret iSCSI CHAP secret of the authenticator. | |
@param[in] SecretLength The length of iSCSI CHAP secret. | |
@param[in] ChapChallenge The challenge message sent by authenticator. | |
@param[in] ChallengeLength The length of iSCSI CHAP challenge message. | |
@param[in] Hash Pointer to the CHAP_HASH structure that | |
determines the hashing algorithm to use. The | |
caller is responsible for making Hash point | |
to an "mChapHash" element. | |
@param[out] ChapResponse The calculation of the expected hash value. | |
@retval EFI_SUCCESS The expected hash value was calculatedly | |
successfully. | |
@retval EFI_PROTOCOL_ERROR The length of the secret should be at least | |
the length of the hash value for the hashing | |
algorithm chosen. | |
@retval EFI_PROTOCOL_ERROR Hash operation fails. | |
@retval EFI_OUT_OF_RESOURCES Failure to allocate resource to complete | |
hashing. | |
**/ | |
EFI_STATUS | |
IScsiCHAPCalculateResponse ( | |
IN UINT32 ChapIdentifier, | |
IN CHAR8 *ChapSecret, | |
IN UINT32 SecretLength, | |
IN UINT8 *ChapChallenge, | |
IN UINT32 ChallengeLength, | |
IN CONST CHAP_HASH *Hash, | |
OUT UINT8 *ChapResponse | |
) | |
{ | |
UINTN ContextSize; | |
VOID *Ctx; | |
CHAR8 IdByte[1]; | |
EFI_STATUS Status; | |
if (SecretLength < ISCSI_CHAP_SECRET_MIN_LEN) { | |
return EFI_PROTOCOL_ERROR; | |
} | |
ASSERT (Hash != NULL); | |
ContextSize = Hash->GetContextSize (); | |
Ctx = AllocatePool (ContextSize); | |
if (Ctx == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = EFI_PROTOCOL_ERROR; | |
if (!Hash->Init (Ctx)) { | |
goto Exit; | |
} | |
// | |
// Hash Identifier - Only calculate 1 byte data (RFC1994) | |
// | |
IdByte[0] = (CHAR8)ChapIdentifier; | |
if (!Hash->Update (Ctx, IdByte, 1)) { | |
goto Exit; | |
} | |
// | |
// Hash Secret | |
// | |
if (!Hash->Update (Ctx, ChapSecret, SecretLength)) { | |
goto Exit; | |
} | |
// | |
// Hash Challenge received from Target | |
// | |
if (!Hash->Update (Ctx, ChapChallenge, ChallengeLength)) { | |
goto Exit; | |
} | |
if (Hash->Final (Ctx, ChapResponse)) { | |
Status = EFI_SUCCESS; | |
} | |
Exit: | |
FreePool (Ctx); | |
return Status; | |
} | |
/** | |
The initiator checks the CHAP response replied by target against its own | |
calculation of the expected hash value. | |
@param[in] AuthData iSCSI CHAP authentication data. | |
@param[in] TargetResponse The response from target. | |
@retval EFI_SUCCESS The response from target passed | |
authentication. | |
@retval EFI_SECURITY_VIOLATION The response from target was not expected | |
value. | |
@retval Others Other errors as indicated. | |
**/ | |
EFI_STATUS | |
IScsiCHAPAuthTarget ( | |
IN ISCSI_CHAP_AUTH_DATA *AuthData, | |
IN UINT8 *TargetResponse | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 SecretSize; | |
UINT8 VerifyRsp[ISCSI_CHAP_MAX_DIGEST_SIZE]; | |
INTN Mismatch; | |
Status = EFI_SUCCESS; | |
SecretSize = (UINT32)AsciiStrLen (AuthData->AuthConfig->ReverseCHAPSecret); | |
ASSERT (AuthData->Hash != NULL); | |
Status = IScsiCHAPCalculateResponse ( | |
AuthData->OutIdentifier, | |
AuthData->AuthConfig->ReverseCHAPSecret, | |
SecretSize, | |
AuthData->OutChallenge, | |
AuthData->Hash->DigestSize, // ChallengeLength | |
AuthData->Hash, | |
VerifyRsp | |
); | |
Mismatch = CompareMem ( | |
VerifyRsp, | |
TargetResponse, | |
AuthData->Hash->DigestSize | |
); | |
if (Mismatch != 0) { | |
Status = EFI_SECURITY_VIOLATION; | |
} | |
return Status; | |
} | |
/** | |
This function checks the received iSCSI Login Response during the security | |
negotiation stage. | |
@param[in] Conn The iSCSI connection. | |
@retval EFI_SUCCESS The Login Response passed the CHAP validation. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory. | |
@retval EFI_PROTOCOL_ERROR Some kind of protocol error occurred. | |
@retval Others Other errors as indicated. | |
**/ | |
EFI_STATUS | |
IScsiCHAPOnRspReceived ( | |
IN ISCSI_CONNECTION *Conn | |
) | |
{ | |
EFI_STATUS Status; | |
ISCSI_SESSION *Session; | |
ISCSI_CHAP_AUTH_DATA *AuthData; | |
CHAR8 *Value; | |
UINT8 *Data; | |
UINT32 Len; | |
LIST_ENTRY *KeyValueList; | |
UINTN Algorithm; | |
CHAR8 *Identifier; | |
CHAR8 *Challenge; | |
CHAR8 *Name; | |
CHAR8 *Response; | |
UINT8 TargetRsp[ISCSI_CHAP_MAX_DIGEST_SIZE]; | |
UINT32 RspLen; | |
UINTN Result; | |
UINTN HashIndex; | |
ASSERT (Conn->CurrentStage == ISCSI_SECURITY_NEGOTIATION); | |
ASSERT (Conn->RspQue.BufNum != 0); | |
Session = Conn->Session; | |
AuthData = &Session->AuthData.CHAP; | |
Len = Conn->RspQue.BufSize; | |
Data = AllocateZeroPool (Len); | |
if (Data == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// Copy the data in case the data spans over multiple PDUs. | |
// | |
NetbufQueCopy (&Conn->RspQue, 0, Len, Data); | |
// | |
// Build the key-value list from the data segment of the Login Response. | |
// | |
KeyValueList = IScsiBuildKeyValueList ((CHAR8 *)Data, Len); | |
if (KeyValueList == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ON_EXIT; | |
} | |
Status = EFI_PROTOCOL_ERROR; | |
switch (Conn->AuthStep) { | |
case ISCSI_AUTH_INITIAL: | |
// | |
// The first Login Response. | |
// | |
Value = IScsiGetValueByKeyFromList ( | |
KeyValueList, | |
ISCSI_KEY_TARGET_PORTAL_GROUP_TAG | |
); | |
if (Value == NULL) { | |
goto ON_EXIT; | |
} | |
Result = IScsiNetNtoi (Value); | |
if (Result > 0xFFFF) { | |
goto ON_EXIT; | |
} | |
Session->TargetPortalGroupTag = (UINT16)Result; | |
Value = IScsiGetValueByKeyFromList ( | |
KeyValueList, | |
ISCSI_KEY_AUTH_METHOD | |
); | |
if (Value == NULL) { | |
goto ON_EXIT; | |
} | |
// | |
// Initiator mandates CHAP authentication but target replies without | |
// "CHAP", or initiator suggets "None" but target replies with some kind of | |
// auth method. | |
// | |
if (Session->AuthType == ISCSI_AUTH_TYPE_NONE) { | |
if (AsciiStrCmp (Value, ISCSI_KEY_VALUE_NONE) != 0) { | |
goto ON_EXIT; | |
} | |
} else if (Session->AuthType == ISCSI_AUTH_TYPE_CHAP) { | |
if (AsciiStrCmp (Value, ISCSI_AUTH_METHOD_CHAP) != 0) { | |
goto ON_EXIT; | |
} | |
} else { | |
goto ON_EXIT; | |
} | |
// | |
// Transit to CHAP step one. | |
// | |
Conn->AuthStep = ISCSI_CHAP_STEP_ONE; | |
Status = EFI_SUCCESS; | |
break; | |
case ISCSI_CHAP_STEP_TWO: | |
// | |
// The Target replies with CHAP_A=<A> CHAP_I=<I> CHAP_C=<C> | |
// | |
Value = IScsiGetValueByKeyFromList ( | |
KeyValueList, | |
ISCSI_KEY_CHAP_ALGORITHM | |
); | |
if (Value == NULL) { | |
goto ON_EXIT; | |
} | |
Algorithm = IScsiNetNtoi (Value); | |
for (HashIndex = 0; HashIndex < ARRAY_SIZE (mChapHash); HashIndex++) { | |
if (Algorithm == mChapHash[HashIndex].Algorithm) { | |
break; | |
} | |
} | |
if (HashIndex == ARRAY_SIZE (mChapHash)) { | |
// | |
// Unsupported algorithm is chosen by target. | |
// | |
goto ON_EXIT; | |
} | |
// | |
// Remember the target's chosen hash algorithm. | |
// | |
ASSERT (AuthData->Hash == NULL); | |
AuthData->Hash = &mChapHash[HashIndex]; | |
Identifier = IScsiGetValueByKeyFromList ( | |
KeyValueList, | |
ISCSI_KEY_CHAP_IDENTIFIER | |
); | |
if (Identifier == NULL) { | |
goto ON_EXIT; | |
} | |
Challenge = IScsiGetValueByKeyFromList ( | |
KeyValueList, | |
ISCSI_KEY_CHAP_CHALLENGE | |
); | |
if (Challenge == NULL) { | |
goto ON_EXIT; | |
} | |
// | |
// Process the CHAP identifier and CHAP Challenge from Target. | |
// Calculate Response value. | |
// | |
Result = IScsiNetNtoi (Identifier); | |
if (Result > 0xFF) { | |
goto ON_EXIT; | |
} | |
AuthData->InIdentifier = (UINT32)Result; | |
AuthData->InChallengeLength = (UINT32)sizeof (AuthData->InChallenge); | |
Status = IScsiHexToBin ( | |
(UINT8 *)AuthData->InChallenge, | |
&AuthData->InChallengeLength, | |
Challenge | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_PROTOCOL_ERROR; | |
goto ON_EXIT; | |
} | |
Status = IScsiCHAPCalculateResponse ( | |
AuthData->InIdentifier, | |
AuthData->AuthConfig->CHAPSecret, | |
(UINT32)AsciiStrLen (AuthData->AuthConfig->CHAPSecret), | |
AuthData->InChallenge, | |
AuthData->InChallengeLength, | |
AuthData->Hash, | |
AuthData->CHAPResponse | |
); | |
// | |
// Transit to next step. | |
// | |
Conn->AuthStep = ISCSI_CHAP_STEP_THREE; | |
break; | |
case ISCSI_CHAP_STEP_THREE: | |
// | |
// One way CHAP authentication and the target would like to | |
// authenticate us. | |
// | |
Status = EFI_SUCCESS; | |
break; | |
case ISCSI_CHAP_STEP_FOUR: | |
ASSERT (AuthData->AuthConfig->CHAPType == ISCSI_CHAP_MUTUAL); | |
// | |
// The forth step, CHAP_N=<N> CHAP_R=<R> is received from Target. | |
// | |
Name = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_CHAP_NAME); | |
if (Name == NULL) { | |
goto ON_EXIT; | |
} | |
Response = IScsiGetValueByKeyFromList ( | |
KeyValueList, | |
ISCSI_KEY_CHAP_RESPONSE | |
); | |
if (Response == NULL) { | |
goto ON_EXIT; | |
} | |
ASSERT (AuthData->Hash != NULL); | |
RspLen = AuthData->Hash->DigestSize; | |
Status = IScsiHexToBin (TargetRsp, &RspLen, Response); | |
if (EFI_ERROR (Status) || (RspLen != AuthData->Hash->DigestSize)) { | |
Status = EFI_PROTOCOL_ERROR; | |
goto ON_EXIT; | |
} | |
// | |
// Check the CHAP Name and Response replied by Target. | |
// | |
Status = IScsiCHAPAuthTarget (AuthData, TargetRsp); | |
break; | |
default: | |
break; | |
} | |
ON_EXIT: | |
if (KeyValueList != NULL) { | |
IScsiFreeKeyValueList (KeyValueList); | |
} | |
FreePool (Data); | |
return Status; | |
} | |
/** | |
This function fills the CHAP authentication information into the login PDU | |
during the security negotiation stage in the iSCSI connection login. | |
@param[in] Conn The iSCSI connection. | |
@param[in, out] Pdu The PDU to send out. | |
@retval EFI_SUCCESS All check passed and the phase-related CHAP | |
authentication info is filled into the iSCSI | |
PDU. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory. | |
@retval EFI_PROTOCOL_ERROR Some kind of protocol error occurred. | |
**/ | |
EFI_STATUS | |
IScsiCHAPToSendReq ( | |
IN ISCSI_CONNECTION *Conn, | |
IN OUT NET_BUF *Pdu | |
) | |
{ | |
EFI_STATUS Status; | |
ISCSI_SESSION *Session; | |
ISCSI_LOGIN_REQUEST *LoginReq; | |
ISCSI_CHAP_AUTH_DATA *AuthData; | |
CHAR8 *Value; | |
CHAR8 ValueStr[256]; | |
CHAR8 *Response; | |
UINT32 RspLen; | |
CHAR8 *Challenge; | |
UINT32 ChallengeLen; | |
EFI_STATUS BinToHexStatus; | |
ASSERT (Conn->CurrentStage == ISCSI_SECURITY_NEGOTIATION); | |
Session = Conn->Session; | |
AuthData = &Session->AuthData.CHAP; | |
LoginReq = (ISCSI_LOGIN_REQUEST *)NetbufGetByte (Pdu, 0, 0); | |
if (LoginReq == NULL) { | |
return EFI_PROTOCOL_ERROR; | |
} | |
Status = EFI_SUCCESS; | |
RspLen = 2 * ISCSI_CHAP_MAX_DIGEST_SIZE + 3; | |
Response = AllocateZeroPool (RspLen); | |
if (Response == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
ChallengeLen = 2 * ISCSI_CHAP_MAX_DIGEST_SIZE + 3; | |
Challenge = AllocateZeroPool (ChallengeLen); | |
if (Challenge == NULL) { | |
FreePool (Response); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
switch (Conn->AuthStep) { | |
case ISCSI_AUTH_INITIAL: | |
// | |
// It's the initial Login Request. Fill in the key=value pairs mandatory | |
// for the initial Login Request. | |
// | |
IScsiAddKeyValuePair ( | |
Pdu, | |
ISCSI_KEY_INITIATOR_NAME, | |
mPrivate->InitiatorName | |
); | |
IScsiAddKeyValuePair (Pdu, ISCSI_KEY_SESSION_TYPE, "Normal"); | |
IScsiAddKeyValuePair ( | |
Pdu, | |
ISCSI_KEY_TARGET_NAME, | |
Session->ConfigData->SessionConfigData.TargetName | |
); | |
if (Session->AuthType == ISCSI_AUTH_TYPE_NONE) { | |
Value = ISCSI_KEY_VALUE_NONE; | |
ISCSI_SET_FLAG (LoginReq, ISCSI_LOGIN_REQ_PDU_FLAG_TRANSIT); | |
} else { | |
Value = ISCSI_AUTH_METHOD_CHAP; | |
} | |
IScsiAddKeyValuePair (Pdu, ISCSI_KEY_AUTH_METHOD, Value); | |
break; | |
case ISCSI_CHAP_STEP_ONE: | |
// | |
// First step, send the Login Request with CHAP_A=<A1,A2...> key-value | |
// pair. | |
// | |
IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_ALGORITHM, mChapHashListString); | |
Conn->AuthStep = ISCSI_CHAP_STEP_TWO; | |
break; | |
case ISCSI_CHAP_STEP_THREE: | |
// | |
// Third step, send the Login Request with CHAP_N=<N> CHAP_R=<R> or | |
// CHAP_N=<N> CHAP_R=<R> CHAP_I=<I> CHAP_C=<C> if target authentication is | |
// required too. | |
// | |
// CHAP_N=<N> | |
// | |
IScsiAddKeyValuePair ( | |
Pdu, | |
ISCSI_KEY_CHAP_NAME, | |
(CHAR8 *)&AuthData->AuthConfig->CHAPName | |
); | |
// | |
// CHAP_R=<R> | |
// | |
ASSERT (AuthData->Hash != NULL); | |
BinToHexStatus = IScsiBinToHex ( | |
(UINT8 *)AuthData->CHAPResponse, | |
AuthData->Hash->DigestSize, | |
Response, | |
&RspLen | |
); | |
ASSERT_EFI_ERROR (BinToHexStatus); | |
IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_RESPONSE, Response); | |
if (AuthData->AuthConfig->CHAPType == ISCSI_CHAP_MUTUAL) { | |
// | |
// CHAP_I=<I> | |
// | |
Status = IScsiGenRandom ((UINT8 *)&AuthData->OutIdentifier, 1); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
AsciiSPrint (ValueStr, sizeof (ValueStr), "%d", AuthData->OutIdentifier); | |
IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_IDENTIFIER, ValueStr); | |
// | |
// CHAP_C=<C> | |
// | |
Status = IScsiGenRandom ( | |
(UINT8 *)AuthData->OutChallenge, | |
AuthData->Hash->DigestSize | |
); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
BinToHexStatus = IScsiBinToHex ( | |
(UINT8 *)AuthData->OutChallenge, | |
AuthData->Hash->DigestSize, | |
Challenge, | |
&ChallengeLen | |
); | |
ASSERT_EFI_ERROR (BinToHexStatus); | |
IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_CHALLENGE, Challenge); | |
Conn->AuthStep = ISCSI_CHAP_STEP_FOUR; | |
} | |
// | |
// Set the stage transition flag. | |
// | |
ISCSI_SET_FLAG (LoginReq, ISCSI_LOGIN_REQ_PDU_FLAG_TRANSIT); | |
break; | |
default: | |
Status = EFI_PROTOCOL_ERROR; | |
break; | |
} | |
FreePool (Response); | |
FreePool (Challenge); | |
return Status; | |
} | |
/** | |
Initialize the CHAP_A=<A1,A2...> *value* string for the entire driver, to be | |
sent by the initiator in ISCSI_CHAP_STEP_ONE. | |
This function sanity-checks the internal table of supported CHAP hashing | |
algorithms, as well. | |
**/ | |
VOID | |
IScsiCHAPInitHashList ( | |
VOID | |
) | |
{ | |
CHAR8 *Position; | |
UINTN Left; | |
UINTN HashIndex; | |
CONST CHAP_HASH *Hash; | |
UINTN Printed; | |
Position = mChapHashListString; | |
Left = sizeof (mChapHashListString); | |
for (HashIndex = 0; HashIndex < ARRAY_SIZE (mChapHash); HashIndex++) { | |
Hash = &mChapHash[HashIndex]; | |
// | |
// Format the next hash identifier. | |
// | |
// Assert that we can format at least one non-NUL character, i.e. that we | |
// can progress. Truncation is checked after printing. | |
// | |
ASSERT (Left >= 2); | |
Printed = AsciiSPrint ( | |
Position, | |
Left, | |
"%a%d", | |
(HashIndex == 0) ? "" : ",", | |
Hash->Algorithm | |
); | |
// | |
// There's no way to differentiate between the "buffer filled to the brim, | |
// but not truncated" result and the "truncated" result of AsciiSPrint(). | |
// This is why "mChapHashListString" has an extra byte allocated, and the | |
// reason why we use the less-than (rather than the less-than-or-equal-to) | |
// relational operator in the assertion below -- we enforce "no truncation" | |
// by excluding the "completely used up" case too. | |
// | |
ASSERT (Printed + 1 < Left); | |
Position += Printed; | |
Left -= Printed; | |
// | |
// Sanity-check the digest size for Hash. | |
// | |
ASSERT (Hash->DigestSize <= ISCSI_CHAP_MAX_DIGEST_SIZE); | |
} | |
} |