5

ユーザーのキーと USB トークン (ドングル) の証明書を使用してファイルに署名したいと考えています。

私はこれをstackoverflowや他のサイトでしばらく検索してきましたが、.NETフレームワークのいくつかの優れた機能(私は使用していません)を除いて、何も役に立ちませんでした。

鍵が公開されていないため、暗号化はハードウェア自体で行っているようです。これは、各ハードウェア メーカーが独自の API を提供しており、この問題に対処する一般的な方法がないということですか?

また、トークンがコンピューターに接続されると、その証明書がシステム ストアに読み込まれることを読みました。お店の証明書は使えますか?このような証明書は、ストア内でどのように識別してアクセスできますか? 秘密鍵はどうですか?

証明書を .p12 または .pfx ファイルから抽出できる場合、デジタル署名に OpenSSL を使用しました。

どこかで間違っている場合は修正してください。このトピックは初めてです。

4

3 に答える 3

12

私は、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 );

トークンにアクセスできるようになったので、OpenSSL 操作で使用する秘密鍵を取得できます。

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

ただし、ENGINE インターフェイスを介して対応する証明書を抽出する方法がわからなかったため、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 リクエストのデータ収集を開始します。トークンのすべての証明書を見て、秘密鍵に対応するものを選択し、残りを 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 );
于 2013-05-12T05:31:04.437 に答える
8

これを行うには、OpenSSL のエンジンを使用し、PKCS#11 エンジンを提供します。

これは、コマンド ライン (-engine フラグ) から、またはエンジンを設定することによって実行できます。openssl ディストリビューションの /apps/ にある apps.c に良い例があります。

コマンドラインからの典型的な呼び出しは次のようになります

usr/bin/openssl << EOM
engine dynamic -pre SO_PATH:/Library/OpenSC/lib/engines/engine_pkcs11.so  -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:opensc-pkcs11.so
req -engine pkcs11 -batch -subj "/CN=moi" -new -key slot_$SLOT-id_$KID -keyform engine -x509 -out cert.pem -text
EOM

デバイスでリクエストを作成して署名します。

何かに署名するには:

${OPENSSL} << EOM || exit 1
engine -vvvv dynamic \
    -pre SO_PATH:/Library/OpenSC/lib/engines/engine_pkcs11.so  \
    -pre ID:pkcs11 \
    -pre LIST_ADD:1 \
    -pre LOAD \
    -pre MODULE_PATH:$PKCS \
    -pre PIN:$PIN  \
    -pre VERBOSE 

x509 -engine pkcs11 -req -in req.csr -out signed.pem \
    -CAfile $CA \
    -keyform engine -key $SLOT:$CAKID \
    -cert $CAKID.pem

ただし、後者の場合、パッチが適用されていないopensslではカード上の証明書を参照できないことに注意してください(キーのみ)。したがって、これを最初に抽出してファイルとして保存する必要があります。

カードのキーを使用して、クライアント認証でサーバーに接続するには:

${OPENSSL} << EOM || exit 1
engine -vvvv dynamic \
    -pre SO_PATH:/Library/OpenSC/lib/engines/engine_pkcs11.so  \
    -pre ID:pkcs11 \
    -pre LIST_ADD:1 \
    -pre LOAD \
    -pre MODULE_PATH:$PKCS \
    -pre PIN:$PIN  \
    -pre VERBOSE 

s_client -engine pkcs11 -connect localhost:1443 \
    -CAfile $CA \
    -keyform engine -key $SLOT:$KID \
    -cert $KID.pem

EOM

これをネイティブ コードに変換するのはかなり簡単です。openssl の apps.c ファイルの apps.c で engine を検索するだけで、どのように行われるかを確認できます。ほとんどの場合、それはいくつかの

pkey = ENGINE_load_private_key(e, file,

古いものとは対照的に

BIO_read_filename(key,file)
pkey=d2i_PrivateKey_bio(key, NULL);
于 2013-03-25T20:44:26.883 に答える
5

2 つのオプションがあります。

  1. PKCS#11。USB 暗号化トークンとスマートカードのほぼすべてのベンダーが、呼び出すことができる PKCS#11 用のドライバー DLL を提供しています。PKCS#11 インターフェースの仕様は非常に緩く、これが異なるベンダー間の癖や非互換性につながることに注意する必要があります。つまり、あるデバイスで証明書属性の 1 つのセットを使用し、別のデバイスで異なる属性のセットを使用する必要がある場合があります。
  2. クリプト API。ほとんどのベンダーは、証明書を Windows 証明書ストレージに "マップ" する CryptoAPI モジュール (CSP) を提供しており、Windows 証明書ストレージで証明書を使用するのと同じ方法で署名に使用します。これは、Windows API でさまざまな Crypt* 、 Cert* 、および同様の関数を使用することを意味します。

OpenSSL をタスクに使用できるとは思いません。CryptoAPI または PKCS#11 を使用する必要があります。

当社のSecureBlackbox製品は、さまざまな暗号化標準に従ってデータに署名し、PKCS#11 および/または CryptoAPI を使用するための統一された高レベル インターフェイスを提供します。それでも PKCS#11 の場合、あなた (または署名が行われるシステムのオペレーター) は、PKCS#11 ドライバー DLL へのパスを知る必要があります。SecureBlackbox は、ライブラリ エディションを使用して C++ から使用できます。

于 2013-03-12T11:49:49.567 に答える