/*
 *  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-2026 OpenVPN Inc <sales@openvpn.net>
 *
 *  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, see <https://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "syshead.h"

#if defined(ENABLE_PKCS11)

#include <pkcs11-helper-1.0/pkcs11h-certificate.h>
#include "basic.h"
#include "error.h"
#include "manage.h"
#include "base64.h"
#include "pkcs11.h"
#include "misc.h"
#include "otime.h"
#include "console.h"
#include "pkcs11_backend.h"

static time_t
__mytime(void)
{
    return openvpn_time(NULL);
}

#if !defined(_WIN32)
static int
__mygettimeofday(struct timeval *tv)
{
    return gettimeofday(tv, NULL);
}
#endif

static void
__mysleep(unsigned long usec)
{
#if defined(_WIN32)
    Sleep(usec / 1000);
#else
    if (usec > UINT_MAX)
    {
        usec = UINT_MAX;
    }
    usleep((useconds_t)usec);
#endif
}


static pkcs11h_engine_system_t s_pkcs11h_sys_engine = { malloc, free, __mytime, __mysleep,
#if defined(_WIN32)
                                                        NULL
#else
                                                        __mygettimeofday
#endif
};

static msglvl_t
_pkcs11_msg_pkcs112openvpn(const unsigned flags)
{
    msglvl_t openvpn_flags;

    switch (flags)
    {
        case PKCS11H_LOG_DEBUG2:
            openvpn_flags = D_PKCS11_DEBUG;
            break;

        case PKCS11H_LOG_DEBUG1:
            openvpn_flags = D_SHOW_PKCS11;
            break;

        case PKCS11H_LOG_INFO:
            openvpn_flags = M_INFO;
            break;

        case PKCS11H_LOG_WARN:
            openvpn_flags = M_WARN;
            break;

        case PKCS11H_LOG_ERROR:
            openvpn_flags = M_FATAL;
            break;

        default:
            openvpn_flags = M_FATAL;
            break;
    }

#if defined(ENABLE_PKCS11_FORCE_DEBUG)
    openvpn_flags = M_INFO;
#endif

    return openvpn_flags;
}

static unsigned
_pkcs11_msg_openvpn2pkcs11(const msglvl_t flags)
{
    unsigned pkcs11_flags;

    if ((flags & D_PKCS11_DEBUG) != 0)
    {
        pkcs11_flags = PKCS11H_LOG_DEBUG2;
    }
    else if ((flags & D_SHOW_PKCS11) != 0)
    {
        pkcs11_flags = PKCS11H_LOG_DEBUG1;
    }
    else if ((flags & M_INFO) != 0)
    {
        pkcs11_flags = PKCS11H_LOG_INFO;
    }
    else if ((flags & M_WARN) != 0)
    {
        pkcs11_flags = PKCS11H_LOG_WARN;
    }
    else if ((flags & M_FATAL) != 0)
    {
        pkcs11_flags = PKCS11H_LOG_ERROR;
    }
    else
    {
        pkcs11_flags = PKCS11H_LOG_ERROR;
    }

#if defined(ENABLE_PKCS11_FORCE_DEBUG)
    pkcs11_flags = PKCS11H_LOG_DEBUG2;
#endif

    return pkcs11_flags;
}

static void
_pkcs11_openvpn_log(void *const global_data, unsigned flags, const char *const szFormat,
                    va_list args)
{
    char Buffer[10 * 1024];

    (void)global_data;

    vsnprintf(Buffer, sizeof(Buffer), szFormat, args);
    Buffer[sizeof(Buffer) - 1] = 0;

    msg(_pkcs11_msg_pkcs112openvpn(flags), "%s", Buffer);
}

static PKCS11H_BOOL
_pkcs11_openvpn_token_prompt(void *const global_data, void *const user_data,
                             const pkcs11h_token_id_t token, const unsigned retry)
{
    struct user_pass token_resp;

    (void)global_data;
    (void)user_data;
    (void)retry;

    ASSERT(token != NULL);

    CLEAR(token_resp);
    token_resp.defined = false;
    token_resp.nocache = true;
    snprintf(token_resp.username, sizeof(token_resp.username), "Please insert %s token",
             token->label);

    if (!get_user_pass(&token_resp, NULL, "token-insertion-request",
                       GET_USER_PASS_MANAGEMENT | GET_USER_PASS_NEED_OK | GET_USER_PASS_NOFATAL))
    {
        return false;
    }
    else
    {
        return strcmp(token_resp.password, "ok") == 0;
    }
}

static PKCS11H_BOOL
_pkcs11_openvpn_pin_prompt(void *const global_data, void *const user_data,
                           const pkcs11h_token_id_t token, const unsigned retry, char *const pin,
                           const size_t pin_max)
{
    struct user_pass token_pass;
    char prompt[1024];
    CLEAR(token_pass);

    (void)global_data;
    (void)user_data;
    (void)retry;

    ASSERT(token != NULL);

    snprintf(prompt, sizeof(prompt), "%s token", token->label);

    token_pass.defined = false;
    token_pass.nocache = true;

    if (!get_user_pass(&token_pass, NULL, prompt,
                       GET_USER_PASS_MANAGEMENT | GET_USER_PASS_PASSWORD_ONLY
                           | GET_USER_PASS_NOFATAL))
    {
        return false;
    }
    else
    {
        strncpynt(pin, token_pass.password, pin_max);
        purge_user_pass(&token_pass, true);

        if (strlen(pin) == 0)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
}

bool
pkcs11_initialize(const bool protected_auth, const int nPINCachePeriod)
{
    CK_RV rv = CKR_FUNCTION_FAILED;

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_initialize - entered");

    if ((rv = pkcs11h_engine_setSystem(&s_pkcs11h_sys_engine)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot initialize system engine %ld-'%s'", rv,
            pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_initialize()) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot initialize %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setLogHook(_pkcs11_openvpn_log, NULL)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set hooks %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    pkcs11h_setLogLevel(_pkcs11_msg_openvpn2pkcs11(get_debug_level()));

    if ((rv = pkcs11h_setForkMode(FALSE)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set fork mode %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setTokenPromptHook(_pkcs11_openvpn_token_prompt, NULL)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set hooks %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setPINPromptHook(_pkcs11_openvpn_pin_prompt, NULL)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set hooks %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setProtectedAuthentication(protected_auth)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set protected authentication mode %ld-'%s'", rv,
            pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setPINCachePeriod(nPINCachePeriod)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set Pcache period %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    rv = CKR_OK;

cleanup:
    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_initialize - return %ld-'%s'", rv,
         pkcs11h_getMessage(rv));

    return rv == CKR_OK;
}

void
pkcs11_terminate(void)
{
    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_terminate - entered");

    pkcs11h_terminate();

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_terminate - return");
}

bool
pkcs11_addProvider(const char *const provider, const bool protected_auth,
                   const unsigned private_mode, const bool cert_private)
{
    CK_RV rv = CKR_OK;

    ASSERT(provider != NULL);

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_addProvider - entered - provider='%s', private_mode=%08x",
         provider, private_mode);

    msg(M_INFO, "PKCS#11: Adding PKCS#11 provider '%s'", provider);

#if PKCS11H_VERSION >= ((1 << 16) | (28 << 8) | (0 << 0))
    if ((rv = pkcs11h_registerProvider(provider)) != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot register provider '%s' %ld-'%s'", provider, rv,
            pkcs11h_getMessage(rv));
    }
    else
    {
        PKCS11H_BOOL allow_protected_auth = protected_auth;
        PKCS11H_BOOL cert_is_private = cert_private;

        rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_LOCATION, provider,
                                         strlen(provider) + 1);

        if (rv == CKR_OK)
        {
            rv = pkcs11h_setProviderProperty(provider,
                                             PKCS11H_PROVIDER_PROPERTY_ALLOW_PROTECTED_AUTH,
                                             &allow_protected_auth, sizeof(allow_protected_auth));
        }
        if (rv == CKR_OK)
        {
            rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_MASK_PRIVATE_MODE,
                                             &private_mode, sizeof(private_mode));
        }
        if (rv == CKR_OK)
        {
            rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_CERT_IS_PRIVATE,
                                             &cert_is_private, sizeof(cert_is_private));
        }
#if defined(WIN32) && defined(PKCS11H_PROVIDER_PROPERTY_LOADER_FLAGS)
        if (rv == CKR_OK && platform_absolute_pathname(provider))
        {
            unsigned loader_flags =
                LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
            rv = pkcs11h_setProviderProperty(provider, PKCS11H_PROVIDER_PROPERTY_LOADER_FLAGS,
                                             &loader_flags, sizeof(loader_flags));
        }
#endif

        if (rv != CKR_OK || (rv = pkcs11h_initializeProvider(provider)) != CKR_OK)
        {
            msg(M_WARN, "PKCS#11: Cannot initialize provider '%s' %ld-'%s'", provider, rv,
                pkcs11h_getMessage(rv));
            pkcs11h_removeProvider(provider);
        }
    }
#else  /* if PKCS11H_VERSION >= ((1<<16) | (28<<8) | (0<<0)) */
    if ((rv = pkcs11h_addProvider(provider, provider, protected_auth, private_mode,
                                  PKCS11H_SLOTEVENT_METHOD_AUTO, 0, cert_private))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot initialize provider '%s' %ld-'%s'", provider, rv,
            pkcs11h_getMessage(rv));
    }
#endif /* if PKCS11H_VERSION >= ((1<<16) | (28<<8) | (0<<0)) */

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_addProvider - return rv=%ld-'%s'", rv,
         pkcs11h_getMessage(rv));

    return rv == CKR_OK;
}

int
pkcs11_logout(void)
{
    return pkcs11h_logout() == CKR_OK;
}

int
pkcs11_management_id_count(void)
{
    pkcs11h_certificate_id_list_t id_list = NULL;
    pkcs11h_certificate_id_list_t t = NULL;
    CK_RV rv = CKR_OK;
    int count = 0;

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_management_id_count - entered");

    if ((rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE_EXIST, NULL,
                                                     PKCS11H_PROMPT_MASK_ALLOW_ALL, NULL, &id_list))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot get certificate list %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    for (count = 0, t = id_list; t != NULL; t = t->next)
    {
        count++;
    }

cleanup:

    pkcs11h_certificate_freeCertificateIdList(id_list);
    id_list = NULL;

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_management_id_count - return count=%d", count);

    return count;
}

bool
pkcs11_management_id_get(const int index, char **id, char **base64)
{
    pkcs11h_certificate_id_list_t id_list = NULL;
    pkcs11h_certificate_id_list_t entry = NULL;
    pkcs11h_certificate_t certificate = NULL;
    CK_RV rv = CKR_OK;
    unsigned char *certificate_blob = NULL;
    size_t certificate_blob_size = 0;
    size_t max;
    char *internal_id = NULL;
    char *internal_base64 = NULL;
    int count = 0;
    bool success = false;

    ASSERT(id != NULL);
    ASSERT(base64 != NULL);

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_management_id_get - entered index=%d", index);

    *id = NULL;
    *base64 = NULL;

    if ((rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE_EXIST, NULL,
                                                     PKCS11H_PROMPT_MASK_ALLOW_ALL, NULL, &id_list))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot get certificate list %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    entry = id_list;
    count = 0;
    while (entry != NULL && count != index)
    {
        count++;
        entry = entry->next;
    }

    if (entry == NULL)
    {
        dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_management_id_get - no certificate at index=%d",
             index);
        goto cleanup;
    }

    if ((rv = pkcs11h_certificate_serializeCertificateId(NULL, &max, entry->certificate_id))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot serialize certificate id %ld-'%s'", rv,
            pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((internal_id = (char *)malloc(max)) == NULL)
    {
        msg(M_FATAL, "PKCS#11: Cannot allocate memory");
        goto cleanup;
    }

    if ((rv = pkcs11h_certificate_serializeCertificateId(internal_id, &max, entry->certificate_id))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot serialize certificate id %ld-'%s'", rv,
            pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_certificate_create(entry->certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL,
                                         PKCS11H_PIN_CACHE_INFINITE, &certificate))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot get certificate %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_certificate_getCertificateBlob(certificate, NULL, &certificate_blob_size))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot get certificate blob %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((certificate_blob = (unsigned char *)malloc(certificate_blob_size)) == NULL)
    {
        msg(M_FATAL, "PKCS#11: Cannot allocate memory");
        goto cleanup;
    }

    if ((rv = pkcs11h_certificate_getCertificateBlob(certificate, certificate_blob,
                                                     &certificate_blob_size))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot get certificate blob %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if (certificate_blob_size > INT_MAX)
    {
        msg(M_WARN, "PKCS#11: Invalid certificate size %zu", certificate_blob_size);
        goto cleanup;
    }

    if (openvpn_base64_encode(certificate_blob, (int)certificate_blob_size, &internal_base64) == -1)
    {
        msg(M_WARN, "PKCS#11: Cannot encode certificate");
        goto cleanup;
    }

    *id = internal_id;
    internal_id = NULL;
    *base64 = internal_base64;
    internal_base64 = NULL;
    success = true;

cleanup:

    pkcs11h_certificate_freeCertificateIdList(id_list);
    id_list = NULL;

    pkcs11h_certificate_freeCertificate(certificate);
    certificate = NULL;

    free(internal_id);
    internal_id = NULL;

    free(internal_base64);
    internal_base64 = NULL;

    free(certificate_blob);
    certificate_blob = NULL;

    dmsg(D_PKCS11_DEBUG, "PKCS#11: pkcs11_management_id_get - return success=%d, id='%s'",
         success ? 1 : 0, *id);

    return success;
}

int
tls_ctx_use_pkcs11(struct tls_root_ctx *const ssl_ctx, bool pkcs11_id_management,
                   const char *const pkcs11_id)
{
    pkcs11h_certificate_id_t certificate_id = NULL;
    pkcs11h_certificate_t certificate = NULL;
    CK_RV rv = CKR_OK;

    bool ok = false;

    ASSERT(ssl_ctx != NULL);
    ASSERT(pkcs11_id_management || pkcs11_id != NULL);

    dmsg(
        D_PKCS11_DEBUG,
        "PKCS#11: tls_ctx_use_pkcs11 - entered - ssl_ctx=%p, pkcs11_id_management=%d, pkcs11_id='%s'",
        (void *)ssl_ctx, pkcs11_id_management ? 1 : 0, pkcs11_id);

    if (pkcs11_id_management)
    {
        struct user_pass id_resp;

        CLEAR(id_resp);

        id_resp.defined = false;
        id_resp.nocache = true;
        snprintf(id_resp.username, sizeof(id_resp.username), "Please specify PKCS#11 id to use");

        if (!get_user_pass(&id_resp, NULL, "pkcs11-id-request",
                           GET_USER_PASS_MANAGEMENT | GET_USER_PASS_NEED_STR
                               | GET_USER_PASS_NOFATAL))
        {
            goto cleanup;
        }

        if ((rv = pkcs11h_certificate_deserializeCertificateId(&certificate_id, id_resp.password))
            != CKR_OK)
        {
            msg(M_WARN, "PKCS#11: Cannot deserialize id %ld-'%s'", rv, pkcs11h_getMessage(rv));
            goto cleanup;
        }
    }
    else
    {
        if ((rv = pkcs11h_certificate_deserializeCertificateId(&certificate_id, pkcs11_id))
            != CKR_OK)
        {
            msg(M_WARN, "PKCS#11: Cannot deserialize id %ld-'%s'", rv, pkcs11h_getMessage(rv));
            goto cleanup;
        }
    }

    if ((rv = pkcs11h_certificate_create(certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL,
                                         PKCS11H_PIN_CACHE_INFINITE, &certificate))
        != CKR_OK)
    {
        msg(M_WARN, "PKCS#11: Cannot get certificate %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((pkcs11_init_tls_session(certificate, ssl_ctx)))
    {
        /* Handled by SSL context free */
        certificate = NULL;
        goto cleanup;
    }

    /* Handled by SSL context free */
    certificate = NULL;
    ok = true;

cleanup:
    if (certificate != NULL)
    {
        pkcs11h_certificate_freeCertificate(certificate);
        certificate = NULL;
    }

    if (certificate_id != NULL)
    {
        pkcs11h_certificate_freeCertificateId(certificate_id);
        certificate_id = NULL;
    }

    dmsg(D_PKCS11_DEBUG, "PKCS#11: tls_ctx_use_pkcs11 - return ok=%d, rv=%ld", ok ? 1 : 0, rv);

    return ok ? 1 : 0;
}

static PKCS11H_BOOL
_pkcs11_openvpn_show_pkcs11_ids_pin_prompt(void *const global_data, void *const user_data,
                                           const pkcs11h_token_id_t token, const unsigned retry,
                                           char *const pin, const size_t pin_max)
{
    struct gc_arena gc = gc_new();
    struct buffer pass_prompt = alloc_buf_gc(128, &gc);

    (void)global_data;
    (void)user_data;
    (void)retry;

    ASSERT(token != NULL);

    buf_printf(&pass_prompt, "Please enter '%s' token PIN or 'cancel': ", token->display);
    if (!query_user_SINGLE(BSTR(&pass_prompt), pin, (int)pin_max, false))
    {
        msg(M_FATAL, "Could not retrieve the PIN");
    }

    gc_free(&gc);

    if (!strcmp(pin, "cancel"))
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

void
show_pkcs11_ids(const char *const provider, bool cert_private)
{
    struct gc_arena gc = gc_new();
    pkcs11h_certificate_id_list_t user_certificates = NULL;
    pkcs11h_certificate_id_list_t current = NULL;
    CK_RV rv = CKR_FUNCTION_FAILED;

    if ((rv = pkcs11h_initialize()) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot initialize %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setLogHook(_pkcs11_openvpn_log, NULL)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set hooks %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    pkcs11h_setLogLevel(_pkcs11_msg_openvpn2pkcs11(get_debug_level()));

    if ((rv = pkcs11h_setProtectedAuthentication(TRUE)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set protected authentication %ld-'%s'", rv,
            pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if ((rv = pkcs11h_setPINPromptHook(_pkcs11_openvpn_show_pkcs11_ids_pin_prompt, NULL)) != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot set PIN hook %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    if (!pkcs11_addProvider(provider, TRUE, 0, cert_private ? TRUE : FALSE))
    {
        msg(M_FATAL, "Failed to add PKCS#11 provider '%s", provider);
        goto cleanup;
    }

    if ((rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE_EXIST, NULL,
                                                     PKCS11H_PROMPT_MASK_ALLOW_ALL, NULL,
                                                     &user_certificates))
        != CKR_OK)
    {
        msg(M_FATAL, "PKCS#11: Cannot enumerate certificates %ld-'%s'", rv, pkcs11h_getMessage(rv));
        goto cleanup;
    }

    msg(M_INFO | M_NOPREFIX | M_NOLF,
        ("\n"
         "The following objects are available for use.\n"
         "Each object shown below may be used as parameter to\n"
         "--pkcs11-id option please remember to use single quote mark.\n"));
    for (current = user_certificates; current != NULL; current = current->next)
    {
        pkcs11h_certificate_t certificate = NULL;
        char *dn = NULL;
        char serial[1024] = { 0 };
        char *ser = NULL;
        size_t ser_len = 0;

        if ((rv = pkcs11h_certificate_serializeCertificateId(NULL, &ser_len,
                                                             current->certificate_id))
            != CKR_OK)
        {
            msg(M_FATAL, "PKCS#11: Cannot serialize certificate %ld-'%s'", rv,
                pkcs11h_getMessage(rv));
            goto cleanup1;
        }

        if (rv == CKR_OK && (ser = (char *)malloc(ser_len)) == NULL)
        {
            msg(M_FATAL, "PKCS#11: Cannot allocate memory");
            goto cleanup1;
        }

        if ((rv =
                 pkcs11h_certificate_serializeCertificateId(ser, &ser_len, current->certificate_id))
            != CKR_OK)
        {
            msg(M_FATAL, "PKCS#11: Cannot serialize certificate %ld-'%s'", rv,
                pkcs11h_getMessage(rv));
            goto cleanup1;
        }

        if ((rv = pkcs11h_certificate_create(current->certificate_id, NULL,
                                             PKCS11H_PROMPT_MASK_ALLOW_ALL,
                                             PKCS11H_PIN_CACHE_INFINITE, &certificate)))
        {
            msg(M_FATAL, "PKCS#11: Cannot create certificate %ld-'%s'", rv, pkcs11h_getMessage(rv));
            goto cleanup1;
        }

        if ((dn = pkcs11_certificate_dn(certificate, &gc)) == NULL)
        {
            goto cleanup1;
        }

        if ((pkcs11_certificate_serial(certificate, serial, sizeof(serial))))
        {
            goto cleanup1;
        }

        msg(M_INFO | M_NOPREFIX | M_NOLF,
            ("\n"
             "Certificate\n"
             "       DN:             %s\n"
             "       Serial:         %s\n"
             "       Serialized id:  %s\n"),
            dn, serial, ser);

cleanup1:

        if (certificate != NULL)
        {
            pkcs11h_certificate_freeCertificate(certificate);
            certificate = NULL;
        }

        free(ser);
        ser = NULL;
    }

cleanup:
    pkcs11h_certificate_freeCertificateIdList(user_certificates);
    user_certificates = NULL;

    pkcs11h_terminate();
    gc_free(&gc);
}
#endif /* ENABLE_PKCS11 */
