/*******************************************************************************
 * Copyright 2016, 2017 ARM Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
#include "stdlib.h"
#include "string.h"
#include "time.h"
#include "pal.h"
#include "pal_plat_Crypto.h"
#include "pal_plat_rtos.h"
#include "mbedtls/aes.h"
#if (PAL_ENABLE_X509 == 1)
#include "mbedtls/x509_crt.h"
#include "mbedtls/x509_csr.h"
#endif 
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/sha256.h"
#include "mbedtls/md.h"
#include "mbedtls/ccm.h"
#include "mbedtls/entropy.h"
#include "mbedtls/cmac.h"
#include "mbedtls/asn1.h"
#include "mbedtls/ecp.h"

#include "mbedtls/ecdh.h"
#include "mbedtls/oid.h"
#include "mbedtls/platform_time.h"


typedef mbedtls_ccm_context palCCM_t;
typedef mbedtls_ecp_group palECGroup_t;
typedef mbedtls_ecp_point palECPoint_t;
typedef mbedtls_mpi palMP_t;
typedef mbedtls_pk_context palECKey_t;

#if (PAL_ENABLE_X509 == 1)
typedef mbedtls_x509write_csr palx509CSR_t; 
#endif

typedef mbedtls_cipher_context_t palCipherCtx_t;


//! forward declaration
//! This function is based on PAL random algorithm which uses CTR-DRBG algorithm
PAL_PRIVATE int pal_plat_entropySource( void *data, unsigned char *output, size_t len);

//! forward declarations
//! This function access directly to the plarform entropy source
//! it was added specialy for DRBG reseeding process
PAL_PRIVATE int pal_plat_entropySourceDRBG( void *data, unsigned char *output, size_t len);


typedef struct palSign{
    mbedtls_mpi r;
    mbedtls_mpi s;
}palSignature_t;

typedef struct palCtrDrbgCtx{
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctrDrbgCtx;
}palCtrDrbgCtx_t;

typedef struct palAes{
    mbedtls_aes_context platCtx;
    unsigned char stream_block[PAL_CRYPT_BLOCK_SIZE];  //The saved stream-block for resuming. Is overwritten by the function.
    size_t nc_off;   //The offset in the current stream_block
}palAes_t;

#if (PAL_ENABLE_X509 == 1)
typedef struct palX509Ctx{
    mbedtls_x509_crt crt;
}palX509Ctx_t;
#endif

typedef struct palMD{
     mbedtls_md_context_t md;
}palMD_t;

#define CRYPTO_PLAT_SUCCESS 0
#define CRYPTO_PLAT_GENERIC_ERROR (-1)

palStatus_t pal_plat_initCrypto()
{
    return PAL_SUCCESS;
}

palStatus_t pal_plat_cleanupCrypto()
{
    return PAL_SUCCESS;
}

palStatus_t pal_plat_initAes(palAesHandle_t *aes)
{
    palStatus_t status = PAL_SUCCESS;
    palAes_t* localCtx = NULL;

    localCtx = (palAes_t*)malloc(sizeof(palAes_t));
    if (NULL == localCtx)
    {
        status = PAL_ERR_CREATION_FAILED;
    }
    else
    {
        mbedtls_aes_init(&localCtx->platCtx);
        localCtx->nc_off = 0;
        memset(localCtx->stream_block, 0, 16);

        *aes = (palAesHandle_t)localCtx;
    }
    return status;
}

palStatus_t pal_plat_freeAes(palAesHandle_t *aes)
{
    palStatus_t status = PAL_SUCCESS;
    palAes_t* localCtx = NULL;
    
    localCtx = (palAes_t*)*aes;
    
    mbedtls_aes_free(&localCtx->platCtx);
    free(localCtx);
    *aes = NULLPTR;
    return status;
}

palStatus_t pal_plat_setAesKey(palAesHandle_t aes, const unsigned char* key, uint32_t keybits, palAesKeyType_t keyTarget)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palAes_t* localCtx = (palAes_t*)aes;

    if (PAL_KEY_TARGET_ENCRYPTION == keyTarget)
    {
        platStatus = mbedtls_aes_setkey_enc(&localCtx->platCtx, key, keybits);
    }
    else
    {
        platStatus = mbedtls_aes_setkey_dec(&localCtx->platCtx, key, keybits);
    }

    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_AES_INVALID_KEY_LENGTH;
    }

    return status;    
}

palStatus_t pal_plat_aesCTR(palAesHandle_t aes, const unsigned char* input, unsigned char* output, size_t inLen, unsigned char iv[16], bool zeroOffset)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palAes_t* localCtx = (palAes_t*)aes;

    if (true == zeroOffset)
    {
        localCtx->nc_off = 0;
        memset(localCtx->stream_block, 0, 16);
    }

    platStatus = mbedtls_aes_crypt_ctr(&localCtx->platCtx, inLen, &localCtx->nc_off, iv, localCtx->stream_block, input, output);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        PAL_LOG(ERR, "Crypto aes ctr status %" PRId32 "", platStatus);
        status = PAL_ERR_GENERIC_FAILURE;
    }
    return status;
}

palStatus_t pal_plat_aesECB(palAesHandle_t aes, const unsigned char input[PAL_CRYPT_BLOCK_SIZE], unsigned char output[PAL_CRYPT_BLOCK_SIZE], palAesMode_t mode)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palAes_t* localCtx = (palAes_t*)aes;

    platStatus = mbedtls_aes_crypt_ecb(&localCtx->platCtx, (PAL_AES_ENCRYPT == mode ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT), input, output);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        PAL_LOG(ERR, "Crypto aes ecb status  %" PRId32 "", platStatus);
        status = PAL_ERR_GENERIC_FAILURE;
    }
    return status;
}

palStatus_t pal_plat_sha256(const unsigned char* input, size_t inLen, unsigned char* output)
{    
    mbedtls_sha256(input, inLen, output, 0);
     
    return PAL_SUCCESS;
}
#if (PAL_ENABLE_X509 == 1)
palStatus_t pal_plat_x509Initiate(palX509Handle_t* x509)
{
    palStatus_t status = PAL_SUCCESS;
    palX509Ctx_t* localCtx = NULL;

    localCtx = (palX509Ctx_t*)malloc(sizeof(palX509Ctx_t));
    if (NULL == localCtx)
    {
        status = PAL_ERR_CREATION_FAILED;
    }
    else
    {
        mbedtls_x509_crt_init(&localCtx->crt);
        *x509 = (uintptr_t)localCtx;
    }

    return status;
}


palStatus_t pal_plat_x509CertParse(palX509Handle_t x509, const unsigned char* input, size_t inLen)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palX509Ctx_t* localCtx = (palX509Ctx_t*)x509;

     platStatus = mbedtls_x509_crt_parse_der(&localCtx->crt, input, inLen);
    if (platStatus < CRYPTO_PLAT_SUCCESS)
    {
		if (MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE == platStatus)
		{
			status = PAL_ERR_NOT_SUPPORTED_CURVE;
		}
		
        else if (-(MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG) == ((-platStatus) & 0xFF80))
        {
            status = PAL_ERR_INVALID_MD_TYPE;
        }
        
        else
        {
            status = PAL_ERR_CERT_PARSING_FAILED;
        }
    }

    return status;
}

PAL_PRIVATE palStatus_t pal_plat_x509CertGetID(palX509Ctx_t* x509Cert, uint8_t *id, size_t outLenBytes, size_t* actualOutLenBytes)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    platStatus = mbedtls_ecp_point_write_binary( &((mbedtls_ecp_keypair *)((x509Cert->crt).pk).pk_ctx)->grp, &((mbedtls_ecp_keypair *)((x509Cert->crt).pk).pk_ctx)->Q,
         MBEDTLS_ECP_PF_COMPRESSED, actualOutLenBytes, id, outLenBytes);
    if (platStatus != CRYPTO_PLAT_SUCCESS)
    {
        status = PAL_ERR_FAILED_TO_WRITE_PUBLIC_KEY;
    }
    return status;
}

PAL_PRIVATE palStatus_t pal_plat_X509GetField(palX509Ctx_t* x509Ctx, const char* fieldName, void* output, size_t outLenBytes, size_t* actualOutLenBytes)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    const char *shortName = NULL;
    size_t fieldNameLength = 0;
    mbedtls_x509_name *x509Name = &x509Ctx->crt.subject;

    fieldNameLength = strlen(fieldName);
    while( x509Name ) 
    {
        platStatus = mbedtls_oid_get_attr_short_name(&x509Name->oid, &shortName);
        if (CRYPTO_PLAT_SUCCESS != platStatus)
        {
            status = PAL_ERR_INVALID_IOD; 
            break;  
        }
        if (strncmp(shortName, fieldName, fieldNameLength) == 0)
        {
            if (outLenBytes < (x509Name->val.len + 1))
            {
                status = PAL_ERR_BUFFER_TOO_SMALL;
                *actualOutLenBytes = x509Name->val.len + 1;
                break;
            }
            memcpy(output, x509Name->val.p, x509Name->val.len);
            ((char*)output)[x509Name->val.len] = '\0';
            *actualOutLenBytes = x509Name->val.len + 1;
            break;
        }
        x509Name = x509Name->next;
    }
    return status;
}

PAL_PRIVATE bool pal_isLeapYear(uint8_t year)
{
    bool result = false;
    if (year % 4 != 0)
    {
        result = false;
    }
    else if ((year % 100) != 0)
    {
        result = true;
    } 
    else
    {
        result = ((year % 400) == 0);
    }
    return result;
}

PAL_PRIVATE palStatus_t pal_timegm( struct tm *tm, uint64_t* outTime) 
{
    uint64_t epoc = 0;
    uint8_t palMonthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    if (NULL == outTime || NULL == tm || tm->tm_year < 1970 || tm->tm_mon > 12)
    {
        return PAL_ERR_INVALID_ARGUMENT;
    }

    for (uint16_t y = 1970; y < tm->tm_year; ++y)
    {
        if (pal_isLeapYear(y))
        {
            epoc += 366 * PAL_SECONDS_PER_DAY;
        }
        else
        {
            epoc += 365 * PAL_SECONDS_PER_DAY;
        }      
    }
    
    for (uint8_t m = 1; m < tm->tm_mon; ++m) 
    {
        epoc += palMonthDays[m - 1] * PAL_SECONDS_PER_DAY;
        if (m == PAL_FEB_MONTH && pal_isLeapYear(tm->tm_year))
        {
            epoc += PAL_SECONDS_PER_DAY;
        }
    }

    epoc += (tm->tm_mday - 1) * PAL_SECONDS_PER_DAY;
    epoc += tm->tm_hour * PAL_SECONDS_PER_HOUR;
    epoc += tm->tm_min * PAL_SECONDS_PER_MIN;
    epoc += tm->tm_sec;
    *outTime = epoc;
    return PAL_SUCCESS;
}


palStatus_t pal_plat_x509CertGetAttribute(palX509Handle_t x509Cert, palX509Attr_t attr, void* output, size_t outLenBytes, size_t* actualOutLenBytes)
{
    palStatus_t status = PAL_SUCCESS;
    palX509Ctx_t* localCtx = (palX509Ctx_t*)x509Cert;
    *actualOutLenBytes = 0;

    switch(attr)
    {
        case PAL_X509_ISSUER_ATTR:
            if (localCtx->crt.issuer_raw.len <= outLenBytes)
            {
                memcpy(output, localCtx->crt.issuer_raw.p, localCtx->crt.issuer_raw.len);
            }
            else
            {
                status = PAL_ERR_BUFFER_TOO_SMALL;
            }
            *actualOutLenBytes = localCtx->crt.issuer_raw.len;
            break;

        case PAL_X509_SUBJECT_ATTR:
            if (localCtx->crt.subject_raw.len <= outLenBytes)
            {
                memcpy(output, localCtx->crt.subject_raw.p, localCtx->crt.subject_raw.len);
            }
            else
            {
                status = PAL_ERR_BUFFER_TOO_SMALL;
            }
            *actualOutLenBytes = localCtx->crt.subject_raw.len;
            break;

        case PAL_X509_VALID_FROM:
            if ( PAL_CRYPTO_CERT_DATE_LENGTH > outLenBytes)
            {
                status = PAL_ERR_BUFFER_TOO_SMALL;
            }
            else
            {
                struct tm time;
                uint64_t timeOfDay;
                time.tm_year = localCtx->crt.valid_from.year;
                time.tm_mon = localCtx->crt.valid_from.mon;
                time.tm_mday = localCtx->crt.valid_from.day;
                time.tm_hour = localCtx->crt.valid_from.hour;
                time.tm_min = localCtx->crt.valid_from.min;
                time.tm_sec = localCtx->crt.valid_from.sec;
                time.tm_isdst = -1;                                   //unknown DST 
                status = pal_timegm(&time, &timeOfDay);
                if (PAL_SUCCESS != status)
                {
                    status = PAL_ERR_TIME_TRANSLATE;
                }
                else
                {
                    memcpy(output, &timeOfDay, PAL_CRYPTO_CERT_DATE_LENGTH);
                }
            }
            *actualOutLenBytes = PAL_CRYPTO_CERT_DATE_LENGTH;
            break;
	    
        case PAL_X509_VALID_TO:
            if ( PAL_CRYPTO_CERT_DATE_LENGTH > outLenBytes)
            {
                status = PAL_ERR_BUFFER_TOO_SMALL;
            }
            else
            {
                struct tm time;
                uint64_t timeOfDay;
                time.tm_year = localCtx->crt.valid_to.year;
                time.tm_mon = localCtx->crt.valid_to.mon;
                time.tm_mday = localCtx->crt.valid_to.day;
                time.tm_hour = localCtx->crt.valid_to.hour;
                time.tm_min = localCtx->crt.valid_to.min;
                time.tm_sec = localCtx->crt.valid_to.sec;
                time.tm_isdst = -1;                                 //unknown DST
                status = pal_timegm(&time, &timeOfDay);
                if (PAL_SUCCESS != status)
                {
                    status = PAL_ERR_TIME_TRANSLATE;
                }
                else
                {
                    memcpy(output, &timeOfDay, PAL_CRYPTO_CERT_DATE_LENGTH);
                }
            }
            *actualOutLenBytes = PAL_CRYPTO_CERT_DATE_LENGTH;
            break;
        
        case PAL_X509_CN_ATTR:
            status = pal_plat_X509GetField(localCtx, "CN", output, outLenBytes, actualOutLenBytes);
            break; 

        case PAL_X509_OU_ATTR:
            status = pal_plat_X509GetField(localCtx, "OU", output, outLenBytes, actualOutLenBytes);
            break;
        
        case PAL_X509_CERT_ID_ATTR:
            if (PAL_CERT_ID_SIZE > outLenBytes)
            {
                status = PAL_ERR_BUFFER_TOO_SMALL;
                *actualOutLenBytes = PAL_CERT_ID_SIZE;
            }
            else
            {
                status = pal_plat_x509CertGetID(localCtx, output, outLenBytes, actualOutLenBytes);
            }
            break;

        default:
           status = PAL_ERR_INVALID_X509_ATTR;
    }
    return status;
}

PAL_PRIVATE const mbedtls_x509_crt_profile s_PALProfile =
{
    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 ) | MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA224 ),
    MBEDTLS_X509_ID_FLAG( MBEDTLS_PK_ECKEY ) | MBEDTLS_X509_ID_FLAG( MBEDTLS_PK_ECDSA ),
    MBEDTLS_X509_ID_FLAG( MBEDTLS_ECP_DP_SECP256R1 ),
    0x7FFFFFFF // RSA not allowed
};

palStatus_t pal_plat_x509CertVerifyExtended(palX509Handle_t x509Cert, palX509Handle_t x509CertChain, int32_t* verifyResult)
{
    palStatus_t status = PAL_SUCCESS;
    palX509Ctx_t* localCert = (palX509Ctx_t*)x509Cert;
    palX509Ctx_t* localCAChain = (palX509Ctx_t*)x509CertChain;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    uint32_t flags = 0;
    *verifyResult = 0;

    if (NULL == localCAChain)
    {
        platStatus = mbedtls_x509_crt_verify_with_profile(&localCert->crt, NULL, NULL, &s_PALProfile, NULL, &flags, NULL, NULL);
    }
    else
    {
        platStatus = mbedtls_x509_crt_verify_with_profile(&localCert->crt, &localCAChain->crt, NULL, &s_PALProfile, NULL, &flags, NULL, NULL);
    }

    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_X509_CERT_VERIFY_FAILED;
        //! please DO NOT change errors order
        if (MBEDTLS_X509_BADCERT_NOT_TRUSTED & flags)
        {
            *verifyResult |= PAL_ERR_X509_BADCERT_NOT_TRUSTED;
            status = PAL_ERR_X509_BADCERT_NOT_TRUSTED;
        }
        if (MBEDTLS_X509_BADCERT_BAD_KEY & flags)
        {
            *verifyResult |= PAL_ERR_X509_BADCERT_BAD_KEY;
            status = PAL_ERR_X509_BADCERT_BAD_KEY;
        }
        if (MBEDTLS_X509_BADCERT_BAD_PK & flags)
        {
            *verifyResult |= PAL_ERR_X509_BADCERT_BAD_PK;
            status = PAL_ERR_X509_BADCERT_BAD_PK;
        }
        if (MBEDTLS_X509_BADCERT_BAD_MD & flags)
        {
            *verifyResult |= PAL_ERR_X509_BADCERT_BAD_MD;
            status = PAL_ERR_X509_BADCERT_BAD_MD;
        }
        if (MBEDTLS_X509_BADCERT_FUTURE & flags)
        {
            *verifyResult |= PAL_ERR_X509_BADCERT_FUTURE;
            status = PAL_ERR_X509_BADCERT_FUTURE;
        }
        if (MBEDTLS_X509_BADCERT_EXPIRED & flags)
        {
            *verifyResult |= PAL_ERR_X509_BADCERT_EXPIRED;
            status = PAL_ERR_X509_BADCERT_EXPIRED;
        }
    }

    return status;
}

palStatus_t pal_plat_x509Free(palX509Handle_t* x509)
{
    palStatus_t status = PAL_SUCCESS;
    palX509Ctx_t* localCtx = NULL;

    localCtx = (palX509Ctx_t*)*x509;
    mbedtls_x509_crt_free(&localCtx->crt);
    free(localCtx);
    *x509 = NULLPTR;
    return status;
}

#endif

palStatus_t pal_plat_mdInit(palMDHandle_t* md, palMDType_t mdType)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palMD_t* localCtx = NULL;
    const mbedtls_md_info_t* mdInfo = NULL;
    mbedtls_md_type_t mdAlg = MBEDTLS_MD_NONE;

    localCtx = (palMD_t*)malloc(sizeof(palMD_t));
    if (NULL == localCtx)
    {
        status = PAL_ERR_CREATION_FAILED;
        goto finish;
    }

    
    mbedtls_md_init(&localCtx->md);
    
    switch (mdType)
    {
        case PAL_SHA256:
            mdAlg = MBEDTLS_MD_SHA256;
            break;
        default:
            status = PAL_ERR_INVALID_MD_TYPE;
            goto finish;
    }

    mdInfo = mbedtls_md_info_from_type(mdAlg);
    if (NULL == mdInfo)
    {
        status = PAL_ERR_INVALID_MD_TYPE;
        goto finish;
    }

    platStatus = mbedtls_md_setup(&localCtx->md, mdInfo, 0); // 0 because we don't want to use HMAC in mbedTLS to save memory
    switch(platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            break;
        case MBEDTLS_ERR_MD_BAD_INPUT_DATA:
            {
                status = PAL_ERR_MD_BAD_INPUT_DATA;
                goto finish;
            }
        case MBEDTLS_ERR_MD_ALLOC_FAILED:
            {
                status = PAL_ERR_CREATION_FAILED;
                goto finish;
            }
        default: 
            {
                PAL_LOG(ERR, "Crypto md start setup  %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
                goto finish;
            }
    }
    
    platStatus = mbedtls_md_starts(&localCtx->md);
    switch(platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            break;
        case MBEDTLS_ERR_MD_BAD_INPUT_DATA:
            {
                status = PAL_ERR_MD_BAD_INPUT_DATA;
                goto finish;
            }
        default: 
            {
                PAL_LOG(ERR, "Crypto md start status  %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
                goto finish;
            }
    }

    *md = (uintptr_t)localCtx;
finish:
    if (PAL_SUCCESS != status && NULL != localCtx)
    {
        free(localCtx);
    }
    return status;
}

palStatus_t pal_plat_mdUpdate(palMDHandle_t md, const unsigned char* input, size_t inLen)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palMD_t* localCtx = (palMD_t*)md;

    platStatus =  mbedtls_md_update(&localCtx->md, input, inLen);
    switch(platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            break;
        case MBEDTLS_ERR_MD_BAD_INPUT_DATA:
            status = PAL_ERR_MD_BAD_INPUT_DATA;
            break;
        default: 
            {
                PAL_LOG(ERR, "Crypto md update status %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
            }
    }
    return status;
}

palStatus_t pal_plat_mdGetOutputSize(palMDHandle_t md, size_t* bufferSize)
{
    palStatus_t status = PAL_SUCCESS;
    palMD_t* localCtx = (palMD_t*)md;

    if (NULL != localCtx->md.md_info)
    {
        *bufferSize = (size_t)mbedtls_md_get_size(localCtx->md.md_info);
    }
    else
    {
        PAL_LOG(ERR, "Crypto md get size error");
        status = PAL_ERR_GENERIC_FAILURE;
    }
    
    return status;
}

palStatus_t pal_plat_mdFinal(palMDHandle_t md, unsigned char* output)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palMD_t* localCtx = (palMD_t*)md;

    platStatus =  mbedtls_md_finish(&localCtx->md, output);
    switch(platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            break;
        case MBEDTLS_ERR_MD_BAD_INPUT_DATA:
            status = PAL_ERR_MD_BAD_INPUT_DATA;
            break;
        default: 
            {
                PAL_LOG(ERR, "Crypto md finish status %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
            }
    } 
    return status;
}

palStatus_t pal_plat_mdFree(palMDHandle_t* md)
{
    palStatus_t status = PAL_SUCCESS;
    palMD_t* localCtx = NULL;

    localCtx = (palMD_t*)*md;
    mbedtls_md_free(&localCtx->md);
    free(localCtx);
    *md = NULLPTR;
    return status;
}
#if (PAL_ENABLE_X509 == 1)
palStatus_t pal_plat_verifySignature(palX509Handle_t x509, palMDType_t mdType, const unsigned char *hash, size_t hashLen, const unsigned char *sig, size_t sigLen)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    mbedtls_md_type_t mdAlg = MBEDTLS_MD_NONE;
    palX509Ctx_t* localCtx = (palX509Ctx_t*)x509;

    switch (mdType)
    {
        case PAL_SHA256:
            mdAlg = MBEDTLS_MD_SHA256;
            break;
        default:
            status = PAL_ERR_INVALID_MD_TYPE;
            goto finish;
    }

    platStatus = mbedtls_pk_verify(&localCtx->crt.pk, mdAlg, hash, hashLen, sig, sigLen);
    if (platStatus < CRYPTO_PLAT_SUCCESS)
    {
        status = PAL_ERR_PK_SIG_VERIFY_FAILED;
    }
finish:
    return status;
}
#endif 

palStatus_t pal_plat_ASN1GetTag(unsigned char **position, const unsigned char *end, size_t *len, uint8_t tag )
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    int platTag = 0;

    switch (tag & PAL_ASN1_CLASS_BITS) 
    {
        case 0x00:
            //MBEDTLS_ASN1_PRIMITIVE
            break;
        case PAL_ASN1_CONTEXT_SPECIFIC:
            platTag |= MBEDTLS_ASN1_CONTEXT_SPECIFIC;
            break;
        default:
            status = PAL_ERR_NOT_SUPPORTED_ASN_TAG;
            goto finish;
    }

    if (tag & PAL_ASN1_CONSTRUCTED)
    {
        platTag |= MBEDTLS_ASN1_CONSTRUCTED;
    }


    switch(tag & PAL_ASN1_TAG_BITS)
    {
    case PAL_ASN1_BOOLEAN:
        platTag |= MBEDTLS_ASN1_BOOLEAN;
        break;
    case PAL_ASN1_INTEGER:
        platTag |= MBEDTLS_ASN1_INTEGER;
            break;
    case PAL_ASN1_BIT_STRING:
        platTag |= MBEDTLS_ASN1_BIT_STRING;
            break;
    case PAL_ASN1_OCTET_STRING:
        platTag |= MBEDTLS_ASN1_OCTET_STRING;
                break;
    case PAL_ASN1_NULL:
        platTag |= MBEDTLS_ASN1_NULL;
                break;
    case PAL_ASN1_OID:
        platTag |= MBEDTLS_ASN1_OID;
                break;
    case PAL_ASN1_UTF8_STRING:
        platTag |= MBEDTLS_ASN1_UTF8_STRING;
                break;
    case PAL_ASN1_SEQUENCE:
        platTag |= MBEDTLS_ASN1_SEQUENCE;
                break;
    case PAL_ASN1_SET:
        platTag |= MBEDTLS_ASN1_SET;
                break;
    case PAL_ASN1_PRINTABLE_STRING:
        platTag |= MBEDTLS_ASN1_PRINTABLE_STRING;
                break;
    case PAL_ASN1_T61_STRING:
        platTag |= MBEDTLS_ASN1_T61_STRING;
                break;
    case PAL_ASN1_IA5_STRING:
        platTag |= MBEDTLS_ASN1_IA5_STRING;
                break;
    case PAL_ASN1_UTC_TIME:
        platTag |= MBEDTLS_ASN1_UTC_TIME;
                break;
    case PAL_ASN1_GENERALIZED_TIME:
        platTag |= MBEDTLS_ASN1_GENERALIZED_TIME;
                break;
    case PAL_ASN1_UNIVERSAL_STRING:
        platTag |= MBEDTLS_ASN1_UNIVERSAL_STRING;
                break;
    case PAL_ASN1_BMP_STRING:
        platTag |= MBEDTLS_ASN1_BMP_STRING;
                break;
    default:
        status = PAL_ERR_NOT_SUPPORTED_ASN_TAG;
        goto finish;
    }

    platStatus =  mbedtls_asn1_get_tag(position, end, len, platTag);
    if (platStatus < CRYPTO_PLAT_SUCCESS)
    {
        status = PAL_ERR_ASN1_UNEXPECTED_TAG;
    }
finish:
    return status;
}

palStatus_t pal_plat_CCMInit(palCCMHandle_t* ctx)
{
    palStatus_t status = PAL_SUCCESS;
    palCCM_t* ccmCtx = NULL;

    ccmCtx = (palCCM_t*)malloc(sizeof(palCCM_t));
    if (NULL == ccmCtx)
    {
        status = PAL_ERR_NO_MEMORY;
    }
    else
    {
        mbedtls_ccm_init(ccmCtx);
        *ctx = (palCCMHandle_t)ccmCtx;
    }

    return status;
}

palStatus_t pal_plat_CCMFree(palCCMHandle_t* ctx)
{
    palStatus_t status = PAL_SUCCESS;
    palCCM_t* ccmCtx = (palCCM_t*)*ctx;

    mbedtls_ccm_free(ccmCtx);
    free(ccmCtx);
    *ctx = NULLPTR;
    return status;
}

palStatus_t pal_plat_CCMSetKey(palCCMHandle_t ctx, palCipherID_t id, const unsigned char *key, unsigned int keybits)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palCCM_t* ccmCtx = (palCCM_t*)ctx;
    mbedtls_cipher_id_t mbedtls_cipher_id;

    switch (id) 
    {
        case PAL_CIPHER_ID_AES:
            mbedtls_cipher_id = MBEDTLS_CIPHER_ID_AES;
            break;
        default:
            return PAL_ERR_INVALID_ARGUMENT;
    }

    platStatus = mbedtls_ccm_setkey(ccmCtx, mbedtls_cipher_id, key, keybits);

    switch(platStatus)
    {
    case CRYPTO_PLAT_SUCCESS:
        status = PAL_SUCCESS;
        break;
    default:
        {
            PAL_LOG(ERR, "Crypto ccm setkey status %" PRId32 "", platStatus);
            status = PAL_ERR_GENERIC_FAILURE;
        }
    }
    return status;
}

palStatus_t pal_plat_CCMDecrypt(palCCMHandle_t ctx, unsigned char* input, size_t inLen, unsigned char* iv, size_t ivLen, unsigned char* add, size_t addLen, unsigned char* tag, size_t tagLen, unsigned char* output)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palCCM_t* ccmCtx = (palCCM_t*)ctx;

    platStatus = mbedtls_ccm_auth_decrypt(ccmCtx, inLen, iv, ivLen, add, addLen, input, output, tag, tagLen);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        switch(platStatus)
        {
        default:
            {
                PAL_LOG(ERR, "Crypto ccm decrypt status %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
            }
        }
    }
    return status;
}

palStatus_t pal_plat_CCMEncrypt(palCCMHandle_t ctx, unsigned char* input, size_t inLen, unsigned char* iv, size_t ivLen, unsigned char* add, size_t addLen, unsigned char* output, unsigned char* tag, size_t tagLen)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palCCM_t* ccmCtx = (palCCM_t*)ctx;

    platStatus = mbedtls_ccm_encrypt_and_tag(ccmCtx, inLen, iv, ivLen, add, addLen, input, output, tag, tagLen);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        switch(platStatus)
        {
        default:
            {
                PAL_LOG(ERR, "Crypto ccm encrypt status %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
            }
        }
    }
    return status;
}

palStatus_t pal_plat_CtrDRBGInit(palCtrDrbgCtxHandle_t* ctx)
{
    palStatus_t status = PAL_SUCCESS;
    palCtrDrbgCtx_t* palCtrDrbgCtx = NULL;

    palCtrDrbgCtx = (palCtrDrbgCtx_t*)malloc(sizeof(palCtrDrbgCtx_t));
    if (NULL == palCtrDrbgCtx)
    {
        status = PAL_ERR_NO_MEMORY;
    }
    else
    {
        mbedtls_ctr_drbg_init(&palCtrDrbgCtx->ctrDrbgCtx);
        mbedtls_entropy_init(&palCtrDrbgCtx->entropy);
        *ctx = (palCtrDrbgCtxHandle_t)palCtrDrbgCtx;
    }

    return status;
}

palStatus_t pal_plat_CtrDRBGFree(palCtrDrbgCtxHandle_t* ctx)
{
    palStatus_t status = PAL_SUCCESS;
    palCtrDrbgCtx_t* palCtrDrbgCtx = (palCtrDrbgCtx_t*)*ctx;

    mbedtls_ctr_drbg_free(&palCtrDrbgCtx->ctrDrbgCtx);
    mbedtls_entropy_free(&palCtrDrbgCtx->entropy);
    free(palCtrDrbgCtx);
    *ctx = NULLPTR;

    return status;
}

palStatus_t pal_plat_CtrDRBGSeed(palCtrDrbgCtxHandle_t ctx, const void* seed, size_t len)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palCtrDrbgCtx_t* palCtrDrbgCtx = (palCtrDrbgCtx_t*)ctx;

    platStatus = mbedtls_ctr_drbg_seed_entropy_len(&palCtrDrbgCtx->ctrDrbgCtx, pal_plat_entropySourceDRBG, &palCtrDrbgCtx->entropy, seed, len, 0);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        switch(platStatus)
        {
            case MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED:
                status = PAL_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED;
                break;
            default:
                {
                    PAL_LOG(ERR, "Crypto ctrdrbg seed status %" PRId32 "", platStatus);
                    status = PAL_ERR_GENERIC_FAILURE;
                }
        }
    }
    return status;
}

palStatus_t pal_plat_CtrDRBGGenerate(palCtrDrbgCtxHandle_t ctx, unsigned char* out, size_t len)
{
    palStatus_t status = pal_plat_CtrDRBGGenerateWithAdditional(ctx, out, len, NULL, 0);
    return status;
}

palStatus_t pal_plat_CtrDRBGGenerateWithAdditional(palCtrDrbgCtxHandle_t ctx, unsigned char* out, size_t len, unsigned char* additional, size_t additionalLen)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palCtrDrbgCtx_t* palCtrDrbgCtx = (palCtrDrbgCtx_t*)ctx;
    
    platStatus = mbedtls_ctr_drbg_random_with_add(&palCtrDrbgCtx->ctrDrbgCtx, out, len, additional, additionalLen);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        switch (platStatus)
        {
            case MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED:
                status = PAL_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED;
                break;
            case MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG:
                status = PAL_ERR_CTR_DRBG_REQUEST_TOO_BIG;
                break;
            default:
            {
                PAL_LOG(ERR, "Crypto ctrdrbg generate status %" PRId32 "", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
            }
        }
    }
    return status;
}

#if PAL_CMAC_SUPPORT
palStatus_t pal_plat_cipherCMAC(const unsigned char *key, size_t keyLenInBits, const unsigned char *input, size_t inputLenInBytes, unsigned char *output)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    const mbedtls_cipher_info_t *cipherInfo;

    cipherInfo = mbedtls_cipher_info_from_values(MBEDTLS_CIPHER_ID_AES, keyLenInBits, MBEDTLS_MODE_ECB);
    if (NULL == cipherInfo)
    {
        PAL_LOG(ERR, "Crypto cipher cmac error");
        status = PAL_ERR_CMAC_GENERIC_FAILURE;
        goto finish;
    }

    platStatus = mbedtls_cipher_cmac( cipherInfo, key, keyLenInBits, input, inputLenInBytes, output);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        PAL_LOG(ERR, "Crypto cipher cmac status %" PRId32 "", platStatus);
        status = PAL_ERR_CMAC_GENERIC_FAILURE;
    }
finish:
    return status;
}

palStatus_t pal_plat_CMACStart(palCMACHandle_t *ctx, const unsigned char *key, size_t keyLenBits, palCipherID_t cipherID)
{
    palStatus_t status = PAL_SUCCESS;
    palCipherCtx_t* localCipher = NULL;
    const mbedtls_cipher_info_t* cipherInfo = NULL;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    mbedtls_cipher_type_t platType = MBEDTLS_CIPHER_NONE;

    switch(cipherID)
    {
        case PAL_CIPHER_ID_AES:
            platType = MBEDTLS_CIPHER_AES_128_ECB;
            break;
        default:
            status = PAL_ERR_INVALID_CIPHER_ID;
            goto finish;
    }

    cipherInfo = mbedtls_cipher_info_from_type(platType);
    if (NULL == cipherInfo)
    {
        PAL_LOG(ERR, "Crypto cmac cipher info error");
        status = PAL_ERR_CMAC_GENERIC_FAILURE;
        goto finish;
    }

    localCipher = (palCipherCtx_t*)malloc(sizeof(palCipherCtx_t));
    if (NULL == localCipher)
    {
        status = PAL_ERR_NO_MEMORY;
        goto finish;
    }

    mbedtls_cipher_init(localCipher);
    platStatus = mbedtls_cipher_setup(localCipher, cipherInfo);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        PAL_LOG(ERR, "Crypto cmac cipher setup status %" PRId32 ".", platStatus);
        status = PAL_ERR_CMAC_GENERIC_FAILURE;
        goto finish;
    }

    platStatus = mbedtls_cipher_cmac_starts(localCipher, key, keyLenBits);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_CMAC_START_FAILED;
        goto finish;
    }

    *ctx = (palCMACHandle_t)localCipher;
finish:
    if (PAL_SUCCESS != status && NULL != localCipher)
    {
        free(localCipher);
    }
    return status;
}

palStatus_t pal_plat_CMACUpdate(palCMACHandle_t ctx, const unsigned char *input, size_t inLen)
{
    palStatus_t status = PAL_SUCCESS;
    palCipherCtx_t* localCipher = (palCipherCtx_t*)ctx;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    platStatus = mbedtls_cipher_cmac_update(localCipher, input, inLen);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_CMAC_UPDATE_FAILED;
    }

    return status;
}

palStatus_t pal_plat_CMACFinish(palCMACHandle_t *ctx, unsigned char *output, size_t* outLen)
{
    palStatus_t status = PAL_SUCCESS;
    palCipherCtx_t* localCipher = (palCipherCtx_t*)*ctx;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    platStatus = mbedtls_cipher_cmac_finish(localCipher, output);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_CMAC_FINISH_FAILED;
    }
    else
    {
        *outLen = localCipher->cipher_info->block_size;
    }

    

    mbedtls_cipher_free(localCipher);
    free(localCipher);
    *ctx = NULLPTR;
    return status;
}
#endif //PAL_CMAC_SUPPORT
palStatus_t pal_plat_mdHmacSha256(const unsigned char *key, size_t keyLenInBytes, const unsigned char *input, size_t inputLenInBytes, unsigned char *output, size_t* outputLenInBytes)
{
    const mbedtls_md_info_t *md_info = NULL;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palStatus_t status = PAL_SUCCESS;

    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    if (NULL == md_info)
    {
        PAL_LOG(ERR, "Crypto hmac sha256 md info error");
        status = PAL_ERR_HMAC_GENERIC_FAILURE;
    }

    if (PAL_SUCCESS == status)
    {
        platStatus = mbedtls_md_hmac(md_info, key, keyLenInBytes, input, inputLenInBytes, output);
        if (platStatus != CRYPTO_PLAT_SUCCESS)
        {
            if (platStatus == MBEDTLS_ERR_MD_BAD_INPUT_DATA)
            {
                status = PAL_ERR_MD_BAD_INPUT_DATA;
            }
            else
            {
                PAL_LOG(ERR, "Crypto hmac status %" PRId32 "", platStatus);
                status = PAL_ERR_HMAC_GENERIC_FAILURE;
            }
        }
    }

    if ((NULL != outputLenInBytes) && (PAL_SUCCESS == status))
    {
        *outputLenInBytes = (size_t)mbedtls_md_get_size(md_info);
    }

    return status;
}

//! Check EC private key function. 
PAL_PRIVATE palStatus_t pal_plat_ECCheckPrivateKey(palECGroup_t* ecpGroup, palECKeyHandle_t key, bool *verified)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* privateKey = (palECKey_t*)key;
    mbedtls_mpi* prvMP = NULL;
    if(NULL == (mbedtls_ecp_keypair*)privateKey->pk_ctx)
    {
        return PAL_ERR_INVALID_ARGUMENT;
    }

    prvMP = &((mbedtls_ecp_keypair*)privateKey->pk_ctx)->d;

    platStatus =  mbedtls_ecp_check_privkey(ecpGroup, prvMP);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_PRIVATE_KEY_VARIFICATION_FAILED;
    }
    else
    {
        *verified = true;
    }
    
    return status;
}

//! Check EC public key function.
PAL_PRIVATE palStatus_t pal_plat_ECCheckPublicKey(palECGroup_t* ecpGroup, palECKeyHandle_t key, bool *verified)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* publicKey = (palECKey_t*)key;
    mbedtls_ecp_point* pubPoint = NULL;
    if(NULL == (mbedtls_ecp_keypair*)publicKey->pk_ctx)
    {
        return PAL_ERR_INVALID_ARGUMENT;
    }

	pubPoint = &((mbedtls_ecp_keypair*)publicKey->pk_ctx)->Q;

    platStatus =  mbedtls_ecp_check_pubkey(ecpGroup, pubPoint);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_PUBLIC_KEY_VARIFICATION_FAILED;
    }
    else
    {
        *verified = true;
    }
    
    return status;
}

palStatus_t pal_plat_ECCheckKey(palCurveHandle_t grp, palECKeyHandle_t key, uint32_t type, bool *verified)
{
    palStatus_t status = PAL_SUCCESS;
    palECGroup_t* ecpGroup = (palECGroup_t*)grp;

    *verified = false;

    if ((PAL_CHECK_PRIVATE_KEY & type) != 0)
    {
        status = pal_plat_ECCheckPrivateKey(ecpGroup, key, verified);
    }

    if ((PAL_SUCCESS == status) && ((PAL_CHECK_PUBLIC_KEY & type) != 0))
    {
        status = pal_plat_ECCheckPublicKey(ecpGroup, key, verified);
    }

    return status;
}


palStatus_t pal_plat_ECKeyNew(palECKeyHandle_t* key)
{
    palStatus_t status = PAL_SUCCESS;
    palECKey_t* localECKey = NULL;

    localECKey = (palECKey_t*)malloc(sizeof(palECKey_t));
    if (NULL == localECKey)
    {
        status = PAL_ERR_NO_MEMORY;
    }
    else
    {
        mbedtls_pk_init(localECKey);
        *key = (palECKeyHandle_t)localECKey;
    }
    
    return status;
}

palStatus_t pal_plat_ECKeyFree(palECKeyHandle_t* key)
{
    palECKey_t* localECKey = NULL;

    localECKey = (palECKey_t*)*key;
    mbedtls_pk_free(localECKey);
    free(localECKey);
    *key = NULLPTR;
    return PAL_SUCCESS;
}

//! Check if the given data is a valid PEM format or not by checking the
//! the header and the footer of the data.
PAL_PRIVATE bool pal_plat_isPEM(const unsigned char* key, size_t keyLen)
{
    bool result = false;
    const unsigned char *s1 = NULL;
    const unsigned char *s2 = NULL;

    s1 = (unsigned char *) strstr( (const char *) key, "-----BEGIN ");
    if (NULL != s1)
    {
        result = true;
    }
    else
    {
        s2 = (unsigned char *) strstr( (const char *) key, "-----END " );
        if (NULL != s2)
        {
            result = true;
        }
    }

    return result;
}

palStatus_t pal_plat_parseECPrivateKeyFromDER(const unsigned char* prvDERKey, size_t keyLen, palECKeyHandle_t key)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)key;

    if(pal_plat_isPEM(prvDERKey, keyLen))
    {
    	return PAL_ERR_INVALID_ARGUMENT;
    }

    platStatus = mbedtls_pk_parse_key(localECKey, prvDERKey, keyLen, NULL, 0);
    switch(platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            break;
        case MBEDTLS_ERR_PK_UNKNOWN_PK_ALG:
            status = PAL_ERR_PK_UNKNOWN_PK_ALG;
            break;
        case MBEDTLS_ERR_PK_KEY_INVALID_VERSION:
            status = PAL_ERR_PK_KEY_INVALID_VERSION;
            break;
        case MBEDTLS_ERR_PK_KEY_INVALID_FORMAT:
            status = PAL_ERR_PK_KEY_INVALID_FORMAT;
            break;
        case MBEDTLS_ERR_PK_PASSWORD_REQUIRED:
            status = PAL_ERR_PK_PASSWORD_REQUIRED;
            break;
        default:
            status = PAL_ERR_PARSING_PRIVATE_KEY;
    }

    return status;
}

palStatus_t pal_plat_parseECPublicKeyFromDER(const unsigned char* pubDERKey, size_t keyLen, palECKeyHandle_t key)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)key;

    if (pal_plat_isPEM(pubDERKey, keyLen))
    {
        return PAL_ERR_INVALID_ARGUMENT;
    }

    platStatus = mbedtls_pk_parse_public_key(localECKey, pubDERKey, keyLen);
    switch(platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            break;
        case MBEDTLS_ERR_PK_UNKNOWN_PK_ALG:
            status = PAL_ERR_PK_UNKNOWN_PK_ALG;
            break;
        case MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE:
            status = PAL_ERR_NOT_SUPPORTED_CURVE;
            break;
        case MBEDTLS_ERR_PK_KEY_INVALID_FORMAT:
            status = PAL_ERR_PK_KEY_INVALID_FORMAT;
            break;
        case MBEDTLS_ERR_PK_INVALID_PUBKEY + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH: //This is how mbedTLS returns erros for this function
            status = PAL_ERR_PK_INVALID_PUBKEY_AND_ASN1_LEN_MISMATCH;
            break;
        case MBEDTLS_ERR_ECP_INVALID_KEY:
            status = PAL_ERR_ECP_INVALID_KEY;
            break;
        default:
            status = PAL_ERR_PARSING_PUBLIC_KEY;
    }

    return status;
}

//! Move data from the end of the buffer to the begining, this function is needed since mbedTLS
//! write functions write the data at the end of the buffers.
PAL_PRIVATE void moveDataToBufferStart(unsigned char* buffer, size_t bufferSize, size_t actualSize)
{
    size_t j = 0;
    size_t i = bufferSize - actualSize;
    if (bufferSize == actualSize)
    {
        return;
    }

    for( ; j < actualSize ; ++i , ++j)
    {
        buffer[j] = buffer[i];
        buffer[i] = (unsigned char)0;
    }
}

palStatus_t pal_plat_writePrivateKeyToDer(palECKeyHandle_t key, unsigned char* derBuffer, size_t bufferSize, size_t* actualSize)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)key;

    platStatus = mbedtls_pk_write_key_der(localECKey, derBuffer, bufferSize);
    if (CRYPTO_PLAT_SUCCESS < platStatus)
    {
        *actualSize = platStatus;
        moveDataToBufferStart(derBuffer, bufferSize, *actualSize);
    }
    else
    {
        status = PAL_ERR_FAILED_TO_WRITE_PRIVATE_KEY;
    }

    return status;
}

palStatus_t pal_plat_writePublicKeyToDer(palECKeyHandle_t key, unsigned char* derBuffer, size_t bufferSize, size_t* actualSize)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)key;

    platStatus = mbedtls_pk_write_pubkey_der(localECKey, derBuffer, bufferSize);
    if (CRYPTO_PLAT_SUCCESS < platStatus)
    {
        *actualSize = platStatus;
        moveDataToBufferStart(derBuffer, bufferSize, *actualSize);
    }
    else
    {
        status = PAL_ERR_FAILED_TO_WRITE_PUBLIC_KEY;
    }

    return status;
}

palStatus_t pal_plat_ECKeyGenerateKey(palGroupIndex_t grpID, palECKeyHandle_t key)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    mbedtls_ecp_group_id platCurve = MBEDTLS_ECP_DP_NONE;
    palECKey_t* localECKey = (palECKey_t*)key;
    mbedtls_ecp_keypair* keyPair = NULL;

    keyPair = (mbedtls_ecp_keypair*)localECKey->pk_ctx;

    switch(grpID)
    {
        case PAL_ECP_DP_SECP256R1:
            platCurve = MBEDTLS_ECP_DP_SECP256R1;
            break;
        default: 
            status = PAL_ERR_NOT_SUPPORTED_CURVE;
            goto finish;
    }

    platStatus = mbedtls_ecp_gen_key(platCurve, keyPair, pal_plat_entropySource, NULL);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_KEYPAIR_GEN_FAIL;
    }

finish:
    return status;
}

palStatus_t pal_plat_ECKeyGetCurve(palECKeyHandle_t key, palGroupIndex_t* grpID)
{
    palStatus_t status = PAL_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)key;
    mbedtls_ecp_keypair* keyPair = NULL;

    if (NULL == (mbedtls_ecp_keypair*)localECKey->pk_ctx)
    {
        return PAL_ERR_INVALID_ARGUMENT;
    }
    keyPair = (mbedtls_ecp_keypair*)localECKey->pk_ctx;

    switch(keyPair->grp.id)
    {
        case MBEDTLS_ECP_DP_SECP256R1:
            *grpID = PAL_ECP_DP_SECP256R1;
            break;
        default:
            *grpID = PAL_ECP_DP_NONE;
            status = PAL_ERR_NOT_SUPPORTED_CURVE;
    }
    return status;
}

palStatus_t pal_plat_ECGroupFree(palCurveHandle_t* grp)
{
    palStatus_t status = PAL_SUCCESS;
    palECGroup_t* localGroup = NULL;

    localGroup = (palECGroup_t*)*grp;
    mbedtls_ecp_group_free(localGroup);
    free(localGroup);
    *grp = NULLPTR;
    return status;
}

palStatus_t pal_plat_ECGroupInitAndLoad(palCurveHandle_t* grp, palGroupIndex_t index)
{
    palStatus_t status = PAL_SUCCESS;
    mbedtls_ecp_group_id platCurve = MBEDTLS_ECP_DP_NONE;
    palECGroup_t* localGroup = NULL;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    localGroup = (palECGroup_t*)malloc(sizeof(palECGroup_t));
    if (NULL == localGroup)
    {
        status = PAL_ERR_NO_MEMORY;
        goto finish;
    }

    mbedtls_ecp_group_init(localGroup);
    switch(index)
    {
        case PAL_ECP_DP_SECP256R1:
            platCurve = MBEDTLS_ECP_DP_SECP256R1;
            break;
        default: 
            status = PAL_ERR_NOT_SUPPORTED_CURVE;
            goto finish;
    }

    platStatus = mbedtls_ecp_group_load(localGroup ,platCurve);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_GROUP_LOAD_FAILED;
    }
    else
    {
        *grp = (palCurveHandle_t)localGroup;
    }
    
finish:
    if (PAL_SUCCESS != status && localGroup != NULL)
    {
        free(localGroup);
    }

    return status;
}


palStatus_t pal_plat_ECDHComputeKey(const palCurveHandle_t grp, const palECKeyHandle_t peerPublicKey, const palECKeyHandle_t privateKey, palECKeyHandle_t outKey)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECGroup_t* ecpGroup = (palECGroup_t*)grp;
    mbedtls_ecp_keypair* pubKeyPair = NULL;
    mbedtls_ecp_keypair* prvKeyPair = NULL;
    mbedtls_ecp_keypair* outKeyPair = NULL;
    mbedtls_ctr_drbg_context ctrDrbgCtx;

    mbedtls_ctr_drbg_init(&ctrDrbgCtx);

    pubKeyPair = (mbedtls_ecp_keypair*)((palECKey_t*)peerPublicKey)->pk_ctx;
    prvKeyPair = (mbedtls_ecp_keypair*)((palECKey_t*)privateKey)->pk_ctx;
    outKeyPair = (mbedtls_ecp_keypair*)((palECKey_t*)outKey)->pk_ctx;

    if (NULL != pubKeyPair && NULL != prvKeyPair && NULL != outKeyPair)
    {
        platStatus = mbedtls_ecdh_compute_shared(ecpGroup, &outKeyPair->d, &pubKeyPair->Q, &prvKeyPair->d, mbedtls_ctr_drbg_random, (void*)&ctrDrbgCtx);
        if (CRYPTO_PLAT_SUCCESS != platStatus)
        {
            status = PAL_ERR_FAILED_TO_COMPUTE_SHRED_KEY;
        }
    }
    else 
    {
        status = PAL_ERR_INVALID_ARGUMENT;
    }


    mbedtls_ctr_drbg_free(&ctrDrbgCtx);

    return status;
}


palStatus_t pal_plat_ECDSASign(palCurveHandle_t grp, palMDType_t mdType, palECKeyHandle_t prvKey, unsigned char* dgst, uint32_t dgstLen, unsigned char* sig, size_t* sigLen)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)prvKey;
    mbedtls_ecp_keypair* keyPair = NULL;
    mbedtls_ecdsa_context localECDSA;
    palECGroup_t* localGroup = (palECGroup_t*)grp;
    mbedtls_md_type_t mdAlg = MBEDTLS_MD_NONE;

    keyPair = (mbedtls_ecp_keypair*)localECKey->pk_ctx;

    mbedtls_ecdsa_init(&localECDSA);
    platStatus = mbedtls_ecdsa_from_keypair(&localECDSA, keyPair);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_FAILED_TO_COPY_KEYPAIR;
        goto finish;
    }

    platStatus = mbedtls_ecp_group_copy(&localECDSA.grp, localGroup);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_FAILED_TO_COPY_GROUP;
        goto finish;
    }

    switch (mdType)
    {
        case PAL_SHA256:
            mdAlg = MBEDTLS_MD_SHA256;
            break;
        default:
            status = PAL_ERR_INVALID_MD_TYPE;
            goto finish;
    }

    platStatus = mbedtls_ecdsa_write_signature(&localECDSA, mdAlg, dgst, dgstLen, sig, sigLen, NULL, NULL);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_FAILED_TO_WRITE_SIGNATURE;
    }

finish:
    mbedtls_ecdsa_free(&localECDSA);
    return status;
}

palStatus_t pal_plat_ECDSAVerify(palECKeyHandle_t pubKey, unsigned char* dgst, uint32_t dgstLen, unsigned char* sig, size_t sigLen, bool* verified)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palECKey_t* localECKey = (palECKey_t*)pubKey;
    mbedtls_ecp_keypair* keyPair = NULL;
    mbedtls_ecdsa_context localECDSA;

    keyPair = (mbedtls_ecp_keypair*)localECKey->pk_ctx;

    mbedtls_ecdsa_init(&localECDSA);
    platStatus = mbedtls_ecdsa_from_keypair(&localECDSA, keyPair);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_FAILED_TO_COPY_KEYPAIR;
        goto finish;
    }

    platStatus = mbedtls_ecdsa_read_signature(&localECDSA, dgst, dgstLen, sig, sigLen);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_FAILED_TO_VERIFY_SIGNATURE;
        *verified = false;
    }
    else
    {
        *verified = true;
    }
finish:
    mbedtls_ecdsa_free(&localECDSA);
    return status;
}
#if (PAL_ENABLE_X509 == 1)
palStatus_t pal_plat_x509CSRInit(palx509CSRHandle_t *x509CSR)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t *localCSR = NULL;

    localCSR = (palx509CSR_t*)malloc(sizeof(palx509CSR_t));
    if (NULL == localCSR)
    {
        status = PAL_ERR_NO_MEMORY;
    }
    else
    {
        mbedtls_x509write_csr_init(localCSR);
        *x509CSR = (palx509CSRHandle_t)localCSR;
    }
    return status;
}

palStatus_t pal_plat_x509CSRSetSubject(palx509CSRHandle_t x509CSR, const char* subjectName)
{
    palStatus_t status = PAL_SUCCESS;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;
    palx509CSR_t *localCSR = (palx509CSR_t*)x509CSR;

    platStatus = mbedtls_x509write_csr_set_subject_name(localCSR, subjectName);
    switch (platStatus)
    {
        case CRYPTO_PLAT_SUCCESS:
            status = PAL_SUCCESS;
            break;
        case MBEDTLS_ERR_X509_UNKNOWN_OID:
            status = PAL_ERR_X509_UNKNOWN_OID;
            break;
        case MBEDTLS_ERR_X509_INVALID_NAME:
            status = PAL_ERR_X509_INVALID_NAME;
            break;
        default:
            {
                PAL_LOG(ERR, "Crypto x509 CSR set subject status %" PRId32 ".", platStatus);
                status = PAL_ERR_GENERIC_FAILURE;
            }
    }

    return status;
}

palStatus_t pal_plat_x509CSRSetKey(palx509CSRHandle_t x509CSR, palECKeyHandle_t pubKey, palECKeyHandle_t prvKey)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t *localCSR = (palx509CSR_t*)x509CSR;
    palECKey_t* localPubKey = (palECKey_t*)pubKey;
    palECKey_t* localPrvKey = (palECKey_t*)prvKey;

    if (NULL != localPrvKey)
    {
        int32_t platStatus = CRYPTO_PLAT_SUCCESS;
        mbedtls_ecp_keypair* pubKeyPair = NULL;
        mbedtls_ecp_keypair* prvKeyPair = NULL;

        pubKeyPair = (mbedtls_ecp_keypair*)localPubKey->pk_ctx;
        prvKeyPair = (mbedtls_ecp_keypair*)localPrvKey->pk_ctx;

        if (NULL != pubKeyPair && NULL != prvKeyPair)
        {
            platStatus = mbedtls_mpi_copy(&(pubKeyPair->d), &(prvKeyPair->d));
            if (CRYPTO_PLAT_SUCCESS != platStatus)
            {
                status = PAL_ERR_FAILED_TO_COPY_KEYPAIR;
            }
        }
        else
        {
            status = PAL_ERR_INVALID_ARGUMENT;
        }
    }
    
    if (PAL_SUCCESS == status)
    {
        mbedtls_x509write_csr_set_key(localCSR, localPubKey);
    }
    
    return status;
}
    
palStatus_t pal_plat_x509CSRSetMD(palx509CSRHandle_t x509CSR, palMDType_t mdType)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t *localCSR = (palx509CSR_t*)x509CSR;
    mbedtls_md_type_t mdAlg = MBEDTLS_MD_NONE;

    switch (mdType)
    {
        case PAL_SHA256:
            mdAlg = MBEDTLS_MD_SHA256;
            break;
        default:
            status = PAL_ERR_INVALID_MD_TYPE;
            goto finish;
    }

    mbedtls_x509write_csr_set_md_alg(localCSR, mdAlg);

finish:
    return status;
}

palStatus_t pal_plat_x509CSRSetKeyUsage(palx509CSRHandle_t x509CSR, uint32_t keyUsage)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t *localCSR = (palx509CSR_t*)x509CSR;
    uint8_t localKeyUsage = 0;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    if (PAL_X509_KU_DIGITAL_SIGNATURE & keyUsage)
    {
        localKeyUsage |= MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
    }
    if (PAL_X509_KU_KEY_CERT_SIGN & keyUsage)
    {
        localKeyUsage |= MBEDTLS_X509_KU_KEY_CERT_SIGN;
    }
    if (PAL_X509_KU_NON_REPUDIATION & keyUsage)
    {
        localKeyUsage |= MBEDTLS_X509_KU_NON_REPUDIATION;
    }

    if (0 == localKeyUsage)
    {
        status = PAL_ERR_INVALID_KEY_USAGE;
    }
    else
    {
        platStatus = mbedtls_x509write_csr_set_key_usage(localCSR, localKeyUsage);
        if (CRYPTO_PLAT_SUCCESS != platStatus)
        {
            status = PAL_ERR_FAILED_TO_SET_KEY_USAGE;
        }
    }
    return status;
}

palStatus_t pal_plat_x509CSRSetExtension(palx509CSRHandle_t x509CSR,const char* oid, size_t oidLen, const unsigned char* value, size_t valueLen)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t *localCSR = (palx509CSR_t*)x509CSR;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    platStatus = mbedtls_x509write_csr_set_extension(localCSR, oid, oidLen, value, valueLen);
    if (CRYPTO_PLAT_SUCCESS != platStatus)
    {
        status = PAL_ERR_SET_EXTENTION_FAILED;
    }
    return status;
}

palStatus_t pal_plat_x509CSRWriteDER(palx509CSRHandle_t x509CSR, unsigned char* derBuf, size_t derBufLen, size_t* actualDerLen)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t *localCSR = (palx509CSR_t*)x509CSR;
    int32_t platStatus = CRYPTO_PLAT_SUCCESS;

    platStatus = mbedtls_x509write_csr_der(localCSR, derBuf, derBufLen, pal_plat_entropySource, NULL);
    if (CRYPTO_PLAT_SUCCESS >= platStatus) //! mbedtls_x509write_csr_der() returns the size of the written CSR
    {                                      //! we need to check if the length larger than zero
        status = PAL_ERR_CSR_WRITE_DER_FAILED;
        goto finish;
    }

    moveDataToBufferStart(derBuf, derBufLen, platStatus);
    if (actualDerLen)
    {
        *actualDerLen = platStatus;
    }
 finish:
    return status;
}

palStatus_t pal_plat_x509CSRFree(palx509CSRHandle_t *x509CSR)
{
    palStatus_t status = PAL_SUCCESS;
    palx509CSR_t* localCSR = (palx509CSR_t*)*x509CSR;

    mbedtls_x509write_csr_free(localCSR);
    free(localCSR);
    *x509CSR = NULLPTR;
    return status;
}
#endif
PAL_PRIVATE int pal_plat_entropySourceDRBG( void *data, unsigned char *output, size_t len)
{
    (void)data;
    return CRYPTO_PLAT_SUCCESS;
}

PAL_PRIVATE int pal_plat_entropySource( void *data, unsigned char *output, size_t len)
{
	palStatus_t status = PAL_SUCCESS;
    (void)data;
    
    status = pal_osRandomBuffer((uint8_t*) output, len);
    if (PAL_SUCCESS == status)
    {
        return CRYPTO_PLAT_SUCCESS;
    }
    else
    {
        return CRYPTO_PLAT_GENERIC_ERROR;
    }
}

#ifdef __arm__ // we are compiling using the ARM compiler
/* This function is provided for ARM-CC compiler, since mbedTLS uses it and it returns NULL
 * in ARM-CC, we need to provide replacement function to keep correct functionality
 * mbedTLS will change the internal implementation which uses gmtime()
 */ 
struct tm *gmtime(const time_t *timep)
{
    return localtime(timep);
}

#endif
