目录
1 数字证书简介
1.1 什么是数字证书
要开车得先考驾照,驾照上面记有本人的照片、姓名、出生日期等个人信息,以及有效期、准驾车辆的类型等信息,并由公安局在上面盖章。我们只要看到驾照,就可以知道公安局认定此人具有驾驶车辆的资格。
公钥证书(Public-Key Certificate,PKC)其实和驾照很相似,里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority、Certifying Authority,CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人。公钥证书也简称为证书(certificate)。
1.2 为什么需要数字证书
用数字签名既可以识别出篡改和伪装,还可以防止否认。也就是说,我们同时实现了确认消息的完整性、进行认证以及否认防止。现代社会中的计算机通信从这一技术中获益匪浅。
然而,要正确使用数字签名,有一个大前提,那就是用于验证签名的公钥必须属于真正的发送者。即便数字签名算法再强大,如果你得到的公钥是伪造的,那么数字签名也会完全失效。
现在我们发现自己陷入了一个死循环一一数字签名是用来识别消息篡改、伪装以及否认的,但是为此我们又必须从没有被伪装的发送者得到没有被篡改的公钥才行。
为了能够确认自己得到的公钥是否合法,我们需要使用证书。所谓证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。
1.3 数字证书的格式
目前使用的数字证书格式遵循X.509标准;X.509定义了公钥证书结构的基本标准;X.509证书格式当前的版本是X.509v3。
2 OpenSSL代码实现
2.1 生成X.509证书
/***************************************************
功 能:生成X.509证书
参 数:pemFileName - [out] 生成证书文件路径(*.cer)
pubKey - [in] 申请证书单位的公钥(PKCS#8-PEM编码)
pubLen - [in] 申请证书单位的公钥长度
priKey - [in] 签名证书机构的私钥(PKCS#8-PEM编码)
priLen - [in] 签名证书机构的私钥长度
cerId - [in] 证书id
validFrom - [in] 证书有效期开始时间(注意:是格林威治标准时间,与我们东八区差8个小时)
年月日时分秒Z 如20240714151314Z
validTo - [in] 证书有效期截止时间: 年月日时分秒Z 如20250714151314Z
issuerCN - [in] 颁发者 CN(Common Name)通用名称 如:MIAXIS MOSIP CA
issuerOU - [in] 颁发者 OU(Organizational Unit name)机构单元名称 如:www.miaxis.com
issuerO - [in] 颁发者 O(Organization name)机构名 如:MIAXIS
issuerL - [in] 颁发者 L(Locality)地理位置 如:ZHANGZHOU
issuerST - [in] 颁发者 S(State or province name)州/省名 如:ZHEJIANG
issuerC - [in] 颁发者 C(Country)国名 如:CN
subjectCN - [in] 主题者 CN(Common Name)通用名称 如:MIAXIS MOSIP CA
subjectOU - [in] 主题者 OU(Organizational Unit name)机构单元名称 如:www.miaxis.com
subjectO - [in] 主题者 O(Organization name)机构名 如:MIAXIS
subjectL - [in] 主题者 L(Locality)地理位置 如:ZHANGZHOU
subjectST - [in] 主题者 S(State or province name)州/省名 如:ZHEJIANG
subjectC - [in] 主题者 C(Country)国名 如:CN
返 回:0 - 成功,其他 - 失败
备 注:如何判断该证书是否是自签证书, issuer和subject一致
***************************************************/
int WINAPI opensslGenerateX509(char* pemFileName,
unsigned char *pubKey, int pubLen,
unsigned char *priKey, int priLen,
unsigned long long cerId,
char *validFrom,
char *validTo,
char* issuerCN, char* issuerOU, char* issuerO, char* issuerL, char* issuerST, char* issuerC,
char* subjectCN, char* subjectOU, char* subjectO, char* subjectL, char* subjectST, char* subjectC)
{
//导入PEM编码的RSA公钥(PKCS#8格式)
RSA *rsaPubCtx = PemPublicKeyToRsa((unsigned char*)pubKey, pubLen);
if (rsaPubCtx == NULL)
{
return ERR_RSA_PUBKEY;
}
//导入PEM编码的RSA私钥(PKCS#8格式)
RSA *rsaSignCtx = PemPrivateKeyToRsa((unsigned char*)priKey, priLen);
if (rsaSignCtx == NULL)
{
return ERR_RSA_PRIKEY;
}
X509* x509 = NULL;
x509 = X509_new();
//设置版本好
X509_set_version(x509, 2);
//设置公钥
EVP_PKEY* pPubKey = NULL;
pPubKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(pPubKey, rsaPubCtx);
X509_set_pubkey(x509, pPubKey);
EVP_PKEY_free(pPubKey);
pPubKey = NULL;
//设置序列号
ASN1_INTEGER* asnInteger = NULL;
asnInteger = ASN1_INTEGER_new();
ASN1_INTEGER_set(asnInteger, cerId);
X509_set_serialNumber(x509, asnInteger); /* from openssl source,ASN1_INTEGER asnInteger copy,should free asnInteger after */
ASN1_INTEGER_free(asnInteger);
//设置Validity 有效期 :开始时间
ASN1_TIME* ascTime = NULL;
ascTime = ASN1_TIME_new();
//ASN1_TIME_set_string(ascTime, "20220604184500Z");
ASN1_TIME_set_string(ascTime, validFrom);
X509_set_notBefore(x509, ascTime);
ASN1_TIME_free(ascTime);
//设置Validity 有效期 :截止时间
ascTime = ASN1_TIME_new();
//ASN1_TIME_set_string(ascTime, "20220605164508Z");
ASN1_TIME_set_string(ascTime, validTo);
X509_set_notAfter(x509, ascTime);
ASN1_TIME_free(ascTime);
//设置issuer
X509_NAME* x509Name = NULL;
x509Name = X509_get_issuer_name(x509);
X509_NAME_add_entry_by_txt(x509Name, "CN", MBSTRING_ASC, (unsigned char*)issuerCN, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "OU", MBSTRING_ASC, (unsigned char*)issuerOU, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "O", MBSTRING_ASC, (unsigned char*)issuerO, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "L", MBSTRING_ASC, (unsigned char*)issuerL, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "ST", MBSTRING_ASC, (unsigned char*)issuerST, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "C", MBSTRING_ASC, (unsigned char*)issuerC, -1, -1, 0);
//设置subject
x509Name = X509_get_subject_name(x509);
X509_NAME_add_entry_by_txt(x509Name, "CN", MBSTRING_ASC, (unsigned char*)subjectCN, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "OU", MBSTRING_ASC, (unsigned char*)subjectOU, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "O", MBSTRING_ASC, (unsigned char*)subjectO, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "L", MBSTRING_ASC, (unsigned char*)subjectL, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "ST", MBSTRING_ASC, (unsigned char*)subjectST, -1, -1, 0);
X509_NAME_add_entry_by_txt(x509Name, "C", MBSTRING_ASC, (unsigned char*)subjectC, -1, -1, 0);
//设置签名秘钥,并签名
EVP_PKEY* pSignKey = NULL;
pSignKey = EVP_PKEY_new();
EVP_PKEY_set1_RSA(pSignKey, rsaSignCtx);
X509_sign(x509, pSignKey, EVP_sha256());
//保存生成证书
FILE* pfile = NULL;
pfile = fopen(pemFileName, "w");
PEM_write_X509(pfile, x509);
fclose(pfile);
//释放内存
if (x509 != NULL) {
X509_free(x509);
x509 = NULL;
}
RSA_free(rsaPubCtx);
RSA_free(rsaSignCtx);
return 0;
}
2.2 解析X.509证书
2.2.1 X.509中提取公钥
/****************************************************************************
功 能:从X.509证书中提取PEM编码的公钥(PKCS#8格式)
参 数:cert - 输入,x509证书
certLen - 输入,x509证书长度
pPemPubKey - 输出,PEM编码的公钥(PKCS#8格式)
PemKeyLen - 输出,PEM编码的公钥长度
返 回:0 - 成功,其他 - 失败
*****************************************************************************/
int WINAPI opensslParseX509PubKey(unsigned char* cert, int certLen,
unsigned char *pPemPubKey, int *PemKeyLen)
{
BIO* certBio = BIO_new(BIO_s_mem());
BIO_write(certBio, cert, certLen);
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL);
if (!certX509) {
return ERR_X509;
}
int nRet = ERR_OK;
int pubkey_algonid = OBJ_obj2nid(certX509->cert_info->key->algor->algorithm);
if (pubkey_algonid == NID_undef) {
BIO_free(certBio);
X509_free(certX509);
return ERR_PUBKEY_ALGONID;
}
if (pubkey_algonid == NID_rsaEncryption || pubkey_algonid == NID_dsa) {
EVP_PKEY *pkey = X509_get_pubkey(certX509);
if (pkey == NULL)
{
BIO_free(certBio);
X509_free(certX509);
return ERR_X509_PUBKEY;
}
RSA *rsa_key;
switch (pubkey_algonid) {
case NID_rsaEncryption:
rsa_key = pkey->pkey.rsa;
if (rsa_key == NULL)
{
nRet = ERR_X509_RSAKEY;
}
else
{
RsaToPemPublicKey(rsa_key, pPemPubKey, PemKeyLen);
}
break;
default:
nRet = ERR_UNKNOWN;
break;
}
EVP_PKEY_free(pkey);
}
BIO_free(certBio);
X509_free(certX509);
return nRet;
}
2.2.2 X.509中提取指纹
/****************************************************************************
功 能:从X.509证书中SHA1指纹(HEX编码)
参 数:cert - 输入,x509证书
certLen - 输入,x509证书长度
thumbprint - 输出,SHA1指纹(HEX编码) :40字节
返 回:0 - 成功,其他 - 失败
*****************************************************************************/
int WINAPI opensslParseX509PemSha1thumbprint(unsigned char* cert, int certLen, char *sha1thumbprint)
{
BIO* certBio = BIO_new(BIO_s_mem());
BIO_write(certBio, cert, certLen);
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL);
if (!certX509) {
return ERR_X509;
}
#define SHA1LEN 20
char buf[SHA1LEN];
const EVP_MD *digest = EVP_sha1();
unsigned len;
int rc = X509_digest(certX509, digest, (unsigned char*)buf, &len);
if (rc == 0 || len != SHA1LEN) {
return ERR_X509_DIGEST;
}
BIO_free(certBio);
X509_free(certX509);
hex_encode((unsigned char*)buf, sha1thumbprint, SHA1LEN);
return 0;
}
/****************************************************************************
功 能:从X.509证书中SHA256指纹(HEX编码)
参 数:cert - 输入,x509证书
certLen - 输入,x509证书长度
thumbprint - 输出,SHA256指纹(HEX编码) :64字节
返 回:0 - 成功,其他 - 失败
*****************************************************************************/
int WINAPI opensslParseX509PemSha256thumbprint(unsigned char* cert, int certLen, char *sha256thumbprint)
{
BIO* certBio = BIO_new(BIO_s_mem());
BIO_write(certBio, cert, certLen);
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL);
if (!certX509) {
return ERR_X509;
}
#define SHA256LEN 32
char buf[SHA256LEN];
const EVP_MD *digest = EVP_sha256();
unsigned len;
int rc = X509_digest(certX509, digest, (unsigned char*)buf, &len);
if (rc == 0 || len != SHA256LEN) {
return ERR_X509_DIGEST;
}
BIO_free(certBio);
X509_free(certX509);
hex_encode((unsigned char*)buf, sha256thumbprint, SHA256LEN);
return 0;
}
3 演示Demo
3.1 开发环境
-
OpenSSL 1.0.2l
-
Visual Studio 2015
-
Windows 10 Pro x64
3.2 功能介绍
演示程序主界面如下图所示,包括生成子签名X.509,X.509提取公钥和X.509提取指纹等功能。