/** @file
  Common interfaces to call Security library.

  Copyright (c) 2009 - 2011, 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 "IpSecCryptIo.h"
//
// The informations for the supported Encrypt/Decrpt Alogrithm.
//
GLOBAL_REMOVE_IF_UNREFERENCED ENCRYPT_ALGORITHM mIpsecEncryptAlgorithmList[IPSEC_ENCRYPT_ALGORITHM_LIST_SIZE] = {
  {IKE_EALG_NULL, 0, 0, 1, NULL, NULL, NULL, NULL},
  {IKE_EALG_NONE, 0, 0, 1, NULL, NULL, NULL, NULL},  
  {IKE_EALG_3DESCBC, 24, 8, 8, TdesGetContextSize, TdesInit, TdesCbcEncrypt, TdesCbcDecrypt},
  {IKE_EALG_AESCBC, 16, 16, 16, AesGetContextSize, AesInit, AesCbcEncrypt, AesCbcDecrypt}
};

//
// The informations for the supported Authentication algorithm
//
GLOBAL_REMOVE_IF_UNREFERENCED AUTH_ALGORITHM mIpsecAuthAlgorithmList[IPSEC_AUTH_ALGORITHM_LIST_SIZE] = {
  {IKE_AALG_NONE, 0, 0, 0, NULL, NULL, NULL, NULL},
  {IKE_AALG_NULL, 0, 0, 0, NULL, NULL, NULL, NULL},
  {IKE_AALG_SHA1HMAC, 20, 12, 64, HmacSha1GetContextSize, HmacSha1Init, HmacSha1Update, HmacSha1Final}
};

//
// The information for the supported Hash aglorithm
//
GLOBAL_REMOVE_IF_UNREFERENCED HASH_ALGORITHM mIpsecHashAlgorithmList[IPSEC_HASH_ALGORITHM_LIST_SIZE] = {
  {IKE_AALG_NONE, 0, 0, 0, NULL, NULL, NULL, NULL},
  {IKE_AALG_NULL, 0, 0, 0, NULL, NULL, NULL, NULL},
  {IKE_AALG_SHA1HMAC, 20, 12, 64, Sha1GetContextSize, Sha1Init, Sha1Update, Sha1Final}
};

BOOLEAN  mInitialRandomSeed = FALSE;

/**
  Get the block size of specified encryption alogrithm.

  @param[in]  AlgorithmId          The encryption algorithm ID.

  @return The value of block size.

**/
UINTN
IpSecGetEncryptBlockSize (
  IN UINT8   AlgorithmId
  )
{
  UINT8 Index;

  for (Index = 0; Index < IPSEC_ENCRYPT_ALGORITHM_LIST_SIZE; Index++) {
    if (AlgorithmId == mIpsecEncryptAlgorithmList[Index].AlgorithmId) {
      return mIpsecEncryptAlgorithmList[Index].BlockSize;
    }
  }

  return (UINTN) -1;
}

/**
  Get the key length of the specified encryption alogrithm.

  @param[in]  AlgorithmId          The encryption algorithm ID.

  @return The value of key length.

**/
UINTN
IpSecGetEncryptKeyLength (
  IN UINT8   AlgorithmId
  )
{
  UINT8 Index;

  for (Index = 0; Index < IPSEC_ENCRYPT_ALGORITHM_LIST_SIZE; Index++) {
    if (AlgorithmId == mIpsecEncryptAlgorithmList[Index].AlgorithmId) {
      return mIpsecEncryptAlgorithmList[Index].KeyLength;
    }
  }

  return (UINTN) -1;
}

/**
  Get the IV size of the specified encryption alogrithm.

  @param[in]  AlgorithmId          The encryption algorithm ID.

  @return The value of IV size.

**/
UINTN
IpSecGetEncryptIvLength (
  IN UINT8 AlgorithmId
  )
{
  UINT8 Index;

  for (Index = 0; Index < IPSEC_ENCRYPT_ALGORITHM_LIST_SIZE; Index++) {
    if (AlgorithmId == mIpsecEncryptAlgorithmList[Index].AlgorithmId) {
      return mIpsecEncryptAlgorithmList[Index].IvLength;
    }
  }

  return (UINTN) -1;
}

/**
  Get the HMAC digest length by the specified Algorithm ID.

  @param[in]  AlgorithmId  The specified Alogrithm ID.

  @return The digest length of the specified Authentication Algorithm ID.

**/
UINTN
IpSecGetHmacDigestLength (
  IN UINT8  AlgorithmId
  )
{
  UINT8 Index;

  for (Index = 0; Index < IPSEC_AUTH_ALGORITHM_LIST_SIZE; Index++) {
    if (mIpsecAuthAlgorithmList[Index].AlgorithmId == AlgorithmId) {
      //
      // Return the Digest Length of the Algorithm.
      //
      return mIpsecAuthAlgorithmList[Index].DigestLength;
    }
  }

  return 0;
}

/**
  Get the ICV size of the specified Authenticaion alogrithm.

  @param[in]  AlgorithmId          The Authentication algorithm ID.

  @return The value of ICV size.

**/
UINTN
IpSecGetIcvLength (
  IN UINT8  AlgorithmId
  )
{
  UINT8 Index;

  for (Index = 0; Index < IPSEC_AUTH_ALGORITHM_LIST_SIZE; Index++) {
    if (AlgorithmId == mIpsecAuthAlgorithmList[Index].AlgorithmId) {
      return mIpsecAuthAlgorithmList[Index].IcvLength;
    }
  }

  return (UINTN) -1;
}

/**
  Generate a random data for IV. If the IvSize is zero, not needed to create
  IV and return EFI_SUCCESS.

  @param[in]  IvBuffer  The pointer of the IV buffer.
  @param[in]  IvSize    The IV size in bytes.

  @retval     EFI_SUCCESS  Create a random data for IV.

**/
EFI_STATUS
IpSecGenerateIv (
  IN UINT8                           *IvBuffer,
  IN UINTN                           IvSize
  )
{
  if (IvSize != 0) {
    return IpSecCryptoIoGenerateRandomBytes (IvBuffer, IvSize);
  }
  
  return EFI_SUCCESS;
}

/**
  Get index of the specified encryption alogrithm from the mIpsecEncryptAlgorithemList.

  @param[in]  AlgorithmId          The encryption algorithm ID.

  @return the index.
  
**/
UINTN
IpSecGetIndexFromEncList (
  IN UINT8   AlgorithmId
  )
{
  UINT8 Index;
  
  for (Index = 0; Index < IPSEC_ENCRYPT_ALGORITHM_LIST_SIZE; Index++) {
    if (AlgorithmId == mIpsecEncryptAlgorithmList[Index].AlgorithmId) {
      return Index;
    }
  }
  
  return (UINTN) -1;
}

/**
  Get index of the specified encryption alogrithm from the mIpsecAuthAlgorithemList.

  @param[in]  AlgorithmId          The encryption algorithm ID.

  @return the index.
  
**/
UINTN
IpSecGetIndexFromAuthList (
  IN UINT8   AlgorithmId
  )
{
  UINT8 Index;
  
  for (Index = 0; Index < IPSEC_AUTH_ALGORITHM_LIST_SIZE; Index++) {
    if (AlgorithmId == mIpsecAuthAlgorithmList[Index].AlgorithmId) {
      //
      // The BlockSize is same with IvSize.
      //
      return Index;
    }
  }
  
  return (UINTN) -1;
}

/**
  Encrypt the buffer.

  This function calls relevant encryption interface from CryptoLib according to
  the input alogrithm ID. The InData should be multiple of block size. This function
  doesn't perform the padding. If it has the Ivec data, the length of it should be
  same with the block size. The block size is different from the different algorithm.

  @param[in]       AlgorithmId    The Alogrithem identification defined in RFC.
  @param[in]       Key            Pointer to the buffer containing encrypting key.
  @param[in]       KeyBits        The length of the key in bits.
  @param[in]       Ivec           Point to the buffer containning the Initializeion
                                  Vector (IV) data.
  @param[in]       InData         Point to the buffer containing the data to be
                                  encrypted.
  @param[in]       InDataLength   The length of InData in Bytes.
  @param[out]      OutData        Point to the buffer that receives the encryption
                                  output.

  @retval EFI_UNSUPPORTED       The input Algorithm is not supported.
  @retval EFI_OUT_OF_RESOURCE   The required resource can't be allocated.
  @retval EFI_SUCCESS           The operation completed successfully.

**/
EFI_STATUS
IpSecCryptoIoEncrypt (
  IN CONST UINT8      AlgorithmId,
  IN CONST UINT8      *Key,
  IN CONST UINTN      KeyBits,
  IN CONST UINT8      *Ivec, OPTIONAL
  IN       UINT8      *InData,
  IN       UINTN      InDataLength,
     OUT   UINT8      *OutData
  )
{  
  UINTN         Index;
  UINTN         ContextSize;
  UINT8         *Context;
  EFI_STATUS    Status;
  
  Status = EFI_UNSUPPORTED;
  
  switch (AlgorithmId) {

  case IKE_EALG_NULL:
  case IKE_EALG_NONE:
    CopyMem (OutData, InData, InDataLength);
    return EFI_SUCCESS;

  case IKE_EALG_3DESCBC:
  case IKE_EALG_AESCBC:
    Index = IpSecGetIndexFromEncList (AlgorithmId);
    if (Index == -1) {
      return Status;
    }
    //
    // Get Context Size
    //
    ContextSize = mIpsecEncryptAlgorithmList[Index].CipherGetContextSize ();
    Context     = AllocateZeroPool (ContextSize);

    if (Context == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    //
    // Initiate Context
    //
    if (mIpsecEncryptAlgorithmList[Index].CipherInitiate (Context, Key, KeyBits)) {
      if (mIpsecEncryptAlgorithmList[Index].CipherEncrypt (Context, InData, InDataLength, Ivec, OutData)) {
        Status = EFI_SUCCESS;
      }
    }
    break;

  default:
    return Status;

  }

  if (Context != NULL) {
    FreePool (Context);
  }
  
  return Status;
}

/**
  Decrypts the buffer.

  This function calls relevant Decryption interface from CryptoLib according to
  the input alogrithm ID. The InData should be multiple of block size. This function
  doesn't perform the padding. If it has the Ivec data, the length of it should be
  same with the block size. The block size is different from the different algorithm.

  @param[in]       AlgorithmId    The Alogrithem identification defined in RFC.
  @param[in]       Key            Pointer to the buffer containing encrypting key.
  @param[in]       KeyBits        The length of the key in bits.
  @param[in]       Ivec           Point to the buffer containning the Initializeion
                                  Vector (IV) data.
  @param[in]       InData         Point to the buffer containing the data to be
                                  decrypted.
  @param[in]       InDataLength   The length of InData in Bytes.
  @param[out]      OutData        Pointer to the buffer that receives the decryption
                                  output.

  @retval EFI_UNSUPPORTED       The input Algorithm is not supported.
  @retval EFI_OUT_OF_RESOURCE   The required resource can't be allocated.
  @retval EFI_SUCCESS           The operation completed successfully.

**/
EFI_STATUS
IpSecCryptoIoDecrypt (
  IN CONST UINT8      AlgorithmId,
  IN CONST UINT8      *Key,
  IN CONST UINTN      KeyBits,
  IN CONST UINT8      *Ivec, OPTIONAL
  IN       UINT8      *InData,
  IN       UINTN      InDataLength,
     OUT   UINT8      *OutData
  )
{  
  UINTN         Index;
  UINTN         ContextSize;
  UINT8         *Context;
  EFI_STATUS    Status;

  Status = EFI_UNSUPPORTED;

  switch (AlgorithmId) {

  case IKE_EALG_NULL:
  case IKE_EALG_NONE:
    CopyMem (OutData, InData, InDataLength);
    return EFI_SUCCESS;

  case IKE_EALG_3DESCBC:
  case IKE_EALG_AESCBC:
    Index = IpSecGetIndexFromEncList(AlgorithmId);
    if (Index == -1) {
      return Status;
    }

    //
    // Get Context Size
    //
    ContextSize = mIpsecEncryptAlgorithmList[Index].CipherGetContextSize();
    Context     = AllocateZeroPool (ContextSize);
    if (Context == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // Initiate Context
    //
    if (mIpsecEncryptAlgorithmList[Index].CipherInitiate (Context, Key, KeyBits)) {
      if (mIpsecEncryptAlgorithmList[Index].CipherDecrypt (Context, InData, InDataLength, Ivec, OutData)) {
        Status = EFI_SUCCESS;      
      }
    }
    break;

  default:
    return Status;
  }

  if (Context != NULL) {
    FreePool (Context);
  }

  return Status;
}

/**
  Digests the Payload with key and store the result into the OutData.

  This function calls relevant Hmac interface from CryptoLib according to
  the input alogrithm ID. It computes all datas from InDataFragment and output
  the result into the OutData buffer. If the OutDataSize is larger than the related
  HMAC alogrithm output size, return EFI_INVALID_PARAMETER.
  
  @param[in]      AlgorithmId     The authentication Identification.
  @param[in]      Key             Pointer of the authentication key.
  @param[in]      KeyLength       The length of the Key in bytes.
  @param[in]      InDataFragment  The list contains all data to be authenticated.
  @param[in]      FragmentCount   The size of the InDataFragment.
  @param[out]     OutData         For in, the buffer to receive the output data.
                                  For out, the buffer contains the authenticated data.
  @param[in]      OutDataSize     The size of the buffer of OutData.

  @retval EFI_UNSUPPORTED       If the AuthAlg is not in the support list.
  @retval EFI_INVALID_PARAMETER The OutData buffer size is larger than algorithm digest size.
  @retval EFI_SUCCESS           Authenticate the payload successfully.
  @retval otherwise             Authentication of the payload fails.

**/
EFI_STATUS
IpSecCryptoIoHmac (
  IN     CONST UINT8              AlgorithmId,
  IN     CONST UINT8              *Key,
  IN           UINTN              KeyLength,
  IN           HASH_DATA_FRAGMENT *InDataFragment,
  IN           UINTN              FragmentCount,
     OUT       UINT8              *OutData,
  IN           UINTN              OutDataSize
  )
{
  UINTN        ContextSize;
  UINTN        Index;
  UINT8        FragmentIndex;
  UINT8        *HashContext;
  EFI_STATUS   Status;
  UINT8        *OutHashData;
  UINTN        OutHashSize;

  Status      = EFI_UNSUPPORTED;
  OutHashData = NULL;

  OutHashSize = IpSecGetHmacDigestLength (AlgorithmId);
  //
  // If the expected hash data size is larger than the related Hash algorithm
  // output length, return EFI_INVALID_PARAMETER.
  //
  if (OutDataSize > OutHashSize) {
    return EFI_INVALID_PARAMETER;
  }
  OutHashData = AllocatePool (OutHashSize);

  if (OutHashData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  switch (AlgorithmId) {

  case IKE_AALG_NONE :
  case IKE_AALG_NULL :
    return EFI_SUCCESS;

  case IKE_AALG_SHA1HMAC:
    Index = IpSecGetIndexFromAuthList (AlgorithmId);
    if (Index == -1) {
      return Status;
    }

    //
    // Get Context Size
    //
    ContextSize = mIpsecAuthAlgorithmList[Index].HmacGetContextSize();
    HashContext = AllocateZeroPool (ContextSize);

    if (HashContext == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Exit;
    }

    //
    // Initiate HMAC context and hash the input data.
    //
    if (mIpsecAuthAlgorithmList[Index].HmacInitiate(HashContext, Key, KeyLength)) {
      for (FragmentIndex = 0; FragmentIndex < FragmentCount; FragmentIndex++) {
        if (!mIpsecAuthAlgorithmList[Index].HmacUpdate (
                HashContext,
                InDataFragment[FragmentIndex].Data,
                InDataFragment[FragmentIndex].DataSize
                )
          ) {
          goto Exit;
        }
      }
      if (mIpsecAuthAlgorithmList[Index].HmacFinal (HashContext, OutHashData)) {
        //
        // In some cases, like the Icv computing, the Icv size might be less than
        // the key length size, so copy the part of hash data to the OutData.
        //
        CopyMem (OutData, OutHashData, OutDataSize);
        Status = EFI_SUCCESS;
      }

      goto Exit;
    }    
      
  default:
    return Status;
  }

Exit:
  if (HashContext != NULL) {
    FreePool (HashContext);
  }
  if (OutHashData != NULL) {
    FreePool (OutHashData);
  }

  return Status;
}

/**
  Digests the Payload and store the result into the OutData.

  This function calls relevant Hash interface from CryptoLib according to
  the input alogrithm ID. It computes all datas from InDataFragment and output
  the result into the OutData buffer. If the OutDataSize is larger than the related
  Hash alogrithm output size, return EFI_INVALID_PARAMETER.

  @param[in]      AlgorithmId     The authentication Identification.
  @param[in]      InDataFragment  A list contains all data to be authenticated.
  @param[in]      FragmentCount   The size of the InDataFragment.
  @param[out]     OutData         For in, the buffer to receive the output data.
                                  For out, the buffer contains the authenticated data.
  @param[in]      OutDataSize     The size of the buffer of OutData.

  @retval EFI_UNSUPPORTED       If the AuthAlg is not in the support list.
  @retval EFI_SUCCESS           Authenticated the payload successfully.
  @retval EFI_INVALID_PARAMETER If the OutDataSize is larger than the related Hash
                                algorithm could handle.
  @retval otherwise             Authentication of the payload failed.

**/
EFI_STATUS
IpSecCryptoIoHash (
  IN     CONST UINT8              AlgorithmId,
  IN           HASH_DATA_FRAGMENT *InDataFragment,
  IN           UINTN              FragmentCount,
     OUT       UINT8              *OutData,
  IN           UINTN              OutDataSize
  )
{
  UINTN        ContextSize;
  UINTN        Index;
  UINT8        FragmentIndex;
  UINT8        *HashContext;
  EFI_STATUS   Status;
  UINT8        *OutHashData;
  UINTN        OutHashSize;

  Status      = EFI_UNSUPPORTED;
  OutHashData = NULL;
  
  OutHashSize = IpSecGetHmacDigestLength (AlgorithmId);
  //
  // If the expected hash data size is larger than the related Hash algorithm
  // output length, return EFI_INVALID_PARAMETER.  
  //
  if (OutDataSize > OutHashSize) {
    return EFI_INVALID_PARAMETER;
  }
  OutHashData = AllocatePool (OutHashSize);
  if (OutHashData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  
  switch (AlgorithmId) {

  case IKE_AALG_NONE:
  case IKE_AALG_NULL:
    return EFI_SUCCESS;

  case IKE_AALG_SHA1HMAC:
    Index = IpSecGetIndexFromAuthList (AlgorithmId);
    if (Index == -1) {
      return Status;
    }
    //
    // Get Context Size
    //
    ContextSize = mIpsecHashAlgorithmList[Index].HashGetContextSize();
    HashContext = AllocateZeroPool (ContextSize);
    if (HashContext == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Exit;
    }
    
    //
    // Initiate Hash context and hash the input data.
    //
    if (mIpsecHashAlgorithmList[Index].HashInitiate(HashContext)) {
      for (FragmentIndex = 0; FragmentIndex < FragmentCount; FragmentIndex++) {
        if (!mIpsecHashAlgorithmList[Index].HashUpdate (
                HashContext,
                InDataFragment[FragmentIndex].Data,
                InDataFragment[FragmentIndex].DataSize
                )
          ) {
          goto Exit;
        }
      }
      if (mIpsecHashAlgorithmList[Index].HashFinal (HashContext, OutHashData)) {
        //
        // In some cases, like the Icv computing, the Icv size might be less than
        // the key length size, so copy the part of hash data to the OutData.
        //
        CopyMem (OutData, OutHashData, OutDataSize);            
        Status = EFI_SUCCESS;
      }
      
      goto Exit;        
    }    
    
  default:
    return Status;
  }

Exit:
  if (HashContext != NULL) {
    FreePool (HashContext);
  }
  if (OutHashData != NULL) {
    FreePool (OutHashData);
  }

  return Status;
}

/**
  Generates the Diffie-Hellman public key.

  This function first initiate a DHContext, then call the DhSetParameter() to set
  the prime and primelenght, at end call the DhGenerateKey() to generates random
  secret exponent, and computes the public key. The output returned via parameter
  PublicKey and PublicKeySize. DH context is updated accordingly. If the PublicKey
  buffer is too small to hold the public key, EFI_INVALID_PARAMETER is returned
  and PublicKeySize is set to the required buffer size to obtain the public key.

  @param[in, out] DhContext       Pointer to the DH context.
  @param[in]      Generator       Vlaue of generator.
  @param[in]      PrimeLength     Length in bits of prime to be generated.
  @param[in]      Prime           Pointer to the buffer to receive the generated
                                  prime number.
  @param[out]     PublicKey       Pointer to the buffer to receive generated public key.
  @param[in, out] PublicKeySize   For in, the size of PublicKey buffer in bytes.
                                  For out, the size of data returned in PublicKey
                                  buffer in bytes.

  @retval EFI_SUCCESS             The operation perfoms successfully.
  @retval Otherwise               The operation is failed.

**/
EFI_STATUS
IpSecCryptoIoDhGetPublicKey (
  IN OUT   UINT8  **DhContext,
  IN       UINTN  Generator,
  IN       UINTN  PrimeLength,
  IN CONST UINT8  *Prime,
     OUT   UINT8  *PublicKey,
  IN OUT   UINTN  *PublicKeySize
  ) 
{
  EFI_STATUS   Status;
  
  *DhContext = DhNew ();
  ASSERT (*DhContext != NULL);
  if (!DhSetParameter (*DhContext, Generator, PrimeLength, Prime)) {
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }

  if (!DhGenerateKey (*DhContext, PublicKey, PublicKeySize)) {
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }
  return EFI_SUCCESS;

Exit:
  if (*DhContext != NULL) {
    DhFree (*DhContext);
    DhContext = NULL;
  }
  
  return Status;
}

/**
  Generates exchanged common key.

  Given peer's public key, this function computes the exchanged common key, based
  on its own context including value of prime modulus and random secret exponent.

  @param[in, out] DhContext         Pointer to the DH context.
  @param[in]      PeerPublicKey     Pointer to the peer's Public Key.
  @param[in]      PeerPublicKeySize Size of peer's public key in bytes.
  @param[out]     Key               Pointer to the buffer to receive generated key.
  @param[in, out] KeySize           For in, the size of Key buffer in bytes.
                                    For out, the size of data returned in Key
                                    buffer in bytes.

  @retval EFI_SUCCESS              The operation perfoms successfully.
  @retval Otherwise                The operation is failed.

**/
EFI_STATUS
IpSecCryptoIoDhComputeKey (
  IN   OUT   UINT8  *DhContext,     
  IN   CONST UINT8  *PeerPublicKey,
  IN         UINTN  PeerPublicKeySize,
       OUT   UINT8  *Key,
  IN   OUT   UINTN  *KeySize
  )
{
  if (!DhComputeKey (DhContext, PeerPublicKey, PeerPublicKeySize, Key, KeySize)) {
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}

/**
  Releases the DH context. If DhContext is NULL, return EFI_INVALID_PARAMETER.

  @param[in, out]     DhContext         Pointer to the DH context to be freed.

  @retval EFI_SUCCESS              The operation perfoms successfully.
  @retval EFI_INVALID_PARAMETER    The DhContext is NULL.
  
**/
EFI_STATUS
IpSecCryptoIoFreeDh (
  IN   OUT   UINT8  **DhContext
  )
{ 
  if (*DhContext == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  DhFree (*DhContext);
  return EFI_SUCCESS;
}

/**
  Generates random numbers of specified size.

  If the Random Generator wasn't initiated, initiate it first, then call RandomBytes.

  @param[out]  OutBuffer        Pointer to buffer to receive random value.
  @param[in]   Bytes            Size of randome bytes to generate.

  @retval EFI_SUCCESS              The operation perfoms successfully.
  @retval Otherwise                The operation is failed.

**/
EFI_STATUS
IpSecCryptoIoGenerateRandomBytes (
  OUT UINT8*    OutBuffer,
  IN  UINTN     Bytes
  )
{
  if (!mInitialRandomSeed) {
    RandomSeed (NULL, 0);
    mInitialRandomSeed = TRUE;
  }
  if (RandomBytes (OutBuffer, Bytes)) {
    return EFI_SUCCESS;
  } else {
    return EFI_INVALID_PARAMETER;
  }
}

/**
  Authenticate data with the certificate.

  @param[in]      InData          Pointer to the Data to be signed.
  @param[in]      InDataSize      InData size in bytes.
  @param[in]      PrivateKey      Pointer to the  private key.
  @param[in]      PrivateKeySize  The size of Private Key in bytes.
  @param[in]      KeyPassWord     Pointer to the password for retrieving private key.
  @param[in]      KeyPwdSize      The size of Key Password in bytes.
  @param[out]     OutData         The pointer to the signed data.
  @param[in, out] OutDataSize     Pointer to contain the size of out data.
 
**/
VOID
IpSecCryptoIoAuthDataWithCertificate (
  IN     UINT8   *InData,
  IN     UINTN   InDataSize,
  IN     UINT8   *PrivateKey,
  IN     UINTN   PrivateKeySize,
  IN     UINT8   *KeyPassWord,
  IN     UINTN   KeyPwdSize,
     OUT UINT8   **OutData,
  IN OUT UINTN   *OutDataSize
  )
{
  UINT8         *RsaContext;
  UINT8         *Signature;
  UINTN         SigSize;
   
  SigSize   = 0;
  //
  // Retrieve RSA Private Key from password-protected PEM data
  //
  RsaGetPrivateKeyFromPem (
    (CONST UINT8 *)PrivateKey,
    PrivateKeySize,
    (CONST CHAR8 *)KeyPassWord,
    (VOID **) &RsaContext
    );
  if (RsaContext == NULL) {
    return;
  }

  //
  // Sign data
  //
  Signature = NULL;  
  if (!RsaPkcs1Sign (RsaContext, InData, InDataSize, Signature, &SigSize)) {
    Signature = AllocateZeroPool (SigSize);
  } else {
    return;
  } 

  RsaPkcs1Sign (RsaContext, InData, InDataSize, Signature, &SigSize);

  *OutData     = Signature;
  *OutDataSize = SigSize;

  if (RsaContext != NULL) {
    RsaFree (RsaContext);
  }
}

/**
  Verify the singed data with the public key which is contained in a certificate.

  @param[in]     InCert          Pointer to the Certificate which contains the
                                 public key.
  @param[in]     CertLen         The size of Certificate in bytes.
  @param[in]     InCa            Pointer to the CA certificate
  @param[in]     CaLen           The size of CA certificate in bytes.
  @param[in]     InData          Pointer to octect message hash to be checked.
  @param[in]     InDataSize      Size of the message hash in bytes.
  @param[in]     Singnature      The pointer to the RSA PKCS1-V1_5 signature to be verifed.
  @param[in]     SigSize         Size of signature in bytes.

  @retval  TRUE   Valid signature encoded in PKCS1-v1_5.
  @retval  FALSE  Invalid signature or invalid RSA context.
 
**/
BOOLEAN
IpSecCryptoIoVerifySignDataByCertificate (
  IN     UINT8   *InCert,
  IN     UINTN   CertLen,
  IN     UINT8   *InCa,
  IN     UINTN   CaLen,
  IN     UINT8   *InData,
  IN     UINTN   InDataSize,
  IN     UINT8   *Singnature,
  IN     UINTN   SigSize
  )
{
  UINT8         *RsaContext;
  BOOLEAN       Status;

  //
  // Create the RSA Context
  //
  RsaContext = RsaNew ();
  if (RsaContext == NULL) {
    return FALSE;
  }

  //
  // Verify the validity of X509 Certificate
  //
  if (!X509VerifyCert (InCert, CertLen, InCa, CaLen)) {
    return FALSE;
  }

  //
  // Retrieve the RSA public Key from Certificate
  //
  RsaGetPublicKeyFromX509 ((CONST UINT8 *)InCert, CertLen, (VOID **)&RsaContext);
 
  //
  // Verify data
  //
  Status = RsaPkcs1Verify (RsaContext, InData, InDataSize, Singnature, SigSize);

  if (RsaContext != NULL) {
    RsaFree (RsaContext);
  }

  return Status;
}

/**
  Retrieves the RSA Public Key from one X509 certificate (DER format only).

  @param[in]     InCert            Pointer to the certificate.
  @param[in]     CertLen           The size of the certificate in bytes.
  @param[out]    PublicKey         Pointer to the retrieved public key.
  @param[out]    PublicKeyLen      Size of Public Key in bytes.

  @retval  EFI_SUCCESS            Successfully get the public Key.
  @retval  EFI_INVALID_PARAMETER  The certificate is malformed.

**/
EFI_STATUS
IpSecCryptoIoGetPublicKeyFromCert (
  IN     UINT8   *InCert,
  IN     UINTN   CertLen,
  OUT    UINT8   **PublicKey,
  OUT    UINTN   *PublicKeyLen
  )
{
  UINT8         *RsaContext;
  EFI_STATUS    Status;

  Status = EFI_SUCCESS;

  //
  // Create the RSA Context
  //
  RsaContext = RsaNew ();

  //
  // Retrieve the RSA public key from CA Certificate
  //
  if (!RsaGetPublicKeyFromX509 ((CONST UINT8 *)InCert, CertLen, (VOID **) &RsaContext)) {
    Status = EFI_INVALID_PARAMETER;
    goto EXIT;
  }

  *PublicKeyLen = 0;
 
  RsaGetKey (RsaContext, RsaKeyN, NULL, PublicKeyLen);
 
  *PublicKey = AllocateZeroPool (*PublicKeyLen);
  ASSERT (*PublicKey != NULL);

  if (!RsaGetKey (RsaContext, RsaKeyN, *PublicKey, PublicKeyLen)) {
    Status = EFI_INVALID_PARAMETER;
  }

EXIT:
  if (RsaContext != NULL) {
    RsaFree (RsaContext);
  }

  return Status;
}

/**
  Retrieves the subject name from one X509 certificate (DER format only).

  @param[in]     InCert            Pointer to the X509 certificate.
  @param[in]     CertSize          The size of the X509 certificate in bytes.
  @param[out]    CertSubject       Pointer to the retrieved certificate subject.
  @param[out]    SubjectSize       The size of Certificate Subject in bytes.
  
  @retval  EFI_SUCCESS            Retrieved the certificate subject successfully.
  @retval  EFI_INVALID_PARAMETER  The certificate is malformed.
 
**/
EFI_STATUS
IpSecCryptoIoGetSubjectFromCert (
  IN     UINT8   *InCert,
  IN     UINTN   CertSize,
  OUT    UINT8   **CertSubject,
  OUT    UINTN   *SubjectSize
  )
{
  EFI_STATUS    Status;

  Status = EFI_SUCCESS;

  *SubjectSize = 0;
  X509GetSubjectName (InCert, CertSize, *CertSubject, SubjectSize);

  *CertSubject = AllocateZeroPool (*SubjectSize);
  if (!X509GetSubjectName (InCert, CertSize, *CertSubject, SubjectSize)) {
    Status = EFI_INVALID_PARAMETER;
  }

  return Status;
}
