Openssl和PKCS#11的故事
原文:http://www.cnblogs.com/adylee/archive/2009/08/03/1537813.html
1.1?? What to do
通過Openssl和PKCS#11接口,使用USBKEY中的私鑰和證書來簽發(fā)一個下級證書。
1.2????????????? 背景
數(shù)字證書頒發(fā)過程一般為:用戶首先產(chǎn)生自己的密鑰對,并將公共密鑰及部分個人身份信息傳送給認(rèn)證中心。認(rèn)證中心在核實身份后,將執(zhí)行一些必要的步驟,以確信請求確實由用戶發(fā)送而來,然后,認(rèn)證中心將發(fā)給用戶一個數(shù)字證書,該證書內(nèi)包含用戶的個人信息和他的公鑰信息,同時還附有認(rèn)證中心的簽名信息。
?
?
?
?
一個標(biāo)準(zhǔn)的X.509數(shù)字證書包含以下一些內(nèi)容:
證書的版本信息;
證書的序列號,每個證書都有一個唯一的證書序列號;
證書所使用的簽名算法;
證書的發(fā)行機(jī)構(gòu)名稱,命名規(guī)則一般采用X.500格式;
證書的有效期,現(xiàn)在通用的證書一般采用UTC時間格式,它的計時范圍為1950-2049;
證書所有人的名稱,命名規(guī)則一般采用X.500格式;
證書所有人的公開密鑰;
證書發(fā)行者對證書的簽名。
?
簡而言之,CA從PKCS#10證書請求(或者P7格式)中讀取用戶信息和公鑰信息,使用這些信息封裝成一個X.509格式(可能是不同版本,比較普遍是V3),此時唯一沒有包括的是證書發(fā)行者對證書的簽名,此時使用CA的私鑰進(jìn)行簽名,得到簽名值后CA將其填充到X.509相對應(yīng)的結(jié)構(gòu)中去,一個X.509證書寶寶就此誕生了。
?
此處唯一不同的是CA的公私鑰對和證書都存放在USBKEY中(當(dāng)然也能存放在加密機(jī)或加密卡中),所以將通過USBKEY的PKCS#11接口完成上述操作,而證書相關(guān)操作就由Openssl代勞了。
?
1.3????????????? 正題
第一、使用Usbkey向某個CA申請一個證書
通過下面的命令來驗證,第一組公私鑰對和證書是簽名證書,第二組是加密證書。可以很明顯地看出他們是通過Csp方式操作整個證書申請過程的。
C:\Program Files\Smart card bundle>pkcs11-tool.exe --module DMPKCS11.dll –O
Certificate Object, type = X.509 cert
label:????? cert addey by CSP
ID:???????? 37af001ddbd525e640ca3c3f6d78b009741d1f48
Public Key Object; RSA 1024 bits
label:????? pub key addey by CSP
ID:???????? 37af001ddbd525e640ca3c3f6d78b009741d1f48
Usage:????? encrypt, verify
Private Key Object; RSA
label:????? private key addey by CSP
ID:???????? 37af001ddbd525e640ca3c3f6d78b009741d1f48
Usage:????? decrypt, sign
Certificate Object, type = X.509 cert
label:????? cert addey by CSP
ID:???????? ab268f4320a426b4a6ce70d757cd11fcd83b8ddd
Public Key Object; RSA 1024 bits
label:????? pub key addey by CSP
ID:???????? ab268f4320a426b4a6ce70d757cd11fcd83b8ddd
Usage:????? encrypt, verify
Private Key Object; RSA
label:????? private key addey by CSP
ID:???????? ab268f4320a426b4a6ce70d757cd11fcd83b8ddd
Usage:????? decrypt, sign
?
第二、生成PKCS#11的證書請求
這里直接使用Java程序生成一個證書請求。
import?java.io.OutputStreamWriter;
import?java.security.KeyPair;
import?java.security.KeyPairGenerator;
?
import?javax.security.auth.x500.X500Principal;
?
import?org.bouncycastle.jce.PKCS10CertificationRequest;
import?org.bouncycastle.openssl.PEMWriter;
?
/**
* Generation of a basic PKCS #10 request.
*/
public?class?PKCS10CertRequestExample
{
????public?static?PKCS10CertificationRequest generateRequest(
??????? KeyPair pair)
????????throws?Exception
??? {
????????return?new?PKCS10CertificationRequest(
????????????????"SHA256withRSA",
????????????????new?X500Principal("C=CN,ST=上海,L=上海,O=火星,OU=北極,CN=超人"),
??????????????? pair.getPublic(),
????????????????null,
??????????????? pair.getPrivate());
??? }
???
????public?static?void?main(
??????? String[]??? args)
?? ?????throws?Exception
??? {
??????? // create the keys
??????? KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
??????? kpGen.initialize(1024, Utils.createFixedRandom());
??????? KeyPair????????? pair = kpGen.generateKeyPair();
??????? PKCS10CertificationRequest request =?generateRequest(pair);
??????? PEMWriter??????? pemWrt =?new?PEMWriter(new?OutputStreamWriter(System.out));
??????? pemWrt.writeObject(request);
??????? pemWrt.close();
??? }
}
?
?
證書請求
-----BEGIN CERTIFICATE REQUEST-----
MIIBoDCCAQkCAQAwYjEPMA0GA1UEAwwG6LaF5Lq6MQ8wDQYDVQQLDAbljJfmnoEx
DzANBgNVBAoMBueBq+aYnzEPMA0GA1UEBwwG5LiK5rW3MQ8wDQYDVQQIDAbkuIrm
tbcxCzAJBgNVBAYTAkNOMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw7iyU
/8p1lCxnJifdqxNYO1cTVg35BBtscQsrtug9Br3Vge/kNX9KC5xOGhdcK1IDjl3d
1CGsRtnb4dEFqtkjKWQ1z5WZxXWoVfkqwP3AJg8y10BhiiDqPPbn3II4o8Nc+bvz
tDm32HbNXcyXWLR5aEJx1FiJYdDmDbRbgGrcawIDAQABMA0GCSqGSIb3DQEBCwUA
A4GBAJSr2pe1LJp++gSWAc7yVufbnYXG3QgzIdoEUhP1I/3LNeqUYyuTaL/fTgAF
oEjTvwOlAVizcve8qiD9/ApY+MtjgRKFDbZYnkC3mRgJTDxV3WzDmdj4YEQGIUVG
O+XRfiWP132n9N3aI6gaJVj2m7Zu56akrE3F2c4kawZL/aIK
-----END CERTIFICATE REQUEST-----
?
第三、程序簽發(fā)
1. engine_pkcs11的使用方式
?????? 使用openssl調(diào)用USBKEY的PKCS#11接口,可以通過OpenSC項目的engine_pkcs11接口。原本使用編寫openssl配置文件方式(見[1]),但是就是無法使用,兩次調(diào)用ListEngine()都無法發(fā)現(xiàn)pkcs11 engine的影子。
Openssl.conf 內(nèi)容:
?
openssl_conf = openssl_def
?
[openssl_def]
engines = engine_section
?
[engine_section]
pkcs11 = pkcs11_section
?
[pkcs11_section]
engine_id = pkcs11
dynamic_path = "C:\\Program Files\\Smart card bundle\\engine_pkcs11.dll"
MODULE_PATH = C:\\Windows\\System32\\DMPKCS11.dll
init = 0
?
[req]
distinguished_name = req_distinguished_name
?
[req_distinguished_name]
?
可以通過下面命令驗證配置文件并沒有寫錯,openssl識別出了pkcs11 engine,并且生成了證書請求。
C:\Program Files\Smart card bundle>openssl req -config openssl.conf -engine pkcs11 -new -key id_37af001ddbd525e640ca3c3f6d78b009741d1f48 -keyform engine -out req.pem -text -x509 -subj "/CN=Andreas Jellinghaus"
engine "pkcs11" set.
PKCS#11 token PIN:
?
所以最后還是使用動態(tài)調(diào)用的方式導(dǎo)入pkcs11 engine,即ENGINE_load_dynamic。所以兩次調(diào)用ListEngine()后發(fā)現(xiàn), dynamic engine導(dǎo)入pkcs11 engine后就會被其替換。
導(dǎo)入前
id: dynamic, name: Dynamic engine loading support
導(dǎo)入后
id: pkcs11, name: pkcs11 engine
?
2. 導(dǎo)出USBKEY中的CA證書
需要導(dǎo)出CA證書,這是因為CA需要填充X.509格式中的證書的發(fā)行機(jī)構(gòu)名稱。
通過” LOAD_CERT_CTRL”命令來獲取證書,輸入的參數(shù)為證書的表示。
"slot_0-id_37af001ddbd525e640ca3c3f6d78b009741d1f48"
slot_0 ??? PKCS#11 表示的第一個插槽(一個插槽配一個Token)
id_37af001ddbd525e640ca3c3f6d78b009741d1f48 證書的Id號(同一組公私鑰對和證書這個ID是相同的),這個ID可以通過pkcs11-tools獲得。
命令返回的parms.cert就指向一個X.509結(jié)構(gòu)的證書。
但是必須要注意的是導(dǎo)出證書前,必須設(shè)置過正確的PIN
??? struct {
?????? const char * cert_id;
?????? X509 * cert;
??? } parms;
?
??? parms.cert_id = "slot_0-id_37af001ddbd525e640ca3c3f6d78b009741d1f48";
??? parms.cert = NULL;
??? ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &parms, NULL, 1);
?
?
3. 證書請求
通過JAVA生成的證書請求,直接復(fù)制粘貼到工程目錄下的文本文件certreq.txt即可,并且需要包含BEGIN/END部分。
?
4. 從證書請求中獲取用戶信息
??? //設(shè)置證書的主體名稱,req就是剛剛生成的請求證書
??? X509_set_subject_name(m_pClientCert, X509_REQ_get_subject_name(req));
??? //設(shè)置證書的公鑰信息
??? X509_set_pubkey(m_pClientCert, X509_PUBKEY_get(req->req_info->pubkey));
?
5. 設(shè)置證書的簽發(fā)者信息
??? //設(shè)置證書的簽發(fā)者信息,m_pCACert是CA證書
??? X509_set_issuer_name(m_pClientCert, X509_get_subject_name(m_pCACert));
?
6. 證書簽名
注意這里采用的是sha1的摘要算法,當(dāng)然也可使用MD5
??? //設(shè)置簽名值
??? // EVP_sha1 是否可以設(shè)置成別的,如EVP_md5
??? // 這樣一份X509證書就生成了,下面的任務(wù)就是對它進(jìn)行編碼保存。
??? X509_sign(m_pClientCert, m_pCAKey, EVP_sha1());
?
此處還有些補(bǔ)充的內(nèi)容,為了驗證X509_sign調(diào)用PKCS#11接口的情況,自己實現(xiàn)了一個PKCS#11的包裝殼(68個導(dǎo)出函數(shù)),實現(xiàn)時注意C_GetFunctionList應(yīng)該指向本包裝殼的函數(shù),不然錯誤的使用實際的C_GetFunctionList作返回結(jié)構(gòu)便也就失去意義了。X509_sign的調(diào)用方式還是不同的,java中如果使用SHA1WithRSA傳入到PKCS#11接口的C_Sign或者C_SignUpdate的數(shù)據(jù)是完整的明文,但是X509_sign傳入的是一個ASN.1 Sequence的一個結(jié)構(gòu),結(jié)構(gòu)中包含待簽名數(shù)據(jù)的摘要散列。
舉例來說:
?
待加密的數(shù)據(jù)是Hello World! ,在C_Sign傳入的數(shù)據(jù)中就可以發(fā)現(xiàn)Hello World!的SHA-1的摘要散列。
待加密:Hello World!
SHA-1: 2EF7BDE608CE5404E97D5F042F95F89F1C232871
?
C_Sign:
30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 2e f7 bd e6 08 ce 54 04 e9 7d 5f 04 2f 95 f8 9f 1c 23 28 71
?
使用ASN.1dump來觀察就看的更加清楚了。
?
從X509_sign的實現(xiàn)也可以看的這一點,在RSA_Sign之前首先進(jìn)行摘要算法,并且這個摘要并不使用PKCS#11中的接口函數(shù),直接使用Openssl自己的摘要算法,所以傳入到最后的只是明文摘要散列了。
int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md)
{
//先進(jìn)行ret->cert_info->signature,以及ret->sig_alg的設(shè)置;
inl=i2d_X509_CINF(ret->cert_info,NULL);//求出證書編碼后的長度
buf_in=(unsigned char *)OPENSSL_malloc((unsigned int)inl);//申請空間
outll=outl=EVP_PKEY_size(pkey1);
buf_outl=(unsigned char *)OPENSSL_malloc((unsigned int)inl);
if ((buf_in == NULL) ││ (buf_outl== NULL))
??? {
??? outl=0;
??? goto err;
??? }
??? p=buf_in;//p與buf-in共享一段地址
??? i2d_X509_CINF(ret->cert_info,&p);//將證書編碼存入buf-in
??? EVP_MD_CTX_init(&ctxl);//初始化
??? EVP_SignInit(&ctxl,dgst);//將需要使用的摘要算法存入ctxl中
??? EVP_SignUpdate(&ctxl,(unsigned char *)buf_in,inl);//存入證書的編碼值
??? EVP_DigestFinal(&ctxl,&(m[0]),&m_len);//求取編碼的長度為m_len摘要值存入m中
??? RSA_sign(ctxl->digest->type,m,m_len,buf_out,outl,pkey->pkey.rsa)//求取摘要值的簽名值,最后將長度為outl的簽名值存入buf-out。
??? ......
?
7.?最后生成的證書
USBKEY?中包含證書是向三級CA申請的,所以處于第四級,使用第四級證書來簽發(fā)新證書,”?超人”寶寶就只能到第五級去了(也許是第五項修煉吧)。
其實還是個問題,第四級證書報“此證書似乎對于所選的目的是有效。”,出現(xiàn)此問題的原因嵌入在消息中的簽名證書鏈包含一個無效的交叉引用,估計第四級是一個用戶證書,要消除這個感嘆號,第四級證書的證書用法中應(yīng)該包含Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)這幾項
8.完整代碼
// SignWithOpenSSL.cpp :?定義控制臺應(yīng)用程序的入口點。
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/engine.h>
#include <openssl/conf.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/x509v3.h>
#define OPENSSL_LOAD_CONF
#define UC_ENGINE_SOPATH "C:\\Program Files\\Smart card bundle\\engine_pkcs11.dll"
#define UC_EXPECTED_ENGINE_ID "pkcs11"
#define UC_ENGINE_MODULEPATH "C:\\Windows\\System32\\DMPKCS11.dll"
//?列出當(dāng)前所有的engine
/*
ENGINE *ENGINE_get_first(void);
ENGINE *ENGINE_get_last(void);
ENGINE *ENGINE_get_next(ENGINE *e);
ENGINE *ENGINE_get_prev(ENGINE *e);
*/
void ListEngine()
{
??? ENGINE *current;
??? current = ENGINE_get_first();
??? if( NULL != current )
??? {
?????? printf("id: %s, name: %s\n",
?????????? ENGINE_get_id(current),
?????????? ENGINE_get_name(current));
?????? while( NULL != (current = ENGINE_get_next(current)))
?????? {
?????????? printf("id: %s, name: %s\n",
????????????? ENGINE_get_id(current),
????????????? ENGINE_get_name(current));
?????? }
??? }
}
/*存儲證書*/
int save_cert(X509 *pCert, char *pCertFile)
{
??? BIO *pbio;
??? if(NULL == pCert || NULL == pCertFile)
??? {
?????? return -1;
??? }
??? pbio = BIO_new_file(pCertFile, "w");
??? if(NULL == pbio)
??? {
?????? return -1;
??? }
??? if(!i2d_X509_bio(pbio, pCert))
??? {
?????? printf("save_cert:call PEM_write_bio_X509 error ");
?????? return -1;
??? }
??? printf("Bingo, New Cert is borned\n");
??? BIO_free(pbio);
??? return 0;
}
void add_subject_entity(X509_NAME *pSubjectName, char *key, char *value)
{
??? int nid;
??? X509_NAME_ENTRY *ent;
??? if( (nid =OBJ_txt2nid(key)) == NID_undef )
??? {
?????? printf(" add_subject_entity:concert nid error");
?????? return ;
??? }
??? ent = X509_NAME_ENTRY_create_by_NID( NULL, nid, MBSTRING_UTF8,
?????? (unsigned char*)value, -1);
??? if(ent == NULL)
??? {
?????? printf("add_subject_entity:create ent error");
?????? return;
??? }
??? if(X509_NAME_add_entry(pSubjectName, ent, -1, 0) != 1)
??? {
?????? printf("add_subject_entity:add to subjectname error");
?????? return;
??? }
??? return;
}
int CreateX509Cert(X509 *m_pCACert, EVP_PKEY *m_pCAKey)
{
??? //?讀取證書請求
??? BIO?????????? *in;
??? X509_REQ????? *req=NULL,**req2=NULL;
??? in = BIO_new_file("certreq.txt","r");
??? req = PEM_read_bio_X509_REQ(in,NULL,NULL,NULL);
??? if( req == NULL )
??? {
?????? printf("DER Decode Error!\n");
??? }
??? else
??? {
?????? printf("DER Decode Success!\n");
??? }
??? //?使用usbkey中的私鑰進(jìn)行簽名
??? X509 *m_pClientCert;
??? m_pClientCert = X509_new();
??? //設(shè)置版本號
??? X509_set_version(m_pClientCert, 2);
??? //設(shè)置證書序列號,這個sn就是CA中心頒發(fā)的第N份證書
??? ASN1_INTEGER_set(X509_get_serialNumber(m_pClientCert),100);
??? //設(shè)置證書開始時間
??? X509_gmtime_adj(X509_get_notBefore(m_pClientCert),0);
??? //設(shè)置證書結(jié)束時間
??? X509_gmtime_adj(X509_get_notAfter(m_pClientCert), (long)60*60*24);
??? //設(shè)置證書的主體名稱,req就是剛剛生成的請求證書
??? X509_set_subject_name(m_pClientCert, X509_REQ_get_subject_name(req));
??? //設(shè)置證書的公鑰信息
??? X509_set_pubkey(m_pClientCert, X509_PUBKEY_get(req->req_info->pubkey));
??? //設(shè)置證書的簽發(fā)者信息,m_pCACert是CA證書
????X509_set_issuer_name(m_pClientCert, X509_get_subject_name(m_pCACert));
??? //設(shè)置擴(kuò)展項目
??? X509V3_CTX ctx;
??? X509V3_set_ctx(&ctx, m_pCACert, m_pClientCert, NULL, NULL, 0);
??? X509_EXTENSION *x509_ext = X509_EXTENSION_new();
??? x509_ext = X509V3_EXT_conf(NULL, &ctx, "HELLO", "HELLO");
??? X509_add_ext(m_pClientCert,x509_ext,-1);
??? //設(shè)置簽名值
??? // EVP_sha1?是否可以設(shè)置成別的,如EVP_md5
??? //?這樣一份X509證書就生成了,下面的任務(wù)就是對它進(jìn)行編碼保存。
????X509_sign(m_pClientCert, m_pCAKey, EVP_sha1());
??? //?輸出證書
??? save_cert(m_pClientCert, "d:\\test.cer");
??? return 0;
}
int main(int argc, CHAR* argv[])
{
??? ENGINE *e;
??? const char *engine_id = "pkcs11";
??? const char *key_id = "37af001ddbd525e640ca3c3f6d78b009741d1f48";
??? UI_METHOD *ui_method = NULL;
??? EVP_PKEY *priv_key;
??? void *cb_data;
??? const char *config_name = NULL;
??? BIO *bio_err=NULL;
??? /* Load the config file */
??? //OPENSSL_config(config_name); //?不使用Openssl0.9.8e的配置文件來導(dǎo)入PKCS11
??? ENGINE_load_dynamic();
??? ListEngine();
??? printf("\nLoading Dynamic...\n");
??? /* Register engine */
??? printf("Registering enginen");
??? e = ENGINE_by_id("dynamic");
??? if(!e) {
?????? /* the engine isn't available */
?????? printf("The engine isn't available\n");
?????? return 0;
??? }
??? //int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)(void));
??? ENGINE_ctrl(e, ENGINE_CTRL_SET_LOGSTREAM, 0, bio_err, 0);
??? //?設(shè)置engine_pkcs11的路徑
??? ENGINE_ctrl_cmd_string(e, "SO_PATH", UC_ENGINE_SOPATH, 0);
??? ENGINE_ctrl_cmd_string(e, "ID", UC_EXPECTED_ENGINE_ID, 0);
??? ENGINE_ctrl_cmd_string(e, "LIST_ADD", "1", 0);
??? ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0);
??? //?設(shè)置USBKEY廠商PKCS#11實現(xiàn)的路徑
??? ENGINE_ctrl_cmd_string(e, "MODULE_PATH", UC_ENGINE_MODULEPATH, 0);
??? //?設(shè)置PIN碼
??? if(!ENGINE_ctrl_cmd_string(e, "PIN", "111111", 0)){
?????? printf("Error sending PIN to = engine");
?????? ENGINE_free(e);
?????? return 0;
??? }??
??? ListEngine();
??? if(!ENGINE_init(e)) {
?????? /* the engine couldn't initialise, release 'e' */
?????? printf("The engine couldn't initialise\n");
?????? ENGINE_free(e);
?????? return 0;
??? }
??? if(!ENGINE_register_RSA(e)){
?????? /* This should only happen when 'e' can't initialise, but the previous
?????? * statement suggests it did. */
?????? printf("This should not happen\n");
?????? abort();
??? }
??? //?直接從usb-key中導(dǎo)入證書,但必須初始化后。
??? struct {
?????? const char * cert_id;
?????? X509 * cert;
??? } parms;
??? parms.cert_id = "slot_0-id_37af001ddbd525e640ca3c3f6d78b009741d1f48";
??? parms.cert = NULL;
??? ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &parms, NULL, 1);
??? // Load private key
??? printf("Loading private key\n");
??? priv_key = ENGINE_load_private_key(e, key_id, ui_method, &cb_data);
??? //?產(chǎn)生證書
??? CreateX509Cert(parms.cert, priv_key);
??? // Release the functional reference from ENGINE_init()
??? ENGINE_finish(e);
??? // Release the structural reference from ENGINE_by_id()
??? ENGINE_free(e);
??? return 0;
}
?
1.4??????????????參考
[1]?DNSSEC Signers and OpenSSL
[END]
總結(jié)
以上是生活随笔為你收集整理的Openssl和PKCS#11的故事的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 恶心的openssl。。。
- 下一篇: sha1withRSA md5withR