2019年8月

实现X509格式证书的链式校验

//cert_public.cpp : Defines the exported functions for the DLL application.//#include"stdafx.h"#include<string.h>#include<stdio.h>#include<string>#include<stdarg.h>#include<openssl/pem.h>#include<openssl/x509.h>#include<openssl/x509v3.h>#include"sm_public.h"

extern "C"{/*****************************************************************************
* @brief : 通过X509格式的字符串得到一个X509内部结构对象
* @author : xiaomw
* @date : 2019/8/13
* @inparam : plaintext X509格式的字符串
* @return : 成功返回非0 失败返回0
****************************************************************************
*/CRYPT_SMDLL_Cvoid* X509_Str2Object(const char *in)
{
BIO
*bio_obj =NULL;
X509
* x509_obj =NULL;//根据字符串内容,构造一个BIO对象 bio_obj = BIO_new_mem_buf(in, strlen(in));if (NULL ==bio_obj){returnNULL;
}
//调用接口创建呗 x509_obj = (X509 *) PEM_ASN1_read_bio((d2i_of_void *)d2i_X509, PEM_STRING_X509, bio_obj, NULL, NULL, NULL);//释放对象 BIO_free(bio_obj);returnx509_obj;
}

X509
* _X509_RootCertStr2Object(const char *in)
{
BIO
*bio_obj =NULL;
X509
* x509_obj =NULL;
X509_INFO
*itmp =NULL;
STACK_OF(X509_INFO)
*inf =NULL;//根据字符串内容,构造一个BIO对象 bio_obj = BIO_new_mem_buf(in, strlen(in));if (NULL ==bio_obj){returnNULL;
}
inf
=PEM_X509_INFO_read_bio(bio_obj, NULL, NULL, NULL);//释放对象 BIO_free(bio_obj);if (NULL ==inf){returnNULL;
}
for (int i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp
=sk_X509_INFO_value(inf, i);if (itmp->x509) {//复制一个X509对象 x509_obj = X509_dup(itmp->x509);
sk_X509_INFO_pop_free(inf, X509_INFO_free);
returnx509_obj;
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
returnNULL;
}
static UINT32 _x509_get_entry_value(const X509_NAME* x509_name, X509_NAME_ENTRY_TYPE nEntry, char*value)
{
ASN1_STRING
* strNameValue =NULL;const X509_NAME_ENTRY *x_name_entry =NULL;//获取到相应的NameEntry x_name_entry =X509_NAME_get_entry(x509_name, nEntry);if (NULL ==x_name_entry)
{
return 1;
}

strNameValue
=X509_NAME_ENTRY_get_data(x_name_entry);if (NULL ==strNameValue)
{
return 1;
}
//拷贝内容 lstrcpyA(value, (const char*)ASN1_STRING_data(strNameValue));return 0;
}

CRYPT_SMDLL_C UINT32 X509_GetIssuerName(
const void* pX509_object, X509_NAME_ENTRY_TYPE nEntry, char*value)
{
const X509_NAME* x_name =NULL;
ASN1_STRING
* strNameValue =NULL;const X509_NAME_ENTRY *x_name_entry =NULL;if (NULL ==pX509_object)
{
return 1;
}
//校验nEntry if (nEntry < NID_commonName || nEntry >NID_organizationalUnitName)
{
return 1;
}
//获取到值 x_name = X509_get_issuer_name((const X509*)pX509_object);if (NULL ==x_name)
{
return 1;
}
//调用一样的接口返回 return_x509_get_entry_value(x_name, nEntry, value);
}

CRYPT_SMDLL_C UINT32 X509_GetSubjectName(
const void* pX509_object, X509_NAME_ENTRY_TYPE nEntry, char*value)
{
const X509_NAME* x_name =NULL;
ASN1_STRING
* strNameValue =NULL;const X509_NAME_ENTRY *x_name_entry =NULL;if (NULL ==pX509_object)
{
return 1;
}
//校验nEntry if (nEntry < NID_commonName || nEntry >NID_organizationalUnitName)
{
return 1;
}
//获取到值 x_name = X509_get_subject_name((const X509*)pX509_object);if (NULL ==x_name)
{
return 1;
}
//调用一样的接口返回 return_x509_get_entry_value(x_name, nEntry, value);
}
classMini_X509_Verify_Class
{
private:
X509
*x509_obj;
X509_STORE
*x509_store;
STACK_OF(X509)
*x509_chain;
X509_STORE_CTX
*x509_store_ctx;public:
Mini_X509_Verify_Class(): x509_obj(NULL), x509_store(NULL), x509_chain(NULL), x509_store_ctx(NULL){}
~Mini_X509_Verify_Class()
{
if (NULL !=x509_obj){
X509_free(x509_obj);
}
if (NULL !=x509_store){
X509_STORE_free(x509_store);
}
if (NULL !=x509_chain){
sk_X509_pop_free(x509_chain, X509_free);
}
if (NULL !=x509_store_ctx){
X509_STORE_CTX_cleanup(x509_store_ctx);
}
}

UINT32 set_verify_cert(
const char*cert)
{
x509_obj
= (X509*)X509_Str2Object(cert);if (NULL ==x509_obj){return 1;
}
return 0;
}

UINT32 add_root_cert(
const char*root_cert)
{
//没有存储对象,则先创建一个 if (NULL ==x509_store){
x509_store
=X509_STORE_new();if (NULL ==x509_store)
{
return 1;
}
}
//得到X509对象 X509* x509_root_obj = (X509*)_X509_RootCertStr2Object(root_cert);if (NULL ==x509_root_obj){return 1;
}
//加入存储区 if (0 ==X509_STORE_add_cert(x509_store, x509_root_obj)){

X509_free(x509_root_obj);
return 1;
}
return 0;
}

UINT32 add_cert_chain(
const char*cert_node)
{
//没有X509链,则先创建一个 if (NULL ==x509_chain){
x509_chain
=sk_X509_new_null();if (NULL ==x509_chain)
{
return 1;
}
}
//得到X509对象 X509* x509_node_obj = (X509*)X509_Str2Object(cert_node);if (NULL ==x509_node_obj){return 1;
}
//加入X509链 if (0 ==sk_X509_push(x509_chain, x509_node_obj)){

X509_free(x509_node_obj);
return 1;
}
return 0;
}

BOOL verify(X509
*cert)
{
X509
* pTemp =NULL;
EVP_PKEY
*pkey=NULL;//每次都采取遍历证书的办法吧 if (NULL !=x509_chain){//获取链条总长度 int nSize =sk_X509_num(x509_chain);for (int index = 0; index < nSize; index ++) {//取出相应的数值 pTemp =sk_X509_value(x509_chain, index);//取出公钥 pkey =X509_get_pubkey(pTemp);//测试能否验证过,能够验证过,递归下去验证 if(X509_verify(cert, pkey)){returnverify(pTemp);
}
}
}
//如果链条中都验证不过,试一下根证书 STACK_OF(X509_OBJECT) * root_obj =X509_STORE_get0_objects(x509_store);if (NULL ==root_obj) {returnFALSE;
}
int root_obj_num =sk_X509_OBJECT_num(root_obj);if (root_obj_num <= 0) {returnFALSE;
}

X509
*root_cert =NULL;
X509_OBJECT
* x509_obj =NULL;for(int j = 0; j < root_obj_num; j ++){//取出OBJ x509_obj =sk_X509_OBJECT_value(root_obj, j);//取出证书 root_cert =X509_OBJECT_get0_X509(x509_obj);//取出公钥 pkey =X509_get_pubkey(root_cert);//测试能否验证过,能够验证过,返回成功 if(X509_verify(cert, pkey)){returnTRUE;
}
}
returnFALSE;
}

UINT32 verify_cert()
{
if (!x509_obj){return 1;
}
if(verify(x509_obj)) {return 0;
}
return 1;
}
};
//验证一个证书,输入根证书及相应的二级、三级...证书 UINT32 X509_Verify(const char *cert, const char*root_cert, UINT32 ulLevelNum, ...)
{
UINT32 index
= 0;
va_list arg_ptr;
const char* cert_node =NULL;
Mini_X509_Verify_Class x509_verify;
if (NULL ==cert)
{
return 1;
}
if (NULL ==root_cert)
{
return 1;
}
//最多支持6级 if (ulLevelNum > 6)
{
return 1;
}
//存放待验证证书 if(x509_verify.set_verify_cert(cert)) {return 1;
}
//存放根证书 if(x509_verify.add_root_cert(root_cert)) {return 1;
}

va_start(arg_ptr, ulLevelNum);
for(index = 0; index < ulLevelNum; index ++){
cert_node
= va_arg(arg_ptr, const char*);//存放证书链接 if(x509_verify.add_cert_chain(cert_node)) {return 1;
}
}
va_end(arg_ptr);
//开始校验 if(x509_verify.verify_cert()) {return 1;
}
return 0;
}

};

DEMO验证代码

BOOL LoadCertFileToStr(const char* strFile, std::string&strBuff)
{
//打开文件,读取内容 FILE * hSrcfile =NULL;
fopen_s(
&hSrcfile, strFile,"rb");if (hSrcfile ==NULL) {returnFALSE;
}
//读取文件 fseek (hSrcfile, 0, SEEK_END); ///将文件指针移动文件结尾 long size = ftell (hSrcfile); ///求出当前文件指针距离文件开始的字节数 //分配内存 strBuff.resize(size + 1, '\0');//重新开始读取文件 fseek (hSrcfile, 0, SEEK_SET);//读取文件 fread(&strBuff[0], size,1, hSrcfile);

fclose(hSrcfile);
returnTRUE;
}
int main(int argc, char*argv[])
{
//cwSL3D_test_sum();//测试能否成功调用所有接口//至少两个证书文件 if (argc < 3) {
std::cout
<< "缺少证书文件" <<std::endl;return -1;
}
if (argc > 9) {
std::cout
<< "证书文件过多,目前不支持" <<std::endl;return -1;
}
//第一个证书文件,根证书文件 std::stringroot_cert;if (!LoadCertFileToStr(argv[1], root_cert)) {
std::cout
<< "读取根证书文件失败" <<std::endl;return -1;
}
//第二个证书文件,待验证的证书 std::stringcert_file;if (!LoadCertFileToStr(argv[2], cert_file)) {
std::cout
<< "读取待验证证书文件失败" <<std::endl;return -1;
}
//后续的证书文件,证书链条上的文件 std::string cert_chain[6];for(int index = 0; index < 6 && index < argc - 3; index ++) {if (!LoadCertFileToStr(argv[index + 3], cert_chain[index])) {
std::cout
<< "读取证书文件失败:" << argv[index + 2] <<std::endl;return -1;
}
}
//直接调用接口验证吧 if (X509_Verify(cert_file.c_str(), root_cert.c_str(), argc - 3, cert_chain[0].c_str(), cert_chain[1].c_str(), cert_chain[2].c_str(), cert_chain[3].c_str(), cert_chain[4].c_str(), cert_chain[5].c_str())) {
std::cout
<< "验证证书文件失败" <<std::endl;return -1;
}
return 0;
}

 

 声明:OpenSSL之命令行详解是根据卢队长发布在https://blog.csdn.net/as3luyuan123/article/details/16105475的系列文章整理修改而成,我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!

1 标准命令
  查看帮助的办法:openssl 命令 -h。

命令 功能 备注
证书类 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX =====
req PKCS10 X.509证书签名请求(CSR)管理。 申请证书
x509 X.509证书管理。显示证书信息、转换证书格式、签名证书请求及改变证书信任设置 证书工具
verify X.509证书验证。 证书验证
ca 证书颁发机构(CA)管理。签发证书请求和生成CRL,维护一个已签发证书状态的文本数据库。 证书中心
crl 证书撤销清单(CRL)管理。工具,用于处理PEM或DER格式的CRL文件 证书吊销
crl2pkcs7 CRL到PKCS#7转换。根据CRL或者证书生成pkcs7消息; 证书吊销
对称加密 *XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX* =====
enc 加密和解密 对称加解密
摘要 *XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX* ====
dgst 消息摘要计算。 摘要集合
非对称 XXXXXXXXX所有的非对称都可以使用pkey系列来操作,鼓励使用pkey系列XXXXXXX ======
pkey 公钥和私钥管理。 非对称加密
pkeyparam 公钥算法参数管理。 非对称加密
pkeyutl 公钥算法加密操作实用程序。 非对称加密
genpkey 生成私钥或参数。 非对称加密
PKI XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
pkcs7 PKCS7加密消息语法,各种消息存放的格式标准;用于处理DER或者PEM格式的pkcs7文件 消息格式
pkcs8 私钥转换工具,pkcs8格式 转换证书
pkcs12 PKCS12数据管理。工具,用于生成和分析pkcs12文件 个人证书
工具 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX =======
asn1parse 解析ASN.1序列。用于诊断ASN.1结构的工具,也可从ASN.1数据中提取数据; 诊断工具
ciphers 密码套件描述,显示支持的加密套件
cms CMS(加密消息语法)实用程序
engine 引擎(loadble模块)信息和操纵。
errstr 错误字符串转换的错误号。
nseq 创建或检查netscape证书序列,多证书与netscape证书序列间相互转化 序列转换
ocsp 在线证书状态协议实用程序。 在线证书状态
passwd 生成散列密码。生成各种口令密文
prime 检查一个数是否是素数
rand 生成伪随机字节。
smime S / MIME邮件处理。处理S/MIME邮件,加密、解密、签名和验证 邮件验证工具
speed 算法速度测量。,调整测试库的性能 性能工具
s_time SSL连接定时器。提供的SSL/TLS性能测试工具,测试服务 性能工具
spkac SPKAC打印和生成实用程序
ts 时间戳机构工具(客户端/服务器)
sess_id SSL会话数据管理。SSL/TLS协议的session处理工具 协议会话工具
s_client 这将实现一个通用SSL / TLS客户端,可以建立与远程服务器的SSL / TLS透明连接。 协议会话工具
s_server 这实现了一个通用SSL / TLS服务器,它接受来自远程客户端的SSL / TLS连接。 协议会话工具
version OpenSSL版本信息。
===========全部已经时的禁止再使用,主要为了兼容===================
ec EC(椭圆曲线)密钥处理,用于数字签名和加密 ecc算法
ecparam EC参数的操作和生成,产生ECC密钥对。用于数字签名和加密 ecc算法
dh Diffie-Hellman参数管理。被dhparam淘汰了 dh算法
gendh 生成Diffie-Hellman参数。被dhparam淘汰了 dh算法
dhparam 生成和管理Diffie-Hellman参数。被genpkey和pkeyparam取代 dh算法
genrsa 生成RSA私钥。由genpkey取代 rsa算法
rsa RSA密钥管理。处理RSA密钥、格式转换和打印信息 rsa算法
rsautl RSA实用程序用于签名,验证,加密和解密。取而代之的是pkeyutl rsa算法
dsa DSA数据管理。处理DSA密钥、格式转换和打印信息,用于数字签名 dsa算法
dsaparam DSA参数生成与管理。用于生成和操作dsa证书参数,用于数字签名,被genpkey和pkeyparam取代 dsa算法
gendsa 从参数生成DSA私钥。生成DSA密钥,密钥参数用dsaparam生成,用于数字签名,由genpkey和pkey取代 dsa算法
2 命令详解
2.1 enc
  对称加密算法工具。它能够运用块或者流算法对数据加/解密。还能够把加密/接密,还可以把结果进行base64编码。

openssl enc -ciphername [-in filename] [-out filename] [-pass arg] [-e] [-d] [-a/-base64] [-A]
[-k password] [-kfile filename] [-K key] [-iv IV] [-S salt] [-salt] [-nosalt] [-z] [-md] [-p]
[-P] [-bufsize number] [-nopad] [-debug] [-none] [-engine id]
1
2
3
-in filename —— 输入文件
-out filename ——输出文件,省略则为标准输出
-pass arg —— 口令设置,用于没有提供密钥时,采用摘要算法从口令中生成一个密钥,如果加密时使用了口令,则解密也要使用口令,arg如下所示:
-pass pass:"123" #密码是123
-pass pass:123 #密码是123
-pass evn:VAR #密码从环境变量VAR中去
-pass file:p.txt #密码从文件p.txt第一行取,不包括换行符,注意DOS格式的^M及回车符。
-pass fd:3 #密码从文件描述符3中读
-pass stdin #标准输入
1
2
3
4
5
6
-e —— 加密,二选一
-d —— 解密,二选一
-a/-base64 —— 加密时设置结果以base64编码,,解密时设置输入为base64
-A ——加密时设置结果以base64编码成一行,解密时设置输入为一行base64
-k password—— 口令,已经被-pass代替,二选一
-kfile filename] —— 口令文件 已经被-pass代替,二选一
-K key —— 加密秘钥,16进制
-iv IV ——初始向量,16进制
-S salt ——指定16进制盐值
-salt ——带有随机盐值 二选一
-nosalt ——不带盐值,已经关闭此选项,二选一
-z ——是否压缩,需要编译时选择了zlib库
-md digstname —— 摘要算法,当没有提供密钥时候,用于从口令中生成一个密钥,默认md5
-p ——打印出使用的salt、口令以及初始化向量IV。二选一
-P ——打印出使用的salt、口令以及初始化向量IV。不做加解密,二选一
-bufsize number ——缓冲区大小
-nopad ——无填充
-debug ——打印调试信息
-none ——不执行加 解密
-engine id ——引擎
-cihername ——要使用的加密算法,支持的加密算法如下

-aes-128-cbc -aes-128-cbc-hmac-sha1 -aes-128-cbc-hmac-sha256
-aes-128-ccm -aes-128-cfb -aes-128-cfb1
-aes-128-cfb8 -aes-128-ctr -aes-128-ecb
-aes-128-gcm -aes-128-ofb -aes-128-xts
-aes-192-cbc -aes-192-ccm -aes-192-cfb
-aes-192-cfb1 -aes-192-cfb8 -aes-192-ctr
-aes-192-ecb -aes-192-gcm -aes-192-ofb
-aes-256-cbc -aes-256-cbc-hmac-sha1 -aes-256-cbc-hmac-sha256
-aes-256-ccm -aes-256-cfb -aes-256-cfb1
-aes-256-cfb8 -aes-256-ctr -aes-256-ecb
-aes-256-gcm -aes-256-ofb -aes-256-xts
-aes128 -aes192 -aes256
-bf -bf-cbc -bf-cfb
-bf-ecb -bf-ofb -blowfish
-camellia-128-cbc -camellia-128-cfb -camellia-128-cfb1
-camellia-128-cfb8 -camellia-128-ecb -camellia-128-ofb
-camellia-192-cbc -camellia-192-cfb -camellia-192-cfb1
-camellia-192-cfb8 -camellia-192-ecb -camellia-192-ofb
-camellia-256-cbc -camellia-256-cfb -camellia-256-cfb1
-camellia-256-cfb8 -camellia-256-ecb -camellia-256-ofb
-camellia128 -camellia192 -camellia256
-cast -cast-cbc -cast5-cbc
-cast5-cfb -cast5-ecb -cast5-ofb
-des -des-cbc -des-cfb
-des-cfb1 -des-cfb8 -des-ecb
-des-ede -des-ede-cbc -des-ede-cfb
-des-ede-ofb -des-ede3 -des-ede3-cbc
-des-ede3-cfb -des-ede3-cfb1 -des-ede3-cfb8
-des-ede3-ofb -des-ofb -des3
-desx -desx-cbc -id-aes128-CCM
-id-aes128-GCM -id-aes128-wrap -id-aes192-CCM
-id-aes192-GCM -id-aes192-wrap -id-aes256-CCM
-id-aes256-GCM -id-aes256-wrap -id-smime-alg-CMS3DESwrap
-rc2 -rc2-40-cbc -rc2-64-cbc
-rc2-cbc -rc2-cfb -rc2-ecb
-rc2-ofb -rc4 -rc4-40
-rc4-hmac-md5 -seed -seed-cbc
-seed-cfb -seed-ecb -seed-ofb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
注意:
该程序可以通过openssl ciphername或者openssl enc -ciphername 两种方式调用,但是前一种不支持引擎加密.
应在配置文件中配置提供全新加密算法的引擎(如提供gost89算法的ccgost引擎)。在命令行中使用-engine选项指定的引擎只能用于由配置文件中指定的OpenSSL内核或其他引擎支持的密码的硬件辅助实现。
当enc命令列出支持的加密算法时,也列出了配置文件中指定的引擎提供的算法。
如果需要,将提示输入密钥以获得密钥。
如果从密码派生密钥,则应使用-salt选项,除非您希望与以前版本的OpenSSL和SSLeay兼容。没有-salt选项,可以对密码执行有效的字典攻击,并攻击流密码加密数据。原因是没有slat,相同的密码总是生成相同的加密密钥。当slat被使用时,加密数据的前八个字节被保留给盐:它在加密文件时被随机生成,并且在被解密时从加密文件读取。
一些密码没有大的密钥,如果不正确使用,会带来安全隐患。建议初学者在CBC模式下使用强分组密码,如bf或des3。
所有块密码通常使用PKCS#5填充也称为标准块填充:这允许执行基本的完整性或密码检查。然而,由于随机数据通过测试的机会优于256中的1,这不是一个非常好的测试。
如果禁止填充,则输入数据必须是密码块长度的倍数。
所有RC2密码具有相同的密钥和有效的密钥长度。
Blowfish和RC5算法使用128位密钥。
//加密(提供密钥)
$openssl enc -aes-128-cbc -e -K 000 -iv 000 -in test.txt -out test.cipher -a -A -p
//解密
$openssl enc -aes-128-cbc -d -K 000 -iv 000 -in test.ciphet -a -A -out test.plain -p
//加密(提供口令)
$openssl enc -aes-128-cbc -e -pass pass:123456 -S a1bec3d4e5f6 -salt -md sha1 -iv 000 in test.txt -out test.cipher -a -A -p
//解密
$openssl enc -aes-128-cbc -d -pass pass:123456 -S a1bec3d4e5f6 -salt -md sha1 -iv 000 in test.cipher -out test.plain -a -A -p
1
2
3
4
5
6
7
8
9
2.2 dgst
  主要用于数据摘要。它也可以用于数据签名以及验证签名

openssl dgst [-sha|-sha1|-mdc2|-ripemd160|-sha224|-sha256|-sha384|-sha512|-md2|-md4|-md5|-dss1] [-c] [-d] [-hex] [-binary]
[-r][-non-fips-allow] [-out filename] [-sign filename] [-keyform arg] [-passin arg] [-verify filename] [-prverify filename]
[-signature filename] [-hmac key] [-non-fips-allow] [-fips-fingerprint] [file...]
1
2
3
-sha|-sha1|-mdc2|-ripemd160|-sha224|-sha256|-sha384|-sha512|-md2|-md4|-md5|-dss1 摘要算法,多选一
-c ——当设置了-hex,输出结果每两个字符中加一个冒号
-d ——打印出BIO调试信息值。
-hex ——以十六进输出结果,二选一
-binary ——以二进制输出结果,二选一
-r —— 以sha1sum的“coreutils”格式输出摘要
-non-fips-allow —— 允许在FIPS模式使用非FIPS算法
-out filename—— 输出文件,默认标准输出
-sign filename—— 使用文件中的私钥签名
-keyform arg —— 私钥格式,PEM | DER
-passin arg—— 私钥密码
-verify filename ——公钥验签
-prverify filename ——私钥验签
-signature filename ——签名文件
-hmac key ——hmac 秘钥
-non-fips-allow ——允许在FIPS模式使用非FIPS算法
-fips-fingerprint ——在某些openssl——FIPS中使用特殊密钥计算HMAC
file ——输入文件
注意

所有新应用程序的选择的摘要算法是SHA1。 然而其他摘要算法仍然被广泛使用。
在签名时,dgst将根据私钥的ASN.1信息自动确定用于签名的算法(RSA,ECC等)。
当验证签名时,它只处理RSA,DSA或ECDSA签名本身,而不是分析相关数据来识别签名者和相关算法,如x.509,CMS和S / MIME的签名者和算法。
某些签名算法,特别是ECDSA和DSA需要一个随机数源。
仅当单个文件要签名或验证时,才能使用签名和验证选项。
十六进制签名无法使用openssl进行验证。使用“xxd -r”或类似程序在验证之前将十六进制签名转换为二进制签名。
例子

//计算摘要
$openssl dgst -sh1 -c -hex -out md.sha1 test.txt
//私钥签名
$ openssl dgst -sign rsa_pri.key -keyform PEM -passin pass:123456 -out rsa_sign.sig test.txt
//私钥验签
$openssl dgst -prverify rsa_pri.key -keyform PEM -passin pass:123456 -signature rsa_sign.sig test.txt
//公钥验签
$openssl dgst -verify rsa_pub.key -keyform PEM -signature rsa_sign.sig test.txt
//hmac
$openssl dgst -hmac rsa_pri.key -keyform PEM -passin pass:123456 -sha1 -out rsa.hmac test.txt
1
2
3
4
5
6
7
8
9
10
2.3 非对称
2.3.1 pkeyparam
  公共密钥算法的参数处理工具。它能够处理公钥或私钥文件。它能够转换组件信息并将它们打印出来。

openssl pkeyparam [-in filename] [-out filename] [-text] [-noout] [-engine id]
1
-in filename——输入文件, 如果未指定此选项,则从标准输入读取。
-out filename——输出文件,如果未指定此选项,则输出参数到标准输出
-text——除了编码版本信息本外,以明文打印出参数
-noout——不打印参数编码的版本信息。
engine id——引擎
注意
该命令没有-inform或-outform选项,因为仅支持PEM格式,因为密钥类型由PEM头决定。
例子
$ openssl pkeyparam -in param.pem -text
1
2.3.2 genpkey
  生成一个私钥。

openssl genpkey [-out filename] [-outform PEM|DER] [-pass arg] [-cipher] [-engine id]
[-paramfile file] [-algorithm alg] [-pkeyopt opt:value][-genparam] [-text]
1
2
3
-out filename——输出文件名。如果未指定此参数,则使用标准输出。
-outform PEM|DER——输出格式
-pass arg—— 私钥访问密码
-cipher——私钥的对称加密方式
-engine id——引擎,如要使用应该放在第一个位置
-paramfile file—— 一些公钥算法基于一组参数生成私钥。可以使用此选项提供参数。如果使用此选项,则使用的公钥算法由参数决定。如果使用此选项则必须在-pkeyopt选项之前。选项-paramfile和 -algorithm是互斥的。
-algorithm alg—— 使用公钥算法如RSA,DSA或DH。如果使用此选项,则必须位于任何-pkeyopt选项之前。 -paramfile和-algorithm选项是互斥的。
-pkeyopt opt:value——设置公钥算法选项选择值。支持的精确选项取决于所使用的公钥算法及其实现。有关更多详细信息,请参见下面的密钥生成选项。
-genparam——生成一组参数,而不是私钥。如果使用此选项必须在-paramfile 和 -algorithm, -pkeyopt 选项之前。
-text——打印私钥和公钥和参数(PEM或DER结构)的(未加密)文本。
密钥生成选项——每个算法支持的选项以及算法的每个实现都可以有所不同。 OpenSSL实现的选项如下。

RSA关键生成选项
rsa_keygen_bits:NUMBITS——生成的密钥中的比特数。如果未指定1024。
rsa_keygen_pubexp:value——RSA公众指数值。如果前面加上0x,这可以是一个大的十进制或十六进制值。默认值为65537。
DSA参数生成选项
dsa_paramgen_bits:NUMBITS——生成参数中的位数。如果未指定1024。
DH参数生成选项
dh_paramgen_prime_len:NUMBITS——素数参数p的位数。
dh_paramgen_generator:value——生成g所用的的值。
dh_rfc5114:NUM—— 如果设置了此选项,则使用相应的RFC5114参数,而不是生成新参数。值num可以采用与RFC5114 DH参数相对应的值1,2或3,该参数包括1024位组与160位子组,2048位组与224位子组和2048位组与256位子组,如RFC5114第2.1,2.2节中所述和2.3
EC参数生成选项
ec_paramgen_curve:_curve——要使用的ECC曲线
GOST2001密钥生成和参数选项

Gost 2001支持默认情况下不启用。要启用此算法,应在OpenSSL配置文件中加载ccgost引擎。有关详细信息,请参阅源分发引擎/ ccgost directiry中的README.gost文件。 使用GOST R 34.10算法的参数文件是可选的。参数可以在密钥生成期间直接指定,也可以在生成参数文件期间指定。

paramset:name——根据RFC 4357指定GOST R 34.10-2001参数集。参数集可以使用缩写名称,对象短名称或数字OID进行指定。支持以下参数集:

paramset OID Usage
A 1.2.643.2.2.35.1 Signature
B 1.2.643.2.2.35.2 Signature
C 1.2.643.2.2.35.3 Signature
XA 1.2.643.2.2.36.0 Key exchange
XB 1.2.643.2.2.36.1 Key exchange
test 1.2.643.2.2.35.0 Test purposes
1
2
3
4
5
6
7
注意
鼓励使用genpkey,因为可以使用额外的算法选项和ENGINE提供的算法。
例子
//生成RSA私钥
$openssl genpkey -out rsa_pri.key -outform PEM -pass pass:123456 -aes-128-cbc \
-algorithm RSA -pkeyopt rsa_keygen_bits:1024 -text
//这种方式生成的RSA私钥 不仅代用 私钥和大数,还代用公钥,以及其它信息,以用于快速计算。
1
2
3
4
2.3.3 pkey
   pkey命令处理公钥或私钥。它们可以在各种形式之间进行转换,并将其结构打印出来。

openssl pkey [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename]
[-passout arg] [-cipher] [-text] [-text_pub] [-noout] [-pubin] [-pubout] [-engine id]
1
2
3
-inform PEM|DER—— 输入格式
-outform PEM|DER—— 输出格式
-in filename——输入密钥文件,如果未指定此选项,则从标准输入读取。如果密钥加密,将提示输入密码。
-passin arg—— 输入密钥的密码
-out filename——输出文件, 如果未指定此选项,则输出到标准输出。如果设置了任何加密选项,则会提示输入密码。输出文件名不能与输入文件名相同
-passout arg 输出密钥的加密口令
-cipher—— 输出密钥的加密方式
-text—— 除了编码版本之外,以明文形式打印各种公共或私人密钥。
-text_pub——只打印公钥,当一个私钥在处理时
-noout—— 不输出密钥的编码版本
-pubin——默认是读入私钥,该选项指定读入公钥
-pubout—— 默认情况下会输出私钥:使用此选项将会输出公钥。 如果输入是公钥,则会自动设置此选项。用于从私钥生成公钥。
-engine id——引擎id
举例
//由私钥生成公钥
$openssl pkey -in rsa_pri.key -inform PEM -passin pass:123456 -out rsa_pub.key -outform PEM -pubout -text
//私钥改密码
$openssl pkey -in rsa_pri.key -inform PEM -passin pass:123456 -out rsa_change.key -outform PEM -passout pass:456789 -des-cbc -text
//改变公钥格式
$openssl pkey -in rsa_pub.key -inform PEM -out rsa_pub_der.key -outform DER -pubin -pubout -text
1
2
3
4
5
6
2.3.4 pkeyutl
&emsp pkeyutl命令可用于执行支持的公钥操作.

openssl pkeyutl [-in file] [-out file] [-sigfile file] [-inkey file]
[-keyform PEM|DER] [-passin arg] [-peerkey file] [-peerform PEM|DER] [-pubin]
[-certin] [-rev] [-sign] [-verify] [-verifyrecover] [-encrypt][-decrypt]
[-derive] [-pkeyopt opt:value] [-hexdump] [-asn1parse] [-engine id]

1
2
3
4
5
6
-in file——输入文件,如果不指定,则从标准输入读取
-out file——输出文件,如果不指定,则输出到标准输出
-sigfile file——签名文件,默认为标准输入
-inkey file——输入密钥文件,默认情况下应该是一个私钥
-keyform PEM|DER——密钥格式为PEM,DER或ENGINE
-passin arg——输入密钥的口令
-peerkey file——对等密钥文件,由密钥导出(协议)操作使用
-peerform PEM|DER—— 指定对等密钥格式为PEM,DER或ENGINE
-pubin—— 输入密钥是一个公钥,默认为私钥
-certin——输入的是包含公钥的证书。
-rev——反转输入缓冲器的顺序。这对于以小端序格式表示缓冲区的一些库(如CryptoAPI)很有用。
-sign——给输入文件签名,需要私钥
-verify——根据签名文件验证输入数据,并指示验证是成功还是失败
-verifyrecover——验证输入数据并输出转换后的数据
-encrypt——使用公钥加密输入数据
-decrypt——使用私钥解密输入数据
-derive—— 使用对等密钥导出共享密钥
-pkeyopt opt:value——公钥参数选项
-hexdump——hex转储输出数据。
-asn1parse—— 对输出的数据进行ASN1分析。当对ASN1结构签名与-verifyrecover选项联合使用非常有用
-engine id——引擎
注意:支持的操作和选项根据密钥算法及其实现而有所不同。 OpenSSL操作和选项如下所示。

除非另有说明,否则所有算法都支持digest:alg选项,指定摘要算法进行签名,验签和verifyrecover操作。值alg应该是EVP_get_digestbyname()函数中使用的摘要名称,例如sha1。该值仅用于对传入pkeyutl的数据的长度进行健全检查,并用于创建构成签名的结构(例如RSASSA PKCS#1 v1.5签名中的DigestInfo)。在RSA,ECDSA和DSA签名的情况下,该实用程序不会对输入数据执行散列,而是直接使用数据作为签名算法的输入。(因此要注意的输入的数据长度)
根据密钥类型,签名类型和填充模式,输入数据的最大可接受长度不同。一般来说,使用RSA,签名数据不能长于密钥模数,在ECDSA和DSA的情况下,数据不应该长于字段大小,否则将被默认地截断为字段大小。换句话说,如果digest的值为sha1,则输入应为SHA-1函数输出的20字节长的二进制编码。
RSA算法,RSA算法通常支持encrypt,decrypt,sign,verify和verifyrecover操作。一些填充模式只支持其中一些操作
rsa_padding_mode:mode
RSA中的参数rsa_padding_mode:mode设置RSA的填充模式,支持的填充模式有:用PKCS#1来设置PKCS#1填充模式,用sslv23来设置SSLv23填充模式,用none来设置no填充模式,用oaep来设置OAEP填充模式,用x931来设置X9.31填充模式以及用pss来设置PSS。
在PKCS#1填充中,如果摘要算法未设置,则提供的数据将被直接签名或验证,而不是使用DigestInfo结构。如果设置摘要,则使用DigestInfo结构,其长度必须对应于摘要类型。
对于oeap模式,仅支持加密和解密。
对X9.31填充模式来说,如果设置了摘要算法,并将它用于格式块数据,否则第一个字节必须要用X9.31摘要ID。它支持签名、验证签名以及验证恢复操作。
对于pss模式,仅支持签名和验证,并且必须指定摘要算法
rsa_pss_saltlen:LEN
对于pss模式,只有此选项指定填充长度。它支持两个值:
-1设置填充长度为摘要长度。
-2表示当签名的时候,设置填充字节为最大的可允许的字节数。当验证的时候,表示填充字节由块结构体来决定。
DSA算法——DSA算法仅支持sign和verify操作。 目前除了digest之外没有其他选项。 默认情况下,只能使用SHA1摘要,并默认此摘要算法。
DH算法—— DH算法只支持derived操作,没有附加选项。
EC算法——EC算法支持sign,verify和derived操作。 sign和verify操作使用ECDSA,derived使用ECDH。 目前除了digest之外没有其他选项。 只支持SHA1摘要,默认摘此要算法 。

举列

//签名
$openssl pkeyutl -sign -inkey rsa_pri.key -keyform PEM -passin pass:123456 -in md.sha1 -out md.sig
//验签
$openssl pkeyutl -verify -inkey rsa_pub.key -keyform PEM -pubin -in md.sha1 -sigfile md.sig
//加密
$openssl pkeyutl -encrypt -inkey rsa_pub.key -keyform PEM -pubin -in md.sha1 -out md.cipher
//解密
$openssl pkeyutl -decrypt -inkey rsa_pri.key -keyform PEM -passin pass:123456 -in md.cipher -out md.plain
//创建一个共享密钥
$openssl pkeyutl -derive -inkey rsa_pri.pem -passin pass:123456 -peerkey pubkey.pem -out secret
1
2
3
4
5
6
7
8
9
10
2.3 证书类
2.3.1 req
  req命令主要创建证书请求(可以新生成私钥),查看证书请求。它可以创建自签名证书,以作为root CA使用。但不能读取证书。

openssl req [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg]
[-out filename] [-passout arg] [-text] [-pubkey] [-noout] [-verify][-modulus]
[-new] [-rand file(s)] [-newkey arg] [-nodes] [-key filename] [-keyform PEM|DER]
[-keyout filename][-keygen_engine id] [-[digest]] [-config filename] [-multivalue-rdn]
[-x509] [-days n] [-set_serial n] [-asn1-kludge] [-no-asn1-kludge] [-newhdr]
[-extensions section] [-reqexts section] [-utf8] [-nameopt] [-reqopt]
[-subject] [-subj arg] [-batch] [-verbose] [-engine id]
1
2
3
4
5
6
7
输入输出项,通用选项
-inform PEM|DER——输入的证书请求的格式。 DER选项使用与PKCS#10兼容的ASN1 DER编码格式。 PEM格式是默认格式:它由DER格式base64编码,带有附加的头部和尾部。
-outform PEM|DER——输出的证书或证书请求的格式,默认PEM
-in filename——输入的证书请求文件名,如果未指定此选项,则从标准输入。只有未指定选项-new和-newkey)时,才会读取证书请求
-out filename——输出证书或证书请求文件名,不指定则默认标准输出
-[digest]——指定消息摘要算法(如-md5,-sha1)来对证书请求签名。这将覆盖配置文件中指定的摘要算法。 * 一些公钥算法可以忽略这个选项*。例如,DSA签名始终使用SHA1,GOST R 34.10签名始终使用GOST R 34.11-94(-md_gost94)。
-config filename——使用的config文件的名称。本选项如果没有设置,将使用缺省的config文件
私钥选项
-key filename—— 指定从中读取私钥的文件。它也读取PEM格式的PKCS#8私钥
-keyform PEM|DER——在-key参数中指定的读入的私钥文件的格式。默认 PEM
-passin arg——私钥的口令
-keyout filename—— 指定新创建的私钥的输出文件名字。如果未指定此选项,则使用配置文件中的文件名。只能以PEM格式输出
-passout arg——新建的私钥口令
-rand file(s)——指定随机数种子文件,或者EGD套接字的随机数据的文件,多个文件间用分隔符分开,windows用“;”,OpenVMS用“,“,其他系统用“:”
-nodes——如果指定了此选项,创建私钥时则不会对其进行加密
-keygen_engine id——生成私钥的引擎
-pkeyopt opt:value 设置公钥算法选项选择值。genpkey手册页中的密钥生成选项。
-newkey arg——用于生成新的私钥以及证书请求。arg 可以是下列:
rsa:nbits——其中nbits是比特数,指明生成一个RSA密钥的长度。如果省略nbits,如:-newkey rsa,则使用配置文件中指定的默认密钥长度。
alg:file——所有其他算法都支持-newkey alg:filename,其中文件可能是由genpkey -genparam命令创建的算法参数文件或于具有适用于alg算法的密钥的X.509证书。
param:file —— 用参数文件或证书文件生成一个密钥,算法由参数决定,
algname:file—— 使用algname指定的算法和flle指定的参数生成密钥, 算法与参数必须匹配,algname指定代表所使用的算法,file可以省略,但必须用 -pkeyopt parameter指定参数。
dsa:filename——使用文件filename中的参数生成DSA密钥。
ec:filename——生成EC密钥(可用于ECDSA或ECDH算法),
gost2001:filename——生成GOST R 34.10-2001密钥(需要在配置文件中配置ccgost引擎)。如果只指定gost2001,则应通过-pkeyopt paramset:X指定参数集
证书请求选项
-verify—— 验证证书请求上的签名。
-new——本选项产生一个新的证书请求,它会要用户输入创建证书请求的一些必须的信息。至于需要哪些信息,是在config文件里面定义好了的。如果-key没有被设置,,那么就将根据config文件里的信息先产生一对新的RSA密钥值。
-multivalue-rdn——当采用-subj arg选项时,允许多个值的rdn,比如arg参数写作:/CN=china/OU=test/O=abc/UID=123456+CN=forxy。如果不使用此选项,则UID值为123456+CN=forxy
-asn1-kludge——缺省的req指令输出不带属性的完全符合PKCS10格式的证书请求,但有的CA仅仅接受一种非正常格式的证书请求,这个选项的设置就可以输出那种格式的证书请求。PKCS#10证书请求中的属性被定义为SET OF属性。 它们不是可选的,因此如果没有属性存在,那么它们应该被编码为空的SET OF。 而非正常格式的证书请求中不包括空的SET OF。 应该注意的是,很少的CA仍然需要使用这个选项。
-no-asn1-kludge——不输出特定格式的证书请求。
-newhdr——在CSR问的第一行和最后一行中加一个单词”NEW”,有的软件(netscape certificate server)和有的CA就有这样子的怪癖嗜好。如果那些必须要的选项的参数没有在命令行给出,那么就会到config文件里去查看是否有缺省值
-extensions section -reqexts section——这俩个选项指定config文件里面的与证书扩展和证书请求扩展有关的俩个section的名字(如果-x509这个选项被设置)。这样你可以在config文件里弄几个不同的与证书扩展有关的section,然后为了不同的目的给证书请求签名的时候指明不同的section来控制签名的行为。。
-utf8—— 此选项将字段值解释为UTF8字符串,默认情况下将其解释为ASCII。这意味着字段值(无论是从终端提示还是从配置文件获取)必须是有效的UTF8字符串。
-subj arg——用于指定生成的证书请求的用户信息,或者处理证书请求时用指定参数替换。生成证书请求时,如果不指定此选项,程序会提示用户来输入各个用户信息,包括国名、组织等信息,如果采用此选择,则不需要用户输入了。比如:-subj /CN=china/OU=test/O=abc/CN=forxy,注意这里等属性必须大写。
-batch——不询问用户任何信息(私钥口令除外),采用此选项生成证书请求时,不询问证书请求当各种信息
-verbose—— 打印关于执行操作的额外的详细信息
-engine id——引擎
证书签名

-x509—— 此选项输出自签名证书。这通常用于生成测试证书或自签名根证书。添加到证书的扩展项(如果有)在配置文件中指定。除非使用set_serial选项,否则将使用较大的随机数作为序列号。
-days n—— 当使用-x509选项时,指定证书的有效期。默认为30天。
-set_serial n—— 输出自签名证书时使用的序列号。如果前缀为0x,则可以将其指定为十进制值或十六进制值。可以使用负序列号,但不建议这样做。
显示选项

-text—— 以文本形式打印证书请求
-pubkey—— 输出公钥。
-noout——选项可防止输出证书请求的编码版本
-modulus—— 该选项打印证书请求中公钥的模数值
-subject—— 打印证书请求的subject
-nameopt——用于确定主题或发行者名称的显示方式。选项参数可以是单个选项或多个选项,以逗号分隔。-nameopt开关可以被多次使用以设置多个选项。参见X509。
-reqopt—— 定制与-text一起使用时的输出格式。选项参数可以是单个选项或多个选项,以逗号分隔。 请参阅x509命令中-certopt参数的说明。
配置文件格式

配置选项在配置文件的req部分中指定。与所有配置文件一样,如果在特定部分(即req)中未指定任何值,则使用默认值。
input_password output_password ——输入私钥文件(如果存在)和输出私钥文件(如果将被创建)的口令。命令行选项passin和passout覆盖此值。
default_bits—— 指定默认密钥大小(以位为单位)。 此选项与-new选项结合使用以生成新密钥。可以通过在-newkey选项中指定密钥大小来覆盖它。最小的接受密钥大小是512位。如果没有指定密钥大小,则使用2048位。
default_keyfile—— 这是将私钥输出时的默认文件名。如果未指定,则将该键写入标准输出。这可以被-keyout选项覆盖。
oid_file——指定一个包含其他对象标识符的文件。文件的每一行都应包含对象标识符的数字形式,后跟空格,然后是短名称,后跟空格,最后是长名称。。
oid_section——指定配置文件中包含额外对象标识符的部分。每一行都应包含对象标识符的短名称,后面跟着=和数字形式。当使用此选项时,短名称和长名称相同。
RANDFILE—— 这指定了放置和读取随机数种子信息或EGD套接字的文件名(请参阅RAND_egd)。它用于私钥生成。
encrypt_key——如果这被设置为否,则如果生成私钥,则不加密。这相当于-nodes命令行选项。为了兼容性,encrypt_rsa_key是一个等效的选项。
default_md——此选项指定要使用的摘要算法。可能的值包括md5 sha1 mdc2。如果不存在,则使用MD5。该选项可以在命令行中被覆盖。
string_mask—— 此选项会遮蔽某些字段中某些字符串类型的使用。大多数用户不需要更改此选项。它可以设置默认值,默认选项使用PrintableStrings,T61Strings和BMPStrings,如果设置为pkix,则只能使用PrintableStrings和BMPStrings。这符合RFC2459中的PKIX建议。如果设置utf8only,则仅使用UTF8Strings:这是2003年之后的RFC2459中的PKIX建议。最后,设置为nombstr仅使用PrintableStrings和T61Strings:某些软件在BMPStrings和UTF8Strings中有问题:特别是Netscape。
req_extensions——它指定了配置文件中字节包含了一系列的额外信息,这些额外信息将会被添加到证书请求信息中。它可以被-reqexts命令覆盖。有关详细信息,请参阅x509v3_config(5)。
x509_extensions 它指定了配置文件中字节包含了一系列的扩展信息,当-x509选项使用时,这些额外信息将会被添加到证书中。它可以被- extensions命令行所覆盖。
prompt——如果设置为值no,则禁用提示字段,并直接从配置文件获取值。它还会更改distinguished_name和attributes部分的预期格式。
utf8—— 如果设置为yes,则域的值将转换为utf8格式。默认的是ASCII格式。这就意味着这些域值不能从终端获取值,只能从配置文件中获取,还必须是可利用的UTF8字符串。
attributes——这个指定了包含很多请求属性的字节:格式和distinguished_name一样。具有代表性的是包含challengePassword 或 unstructuredName 类型。目前来说,他们是被OpenSSL的请求签名实体所忽略了的,但是一些CA有可能需要他们。
distinguished_name——这个指定了包含distinguished_name域,当产生一个证书或证书请求时,有提示。
注意:PEM格式的页眉和页脚线通常为:
    —–BEGIN CERTIFICATE REQUEST—–
    —–END CERTIFICATE REQUEST—–
  某些软件(某些版本的Netscape证书服务器)需要:
—–BEGIN NEW CERTIFICATE REQUEST—–
—–END NEW CERTIFICATE REQUEST—–
  它使用-newhdr选项生成,但以其他方式兼容。 任何形式的输入都被透明地接受。
  Xenroll与MSIE生成的证书请求已添加扩展名。 它包括keyUsage扩展,它决定了密钥的类型(仅限签名或通用目的)以及脚本在extendedKeyUsage扩展中输入的任何其他OID。
distinguished_name和 attributes格式  
有两种单独的格式。如果 prompt选项设置为否,则这些部分仅由字段名称和值组成, 这允许外部程序(例如基于GUI)生成具有所有字段名称和值的模板文件,并将其传递给req:例如,
CN=My Name
OU=My Organization
emailAddress=someone@somewhere.org
或者,如果提示选项不存在或未设置为否,则该文件包含字段提示信息。它由以下形式组成:
   fieldName=”prompt”
   fieldName_default=”default field value”
   fieldName_min= 2
   fieldName_max= 4
”fieldName”是正在使用的字段名称,例如commonName(或CN)。 “提示”字符串用于要求用户输入相关详细信息。如果用户没有输入任何内容,那么如果没有默认值,则使用默认值,r如果没有默认值则省略该字段。如果用户刚刚输入’.’,即使存在默认值,仍然可以省略一个字段。
输入的字符数必须在fieldName_min和fieldName_max限制之间:根据正在使用的字段可能会有其他限制(例如,countryName只能长两个字符,并且必须适合PrintableString)。
某些字段(如organizationName)可以在DN中多次使用。 这是一个问题,因为配置文件将不会识别出现两次相同的名称。 为了避免这个问题,如果fieldName包含一些字符后跟一个完整的停止将被忽略。 所以第二个organizationName可以通过调用“1.organizationName”来输入。
实际上所许可的域名字是一些对象标识符的短或长名字。它们在OpenSSL中编译和包含常用的值例如as commonName、countryName、localityName、 organizationName、organizationUnitName、stateOrProvinceName。此外emailAddress也包含name、 surname、givenName initials 和 dnQualifier。
可以使用配置文件中的oid_file或oid_section选项来定义附加对象标识符。 任何其他字段将被视为一个DirectoryString。
诊断

经常询问以下消息:
Using configuration from /some/path/openssl.cnf
Unable to load config info
 在一段时间后跟…
unable to find ‘distinguished_name’ in config
problems making Certificate Request
  第一个错误信息是线索:找不到配置文件!某些操作(如检查证书请求)不需要配置文件,因此不会强制使用它。然而生成证书或请求确实需要一个配置文件。这可以被认为是一个bug。
   另一个令人困惑的消息是:
Attributes:
a0:00
  当没有属性存在并且该请求包括正确的空的SET OF结构(其DER编码为0xa0×0x00)时显示。如果你只是看到: Attributes:
  那么SET OF丢失,编码在技术上是无效的(但是它是容忍的)。有关详细信息,请参阅命令行选项-asn1-kludge的说明。

环境变量

如果定义了变量OPENSSL_CONF,则允许指定备用配置文件,它将被-config选项覆盖(如果存在)。出于兼容性原因,SSLEAY_CONF环境变量用于相同的用途,但不鼓励使用它。

BUGS

  OpenSSL处理T61Strings(又名TeletexStrings)是坏的:它将它们视为ISO-8859-1(拉丁文1),Netscape和MSIE具有相似的行为。如果你需要的字符是PrintableStrings中不可用的字符,并且您不想使用或不能使用BMPStrings,则可能会导致问题。
  作为T61String处理的结果,在OpenSSL中唯一正确的表示重音字符的方法是使用BMPString:不幸的是Netscape当前对这些字符进行调整。如果您必须使用Netscape和MSIE重音字符,那么您当前需要使用无效的T61String格式。
   目前的提示不是很友好。它不允许您确认您刚刚输入的内容。在配置文件中静态定义了证书请求中的其他扩展。其中一些:像subjectAltName中的电子邮件地址应由用户输入。

例子

//验证证书请求
$openssl req -verify -in root.req -text -noout -pubkey -modulus -subject

//创建一个私钥,并用其生成一个证书请求
$openssl genpkey -out rsa_pri.key -outform PEM -pass pass:123456 -aes-128-cbc \
-algorithm RSA -pkeyopt rsa_keygen_bits:1024

$openssl req -new -key rsa_pri.key -inform PEM -passin pass:123456 -out root.req -subj
/CN=china/OU=test/O=abc/CN=forxy

//等效于
$ openssl req -newkey rsa:1024 -keyout new_rsa_pri.key -outform PEM -passout pass:123456 -out root.req -subj /CN=china/OU=test/O=abc/CN=forxy

//产生一个自签名的根证书,不提供私钥
$openssl req -x509 -set_serial 10001 -days 10 -subj /CN=china -newkey rsa:1024 -keyout new_rsa_pri.pkey -outform PEM -passout pass:123456 -out root_new.cer -outform PEM

//产生一个自签名的根证书,提供私钥
$openssl req -x509 -set_serial 10001 -days 10 -key rsa_pri.pem -keyform PEM -passin pass:123456 -in root.req -inform PEM -out root.cer -outform PEM

//oid_file选项指定的文件
1.2.3.4 shortName A longer Name
1.2.3.6 otherName Other longer Name

//oid_section指定的文件
testoid1=1.2.3.5
testoid2=${testoid1}.6

//带提示的配置文件
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca

dirstring_type = nobmp

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = AU
countryName_min = 2
countryName_max = 2

localityName = Locality Name (eg, city)

organizationalUnitName = Organizational Unit Name (eg, section)

commonName = Common Name (eg, YOUR name)
commonName_max = 64

emailAddress = Email Address
emailAddress_max = 40

[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20

[ v3_ca ]

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always
basicConstraints = CA:true

// 包含所有字段的配置文件

RANDFILE = $ENV::HOME/.rnd

[ req ]
default_bits = 2048
default_keyfile = keyfile.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
output_password = mypass

[ req_distinguished_name ]
C = GB
ST = Test State or Province
L = Test Locality
O = Organization Name
OU = Organizational Unit Name
CN = Common Name
emailAddress = test@email.address

[ req_attributes ]
challengePassword = A challenge password
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
2.3.2 x509
  本指令是一个功能很丰富的证书处理工具。可以用来显示证书的内容,转换证书格式,给证书请求签名,也可自签根证书等等。由于功能太多,我们按功能分成几部分来讲。

openssl x509 [-inform DER|PEM|NET] [-outform DER|PEM|NET] [-keyform DER|PEM]
[-CAform DER|PEM] [-CAkeyform DER|PEM] [-in filename] [-out filename][-serial] [-hash]
[-subject_hash] [-issuer_hash] [-ocspid] [-subject] [-issuer] [-nameopt option]
[-modulus] [-pubkey] [-fingerprint] [-alias] [-noout] [-trustout] [-clrtrust]
[-clrreject] [-addtrust arg][-addreject arg] [-setalias arg] [-days arg] [-set_serial n]
[-signkey filename] [-passin arg] [-x509toreq] [-req] [-CA filename] [-CAkey filename]
[-CAcreateserial] [-CAserial filename] [-force_pubkey key] [-text] [-certopt option]
[-C] [-md2|-md5|-sha1|-mdc2] [-clrext] [-extfile filename] [-extensions section]
[-engine id]
1
2
3
4
5
6
7
8
9
输入,输出和一般用途选项
-inform DER | PEM | NET— X509证书或证书请求的输入格式, DER格式是证书的DER编码,PEM是添加了头部和尾部的DER编码的base64编码。 NET选项是一个晦涩的Netscape服务器格式,现在已经过时了。
-outform DER | PEM | NET——输出格式,这些选项与-inform选项具有相同的含义。
in filename——如果未指定此选项,则从标准输入读取。
-out文件名——默认情况下,它指定要写入的输出文件名或标准输出。
-md2 | -md5 | -sha1 | -mdc2—— 摘要算法。 这会影响使用消息摘要的任何签名或显示选项,例如-fingerprint,-signkey和-CA选项。 如果未指定,则使用SHA1。 如果用于登录的密钥是DSA密钥,则此选项不起作用:SHA1始终与DSA密钥一起使用。
-engine id—— 指定引擎。
显示选项
注意:-alias和-purpose选项也是显示选项,但在解释在“信任设置”部分。
-text—— 以文本形式打印证书。输出全部细节,包括公钥,签名算法,发行人和主题名称,任何扩展名的序列号和任何信任设置。
-certopt——定制与-text一起使用的输出格式。选项参数可以是单个选项或多个选项,以逗号分隔。可以使用-certopt开关多次设置多个选项。有关详细信息,请参阅文本选项部分。
-noout——此选项可防止输出编码版本信息。
-pubkey——以PEM格式输出证书的SubjectPublicKeyInfo。
-modulus——此选项打印证书中公钥的模数值。
-serial——输出证书序列号。
-subject_hash——输出证书主题名称的“哈希”。这在OpenSSL中用于形成索引,以允许按主题名称查找目录中的证书。
-issuer_hash——输出证书颁发者名称的“哈希”。
-ocspid——输出主题名称和公钥的OCSP哈希值。
-hash——“-subject_hash”的同义词,用于向后兼容性。
-subject_hash_old——使用OpenSSL版本1.0.0之前使用的旧算法输出证书主题名称的“散列”。
-issuer_hash_old—— 使用OpenSSL版本1.0.0之前使用的旧算法输出证书颁发者名称的“哈希”。
-subject——输出证书的主题。
-issuer——输出颁发者名称。
-nameopt option——用于确定主题或发行者名称的显示方式。选项参数可以是单个选项或多个选项,以逗号分隔。-nameopt可以被多次使用以设置多个选项。有关详细信息,请参阅“NAME OPTIONS”部分。
-email——输出电子邮件地址(如果有)。
-ocsp_uri——输出OCSP响应者地址(如果有)。
-startdate——打印证书的开始日期,即notBefore日期。
-enddate——打印证书的到期日期,即notAfter日期。
-dates——打印证书的开始和到期日期。
-checkend arg——检查证书是否在下一个arg秒内到期,如果是,则返回非零,否则为零。
-fingerprint——打印整个证书的DER编码版本的摘要(请参阅摘要选项)。
-C——这将以C源文件的形式输出证书。
可信设置——请注意,这些选项目前是实验性的,可能会有所改变。
受信任的证书是普通证书,其中附有数个附加的信息,例如证书的许可和禁止使用以及“别名”。通常当证书被验证时,至少有一个证书必须是“可信任的”。默认情况下,受信任的证书必须在本地存储,并且必须是根CA:任何以CA结尾的证书链可用于任何目的。目前的信任设置仅与根CA一起使用。它们允许对根CA可以使用的目的进行更精细的控制。例如,CA可能被信任为SSL客户端,但不能使用SSL服务器。 有关信任设置的含义的更多信息,请参阅verify实用程序的说明。未来版本的OpenSSL将会识别任何证书上的信任设置,不仅仅是根CA。
-trustout——使x509输出可信证书。可以输入普通或可信任的证书,但默认情况下会输出普通证书,并丢弃任何信任设置。使用-trustout选项,输出可信证书。如果任何信任设置被修改,将自动输出受信任的证书。
-setalias arg—— 设置证书的别名。 这将允许使用昵称来引用证书,例如“Steve’s Certificate”。
-alias——输出证书别名(如果有)。
-clrtrust——清除证书附加项里所有有关用途允许的内容。
-clrreject——清除证书附加项里所有有关用途禁止的内容。
-addtrust arg——添加证书附加项里所有有关用途允许的内容。
-addreject arg——添加证书附加项里所有有关用途禁止的内容。
-purpose——打印出证书附加项里所有有关用途允许和用途禁止的内容。
签名选项—— x509实用程序可用于签署证书请求:因此它可以像“迷你CA”一样运行。
-signkey filename——私钥文件。 如果输入文件是证书,则将颁发者设置为其主题名称(即使其自签名)将公钥更改为提供的值,并更改开始和结束日期。开始日期设置为当前时间,结束日期设置为由-days选项确定的值。除非提供了-clrext选项,否则将保留任何证书扩展名。如果输入是证书请求,则使用提供的私钥按照证书请求中的subject创建自签名证书。(主要用于自签名,与req程序中的-x509一样)
-passin arg—— 私钥口令。有关arg的格式的更多信息
-clrext——从证书中删除任何扩展项。当从另一个证书创建证书时使用此选项(例如使用-signkey或-CA选项)。通常所有的扩展都保留。
-keyform PEM | DER——指定-signkey选项中使用的私钥文件的格式(DER或PEM)。
-days arg—— 指定使证书有效的天数。默认为30天。
-x509toreq——将证书转换为证书请求。 -signkey选项用于传递所需的私钥。
-req——默认-in输入证书。使用此选项表示输入的是证书请求。
-set_serial n——指定要使用的序列号。此选项可与-signkey或-CA选项一起使用。如果与-CA选项结合使用,则不使用序列号文件(由-CAserial或-CAcreateserial选项指定)。 序列号可以是十进制或十六进制(如果前面是0x)。也可以指定负序列号,但不建议使用它们。
-CA filename——指定要用于签名的CA证书。当此选项存在时,x509的行为就像“迷你CA”。该CA使用此选项对输入文件进行签名:将其颁发者名称设置为CA的主题名称,并使用CAs私钥进行数字签名。此选项通常与-req选项组合。没有-req选项,输入是必须是自签名的证书。
-CAform PEM|DER ——指定CA证书的格式
-CAkey filename——设置CA私钥以签署证书。如果未指定此选项,则假定CA私钥存在于CA证书文件中。
-CAkeyform PEM|DER——指定CA私钥的格式
-CAserial filename——设置要使用的CA序列号文件,当-CA选项用于签署证书时,它使用文件中指定的序列号。该文件包含一行偶数个十六进制数字。每次使用后,序列号将增加并再次写入文件。默认文件名由附加了“.srl”的CA证书文件基础名称组成。例如,如果CA证书文件被称为“mycacert.pem”,则它希望找到一个名为“mycacert.srl”的序列号文件。
-CAcreateserial——使用此选项,当CA序列号文件不存在时将被创建:它将包含序列号“02”,正在签名的证书将具有1作为其序列号。通常如果指定了-CA选项并且序列号文件不存在,则会出现错误。
-extfile filename—— 指定包含证书扩展项的文件名。如果没有,那么生成的证书将没有任何扩展项。
-extensions section——指定要添加证书扩展项的section。 如果未指定此选项,则扩展名应包含在未命名(默认)部分中,默认部分应包含一个名为“extensions”的变量,该变量包含要使用的部分。 有关扩展部分格式的详细信息,请参阅x509v3_config手册页。
-force_pubkey key—— 当创建证书时,将其公钥设置为密钥而不是证书或证书请求中的密钥。 此选项对于创建证书,在算法无法正常签署请求时很有用,例如DH。 可以使用-keyform选项指定公钥格式。
名称选项——nameopt选项确定主题和发行者的显示方式。如果没有nameopt开关,则使用与以前兼容的默认“oneline”格式。OpenSSL版本。每个选项在下面详细描述,所有选项都可以在前面加a -来关闭该选项。通常只会使用前四个。
compat——使用旧格式。这相当于没有指定任何名称选项。
RFC2253——相当于使用esc2253,esc_ctrl,esc_msb,utf8,dump_nostr,dump_unknown,dump_der,sep_comma_plus,dn_rev和sname 参数
oneline——一种比RFC2253更可读的一行格式。相当于指定esc_2253,esc_ctrl,esc_msb,utf8,dump_nostr,dump_der,use_quote,sep_comma_plus_space,space_eq和sname选项。
multiline—— 多行格式。它等效于esc_ctrl,esc_msb,sep_multiline,space_eq,lname和align。
esc_2253——在字符串中转义RFC2253所需的“特殊”字符,即+“<>;另外,在字符串的开头的#和字符串开头或结尾处的空格字符也要进行转义。
esc_ctrl ——转义控制字符。ASCII值小于0x20(空格)的字符和delete(0x7f)字符。它们使用RFC2253 中的\ XX符号进行转义(其中XX是表示字符值的两个十六进制数字)
esc_msb——转义MSB集合字符,即ASCII值大于127。
use_quote—— 通过以引号围绕整个字符串转义一些字符,没有选项时,所有的转义都是用\字符完成的。
utf8——首先将所有字符串转换为UTF8格式。这是RFC2253所要求的。如果您有UTF8兼容终端,则使用此选项(而不是设置esc_msb)可能会让多字节(国际)字符的正确显示。此选项不存在,则大于0xff的多字节字符将使用格式\ UXXXX(16位)和\ WXXXXXXXX(32位)来表示。此外,如果此选项关闭,任何UTF8Strings将首先转换为其字符形式。
ignore_type——此选项不会以任何方式尝试解释多字节字符。这就是他们的内容八位字节只是被转储,就像一个八位位组代表每个字符一样。这对于诊断目的是有用的,但会导致相当奇怪的输出。
show_type——显示ASN1字符串的类型。该类型位于字段内容之前。例如“BMPSTRING:Hello World”。
dump_der——当设置此选项时,需要使用hexdumped的任何字段将使用字段的DER编码进行转储。否则只会显示内容八位字节。两种选项都使用RFC2253 #XXXX …格式。
dump_nostr——转储非字符串类型(例如OCTET STRING),如果未设置此选项,则将显示非字符串类型,因为每个内容字节表示单个字符。
dump_all——转储所有字段。当与dump_der一起使用时,该选项允许明确地确定结构的DER编码。
dump_unknown——转储OpenSSL无法识别OID的任何字段。
sep_comma_plus, sep_comma_plus_space, sep_semi_plus_space, sep_multiline—— 这些选项决定了字段分隔符。第一个字符在RDN之间,而第二个是多个AVA之间(多个AVA非常罕见,并且不鼓励使用它们)。以“空格”结尾的选项还会在分隔符之后放置一个空格,使其更易于阅读。 sep_multiline对RDN分隔符使用换行字符,并为AVA分隔符使用间隔的+。它还将字段缩小四个字符。如果没有指定字段分隔符,则默认使用sep_comma_plus_space。
dn_rev——反转DN的字段。 这是RFC2253所要求的。 作为副作用,这也反转了多个AVA的顺序,但这是允许的。
nofname,sname,lname,oid—— 这些选项会改变字段名称的显示方式。
nofname根本不显示字段。
sname使用“短名称”表单(例如,用于commonName的CN)。
lname使用长格式。
oid以数字形式表示OID,可用于诊断目的。
align——调整字段值以获得更可读的输出。 仅适用于sep_multiline。
space_eq——在字段名后面的=字符上放置空格。
文本选项 ——除了自定义name输出格式,还可以在文本选项存在时自定义使用certopt选项打印的实际字段。默认行为是打印所有字段
compatible—— 使用旧格式。这相当于完​​全没有指定输出选项。
no_header——不要打印头信息:只打印“证书”和“数据”的行。
no_version——不要打印出版本号。
no_serial——不要打印出序列号。
no_signame——不打印出使用的签名算法。
no_validity—— 不打印有效日期,那就是notBefore和notAfter字段
no_subject——不打印主题名称
no_issuer——不要打印出颁发者
no_pubkey—— 不要打印出公钥
no_sigdump——不要给出证书签名的十六进制dump
no_aux—— 不要打印证书信任信息
no_extensions——不要打印任何X509V3扩展。
ext_default—— 保留默认扩展行为:尝试打印不支持的证书扩展名
ext_error——打印不支持的证书扩展的错误消息
ext_parse—— ASN1解析不受支持的扩展
ext_dump—— 十六进制转储不支持扩展
ca_default—— ca实​​用程序使用的值,相当于no_issuer,no_pubkey,no_header,no_version,no_sigdump和no_signame。
注意
PEM格式使用头部和尾部:
—–BEGIN CERTIFICATE—–
—–END CERTIFICATE—–
它还将处理包含的文件:
—–BEGIN X509 CERTIFICATE—–
—–END X509 CERTIFICATE—–
可信任的证书有以下行:
—–BEGIN TRUSTED CERTIFICATE—–
—–END TRUSTED CERTIFICATE—–
使用nameopt选项转为UTF8格式的假定T61Strings使用ISO8859-1字符集。 虽然这是错误的,但Netscape和MSIE都是做许多证书。 所以尽管这是不正确的,但更有可能正确显示大多数证书。
-fingerprint选项使用DER编码证书的摘要。 这通常被称为“指纹”。 由于消息摘要的性质,证书的指纹对于该证书是唯一的,并且具有相同指纹的两个证书可以被认为是相同的。
Netscape指纹使用MD5,而MSIE使用SHA1。
-email选项搜索主题名称和主题备用名称扩展名。 只会打印出唯一的电子邮件地址:它不会多次打印相同的地址。
证书扩展项
-purpose选项检查证书扩展项,并确定可以使用的扩展项。完成的实际检查相当复杂,包括处理破坏的证书和软件的各种黑客和解决方法。在证书链中,当验证不信任的证书也将使用同样的代码。因为这个字段对验证代码已经拒绝了的证书链来说非常有用。
basicConstraints扩展CA标志用于确定证书是否可以用作CA。如果CA标志为真,那么它是CA,如果CA标志为false,那么它不是CA。所有CA应该将CA标志设置为true。 如果basicConstraints扩展名不存在,则证书被认为是“可能的CA”,根据证书的预期用途检查其他扩展项。在这种情况下会发出警告,因为证书实际上不应被视为CA:但是它被允许作为CA来解决一些破坏的软件。如果证书是V1证书(因此没有扩展名),并且它是自签名的,它也被认为是CA,但是再次给出警告:这是为了解决Verisign根的问题,这是V1自签名证书。 如果keyUsage扩展项存在,则对证书的使用作出额外限制。如果存在keyUsage扩展项,CA证书必须设置keyCertSign位。如果扩展密钥用法存在,则附加的约束将会作用到该证书上。如果扩展密钥用法存在,密钥仅仅能够用于指定的证书用途。 以下给出了每个测试的完整描述。关于basicConstraints和keyUsage和V1证书的注释适用于所有CA证书。
SSL Client ——扩展密钥用法必须不存在或包含“web client authentication”OID值。密钥用法必须不存在或者必须要设置digitalSignature字节。Netscape类型必须不存在或者必须要设置SSL Client字节。
SSL Client CA—— 扩展密钥用法扩展项必须不存在,或包括“Web客户端认证”OID。 Netscape证书类型必须不存在,或者必须设置SSL CA位:如果basicConstraints扩展名不存在,则将其用作一个工作。
SSL Server——扩展密钥用法必须不存在或包含“web server authentication”OID值。密钥用法必须不存在或者必须有digitalSignature,keyEncipherment设置或者两者都要设置。Netscape类型必须不存在或者必须要设置SSL Server字节。
SSL Server CA——扩展密钥用法必须不存在或包含“web server authentication”OID值。Netscape类型必须不存在或者必须要设置SSL Server字节:如果basicConstraints额外信息不存在,它将会使用。
Netscape SSL Server如果密钥用法存在,Netscape格式的SSL 客户端必须要有keyEncipherment字节被设置后才能连接到服务器。这个选项不是有效的因为一些算法套件要用密钥进行数字签名。要不然就和普通的SSL Server一样。
Common S/MIME Client Tests——扩展密钥用法必须不存在或包含“email protection”OID值。Netscape类型必须不存在或者必须要设置S/MIME字节。如果在Netscape 格式证书中,S/MIME字节没有被设置,SSL client字节将会默许作为一个可供选择。但是一个警告将会显示:这是因为一些证书没有设置S/MIME字节。
S/MIME Signing——如果该扩展密钥用法存在,则普通的S/MIME客户端必须设置digitalSignature字节。
S/MIME Encryption——如果该扩展密钥用法存在,则普通的S/MIME客户端必须设置keyEncipherment字节。
S/MIME CA——扩展密钥用法必须不存在或包含“email protection”OID值。Netscape类型必须不存在或者必须要设置S/MIME CA字节:如果basicConstraints额外信息不存在,它将会使用。
CRL Signing——扩展密钥用法必须不存在或或者必须要设置CRL signing字节。
CRL Signing CA——普通CA测试申请。
BUGs:
证书中的扩展不会转发到证书请求中,反之亦然。
在某些情况下,有可能通过指定错误的私钥或使用不一致的选项来生成无效的证书或请求:应该检查这些证书或请求。
应该有明确设置开始和结束日期的选项,而不是与当前时间的偏移。
目前正在开发实施TRUST SETTINGS中描述的验证行为的代码。 因此,它描述了预期的行为,而不是当前的行为。 希望它在OpenSSL 0.9.5及更高版本中代表现实。
例子
//自签证书
$openssl x509 -req -signkey rsa_pri.key -keyform PEM -passin pass:123456 -in root.req -inform PEM -out root.cer -outform PEM

//签发用户证书
$openssl x509 -req -set_serial 00001 -days 100 -CA root.cer -CAform PEM -CAkey rsa_pri.key -CAkeyform PEM -passin pass:123456 -in user.req -inform PEM -out user.cer -outform PEM

//解析证书完整信息
$openssl x509 -in user.cer -inform PEM -text -noout

//查看证书指定信息
$openssl x509 -in user.cer -inform PEM -noout -pubkey -modulus -serial -subject -issuer -email -startdate -enddate -dates -fingerprint -subject_hash -issuer_hash -subject_hash_old -issuer_hash_old

//以C源码输出证书
$openssl x509 -in user.cer -inform PEM -out user.infor -C

//转换证书格式
$openssl x509 -in user.cer -inform PEM -out user.der -outform DER
//查看证书用途
$openssl x509 -purpose -noout -in user.cer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.3.3 verify
  用于验证证书链。

openssl verify [-CApath directory] [-CAfile file] [-purpose purpose] [-policy arg]
[-crl_check] [-crl_check_all] [-policy_check] [-explicit_policy] [-inhibit_any] [-inhibit_map] [-x509_strict][-extended_crl] [-use_deltas] [-policy_print] [-no_alt_chains] [-untrusted file] [-help] [-issuer_checks] [-trusted file] [-verbose] [-] [certificates]
1
2
2.4 PKI
2.4.1 pkcs7
  pkcs7处理DER或者PEM格式的pkcs#7文件

openssl pkcs7 [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-out filename]
[-print_certs] [-text] [-noout] [-engine id]
1
2
-inform PEM|DER:输入文件格式
-outform DER|PEM:输出文件格式,DER或者PEM格式。
-in filename:输入的需要处理的文件,默认为标准输入。
-out filename:输出文件,默认为标准输出。
-print:打印出pkcs7的所有信息值。
-print_certs:打印文件中的证书或CRL信息,在一行中打印出持有者和颁发者。
-text:打印证书相关信息。
-noout:不打印证书编码版本信息。
-engine id:指定引擎。
注意:
The PEM PKCS#7 format uses 头部和尾部:

-----BEGIN PKCS7-----
-----END PKCS7-----
1
2
还有一种可兼容的格式:

-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
1
2
例子
//把一个PKCS#7文件从PEM格式转换成DER格式:
$openssl pkcs7 -in pk7.pem -outform DER -out pk7.der

//打印文件所有证书
$openssl pkcs7 -in pk7.pem -print_certs -out certs.pem
1
2
3
4
5
2.4.2 pkcs8
  pkcs8命令处理PKCS#8格式的私钥。它可以使用各种PKCS#5(v1.5和v2.0)和PKCS#12算法来处理未加密的 PrivateKeyInfo格式的和已加密的PrivateKeyInfo格式 的PKCS#8。

openssl pkcs8 [-topk8] [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg]
[-out filename] [-passout arg] [-noiter] [-nocrypt][-nooct] [-embed] [-nsdb] [-v2 alg]
[-v2prf alg] [-v1 alg] [-engine id]
1
2
3
-topk8——默认输入PKCS#8私钥,输出传统格式私钥。使用-topk8选项,情况相反:它读取传统的格式私钥,输出一个PKCS#8格式的密钥。
-inform PEM|DER——输入私钥文件的格式
-outform PEM|DER——输出撕咬的格式
-in filename——输入的私钥文件名
-passin arg——输入私钥口令
-out filename——输出私钥文件名
-passout arg——输出私钥口令
-noiter——MAC保护计算次数为1
-nocrypt——输入或者输出pkcs8默认是加密的,使用此选型不加密
-nooct——此选项以某种软件使用的损坏格式生成RSA私钥。具体来说,私钥应该包含在OCTET STRING中,但是一些软件只包含结构本身,而不需要周围的OCTET STRING。
-embed——此选项以损坏的格式生成DSA密钥。 DSA参数嵌入在PrivateKey结构中。在这种形式下,OCTET STRING包含一个由两个结构组成的ASN1 SEQUENCE:一个包含参数的SEQUENCE和一个包含私钥的ASN1 INTEGER。
-nsdb—— 此选项用Netscape私钥数据兼容的损坏格式生成DSA密钥。 PrivateKey分别包含由公钥和私钥组成的SEQUENCE。
-v2 alg—— 此选项可以使用PKCS#5 v2.0加密算法。通常PKCS#8私钥使用基于口令的加密算法加密,称为pbeWithMD5AndDES-CBC,它使用56位DES加密,是PKCS#5 v1.5中最强大的加密算法。使用-v2选项指定使用PKCS#5 v2.0算法,这可以使用任何加密算法,例如168位三重DES或128位RC2,但是还没有很多应用支持PKCS#5 v2.0。如果您只是使用OpenSSL私钥,那么这并不重要。alg参数是要使用的加密算法,有效值包括des,des3和rc2。 建议使用des3。
-v2prf alg—— 此选项将PRF算法设置为与PKCS#5 v2.0一起使用。 一个典型的值是hmacWithSHA256。 如果未设置此选项,则使用默认值,如果没有默认值,则使用hmacWithSHA1。
-engine id—— 引擎
-v1 alg——此选项指定要使用的PKCS#5 v1.5或PKCS#12算法。 下面列出了可能的算法的完整列表。

PBE-MD2-DES PBE-MD5-DES——这些算法被包含在原始的PKCS#5 v1.5规范中。他们只提供56位保护,因为它们都使用DES。
PBE-SHA1-RC2-64 PBE-MD2-RC2-64 PBE-MD5-RC2-64 PBE-SHA1-DES——这些算法在原始PKCS#5 v1.5规范中没有提及,但是它们使用相同的密钥导出算法,并被一些软件支持。它们在PKCS#5 v2.0中被提及。它们使用64位RC2或56位DES。
PBE-SHA1-RC4-128 PBE-SHA1-RC4-40 PBE-SHA1-3DES PBE-SHA1-2DES PBE-SHA1-RC2-128 PBE-SHA1-RC2-40——这些算法使用基于PKCS#12密码的加密算法,并允许使用诸如三重DES或128位RC2的强加密算法。
注意

PEM编码PKCS#8文件的加密形式使用以头部和尾部:
—–BEGIN ENCRYPTED PRIVATE KEY—–
—–END ENCRYPTED PRIVATE KEY—–
未加密的使用:
—–BEGIN PRIVATE KEY—–
—–END PRIVATE KEY—–

使用PKCS#5 v2.0算法和高迭代次数加密的私钥比那些使用传统SSLeay兼容格式加密的私钥更安全。因此,如果安全性被认为是重要的,则应转换密钥。

默认加密只有56位,因为这是PKCS#8的大多数当前实现将支持的加密。
某些软件可能使用pkcs#12加密算法的PKCS#8格式私钥:这些是自动处理的,但没有选项生成它们。
可以输出DER编码的PKCS#8格式私钥,因为加密细节包含在ASN1级别,而传统格式包括它们在PEM级别。
标准

来自该PKCS#5 v2.0实现的测试向量使用三重DES,DES和RC2以较高的迭代计数发布到pkcs-tng邮件列表,有几个人确认他们可以解密所生成的私钥,因此可以认为 至少在这些算法方面,PKCS#5 v2.0的实现是相当准确的。
PKCS#8 DSA(和其他)私钥的格式没有很好的记录:它被隐藏在PKCS#11 v2.01,第11.9节。 OpenSSL的默认DSA PKCS#8私钥格式符合本标准。
BUGS
应该有一个选项,打印出使用的加密算法和其他细节,如迭代计数。
PKCS#8使用三重DES和PKCS#5 v2.0应该是OpenSSL的默认私钥格式:为了兼容性,几个实用程序目前使用旧格式。
例子
//pkcs#1私钥生成pkcs8的私钥
$openssl pkcs8 -topk8 -in rsa_pri.key -inform PEM -passin pass:123456 -out rsa_pri.pk8 -outform PEM -passout pass:123456 -v2 des3
//pkcs8的私钥生成pkcs#1私钥
$openssl pkcs8 -in rsa_pri.pk8 -inform PEM -passin pass:123456 -out rsa_pri.key -outform PEM -passout pass:123456 -v2 des3
1
2
3
4
2.4.3 pkcs12
pkcs12工具,能生成和分析pkcs12,也叫pfx文件。PKCS#12文件可以被用于多个程序,例如包含Netscape、 MSIE 和 MS Outlook。

openssl pkcs12 [-export] [-chain] [-inkey filename] [-certfile filename] [-name name]
[-caname name] [-in filename] [-out filename] [-noout] [-nomacver] [-nocerts] [-clcerts]
[-cacerts] [-nokeys] [-info]
[-des | -des3 | -idea | -aes128 | -aes192 | -aes256| -camellia128 | -camellia192 | -camellia256 | -nodes]
[-noiter] [-maciter | -nomaciter | -nomac][-twopass] [-descert] [-certpbe cipher]
[-keypbe cipher] [-macalg digest] [-keyex] [-keysig] [-password arg] [-passin arg]
[-passout arg] [-rand file(s)][-CAfile file] [-CApath dir] [-CSP name]
1
2
3
4
5
6
7
这里有许多选项,默认情况下pkcs12用于解析pkcs12文件,使用-export选项可以创建一个PKCS#12文件
解析选项
-in filename——指定输入的pkcs12文件,不指定则从标准输入
-out filename——指定输出的证书或私钥文件,不指定则为标准输出,只能时PEM格式
-passin arg——输入的pkcs12文件的口令
-passout arg——输出的口令
-password arg——和-export一起使用时等同于 -passout,否则等同于-passin
-noout——禁止在将私钥和证书输出为pkcs12时,显示编码版本信息
-clcerts—— 只输出客户端证书(而不是CA证书)
-cacerts——只输出CA证书(不是客户端证书)。
-nocerts—— 不会输出任何证书。
-nokeys ——不输出私钥。
-info——输出有关PKCS#12文件结构,使用的算法和迭代计数等附加信息。
-des | -des3 | -idea | -aes128 | -aes192 | -aes256| -camellia128 | -camellia192 | -camellia256 输出私钥的加密方式
-nodes——根本不加密私钥。
-nomacver——在读取文件之前,不要尝试验证MAC完整性。
-twopass——提示单独的完整性密码和加密密码:大多数软件总是假定都是一样的,所以这个选项会使这样的PKCS#12文件不可读。
文件创建选项
-export——此选项指定PKCS#12文件将被创建而不是解析。
-out filename—— 这指定将PKCS#12文件输出的文件名。默认使用标准输出。
-in filename——输入的证书和私钥的文件名,默认为标准输入。他们都必须是PEM格式。顺序无关紧要,但应有一个私钥及其相应的证书。如果存在其他证书,它们也将被包含在PKCS#12文件中。
-inkey filename——指定读取私钥的文件。如果不存在,则私钥必须存在于输入文件中。
-name friendlyname——这指定了证书和私钥的“友好名称”。该名称通常通过软件导入文件显示在列表框中。
-certfile filename——从其中读取其他证书的文件名。
-caname friendlyname—— 这为其他证书指定了“友好名称”。可以多次使用此选项以按照它们显示的顺序为所有证书指定名称。 Netscape忽略其他证书上的友好名称,而MSIE显示它们。
-pass arg, -passout arg——输出的pkcs12文件的口令
-passin password——输入的私钥的口令
-chain—— 如果此选项存在,则尝试包括用户证书的整个证书链。标准CA store 用于此搜索。如果搜索失败,则被认为是致命的错误。
-descert——使用三重DES加密证书,这可能会导致PKCS#12文件无法读取某些“导出等级”软件。默认情况下,私钥使用三重DES加密,证书使用40位RC2加密。
-keypbe alg,-certpbe alg——这些选项允许指定加密私钥和证书的算法。可以使用任何PKCS#5 v1.5或PKCS#12 PBE算法名称。如果指定了一个密码名称(如list-cipher-algorithms命令的输出),那么它与PKCS#5 v2.0一起使用。为了实现互操作性,建议只使用PKCS#12算法。
-keyex|-keysig——指定私钥用于密钥交换或只是签名。 此选项仅由MSIE和类似的MS软件解释。 通常,“出口等级”软件只允许512位RSA密钥用于加密目的,但用于签名的任意长度密钥。 -keysig选项标记仅用于签名的键。 只能签名密钥可用于S / MIME签名,Authenticode(ActiveX控件签名)和SSL客户端身份验证,但只有MSIE 5.0及更高版本的才支持使用仅签名密钥进行SSL客户端身份验证。
-macalg digest—— 指定MAC摘要算法。 默认使用SHA1。
-nomaciter,-noiter——这些选项会影响MAC和密钥算法的迭代次数。除非您希望生成与MSIE 4.0兼容的文件,否则您应该不使用这些选项。为了阻止通过使用大量常用密码的字典来攻击,从密码导出密钥的算法可能会使用迭代计数:这会导致算法的某一部分被重复并减慢。 MAC用于检查文件的完整性,但是由于它通常具有与密钥和证书相同的密码,因此它也可能被攻击。默认情况下,MAC和加密迭代计数都设置为2048,使用这些选项可以将MAC和加密迭代计数设置为1,这将减少文件的安全性,除非您真的必须使用这些选项。大多数软件支持MAC和密钥迭代计数。 MSIE 4.0不支持MAC迭代计数,因此需要-nomaciter选项。
-maciter——此选项以是为了与以前的版本兼容,以前需要使用MAC迭代计数,但现在默认情况下它们已被使用。
-nomac——不要尝试提供MAC完整性。
-rand file(s)——指定随机数种子文件,或者EGD套接字的随机数据的文件,多个文件间用分隔符分开,windows用“;”,OpenVMS用“,“,其他系统用“:”
-CAfile file—— CA存储为文件。
-CApath dir——CA存储为目录。此目录必须是标准证书目录:这是每个主题名称的哈希(使用x509 -hash)应链接到每个证书。
-CSP name——将名称写为Microsoft CSP名称。
注意
虽然有很多选项,但大多数选项很少使用。对于仅PKCS#12文件解析,-in和-out需要用使用,对于PKCS#12文件创建,-export和-name也要使用。
如果不存在-cacerts,-cacerts或-nocerts选项,则所有证书将按照它们在输入PKCS#12文件中显示的顺序输出。不能保证第一个证书是与私钥对应的证书。某些软件需要私钥和证书并假定文件中第一个证书是与私钥相对应:这可能并非总是如此。使用-clcerts选项将通过仅输出与私钥对应的证书来解决此问题。如果需要CA证书,则可以使用-nokeys -cacerts选项将其输出到单独的文件,以便输出CA证书。
-keypbe和-certpbe算法允许指定私钥和证书的精确加密算法。 通常,默认值是正常的,但是偶尔软件不能处理三重DES加密的私钥,那么选项-keypbe PBE-SHA1-RC2-40可以用于将私钥加密减少到40位RC2。 所有算法的完整描述包含在pkcs8手册页中。
BUGS
有些人会认为PKCS#12标准是一个大错误
在0.9.6a之前的OpenSSL版本在PKCS#12密钥生成中有错误。在极少的情况下,这可能会产生一个使用无效密钥加密的PKCS#12文件。因此,一些从其他应用(MSIE或Netscape)触发这个错误的PKCS#12文件无法被OpenSSL解密,并且OpenSSL也可能产生不能被其他应用解密的PKCS#12文件。生成这样的文件的机会相对较小:256个不到1个。
修复这个错误的副作用是任何旧的无效加密的PKCS#12文件不能再被固定版本解析。在这种情况下,pkcs12实用程序会报告MAC是否正常,但是在提取私钥时会出现解密错误。
通过使用较早版本的OpenSSL从PKCS#12文件中提取私钥和证书,并使用较新版本的OpenSSL从密钥和证书中重新创建PKCS#12文件,可以解决此问题。例如:
old-openssl -in bad.p12 -out keycerts.pem
openssl -in keycerts.pem -export -name“我的PKCS#12文件”-out fixed.p12
1
2
例子
$openssl pkcs12
1
2.5 工具类
2.5.1 smime
   smime命令处理S / MIME邮件。它可以加密,解密,签名和验证S / MIME消息

openssl smime [-encrypt] [-decrypt] [-sign] [-resign] [-verify] [-pk7out] [-[cipher]]
[-in file] [-no_alt_chains] [-certfile file] [-signer file] [-recip file]
[-inform SMIME|PEM|DER] [-passin arg] [-inkey file] [-out file] [-outform SMIME|PEM|DER]
[-content file] [-to addr] [-from ad] [-subject s] [-text] [-indef] [-noindef] [-stream]
[-rand file(s)] [-md digest] [cert.pem]...
1
2
3
4
5
命令选项——有六个操作选项可以设置要执行的操作类型。其他选项的含义因操作类型而异
-encrypt—— 加密指定收件人证书的邮件。输入文件是要加密的消息。输出文件是MIME格式的加密邮件。
-decrypt—— 使用提供的证书和私钥解密邮件。输入文件的是MIME格式的加密邮件消息。解密的邮件被写入到输出文件。
-sign——使用提供的证书和私钥签名。输入文件是要签名的消息。 MIME格式的签名消息将写入到输出文件。
-verify——验证签名邮件。期望输入的签名邮件消息并输出签名数据。支持明文和不透明签名。
-pk7out——输入消息并写出PEM编码的PKCS#7结构。
-resign——利用已有的消息或者一个或多个签名者重新签名。
-in filename——要加密或签名的输入消息或要解密或验证的MIME消息。
-inform SMIME|PEM|DER——这指定了PKCS#7结构的输入格式。默认值为SMIME,它读取S / MIME格式的消息。 指定PEM和DER以读取PEM和DER格式PKCS#7结构。这当前只影响PKCS#7结构的输入格式,如果没有输入PKCS#7结构(例如使用-encrypt或-sign),则此选项不起作用。
-out filename——输出的已解密或验证的消息文本或已签名或加密的MIME格式消息。
-outform SMIME|PEM|DER—— 这指定了PKCS#7结构的输出格式。默认是SMIME,它写入一个S / MIME格式的消息。 PEM和DER格式更改为输出PEM和DER格式PKCS#7结构。这当前只影响PKCS#7结构的输出格式,如果没有输出PKCS#7结构(例如使用-verify或-decrypt),则此选项不起作用。
-stream -indef ——-stream和-indef选项是等效的,启用用于编码操作的流I / O。这允许数据的单次处理,而不需要将整个内容保存在内存中,可支持非常大的文件。如果输出格式为SMIME,则默认情况下,所有其他操作都关闭流,S / MIME签名将自动设置为流。
-noindef——在产生不定长度构造的编码时禁用流IO。 此选项目前无效。 在将来流式传输将在所有相关操作上默认启用,此选项将禁用它。
-content filename—— 这指定一个包含分离的内容的文件,这仅对-verify命令有用。 这仅在PKCS#7结构使用不包含内容的分离签名格式才可用。 如果输入格式为S / MIME,则此选项将覆盖任何内容,并使用多部分/已签名的MIME内容类型。
-text—— 如果加密或签名,此选项会将明文的(文本/纯文本)MIME头部添加到提供的消息中。 如果解密或验证它剥离明文头部:如果解密或已验证的消息不是MIME类型text / plain,则会发生错误。
-CAfile file—— 包含受信任的CA证书的文件,仅与-verify一起使用。
-CApath dir—— 包含受信任的CA证书的目录,仅与-verify一起使用。此目录必须是标准证书目录:这是每个主题名称的哈希(使用x509 -hash)应链接到每个证书。
-md digest——在签名或辞职时使用的摘要算法。如果不存在,那么将使用默认摘要算法签名(通常是SHA1)。
-[cipher]——使用加密算法。例如,DES(56位) -des,三重DES(168位)-des3,EVP_get_cipherbyname()函数)也可以在破折号之前使用,例如-aes_128_cbc。请参阅OpenSSL支持的密码列表。如果未指定,则使用三重DES。仅用于-encrypt。
-nointern—— 当验证消息时,通常将搜索包含在消息中的签名证书。使用此选项,只会使用-certfile选项中指定的证书。但是,提供的证书仍然可以用作不可信CA。
-noverify—— 不要验证签名信息的签名者证书。
-nochain——不要对签名者证书进行证书链验证:不要将签名的邮件中的证书作不可信CAs。
-nosigs——不要尝试验证邮件上的签名。
-nocerts——当签名的时候不包含签名者的证书信息值,默认是要包含的,这将减少签名消息的大小,但验证者必须具有本地可用的签名者证书的副本(例如使用-certfile选项传递)。
-noattr——通常当消息被签名时,应包括一组属性,其中有签名时间和支持的对称算法。使用此选项,它们不包括在内。
-binary——通常,输入消息被转换为“规范”格式,其按照S / MIME规范的要求使用CR和LF作为行尾。当此选项存在时,不会发生转换。这在处理可能不是MIME格式的二进制数据时很有用。
-nodetach——签名时使用不透明的签名:此格式更能抵抗邮件中继的翻译,但不能由不支持S / MIME的邮件代理读取。如果没有此选项,则使用MIME类型multipart / signed进行明文签名。
-certfile file——允许指定其他证书。当签名这些证书将包含在消息中。当验证这些将搜索签署者证书。证书应采用PEM格式。
-signer file——在签名或者重新签名时的签名证书,如果需要多个签名者,该选项可以多次使用。 如果正在验证消息,则验证成功后,签名者证书将被写入该文件。
-recip file——解密消息时的收件人证书。 此证书必须与消息的一个收件人匹配,否则发生错误。
-inkey file—— 签名或解密时使用的私钥。 这必须匹配相应的证书。 如果未指定此选项,则私钥必须包含在使用-recip或-signer文件指定的证书文件中。 签名时,可以多次使用此选项来指定连续的私钥。
-passin arg——私钥口令
-rand file(s)——指定随机数种子文件,或者EGD套接字的随机数据的文件,多个文件间用分隔符分开,windows用“;”,OpenVMS用“,“,其他系统用“:”
cert.pem…——一个或多个邮件收件人证书:在加密邮件时使用。
-to, -from, -subject——相关邮件标题。 这些包含在消息的签名部分之外,可以手动包含它们。 如果签名,那么许多S / MIME邮件客户端会检查签名者证书的电子邮件地址是否匹配,在From:address中指定。
-purpose, -ignore_critical, -issuer_checks, -crl_check, -crl_check_all, -policy_check, -extended_crl, -x509_strict, -policy -check_ss_sig -no_alt_chains——设置证书链验证的各种选项。 详见验证手册页。
注意
发送MIME消息必须在标头和输出之间没有任何空白行。某些邮件程序会自动添加一个空行。将邮件直接发送到sendmail是实现正确格式的一种方式。
提供的要签名或加密的消息必须包含必需的MIME头部,否则许多S / MIME客户端不会正确显示(如果有的话)。您可以使用-text选项自动添加纯文本标题。
“签名并且加密”消息是一个已签名且被加密的消息。这可以通过加密已经签名的消息来生成:参见示例部分。
该版本的程序只允许每个消息一个签名者,但它将验证接收到的消息的多个签名者。某些S / MIME客户端如果消息包含多个签名者,则会阻塞。通过签名已经签名的消息可以并行地签署消息。
选项-encrypt和-decrypt反映S / MIME客户端中的常见用法。严格来说,者用于处理PKCS#7信封数据:PKCS#7加密数据用于其他目的。
-resign选项在添加新签名者时使用现有的消息摘要。这意味着属性至少必须存在于使用相同消息摘要的一个现有签名者中,否则此操作将失败。
-stream和-indef选项支持实验流I / O支持。因此,编码是使用不确定长度构造的BER编码而不再是DER。如果内容未分离,则-encrypt操作和-sign操作支持流式传输。
Streaming始终用于具有分离数据的-sign操作,但是内容不再是PKCS#7结构的一部分,而编码仍然是DER。
退出代码
0——操作完全成功。
1——解析命令选项发生错误。
2——其中一个输入文件无法读取。
3——创建PKCS#7文件或读取MIME消息时发生错误。
4——解密或验证消息时发生错误。
5——消息已正确验证,但写入签名者证书时出现错误。
bug
MIME解析器不是很聪明:它似乎处理了我抛出的大多数消息,但它可能会阻碍别人。
当前只会将签名者的证书写入文件:如果签名者具有单独的加密证书,则必须手动提取。
理想情况下,应保留每个电子邮件地址的证书的数据库
代码当前没有注意SMIMECapabilities signed属性中提供的允许的对称加密算法。 这意味着用户必须手动包括正确的加密算法。 它应该将允许的密码列表存储在数据库中,并使用它们。
签名人的证书不进行吊销检查。
当前的代码只能处理S / MIME v2消息,更复杂的S / MIME v3结构可能会导致解析错误。
例子
1
  声明:OpenSSL之命令行详解是根据卢队长发布在https://blog.csdn.net/as3luyuan123/article/details/16105475的系列文章整理修改而成,我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!
---------------------
原文链接:https://blog.csdn.net/liao20081228/article/details/77159039

【引言】
ASN.1全称为Abstract Syntax NotationOne,是一种描述数字对象的方法和标准。openssl的编码方法就是基于该标准的,目前,很多其他软件的编码方法也是基于该标准。对于直接使用openssl的API或者应用程序来说,可能对ASN.1的了解并不需要很清楚,但是为了使大家对后续介绍的各个API有一个更深刻的编码知识基础,所以对该ASN.1以及openssl相应提供的API处理函数作介绍。

【ASN.1概述】
ASN.1作为一个数字对象描述标准,包括了两部分,分别为数据描述语言(ISO8824)和数据编码规则(ISO8825)。ASN.1的数据描述语言标准允许用户自定义基本数据类型,并可以通过简单的数据类型组成更复杂的数据类型。比如一个复杂的数据对象,如X509证书,就是在其它一些数据类型上定义的,而其它数据类型又是在更基本的数据类型上建立的,直到回溯到定义的最基本的数据类型。
比如ASN.1定义的X509证书的一个子域Validity(证书有效期)就定义如下:
Validity ::= SEQUENCE 
{
  notBefore            UTCTIME,
  notAfter             UTCTIME
}
其意义就是定义Validity为一个有序序列,由两个个UTCTIME类型的数据notBefore和notAfter组成。然后,就需要找出UTCTIME是怎么定义的,当然,事实上UTCTIME是ASN.1定义的一个基本的时间数据类型。
在上述数据定义的基础上,ASN.1定义了一组编码规则,以规定怎么将上述描述的对象转换成应用程序能够处理和进行传输的二进制编码形式。ASN.1定义了多种编码方法,包括了BER, DER, PER, 和XER等,不过,虽然最基本最常用的编码方式是BER(Basic EncodingRules),但是由于该编码方法可能对一个相同的对象有几种不同的合法二进制编码,所以在openssl里面使用的是BER的子集DER(Distinguished Encoding Rules),使用DER编码方法,对于每一个ASN.1对象,其相应的二进制编码是唯一的。

ASN.1里定义的每个基本对象都有一个对应的数字标识tag,在进行二进制编码的时候需要使用该标志。
【ASN.1定义的基本数据类型】
下面列出ASN.1定义的部分基本数据类型,其各字段的意义如下:
[数据类型]-[数据说明]-[Tag(16进制)]
[BOOLEAN]-[有两个值:false或true]-[01]
[INTEGER]-[整型值]-[02]
[BIT STRING]-[0位或多位]-[03]
[OCTET STRING]-[0字节或多字节]-[04]
[NULL]-[NULL值]-[05]
[OBJECT IDENTIFIER]-[相应于一个对象的独特标识数字]-[06]
[OBJECT DESCRIPTOR]-[一个对象的简称]-[07]
[EXTERNAL]-[ASN.1没有定义的数据类型]-[08]
[REAL]-[实数值]-[09]
[ENUMERATED]-[数值列表,这些数据每个都有独特的标识符,作为ASN.1定义数据类型的一部分]-[0a]
[SEQUENCE和SEQUENCE OF]-[有序数列,SEQUENCE里面的每个数值都可以是不同类型的,而SEQUENCE OF里是0个或多个类型相同的数据]-[10]
[SET和SET OF]-[无序数列,SET里面的每个数值都可以是不同类型的,而SET OF里是0个或多个类型相同的数据]-[11]
[NumericString]-[0-9以及空格]-[12]
[PrintableString]-[A-Z、a-z、0-9、空格以及符号 ()+,-./:=?]-[13]
[UTCTime]-[统一全球时间格式]-[17]

除了上述基本类型,ASN.1还定义了另外一些专用的数据类型,这里不再一一叙述。

 

 

openssl之ASN.1系列之2---ASN.1编码方法简介

参考资料:“Computer Network”,“A Layman s Guide to a Subset of ASN.1, BER, and DER”


ASN.1对象的编码是ASN.1标准的重要部分,目前,通常采用的是BER,而DER则是其一个子集。本文将对该编码方法作简单的介绍。
一个标准的ASN.1编码对象有四个域:对象标识域、数据长度域、数据域以及结束标志(可选,在长度不可知情况下需要,openssl中没有该标志)。
【对象标识域】
对象标识域有两种形式,低Tag数字(Tag值在0到30)和高Tag数字(Tag值大于30)形式。
低Tag数字形式只有一个字节,包含三部分,从低位为1开始编号,8和7位是Tag类型,共有四种,分别是universal(00)、application(0 1)、context-specific(1 0)和private(11);第6位是0,表明编码类型是基本类型,第5-1位是Tag值。
高Tag数字形式可以有两个或多个字节,第一个字节跟低Tag数字形式一样,但低5位值全为1,而在后续的第二个和其后的字节中给出Tag值,这些字节都只使用了低7位为数据位,最高位都设为0,但最后一个字节的最高位设为1,采用高位优先,经可能少的数字原则。
【数据长度域】
数据长度域也有两种形式,短形式和长形式。
短形式的数据长度域只有一个字节,第8位为0,其它低7位给出数据长度。
长形式的数据长度域有2到127个字节。第一个字节的第8位为1,其它低7位给出后面该域使用的字节的数量,从该域第二个字节开始给出数据的长度,基于256,高位优先。
【数据域】
数据域给出了具体的数据值。该域的编码对不同的数据类型不一样,这里就不在一一详述了,有兴趣的可以参看参考资料。

【一个编码例子】
下面是SSLDocument给出的对一个对象进行DER编码的例子,更多的例子可以参看本文给出的参考资料。
例子使用的对象是ASN.1定义的BIT STRING类型的对象,其编码的步骤如下:
1.对位串使用"0"进行填补,使其长度为8的整数倍(如果已经是整数倍,则不需要进行填补);
2.计算填补的位数并写下来,成为数据内容的第一个字节;
3.写入填补后的位串,高位字节优先。这些数据跟前面的一个字节组成数据内容的全部字节;
4.在这些数据前面加上一个头字节,这个字节定义如下(编号是从低位为1开始):
 第8、7位:00(universal类型)
 第 6 位 :0(表明是基本类型,有限长度的编码)
 第5-1位:0x03(表明是BIT STRING)
这个字节定义了对象标识域;
5.然后在对象标识域字节和数据字节之间加入下面计算的定义的字节:
 计算有多少字节的数据内容(对象标识域数据除外),如果少于127字节,那么就定义一个字节如下:
  第8位:0
  第7-1位:数据内容的字节数量
 如果数据内容的字节数量大于127,就需要定义两个或多个字节,其中,第一个字节的定义如下:
  第8位:1
  第7-1位:该域后面还有多少字节
  其后的字节是数据内容的字节数量,每字节基于256,高位优先
下面是一个实际的数据例子:
位串: 01000100111011 
1.补齐两个0在后面,成为8的整数倍,得到 0100010011101100 ;
2. 02 作为第一个数据内容的字节;
3. 44 ec 作为其余的数据内容的字节;
4. 03 作为前面的对象标识字节;
5.因为BIT STRING的tag值3<=127,所以只有一个字节的长度域 03 ;
那么得到的这个位串的DER编码就是03 03 02 44 ec,其中,第一个字节是对象标识域,第二个字节是数据长度域,其他为数据域。

 

openssl之ASN.1系列之3---ASN.1函数概述和结构

【ASN.1函数库概述】
因为X509相关的协议都是基于ASN.1和DER编码的,所以openssl提供了一组函数,这些函数可以读取DER编码的对象,并将它们转换成openssl能够处理的内部格式;这些函数也可以将openssl里定义的C格式的对象结构转换成DER编码的对象。此外,该系列还提供了一些对这些对象进行比较、读取和设定指定值的函数。该系列函数还包括了一些签名函数,这是因为在签名之前,有些对象需要进行DER编码。

下面对ASN.1函数库中重要的数据结构做简单的介绍。
【ASN1_CTX】
该结构用来在ASN1处理过程中维护跟踪各种相关变量,其定义如下:
typedef struct asn1_ctx_st
{

 unsigned char *p;
 int eos;
 int error;
 int inf; 
 int tag; 
 int xclass; 
 long slen; 
 unsigned char *max; 
 unsigned char *q;  
 unsigned char **pp;
 int line; 

} ASN1_CTX;

  • 参数p是工作字符指针,其最大长度由参数max指定;
  • eos是indefinite编码模式的结束标识标志;
  • error是错误代码;
  • inf值为0x20代表constructd模式,为0x21代表indefinite模式;
  • tag是最后取得的对象的tag值;
  • xclass是最后取得的对象的类型;
  • slen是最后取得的对象的长度;
  • line变量在出错处理的时候使用。

【ASN1_OBJECT】
该结构用来保存一个ASN1对象,其定义如下:
typedef struct asn1_object_st
{
        char *sn,*ln;
        int nid;
        int length;
        unsigned char *data;
        int flags;
}ASN1_OBJECT;

  • nid是openssl内部定义的每个数字对象的独特标识码;
  • sn是对象的简称;
  • ln是对象的长名或小写名;
  • data是相应对象的数据,
  • length是该data字段的长度,
  • flags是一个释放标志。

【ASN1_STRING】
该结构是openssl里一个很基本的ASN.1对象结构,Openssl里定义的很多类型的对象都是采用该结构的,他们包括ASN1_INTEGER、ASN1_BIT_STRING、ASN1_OCTET_STRING、ASN1_PRINTABLESTRING、ASN1_T61STRING、ASN1_IA5STRING、ASN1_UTCTIME、ASN1_GENERALIZEDTIME、ASN1_GENERALSTRING、ASN1_UNIVERSALSTRING和ASN1_BMPSTRING。该结构的定义如下:
typedef struct asn1_string_st
{
       int length;
       int type;
       unsigned char *data;

 long flags;

} ASN1_STRING;

  • type参数指明对象的类型;
  • data参数是对象的数据,
  • length指定了其长度;
  • flags值跟type有关,一般来说,在BIT_STRING对象中使用。

【ASN1_TYPE】
该结构可以保存任意类型的ASN.1对象,其定义如下:
typedef struct asn1_type_st
{
        int type;
        union   
 {
                char *ptr;
  ASN1_BOOLEAN  boolean;
                ASN1_STRING *           asn1_string;
                ASN1_OBJECT *           object;
                ASN1_INTEGER *          integer;
  ASN1_ENUMERATED * enumerated;
                ASN1_BIT_STRING *       bit_string;
                ASN1_OCTET_STRING *     octet_string;
                ASN1_PRINTABLESTRING *  printablestring;
                ASN1_T61STRING *        t61string;
                ASN1_IA5STRING *        ia5string;
                ASN1_GENERALSTRING *    generalstring;
                ASN1_BMPSTRING *        bmpstring;
                ASN1_UNIVERSALSTRING *  universalstring;
                ASN1_UTCTIME *          utctime;
                ASN1_GENERALIZEDTIME *  generalizedtime;
  ASN1_VISIBLESTRING * visiblestring;
  ASN1_UTF8STRING * utf8string;
                ASN1_STRING *           set;
                ASN1_STRING *           sequence;
 } value;
} ASN1_TYPE;
其中,参数type指定了对象的类型。
【ASN1_METHOD】
该结构包含了指向一组函数的指针,这些函数定义了在openssl内部结构和DER编码对象之间进行格式转换的功能,还定义了分配和释放一个结构的功能。其定义如下:
typedef struct asn1_method_st
{
        int (*i2d)();
        char *(*d2i)();
        char *(*create)();
        void (*destroy)();
} ASN1_METHOD;

  • i2d指向的函数将内部格式转换成DER编码格式;
  • d2i指向的函数将DER编码的对象转换成内部结构;
  • create指向的函数给新对象分配内存;
  • destroy指向的函数释放对象的内存。

例如,在文件x_x509.c里,X509对象的METHOD结构初始化如下:
static ASN1_METHOD meth=
{
        (int (*)())  i2d_X509,
        (char *(*)())d2i_X509,
        (char *(*)())X509_new,
        (void (*)()) X509_free
};

ASN1_METHOD *X509_asn1_meth()
{
        return(&meth);
}
【ASN1_HEADER】
该结构只在Netscape格式的证书里使用了(参考apps/x509.c文件),其定义如下:
typedef struct asn1_header_st
{
        ASN1_OCTET_STRING *header;
        char *data;
        ASN1_METHOD *meth;
} ASN1_HEADER;

除了上述介绍的基本结构外,ASN.1相关的结构还有几个,但这些因为不是特别通用,在这里不再作介绍,有兴趣可以看文件x509.h。

 

 

openssl之ASN.1系列之4---ASN.1对象的构造和释放

因为每种ASN.1对象都有相应的数据结构,所以openssl也提供了一系列创建和释放这些对象的函数。事实上,基本的函数并不多,很多函数是在基本的函数上提供的宏定义,主要是为了方便用户使用。
基本的对象构造和释放函数定义如下(crypto\asn1\asn1.h):
ASN1_OBJECT * ASN1_OBJECT_new(void );
void  ASN1_OBJECT_free(ASN1_OBJECT *a);
ASN1_OBJECT * ASN1_OBJECT_create(int nid, unsigned char *data,int len,const char *sn, const char *ln);
ASN1_STRING * ASN1_STRING_new(void);
void  ASN1_STRING_free(ASN1_STRING *a);
ASN1_STRING * ASN1_STRING_type_new(int type );
ASN1_HEADER * ASN1_HEADER_new(void );
void   ASN1_HEADER_free(ASN1_HEADER *a);
ASN1_VALUE * ASN1_item_new(const ASN1_ITEM *it);
void   ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it);

【ASN1_OBJECT】
该系列的new和free函数分别完成了ASN1_OBJECT对象的创建和释放。在创建ASN1_OBJECT对象的时候,该函数给对象分配内存空间,并将结构内所有指针类型的变量值都设为NULL,nid和长度都初始化为0,并将flags设置为ASN1_OBJECT_FLAG_DYNAMIC,返回创建的对象的指针,如果失败,返回NULL。对象释放的时候,free函数将所有对象成员的内存和自身的内存释放,并将lenght设置为0。
除了使用ASN1_OBJECT_new创建ASN1_OBJECT对象,还可以使用ASN1_OBJECT_create函数创建对象,该函数根据给定的参数创建一个ASN1_OBJECT对象。其中,nid是对象的独特标识NID;data为对象的数据,len指定了data有效数据的长度;sn是对象的简称;ln是对象的完整名字或小写名字;该函数将falgs标志设为ASN1_OBJECT_FLAG_DYNAMIC|ASN1_OBJECT_FLAG_DYNAMIC_STRINGS|ASN1_OBJECT_FLAG_DYNAMIC_DATA。该函数返回一个对象结构体,而不是指针。

【ASN1_STRING】
ASN1_STRING_type_new函数根据给定的参数type创建并返回一个ASN1_STRING对象指针。事实上,该函数不管type是什么,都是创建一个ASN1_STRING对象,然后将成员data初始化为NULL,flags和length初始化为0。唯一有区别的就是令成员变量type的值为参数type的值,目前支持的type值如下:
V_ASN1_BIT_STRING
V_ASN1_INTEGER
V_ASN1_ENUMERATED
V_ASN1_OCTET_STRING
V_ASN1_T61STRING
V_ASN1_PRINTABLESTRING
V_ASN1_VISIBLESTRING
V_ASN1_IA5STRING
V_ASN1_UTCTIME
V_ASN1_GENERALIZEDTIME
V_ASN1_GENERALSTRING
V_ASN1_UNIVERSALSTRING
V_ASN1_BMPSTRING
V_ASN1_UTF8STRING
ASN1_STRING_new函数是调用返回了ASN1_STRING_type_new函数实现的,其type参数为V_ASN1_OCTET_STRING。上述两个函数成功执行返回一个ASN1_STRING的指针,否则返回NULL。ASN1_STRING_free函数释放了用上述两个函数创建ASN1_STRING对象,没有返回值。

【ASN1_HEADER】
这两个函数用于处理Netscape格式的证书和私钥对象。事实上,new函数创建了一个ASN1_HEADER对象,并调用了ASN1_STRING_type_new函数,使用type参数为V_ASN1_OCTET_STRING对该对象的header成员变量进行初始化,并将meth和data设置为NULL,返回ASN1_HEADER对象的指针。free函数释放该类型对象,没有返回值。
【ASN1_VALUE】
这两个函数一般已经不在使用,只是为了兼容以前的版本而保留了下来。这里不再作介绍。
【基于上述基本函数的宏定义函数】
基于上述的基本函数,尤其是ASN1_STRING_type_new函数,openssl提供了很多为了方便用户使用的宏定义,这些宏定义的形式是如下述形式:
构造函数:M_对象名_new()
释放函数:M_对象名_free(a)
由于函数较多,具体可以参考asn1.h文件,这里不再一一列出了。

原文链接:https://blog.csdn.net/jasenwan88/article/details/7718851

Parsing X.509 Certificates with OpenSSL and C

Zakir Durumeric | October 13, 2013

While OpenSSL has become one of the defacto libraries for performing SSL and TLS operations, the library is surprisingly opaque and its documentation is, at times, abysmal. As part of our recent research, we have been performing Internet-wide scans of HTTPS hosts in order to better understand the HTTPS ecosystem (Analysis of the HTTPS Certificate EcosystemZMap: Fast Internet-Wide Scanning and its Security Applications). We use OpenSSL for many of these operations including parsing X.509 certificates. However, in order to parse and validate certificates, our team had to dig through parts of the OpenSSL code base and multiple sources of documention to find the correct functions to parse each piece of data. This post is intended to document many of these operations in a single location in order to hopefully alleviate this painful process for others.

If you have found other pieces of code particularly helpful, please don’t hesitate to send them alongand we’ll update the post. I want to note that if you’re starting to develop against OpenSSL, O’Reilly’sNetwork Security with OpenSSL is an incredibly helpful resource; the book contains many snippets and pieces of documentation that I was not able to find anywhere online. I also want to thank James Kastenwho helped find and document several of these solutions.

Creating an OpenSSL X509 Object

All of the operations we discuss start with either a single X.509 certificate or a “stack” of certificates. OpenSSL represents a single certificate with an X509 struct and a list of certificates, such as the certificate chain presented during a TLS handshake as a STACK_OF(X509). Given that the parsing and validation stems from here, it only seems reasonable to start with how to create or access an X509 object. A few common scenarios are:

1. You have initiated an SSL or TLS connection using OpenSSL.

In this case, you have access to an OpenSSL SSL struct from which you can extract the presented certificate as well as the entire certificate chain that the server presented to the client. In our specific case, we use libevent to perform TLS connections and can access the SSL struct from the libevent bufferevent: SSL *ssl = bufferevent_openssl_get_ssl(bev). This will clearly be different depending on how you complete your connection. However, once you have your SSL context, the server certificate and presented chain can be extracted as follows:

#include <openssl/x509.h>
#include <openssl/x509v3.h>

X509 *cert = SSL_get_peer_certificate(ssl);
STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);

We have found that at times, OpenSSL will produce an empty certificate chain (SSL_get_peer_cert_chain will come back NULL) even though a client certificate has been presented (the server certificate is generally presented as the first certificate in the stack along with the remaining chain). It’s unclear to us why this happens, but it’s not a deal breaker, as it’s easy to create a new stack of certificates:

X509 *cert = SSL_get_peer_certificate(ssl);
STACK_OF(X509) *sk = sk_X509_new_null();
sk_X509_push(sk, cert);

2. You have stored a certificate on disk as a PEM file.

For reference, a PEM file is the Base64-encoded version of an X.509 certificate, which should look similar to the following:

-----BEGIN CERTIFICATE-----
MIIHIDCCBgigAwIBAgIIMrM8cLO76sYwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
iftrJvzAOMAPY5b/klZvqH6Ddubg/hUVPkiv4mr5MfWfglCQdFF1EBGNoZSFAU7y
ZkGENAvDmv+5xVCZELeiWA2PoNV4m/SW6NHrF7gz4MwQssqP9dGMbKPOF/D2nxic
TnD5WkGMCWpLgqDWWRoOrt6xf0BPWukQBDMHULlZgXzNtoGlEnwztLlnf0I/WWIS
eBSyDTeFJfopvoqXuws23X486fdKcCAV1n/Nl6y2z+uVvcyTRxY2/jegmV0n0kHf
gfcKzw==
-----END CERTIFICATE-----

In this case, you can access the certificate as follows:

#include <stdio.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

FILE *fp = fopen(path, "r");
if (!fp) {
fprintf(stderr, "unable to open: %s\n", path);
return EXIT_FAILURE;
}

X509 *cert = PEM_read_X509(fp, NULL, NULL, NULL);
if (!cert) {
fprintf(stderr, "unable to parse certificate in: %s\n", path);
fclose(fp);
return EXIT_FAILURE;
}

// any additional processing would go here..

X509_free(cert);
fclose(fp);

3. You have access to the raw certificate in memory.

In the case that you have access to the raw encoding of the certificate in memory, you can parse it as follows. This is useful if you have stored raw certificates in a database or similar data store.

#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/bio.h>

const unsigned char *data = ... ;
size_t len = ... ;

X509 *cert = d2i_X509(NULL, &data, len);
if (!cert) {
fprintf(stderr, "unable to parse certificate in memory\n");
return EXIT_FAILURE;
}

// any additional processing would go here..

X509_free(cert);

4. You have access to the Base64 encoded PEM in memory.

char* pemCertString = ..... (includes "-----BEGIN/END CERTIFICATE-----")
size_t certLen = strlen(pemCertString);

BIO* certBio = BIO_new(BIO_s_mem());
BIO_write(certBio, pemCertString, certLen);
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL);
if (!certX509) {
fprintf(stderr, "unable to parse certificate in memory\n");
return EXIT_FAILURE;
}

// do stuff

BIO_free(certBio);
X509_free(certX509);

Parsing Certificates

Now that we have access to a certificate in OpenSSL, we’ll focus on how to extract useful data from the certificate. We don’t include the #includes in every statement, but use the following headers throughout our codebase:

#include <openssl/x509v3.h>
#include <openssl/bn.h>
#include <openssl/asn1.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <openssl/pem.h>
#include <openssl/bio.h>

OpenSSL_add_all_algorithms();

You will also need the development versions of the OpenSSL libraries and to compile with -lssl.

Subject and Issuer

The certificate subject and issuer can be easily extracted and represented as a single string as follows:

char *subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);

These can be freed by calling OPENSSL_free.

By default, the subject and issuer are returned in the following form:

/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.google.com

If you want to convert these into a more traditional looking DN, such as:

C=US, ST=Texas, L=Austin, O=Polycom Inc., OU=Video Division, CN=a.digitalnetbr.net

they can be converted with the following code:

int i, curr_spot = 0;
char *s = tmpBuf + 1; /* skip the first slash */
char *c = s;
while (1) {
if (((*s == '/') && ((s[1] >= 'A') && (s[1] <= 'Z') &&
((s[2] == '=') || ((s[2] >= 'A') && (s[2] <= 'Z')
&& (s[3] == '='))))) || (*s == '\0')) {
i = s - c;
strncpy(destination + curr_spot, c, i);
curr_spot += i;
assert(curr_spot < size);
c = s + 1; /* skip following slash */
if (*s != '\0') {
strncpy(destination + curr_spot, ", ", 2);
curr_spot += 2;
}
}
if (*s == '\0')
break;
++s;
}

It is also possible to extract particular elements from the subject. For example, the following code will iterate over all the values in the subject:

X509_NAME *subj = X509_get_subject_name(cert);

for (int i = 0; i < X509_NAME_entry_count(subj); i++) {
X509_NAME_ENTRY *e = X509_NAME_get_entry(subj, i);
ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
char *str = ASN1_STRING_data(d);
}

or

for (;;) {
int lastpos = X509_NAME_get_index_by_NID(subj, NID_commonName, lastpos);
if (lastpos == -1)
break;
X509_NAME_ENTRY *e = X509_NAME_get_entry(subj, lastpos);
/* Do something with e */
}

Cryptographic (e.g. SHA-1) Fingerprint

We can calculate the SHA-1 fingerprint (or any other fingerprint) with the following code:

#define SHA1LEN 20
char buf[SHA1LEN];

const EVP_MD *digest = EVP_sha1();
unsigned len;

int rc = X509_digest(cert, digest, (unsigned char*) buf, &len);
if (rc == 0 || len != SHA1LEN) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

This will produce the raw fingerprint. This can be converted to the human readable hex version as follows:

void hex_encode(unsigned char* readbuf, void *writebuf, size_t len)
{
for(size_t i=0; i < len; i++) {
char *l = (char*) (2*i + ((intptr_t) writebuf));
sprintf(l, "%02x", readbuf[i]);
}
}

char strbuf[2*SHA1LEN+1];
hex_encode(buf, strbuf, SHA1LEN);

Version

Parsing the certificate version is straight-foward; the only oddity is that it is zero-indexed:

int version = ((int) X509_get_version(cert)) + 1;

Serial Number

Serial numbers can be arbitrarily large as well as positive or negative. As such, we handle it as a string instead of a typical integer in our processing.

#define SERIAL_NUM_LEN 1000;
char serial_number[SERIAL_NUM_LEN+1];

ASN1_INTEGER *serial = X509_get_serialNumber(cert);

BIGNUM *bn = ASN1_INTEGER_to_BN(serial, NULL);
if (!bn) {
fprintf(stderr, "unable to convert ASN1INTEGER to BN\n");
return EXIT_FAILURE;
}

char *tmp = BN_bn2dec(bn);
if (!tmp) {
fprintf(stderr, "unable to convert BN to decimal string.\n");
BN_free(bn);
return EXIT_FAILURE;
}

if (strlen(tmp) >= len) {
fprintf(stderr, "buffer length shorter than serial number\n");
BN_free(bn);
OPENSSL_free(tmp);
return EXIT_FAILURE;
}

strncpy(buf, tmp, len);
BN_free(bn);
OPENSSL_free(tmp);

Signature Algorithm

The signature algorithm on a certificate is stored as an OpenSSSL NID:

int pkey_nid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);

if (pkey_nid == NID_undef) {
fprintf(stderr, "unable to find specified signature algorithm name.\n");
return EXIT_FAILURE;
}

This can be translated into a string representation (either short name or long description):

char sigalgo_name[SIG_ALGO_LEN+1];
const char* sslbuf = OBJ_nid2ln(pkey_nid);

if (strlen(sslbuf) > PUBKEY_ALGO_LEN) {
fprintf(stderr, "public key algorithm name longer than allocated buffer.\n");
return EXIT_FAILURE;
}

strncpy(buf, sslbuf, PUBKEY_ALGO_LEN);

This will result in a string such as sha1WithRSAEncryption or md5WithRSAEncryption.

Public Key

Parsing the public key on a certificate is type-specific. Here, we provide information on how to extract which type of key is included and to parse RSA and DSA keys:

char pubkey_algoname[PUBKEY_ALGO_LEN];

int pubkey_algonid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);

if (pubkey_algonid == NID_undef) {
fprintf(stderr, "unable to find specified public key algorithm name.\n");
return EXIT_FAILURE;
}

const char* sslbuf = OBJ_nid2ln(pubkey_algonid);
assert(strlen(sslbuf) < PUBKEY_ALGO_LEN);
strncpy(buf, sslbuf, PUBKEY_ALGO_LEN);

if (pubkey_algonid == NID_rsaEncryption || pubkey_algonid == NID_dsa) {

EVP_PKEY *pkey = X509_get_pubkey(cert);
IFNULL_FAIL(pkey, "unable to extract public key from certificate");

RSA *rsa_key;
DSA *dsa_key;
char *rsa_e_dec, *rsa_n_hex, *dsa_p_hex, *dsa_p_hex,
*dsa_q_hex, *dsa_g_hex, *dsa_y_hex;

switch(pubkey_algonid) {

case NID_rsaEncryption:

rsa_key = pkey->pkey.rsa;
IFNULL_FAIL(rsa_key, "unable to extract RSA public key");

rsa_e_dec = BN_bn2dec(rsa_key->e);
IFNULL_FAIL(rsa_e_dec, "unable to extract rsa exponent");

rsa_n_hex = BN_bn2hex(rsa_key->n);
IFNULL_FAIL(rsa_n_hex, "unable to extract rsa modulus");

break;

case NID_dsa:

dsa_key = pkey->pkey.dsa;
IFNULL_FAIL(dsa_key, "unable to extract DSA pkey");

dsa_p_hex = BN_bn2hex(dsa_key->p);
IFNULL_FAIL(dsa_p_hex, "unable to extract DSA p");

dsa_q_hex = BN_bn2hex(dsa_key->q);
IFNULL_FAIL(dsa_q_hex, "unable to extract DSA q");

dsa_g_hex = BN_bn2hex(dsa_key->g);
IFNULL_FAIL(dsa_g_hex, "unable to extract DSA g");

dsa_y_hex = BN_bn2hex(dsa_key->pub_key);
IFNULL_FAIL(dsa_y_hex, "unable to extract DSA y");

break;

default:
break;
}

EVP_PKEY_free(pkey);
}

Validity Period

OpenSSL represents the not-valid-after (expiration) and not-valid-before as ASN1_TIME objects, which can be extracted as follows:

ASN1_TIME *not_before = X509_get_notBefore(cert);
ASN1_TIME *not_after = X509_get_notAfter(cert);

These can be converted into ISO-8601 timestamps using the following code:

#define DATE_LEN 128

int convert_ASN1TIME(ASN1_TIME *t, char* buf, size_t len)
{
int rc;
BIO *b = BIO_new(BIO_s_mem());
rc = ASN1_TIME_print(b, t);
if (rc <= 0) {
log_error("fetchdaemon", "ASN1_TIME_print failed or wrote no data.\n");
BIO_free(b);
return EXIT_FAILURE;
}
rc = BIO_gets(b, buf, len);
if (rc <= 0) {
log_error("fetchdaemon", "BIO_gets call failed to transfer contents to buf");
BIO_free(b);
return EXIT_FAILURE;
}
BIO_free(b);
return EXIT_SUCCESS;
}

char not_after_str[DATE_LEN];
convert_ASN1TIME(not_after, not_after_str, DATE_LEN);

char not_before_str[DATE_LEN];
convert_ASN1TIME(not_before, not_before_str, DATE_LEN);

CA Status

Checking whether a certificate is a valid CA certificate is not a boolean operation as you might expect. There are several avenues through which a certificate can be interpreted as CA certificate. As such, instead of directly checking various X.509 extensions, it is more reliable to use X509_check_ca. Any value >= 1 is considered a CA certificate whereas 0 is not a CA certificate.

int raw = X509_check_ca(cert);

Other X.509 Extensions

Certificates can contain any other arbitrary extensions. The following code will loop through all of the extensions on a certificate and print them out:

STACK_OF(X509_EXTENSION) *exts = cert->cert_info->extensions;

int num_of_exts;
if (exts) {
num_of_exts = sk_X509_EXTENSION_num(exts);
} else {
num_of_exts = 0
}

IFNEG_FAIL(num_of_exts, "error parsing number of X509v3 extensions.");

for (int i=0; i < num_of_exts; i++) {

X509_EXTENSION *ex = sk_X509_EXTENSION_value(exts, i);
IFNULL_FAIL(ex, "unable to extract extension from stack");
ASN1_OBJECT *obj = X509_EXTENSION_get_object(ex);
IFNULL_FAIL(obj, "unable to extract ASN1 object from extension");

BIO *ext_bio = BIO_new(BIO_s_mem());
IFNULL_FAIL(ext_bio, "unable to allocate memory for extension value BIO");
if (!X509V3_EXT_print(ext_bio, ex, 0, 0)) {
M_ASN1_OCTET_STRING_print(ext_bio, ex->value);
}

BUF_MEM *bptr;
BIO_get_mem_ptr(ext_bio, &bptr);
BIO_set_close(ext_bio, BIO_NOCLOSE);

// remove newlines
int lastchar = bptr->length;
if (lastchar > 1 && (bptr->data[lastchar-1] == '\n' || bptr->data[lastchar-1] == '\r')) {
bptr->data[lastchar-1] = (char) 0;
}
if (lastchar > 0 && (bptr->data[lastchar] == '\n' || bptr->data[lastchar] == '\r')) {
bptr->data[lastchar] = (char) 0;
}

BIO_free(ext_bio);

unsigned nid = OBJ_obj2nid(obj);
if (nid == NID_undef) {
// no lookup found for the provided OID so nid came back as undefined.
char extname[EXTNAME_LEN];
OBJ_obj2txt(extname, EXTNAME_LEN, (const ASN1_OBJECT *) obj, 1);
printf("extension name is %s\n", extname);
} else {
// the OID translated to a NID which implies that the OID has a known sn/ln
const char *c_ext_name = OBJ_nid2ln(nid);
IFNULL_FAIL(c_ext_name, "invalid X509v3 extension name");
printf("extension name is %s\n", c_ext_name);
}

printf("extension length is %u\n", bptr->length)
printf("extension value is %s\n", bptr->data)
}

Misordered Certificate Chains

At times, we’ll receive misordered certificate chains. The following code will attempt to reorder certificates to construct a rational certificate chain based on each certificate’s subject and issuer string. The algorithm is O(n^2), but we generally only receive two or three certificates and in the majority-case, they will already be in the correct order.

	STACK_OF(X509) *r_sk = sk_X509_new_null();
sk_X509_push(r_sk, sk_X509_value(st, 0));

for (int i=1; i < sk_X509_num(st); i++) {
X509 *prev = sk_X509_value(r_sk, i-1);
X509 *next = NULL;
for (int j=1; j < sk_X509_num(st); j++) {
X509 *cand = sk_X509_value(st, j);
if (!X509_NAME_cmp(cand->cert_info->subject, prev->cert_info->issuer)
|| j == sk_X509_num(st) - 1) {
next = cand;
break;
}
}
if (next) {
sk_X509_push(r_sk, next);
} else {
// we're unable to figure out the correct stack so just use the original one provided.
sk_X509_free(r_sk);
r_sk = sk_X509_dup(st);
break;
}
}

Validating Certificates

In our scans, we oftentimes use multiple CA stores in order to emulate different browsers. Here, we describe how we create specialized stores and validate against them.

We can create a store based on a particular file with the following:

X509_STORE *s = X509_STORE_new();
if (s == NULL) {
fprintf(stderr, "unable to create new X509 store.\n");
return NULL;
}
int rc = X509_STORE_load_locations(s, store_path, NULL);
if (rc != 1) {
fprintf(stderr, "unable to load certificates at %s to store\n", store_path);
X509_STORE_free(s);
return NULL;
}
return s;

And then validate certificates against the store with the following:

X509_STORE_CTX *ctx = X509_STORE_CTX_new();
if (!ctx) {
fprintf(stderr, "unable to create STORE CTX\n");
return -1;
}
if (X509_STORE_CTX_init(ctx, store, cert, st) != 1) {
fprintf(stderr, "unable to initialize STORE CTX.\n");
X509_STORE_CTX_free(ctx);
return -1;
}
int rc = X509_verify_cert(ctx);
X509_STORE_CTX_free(ctx);
return rc;

It’s worth noting that self-signed certificates will always fail OpenSSL’s validation. While this might make sense in most client applications, we are oftentimes interested in other errors that might be present. We validate self-signed certificates by adding them into a temporary store and then validating against it. It’s a bick hackish, but is much easier than re-implementing OpenSSL’s validation techniques.

X509_STORE *s = X509_STORE_new();
int num = sk_X509_num(sk);
X509 *top = sk_X509_value(st, num-1);
X509_STORE_add_cert(s, top);
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(ctx, s, cert, st);
int rc = X509_verify_cert(ctx);
if (rc == 1) {
// validated OK. either trusted or self signed.
} else {
// validation failed
int err = X509_STORE_CTX_get_error(ctx);
}

// any additional processing..

X509_STORE_CTX_free(ctx);
X509_STORE_free(s);

Sometimes you will also find that you just need to check whether a certificate has been issued by a trusted source instead of just considering whether it is currently valid, which can be done using X509_check_issued. For example, if you wanted to check whether a certificate was self-signed:

if (X509_check_issued(cert, cert) == X509_V_OK) {
is_self_signed = 1;
} else {
is_self_signed = 0;
}

Helper Functions

There are several other functions that were used in troubleshooting and might be of help while you’re developing code against OpenSSL.

Print out the basic information about a certificate:

#define MAX_LENGTH 1024

void print_certificate(X509* cert) {
char subj[MAX_LENGTH+1];
char issuer[MAX_LENGTH+1];
X509_NAME_oneline(X509_get_subject_name(cert), subj, MAX_LENGTH);
X509_NAME_oneline(X509_get_issuer_name(cert), issuer, MAX_LENGTH);
printf("certificate: %s\n", subj);
printf("\tissuer: %s\n\n", issuer);
}

Print out each certificate in a given stack:

void print_stack(STACK_OF(X509)* sk)
{
unsigned len = sk_X509_num(sk);
printf("Begin Certificate Stack:\n");
for(unsigned i=0; i<len; i++) {
X509 *cert = sk_X509_value(sk, i);
print_certificate(cert);
}
printf("End Certificate Stack\n");
}

Check whether two certificate stacks are identical:

int certparse_sk_X509_cmp(STACK_OF(X509) *a, STACK_OF(X509) *b)
{
int a_len = sk_X509_num(a);
int b_len = sk_X509_num(b);
if (a_len != b_len) {
return 1;
}
for (int i=0; i < a_len; i++) {
if (X509_cmp(sk_X509_value(a, i), sk_X509_value(b, i))) {
return 1;
}
}
return 0;
}

Check whether the subject and issuer string on a certificate are identical:

int certparse_subjeqissuer(X509 *cert)
{
char *s = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
char *i = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
int rc = strcmp(s, i);
OPENSSL_free(s);
OPENSSL_free(i);
return (!rc);
}

Convert an OpenSSL error constant into a human readable string:

const char* get_validation_errstr(long e) {
switch ((int) e) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
return "ERR_UNABLE_TO_GET_ISSUER_CERT";
case X509_V_ERR_UNABLE_TO_GET_CRL:
return "ERR_UNABLE_TO_GET_CRL";
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
return "ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE";
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
return "ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE";
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
return "ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY";
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
return "ERR_CERT_SIGNATURE_FAILURE";
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
return "ERR_CRL_SIGNATURE_FAILURE";
case X509_V_ERR_CERT_NOT_YET_VALID:
return "ERR_CERT_NOT_YET_VALID";
case X509_V_ERR_CERT_HAS_EXPIRED:
return "ERR_CERT_HAS_EXPIRED";
case X509_V_ERR_CRL_NOT_YET_VALID:
return "ERR_CRL_NOT_YET_VALID";
case X509_V_ERR_CRL_HAS_EXPIRED:
return "ERR_CRL_HAS_EXPIRED";
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
return "ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD";
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
return "ERR_ERROR_IN_CERT_NOT_AFTER_FIELD";
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
return "ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD";
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
return "ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD";
case X509_V_ERR_OUT_OF_MEM:
return "ERR_OUT_OF_MEM";
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
return "ERR_DEPTH_ZERO_SELF_SIGNED_CERT";
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
return "ERR_SELF_SIGNED_CERT_IN_CHAIN";
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
return "ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY";
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
return "ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE";
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
return "ERR_CERT_CHAIN_TOO_LONG";
case X509_V_ERR_CERT_REVOKED:
return "ERR_CERT_REVOKED";
case X509_V_ERR_INVALID_CA:
return "ERR_INVALID_CA";
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
return "ERR_PATH_LENGTH_EXCEEDED";
case X509_V_ERR_INVALID_PURPOSE:
return "ERR_INVALID_PURPOSE";
case X509_V_ERR_CERT_UNTRUSTED:
return "ERR_CERT_UNTRUSTED";
case X509_V_ERR_CERT_REJECTED:
return "ERR_CERT_REJECTED";
case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
return "ERR_SUBJECT_ISSUER_MISMATCH";
case X509_V_ERR_AKID_SKID_MISMATCH:
return "ERR_AKID_SKID_MISMATCH";
case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
return "ERR_AKID_ISSUER_SERIAL_MISMATCH";
case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
return "ERR_KEYUSAGE_NO_CERTSIGN";
case X509_V_ERR_INVALID_EXTENSION:
return "ERR_INVALID_EXTENSION";
case X509_V_ERR_INVALID_POLICY_EXTENSION:
return "ERR_INVALID_POLICY_EXTENSION";
case X509_V_ERR_NO_EXPLICIT_POLICY:
return "ERR_NO_EXPLICIT_POLICY";
case X509_V_ERR_APPLICATION_VERIFICATION:
return "ERR_APPLICATION_VERIFICATION";
default:
return "ERR_UNKNOWN";
}
}

I hope this helps. As I stated earlier, if you find other pieces of information useful, let me know and we’ll get things updated. Similarly, if you find that any of the examples don’t work, let me know.

Thanks to Jordan Whitehead for various corrections.

orig url:

https://zakird.com/2013/10/13/certificate-parsing-with-openssl

一、生成证书
openSSL生成RSA证书
1 生成自签CA

生成CA密钥
genrsa -aes256 -passout pass:123456 -out ca_rsa_private.pem 2048
1
自签名证书
req -new -key server_rsa_private.pem -passin pass:server -out server.csr
1
2 生成服务端证书

生成服务端密钥
genrsa -aes256 -passout pass:server -out server_rsa_private.pem 2048
1
生成服务端代签名证书
req -new -key server_rsa_private.pem -passin pass:server -out server.csr
1
使用CA证书及密钥对服务器证书进行签名
x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca_rsa_private.pem -passin pass:123456 -CAcreateserial -out server.crt
1
3 生成客户端证书

生成客户端密钥
genrsa -aes256 -passout pass:client -out client_rsa_private.pem 2048
1
生成客户端代签名证书
req -new -key client_rsa_private.pem -passin pass:client -out client.csr
1
使用CA证书及密钥对客户端证书进行签名
x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca_rsa_private.pem -passin pass:123456 -CAcreateserial -out client.crt
1
openSSL生成SM2证书
1 生成自签CA
生成CA密钥
ecparam -genkey -name SM2 -out ca.key
1
自签名证书
req -new -x509 -days 3650 -key ca.key -out ca.crt
1
这里ecdsa with sha256可能需要换成sm3,不过在RFC 5349中规定为ecdsa SHA做digest,所以需要做二次开发,这次暂时用这个.
2 生成服务端证书

生成服务端密钥
ecparam -genkey -name SM2 -out server_sm2_private.pem
1
生成服务端代签名证书
req -new -key server_sm2_private.pem -out server.csr
1
使用CA证书及密钥对服务器证书进行签名
x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
1
3 生成客户端证书

生成客户端密钥
ecparam -genkey -name SM2 -out client_sm2_private.pem
1
生成客户端代签名证书
req -new -key client_sm2_private.pem -out client.csr
1
使用CA证书及密钥对客户端证书进行签名
x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
1
生成ECC证书
和SM2大同小异,SM2也是ECC改造的国密算法。

1 生成自签CA
生成CA密钥
ecparam -genkey -name prime256v1 -out ca.key
1
自签名证书
req -new -x509 -days 3650 -key ca.key -out ca.crt
1
2 生成服务端证书

生成服务端密钥
ecparam -genkey -name prime256v1 -out server_ecc_private.pem
1
生成服务端代签名证书
req -new -key server_ecc_private.pem -out server.csr
1
使用CA证书及密钥对服务器证书进行签名
x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
1
3 生成客户端证书

生成客户端密钥
ecparam -genkey -name prime256v1 -out client_ecc_private.pem
1
生成客户端代签名证书
req -new -key client_ecc_private.pem -out client.csr
1
使用CA证书及密钥对客户端证书进行签名
x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
1
证书项
C-----国家(Country Name)
ST----省份(State or Province Name)
L----城市(Locality Name)
O----公司(Organization Name)
OU----部门(Organizational Unit Name)
CN----产品名(Common Name)
emailAddress----邮箱(Email Address)

二、身份认证
Server代码:

#include "stdafx.h"#include<stdio.h>#include<stdlib.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<winsock2.h>#include<ws2tcpip.h>#include<tchar.h>#include<io.h>#include<process.h>#include<windows.h>#include<openssl/ssl.h>#include<openssl/ssl2.h>#include<openssl/ssl3.h>#include<openssl/err.h>
#pragma warning(disable:4996)
#define MAXBUF 1024
char caCertFilePath[MAX_PATH]={0};      //ca证书路径
char serverCertFilePath[MAX_PATH]={0};  //服务端证书路径
char serverPrivateFilePath[MAX_PATH]={0};    //服务端私钥路径

void ShowCerts(SSL *ssl)
{
X509
*cert;char *line;

cert
=SSL_get_peer_certificate(ssl);//SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证//如果验证不通过,那么程序抛出异常中止连接 if(SSL_get_verify_result(ssl) ==X509_V_OK){
printf(
"证书验证通过\n");
}
if (cert !=NULL) {
printf(
"数字证书信息:\n");
line
= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf(
"证书: %s\n", line);//free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf(
"颁发者: %s\n", line);//free(line); X509_free(cert);
}
elseprintf("无证书信息!\n");
}
voidTwo_Auth()
{
intsockfd, new_fd;
socklen_t len;
structsockaddr_in my_addr, their_addr;
unsigned
intmyport, lisnum;char buf[MAXBUF + 1];
SSL_CTX
*ctx;//if (argv[1])//myport = atoi(argv[1]);//else myport = 7838;//if (argv[2])//lisnum = atoi(argv[2]);//else lisnum = 1;/*SSL 库初始化*/SSL_library_init();/*载入所有 SSL 算法*/OpenSSL_add_all_algorithms();/*载入所有 SSL 错误消息*/SSL_load_error_strings();/*以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text*/ctx=SSL_CTX_new(SSLv23_server_method());/*也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准*/ if (ctx ==NULL) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
//双向验证//SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行//SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);//设置信任根证书 if (SSL_CTX_load_verify_locations(ctx, caCertFilePath,NULL)<=0){
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
/*载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥*/ /*FILE *caf=fopen("../file/server.crt","r");
char bufStr[5000]={0};
fread(bufStr,1,5000,caf);
fclose(caf);
*/ if (SSL_CTX_use_certificate_file(ctx, serverCertFilePath, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
/*载入用户私钥*/ /*memset(bufStr,0,5000);
FILE *prif=fopen("../file/server_rsa_private.pem.unsecure","r");
fread(bufStr,1,5000,prif);
fclose(prif);
*/ if (SSL_CTX_use_PrivateKey_file(ctx,serverPrivateFilePath, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(
1);
}
/*检查用户私钥是否正确*/ if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
WSADATA wsd;
int resStartup = WSAStartup(MAKEWORD(2,2),&wsd);if(0 !=resStartup)
{
printf(
"failed to WSAStartup!\n");
system(
"pause");
exit(
1);
}
/*开启一个 socket 监听*/ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror(
"socket");
system(
"pause");
exit(
1);
}
elseprintf("socket created\n");
memset(
&my_addr,0,sizeof(my_addr));//bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family =PF_INET;
my_addr.sin_port
=htons(myport);
my_addr.sin_addr.s_addr
=INADDR_ANY;if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(structsockaddr))== -1) {
perror(
"bind");
system(
"pause");
exit(
1);
}
elseprintf("binded\n");if (listen(sockfd, lisnum) == -1) {
perror(
"listen");
system(
"pause");
exit(
1);
}
elseprintf("begin listen\n");while (1) {
SSL
*ssl;
len
= sizeof(structsockaddr);/*等待客户端连上来*/ if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len))== -1) {
perror(
"accept");
system(
"pause");
exit(errno);
}
elseprintf("server: got connection from %s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port),
new_fd);
/*基于 ctx 产生一个新的 SSL*/ssl=SSL_new(ctx);/*将连接用户的 socket 加入到 SSL*/SSL_set_fd(ssl, new_fd);/*建立 SSL 连接*/ if (SSL_accept(ssl) == -1) {
perror(
"accept");
close(new_fd);
system(
"pause");break;
}
ShowCerts(ssl);
/*开始处理每个新连接上的数据收发*/ //bzero(buf, MAXBUF + 1); memset(buf,0,MAXBUF + 1);
strcpy(buf,
"server->client");/*发消息给客户端*/len=SSL_write(ssl, buf, strlen(buf));if (len <= 0) {
printf(
"消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno,
strerror(errno));
gotofinish;
}
elseprintf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);

memset(buf,
0, MAXBUF + 1);/*接收客户端的消息*/len=SSL_read(ssl, buf, MAXBUF);if (len > 0)
printf(
"接收消息成功:'%s',共%d个字节的数据\n", buf, len);elseprintf("消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
/*处理每个新连接上的数据收发结束*/finish:/*关闭 SSL 连接*/SSL_shutdown(ssl);/*释放 SSL*/SSL_free(ssl);/*关闭 socket*/ //close(new_fd); WSACleanup();
}
/*关闭监听的 socket*/close(sockfd);/*释放 CTX*/SSL_CTX_free(ctx);
WSACleanup();
}
voidOne_Auth()
{
int sockfd=0, new_fd=0;
socklen_t len
=0;structsockaddr_in my_addr, their_addr;
unsigned
intmyport, lisnum;char buf[MAXBUF + 1]={0};
SSL_CTX
*ctx;//if (argv[1])//myport = atoi(argv[1]);//else myport = 7838;//if (argv[2])//lisnum = atoi(argv[2]);//else lisnum = 1;/*SSL 库初始化*/SSL_library_init();/*载入所有 SSL 算法*/OpenSSL_add_all_algorithms();/*载入所有 SSL 错误消息*/SSL_load_error_strings();/*以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text*/ctx=SSL_CTX_new(SSLv23_server_method());/*也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准*/ if (ctx ==NULL) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
//单向验证//SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行//SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行//SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);//设置信任根证书 /*if (SSL_CTX_load_verify_locations(ctx, "../file/ca.crt",NULL)<=0){
ERR_print_errors_fp(stdout);
system("pause");
exit(1);
}
*/ /*载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥*/ /*FILE *caf=fopen("../file/server.crt","r");
char bufStr[5000]={0};
fread(bufStr,1,5000,caf);
fclose(caf);
*/ if (SSL_CTX_use_certificate_file(ctx, serverCertFilePath, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
/*载入用户私钥*/ /*memset(bufStr,0,5000);
FILE *prif=fopen("../file/server_rsa_private.pem.unsecure","r");
fread(bufStr,1,5000,prif);
fclose(prif);
*/ if (SSL_CTX_use_PrivateKey_file(ctx,serverPrivateFilePath , SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(
1);
}
/*检查用户私钥是否正确*/ if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
WSADATA wsd;
int resStartup = WSAStartup(MAKEWORD(2,2),&wsd);if(0 !=resStartup)
{
printf(
"failed to WSAStartup!\n");
system(
"pause");
exit(
1);
}
/*开启一个 socket 监听*/ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror(
"socket");
system(
"pause");
exit(
1);
}
elseprintf("socket created\n");
memset(
&my_addr,0,sizeof(my_addr));//bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family =PF_INET;
my_addr.sin_port
=htons(myport);
my_addr.sin_addr.s_addr
=INADDR_ANY;if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(structsockaddr))== -1) {
perror(
"bind");
system(
"pause");
exit(
1);
}
elseprintf("binded\n");if (listen(sockfd, lisnum) == -1) {
perror(
"listen");
system(
"pause");
exit(
1);
}
elseprintf("begin listen\n");while (1) {
SSL
*ssl;
len
= sizeof(structsockaddr);/*等待客户端连上来*/ if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len))== -1) {
perror(
"accept");
system(
"pause");
exit(errno);
}
elseprintf("server: got connection from %s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port),new_fd);
/*基于 ctx 产生一个新的 SSL*/ssl=SSL_new(ctx);/*将连接用户的 socket 加入到 SSL*/SSL_set_fd(ssl, new_fd);/*建立 SSL 连接*/ if (SSL_accept(ssl) == -1) {
perror(
"accept");
close(new_fd);
system(
"pause");break;
}
ShowCerts(ssl);
/*开始处理每个新连接上的数据收发*/ //bzero(buf, MAXBUF + 1); memset(buf,0,MAXBUF + 1);
strcpy(buf,
"server->client");/*发消息给客户端*/len=SSL_write(ssl, buf, strlen(buf));if (len <= 0) {
printf(
"消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno,
strerror(errno));
gotofinish;
}
elseprintf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);

memset(buf,
0, MAXBUF + 1);/*接收客户端的消息*/len=SSL_read(ssl, buf, MAXBUF);if (len > 0)
printf(
"接收消息成功:'%s',共%d个字节的数据\n", buf, len);elseprintf("消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
/*处理每个新连接上的数据收发结束*/finish:/*关闭 SSL 连接*/SSL_shutdown(ssl);/*释放 SSL*/SSL_free(ssl);/*关闭 socket*/ //close(new_fd); WSACleanup();
}
/*关闭监听的 socket*/close(sockfd);/*释放 CTX*/SSL_CTX_free(ctx);
WSACleanup();
}
int _tmain(int argc, _TCHAR*argv[]) {int alogType=-1;
printf(
"服务端---请选择算法:\n");
printf(
"1:RSA 2:SM2\n");
scanf(
"%d",&alogType);if (alogType==1)
{
char *rsaCaCertFile="../file/ca.crt";char *rsaServerCertFile="../file/server.crt";char *rsaServerPrivateFile="../file/server_rsa_private.pem.unsecure";
strcpy(caCertFilePath,rsaCaCertFile);
strcpy(serverCertFilePath,rsaServerCertFile);
strcpy(serverPrivateFilePath,rsaServerPrivateFile);
}
else if (alogType==2)
{
char *sm2CaCertFile="../SM2_Cert/ca.crt";char *sm2ServerCertFile="../SM2_Cert/server.crt";char *sm2ServerPrivateFile="../SM2_Cert/server_sm2_private.pem";
strcpy(caCertFilePath,sm2CaCertFile);
strcpy(serverCertFilePath,sm2ServerCertFile);
strcpy(serverPrivateFilePath,sm2ServerPrivateFile);
}
int type=-1;
printf(
"服务端----请选择认证方式:\n");
printf(
"1:单向认证 2:双向认证\n");
scanf(
"%d",&type);switch(type)
{
case 1: One_Auth();break;case 2: Two_Auth();break;default:break;
}
system(
"pause");return 0;
}

Client代码:

#include "stdafx.h"#include<stdio.h>#include<stdlib.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<winsock2.h>#include<ws2tcpip.h>#include<winsock.h>#include<io.h>#include<process.h>#include<windows.h>#include<openssl/err.h>#include<openssl/ssl.h>
#pragma warning(disable:4996)
#define MAXBUF 1024
char caCertFilePath[MAX_PATH]={0};      //ca证书路径
char clientCertFilePath[MAX_PATH]={0};  //服务端证书路径
char clientPrivateFilePath[MAX_PATH]={0};    //服务端私钥路径

void ShowCerts(SSL *ssl)
{
X509
*cert;char *line;

cert
=SSL_get_peer_certificate(ssl);//SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证//如果验证不通过,那么程序抛出异常中止连接 if(SSL_get_verify_result(ssl) ==X509_V_OK){
printf(
"证书验证通过\n");
}
if (cert !=NULL) {
printf(
"数字证书信息:\n");
line
= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf(
"证书: %s\n", line);//free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf(
"颁发者: %s\n", line);//free(line); X509_free(cert);
}
elseprintf("无证书信息!\n");
}
voidAuth_Two()
{
intsockfd, len;structsockaddr_in dest;char buffer[MAXBUF + 1];
SSL_CTX
*ctx;
SSL
*ssl;
unsigned
intmyport;char *myip="127.0.0.1";
myport
=7838;/*if (argc != 5) {
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个"
"IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
argv[0], argv[0]);
system("pause");
exit(0);
}
*/ /*SSL 库初始化,参看 ssl-server.c 代码*/SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx
=SSL_CTX_new(SSLv23_client_method());if (ctx ==NULL) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
//双向验证//SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行//SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);//设置信任根证书 if (SSL_CTX_load_verify_locations(ctx, caCertFilePath,NULL)<=0){
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
/*载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥*/ /*FILE *clientf=fopen("../file/client.crt","r");
char bufStr[5000]={0};
fread(bufStr,1,5000,clientf);
fclose(clientf);
*/ if (SSL_CTX_use_certificate_file(ctx, clientCertFilePath, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
/*载入用户私钥*/ /*FILE *clientRsaf=fopen("../file/client_rsa_private.pem.unsecure","r");
memset(bufStr,0,5000);
fread(bufStr,1,5000,clientRsaf);
fclose(clientRsaf);
*/ if (SSL_CTX_use_PrivateKey_file(ctx, clientPrivateFilePath, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
/*检查用户私钥是否正确*/ if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
WSADATA wsd;
int resStartup = WSAStartup(MAKEWORD(2,2),&wsd);if(0 !=resStartup)
{
printf(
"failed to WSAStartup!\n");
system(
"pause");
exit(
1);
}
/*创建一个 socket 用于 tcp 通信*/ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror(
"Socket");
system(
"pause");
exit(errno);
}
printf(
"socket created\n");/*初始化服务器端(对方)的地址和端口信息*/ //bzero(&dest, sizeof(dest)); memset(&dest,0,sizeof(dest));
dest.sin_family
=AF_INET;
dest.sin_port
=htons(myport);

unsigned
long l1=0;
l1
=inet_addr(myip);
in_addr addr1;
memcpy(
&addr1, &l1, 4);if (inet_ntoa(addr1) == 0) {
perror(myip);
system(
"pause");
exit(errno);
}
dest.sin_addr
=addr1;
printf(
"address created\n");/*连接服务器*/ if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror(
"Connect");
system(
"pause");
exit(errno);
}
printf(
"server connected\n");/*基于 ctx 产生一个新的 SSL*/ssl=SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
/*建立 SSL 连接*/ if (SSL_connect(ssl) == -1)
ERR_print_errors_fp(stderr);
else{
printf(
"Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
/*接收对方发过来的消息,最多接收 MAXBUF 个字节*/memset(buffer,0, MAXBUF + 1);/*接收服务器来的消息*/len=SSL_read(ssl, buffer, MAXBUF);if (len > 0)
printf(
"接收消息成功:'%s',共%d个字节的数据\n",
buffer, len);
else{
printf
(
"消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
gotofinish;
}
memset(buffer,
0, MAXBUF + 1);
strcpy(buffer,
"from client->server");/*发消息给服务器*/len=SSL_write(ssl, buffer, strlen(buffer));if (len < 0)
printf
(
"消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
buffer, errno, strerror(errno));
elseprintf("消息'%s'发送成功,共发送了%d个字节!\n",
buffer, len);

finish:
/*关闭连接*/SSL_shutdown(ssl);
SSL_free(ssl);
//close(sockfd); SSL_CTX_free(ctx);
WSACleanup();
}
voidAuth_One()
{
intsockfd, len;structsockaddr_in dest;char buffer[MAXBUF + 1];
SSL_CTX
*ctx;
SSL
*ssl;
unsigned
intmyport;char *myip="127.0.0.1";
myport
=7838;/*if (argc != 5) {
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个"
"IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
argv[0], argv[0]);
system("pause");
exit(0);
}
*/ /*SSL 库初始化,参看 ssl-server.c 代码*/SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx
=SSL_CTX_new(SSLv23_method());if (ctx ==NULL) {
ERR_print_errors_fp(stdout);
system(
"pause");
exit(
1);
}
//单向验证 /*// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行
// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
// 设置信任根证书
if (SSL_CTX_load_verify_locations(ctx,rsaCaCertFile,NULL)<=0){
ERR_print_errors_fp(stdout);
system("pause");
exit(1);
}

/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 * /
if (SSL_CTX_use_certificate_file(ctx, rsaServerCertFile, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
system("pause");
exit(1);
}
/* 载入用户私钥 * /
if (SSL_CTX_use_PrivateKey_file(ctx, clientPrivateFilePath, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
system("pause");
exit(1);
}
/* 检查用户私钥是否正确 * /
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
system("pause");
exit(1);
}
*/WSADATA wsd;int resStartup = WSAStartup(MAKEWORD(2,2),&wsd);if(0 !=resStartup)
{
printf(
"failed to WSAStartup!\n");
system(
"pause");
exit(
1);
}
/*创建一个 socket 用于 tcp 通信*/ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror(
"Socket");
system(
"pause");
exit(errno);
}
printf(
"socket created\n");/*初始化服务器端(对方)的地址和端口信息*/ //bzero(&dest, sizeof(dest)); memset(&dest,0,sizeof(dest));
dest.sin_family
=AF_INET;
dest.sin_port
=htons(myport);

unsigned
long l1=0;
l1
=inet_addr(myip);
in_addr addr1;
memcpy(
&addr1, &l1, 4);if (inet_ntoa(addr1) == 0) {
perror(myip);
system(
"pause");
exit(errno);
}
dest.sin_addr
=addr1;
printf(
"address created\n");/*连接服务器*/ if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror(
"Connect");
system(
"pause");
exit(errno);
}
printf(
"server connected\n");/*基于 ctx 产生一个新的 SSL*/ssl=SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
/*建立 SSL 连接*/ if (SSL_connect(ssl) == -1)
ERR_print_errors_fp(stderr);
else{
printf(
"Connected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
/*接收对方发过来的消息,最多接收 MAXBUF 个字节*/memset(buffer,0, MAXBUF + 1);/*接收服务器来的消息*/len=SSL_read(ssl, buffer, MAXBUF);if (len > 0)
printf(
"接收消息成功:'%s',共%d个字节的数据\n",
buffer, len);
else{
printf
(
"消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
gotofinish;
}
memset(buffer,
0, MAXBUF + 1);
strcpy(buffer,
"from client->server");/*发消息给服务器*/len=SSL_write(ssl, buffer, strlen(buffer));if (len < 0)
printf
(
"消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
buffer, errno, strerror(errno));
elseprintf("消息'%s'发送成功,共发送了%d个字节!\n",
buffer, len);

finish:
/*关闭连接*/SSL_shutdown(ssl);
SSL_free(ssl);
//close(sockfd); SSL_CTX_free(ctx);
WSACleanup();
}
int _tmain(int argc, _TCHAR*argv[])
{
int alogType=-1;
printf(
"客户端---请选择算法:\n");
printf(
"1:RSA 2:SM2\n");
scanf(
"%d",&alogType);if (alogType==1)
{
char *rsaCaCertFile="../file/ca.crt";char *rsaClientCertFile="../file/client.crt";char *rsaClientPrivateFile="../file/client_rsa_private.pem.unsecure";
strcpy(caCertFilePath,rsaCaCertFile);
strcpy(clientCertFilePath,rsaClientCertFile);
strcpy(clientPrivateFilePath,rsaClientPrivateFile);
}
else if (alogType==2)
{
char *sm2CaCertFile="../SM2_Cert/ca.crt";char *sm2ClientCertFile="../SM2_Cert/client.crt";char *sm2ClientPrivateFile="../SM2_Cert/client_sm2_private.pem";
strcpy(caCertFilePath,sm2CaCertFile);
strcpy(clientCertFilePath,sm2ClientCertFile);
strcpy(clientPrivateFilePath,sm2ClientPrivateFile);
}
int type=-1;
printf(
"客户端----请选择认证方式:\n");
printf(
"1:单向认证 2:双向认证\n");
scanf(
"%d",&type);switch(type)
{
case 1: Auth_One();break;case 2: Auth_Two();break;default:break;
}
system(
"pause");return 0;
}

 

以上代码RSA和ECC都测试通过了,但是SM2测试时候报错了。

45444:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:ssl\record\rec_layer_s3.c:1528:SSL alert number 40
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 311 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
error in s_client

以上是用命令行进行身份认证时报的错误信息,用上述的代码测试同样会报此类错误信息,在握手的时候直接崩溃

45444:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:ssl\record\rec_layer_s3.c:1528:SSL alert number 40

  

应该是openssl1.1.1和1.1.1a都是这样的错误,通过抓包可以看到客户端和服务端在握手的时候使用TLS版本不一致在这里插入图片描述

  

 

 

一个是TLS1.2一个是TLS1.3,个人感觉是因为不能识别证书,导致降低了TLS版本,从而不能握手成功,不知道这个是不是openssl1.1.1没有完善的地方,后续如果解决了会及时更新。

提供一个完善的代码和文档下载链接,方便大家研究openssl身份认证

下载链接:https://download.csdn.net/download/xuebing1995/10947453

---------------------
原文链接:https://blog.csdn.net/xuebing1995/article/details/86742078