原文:http://www.cnblogs.com/adylee/archive/2009/08/03/1537813.html

1.1   What to do

通过Openssl和PKCS#11接口,使用USBKEY中的私钥和证书来签发一个下级证书。

1.2              背景

数字证书颁发过程一般为:用户首先产生自己的密钥对,并将公共密钥及部分个人身份信息传送给认证中心。认证中心在核实身份后,将执行一些必要的步骤,以确信请求确实由用户发送而来,然后,认证中心将发给用户一个数字证书,该证书内包含用户的个人信息和他的公钥信息,同时还附有认证中心的签名信息。

一个标准的X.509数字证书包含以下一些内容:

证书的版本信息;

证书的序列号,每个证书都有一个唯一的证书序列号;

证书所使用的签名算法;

证书的发行机构名称,命名规则一般采用X.500格式;

证书的有效期,现在通用的证书一般采用UTC时间格式,它的计时范围为1950-2049;

证书所有人的名称,命名规则一般采用X.500格式;

证书所有人的公开密钥;

证书发行者对证书的签名。

简而言之,CA从PKCS#10证书请求(或者P7格式)中读取用户信息和公钥信息,使用这些信息封装成一个X.509格式(可能是不同版本,比较普遍是V3),此时唯一没有包括的是证书发行者对证书的签名,此时使用CA的私钥进行签名,得到签名值后CA将其填充到X.509相对应的结构中去,一个X.509证书宝宝就此诞生了。

此处唯一不同的是CA的公私钥对和证书都存放在USBKEY中(当然也能存放在加密机或加密卡中),所以将通过USBKEY的PKCS#11接口完成上述操作,而证书相关操作就由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-----

第三、程序签发

1. engine_pkcs11的使用方式

使用openssl调用USBKEY的PKCS#11接口,可以通过OpenSC项目的engine_pkcs11接口。原本使用编写openssl配置文件方式(见[1]),但是就是无法使用,两次调用ListEngine()都无法发现pkcs11 engine的影子。

Openssl.conf 内容:

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:

所以最后还是使用动态调用的方式导入pkcs11 engine,即ENGINE_load_dynamic。所以两次调用ListEngine()后发现, dynamic engine导入pkcs11 engine后就会被其替换。

导入前

id: dynamic, name: Dynamic engine loading support

导入后

id: pkcs11, name: pkcs11 engine

2. 导出USBKEY中的CA证书

需要导出CA证书,这是因为CA需要填充X.509格式中的证书的发行机构名称。

通过” LOAD_CERT_CTRL”命令来获取证书,输入的参数为证书的表示。

"slot_0-id_37af001ddbd525e640ca3c3f6d78b009741d1f48"

slot_0     PKCS#11 表示的第一个插槽(一个插槽配一个Token)

id_37af001ddbd525e640ca3c3f6d78b009741d1f48 证书的Id号(同一组公私钥对和证书这个ID是相同的),这个ID可以通过pkcs11-tools获得。

命令返回的parms.cert就指向一个X.509结构的证书。

但是必须要注意的是导出证书前,必须设置过正确的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生成的证书请求,直接复制粘贴到工程目录下的文本文件certreq.txt即可,并且需要包含BEGIN/END部分。

4. 从证书请求中获取用户信息

//设置证书的主体名称,req就是刚刚生成的请求证书

X509_set_subject_name(m_pClientCert, X509_REQ_get_subject_name(req));

//设置证书的公钥信息

X509_set_pubkey(m_pClientCert, X509_PUBKEY_get(req->req_info->pubkey));

5. 设置证书的签发者信息

//设置证书的签发者信息,m_pCACert是CA证书

X509_set_issuer_name(m_pClientCert, X509_get_subject_name(m_pCACert));

6. 证书签名

注意这里采用的是sha1的摘要算法,当然也可使用MD5

//设置签名值

// EVP_sha1 是否可以设置成别的,如EVP_md5

// 这样一份X509证书就生成了,下面的任务就是对它进行编码保存。

X509_sign(m_pClientCert, m_pCAKey, EVP_sha1());

此处还有些补充的内容,为了验证X509_sign调用PKCS#11接口的情况,自己实现了一个PKCS#11的包装壳(68个导出函数),实现时注意C_GetFunctionList应该指向本包装壳的函数,不然错误的使用实际的C_GetFunctionList作返回结构便也就失去意义了。X509_sign的调用方式还是不同的,java中如果使用SHA1WithRSA传入到PKCS#11接口的C_Sign或者C_SignUpdate的数据是完整的明文,但是X509_sign传入的是一个ASN.1 Sequence的一个结构,结构中包含待签名数据的摘要散列。

举例来说:

待加密的数据是Hello World! ,在C_Sign传入的数据中就可以发现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的实现也可以看的这一点,在RSA_Sign之前首先进行摘要算法,并且这个摘要并不使用PKCS#11中的接口函数,直接使用Openssl自己的摘要算法,所以传入到最后的只是明文摘要散列了。

int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md)
{
//先进行ret->cert_info->signature,以及ret->sig_alg的设置;
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申请的,所以处于第四级,使用第四级证书来签发新证书,” 超人”宝宝就只能到第五级去了(也许是第五项修炼吧)。

其实还是个问题,第四级证书报“此证书似乎对于所选的目的是有效。”,出现此问题的原因嵌入在消息中的签名证书链包含一个无效的交叉引用,估计第四级是一个用户证书,要消除这个感叹号,第四级证书的证书用法中应该包含Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)这几项

8.完整代码

// SignWithOpenSSL.cpp : 定义控制台应用程序的入口点。

//

#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"

// 列出当前所有的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中的私钥进行签名

X509 *m_pClientCert;

m_pClientCert = X509_new();

//设置版本号

X509_set_version(m_pClientCert, 2);

//设置证书序列号,这个sn就是CA中心颁发的第N份证书

ASN1_INTEGER_set(X509_get_serialNumber(m_pClientCert),100);

//设置证书开始时间

X509_gmtime_adj(X509_get_notBefore(m_pClientCert),0);

//设置证书结束时间

X509_gmtime_adj(X509_get_notAfter(m_pClientCert), (long)60*60*24);

//设置证书的主体名称,req就是刚刚生成的请求证书

X509_set_subject_name(m_pClientCert, X509_REQ_get_subject_name(req));

//设置证书的公钥信息

X509_set_pubkey(m_pClientCert, X509_PUBKEY_get(req->req_info->pubkey));

//设置证书的签发者信息,m_pCACert是CA证书

X509_set_issuer_name(m_pClientCert, X509_get_subject_name(m_pCACert));

//设置扩展项目

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);

//设置签名值

// EVP_sha1 是否可以设置成别的,如EVP_md5

// 这样一份X509证书就生成了,下面的任务就是对它进行编码保存。

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的配置文件来导入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);

// 设置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);

// 设置USBKEY厂商PKCS#11实现的路径

ENGINE_ctrl_cmd_string(e, "MODULE_PATH", UC_ENGINE_MODULEPATH, 0);

// 设置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中导入证书,但必须初始化后。

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);

// 产生证书

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]

Openssl和PKCS#11的故事相关推荐

  1. PKCS#11 in OP-TEE

    目录 1.PCCS#11简介 2.PKCS#11 specifications 3.Achievements 4.编译和运行 5.libckteec 1.PCCS#11简介 PKCS#11标准定义了独 ...

  2. java pkcs#11读取证书加解密(初学-分享)

    java pkcs#11读取证书加解密(初学-分享) http://www.cnblogs.com/sunfb/archive/2013/02/28/2937227.html 插入USB-KEY, 想 ...

  3. 转: 中/英文资料 PKCS #11 函数列表

    https://www.cryptsoft.com/pkcs11doc/v220/ PKCS #11 函数列表 PKCS #11 加密令牌接口函数列表中文版 , 取自 Oracle Solaris 1 ...

  4. [crypto]-05.1-PKCS PKCS#1 PKCS#7 PKCS#11的介绍

    相关推荐:          [crypto]-01-对称加解密AES原理概念详解          [crypto]-02-非对称加解密RSA原理概念详解          [crypto]-03- ...

  5. JDK5.0环境下配置PKCS#11

    JDK5.0环境下配置PKCS#11 发表日期:2008-1-5 新浪微博 QQ空间 QQ微博 百度搜藏 腾讯朋友 QQ收藏 百度空间 人人网 开心网 这篇文章不介绍具体的编程方法,而是针对PKCS# ...

  6. PKCS#11标准解读-Cryptoki库如何工作

    此为完结篇,介绍Cryptoki库如何工作. PKCS#11是使用非常普遍的密码设备接口,在实际应用中,国密的密码设备应用接口规范GMT0018与之作用相同,在技术体系架构中处于类似的位置. 在密码产 ...

  7. PKCS#11及CSP接口标准

    RSA非对称密码算法的三个创始人的姓的第一个字母联合起来就是RSA了,他们三个创建的公司的名字也就叫做RSA.在RSA有一个著名的公钥算法的实验室,这个实验室颁发的一系列行业标准就称作为PKCS标准, ...

  8. PKCS# 11和CSP概念技术对比

    本文针对PKCS# 11和CSP进行学习,以及基于CSP的USBKey密码符合性检测平台的模块设计. 开发商按照标准开发 CSP,就可以把自己开发的软件或硬件密码模块无缝连接到 CryptoAPI 的 ...

  9. CSP和PKCS#11的关系

    一.引言  PKI,被誉为现代信息社会安全的基石,它能够为所有网络透明地提供加密和数字签名等密码服务所需的密钥和证书管理功能,能够提供认证.访问控制.数据完整性机密性.和不可否认性等核心安全服务.由于 ...

最新文章

  1. C++ 单例模式析构函数的运用,析构函数的线程安全
  2. python语法syntaxerror怎么修改-Python 语法错误
  3. 学界 | 数据并行化对神经网络训练有何影响?谷歌大脑进行了实证研究
  4. F5 LTM设备降级实战
  5. python从低到高排序_使用python对matplotlib直方图中的xaxis值从最低值到最高值排序...
  6. 神策数据获华农保险2020年“最佳合作机构”荣誉称号
  7. linux文件属性和类型
  8. gddr6速率_GDDR6 显存两年后问世:比 GDDR5X 更快,速率可达 16Gbps
  9. linux下安装微信,qq,企业微信,百度网盘,Foxmail等软件方法
  10. C++之指针探究(十八):typedef结合结构体指针
  11. 洛谷——P1657 选书
  12. WordPress 简约主题 NDNAV 网址导航网站模版
  13. linux文件夹怎么看md5,怎么看md5
  14. multitask_note
  15. 测试用例的思路---矿泉水瓶
  16. java中principal对象,如何使用OAuth2获取Spring的自定义Principal对象?
  17. element tab选项卡标签样式
  18. 单片机开发用到的intrins.h文件
  19. 总结在使用vue-photo-preview---图片查看大图插件,点击图片无法显示预览等问题
  20. IDA 和 IDA-Python 学习笔记

热门文章

  1. 【手写数据结构】双链表最详细图解
  2. Java 设计模式——外观模式
  3. 基于正态分布的图片高斯模糊算法
  4. MyBatis Generator:代码生成器
  5. 转:初学者简易 .vimrc编写指南
  6. matlab双重for训话,Question 2:
  7. 故障模块名称kernelbase.dll_故障码都看不懂,你还修啥车?
  8. 箭头函数可被打印却不能调用_5种应该避免使用箭头函数的情况
  9. java 文件上传 jar_JavaWeb 之 使用 commons-fileupload.jar 实现文件上传
  10. 嵌入式linux 分区挂载,嵌入式linux系统的开发——文件系统的分区和挂载