2013-03-12 110 views
3

我想使用USB令牌(加密狗)中的用戶密鑰和證書籤署文件。使用USB令牌中的證書和密鑰進行數字簽名

我一直在尋找這個一段時間,在stackoverflow和其他網站,但沒有得到任何有用的東西,除了在.NET框架(我沒有使用)的一些好的功能。

看來,由於密鑰沒有公開,所以加密是由硬件本身完成的。這是否意味着每個硬件製造商都提供了自己的API,並且沒有通用的方法來解決這個問題?

此外,我讀到,一旦令牌插入計算機,它的證書被加載到系統存儲。是否可以使用商店的證書?如何在商店中識別和訪問這樣的證書?那私鑰呢?

當證書可以從.p12或.pfx文件中提取時,我已經使用OpenSSL進行數字簽名。

糾正我,如果我錯了某處,我是這個主題的新手。

+0

有一個叫PKCS11使用的硬件設備登錄標準。我不知道這是否會對你有幫助,但你可以通過閱讀例如http://en.wikipedia.org/wiki/PKCS_%E2%99%AF11 – jcoder 2013-03-12 11:17:37

+0

來了解更多。正如@jcoder所說,許多硬件設備使用PKCS#11接口進行加密操作。在繼續之前,您需要向硬件供應商詢問他們支持哪些API。 – 2013-03-12 11:22:30

+0

是的,我簡要閱讀了PKCS#11。但我希望有一個更優雅的解決方案,而不是單獨支持每個供應商。特別是,當我不知道正在使用的硬件的供應商。 – sg1 2013-03-12 11:31:24

回答

6

存在着兩種選擇:

  1. PKCS#11。幾乎每個USB加密令牌和智能卡供應商都提供了可供您調用的PKCS#11的驅動程序DLL。我需要注意的是,PKCS#11接口規範相當寬鬆,這導致不同供應商之間的怪癖和不兼容。即您可能需要在一臺設備中使用一組證書屬性,並在另一臺設備中使用不同的一組屬性。
  2. CryptoAPI。大多數供應商提供CryptoAPI模塊(CSP),將證書「映射」到Windows證書存儲中,並以與使用Windows證書存儲中的任何證書相同的方式使用它進行簽名。這意味着在Windows API中使用各種Crypt *,Cert *和類似的函數。

我不認爲OpenSSL可以用於您的任務 - 您需要使用CryptoAPI或PKCS#11。

我們的SecureBlackbox產品根據各種加密標準並使用PKCS#11和/或CryptoAPI提供統一的高級接口,用於對數據進行簽名。在PKCS#11的情況下,您(或系統的簽名完成者)需要知道PKCS#11驅動程序DLL的路徑。 SecureBlackbox可以在C++中使用其庫版本使用。

6

您可以通過使用OpenSSL的引擎來完成此操作,併爲其提供PKCS#11引擎。

這可以通過命令行(-engine標誌)或通過設置引擎完成。 apps/in/apps/openssl發行版中的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 .c文件 - 看看這是如何完成的。在大多數情況下,一些

pkey = ENGINE_load_private_key(e, file, 

,而不是老

BIO_read_filename(key,file) 
pkey=d2i_PrivateKey_bio(key, NULL); 
10

我不知道,我會表徵的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; 

可能對最奇怪的事引擎是它是一個真正的元引擎:它爲它提供了各種參數,並且當它餵食它時,它會加載動態庫並使新引擎可用。一旦你知道了正確的操作順序,代碼很簡單。在這裏,我們送獲得的動態引擎,配置它,然後要求其在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); 

但是,我無法弄清楚如何提取通過引擎接口相應的證書,所以我直接去了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); 
相關問題