/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
 *  Copyright (C) 2010-2018 Fox Crypto B.V. <openvpn@fox-it.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file Data Channel Cryptography OpenSSL-specific backend interface
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif

#include "syshead.h"

#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL)

#include "basic.h"
#include "buffer.h"
#include "integer.h"
#include "crypto.h"
#include "crypto_backend.h"
#include "openssl_compat.h"

#include <openssl/des.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>

/*
 * Check for key size creepage.
 */

#if MAX_CIPHER_KEY_LENGTH < EVP_MAX_KEY_LENGTH
#warning Some OpenSSL EVP ciphers now support key lengths greater than MAX_CIPHER_KEY_LENGTH -- consider increasing MAX_CIPHER_KEY_LENGTH
#endif

#if MAX_HMAC_KEY_LENGTH < EVP_MAX_MD_SIZE
#warning Some OpenSSL HMAC message digests now support key lengths greater than MAX_HMAC_KEY_LENGTH -- consider increasing MAX_HMAC_KEY_LENGTH
#endif

#if HAVE_OPENSSL_ENGINE
#include <openssl/engine.h>

static bool engine_initialized = false; /* GLOBAL */

static ENGINE *engine_persist = NULL;   /* GLOBAL */

/* Try to load an engine in a shareable library */
static ENGINE *
try_load_engine(const char *engine)
{
    ENGINE *e = ENGINE_by_id("dynamic");
    if (e)
    {
        if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine, 0)
            || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
        {
            ENGINE_free(e);
            e = NULL;
        }
    }
    return e;
}

static ENGINE *
setup_engine(const char *engine)
{
    ENGINE *e = NULL;

    ENGINE_load_builtin_engines();

    if (engine)
    {
        if (strcmp(engine, "auto") == 0)
        {
            msg(M_INFO, "Initializing OpenSSL auto engine support");
            ENGINE_register_all_complete();
            return NULL;
        }
        if ((e = ENGINE_by_id(engine)) == NULL
            && (e = try_load_engine(engine)) == NULL)
        {
            crypto_msg(M_FATAL, "OpenSSL error: cannot load engine '%s'",
                       engine);
        }

        if (!ENGINE_set_default(e, ENGINE_METHOD_ALL))
        {
            crypto_msg(M_FATAL,
                       "OpenSSL error: ENGINE_set_default failed on engine '%s'",
                       engine);
        }

        msg(M_INFO, "Initializing OpenSSL support for engine '%s'",
            ENGINE_get_id(e));
    }
    return e;
}

#endif /* HAVE_OPENSSL_ENGINE */

void
crypto_init_lib_engine(const char *engine_name)
{
#if HAVE_OPENSSL_ENGINE
    if (!engine_initialized)
    {
        ASSERT(engine_name);
        ASSERT(!engine_persist);
        engine_persist = setup_engine(engine_name);
        engine_initialized = true;
    }
#else  /* if HAVE_OPENSSL_ENGINE */
    msg(M_WARN, "Note: OpenSSL hardware crypto engine functionality is not available");
#endif
}

/*
 *
 * Functions related to the core crypto library
 *
 */

void
crypto_init_lib(void)
{
    /*
     * If you build the OpenSSL library and OpenVPN with
     * CRYPTO_MDEBUG, you will get a listing of OpenSSL
     * memory leaks on program termination.
     */

#ifdef CRYPTO_MDEBUG
    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
#endif
}

void
crypto_uninit_lib(void)
{
#ifdef CRYPTO_MDEBUG
    FILE *fp = fopen("sdlog", "w");
    ASSERT(fp);
    CRYPTO_mem_leaks_fp(fp);
    fclose(fp);
#endif

#if HAVE_OPENSSL_ENGINE
    if (engine_initialized)
    {
        ENGINE_cleanup();
        engine_persist = NULL;
        engine_initialized = false;
    }
#endif
}

void
crypto_clear_error(void)
{
    ERR_clear_error();
}

void
crypto_print_openssl_errors(const unsigned int flags)
{
    size_t err = 0;

    while ((err = ERR_get_error()))
    {
        /* Be more clear about frequently occurring "no shared cipher" error */
        if (ERR_GET_REASON(err) == SSL_R_NO_SHARED_CIPHER)
        {
            msg(D_CRYPT_ERRORS, "TLS error: The server has no TLS ciphersuites "
                "in common with the client. Your --tls-cipher setting might be "
                "too restrictive.");
        }
        else if (ERR_GET_REASON(err) == SSL_R_UNSUPPORTED_PROTOCOL)
        {
            msg(D_CRYPT_ERRORS, "TLS error: Unsupported protocol. This typically "
                 "indicates that client and server have no common TLS version enabled. "
                 "This can be caused by mismatched tls-version-min and tls-version-max "
                 "options on client and server. "
                 "If your OpenVPN client is between v2.3.6 and v2.3.2 try adding "
                 "tls-version-min 1.0 to the client configuration to use TLS 1.0+ "
                 "instead of TLS 1.0 only");
        }
        msg(flags, "OpenSSL: %s", ERR_error_string(err, NULL));
    }
}


/*
 *
 * OpenSSL memory debugging.  If dmalloc debugging is enabled, tell
 * OpenSSL to use our private malloc/realloc/free functions so that
 * we can dispatch them to dmalloc.
 *
 */

#ifdef DMALLOC
static void *
crypto_malloc(size_t size, const char *file, int line)
{
    return dmalloc_malloc(file, line, size, DMALLOC_FUNC_MALLOC, 0, 0);
}

static void *
crypto_realloc(void *ptr, size_t size, const char *file, int line)
{
    return dmalloc_realloc(file, line, ptr, size, DMALLOC_FUNC_REALLOC, 0);
}

static void
crypto_free(void *ptr)
{
    dmalloc_free(__FILE__, __LINE__, ptr, DMALLOC_FUNC_FREE);
}

void
crypto_init_dmalloc(void)
{
    CRYPTO_set_mem_ex_functions(crypto_malloc,
                                crypto_realloc,
                                crypto_free);
}
#endif /* DMALLOC */

const cipher_name_pair cipher_name_translation_table[] = {
    { "AES-128-GCM", "id-aes128-GCM" },
    { "AES-192-GCM", "id-aes192-GCM" },
    { "AES-256-GCM", "id-aes256-GCM" },
};
const size_t cipher_name_translation_table_count =
    sizeof(cipher_name_translation_table) / sizeof(*cipher_name_translation_table);


static int
cipher_name_cmp(const void *a, const void *b)
{
    const EVP_CIPHER *const *cipher_a = a;
    const EVP_CIPHER *const *cipher_b = b;

    const char *cipher_name_a =
        translate_cipher_name_to_openvpn(EVP_CIPHER_name(*cipher_a));
    const char *cipher_name_b =
        translate_cipher_name_to_openvpn(EVP_CIPHER_name(*cipher_b));

    return strcmp(cipher_name_a, cipher_name_b);
}

static void
print_cipher(const EVP_CIPHER *cipher)
{
    const char *var_key_size =
        (EVP_CIPHER_flags(cipher) & EVP_CIPH_VARIABLE_LENGTH) ?
        " by default" : "";
    const char *ssl_only = cipher_kt_mode_cbc(cipher) ?
                           "" : ", TLS client/server mode only";

    printf("%s  (%d bit key%s, %d bit block%s)\n",
           translate_cipher_name_to_openvpn(EVP_CIPHER_name(cipher)),
           EVP_CIPHER_key_length(cipher) * 8, var_key_size,
           cipher_kt_block_size(cipher) * 8, ssl_only);
}

void
show_available_ciphers(void)
{
    int nid;
    size_t i;

    /* If we ever exceed this, we must be more selective */
    const EVP_CIPHER *cipher_list[1000];
    size_t num_ciphers = 0;
#ifndef ENABLE_SMALL
    printf("The following ciphers and cipher modes are available for use\n"
           "with " PACKAGE_NAME ".  Each cipher shown below may be use as a\n"
           "parameter to the --cipher option.  The default key size is\n"
           "shown as well as whether or not it can be changed with the\n"
           "--keysize directive.  Using a CBC or GCM mode is recommended.\n"
           "In static key mode only CBC mode is allowed.\n\n");
#endif

    for (nid = 0; nid < 10000; ++nid)
    {
        const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);
        if (cipher && (cipher_kt_mode_cbc(cipher)
#ifdef ENABLE_OFB_CFB_MODE
                       || cipher_kt_mode_ofb_cfb(cipher)
#endif
#ifdef HAVE_AEAD_CIPHER_MODES
                       || cipher_kt_mode_aead(cipher)
#endif
                       ))
        {
            cipher_list[num_ciphers++] = cipher;
        }
        if (num_ciphers == (sizeof(cipher_list)/sizeof(*cipher_list)))
        {
            msg(M_WARN, "WARNING: Too many ciphers, not showing all");
            break;
        }
    }

    qsort(cipher_list, num_ciphers, sizeof(*cipher_list), cipher_name_cmp);

    for (i = 0; i < num_ciphers; i++) {
        if (cipher_kt_block_size(cipher_list[i]) >= 128/8)
        {
            print_cipher(cipher_list[i]);
        }
    }

    printf("\nThe following ciphers have a block size of less than 128 bits, \n"
           "and are therefore deprecated.  Do not use unless you have to.\n\n");
    for (i = 0; i < num_ciphers; i++) {
        if (cipher_kt_block_size(cipher_list[i]) < 128/8)
        {
            print_cipher(cipher_list[i]);
        }
    }
    printf("\n");
}

void
show_available_digests(void)
{
    int nid;

#ifndef ENABLE_SMALL
    printf("The following message digests are available for use with\n"
           PACKAGE_NAME ".  A message digest is used in conjunction with\n"
           "the HMAC function, to authenticate received packets.\n"
           "You can specify a message digest as parameter to\n"
           "the --auth option.\n\n");
#endif

    for (nid = 0; nid < 10000; ++nid)
    {
        const EVP_MD *digest = EVP_get_digestbynid(nid);
        if (digest)
        {
            printf("%s %d bit digest size\n",
                   OBJ_nid2sn(nid), EVP_MD_size(digest) * 8);
        }
    }
    printf("\n");
}

void
show_available_engines(void)
{
#if HAVE_OPENSSL_ENGINE /* Only defined for OpenSSL */
    ENGINE *e;

    printf("OpenSSL Crypto Engines\n\n");

    ENGINE_load_builtin_engines();

    e = ENGINE_get_first();
    while (e)
    {
        printf("%s [%s]\n",
               ENGINE_get_name(e),
               ENGINE_get_id(e));
        e = ENGINE_get_next(e);
    }
    ENGINE_cleanup();
#else  /* if HAVE_OPENSSL_ENGINE */
    printf("Sorry, OpenSSL hardware crypto engine functionality is not available.\n");
#endif
}

/*
 *
 * Random number functions, used in cases where we want
 * reasonably strong cryptographic random number generation
 * without depleting our entropy pool.  Used for random
 * IV values and a number of other miscellaneous tasks.
 *
 */

int
rand_bytes(uint8_t *output, int len)
{
    if (unlikely(1 != RAND_bytes(output, len)))
    {
        crypto_msg(D_CRYPT_ERRORS, "RAND_bytes() failed");
        return 0;
    }
    return 1;
}

/*
 *
 * Key functions, allow manipulation of keys.
 *
 */


int
key_des_num_cblocks(const EVP_CIPHER *kt)
{
    int ret = 0;
    const char *name = OBJ_nid2sn(EVP_CIPHER_nid(kt));
    if (name)
    {
        if (!strncmp(name, "DES-", 4))
        {
            ret = EVP_CIPHER_key_length(kt) / sizeof(DES_cblock);
        }
        else if (!strncmp(name, "DESX-", 5))
        {
            ret = 1;
        }
    }
    dmsg(D_CRYPTO_DEBUG, "CRYPTO INFO: n_DES_cblocks=%d", ret);
    return ret;
}

bool
key_des_check(uint8_t *key, int key_len, int ndc)
{
    int i;
    struct buffer b;

    buf_set_read(&b, key, key_len);

    for (i = 0; i < ndc; ++i)
    {
        DES_cblock *dc = (DES_cblock *) buf_read_alloc(&b, sizeof(DES_cblock));
        if (!dc)
        {
            crypto_msg(D_CRYPT_ERRORS,
                       "CRYPTO INFO: check_key_DES: insufficient key material");
            goto err;
        }
        if (DES_is_weak_key(dc))
        {
            crypto_msg(D_CRYPT_ERRORS,
                       "CRYPTO INFO: check_key_DES: weak key detected");
            goto err;
        }
        if (!DES_check_key_parity(dc))
        {
            crypto_msg(D_CRYPT_ERRORS,
                       "CRYPTO INFO: check_key_DES: bad parity detected");
            goto err;
        }
    }
    return true;

err:
    ERR_clear_error();
    return false;
}

void
key_des_fixup(uint8_t *key, int key_len, int ndc)
{
    int i;
    struct buffer b;

    buf_set_read(&b, key, key_len);
    for (i = 0; i < ndc; ++i)
    {
        DES_cblock *dc = (DES_cblock *) buf_read_alloc(&b, sizeof(DES_cblock));
        if (!dc)
        {
            msg(D_CRYPT_ERRORS, "CRYPTO INFO: fixup_key_DES: insufficient key material");
            ERR_clear_error();
            return;
        }
        DES_set_odd_parity(dc);
    }
}


/*
 *
 * Generic cipher key type functions
 *
 */


const EVP_CIPHER *
cipher_kt_get(const char *ciphername)
{
    const EVP_CIPHER *cipher = NULL;

    ASSERT(ciphername);

    cipher = EVP_get_cipherbyname(ciphername);

    if (NULL == cipher)
    {
        crypto_msg(D_LOW, "Cipher algorithm '%s' not found", ciphername);
        return NULL;
    }


    if (EVP_CIPHER_key_length(cipher) > MAX_CIPHER_KEY_LENGTH)
    {
        msg(D_LOW, "Cipher algorithm '%s' uses a default key size (%d bytes) "
            "which is larger than " PACKAGE_NAME "'s current maximum key size "
            "(%d bytes)", ciphername, EVP_CIPHER_key_length(cipher),
            MAX_CIPHER_KEY_LENGTH);
        return NULL;
    }

    return cipher;
}

const char *
cipher_kt_name(const EVP_CIPHER *cipher_kt)
{
    if (NULL == cipher_kt)
    {
        return "[null-cipher]";
    }
    return EVP_CIPHER_name(cipher_kt);
}

int
cipher_kt_key_size(const EVP_CIPHER *cipher_kt)
{
    return EVP_CIPHER_key_length(cipher_kt);
}

int
cipher_kt_iv_size(const EVP_CIPHER *cipher_kt)
{
    return EVP_CIPHER_iv_length(cipher_kt);
}

int
cipher_kt_block_size(const EVP_CIPHER *cipher)
{
    /*
     * OpenSSL reports OFB/CFB/GCM cipher block sizes as '1 byte'.  To work
     * around that, try to replace the mode with 'CBC' and return the block size
     * reported for that cipher, if possible.  If that doesn't work, just return
     * the value reported by OpenSSL.
     */
    char *name = NULL;
    char *mode_str = NULL;
    const char *orig_name = NULL;
    const EVP_CIPHER *cbc_cipher = NULL;

    int block_size = EVP_CIPHER_block_size(cipher);

    orig_name = cipher_kt_name(cipher);
    if (!orig_name)
    {
        goto cleanup;
    }

    name = string_alloc(translate_cipher_name_to_openvpn(orig_name), NULL);
    mode_str = strrchr(name, '-');
    if (!mode_str || strlen(mode_str) < 4)
    {
        goto cleanup;
    }

    strcpy(mode_str, "-CBC");

    cbc_cipher = EVP_get_cipherbyname(translate_cipher_name_from_openvpn(name));
    if (cbc_cipher)
    {
        block_size = EVP_CIPHER_block_size(cbc_cipher);
    }

cleanup:
    free(name);
    return block_size;
}

int
cipher_kt_tag_size(const EVP_CIPHER *cipher_kt)
{
    if (cipher_kt_mode_aead(cipher_kt))
    {
        return OPENVPN_AEAD_TAG_LENGTH;
    }
    else
    {
        return 0;
    }
}

int
cipher_kt_mode(const EVP_CIPHER *cipher_kt)
{
    ASSERT(NULL != cipher_kt);
    return EVP_CIPHER_mode(cipher_kt);
}

bool
cipher_kt_mode_cbc(const cipher_kt_t *cipher)
{
    return cipher && cipher_kt_mode(cipher) == OPENVPN_MODE_CBC
#ifdef EVP_CIPH_FLAG_AEAD_CIPHER
           /* Exclude AEAD cipher modes, they require a different API */
           && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)
#endif
    ;
}

bool
cipher_kt_mode_ofb_cfb(const cipher_kt_t *cipher)
{
    return cipher && (cipher_kt_mode(cipher) == OPENVPN_MODE_OFB
                      || cipher_kt_mode(cipher) == OPENVPN_MODE_CFB)
#ifdef EVP_CIPH_FLAG_AEAD_CIPHER
           /* Exclude AEAD cipher modes, they require a different API */
           && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)
#endif
    ;
}

bool
cipher_kt_mode_aead(const cipher_kt_t *cipher)
{
#ifdef HAVE_AEAD_CIPHER_MODES
    return cipher && (cipher_kt_mode(cipher) == OPENVPN_MODE_GCM);
#else
    return false;
#endif
}

/*
 *
 * Generic cipher context functions
 *
 */

cipher_ctx_t *
cipher_ctx_new(void)
{
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    check_malloc_return(ctx);
    return ctx;
}

void
cipher_ctx_free(EVP_CIPHER_CTX *ctx)
{
    EVP_CIPHER_CTX_free(ctx);
}

void
cipher_ctx_init(EVP_CIPHER_CTX *ctx, const uint8_t *key, int key_len,
                const EVP_CIPHER *kt, int enc)
{
    ASSERT(NULL != kt && NULL != ctx);

    EVP_CIPHER_CTX_init(ctx);
    if (!EVP_CipherInit(ctx, kt, NULL, NULL, enc))
    {
        crypto_msg(M_FATAL, "EVP cipher init #1");
    }
#ifdef HAVE_EVP_CIPHER_CTX_SET_KEY_LENGTH
    if (!EVP_CIPHER_CTX_set_key_length(ctx, key_len))
    {
        crypto_msg(M_FATAL, "EVP set key size");
    }
#endif
    if (!EVP_CipherInit_ex(ctx, NULL, NULL, key, NULL, enc))
    {
        crypto_msg(M_FATAL, "EVP cipher init #2");
    }

    /* make sure we used a big enough key */
    ASSERT(EVP_CIPHER_CTX_key_length(ctx) <= key_len);
}

void
cipher_ctx_cleanup(EVP_CIPHER_CTX *ctx)
{
    EVP_CIPHER_CTX_cleanup(ctx);
}

int
cipher_ctx_iv_length(const EVP_CIPHER_CTX *ctx)
{
    return EVP_CIPHER_CTX_iv_length(ctx);
}

int
cipher_ctx_get_tag(EVP_CIPHER_CTX *ctx, uint8_t *tag_buf, int tag_size)
{
#ifdef HAVE_AEAD_CIPHER_MODES
    return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag_size, tag_buf);
#else
    ASSERT(0);
#endif
}

int
cipher_ctx_block_size(const EVP_CIPHER_CTX *ctx)
{
    return EVP_CIPHER_CTX_block_size(ctx);
}

int
cipher_ctx_mode(const EVP_CIPHER_CTX *ctx)
{
    return EVP_CIPHER_CTX_mode(ctx);
}

const cipher_kt_t *
cipher_ctx_get_cipher_kt(const cipher_ctx_t *ctx)
{
    return ctx ? EVP_CIPHER_CTX_cipher(ctx) : NULL;
}


int
cipher_ctx_reset(EVP_CIPHER_CTX *ctx, uint8_t *iv_buf)
{
    return EVP_CipherInit_ex(ctx, NULL, NULL, NULL, iv_buf, -1);
}

int
cipher_ctx_update_ad(EVP_CIPHER_CTX *ctx, const uint8_t *src, int src_len)
{
#ifdef HAVE_AEAD_CIPHER_MODES
    int len;
    if (!EVP_CipherUpdate(ctx, NULL, &len, src, src_len))
    {
        crypto_msg(M_FATAL, "%s: EVP_CipherUpdate() failed", __func__);
    }
    return 1;
#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
    ASSERT(0);
#endif
}

int
cipher_ctx_update(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len,
                  uint8_t *src, int src_len)
{
    if (!EVP_CipherUpdate(ctx, dst, dst_len, src, src_len))
    {
        crypto_msg(M_FATAL, "%s: EVP_CipherUpdate() failed", __func__);
    }
    return 1;
}

int
cipher_ctx_final(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len)
{
    return EVP_CipherFinal(ctx, dst, dst_len);
}

int
cipher_ctx_final_check_tag(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len,
                           uint8_t *tag, size_t tag_len)
{
#ifdef HAVE_AEAD_CIPHER_MODES
    ASSERT(tag_len < SIZE_MAX);
    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag))
    {
        return 0;
    }

    return cipher_ctx_final(ctx, dst, dst_len);
#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
    ASSERT(0);
#endif
}

void
cipher_des_encrypt_ecb(const unsigned char key[DES_KEY_LENGTH],
                       unsigned char *src,
                       unsigned char *dst)
{
    DES_key_schedule sched;

    DES_set_key_unchecked((DES_cblock *)key, &sched);
    DES_ecb_encrypt((DES_cblock *)src, (DES_cblock *)dst, &sched, DES_ENCRYPT);
}

/*
 *
 * Generic message digest information functions
 *
 */


const EVP_MD *
md_kt_get(const char *digest)
{
    const EVP_MD *md = NULL;
    ASSERT(digest);
    md = EVP_get_digestbyname(digest);
    if (!md)
    {
        crypto_msg(M_FATAL, "Message hash algorithm '%s' not found", digest);
    }
    if (EVP_MD_size(md) > MAX_HMAC_KEY_LENGTH)
    {
        crypto_msg(M_FATAL, "Message hash algorithm '%s' uses a default hash "
                   "size (%d bytes) which is larger than " PACKAGE_NAME "'s current "
                   "maximum hash size (%d bytes)",
                   digest, EVP_MD_size(md), MAX_HMAC_KEY_LENGTH);
    }
    return md;
}

const char *
md_kt_name(const EVP_MD *kt)
{
    if (NULL == kt)
    {
        return "[null-digest]";
    }
    return EVP_MD_name(kt);
}

int
md_kt_size(const EVP_MD *kt)
{
    return EVP_MD_size(kt);
}


/*
 *
 * Generic message digest functions
 *
 */

int
md_full(const EVP_MD *kt, const uint8_t *src, int src_len, uint8_t *dst)
{
    unsigned int in_md_len = 0;

    return EVP_Digest(src, src_len, dst, &in_md_len, kt, NULL);
}

EVP_MD_CTX *
md_ctx_new(void)
{
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    check_malloc_return(ctx);
    return ctx;
}

void md_ctx_free(EVP_MD_CTX *ctx)
{
    EVP_MD_CTX_free(ctx);
}

void
md_ctx_init(EVP_MD_CTX *ctx, const EVP_MD *kt)
{
    ASSERT(NULL != ctx && NULL != kt);

    EVP_MD_CTX_init(ctx);
    EVP_DigestInit(ctx, kt);
}

void
md_ctx_cleanup(EVP_MD_CTX *ctx)
{
    EVP_MD_CTX_reset(ctx);
}

int
md_ctx_size(const EVP_MD_CTX *ctx)
{
    return EVP_MD_CTX_size(ctx);
}

void
md_ctx_update(EVP_MD_CTX *ctx, const uint8_t *src, int src_len)
{
    EVP_DigestUpdate(ctx, src, src_len);
}

void
md_ctx_final(EVP_MD_CTX *ctx, uint8_t *dst)
{
    unsigned int in_md_len = 0;

    EVP_DigestFinal(ctx, dst, &in_md_len);
}


/*
 *
 * Generic HMAC functions
 *
 */

HMAC_CTX *
hmac_ctx_new(void)
{
    HMAC_CTX *ctx = HMAC_CTX_new();
    check_malloc_return(ctx);
    return ctx;
}

void
hmac_ctx_free(HMAC_CTX *ctx)
{
    HMAC_CTX_free(ctx);
}

void
hmac_ctx_init(HMAC_CTX *ctx, const uint8_t *key, int key_len,
              const EVP_MD *kt)
{
    ASSERT(NULL != kt && NULL != ctx);

    HMAC_CTX_reset(ctx);
    HMAC_Init_ex(ctx, key, key_len, kt, NULL);

    /* make sure we used a big enough key */
    ASSERT(HMAC_size(ctx) <= key_len);
}

void
hmac_ctx_cleanup(HMAC_CTX *ctx)
{
    HMAC_CTX_reset(ctx);
}

int
hmac_ctx_size(const HMAC_CTX *ctx)
{
    return HMAC_size(ctx);
}

void
hmac_ctx_reset(HMAC_CTX *ctx)
{
    HMAC_Init_ex(ctx, NULL, 0, NULL, NULL);
}

void
hmac_ctx_update(HMAC_CTX *ctx, const uint8_t *src, int src_len)
{
    HMAC_Update(ctx, src, src_len);
}

void
hmac_ctx_final(HMAC_CTX *ctx, uint8_t *dst)
{
    unsigned int in_hmac_len = 0;

    HMAC_Final(ctx, dst, &in_hmac_len);
}

#endif /* ENABLE_CRYPTO && ENABLE_CRYPTO_OPENSSL */
