证书生成
通用命令
查看摘要算法列表
openssl list -digest-algorithms
openssl crl -in crl.pem -text 查看信息
openssl x509 -in cert.pem -text
1、sm2证书生成
生成私钥
openssl ecparam -genkey -name SM2 -out private_key.pem
(RSA 公钥的字节结构可以使用标准的SubjectPublicKeyInfo(SPKI)格式来表示。这个格式由一系列的 DER 编码的 ASN.1 结构组成)
用私钥生成公钥
openssl ec -in private_key.pem -pubout -out sm2PubKey.pem
生成证书
1)生成配置文件cert.conf
[ req ]
default_bits = 256
prompt = no
default_md = sm3
distinguished_name = dn
[ dn ]
C = CN
ST = Beijing
L = Beijing
O = Your Organization
OU = Your Unit
CN = Your Common Name
default_bits:指定生成的密钥长度,默认为 256 位。在 SM2 中,通常使用 256 位曲线。
prompt:设置是否显示提示信息。在此示例中,设置为 no,即不显示提示信息。
default_md:指定摘要算法类型,默认为 SM3。SM3 是中国国家标准的密码杂凑算法,用于计算数字签名和生成证书请求。
distinguished_name:指定证书主题的字段。
C:国家名(Country),两个字母的国家代码。例如,CN 表示中国。
ST:省/自治区/直辖市名(State/Province/Region)。
L:地区/城市名(Locality/City)。
O:组织名(Organization)。
OU:组织单位名(Organizational Unit)。
CN:通用名称(Common Name),即证书的主题。
2)用私钥和配置文件生成证书
openssl req -new -key private_key.pem -out cert_req.csr -config cert.conf
不使用配置文件的时候
openssl req -x509 -key private_key.pem -out certificate.pem -days 365 -config <(echo "[ req ]\ndefault_bits = 256\nprompt = no\ndefault_md = SM3\ndistinguished_name = dn\n[ dn ]\nC = CN\nST = SC\nL = Chengdu\nO = zdxlz\nOU = zdxlz\nCN = Test")
易错点
140345702581568:error:100C508A:elliptic curve routines:pkey_ec_ctrl:invalid digest type:../crypto/ec/ec_pmeth.c:331
openssl 1.1.1版本后支持国密算法(去掉default_md = sm3)
2、CRL生成
介绍
包含撤销的证书列表的签名数据结构,类似于信用卡的黑名单,公布某些数字证书无效。
CRL可以分为完全CRL和增量CRL。在完全CRL中包含了所有的被撤销证书信息,增量CRL由一系列的CRL来表明被撤销的证书信息,它每次发布的CRL是对前面发布CRL的增量扩充。
基本的CRL信息有:被撤销证书序列号、撤销时间、撤销原因、签名者以及CRL签名等信息。
基于CRL的验证是一种不严格的证书认证。CRL能证明在CRL中被撤销的证书是无效的。但是,它不能给出不在CRL中的证书的状态。如果执行严格的认证,需要采用在线方式进行认证,即OCSP认证。
结构体
typedef struct X509_revoked_st//一个被撤销证书的信息
{
ASN1_INTEGER *serialNumber;//被撤销证书的序列号;
ASN1_TIME *revocationDate;//撤销时间
STACK_OF(X509_EXTENSION) *extensions;//扩展项,可选
int sequence;//顺序号,用于排序,表示当前被撤销证书信息在crl中的顺序
} X509_REVOKED;
//主体信息
typedef struct X509_crl_info_st
{
ASN1_INTEGER *version;//crl版本
X509_ALGOR *sig_alg;//crl签名法
X509_NAME *issuer;//签发者信息
ASN1_TIME *lastUpdate;//上次更新时间
ASN1_TIME *nextUpdate;//下次更新时间
STACK_OF(X509_REVOKED) *revoked;//被撤销证书信息
STACK_OF(X509_EXTENSION) *extensions;//
ASN1_ENCODING enc;//
} X509_CRL_INFO;
// 完整crl数据结构
struct X509_crl_st
{
X509_CRL_INFO *crl;//crl信息主体
X509_ALGOR *sig_alg;//签名算法,与X509_CRL_INFO中的一致
ASN1_BIT_STRING *signature;//签名值
int references;//引用
} ;
函数
int X509_CRL_add0_revoked(X509_CRL *crl, X509_REVOKED *rev)//添加一个被撤销证书的信息
int X509_CRL_print(BIO *bp,X509_CRL *x)//打印crl内容到BIO中
int X509_CRL_print_fp(FILE *fp, X509_CRL *x)//将crl的内容输出到fp中,此函数调用了X509_CRL_print
int X509_CRL_set_issuer_name(X509_CRL *x, X509_NAME *name)//设置crl的颁发者
int X509_CRL_set_lastUpdate(X509_CRL *x, ASN1_TIME *tm)//设置crl上次发布时间
int X509_CRL_set_nextUpdate(X509_CRL *x, ASN1_TIME *tm)//设置crl下次发布时间
int X509_CRL_set_version(X509_CRL *x, long version)//设置crl版本
int X509_CRL_sign(X509_CRL *x, EVP_PKEY *pkey, const EVP_MD *md)//对crl进行签名,pkey为私钥,md为摘要算法,结果存放在x-> signature中
int X509_CRL_sort(X509_CRL *c)//根据证书序列号对crl排序,此函数实现采用了堆栈排序,堆栈的比较函数为X509_REVOKED_cmp(crypto/asn1/x_crl.c)
//添加CRL扩展,nid为要添加的扩展标识,value为被添加的具体扩展项的内部数据结构地址,crit表明是否为关键扩展,flags表明何种操作。此函数调用X509V3_add1_i2d函数
int X509_CRL_add1_ext_i2d(X509_CRL *x, int nid, void *value, int crit, unsigned long flags)
int X509_CRL_add_ext(X509_CRL *x, X509_EXTENSION *ex, int loc)//添加扩展项到指定堆栈位置,此函数调用X509v3_add_ext,进行堆栈插入操作。
int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b)//CRL比较,此函数调用X509_NAME_cmp,只比较颁发者的名字是否相同。
X509_EXTENSION *X509_CRL_delete_ext(X509_CRL *x, int loc)//删除CRL扩展项堆栈中的某一项,loc指定被删除项在堆栈中的位置
int X509_CRL_digest(const X509_CRL *data, const EVP_MD *type,unsigned char *md, unsigned int *len)//CRL摘要,本函数对X509_CRL进行摘要,type指定摘要算法,摘要结果存放在md中,len表明摘要结果长度
X509_CRL *X509_CRL_dup(X509_CRL *crl);
void *X509_CRL_get_ext_d2i(X509_CRL *x, int nid, int *crit, int *idx)//CRL中的获取扩展项,此函数用于获取crl中指定扩展项的内部数据结构,返回值为具体的扩展项数据结构地址,nid为扩展项标识,它调用了X509V3_get_d2i函数。
int X509_CRL_get_ext_by_critical(X509_CRL *x, int crit, int lastpos)
int X509_CRL_get_ext_by_NID(X509_CRL *x, int nid, int lastpos)
int X509_CRL_get_ext_by_OBJ(X509_CRL *x, ASN1_OBJECT *obj, int lastpos)
int X509_CRL_get_ext_count(X509_CRL *x)//获取crl中扩展项的个数
int X509_CRL_verify(X509_CRL *a, EVP_PKEY *r)//验证CRL。EVP_PKEY结构r中需要给出公钥(与签名相对应的)验证内容:签名,签发者证书,验证crl完整性
long X509_CRL_get_version(const X509_CRL *crl);//获取版本号
X509_NAME *X509_CRL_get_issuer(const X509_CRL *crl);//获取颁发者名称
char *X509_NAME_oneline(const X509_NAME *a, char *buf, int size);//将 X.509 证书的主题或颁发者(发行者)的名称转换为可打印的字符串形式
ASN1_TIME *X509_CRL_get_lastUpdate(X509_CRL *crl)
ASN1_TIME *X509_CRL_get_nextUpdate(X509_CRL *crl)
STACK_OF(X509_REVOKED) *X509_CRL_get_REVOKED(X509_CRL *crl);//用于获取撤销证书列表,该函数返回一个 STACK_OF(X509_REVOKED) 结构的指针,即撤销证书的列表。STACK_OF(X509_REVOKED) 是 OpenSSL 提供的一种堆栈数据结构,用于存储多个 X509_REVOKED(被撤销的证书)对象
static inline int sk_X509_REVOKED_num(const stack_st_X509_REVOKED *sk)//获取 STACK_OF(X509_REVOKED) 结构中元素数量的函数
static inline x509_revoked_st *sk_X509_REVOKED_value(const stack_st_X509_REVOKED *sk, int idx)//获取指定索引位置的元素的函数,返回撤销证书列表中指定索引位置的被撤销证书对象
const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *x);// 获取被撤销证书的序列号
unsigned char *ASN1_STRING_data(ASN1_STRING *x)//获取 ASN1_STRING 结构中数据的指针
int ASN1_STRING_length(const ASN1_STRING *x);//用于获取 ASN1_STRING 结构中数据的长度
ASN1_INTEGER *X509_get_serialNumber(X509 *x);//获取证书序列号
1、配置文件crl.conf
[ca]
default_ca = CA_default CA 的默认设置
[CA_default]
dir = /path/to/CA/folder
crl_dir = $dir/crl 指定存放 CRL 文件的文件夹路径
database = $dir/index.txt 指定用于存储证书签发和撤销信息的数据库文件(索引文件)路径。该文件记录了已颁发的证书和被撤销的证书的相关信息。
serial = $dir/serial 指定一个用于跟踪证书序列号的文件路径。每当颁发一个新证书时,该文件中的序列号将递增。
private_key = /path/to/ca.key 指定 CA 的私钥文件路径。CRL 是由 CA 使用其私钥签名的
certificate = /path/to/ca.crt 指定 CA 的证书文件路径。该证书将包含在生成的 CRL 中。
crlnumber = $dir/crlnumber 指定存储 CRL 号码的文件路径。每次生成 CRL 时,该号码将递增。
default_crl_days = 30 指定生成的 CRL 的默认有效期(以天为单位)。在生成 CRL 时可以覆盖此值.
[crl_ext] CRL 扩展字段的部分
issuerAltName=issuer:copy 指定 CRL 的颁发者别名,以及将从 CA 证书中拷贝的颁发者信息。
authorityKeyIdentifier=keyid:always 声明 CRL 中的颁发者密钥标识符(AKI)字段始终包含在 CRL 中
[req]
default_bits = 2048
prompt = no 控制是否在生成证书请求时提示用户输入信息。将其设置为 "no" 可以禁用提示,使用默认值填充
default_md = sha256 指定证书签名所使用的消息摘要算法,默认为 SHA256
distinguished_name = dn 定义证书主题(Subject)的字段信息
[dn]
CN = Your CA Name
部分参数可忽略
openssl ca -config crl.conf -gencrl -out crl.pem
测试程序
#include "x509.h"
#include "pem.h"
#include "ossl_typ.h"
#include "../third-Part/openssl/include/crypto/x509.h"
#include "../third-Part/openssl/include/crypto/asn1.h"
#include "Debug.h"
#include "parseCRL.h"
#include "parseCertTest.h"
void parseCRL(char *fileStr, X509 *cert)
{
FILE *crlFile = fopen(fileStr, "r");
if(crlFile == NULL){
debugPrintf("crlFile fopen err \n");
return;
}
X509_CRL *crl = NULL;
//PEM 文件的指针, 指向 X509_CRL 结构体指针的指针(读取到才有), 指向密码回调函数的指针,用户数据指针
crl = PEM_read_X509_CRL(crlFile, &crl, NULL, NULL);
if(crl == NULL){
debugPrintf("PEM_read_X509_CRL err \n");
return;
}
//DER格式读取
// BIO *crl_bio = BIO_new_file(fileStr, "r");
// crl = d2i_X509_CRL_bio(crl_bio, NULL);
// BIO_free(crl_bio);
//获取版本号
long version = X509_CRL_get_version(crl);
debugPrintf("version %ld\n", version);
X509_NAME *issuer_name = X509_CRL_get_issuer(crl);
char *issuer_str = X509_NAME_oneline(issuer_name, NULL, 0);
debugPrintf("Issuer: %s\n", issuer_str);
X509_ALGOR *sig_alg = &crl->sig_alg;
//将一个对象的数字标识符(NID)转换为对应的短名(SN)
const char *alg_name = OBJ_nid2sn(sig_alg->algorithm->nid);
debugPrintf("Signature Algorithm: %s\n", alg_name);
// 获取上次更新时间和下次更新时间
ASN1_TIME *last_update = X509_CRL_get_lastUpdate(crl);
ASN1_TIME *next_update = X509_CRL_get_nextUpdate(crl);
printASN1Time(last_update);
printASN1Time(next_update);
// 获取撤销列表,栈
STACK_OF(X509_REVOKED) *revoked_list = X509_CRL_get_REVOKED(crl);
int num_revoked = 0;
if (revoked_list != NULL) {
//获取revoked_list中的数量
num_revoked = sk_X509_REVOKED_num(revoked_list);
// 遍历撤销的证书列表
}
X509_REVOKED *revoked_cert;
ASN1_INTEGER *serial_number;
for (int i = 0; i < num_revoked; i++) {
//获取指定索引位置的元素的函数,返回撤销证书列表中指定索引位置的被撤销证书对象
revoked_cert = sk_X509_REVOKED_value(revoked_list, i);
// 获取被撤销证书的序列号
serial_number = X509_REVOKED_get0_serialNumber(revoked_cert);
// 打印序列号,获取 ASN1_STRING 结构中数据的指针
char *hex_serial = (char *)ASN1_STRING_data(serial_number);
int hex_serial_len = ASN1_STRING_length(serial_number);
for (int j = 0; j < hex_serial_len; j++) {
printf("%02X", hex_serial[j]);
}
printf("\n");
}
//获取证书序列号,验证吊销列表
int isRevoked = 0;
ASN1_INTEGER *cert_serial = X509_get_serialNumber(cert);
for (int i = 0; i < num_revoked; ++i) {
revoked_cert = sk_X509_REVOKED_value(revoked_list, i);
serial_number = X509_REVOKED_get0_serialNumber(revoked_cert);
// 比较证书的序列号
if (ASN1_INTEGER_cmp(cert_serial, serial_number) == 0) {
isRevoked = 1;
break;
}
}
debugPrintf("check serialNumber \n");
}
说明
结合证书校验代码,测试用该证书生成的crl(如果获取的吊销列表为空,注意crl配置文件里面的证书吊销的序列号是否有)。
DER格式和PEM格式略有不同
static void _getASN1Time(ASN1_TIME* Time, char *rcvTime)
{
//BIO是一种用于进行输入/输出操作的抽象类型。它提供了一种统一的接口,可用于在不同的数据源和目标之间进行读写操作。
BIO* bio = BIO_new(BIO_s_mem());
if (bio == NULL) {
return;
}
if (ASN1_TIME_print(bio, Time) <= 0) {
BIO_free(bio);
return;
}
char buf[1024];
//从bio对象中读取结果
int len = BIO_read(bio, buf, sizeof(buf) - 1);
if (len <= 0) {
BIO_free(bio);
return;
}
buf[len] = '\0';
memcpy(rcvTime, buf, len);
BIO_free(bio);
}
/**
* 从字节流或字节数组读取证书结构存储X509
* @param buffer 证书数据
* @param size 大小
* @param crl 返回的证书
* @return 0成功
*/
int crlInit(unsigned char* buffer, int size, X509_CRL **crl)
{
TRACE_INFO("crlInit");
TRACE_HEX_DUMP("buffer", buffer, size);
//der格式
X509_CRL* _crl = d2i_X509_CRL(NULL, &buffer, size);
if (_crl != NULL)
{
TRACE_INFO("d2i_X509_CRL err");
} else{
BIO *bio = BIO_new_mem_buf(buffer, -1);
//pem格式
_crl = PEM_read_bio_X509_CRL(bio, NULL,NULL,NULL);
if(_crl ==NULL){
TRACE_INFO("read crl err");
return -1;
}
}
*crl = _crl;//值传递
return 0;
}
/**
* 获取基本crl基本信息
* @param crl 输入crl
* @param x509_CRL_INFO 返回的结构体信息
* @return
*/
int crlGetInfo(X509_CRL *crl, _X509_CRL_INFO *x509_CRL_INFO)
{
if(x509_CRL_INFO == NULL)
{
TRACE_INFO("x509_CRL_INFO NULL");
return -1;
}
x509_CRL_INFO->version = X509_CRL_get_version(crl) + 1;
if(X509_CRL_get_issuer(crl) != NULL){
X509_NAME *issuer_name = X509_CRL_get_issuer(crl);
char *issuer_str = X509_NAME_oneline(issuer_name, NULL, 0);
x509_CRL_INFO->issuer_name_len = (int)strlen(issuer_str);
x509_CRL_INFO->issuer_name = issuer_str;
}
X509_ALGOR *sig_alg = &crl->sig_alg;
//将一个对象的数字标识符(NID)转换为对应的短名(SN)
const char *alg_name = OBJ_nid2sn(sig_alg->algorithm->nid);
memcpy(x509_CRL_INFO->alg_name, alg_name, strlen(alg_name));
ASN1_TIME *thisUpdate = X509_CRL_get_lastUpdate(crl);
ASN1_TIME *nextUpdate = X509_CRL_get_nextUpdate(crl);
_getASN1Time(thisUpdate,x509_CRL_INFO->thisUpdate);
_getASN1Time(nextUpdate,x509_CRL_INFO->nextUpdate);
if(crl->signature.data != NULL)
{
x509_CRL_INFO->signLen = crl->signature.length;
x509_CRL_INFO->signDate = crl->signature.data;
}
if(crl->crl_number != NULL)
{
x509_CRL_INFO->crlNumber_len = crl->crl_number->length;
x509_CRL_INFO->crlNumber = crl->crl_number->data;
}
if(crl->crl_number != NULL)
{
x509_CRL_INFO->base_crl_number_len = crl->base_crl_number->length;
x509_CRL_INFO->base_crl_number = crl->base_crl_number->data;
}
return 0;
}
/**
*
* @param crl 输入 x509 crl
* @param cert_serial 输入需要校验证书的序列号
* @return 1 证书合法不在吊销列表,0校验失败,证书不合法
*/
int crlCheckRevokedList(X509_CRL *crl, ASN1_INTEGER *cert_serial)
{
STACK_OF(X509_REVOKED) *revoked_list = X509_CRL_get_REVOKED(crl);
int num_revoked = 0;
if (revoked_list != NULL){
num_revoked = sk_X509_REVOKED_num(revoked_list);
} else{
TRACE_INFO("X509_CRL_get_REVOKED NULL");
return 1;
}
X509_REVOKED *revoked_cert;
ASN1_INTEGER *serial_number;
for (int i = 0; i < num_revoked; i++) {
//获取指定索引位置的元素的函数,返回撤销证书列表中指定索引位置的被撤销证书对象
revoked_cert = sk_X509_REVOKED_value(revoked_list, i);
// 获取被撤销证书的序列号
serial_number = X509_REVOKED_get0_serialNumber(revoked_cert);
char *hex_serial = (char *)ASN1_STRING_data(serial_number);
int hex_serial_len = ASN1_STRING_length(serial_number);
TRACE_HEX_DUMP("hex_serial", hex_serial, hex_serial_len);
//对比序列号是否是吊销列表
if (ASN1_INTEGER_cmp(cert_serial, serial_number) == 0) {
return 0;//查出吊销列表
}
}
return 1;
}