/*
 * 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 <mbedtls/cipher.h>
#include <mbedtls/sha256.h>
#include <mbedtls/md5.h>
#include <mbedtls/base64.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/rsa.h>
#include <mbedtls/pk.h>
#include <mbedtls/pem.h>
#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/pkcs5.h>

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

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

#define MAX_PEM_BUFFER_SIZE 4096
#define TAM_MAX_PROP_SIZE 256
#define PASSWORD_KEY_LENGTH 16
#define DIGEST_LENGTH 32

#ifdef IOTCS_MBED_KEYPAIR_FROM_FILE
#ifndef IOTCS_MBED_KEYPAIR_FILENAME
#define IOTCS_MBED_KEYPAIR_FILENAME iotcs_key.der
#endif //IOTCS_MBED_KEYPAIR_FILENAME
#endif //IOTCS_MBED_KEYPAIR_FROM_FILE

// TODO: get prefix from IOTCS_MBED_TS_PATH
char* g_tam_path_prefix = NULL;

iotcs_result iotcs_port_tam_init(void) {
    // TODO: remove this calculations when old TAM is removed
    mbed::FileBase *ptr = mbed::FileBase::get(0);
    int len = 0;
    if (ptr == NULL) {
        LOG_ERRS("Sd card is not mounted.");
        return IOTCS_RESULT_FAIL;
    }
    
    // + 2 = two slash, + 1 = \0
    len = sizeof (char) * (strlen(ptr->getName()) + 2 + 1);
    g_tam_path_prefix = (char*) util_malloc(len);
    if (NULL == g_tam_path_prefix) {
        return IOTCS_RESULT_OUT_OF_MEMORY;
    }
    
    if (0 > util_safe_snprintf(g_tam_path_prefix, len, "/%s/", ptr->getName())) {
        LOG_ERRS("util_safe_snprintf failed");
        return IOTCS_RESULT_FAIL;
    }
    
    LOG_DBG("g_tam_path_prefix=%s", g_tam_path_prefix);
    return IOTCS_RESULT_OK;
}

//extern "C" {

static mbedtls_pk_context *tam_rsa = NULL;
static char* tam_assets_file = NULL;

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];

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;
}

void iotcs_port_tam_finalize(void) {
    mbedtls_pk_free(tam_rsa);
    util_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++) {
        util_free(iotcsp_trust_anchors[i]);
        iotcsp_trust_anchors[i] = NULL;
    }
    util_free(ts_password);
    ts_password = NULL;
}

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) {
    mbedtls_md_type_t md;
    int expectedLength;
    int ret;

    if (type == IOTCS_TYPE_SHA1) {
        md = MBEDTLS_MD_SHA1;
        expectedLength = 20;
    } else if (type == IOTCS_TYPE_SHA256) {
        md = MBEDTLS_MD_SHA256;
        expectedLength = 32;
    } else if (type == IOTCS_TYPE_SHA512) {
        md = MBEDTLS_MD_SHA512;
        expectedLength = 64;
    } else {
        LOG_ERRS("Unexpected SHA type");
        return 0;
    }

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

    if ((ret = mbedtls_md_hmac(mbedtls_md_info_from_type(md), key, key_length, content, content_length, hash)) != 0) {
        LOG_ERR("mbedtls_md_hmac failed with code: %d", ret);
        return 0;
    }
    return expectedLength;
}

iotcs_result iotcs_port_md5(unsigned char *output_buffer, iotcs_port_get_next_string_func func, void *data) {
    mbedtls_md5_context ctx;
    const char* string;

    mbedtls_md5_init(&ctx);
    mbedtls_md5_starts(&ctx);

    while (NULL != (string = func(data))) {
        mbedtls_md5_update(&ctx, (const unsigned char *) string, strlen(string));
    }

    mbedtls_md5_finish(&ctx, output_buffer);
    mbedtls_md5_free(&ctx);

    return IOTCS_RESULT_OK;
}

static iotcs_result read_pem(FILE *file, unsigned char **pem_buffer, size_t *pem_size) {
    iotcs_result result = IOTCS_RESULT_OK;
    size_t size;
    int ret;
    long int start_pos = ftell(file);
    long int end_pos = fseek(file, 0, SEEK_END);
    if (end_pos != 0) {
        LOG_ERRS("Failed to seek in the file");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    end_pos = ftell(file);
    size = (end_pos - start_pos);
    *pem_buffer = (unsigned char *)util_malloc(size + 1);
    if (*pem_buffer == NULL) {
        LOG_CRITS("Failed to allocate PEM buffer");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }
    (*pem_buffer)[size] = 0;
    fseek(file, start_pos, SEEK_SET);
    ret = fread(*pem_buffer, 1, size, file);
    if (ret != (int) size) {
        LOG_ERRS("Failed to load PEM buffer");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    *pem_size = size;
done:
    if (result != IOTCS_RESULT_OK) {
        util_free(*pem_buffer);
        *pem_buffer = NULL;
    }
    return result;
}

static int _pbkdf2(const char *password, unsigned char *key) {
    int ret = -1;
    mbedtls_md_context_t md_ctx;
    mbedtls_md_init(&md_ctx);
    if (!(ret = mbedtls_md_setup(&md_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1))) {
        ret = mbedtls_pkcs5_pbkdf2_hmac(&md_ctx, (const unsigned char *) password, strlen(password), NULL, 0, 1, 16, key);
    }
    mbedtls_md_free(&md_ctx);
    return ret;
}

static iotcs_result derive_key(unsigned char *key) {
    if (ts_password == NULL) {
        LOG_ERRS("No TAM password defined");
        return IOTCS_RESULT_FAIL;
    }

    // get the key, from the password
    if (_pbkdf2(ts_password, key)) {
        LOG_ERRS("PKCS5_PBKDF2_HMAC_SHA1 failed");
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_port_crypto_serialize_private_key(unsigned char *buf, size_t buf_length, size_t *written) {
    int rv;

    if (tam_rsa == NULL) {
        LOG_ERRS("private key isn't initialized");
        return IOTCS_RESULT_FAIL;
    }

    /* data is written at the end of the buffer */
    if (0 > (rv = mbedtls_pk_write_key_der(tam_rsa, buf, buf_length))) {
        LOG_ERR("mbedtls_pk_write_key_der failed 0x%x", -rv);
        return IOTCS_RESULT_FAIL;
    }

    /* move written data to the beginning of the buffer */
    memmove(buf, buf + (buf_length - rv), rv);
    *written = rv;
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_port_crypto_deserialize_private_key(unsigned char *buf, size_t buf_length) {
    int rv;

    if (tam_rsa != NULL) {
        LOG_ERRS("private key is already initialized");
        return IOTCS_RESULT_FAIL;
    }

    CHECK_OOM(tam_rsa = (mbedtls_pk_context*) util_malloc(sizeof (mbedtls_pk_context)));

    mbedtls_pk_init(tam_rsa);

    /* data is written at the end of the buffer */
    if (0 > (rv = mbedtls_pk_parse_key(tam_rsa, buf, buf_length, NULL, 0))) {
        LOG_ERR("mbedtls_pk_parse_key failed 0x%x", -rv);
        mbedtls_pk_free(tam_rsa);
        util_free(tam_rsa);
        tam_rsa = NULL;
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;

}

static iotcs_result apply_aes_cbc(mbedtls_operation_t op, unsigned char *out, size_t* out_len,
                                  const unsigned char* in, size_t in_len, const unsigned char* iv) {
    iotcs_result rv = IOTCS_RESULT_FAIL;
    mbedtls_cipher_context_t dec;
    mbedtls_md_context_t md_ctx;
    unsigned char key[PASSWORD_KEY_LENGTH];
    unsigned int block_size_align;

    mbedtls_cipher_init(&dec);
    mbedtls_md_init(&md_ctx);

    GOTO_ERR(mbedtls_cipher_setup(&dec, mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_CBC)));
    block_size_align = mbedtls_cipher_get_block_size(&dec) - 1;

    GOTO_ERR(((in_len + block_size_align) & (block_size_align)) > *out_len);

    /* get the key, from the password */
    GOTO_ERR(mbedtls_md_setup(&md_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1));
    GOTO_ERR(ts_password == NULL);
    GOTO_ERR(mbedtls_pkcs5_pbkdf2_hmac(&md_ctx, (const unsigned char *) ts_password, strlen(ts_password), iv, TAM_AES_CBC_128_IV_LENGTH, 10000, 16, key));

    GOTO_ERR(mbedtls_cipher_setkey(&dec, key, 128, op));
    GOTO_ERR(mbedtls_cipher_crypt(&dec, iv, TAM_AES_CBC_128_IV_LENGTH, in, in_len, out, out_len));

    rv = IOTCS_RESULT_OK;

error:
    mbedtls_cipher_free(&dec);
    mbedtls_md_free(&md_ctx);

    return rv;
}

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) {
    return apply_aes_cbc(MBEDTLS_DECRYPT, out, out_len, in, in_len, iv);
}

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) {
    return apply_aes_cbc(MBEDTLS_ENCRYPT, out, out_len, in, in_len, iv);
}

iotcs_result iotcs_port_tam_decode_shared_secret(char* encrypted_shared_secret, char *shared_secret, int shared_secret_length) {
    iotcs_result result = IOTCS_RESULT_OK;
    mbedtls_cipher_context_t dec;
    int dec_initialized = 0, ret;
    unsigned char key[PASSWORD_KEY_LENGTH];
    unsigned char iv[16];
    size_t data_length;

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

    // encrypted_shared_secret is base64 encoded
    if (mbedtls_base64_decode((unsigned char *) shared_secret, shared_secret_length, &data_length, (const unsigned char *) encrypted_shared_secret, strlen(encrypted_shared_secret))) {
        LOG_ERRS("Failed to decode Base64");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if ((data_length == 0) || ((int) data_length >= shared_secret_length)) {
        LOG_ERRS("Failed to decode Base64");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    memcpy(encrypted_shared_secret, shared_secret, data_length);
    encrypted_shared_secret[data_length] = '\0';
    // we use a zero IV
    memset(iv, 0, sizeof (iv));
    mbedtls_cipher_init(&dec);
    dec_initialized = 1;
    if (mbedtls_cipher_setup(&dec, mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_CBC))) {
        LOG_ERRS("Failed to init AES");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (mbedtls_cipher_setkey(&dec, key, 128, MBEDTLS_DECRYPT)) {
        LOG_ERRS("Failed to init AES");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (mbedtls_cipher_set_iv(&dec, iv, sizeof (iv))) {
        LOG_ERRS("Failed to init AES");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (mbedtls_cipher_reset(&dec)) {
        LOG_ERRS("Failed to init AES");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    size_t outlen;
    if (mbedtls_cipher_update(&dec, (const unsigned char *) encrypted_shared_secret, data_length, (unsigned char *) shared_secret, &outlen)) {
        LOG_ERRS("Failed to perform AES update");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    ret = mbedtls_cipher_finish(&dec, (unsigned char *) shared_secret + outlen, &outlen);
    if (ret) {
        LOG_ERR("Failed to finalize AES %d", ret);
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    // the shared secret is a string, it must be NUL-terminated
    (shared_secret)[outlen] = '\0';

    if ((int) (outlen + 1) > shared_secret_length) {
        LOG_ERRS("Shared secret too long");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

done:
    if (dec_initialized) {
        mbedtls_cipher_free(&dec);
    }
    return result;
}

iotcs_result iotcs_port_tam_load_trust_store_achors(const unsigned char* trust_store_anchors, int length) {
    iotcs_result rv = IOTCS_RESULT_FAIL;
    mbedtls_x509_crt cacert, *cert;
    int i = 0;

    mbedtls_x509_crt_init(&cacert);
    GOTO_ERR_MSG(mbedtls_x509_crt_parse_der(&cacert, trust_store_anchors, length), "Failed to parse DER");

    cert = &cacert;
    do {
        GOTO_ERR_MSG(i >= IOTCSP_MAX_TRUST_ANCHORS, "Too many anchors found");

        iotcsp_trust_anchors_lengths[i] = cert->raw.len;
        if (iotcsp_trust_anchors_lengths[i] > 0) {
            iotcsp_trust_anchors[i] = (char *)util_malloc(iotcsp_trust_anchors_lengths[i]);
            if (iotcsp_trust_anchors[i] == NULL) {
                LOG_CRIT("Failed to allocate trust_anchors[%d]", i);
                rv = IOTCS_RESULT_OUT_OF_MEMORY;
                goto error;
            }
            memcpy(iotcsp_trust_anchors[i], cert->raw.p, iotcsp_trust_anchors_lengths[i]);
            i++;
        }
        cert = cert->next;
    } while (cert != NULL);
    iotcsp_trust_anchors_count = i;
    rv = IOTCS_RESULT_OK;
error:
    mbedtls_x509_crt_free(&cacert);
    return rv;
}

iotcs_result iotcs_port_tam_check_file_signature(FILE *file_ptr, size_t signed_data_length, const char* signature) {
    iotcs_result result = IOTCS_RESULT_OK;
    unsigned char key[PASSWORD_KEY_LENGTH], hash[DIGEST_LENGTH];
    unsigned char *buffer = NULL;
    int ret;
    char *hash_as_hex = NULL;
    mbedtls_md_context_t ctx;

    mbedtls_md_init(&ctx);
    if ((ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1)) != 0) {
        LOG_ERR("mbedtls_md_setup failed with code: %d", ret);
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

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

    if (signed_data_length == 0) {
        fseek(file_ptr, 0, SEEK_END);
        signed_data_length = ftell(file_ptr);
        rewind(file_ptr);
    }
    buffer = (unsigned char *)util_malloc(signed_data_length);

    if (buffer == NULL) {
        LOG_CRITS("Failed to allocate buffer");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }
    fread(buffer, sizeof (unsigned char), signed_data_length, file_ptr);
    if ((ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, PASSWORD_KEY_LENGTH, buffer, signed_data_length, hash)) != 0) {
        LOG_ERR("mbedtls_md_hmac failed with code: %d", ret);
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if (NULL == (hash_as_hex = (char *)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:
    util_free(hash_as_hex);
    util_free(buffer);
    mbedtls_md_free(&ctx);
    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_tam_load_trust_store(FILE *trust_store_file) {
    unsigned char *pem_buffer = NULL;
    const char* fixed_trust_store_path = NULL;
    size_t pem_size;
    iotcs_result result = IOTCS_RESULT_OK;
    mbedtls_x509_crt cacert;
    int i = 0;
    int ret;
    mbedtls_x509_crt *cert;

    if (NULL == trust_store_file) {
        LOG_ERRS("Mbed-tls does not have a built-in ca certificate store");
        return IOTCS_RESULT_FAIL;
    }

    mbedtls_x509_crt_init(&cacert);

    result = read_pem(trust_store_file, &pem_buffer, &pem_size);
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to read PEM buffer");
        goto done;
    }

    ret = mbedtls_x509_crt_parse(&cacert, pem_buffer, pem_size + 1);
    if (ret != 0) {
        LOG_ERRS("Failed to parse");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    cert = &cacert;
    do {
        if (i >= IOTCSP_MAX_TRUST_ANCHORS) {
            LOG_ERR("Too much trust anchors found: %d", (i + 1));
            result = IOTCS_RESULT_FAIL;
            goto done;
        }

        iotcsp_trust_anchors_lengths[i] = cert->raw.len;
        if (iotcsp_trust_anchors_lengths[i] > 0) {
            iotcsp_trust_anchors[i] = (char *)util_malloc(iotcsp_trust_anchors_lengths[i]);
            if (iotcsp_trust_anchors[i] == NULL) {
                LOG_CRIT("Failed to allocate trust_anchors[%d]", i);
                result = IOTCS_RESULT_OUT_OF_MEMORY;
                goto done;
            }
            memcpy(iotcsp_trust_anchors[i], cert->raw.p, iotcsp_trust_anchors_lengths[i]);
            i++;
        }
        cert = cert->next;
    } while (cert != NULL);
    iotcsp_trust_anchors_count = i;
done:
    util_free((void*) fixed_trust_store_path);
    util_free(pem_buffer);
    mbedtls_x509_crt_free(&cacert);
    return result;
}

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;
    unsigned char *pem_buffer = NULL;
    size_t pem_size;
    size_t max_len;
    int ret;
    int tam_assets_file_len;
    int key_len;
    unsigned char *tmp = NULL;
    iotcs_result result = IOTCS_RESULT_OK;
    char value[256];
    int tmp_len;
    if ((client_id == NULL) || (endpoint_id == NULL) || (endpoint_length == NULL) || (public_out == NULL) || (public_length == NULL)) {
        LOG_ERRS("args can't be NULL");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    tam_assets_file_len = strlen(client_id) + strlen(g_tam_path_prefix) + strlen(".bin") + 1;
    tam_assets_file = (char *)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%s.bin", g_tam_path_prefix, client_id)) {
        LOG_ERRS("util_safe_snprintf function 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;
    }
    if (fscanf(file, "endpoint.id=%s", value) == 0) {
        LOG_ERRS("Failed to read endpoint ID");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    tmp_len = strlen(value);
    if ((int) *endpoint_length < tmp_len) {
        LOG_ERRS("Endpoint id is too long");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    memcpy(endpoint_id, value, tmp_len);
    *endpoint_length = tmp_len;

    result = read_pem(file, &pem_buffer, &pem_size);
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to read PEM buffer");
        goto done;
    }

    size_t len;
    mbedtls_pem_context pem;
    mbedtls_pem_init(&pem);
    ret = mbedtls_pem_read_buffer(&pem,
                                      "-----BEGIN RSA PRIVATE KEY-----",
                                      "-----END RSA PRIVATE KEY-----",
                                      pem_buffer, NULL, 0, &len);
    if (ret != 0) {
        LOG_ERRS("Failed to load first PEM");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    tam_rsa = (mbedtls_pk_context*) util_malloc(sizeof (mbedtls_pk_context));
    if (tam_rsa == NULL) {
        LOG_CRITS("Failed to allocate RSA object");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }
    mbedtls_pk_init(tam_rsa);
    if ((ret = mbedtls_pk_setup(tam_rsa, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) != 0) {
        LOG_ERRS("Failed to setup RSA object");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    if ((ret = mbedtls_pk_parse_key(tam_rsa, pem.buf, pem.buflen, NULL, 0)) != 0) {
        LOG_ERRS("Failed to fill RSA private key object");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
    mbedtls_pem_free(&pem);

    max_len = 2 * mbedtls_pk_get_len(tam_rsa);
    tmp = (unsigned char *)util_malloc(max_len);
    if (tmp == NULL) {
        LOG_CRITS("Failed to allocate tmp");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }
    unsigned char *key;
    key_len = mbedtls_pk_write_pubkey_der(tam_rsa, tmp, max_len);
    key = tmp + max_len - key_len;

    if ((int) (*public_length) < key_len) {
        LOG_ERRS("Public key id is too long");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }

    memcpy(public_out, key, key_len);
    *public_length = key_len;
done:
    util_free(tmp);
    util_free(pem_buffer);
    if (result != IOTCS_RESULT_OK && tam_rsa != NULL) {
        mbedtls_pk_free(tam_rsa);
        util_free(tam_rsa);
        tam_rsa = NULL;
    }
    if (file != NULL) {
        fclose(file);
    }
    return result;
}

iotcs_result iotcs_port_tam_credentials_store(void) {
    char *pem_buffer = NULL;
    iotcs_result result = IOTCS_RESULT_OK;
    FILE *file = NULL;
    const char *endpoint_id = tam_get_endpoint_id();
    int ret;

    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;
    }
    pem_buffer = (char *)util_malloc(MAX_PEM_BUFFER_SIZE);
    if (pem_buffer == NULL) {
        LOG_CRITS("Failed to allocate pem_buffer");
        result = IOTCS_RESULT_OUT_OF_MEMORY;
        goto done;
    }
    ret = mbedtls_pk_write_key_pem(tam_rsa, (unsigned char *) pem_buffer, MAX_PEM_BUFFER_SIZE);
    if (ret != 0 || fprintf(file, "%s", pem_buffer) <= 0) {
        LOG_ERRS("Failed to write private key");
        result = IOTCS_RESULT_FAIL;
        goto done;
    }
done:
    util_free(pem_buffer);
    if (file != NULL) {
        fclose(file);
    }
    return result;
}

static mbedtls_entropy_context entropy;
static mbedtls_ctr_drbg_context ctr_drbg;
static int rng_initialized;

static int init_rng(void) {
    if (rng_initialized) {
        return 0;
    } else {
        const char *pers = "SEED_RSA_KEYGEN";
        mbedtls_ctr_drbg_init(&ctr_drbg);
        mbedtls_entropy_init(&entropy);
        return mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen(pers));
    }
}

#ifdef IOTCS_MBED_KEYPAIR_FROM_FILE

static const char *fixup_path(const char* path) {
    if (strncmp(g_tam_path_prefix, path, strlen(g_tam_path_prefix)) == 0) {
        return util_safe_strcpy(path);
    }
    if (strncmp("./", path, strlen("./")) == 0) {
        path += 2;
    }
    size_t len = strlen(path) + strlen(g_tam_path_prefix) + 1;
    char *newpath = (char*) util_malloc(len);
    if (newpath != NULL) {
        if (0 > util_safe_snprintf(newpath, len, "%s%s", g_tam_path_prefix, path)) {
            util_free(newpath);
            return NULL;
        }
    }
    return newpath;
}

iotcs_result parse_keypair_from_file(FILE *file) {
    unsigned char *pem_buffer = NULL;
    size_t pem_size;
    iotcs_result result = IOTCS_RESULT_OK;
    result = read_pem(file, &pem_buffer, &pem_size);
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to read PEM buffer");
        goto done;
    }

    result = iotcs_port_crypto_deserialize_private_key(pem_buffer, pem_size);
done:
    util_free(pem_buffer);
    if (result != IOTCS_RESULT_OK && tam_rsa != NULL) {
        mbedtls_pk_free(tam_rsa);
        util_free(tam_rsa);
        tam_rsa = NULL;
    }
    return result;
}

static iotcs_result read_key_pair() {
    const char *fixed_store_path = NULL;
    FILE *prop_file;
    iotcs_result result = IOTCS_RESULT_OK;

    fixed_store_path = fixup_path(UTIL_STRINGIFY(IOTCS_MBED_KEYPAIR_FILENAME));
    if (fixed_store_path != NULL) {
        LOG_INFO("Try to read key pair from file: %s", fixed_store_path);
        prop_file = fopen(fixed_store_path, "rb");
        if (prop_file != NULL) {
            result = parse_keypair_from_file(prop_file);
            fclose(prop_file);
            if (result != IOTCS_RESULT_OK) {
                LOG_ERRS("Failed to parse keys");
            }
        } else {
            LOG_ERR("Failed to open file: %s", fixed_store_path);
            result = IOTCS_RESULT_FAIL;
        }
        util_free((void*) fixed_store_path);
    } else {
        result = IOTCS_RESULT_FAIL;
    }

    return result;
}

static void store_key_pair() {
    const char *fixed_store_path = NULL;
    FILE *prop_file;
    char *pem_buffer;

    fixed_store_path = fixup_path(UTIL_STRINGIFY(IOTCS_MBED_KEYPAIR_FILENAME));
    if (fixed_store_path != NULL) {
        prop_file = fopen(fixed_store_path, "wb");
        if (prop_file != NULL) {
            pem_buffer = (char*)util_malloc(MAX_PEM_BUFFER_SIZE);
            if (pem_buffer != NULL) {
                size_t written = 0;
                iotcs_port_crypto_serialize_private_key((unsigned char*) pem_buffer, MAX_PEM_BUFFER_SIZE, &written);
                fwrite(pem_buffer, MAX_PEM_BUFFER_SIZE, 1, prop_file);
                fclose(prop_file);
            } else {
                LOG_CRITS("Failed to allocate pem_buffer");
            }
        } else {
            LOG_ERR("Failed to open file with key pair for writing: %s", fixed_store_path);
        }
        util_free((void*) fixed_store_path);
    }
    util_free(pem_buffer);
}
#endif //IOTCS_MBED_KEYPAIR_FROM_FILE

iotcs_result iotcs_port_tam_generate_keypair(size_t key_size, unsigned char* public_out, size_t* public_length) {
    iotcs_result result = IOTCS_RESULT_OK;
    int ret;

    if (tam_rsa != NULL) {
        /* we already have key pair - return public part of it in DER PKCS#1 */
        LOG_INFOS("Use existing RSA Key Pair");
    } else {
        LOG_INFOS("Generate new RSA Key Pair...");

        if ((ret = init_rng())) {
            LOG_ERR("mbedtls_ctr_drbg_seed failed with code %d.", ret);
            result = IOTCS_RESULT_FAIL;
            goto done;
        }
#ifdef IOTCS_MBED_KEYPAIR_FROM_FILE
        if (read_key_pair() != IOTCS_RESULT_OK) {
            LOG_INFOS("Rollback to RSA Key Pair generation...");
#endif //IOTCS_MBED_KEYPAIR_FROM_FILE
            tam_rsa = (mbedtls_pk_context*) util_malloc(sizeof (mbedtls_pk_context));
            if (tam_rsa == NULL) {
                LOG_CRITS("Failed to allocate RSA object");
                result = IOTCS_RESULT_OUT_OF_MEMORY;
                goto done;
            }
            mbedtls_pk_init(tam_rsa);

            if (0 != (ret = mbedtls_pk_setup(tam_rsa, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)))) {
                LOG_ERR("mbedtls_pk_setup failed with code %d.", ret);
                result = IOTCS_RESULT_FAIL;
                goto done;
            }

            if ((ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*tam_rsa), mbedtls_ctr_drbg_random, &ctr_drbg, key_size, 65537)) != 0) {
                LOG_ERR("mbedtls_rsa_gen_key failed with code %d.", ret);
                result = IOTCS_RESULT_FAIL;
                goto done;
            }
            LOG_INFOS("RSA Key Pair generation finished");
#ifdef IOTCS_MBED_KEYPAIR_FROM_FILE
            store_key_pair();
        }
#endif //IOTCS_MBED_KEYPAIR_FROM_FILE
    }
    /* data is written at the end of the buffer */
    if (0 > (ret = mbedtls_pk_write_pubkey_der(tam_rsa, public_out, *public_length))) {
        LOG_ERR("mbedtls_pk_write_key_der failed 0x%x", -ret);
        return IOTCS_RESULT_FAIL;
    }

    /* move written data to the beginning of the buffer */
    memmove(public_out, public_out + (*public_length - ret), ret);
    *public_length = ret;

done:
    if (result != IOTCS_RESULT_OK && tam_rsa != NULL) {
        mbedtls_pk_free(tam_rsa);
        util_free(tam_rsa);
        tam_rsa = NULL;
    }
    return result;
}

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) {
    unsigned char hash[64];
    mbedtls_md_type_t md_type;
    int ret;
    size_t result = 0;
    mbedtls_md_context_t md;
    size_t hash_len;
    (void) signature_maxsize;

    if (type == IOTCS_TYPE_SHA1) {
        md_type = MBEDTLS_MD_SHA1;
        hash_len = 20;
    } else if (type == IOTCS_TYPE_SHA256) {
        md_type = MBEDTLS_MD_SHA256;
        hash_len = 32;
    } else if (type == IOTCS_TYPE_SHA512) {
        md_type = MBEDTLS_MD_SHA512;
        hash_len = 64;
    } else {
        LOG_ERR("Unknown hash algorithm: %d", type);
        return 0;
    }

    if ((ret = init_rng())) {
        LOG_ERR("mbedtls_ctr_drbg_seed failed with code %d.", ret);
        return 0;
    }

    mbedtls_md_init(&md);
    if ((ret = mbedtls_md_init_ctx(&md, mbedtls_md_info_from_type(md_type))) != 0) {
        LOG_ERR("mbedtls_md_init_ctx failed with code %d.", ret);
        return 0;
    }

    if ((ret = mbedtls_md_starts(&md)) != 0) {
        LOG_ERR("mbedtls_md_starts failed with code %d.", ret);
        goto end;
    }

    if ((ret = mbedtls_md_update(&md, input, input_length)) != 0) {
        LOG_ERR("mbedtls_md_update failed with code %d.", ret);
        goto end;
    }

    if ((ret = mbedtls_md_finish(&md, hash)) != 0) {
        LOG_ERR("mbedtls_md_finish failed with code %d.", ret);
        goto end;
    }

    if ((ret = mbedtls_pk_sign(tam_rsa, md_type, hash, hash_len, (unsigned char*) signature, &result, mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) {
        LOG_ERR("mbedtls_pk_sign failed with code %d", ret);
        goto end;
    }
end:
    mbedtls_md_free(&md);
    return result;
}
//}