| /** @file |
| Implementation of EFI TLS Protocol Interfaces. |
| |
| Copyright (c) 2016, Intel Corporation. All rights reserved.<BR> |
| |
| This program and the accompanying materials |
| are licensed and made available under the terms and conditions of the BSD License |
| which accompanies this distribution. The full text of the license may be found at |
| http://opensource.org/licenses/bsd-license.php. |
| |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. |
| |
| **/ |
| |
| #include "TlsImpl.h" |
| |
| EFI_TLS_PROTOCOL mTlsProtocol = { |
| TlsSetSessionData, |
| TlsGetSessionData, |
| TlsBuildResponsePacket, |
| TlsProcessPacket |
| }; |
| |
| /** |
| Set TLS session data. |
| |
| The SetSessionData() function set data for a new TLS session. All session data should |
| be set before BuildResponsePacket() invoked. |
| |
| @param[in] This Pointer to the EFI_TLS_PROTOCOL instance. |
| @param[in] DataType TLS session data type. |
| @param[in] Data Pointer to session data. |
| @param[in] DataSize Total size of session data. |
| |
| @retval EFI_SUCCESS The TLS session data is set successfully. |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: |
| This is NULL. |
| Data is NULL. |
| DataSize is 0. |
| @retval EFI_UNSUPPORTED The DataType is unsupported. |
| @retval EFI_ACCESS_DENIED If the DataType is one of below: |
| EfiTlsClientRandom |
| EfiTlsServerRandom |
| EfiTlsKeyMaterial |
| @retval EFI_NOT_READY Current TLS session state is NOT |
| EfiTlsSessionStateNotStarted. |
| @retval EFI_OUT_OF_RESOURCES Required system resources could not be allocated. |
| **/ |
| EFI_STATUS |
| EFIAPI |
| TlsSetSessionData ( |
| IN EFI_TLS_PROTOCOL *This, |
| IN EFI_TLS_SESSION_DATA_TYPE DataType, |
| IN VOID *Data, |
| IN UINTN DataSize |
| ) |
| { |
| EFI_STATUS Status; |
| TLS_INSTANCE *Instance; |
| UINT16 *CipherId; |
| UINTN Index; |
| |
| EFI_TPL OldTpl; |
| |
| Status = EFI_SUCCESS; |
| CipherId = NULL; |
| |
| if (This == NULL || Data == NULL || DataSize == 0) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| OldTpl = gBS->RaiseTPL (TPL_CALLBACK); |
| |
| Instance = TLS_INSTANCE_FROM_PROTOCOL (This); |
| |
| if (DataType != EfiTlsSessionState && Instance->TlsSessionState != EfiTlsSessionNotStarted){ |
| Status = EFI_NOT_READY; |
| goto ON_EXIT; |
| } |
| |
| switch (DataType) { |
| // |
| // Session Configuration |
| // |
| case EfiTlsVersion: |
| if (DataSize != sizeof (EFI_TLS_VERSION)) { |
| Status = EFI_INVALID_PARAMETER; |
| goto ON_EXIT; |
| } |
| |
| Status = TlsSetVersion (Instance->TlsConn, ((EFI_TLS_VERSION *) Data)->Major, ((EFI_TLS_VERSION *) Data)->Minor); |
| break; |
| case EfiTlsConnectionEnd: |
| if (DataSize != sizeof (EFI_TLS_CONNECTION_END)) { |
| Status = EFI_INVALID_PARAMETER; |
| goto ON_EXIT; |
| } |
| |
| Status = TlsSetConnectionEnd (Instance->TlsConn, *((EFI_TLS_CONNECTION_END *) Data)); |
| break; |
| case EfiTlsCipherList: |
| CipherId = AllocatePool (DataSize); |
| if (CipherId == NULL) { |
| Status = EFI_OUT_OF_RESOURCES; |
| goto ON_EXIT; |
| } |
| |
| for (Index = 0; Index < DataSize / sizeof (EFI_TLS_CIPHER); Index++) { |
| *(CipherId +Index) = HTONS (*(((UINT16 *) Data) + Index)); |
| } |
| |
| Status = TlsSetCipherList (Instance->TlsConn, CipherId, DataSize / sizeof (EFI_TLS_CIPHER)); |
| |
| FreePool (CipherId); |
| break; |
| case EfiTlsCompressionMethod: |
| // |
| // TLS seems only define one CompressionMethod.null, which specifies that data exchanged via the |
| // record protocol will not be compressed. |
| // More information from OpenSSL: http://www.openssl.org/docs/manmaster/ssl/SSL_COMP_add_compression_method.html |
| // The TLS RFC does however not specify compression methods or their corresponding identifiers, |
| // so there is currently no compatible way to integrate compression with unknown peers. |
| // It is therefore currently not recommended to integrate compression into applications. |
| // Applications for non-public use may agree on certain compression methods. |
| // Using different compression methods with the same identifier will lead to connection failure. |
| // |
| for (Index = 0; Index < DataSize / sizeof (EFI_TLS_COMPRESSION); Index++) { |
| Status = TlsSetCompressionMethod (*((UINT8 *) Data + Index)); |
| if (EFI_ERROR (Status)) { |
| break; |
| } |
| } |
| |
| break; |
| case EfiTlsExtensionData: |
| Status = EFI_UNSUPPORTED; |
| goto ON_EXIT; |
| case EfiTlsVerifyMethod: |
| if (DataSize != sizeof (EFI_TLS_VERIFY)) { |
| Status = EFI_INVALID_PARAMETER; |
| goto ON_EXIT; |
| } |
| |
| TlsSetVerify (Instance->TlsConn, *((UINT32 *) Data)); |
| break; |
| case EfiTlsSessionID: |
| if (DataSize != sizeof (EFI_TLS_SESSION_ID)) { |
| Status = EFI_INVALID_PARAMETER; |
| goto ON_EXIT; |
| } |
| |
| Status = TlsSetSessionId ( |
| Instance->TlsConn, |
| ((EFI_TLS_SESSION_ID *) Data)->Data, |
| ((EFI_TLS_SESSION_ID *) Data)->Length |
| ); |
| break; |
| case EfiTlsSessionState: |
| if (DataSize != sizeof (EFI_TLS_SESSION_STATE)) { |
| Status = EFI_INVALID_PARAMETER; |
| goto ON_EXIT; |
| } |
| |
| Instance->TlsSessionState = *(EFI_TLS_SESSION_STATE *) Data; |
| break; |
| // |
| // Session information |
| // |
| case EfiTlsClientRandom: |
| Status = EFI_ACCESS_DENIED; |
| break; |
| case EfiTlsServerRandom: |
| Status = EFI_ACCESS_DENIED; |
| break; |
| case EfiTlsKeyMaterial: |
| Status = EFI_ACCESS_DENIED; |
| break; |
| // |
| // Unsupported type. |
| // |
| default: |
| Status = EFI_UNSUPPORTED; |
| } |
| |
| ON_EXIT: |
| gBS->RestoreTPL (OldTpl); |
| return Status; |
| } |
| |
| /** |
| Get TLS session data. |
| |
| The GetSessionData() function return the TLS session information. |
| |
| @param[in] This Pointer to the EFI_TLS_PROTOCOL instance. |
| @param[in] DataType TLS session data type. |
| @param[in, out] Data Pointer to session data. |
| @param[in, out] DataSize Total size of session data. On input, it means |
| the size of Data buffer. On output, it means the size |
| of copied Data buffer if EFI_SUCCESS, and means the |
| size of desired Data buffer if EFI_BUFFER_TOO_SMALL. |
| |
| @retval EFI_SUCCESS The TLS session data is got successfully. |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: |
| This is NULL. |
| DataSize is NULL. |
| Data is NULL if *DataSize is not zero. |
| @retval EFI_UNSUPPORTED The DataType is unsupported. |
| @retval EFI_NOT_FOUND The TLS session data is not found. |
| @retval EFI_NOT_READY The DataType is not ready in current session state. |
| @retval EFI_BUFFER_TOO_SMALL The buffer is too small to hold the data. |
| **/ |
| EFI_STATUS |
| EFIAPI |
| TlsGetSessionData ( |
| IN EFI_TLS_PROTOCOL *This, |
| IN EFI_TLS_SESSION_DATA_TYPE DataType, |
| IN OUT VOID *Data, OPTIONAL |
| IN OUT UINTN *DataSize |
| ) |
| { |
| EFI_STATUS Status; |
| TLS_INSTANCE *Instance; |
| |
| EFI_TPL OldTpl; |
| |
| Status = EFI_SUCCESS; |
| |
| if (This == NULL || DataSize == NULL || (Data == NULL && *DataSize != 0)) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| OldTpl = gBS->RaiseTPL (TPL_CALLBACK); |
| |
| Instance = TLS_INSTANCE_FROM_PROTOCOL (This); |
| |
| if (Instance->TlsSessionState == EfiTlsSessionNotStarted && |
| (DataType == EfiTlsSessionID || DataType == EfiTlsClientRandom || |
| DataType == EfiTlsServerRandom || DataType == EfiTlsKeyMaterial)) { |
| Status = EFI_NOT_READY; |
| goto ON_EXIT; |
| } |
| |
| switch (DataType) { |
| case EfiTlsVersion: |
| if (*DataSize < sizeof (EFI_TLS_VERSION)) { |
| *DataSize = sizeof (EFI_TLS_VERSION); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_VERSION); |
| *((UINT16 *) Data) = HTONS (TlsGetVersion (Instance->TlsConn)); |
| break; |
| case EfiTlsConnectionEnd: |
| if (*DataSize < sizeof (EFI_TLS_CONNECTION_END)) { |
| *DataSize = sizeof (EFI_TLS_CONNECTION_END); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_CONNECTION_END); |
| *((UINT8 *) Data) = TlsGetConnectionEnd (Instance->TlsConn); |
| break; |
| case EfiTlsCipherList: |
| // |
| // Get the current session cipher suite. |
| // |
| if (*DataSize < sizeof (EFI_TLS_CIPHER)) { |
| *DataSize = sizeof (EFI_TLS_CIPHER); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof(EFI_TLS_CIPHER); |
| Status = TlsGetCurrentCipher (Instance->TlsConn, (UINT16 *) Data); |
| *((UINT16 *) Data) = HTONS (*((UINT16 *) Data)); |
| break; |
| case EfiTlsCompressionMethod: |
| // |
| // Get the current session compression method. |
| // |
| if (*DataSize < sizeof (EFI_TLS_COMPRESSION)) { |
| *DataSize = sizeof (EFI_TLS_COMPRESSION); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_COMPRESSION); |
| Status = TlsGetCurrentCompressionId (Instance->TlsConn, (UINT8 *) Data); |
| break; |
| case EfiTlsExtensionData: |
| Status = EFI_UNSUPPORTED; |
| goto ON_EXIT; |
| case EfiTlsVerifyMethod: |
| if (*DataSize < sizeof (EFI_TLS_VERIFY)) { |
| *DataSize = sizeof (EFI_TLS_VERIFY); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_VERIFY); |
| *((UINT32 *) Data) = TlsGetVerify (Instance->TlsConn); |
| break; |
| case EfiTlsSessionID: |
| if (*DataSize < sizeof (EFI_TLS_SESSION_ID)) { |
| *DataSize = sizeof (EFI_TLS_SESSION_ID); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_SESSION_ID); |
| Status = TlsGetSessionId ( |
| Instance->TlsConn, |
| ((EFI_TLS_SESSION_ID *) Data)->Data, |
| &(((EFI_TLS_SESSION_ID *) Data)->Length) |
| ); |
| break; |
| case EfiTlsSessionState: |
| if (*DataSize < sizeof (EFI_TLS_SESSION_STATE)) { |
| *DataSize = sizeof (EFI_TLS_SESSION_STATE); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_SESSION_STATE); |
| CopyMem (Data, &Instance->TlsSessionState, *DataSize); |
| break; |
| case EfiTlsClientRandom: |
| if (*DataSize < sizeof (EFI_TLS_RANDOM)) { |
| *DataSize = sizeof (EFI_TLS_RANDOM); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_RANDOM); |
| TlsGetClientRandom (Instance->TlsConn, (UINT8 *) Data); |
| break; |
| case EfiTlsServerRandom: |
| if (*DataSize < sizeof (EFI_TLS_RANDOM)) { |
| *DataSize = sizeof (EFI_TLS_RANDOM); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_RANDOM); |
| TlsGetServerRandom (Instance->TlsConn, (UINT8 *) Data); |
| break; |
| case EfiTlsKeyMaterial: |
| if (*DataSize < sizeof (EFI_TLS_MASTER_SECRET)) { |
| *DataSize = sizeof (EFI_TLS_MASTER_SECRET); |
| Status = EFI_BUFFER_TOO_SMALL; |
| goto ON_EXIT; |
| } |
| *DataSize = sizeof (EFI_TLS_MASTER_SECRET); |
| Status = TlsGetKeyMaterial (Instance->TlsConn, (UINT8 *) Data); |
| break; |
| // |
| // Unsupported type. |
| // |
| default: |
| Status = EFI_UNSUPPORTED; |
| } |
| |
| ON_EXIT: |
| gBS->RestoreTPL (OldTpl); |
| return Status; |
| } |
| |
| /** |
| Build response packet according to TLS state machine. This function is only valid for |
| alert, handshake and change_cipher_spec content type. |
| |
| The BuildResponsePacket() function builds TLS response packet in response to the TLS |
| request packet specified by RequestBuffer and RequestSize. If RequestBuffer is NULL and |
| RequestSize is 0, and TLS session status is EfiTlsSessionNotStarted, the TLS session |
| will be initiated and the response packet needs to be ClientHello. If RequestBuffer is |
| NULL and RequestSize is 0, and TLS session status is EfiTlsSessionClosing, the TLS |
| session will be closed and response packet needs to be CloseNotify. If RequestBuffer is |
| NULL and RequestSize is 0, and TLS session status is EfiTlsSessionError, the TLS |
| session has errors and the response packet needs to be Alert message based on error |
| type. |
| |
| @param[in] This Pointer to the EFI_TLS_PROTOCOL instance. |
| @param[in] RequestBuffer Pointer to the most recently received TLS packet. NULL |
| means TLS need initiate the TLS session and response |
| packet need to be ClientHello. |
| @param[in] RequestSize Packet size in bytes for the most recently received TLS |
| packet. 0 is only valid when RequestBuffer is NULL. |
| @param[out] Buffer Pointer to the buffer to hold the built packet. |
| @param[in, out] BufferSize Pointer to the buffer size in bytes. On input, it is |
| the buffer size provided by the caller. On output, it |
| is the buffer size in fact needed to contain the |
| packet. |
| |
| @retval EFI_SUCCESS The required TLS packet is built successfully. |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: |
| This is NULL. |
| RequestBuffer is NULL but RequestSize is NOT 0. |
| RequestSize is 0 but RequestBuffer is NOT NULL. |
| BufferSize is NULL. |
| Buffer is NULL if *BufferSize is not zero. |
| @retval EFI_BUFFER_TOO_SMALL BufferSize is too small to hold the response packet. |
| @retval EFI_NOT_READY Current TLS session state is NOT ready to build |
| ResponsePacket. |
| @retval EFI_ABORTED Something wrong build response packet. |
| **/ |
| EFI_STATUS |
| EFIAPI |
| TlsBuildResponsePacket ( |
| IN EFI_TLS_PROTOCOL *This, |
| IN UINT8 *RequestBuffer, OPTIONAL |
| IN UINTN RequestSize, OPTIONAL |
| OUT UINT8 *Buffer, OPTIONAL |
| IN OUT UINTN *BufferSize |
| ) |
| { |
| EFI_STATUS Status; |
| TLS_INSTANCE *Instance; |
| EFI_TPL OldTpl; |
| |
| Status = EFI_SUCCESS; |
| |
| if ((This == NULL) || (BufferSize == NULL) || |
| (RequestBuffer == NULL && RequestSize != 0) || |
| (RequestBuffer != NULL && RequestSize == 0) || |
| (Buffer == NULL && *BufferSize !=0)) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| OldTpl = gBS->RaiseTPL (TPL_CALLBACK); |
| |
| Instance = TLS_INSTANCE_FROM_PROTOCOL (This); |
| |
| if(RequestBuffer == NULL && RequestSize == 0) { |
| switch (Instance->TlsSessionState) { |
| case EfiTlsSessionNotStarted: |
| // |
| // ClientHello. |
| // |
| Status = TlsDoHandshake ( |
| Instance->TlsConn, |
| NULL, |
| 0, |
| Buffer, |
| BufferSize |
| ); |
| if (EFI_ERROR (Status)) { |
| goto ON_EXIT; |
| } |
| |
| // |
| // *BufferSize should not be zero when ClientHello. |
| // |
| if (*BufferSize == 0) { |
| Status = EFI_ABORTED; |
| goto ON_EXIT; |
| } |
| |
| Instance->TlsSessionState = EfiTlsSessionHandShaking; |
| |
| break; |
| case EfiTlsSessionClosing: |
| // |
| // TLS session will be closed and response packet needs to be CloseNotify. |
| // |
| Status = TlsCloseNotify ( |
| Instance->TlsConn, |
| Buffer, |
| BufferSize |
| ); |
| if (EFI_ERROR (Status)) { |
| goto ON_EXIT; |
| } |
| |
| // |
| // *BufferSize should not be zero when build CloseNotify message. |
| // |
| if (*BufferSize == 0) { |
| Status = EFI_ABORTED; |
| goto ON_EXIT; |
| } |
| |
| break; |
| case EfiTlsSessionError: |
| // |
| // TLS session has errors and the response packet needs to be Alert |
| // message based on error type. |
| // |
| Status = TlsHandleAlert ( |
| Instance->TlsConn, |
| NULL, |
| 0, |
| Buffer, |
| BufferSize |
| ); |
| if (EFI_ERROR (Status)) { |
| goto ON_EXIT; |
| } |
| |
| break; |
| default: |
| // |
| // Current TLS session state is NOT ready to build ResponsePacket. |
| // |
| Status = EFI_NOT_READY; |
| } |
| } else { |
| // |
| // 1. Received packet may have multiple TLS record messages. |
| // 2. One TLS record message may have multiple handshake protocol. |
| // 3. Some errors may be happened in handshake. |
| // TlsDoHandshake() can handle all of those cases. |
| // |
| if (TlsInHandshake (Instance->TlsConn)) { |
| Status = TlsDoHandshake ( |
| Instance->TlsConn, |
| RequestBuffer, |
| RequestSize, |
| Buffer, |
| BufferSize |
| ); |
| if (EFI_ERROR (Status)) { |
| goto ON_EXIT; |
| } |
| |
| if (!TlsInHandshake (Instance->TlsConn)) { |
| Instance->TlsSessionState = EfiTlsSessionDataTransferring; |
| } |
| } else { |
| // |
| // Must be alert message, Decrypt it and build the ResponsePacket. |
| // |
| ASSERT (((TLS_RECORD_HEADER *) RequestBuffer)->ContentType == TLS_CONTENT_TYPE_ALERT); |
| |
| Status = TlsHandleAlert ( |
| Instance->TlsConn, |
| RequestBuffer, |
| RequestSize, |
| Buffer, |
| BufferSize |
| ); |
| if (EFI_ERROR (Status)) { |
| if (Status != EFI_BUFFER_TOO_SMALL) { |
| Instance->TlsSessionState = EfiTlsSessionError; |
| } |
| |
| goto ON_EXIT; |
| } |
| } |
| } |
| |
| ON_EXIT: |
| gBS->RestoreTPL (OldTpl); |
| return Status; |
| } |
| |
| /** |
| Decrypt or encrypt TLS packet during session. This function is only valid after |
| session connected and for application_data content type. |
| |
| The ProcessPacket () function process each inbound or outbound TLS APP packet. |
| |
| @param[in] This Pointer to the EFI_TLS_PROTOCOL instance. |
| @param[in, out] FragmentTable Pointer to a list of fragment. The caller will take |
| responsible to handle the original FragmentTable while |
| it may be reallocated in TLS driver. If CryptMode is |
| EfiTlsEncrypt, on input these fragments contain the TLS |
| header and plain text TLS APP payload; on output these |
| fragments contain the TLS header and cipher text TLS |
| APP payload. If CryptMode is EfiTlsDecrypt, on input |
| these fragments contain the TLS header and cipher text |
| TLS APP payload; on output these fragments contain the |
| TLS header and plain text TLS APP payload. |
| @param[in] FragmentCount Number of fragment. |
| @param[in] CryptMode Crypt mode. |
| |
| @retval EFI_SUCCESS The operation completed successfully. |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: |
| This is NULL. |
| FragmentTable is NULL. |
| FragmentCount is NULL. |
| CryptoMode is invalid. |
| @retval EFI_NOT_READY Current TLS session state is NOT |
| EfiTlsSessionDataTransferring. |
| @retval EFI_ABORTED Something wrong decryption the message. TLS session |
| status will become EfiTlsSessionError. The caller need |
| call BuildResponsePacket() to generate Error Alert |
| message and send it out. |
| @retval EFI_OUT_OF_RESOURCES No enough resource to finish the operation. |
| **/ |
| EFI_STATUS |
| EFIAPI |
| TlsProcessPacket ( |
| IN EFI_TLS_PROTOCOL *This, |
| IN OUT EFI_TLS_FRAGMENT_DATA **FragmentTable, |
| IN UINT32 *FragmentCount, |
| IN EFI_TLS_CRYPT_MODE CryptMode |
| ) |
| { |
| EFI_STATUS Status; |
| TLS_INSTANCE *Instance; |
| |
| EFI_TPL OldTpl; |
| |
| Status = EFI_SUCCESS; |
| |
| if (This == NULL || FragmentTable == NULL || FragmentCount == NULL) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| OldTpl = gBS->RaiseTPL (TPL_CALLBACK); |
| |
| Instance = TLS_INSTANCE_FROM_PROTOCOL (This); |
| |
| if (Instance->TlsSessionState != EfiTlsSessionDataTransferring) { |
| Status = EFI_NOT_READY; |
| goto ON_EXIT; |
| } |
| |
| // |
| // Packet sent or received may have multiple TLS record messages (Application data type). |
| // So,on input these fragments contain the TLS header and TLS APP payload; |
| // on output these fragments also contain the TLS header and TLS APP payload. |
| // |
| switch (CryptMode) { |
| case EfiTlsEncrypt: |
| Status = TlsEncryptPacket (Instance, FragmentTable, FragmentCount); |
| break; |
| case EfiTlsDecrypt: |
| Status = TlsDecryptPacket (Instance, FragmentTable, FragmentCount); |
| break; |
| default: |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| ON_EXIT: |
| gBS->RestoreTPL (OldTpl); |
| return Status; |
| } |