/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include <openssl/md5.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/pkcs12.h>

#include "util/util.h"
#include "iotcs_port_crypto.h"
#include "util/util_memory.h"
#include "trusted_assets_manager/iotcs_tam.h"
#include "iotcs_port_tam.h"
#include "util/util_buffer.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_PORT_TAM
#include "log/log_template.h"

#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define OPENSSL_1_0
#endif

#define PASSWORD_KEY_LENGTH 16
#define DIGEST_LENGTH 32

static size_t iotcsp_trust_anchors_count;
#define        IOTCSP_MAX_TRUST_ANCHORS 10
#define        IOTCSP_MAX_PROP_SIZE 256
static size_t iotcsp_trust_anchors_lengths[IOTCSP_MAX_TRUST_ANCHORS];
static char* iotcsp_trust_anchors[IOTCSP_MAX_TRUST_ANCHORS];

#define MAX(a,b) ((a) > (b)) ? (a) : (b)
#define MIN(a,b) ((a) < (b)) ? (a) : (b)

static EVP_PKEY* tam_rsa = NULL;
static char* tam_assets_file = NULL;
static char* ts_password = NULL;

iotcs_result iotcs_port_tam_set_ts_password(const char* trust_store_password) {
    if (NULL != ts_password) {
        util_free(ts_password);
    }
    if (NULL == (ts_password = util_safe_strcpy(trust_store_password))) {
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }

    return IOTCS_RESULT_OK;
}

static iotcs_result derive_key(unsigned char *key, int key_length) {
    // get the key, from the password
    if (!PKCS5_PBKDF2_HMAC_SHA1(ts_password, strlen(ts_password), NULL, 0, 1, key_length, key)) {
        LOG_ERRS("PKCS5_PBKDF2_HMAC_SHA1 failed");
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_port_tam_init(void) {
    return IOTCS_RESULT_OK;
}

void iotcs_port_tam_finalize(void) {
    EVP_PKEY_free(tam_rsa);
    tam_rsa = NULL;
    util_free(tam_assets_file);
    tam_assets_file = NULL;
    for (size_t i = 0; i < iotcsp_trust_anchors_count; i++) {
        free(iotcsp_trust_anchors[i]);
        iotcsp_trust_anchors[i] = NULL;
    }
    util_free(ts_password);
}

size_t iotcs_port_tam_secret_hash(
        IOTCS_TYPE_SHA type,
        const unsigned char* key, size_t key_length,
        const unsigned char* content, size_t content_length,
        unsigned char *hash,
        size_t hash_maxsize) {
    const EVP_MD* md;
    size_t expectedLength;
    unsigned int result_len;

    if (type == IOTCS_TYPE_SHA1) {
        md = EVP_sha1();
        expectedLength = SHA_DIGEST_LENGTH;
    } else if (type == IOTCS_TYPE_SHA256) {
        md = EVP_sha256();
        expectedLength = SHA256_DIGEST_LENGTH;
    } else if (type == IOTCS_TYPE_SHA512) {
        md = EVP_sha512();
        expectedLength = SHA512_DIGEST_LENGTH;
    } else {
        LOG_ERRS("Unexpected SHA type");
        return 0;
    }

    if (hash_maxsize < expectedLength) {
        LOG_ERRS("Unexpected digest length");
        return 0;
    }

    HMAC(md,
            key, key_length,
            content, content_length,
            hash, &result_len);

    if (result_len > hash_maxsize) {
        LOG_ERR("Buffer overflow: max value - %d returned - %d", hash_maxsize, result_len);
        return 0;
    }

    return result_len;
}

iotcs_result iotcs_port_tam_decode_shared_secret(char* encrypted_shared_secret, char *shared_secret, int shared_secret_length) {
    iotcs_result result;
#ifdef OPENSSL_1_0
    EVP_CIPHER_CTX dec;
#else
    EVP_CIPHER_CTX *dec = EVP_CIPHER_CTX_new();
#endif
    int dec_initialized = 0;
    unsigned char key[PASSWORD_KEY_LENGTH];
    unsigned char iv[TAM_AES_CBC_128_IV_LENGTH];
    BIO* bioB64 = NULL;
    BIO* bioBuf = NULL;
    BIO* bio = NULL;
    long data_length;

    if (!shared_secret || !encrypted_shared_secret) {
        LOG_ERRS("Invalid parameter in iotcs_port_tam_decode_shared_secret method");
        result = IOTCS_RESULT_INVALID_ARGUMENT;
        goto done;
    }

    // get the key, from the password
    result = derive_key(key, PASSWORD_KEY_LENGTH);
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Key derivation failed");
        goto done;
    }

    // encrypted_shared_secret is base64 encoded
    bioB64 = BIO_new(BIO_f_base64());
    if (bioB64 == NULL) {
        LOG_CRITS("Failed to allocate bioB64");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }

    bioBuf = BIO_new_mem_buf((void*) encrypted_shared_secret, strlen(encrypted_shared_secret));
    if (bioBuf == NULL) {
        LOG_ERRS("Failed to allocate bio");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    bio = BIO_push(bioB64, bioBuf);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);

    data_length = BIO_read(bio, (void*) shared_secret, shared_secret_length);
    if ((data_length <= 0) || (data_length >= shared_secret_length)) {
        LOG_ERRS("Failed to decode Base64");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    // we use a zero IV
    memset(iv, 0, sizeof (iv));
#ifdef OPENSSL_1_0
    if (!EVP_DecryptInit(&dec, EVP_aes_128_cbc(), key, iv)) {
#else
    if (!EVP_DecryptInit(dec, EVP_aes_128_cbc(), key, iv)) {
#endif
        LOG_ERRS("Failed to init AES");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    dec_initialized = 1;

    /*
     * encrypted_shared_secret array length will not be smaller than shared_secret length
     * because shared_secret will contains decrypt base64 data. So we can use it as output array for aes_128_cbc decrypting
     */
    int outlen;
#ifdef OPENSSL_1_0
    if (!EVP_DecryptUpdate(&dec, (unsigned char *) encrypted_shared_secret, &outlen, (const unsigned char *) shared_secret, data_length)) {
#else
    if (!EVP_DecryptUpdate(dec, (unsigned char *) encrypted_shared_secret, &outlen, (const unsigned char *) shared_secret, data_length)) {
#endif
        LOG_ERRS("Failed to perform AES update");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

#ifdef OPENSSL_1_0
    if (!EVP_DecryptFinal(&dec, (unsigned char *) encrypted_shared_secret + outlen, &outlen)) {
#else
    if (!EVP_DecryptFinal(dec, (unsigned char *) encrypted_shared_secret + outlen, &outlen)) {
#endif
        LOG_ERRS("Failed to finalize AES");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    /*the shared secret is a string, it must be NULL-terminated*/
    (encrypted_shared_secret)[outlen] = 0;

    /*outlen + "/n"*/
    if (outlen + 1 > shared_secret_length) {
        LOG_ERRS("Decrypted shared secret is too long");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    memcpy(shared_secret, encrypted_shared_secret, outlen + 1);
done:
    if (bio != NULL) {
        BIO_free_all(bio);
    } else {
        if (bioB64 != NULL) {
            BIO_free(bioB64);
        }
        if (bioBuf != NULL) {
            BIO_free(bioBuf);
        }
    }
#ifdef OPENSSL_1_0
    if (dec_initialized) {
        EVP_CIPHER_CTX_cleanup(&dec);
    }
#else
    if (dec_initialized) {
        EVP_CIPHER_CTX_cleanup(dec);
    }
    if (dec) {
        EVP_CIPHER_CTX_free(dec);
    }
#endif
    return result;
}

iotcs_result iotcs_port_tam_check_file_signature(FILE *file, size_t signed_data_length, const char* signature) {
    iotcs_result result;
    unsigned char key[PASSWORD_KEY_LENGTH];
    unsigned char buffer[DIGEST_LENGTH];
    unsigned char hash[DIGEST_LENGTH];
    unsigned int hash_len;
    char *hash_as_hex = NULL;

    // get the key, from the password
    result = derive_key(key, PASSWORD_KEY_LENGTH);
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Key derivation failed");
        goto done;
    }

#ifdef OPENSSL_1_0
    HMAC_CTX hmac_ctx;
    HMAC_CTX_init(&hmac_ctx);
    HMAC_Init_ex(&hmac_ctx, key, sizeof (key), EVP_sha256(), NULL);
#else
    HMAC_CTX *hmac_ctx = HMAC_CTX_new();
    HMAC_CTX_reset(hmac_ctx);
    HMAC_Init_ex(hmac_ctx, key, sizeof (key), EVP_sha256(), NULL);
#endif

    if (signed_data_length == 0) {
        signed_data_length = UINT32_MAX;
    }

    int len;
    int left = signed_data_length;
    while ((left != 0) && (len = fread(buffer, 1, MIN(sizeof (buffer), left), file))) {
        left -= len;
#ifdef OPENSSL_1_0
        HMAC_Update(&hmac_ctx, buffer, len);
#else
        HMAC_Update(hmac_ctx, buffer, len);
#endif
    }

#ifdef OPENSSL_1_0
    HMAC_Final(&hmac_ctx, hash, &hash_len);
    HMAC_CTX_cleanup(&hmac_ctx);
#else
    HMAC_Final(hmac_ctx, hash, &hash_len);
    HMAC_CTX_reset(hmac_ctx);
    if (hmac_ctx) {
        HMAC_CTX_free(hmac_ctx);
    }
#endif


    if (NULL == (hash_as_hex = util_malloc(sizeof (hash) * 2 + 1))) {
        LOG_CRITS("Failed to allocate buffer for signature");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }

    util_digest_to_hex(hash_as_hex, hash, sizeof (hash));

    if (strcasecmp(hash_as_hex, signature) != 0) {
        LOG_ERRS("Signature didn't match");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

done:
    if (hash_as_hex != NULL) {
        util_free(hash_as_hex);
    }
    return result;
}

iotcs_result iotcs_port_tam_load_trust_store_achors(const unsigned char* trust_store_anchors, int length) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    X509 *cert = NULL;
    int i = 0;

    if (NULL == trust_store_anchors || length <= 0) {
        iotcsp_trust_anchors_count = 0;
        return IOTCS_RESULT_OK;
    }

    /*cycle here used because we expects several certs*/
    while ((cert = d2i_X509(NULL, &trust_store_anchors, length)) != NULL) {
        int cert_len;
        GOTO_ERR_MSG(i >= IOTCSP_MAX_TRUST_ANCHORS, "Too many trust anchors found");
        cert_len = i2d_X509(cert, (unsigned char**) &iotcsp_trust_anchors[i]);
        GOTO_ERR_MSG(cert_len <= 0, "Failed to encode X509 structure into DER format");
        length -= cert_len;
        iotcsp_trust_anchors_lengths[i++] = cert_len;
        X509_free(cert);
    }

    iotcsp_trust_anchors_count = i;
    return IOTCS_RESULT_OK;

error:
    X509_free(cert);
    return result;
}

iotcs_result iotcs_port_tam_load_trust_store(FILE *trust_store_file) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    BIO *bio = NULL;
    X509 *cert = NULL;
    int i = 0;

    if (NULL == trust_store_file) {
        iotcsp_trust_anchors_count = 0;
        return IOTCS_RESULT_OK;
    }

    GOTO_ERR_MSG((bio = BIO_new_fp(trust_store_file, BIO_NOCLOSE)) == NULL, "method BIO_new_fp failed");

    while ((cert = PEM_read_bio_X509(bio, NULL, 0, NULL)) != NULL) {
        GOTO_ERR_MSG(i >= IOTCSP_MAX_TRUST_ANCHORS, "Too many trust anchors found");
        iotcsp_trust_anchors_lengths[i] = i2d_X509(cert, (unsigned char**) &iotcsp_trust_anchors[i]);
        if (iotcsp_trust_anchors_lengths[i] > 0) {
            i++;
        }
        X509_free(cert);
    }

    BIO_free(bio);
    iotcsp_trust_anchors_count = i;
    return IOTCS_RESULT_OK;

error:
    BIO_free(bio);
    X509_free(cert);
    return result;
}

size_t iotcs_port_tam_get_trust_anchors_count(void) {
    return iotcsp_trust_anchors_count;
}

const char* iotcs_port_crypto_get_trust_anchor_certificate(size_t index, size_t *len) {
    if ((index >= iotcsp_trust_anchors_count) || !len) {
        LOG_ERRS("Invalid parameters");
        return NULL;
    }

    *len = iotcsp_trust_anchors_lengths[index];
    return iotcsp_trust_anchors[index];
}

iotcs_result iotcs_port_crypto_serialize_private_key(unsigned char *buf, size_t buf_length,
        size_t *written) {
    if ((size_t) i2d_PrivateKey(tam_rsa, NULL) > buf_length) {
        return IOTCS_RESULT_FAIL;
    }

    if ((*written = (size_t) i2d_PrivateKey(tam_rsa, &buf)) < 0) {
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_port_crypto_deserialize_private_key(unsigned char *buf, size_t buf_length) {
    if (NULL == (tam_rsa = d2i_AutoPrivateKey(NULL, (const unsigned char **) &buf, buf_length))) {
        return IOTCS_RESULT_FAIL;
    }
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_port_tam_credentials_load(const char *client_id, char *endpoint_id, size_t *endpoint_length,
        unsigned char *public_out, size_t *public_length) {
    FILE *file = NULL;
    BIO* bio = NULL;
    EVP_PKEY *pkey = NULL;
    RSA *rsa = NULL;
    iotcs_result result = IOTCS_RESULT_OK;
    char value[IOTCSP_MAX_PROP_SIZE];
    char line[IOTCSP_MAX_PROP_SIZE];

    if ((client_id == NULL) || (endpoint_id == NULL) || (public_out == NULL) || (public_length == NULL || endpoint_length == NULL)) {
        LOG_ERRS("args can't be NULL");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    if (tam_assets_file) {
        util_free(tam_assets_file);
    }
    int tam_assets_file_len = strlen(client_id) + strlen(".bin") + 1;
    tam_assets_file = util_malloc(tam_assets_file_len);
    if (tam_assets_file == NULL) {
        LOG_CRITS("Failed to allocate tam_assets_file name");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }
    if (0 > util_safe_snprintf(tam_assets_file, tam_assets_file_len, "%s.bin", client_id)) {
        LOG_ERRS("util_safe_snprintf method failed");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    file = fopen(tam_assets_file, "r");
    if (file == NULL) {
        LOG_WARN("Failed to open assets file for reading: %s", tam_assets_file);
        // that's not an error, we are just no activated yet
        result = IOTCS_RESULT_OK;
        goto done;
    }

    fgets(line, IOTCSP_MAX_PROP_SIZE, file);
    if (sscanf(line, "endpoint.id=%s\n", value) == 0) {
        LOG_ERRS("Failed to read endpoint ID");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    if (strlen(value) > *endpoint_length) {
        LOG_ERRS("Endpoint id has too long length");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    *endpoint_length = strlen(value);
    memcpy(endpoint_id, value, *endpoint_length);

    bio = BIO_new_fp(file, BIO_NOCLOSE);
    if (bio == NULL) {
        LOG_ERR("Failed to map assets file on BIO: %s", tam_assets_file);
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, (char *) ts_password);
    if (pkey == NULL) {
        LOG_ERRS("Failed to read private key");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (tam_rsa) {
        EVP_PKEY_free(tam_rsa);
        tam_rsa = NULL;
    }
    tam_rsa = pkey;
    pkey = NULL;
    rsa = EVP_PKEY_get1_RSA(tam_rsa);
    if (rsa == NULL) {
        LOG_ERRS("Failed to extract private key");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if ((size_t) i2d_RSA_PUBKEY(rsa, NULL) >= *public_length) {
        LOG_ERRS("Length of the public key is greater than library can process");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    *public_length = i2d_RSA_PUBKEY(rsa, &public_out);

done:
    if (result != IOTCS_RESULT_OK) {
        if (pkey != NULL) {
            EVP_PKEY_free(pkey);
        }
        if (tam_rsa) {
            EVP_PKEY_free(tam_rsa);
            tam_rsa = NULL;
        }
    }
    if (bio != NULL) {
        BIO_free_all(bio);
    }
    if (rsa) {
        RSA_free(rsa);
    }
    if (file != NULL) {
        fclose(file);
    }
    return result;
}

iotcs_result iotcs_port_tam_credentials_store(void) {
    iotcs_result result = IOTCS_RESULT_OK;
    FILE *file = NULL;
    BIO* bio = NULL;
    const char *endpoint_id = tam_get_endpoint_id();

    file = fopen(tam_assets_file, "w");
    if (file == NULL) {
        LOG_ERR("Failed to open assets file for writing: %s", tam_assets_file);
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (fprintf(file, "endpoint.id=%s\n", endpoint_id) <= 0) {
        LOG_ERRS("Failed to write endpoint ID");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    bio = BIO_new_fp(file, BIO_NOCLOSE);
    if (bio == NULL) {
        LOG_ERR("Failed to map assets file on BIO: %s", tam_assets_file);
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (PEM_write_bio_PKCS8PrivateKey(bio, tam_rsa, EVP_aes_128_cbc(), NULL, 0, NULL, (char*) ts_password) <= 0) {
        LOG_ERRS("Failed to write private key");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
done:
    if (bio != NULL) {
        BIO_free(bio);
    }
    if (file != NULL) {
        fclose(file);
    }
    return result;
}

iotcs_result iotcs_port_tam_unified_credentials_encrypt(unsigned char *out, size_t* out_len,
        const unsigned char* in, size_t in_len, const unsigned char* iv) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    unsigned char key[PASSWORD_KEY_LENGTH];
    EVP_CIPHER_CTX* ctx = NULL;
    int written = 0;
    int block_size_align;

    /*get the key, from the password*/
    if (!PKCS5_PBKDF2_HMAC_SHA1(ts_password, strlen(ts_password), iv, TAM_AES_CBC_128_IV_LENGTH, 10000, PASSWORD_KEY_LENGTH, key)) {
        LOG_ERRS("PKCS5_PBKDF2_HMAC_SHA1 failed");
        return IOTCS_RESULT_FAIL;
    }

    if (NULL == (ctx = EVP_CIPHER_CTX_new())) {
        LOG_ERRS("EVP_CIPHER_CTX_new failed");
        goto error;
    }

    if (!EVP_EncryptInit(ctx, EVP_aes_128_cbc(), key, iv)) {
        LOG_ERRS("Failed to init AES");
        goto error;
    }

    block_size_align = EVP_CIPHER_CTX_block_size(ctx) - 1;
    if (((in_len + block_size_align) & block_size_align) > *out_len) {
        LOG_ERRS("Not enough space to encode");
        goto error;
    }

    if (!EVP_EncryptUpdate(ctx, (unsigned char *) out, &written, (const unsigned char *) in, in_len)) {
        LOG_ERRS("Failed to perform AES update");
        goto error;
    }
    *out_len = written;
    out += written;

    if (!EVP_EncryptFinal(ctx, (unsigned char *) out, &written)) {
        LOG_ERRS("Failed to finalize AES");
        goto error;
    }
    *out_len += written;

    result = IOTCS_RESULT_OK;

error:
    if (ctx) {
        EVP_CIPHER_CTX_free(ctx);
    }

    return result;
}

iotcs_result iotcs_port_tam_unified_credentials_decrypt(unsigned char *out, size_t* out_len,
        const unsigned char* in, size_t in_len, const unsigned char* iv) {
    int out_total, out_length;
    unsigned char key[PASSWORD_KEY_LENGTH];
    iotcs_result result = IOTCS_RESULT_FAIL;
    EVP_CIPHER_CTX* ctx = NULL;

    /*get the key, from the password*/
    if (!PKCS5_PBKDF2_HMAC_SHA1(ts_password, strlen(ts_password), iv, TAM_AES_CBC_128_IV_LENGTH, 10000, PASSWORD_KEY_LENGTH, key)) {
        LOG_ERRS("PKCS5_PBKDF2_HMAC_SHA1 failed");
        return IOTCS_RESULT_FAIL;
    }

    if (NULL == (ctx = EVP_CIPHER_CTX_new())) {
        LOG_ERRS("EVP_CIPHER_CTX_new failed");
        goto error;
    }

    if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {
        LOG_ERRS("EVP_DecryptInit_ex failed");
        goto error;
    }

    if (!EVP_DecryptUpdate(ctx, out, &out_length, in, in_len)) {
        LOG_ERRS("EVP_DecryptUpdate failed");
        goto error;
    }

    out_total = out_length;
    out += out_length;

    if (!EVP_DecryptFinal_ex(ctx, out, &out_length)) {
        LOG_ERRS("EVP_DecryptFinal_ex failed");
        goto error;
    }

    out_total += out_length;

    *out_len = out_total;

    result = IOTCS_RESULT_OK;

error:

    if (ctx) {
        EVP_CIPHER_CTX_free(ctx);
    }

    return result;
}

iotcs_result iotcs_port_tam_generate_keypair(size_t key_size, unsigned char* public_out, size_t* public_length) {
    BIGNUM* bne;
    RSA *rsa;

    if (!public_length || !public_out) {
        LOG_ERRS("Invalid parameters in iotcs_port_tam_credentials_reset method.");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    //generation using openssl
    bne = BN_new();
    if (bne == NULL) {
        LOG_CRITS("Failed to allocate big number");
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }
    BN_set_word(bne, RSA_3);
    rsa = RSA_new();
    if (rsa == NULL) {
        LOG_CRITS("Failed to allocate RSA object");
        BN_free(bne);
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }
    //public key generation
    if (!RSA_generate_key_ex(rsa, key_size, bne, NULL)) {
        LOG_ERRS("Failed to generate RSA key pair");
        BN_free(bne);
        RSA_free(rsa);
        tam_rsa = NULL;
        return IOTCS_RESULT_FAIL;
    }

    if ((size_t) i2d_RSA_PUBKEY(rsa, NULL) >= *public_length) {
        LOG_ERRS("Length of the public key is greater than library can process");
        BN_free(bne);
        RSA_free(rsa);
        tam_rsa = NULL;
        return IOTCS_RESULT_FAIL;
    }

    *public_length = i2d_RSA_PUBKEY(rsa, &public_out);

    if (tam_rsa) {
        EVP_PKEY_free(tam_rsa);
        tam_rsa = NULL;
    }

    tam_rsa = EVP_PKEY_new();
    if (rsa == NULL) {
        LOG_CRITS("Failed to allocate PKEY object");
        BN_free(bne);
        RSA_free(rsa);
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }

    if (!EVP_PKEY_assign_RSA(tam_rsa, rsa)) {
        LOG_ERRS("Failed to assign PKEY object");
        BN_free(bne);
        RSA_free(rsa);
        return IOTCS_RESULT_FAIL;
    }

    BN_free(bne);

    return IOTCS_RESULT_OK;
}

size_t iotcs_port_tam_sign_with_private_key(IOTCS_TYPE_SHA type, const unsigned char* input, size_t input_length, char* signature, size_t signature_maxsize) {
    const EVP_MD* md;
    unsigned int result = 0;
#ifdef OPENSSL_1_0
    EVP_MD_CTX ctx;
    EVP_MD_CTX_init(&ctx);
#else
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    EVP_MD_CTX_init(ctx);
#endif
    int expectedLength;

    if (type == IOTCS_TYPE_SHA1) {
        md = EVP_sha1();
        expectedLength = SHA_DIGEST_LENGTH;
    } else if (type == IOTCS_TYPE_SHA256) {
        md = EVP_sha256();
        expectedLength = SHA256_DIGEST_LENGTH;
    } else if (type == IOTCS_TYPE_SHA512) {
        md = EVP_sha512();
        expectedLength = SHA512_DIGEST_LENGTH;
    } else {
        LOG_ERR("Unknown hash algorithm: %d", type);
        goto end;
    }

    if (signature_maxsize < expectedLength) {
        LOG_ERRS("Unexpected digest length");
        return 0;
    }

#ifdef OPENSSL_1_0
    if (EVP_SignInit(&ctx, md) != 1) {
#else
    if (EVP_SignInit(ctx, md) != 1) {
#endif
        LOG_ERRS("Failed to initialize signature object");
        goto end;
    }

#ifdef OPENSSL_1_0
    if (EVP_SignUpdate(&ctx, input, input_length) != 1) {
#else
    if (EVP_SignUpdate(ctx, input, input_length) != 1) {
#endif
        LOG_ERRS("Failed to perform signature update");
        goto end;
    }

#ifdef OPENSSL_1_0
    if (EVP_SignFinal(&ctx, (unsigned char*) signature, &result, tam_rsa) != 1) {
#else
    if (EVP_SignFinal(ctx, (unsigned char*) signature, &result, tam_rsa) != 1) {
#endif
        LOG_ERRS("Failed to perform signature finalization");
        result = 0;
        goto end;
    }

    if (result > signature_maxsize) {
        LOG_ERR("Buffer overflow: max value - %d returned - %d", signature_maxsize, result);
        result = 0;
    }

end:
#ifdef OPENSSL_1_0
    EVP_MD_CTX_cleanup(&ctx);
#else
    EVP_MD_CTX_reset(ctx);
    if (ctx) {
        EVP_MD_CTX_free(ctx);
    }
#endif
    return result;
}
