c++ - 使用来自 USB token 的证书和 key 进行数字签名

标签 c++ cryptography digital-signature x509 hsm

我想使用来自 USB token (加密狗)的用户 key 和证书对文件进行签名。

一段时间以来,我一直在 stackoverflow 和其他网站上搜索此内容,但除了 .NET 框架(我没有使用)中的一些优秀功能外,没有得到任何有用的信息。

看来由于 key 没有暴露,所以加密是由硬件自己完成的。这是否意味着每个硬件制造商都提供自己的 API,没有通用的方法来解决这个问题?

另外,我读到一旦将 token 插入计算机,其证书就会加载到系统存储中。是否可以使用商店的证书?如何在商店中识别和访问这样的证书?私钥呢?

当证书可以从 .p12 或 .pfx 文件中提取时,我使用 OpenSSL 进行数字签名。

如果我在某个地方错了,请纠正我,我是这个话题的新手。

最佳答案

我不知道我是否会将 OpenSSL 引擎的任何方面描述为“相当简单”。命令行版本比较乱,自己写代码才搞清楚命令行是干什么的。操作顺序和生命周期没有被很好地调用(我仍然不完全知道它们是什么,但我的系统正在运行并且不再泄漏内存,所以是的。)

我已经在 github 上发布了功能版本:https://github.com/tkil/openssl-pkcs11-samples

以下是 tok-sign.c 相关部分的导览:

首先,一些助手:

#define FAIL( msg, dest )                      \
    do {                                       \
        fprintf( stderr, "error: " msg "\n" ); \
        goto dest;                             \
    } while ( 0 )

/* mandatory is "not optional"... */
const int CMD_MANDATORY = 0;

dynamic 引擎最奇怪的地方可能是它实际上是一个元引擎:您向它提供各种参数,当您向它提供 LOAD 时,它会加载动态库并提供新引擎。一旦您知道正确的操作顺序,它在代码中就很简单。在这里,我们可以访问动态引擎,对其进行配置,然后要求它引入 pkcs11 引擎:

ENGINE_load_dynamic();
ENGINE * dyn = ENGINE_by_id( "dynamic" );
if ( ! dyn )
    FAIL( "retrieving 'dynamic' engine", free_out_sig_file );

// this is the bridge between OpenSSL and any generic PCKS11 provider:
char * engine_pkcs11_so = "/opt/crypto/lib/engines/engine_pkcs11.so";

if ( 1 != ENGINE_ctrl_cmd_string( dyn, "SO_PATH", engine_pkcs11_so, CMD_MANDATORY ) )
    FAIL( "dyn: setting so_path <= 'engine_pkcs11.so'", free_dyn );

if ( 1 != ENGINE_ctrl_cmd_string( dyn, "ID", "pkcs11", CMD_MANDATORY ) )
    FAIL( "dyn: setting id <= 'pkcs11'", free_dyn );

if ( 1 != ENGINE_ctrl_cmd( dyn, "LIST_ADD", 1, NULL, NULL, CMD_MANDATORY ) )
    FAIL( "dyn: setting list_add <= 1", free_dyn );

if ( 1 != ENGINE_ctrl_cmd( dyn, "LOAD", 1, NULL, NULL, CMD_MANDATORY ) )
    FAIL( "dyn: setting load <= 1", free_dyn );

此时,如果所有这些调用都成功,您的 OpenSSL 实例现在可以访问名为“pkcs11”的新引擎。现在我们需要访问那个新引擎,正确配置它,让它自己初始化:

ENGINE * pkcs11 = ENGINE_by_id( "pkcs11" );
if ( ! pkcs11 )
    FAIL( "pkcs11: unable to get engine", free_dyn );

// this is the actual pkcs11 provider we're using.  in this case, it's
// from the OpenSC package.
char * opensc_pkcs11_so = "/opt/crypto/lib/opensc-pkcs11.so";

if ( 1 != ENGINE_ctrl_cmd_string( pkcs11, "MODULE_PATH", opensc_pkcs11_so, CMD_MANDATORY ) )
    FAIL( "setting module_path <= 'opensc-pkcs11.so'", free_pkcs11 );

if ( 1 != ENGINE_ctrl_cmd_string( pkcs11, "PIN", key_pin, CMD_MANDATORY ) )
    FAIL( "setting pin", free_pkcs11 );

if ( 1 != ENGINE_init( pkcs11 ) )
    FAIL( "pkcs11: unable to initialize engine", free_pkcs11 );

现在我们可以访问 token ,我们可以获取用于 OpenSSL 操作的私钥:

EVP_PKEY * key = ENGINE_load_private_key( pkcs11, key_id, NULL, NULL );
if ( ! key )
    FAIL( "reading private key", free_pkcs11 );

但是一直想不通如何通过ENGINE接口(interface)提取对应的证书,所以直接上了LibP11:

PKCS11_CTX * p11_ctx = PKCS11_CTX_new();
if ( ! p11_ctx )
    FAIL( "opening pkcs11 context", free_key );

if ( 0 != PKCS11_CTX_load( p11_ctx, opensc_pkcs11_so ) )
    FAIL( "unable to load module", free_p11_ctx );

PKCS11_SLOT * p11_slots;
unsigned int num_p11_slots;
if ( 0 != PKCS11_enumerate_slots( p11_ctx, &p11_slots, &num_p11_slots ) )
    FAIL( "enumerating slots", free_p11_ctx_module );

PKCS11_SLOT * p11_used_slot =
  PKCS11_find_token( p11_ctx, p11_slots, num_p11_slots );
if ( ! p11_used_slot )
    FAIL( "finding token", free_p11_slots );

PKCS11_CERT * p11_certs;
unsigned int num_p11_certs;
if ( 0 != PKCS11_enumerate_certs( p11_used_slot->token, &p11_certs, &num_p11_certs ) )
    FAIL( "enumerating certs", free_p11_slots );

最后,我们开始为 CMS_Sign 请求收集数据。我们查看 token 上的所有证书,挑出私钥对应的证书,然后将其余的存储在 OpenSSL STACK_OF(X509):

STACK_OF(X509) * extra_certs = sk_X509_new_null();
if ( ! extra_certs )
    FAIL( "allocating extra certs", free_p11_slots );

X509 * key_cert = NULL;
for ( unsigned int i = 0; i < num_p11_certs; ++i )
{
    PKCS11_CERT * p11_cert = p11_certs + i;

    if ( ! p11_cert->label )
        continue;

    // fprintf( stderr, "p11: got cert label='%s', x509=%p\n",
    //         p11_cert->label, p11_cert->x509 );

    if ( ! p11_cert->x509 )
    {
        fprintf( stderr, "p11: ... no x509, ignoring\n" );
        continue;
    }

    const char * label = p11_cert->label;
    const unsigned int label_len = strlen( label );

    if ( strcmp( label, key_label ) == 0 )
    {
        // fprintf( stderr, "p11: ... saving as signing cert\n" );
        key_cert = p11_cert->x509;
    }
    else if ( strncmp( label, "encrypt", 7 ) == 0 &&
              label_len == 8 &&
              '0' <= label[7] && label[7] <= '3' )
    {
        // fprintf( stderr, "p11: ... ignoring as encrypting cert\n" );
    }
    else
    {
        // fprintf( stderr, "p11: ... saving as extra cert\n" );
        if ( ! sk_X509_push( extra_certs, p11_cert->x509 ) )
            FAIL( "pushing extra cert", free_extra_certs );
    }
}

if ( ! key_cert )
    FAIL( "finding signing cert", free_extra_certs );

最后,我们可以对数据进行签名,然后将签名输出为DER格式的文件:

CMS_ContentInfo * ci = CMS_sign( key_cert, key, extra_certs, in_data_file,
                                 CMS_DETACHED | CMS_BINARY );
if ( ! ci )
    FAIL( "could not create signing structure", free_extra_certs );

if ( 1 != i2d_CMS_bio( out_sig_file, ci ) )
       FAIL( "could not write signature in DER", free_ci );

之后就是清理了:

free_ci:
    CMS_ContentInfo_free( ci );

free_extra_certs:
    /* these certs are actually "owned" by the libp11 code, and are
     * presumably freed with the slot or context. */
    sk_X509_free( extra_certs );

free_p11_slots:
    PKCS11_release_all_slots( p11_ctx, p11_slots, num_p11_slots );

free_p11_ctx_module:
    PKCS11_CTX_unload( p11_ctx );

free_p11_ctx:
    PKCS11_CTX_free( p11_ctx );

free_key:
    EVP_PKEY_free( key );

free_pkcs11:
    ENGINE_free( pkcs11 );

free_dyn:
    ENGINE_free( dyn );

关于c++ - 使用来自 USB token 的证书和 key 进行数字签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15359496/

相关文章:

c++ - 使用 ECDSA 或 DSA 对预先计算的哈希进行签名

openssl - openSSL rsautl 和 dgst 的区别

c++ - 关于双重销毁异常对象所需的解释

c++ - 如何访问 MFC 中主对话框的元素?元素是在可视化编辑器 VS 2012 中创建的

Java - KeyPairGenerator.Initialize(int,SecureRandom)NullPointerException

java - 带有/模运算的凯撒密码

java - 使用椭圆曲线加密验证签名

c++ - lambda 函数捕获和修改自己的引用

c++ - 编译器如何使用编译器生成的临时文件确定函数所需的堆栈大小?

node.js - 如何从 PKCS#12 文件中提取私钥以便进一步与 nodeJS OAuth 一起使用