
/*
 * =====================================================================================
 *
 *       Filename:  hdm_x509.c
 *
 *    Description:  HDM x509 certificates manipulation
 *
 *        Version:  1.0
 *        Created:  09/16/2019 15:26:11 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *        Company:  Samsung Electronics
 *        Copyright (c) 2015 by Samsung Electronics, All rights reserved.
 *
 * =====================================================================================
 */

/** Includes */
#include "hdm_x509.h"

/**
 * Trust anchor x509 certificate - SamsungServiceServerCA.crt
 */
uint8_t *trust_anchor_cert = (uint8_t*) "-----BEGIN CERTIFICATE-----\n"\
                           "MIIGljCCBH6gAwIBAgIIMTk4NTA2MDIwDQYJKoZIhvcNAQELBQAwgakxCzAJBgNVBAYTAktSMRowGAYDVQQIDBFSZXB1YmxpYyBvZiBLb3JlYTETMBEGA1UEBwwKU3V3b24gQ2l0eTEmMCQGA1UECgwdU2Ftc3VuZyBFbGVjdHJvbmljcyBDby4sIEx0ZC4xJzAlBgNVBAsMHk1vYmlsZSBDb21tdW5pY2F0aW9ucyBCdXNpbmVzczEYMBYGA1UEAwwPU2Ftc3VuZyBSb290IENBMB4XDTE4MDUyOTA0NDUxM1oXDTQzMDUyMzA0NDUxM1owgb0xCzAJBgNVBAYTAktSMRowGAYDVQQIDBFSZXB1YmxpYyBvZiBLb3JlYTETMBEGA1UEBwwKU3V3b24gQ2l0eTEmMCQGA1UECgwdU2Ftc3VuZyBFbGVjdHJvbmljcyBDby4sIEx0ZC4xJzAlBgNVBAsMHk1vYmlsZSBDb21tdW5pY2F0aW9ucyBCdXNpbmVzczEsMCoGA1UEAwwjU2Ftc3VuZyBTZXJ2aWNlIFNlcnZlciBDQSAtIGNsYXNzIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmo9CeImKfK0zTRkI3ua5cs/O/OOHK4+Y/nylEL2a4UUdghULgIIO9z81N1vkP6jH13ULAN0RqP8Kf/0lbYTw+75Zeveb1UONY/VxISGy0FEGUrymBEGquHH3LmQUtyAyYApHj/y1uI5jz5bDYASUJNucaT0ANIcCbK7XAyBaHVgUYDHtl7+S6buKQ+LQaQRZ43bm0GXoG6n6mUkNdnGe6d2/7jeN/uJ83xZFRgn+gDtpVVGtsi7YuCSu1KB9JdcX5g5tDalpSjCTliL6GD/G7YlsKAdbS2DdJSjK4znor0qWzjDnrSIsMUY+2EgERHD+QnZrLrBHu+7KrN7PqTml5AgMBAAGjggGqMIIBpjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQcEpH0wXgi93JT2HMoIDbgjy7LSTCB3gYDVR0jBIHWMIHTgBSscCkf1Kqh8hUUcAnTJD5u4bDsI6GBr6SBrDCBqTELMAkGA1UEBhMCS1IxGjAYBgNVBAgMEVJlcHVibGljIG9mIEtvcmVhMRMwEQYDVQQHDApTdXdvbiBDaXR5MSYwJAYDVQQKDB1TYW1zdW5nIEVsZWN0cm9uaWNzIENvLiwgTHRkLjEnMCUGA1UECwweTW9iaWxlIENvbW11bmljYXRpb25zIEJ1c2luZXNzMRgwFgYDVQQDDA9TYW1zdW5nIFJvb3QgQ0GCCQDZ5njXVm5+xjAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5zYW1zdW5nLmNvbS9zYW1zdW5nY2Evcm9vdC5jcmwwRwYIKwYBBQUHAQEEOzA5MDcGCCsGAQUFBzABhitodHRwOi8vb2NzcC5zYW1zdW5nLmNvbS9zYW1zdW5nY2Evb2NzcC1yb290MA0GCSqGSIb3DQEBCwUAA4ICAQA+VGQwalMJ+T/hjNOvYYUtyaIp2apHC+pmlo8tP8SX0nFsciDGqjbLCavqragPg1akRdofMxjZTsivIGaIqi4ItBfR/nkEaBkM651gyqyhd9vKv5iQ8lJqMg6qOz5xySXNqfC5Y9n3sBrNRLk0xL7Hku2g5vlsTp7v3AnEU4C1UU55wR/U8F3PJbvnL7VSpoEwPxTl5kQt+uAOEBJZw4BSOSASPUtP038m8JzCW+UAAam3U2NtWDUwN4jlqrcF145VE8RVuyY98Ee136jZRxNkQzOrGkEhIbMe4HtijPOolaaqH2kWO7Z8RkKsKEoDRANiQKWfSkHYernW0bEqAaU4LOggzF4hLgxYbAt4wTWmAS/pmTu1xfWFbIpZZNbAd2IyspTFgDXHj1zwHiR4T8xGsDyHnbXyA6VkjQxe14acXQzrwHtcDtmUsKphX/vfHUdVoO2eLiuWhECxfc6N4Bg9ksHjWqhtDdKLF2fOPkST2lCEciDIkOyYXrjJyQF/BaHagTnl+qIM/bA8lqwoaVMWU4tfdwNqO0ntLOXmHEGTsZ7XXtSvX3dtmqhTmYDfT9FESUr9k5rfhKiLBQv/UTpFaZfQQcuMTfKPA973BnUV+VYAwr+xvqVuGatWlpdJyd07EIokGzkQCcKrolp3rT0W/b3SpIq14F7hpuO1o3FfQg==\n"\
                           "-----END CERTIFICATE-----";

/**
 * Static functions prototypes
 */
static EVP_PKEY *get_public_key(uint8_t *pem_cert);
static hdm_return_code_t verify_cert(uint8_t *pem_cert, uint8_t *pem_trust_anchor);

/**
 * @brief
 * get_public_key
 * Parses public key from x509 PEM certificate
 * @param[in] *pem_cert - PEM certificate
 *
 * @return EVP_PKEY
 */
static EVP_PKEY* get_public_key(uint8_t *pem_cert) {
        HDM_LOG_DEBUG("get_public_key()");

        BIO  *reqbio = NULL;
        X509 *certreq = NULL;

        reqbio = BIO_new_mem_buf(pem_cert, -1);
        if(reqbio == NULL)
        {
                HDM_LOG("Error to alloc memory");
                return NULL;
        }

        if (!(certreq = PEM_read_bio_X509(reqbio, NULL, NULL, NULL))) {
                HDM_LOG("ERROR read bio");
                BIO_free(reqbio);  // SI-16842
                return NULL;
        }

        EVP_PKEY *pkey_trust_anchor = X509_get_pubkey(certreq);
        X509_free(certreq);
        BIO_free(reqbio);

        return pkey_trust_anchor;
}

/**
 * @brief
 * get subjectName from Certificate
 * return subjectName
 * @param[in] *pem_cert - PEM certificate
 * @param[out] *subjectName - subject name from pem_cert
 *
 * @return HDM status code
 */
static hdm_return_code_t get_subject_from_certificate(uint8_t *pem_cert, uint8_t** subjectName) {
        HDM_LOG_DEBUG("get_subject_from_certificate()");

        hdm_return_code_t ret = HDM_STATUS_SUCCESS;
        BIO  *reqbio = NULL;
        X509 *certreq = NULL;

        reqbio = BIO_new_mem_buf(pem_cert, -1);
        if(reqbio == NULL)
        {
                HDM_LOG("Error to alloc memory");
                return HDM_CONVERT_DER_CERT_FAIL;
        }
        certreq = PEM_read_bio_X509(reqbio, NULL, NULL, NULL);

        if (certreq == NULL) {
                HDM_LOG("parse_pem_cert Failed!");
                unsigned long errCode = ERR_peek_last_error();
                char errBuffer[16384];
                ERR_error_string_n(errCode, errBuffer, 16384);

                HDM_LOG_DEBUG("err = %s",errBuffer);
                BIO_free(reqbio); // SI-16842
                return HDM_CONVERT_DER_CERT_FAIL;
        } else {
                HDM_LOG_DEBUG("parse_pem_cert Success!");
        }

        X509_NAME *subj = X509_get_subject_name(certreq);

        int index = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
        X509_NAME_ENTRY *e = X509_NAME_get_entry(subj, index);
        ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
        unsigned char *str = ASN1_STRING_data(d);
        if (str == NULL) {
            HDM_LOG("Wrong subjectName!");
            X509_free(certreq);
            BIO_free(reqbio);
            return HDM_CONVERT_DER_CERT_FAIL;
        }
        int str_len = ASN1_STRING_length(d); // SI-16920

        *subjectName = TEE_Malloc((str_len + 1), 0);
        if (*subjectName != NULL) { // SI-16876
            TEE_MemMove(*subjectName, str, str_len);
            HDM_LOG_DEBUG("CN = %s",*subjectName);
        }
        else {
            HDM_LOG("TEE_Malloc failed");
        }

        X509_free(certreq);
        BIO_free(reqbio);

        return ret;
}

/**
 * @brief
 * Compare string subjectName with subjectName from certificate
 * return subjectName
 * @param[out] *subjectName - subject name from pem_cert
 * * @param[in] *pem_cert - PEM certificate
 *
 * @return HDM status code
 */
static hdm_return_code_t compare_subject_name(uint8_t *subjectName, uint8_t *pem_cert) 
{
        HDM_LOG_DEBUG("compare_subject_name()");

        hdm_return_code_t ret = HDM_STATUS_SUCCESS;
        uint8_t* subjectName_from_cert = NULL;

        ret = get_subject_from_certificate(pem_cert, &subjectName_from_cert);
        if(ret != HDM_STATUS_SUCCESS){
                HDM_LOG_DEBUG("get_subject_from_certificate error: %d", ret);
                goto exit;
        }
        
        if(strlen((char *) subjectName_from_cert) != strlen((char *)subjectName)){
                HDM_LOG_DEBUG("len not match %zu %zu", strlen((char *)subjectName_from_cert), strlen((char *) subjectName));
                ret = HDM_INVALID_CERTCHAIN;
                goto exit;
        }
        ret = strncmp((char *) subjectName,(char *) subjectName_from_cert, strlen((char *) subjectName));
        if(ret != 0){
                HDM_LOG("subjectName is wrong");
                HDM_LOG_DEBUG("subjectName is wrong - subjectName1=%s subjectName=%s", subjectName, subjectName_from_cert);
                ret = HDM_INVALID_CN_CHECK;
        }
        else
        {
                HDM_LOG_DEBUG("subjectName match");
        }
        
exit:
        if(subjectName_from_cert != NULL){
                TEE_Free(subjectName_from_cert);
        }
                
        return ret;
}

/**
 * @brief
 * verify_cert
 * Verify x509 certificate with its trust anchor
 *
 * @param[in] *pem_cert         - PEM certificate
 * @param[in] *pem_trust_anchor - x509 trust anchor certificate
 *
 * @return HDM status code
 */
static hdm_return_code_t verify_cert(uint8_t *pem_cert, uint8_t *pem_trust_anchor) {
        HDM_LOG_DEBUG("verify_cert()");

        hdm_return_code_t ret = HDM_STATUS_SUCCESS;
        BIO  *reqbio = NULL;
        X509 *certreq = NULL;
        EVP_PKEY *pkey_trust_anchor = NULL;

        OpenSSL_add_all_algorithms();
        ERR_load_BIO_strings();

        reqbio = BIO_new_mem_buf(pem_cert, -1);
        if(reqbio == NULL)
        {
                ret = HDM_ALLOC_ERROR;
                HDM_LOG("Error to alloc memory");
                goto exit;
        }

        if (!(certreq = PEM_read_bio_X509(reqbio, NULL, NULL, NULL))) {
                HDM_LOG("ERROR read bio");
                ret = HDM_INVALID_CERTCHAIN;
                goto exit;
        }

        pkey_trust_anchor = get_public_key(pem_trust_anchor);
        if(pkey_trust_anchor == NULL) {
                HDM_LOG("FAIL to get trust anchor");
                ret = HDM_INVALID_CERTCHAIN;
                goto exit;
        }

        ret = X509_verify(certreq, pkey_trust_anchor);
        if(!ret) {
                HDM_LOG("Chain Cert verify FAIL");
                HDM_LOG_DEBUG("X509_verify fail ret = %d",ret);
                ret = HDM_INVALID_CERTCHAIN;
                goto exit;
        }

        HDM_LOG("Certificate signature OK");
        ret = HDM_STATUS_SUCCESS;
exit:
        if (pkey_trust_anchor != NULL) EVP_PKEY_free(pkey_trust_anchor);
        if (certreq != NULL) X509_free(certreq);
        if (reqbio != NULL) BIO_free(reqbio);

        return ret;
}

/**
 * @brief
 * verify_cert_chain
 * Validates x509 certificates chain
 *
 * @param[in] certificates[]     - certificates chain
 * @param[in] certificates_count - number of certificates
 * @param[in] key                - envelopped key
 *
 * @return HDM status code
 */
hdm_return_code_t verify_cert_chain(tz_hdm_header_x5c_t certificates[], uint32_t certificates_count, EVP_PKEY **pkey) {
        HDM_LOG_DEBUG("verify_cert_chain()");
        hdm_return_code_t ret = HDM_INVALID_CERTCHAIN;
        uint32_t em_status = -1;

        if(certificates_count < 2){
                HDM_LOG("Certificate chain is not completed");
                HDM_LOG_DEBUG("Invalid number of certificates: %d", certificates_count);
                return ret;
        }
        
        ret = hdm_ICCC_check_em_status((uint32_t*) &em_status);
        if (ret != HDM_STATUS_SUCCESS) {
                HDM_LOG_DEBUG("ICCC Fail");
                HDM_LOG_DEBUG("Fail to hdm_ICCC_check_em_status: %d", ret);
                return ret;
        }

        switch (em_status) {
                case 1: // DEV Device
                case 2: // Eng Token on PROD Device
                        HDM_LOG("Verifing CN DEV");
                        ret = compare_subject_name((uint8_t *) "HDM TEST", certificates[1].certificate);
                        if(ret != HDM_STATUS_SUCCESS) /* DEV CERT */
                        {
                                HDM_LOG_DEBUG("Invalid DEV subjectName");
                        }
                        else {
                                break;
                        }
                case 0: // PROD Device
                        HDM_LOG_DEBUG("Verifing CN PROD");
                        ret = compare_subject_name((uint8_t *) "HDM Intermediate CA", certificates[1].certificate);
                        if(ret != HDM_STATUS_SUCCESS) /* PROD CERT */
                        {
                                HDM_LOG_DEBUG("Invalid PROD subjectName");
                                return ret;
                        }
                        break;

                default:
                        HDM_LOG_DEBUG("Invalid em_status: %d", em_status);
                        return HDM_READ_EM_STATUS_FAIL;
                        break;
        }

        HDM_LOG("SubjectName checked");

        for (uint32_t i = 0; i < certificates_count-1; i++)
        {
                HDM_LOG_DEBUG("Check signature cert[%d] with cert[%d]", i, i+1);
                ret = verify_cert(certificates[i].certificate, certificates[i+1].certificate);
                if( ret != HDM_STATUS_SUCCESS)
                {
                        HDM_LOG("Invalid signature on certificate chain");
                        return ret;
                }
        }

        HDM_LOG_DEBUG("Check signature cert[%d] with trusted anchor", certificates_count-1);
        ret = verify_cert(certificates[certificates_count-1].certificate, trust_anchor_cert);
        if( ret != HDM_STATUS_SUCCESS)
        {
                HDM_LOG("Invalid signature on certificate chain");
                return ret;
        }

        *pkey = get_public_key(certificates[0].certificate);
        //SRR-14532
        if(*pkey == NULL){
                HDM_LOG_DEBUG("Invalid pkey");
                return HDM_KEY_ERROR;
        }
        ret = HDM_STATUS_SUCCESS;
        return ret;        
}

/**
 * @brief
 * convert_der_to_b64
 * Convert der certificates to PEM.
 * 
 * @param[in]     cert_chain  - array of certificates to be converted
 * @param[in]     num_certs   - the number of certificates in the array
 *
 * @return Status Code
*/
hdm_return_code_t convert_der_to_b64(drk_cert_chain_t *cert_chain, uint32_t num_certs) {
        uint32_t i;
        hdm_return_code_t ret = HDM_STATUS_SUCCESS;

        for (i = 0; i < num_certs; i++) {
                if (base64_encode_in_place(cert_chain[i].certificate, &cert_chain[i].certificate_len, DRK_CERT_MAX_CERT_LENGTH) != BASE64_OK)
                        return HDM_CONVERT_DER_CERT_FAIL;
        }

        return ret;
}
