/** @file This file is for Challenge-Handshake Authentication Protocol (CHAP) Configuration. Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.
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 }, // // 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 }, }; // // Ordered list of mChapHash[*].Algorithm values. It is formatted for the // CHAP_A= 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= CHAP_I= CHAP_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= CHAP_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= 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= CHAP_R= or // CHAP_N= CHAP_R= CHAP_I= CHAP_C= if target authentication is // required too. // // CHAP_N= // IScsiAddKeyValuePair ( Pdu, ISCSI_KEY_CHAP_NAME, (CHAR8 *) &AuthData->AuthConfig->CHAPName ); // // CHAP_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= // IScsiGenRandom ((UINT8 *) &AuthData->OutIdentifier, 1); AsciiSPrint (ValueStr, sizeof (ValueStr), "%d", AuthData->OutIdentifier); IScsiAddKeyValuePair (Pdu, ISCSI_KEY_CHAP_IDENTIFIER, ValueStr); // // CHAP_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= *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); } }