| /** @file | |
| This file is for Challenge-Handshake Authentication Protocol (CHAP) | |
| Configuration. | |
| Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR> | |
| 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> | |
| // | |
| IScsiGenRandom ((UINT8 *)&AuthData->OutIdentifier, 1); | |
| AsciiSPrint (ValueStr, sizeof (ValueStr), "%d", AuthData->OutIdentifier); | |
| IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_IDENTIFIER, ValueStr); | |
| // | |
| // CHAP_C=<C> | |
| // | |
| IScsiGenRandom ( | |
| (UINT8 *)AuthData->OutChallenge, | |
| AuthData->Hash->DigestSize | |
| ); | |
| 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); | |
| } | |
| } |