我不知道,我會表徵的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);
有一個叫PKCS11使用的硬件設備登錄標準。我不知道這是否會對你有幫助,但你可以通過閱讀例如http://en.wikipedia.org/wiki/PKCS_%E2%99%AF11 – jcoder 2013-03-12 11:17:37
來了解更多。正如@jcoder所說,許多硬件設備使用PKCS#11接口進行加密操作。在繼續之前,您需要向硬件供應商詢問他們支持哪些API。 – 2013-03-12 11:22:30
是的,我簡要閱讀了PKCS#11。但我希望有一個更優雅的解決方案,而不是單獨支持每個供應商。特別是,當我不知道正在使用的硬件的供應商。 – sg1 2013-03-12 11:31:24