- 注:本文档基于opensll v1.1.1
- 不支持sm2withsm3 证书
- 使用gmssl v3.0之前的版本支持sm2withsm3 证书, 在下面openssl制作证书的时候加上 -sm3后缀即可
目录
1.主体密钥标识(subjectKeyIdentifier)扩展项
1.openssl 官方文档
2.openssl 自签证书
1、根证书:
(1)获取曲线参数
openssl ecparam -list_curves
(2)生成SM2的密钥:(两条分别是两种生成方式,取其中一种测试即可,下面也是一样)
openssl ecparam -genkey -name SM2 -text -out Root.key 或者(Root.pem)
openssl ecparam -genkey -name SM2 -text -out caKey.pem
(3)生成证书请求文件:
涉及到填写申请信息,Country=CN,Common Name要独特不重复,其他随意填写就可以
openssl req -key Root.key -new -out Root.req
openssl req -key caKey.pem -new -out ca.csr
(4)自签名生成根证书:
openssl x509 -req -days 3650 -in Root.req -signkey Root.key -out RootCA.crt (不要加 -sm3)
openssl x509 -req -days 365 -in ca.csr -signkey caKey.pem -out ca-cert.pem
(5)查看证书:
openssl x509 -in RootCA.crt -text -noout
2、用户证书:
(1)生成私钥:
openssl ecparam -genkey -name SM2 -text -out Server.key
openssl ecparam -genkey -name SM2 -text -out serverKey.pem
(2)证书请求:
openssl req -new -key Server.key -out Server.req
openssl req -new -key serverKey.pem -out server.csr
(3)签发证书:
openssl x509 -req -days 3650 -CA RootCA.crt -CAkey Root.key -CAcreateserial -in Server.req -out ServerCA.crt
openssl x509 -req -days 365 -CA ca-cert.pem -CAkey caKey.pem -CAcreateserial-in server.csr -out server-cert.pem -signkey serverKey.pem
(4)证书验证:
openssl verify -CAfile RootCA.crt ServerCA.crt
(5)查看证书:
openssl x509 -in ServerCA.crt -text -noout
3.对X.509v3证书扩展项的解释:
参考:【精选】密码学专题 openssl的基本概念_openssl extendedkeyusage_MY CUP OF TEA的博客-CSDN博客
针对下面制作证书链要用到的扩展项做出解析,更详细的请参考上面的链接
1.主体密钥标识(subjectKeyIdentifier)扩展项
- 主体密钥标识用于在证书主体拥有多个密钥集的时候指定密钥属于哪个密钥集。subjectKeyIdentifier参数值目前有两种,一种是以十六进制字符串的方式直接给定主体密钥标识,这种方式现在基本上不使用了;另外一种是给定值hash,这样,就会根据PKIX的规定生成合适的主体密钥标识,如下面的例子:
subjectKeyIdentifier = hash
2.验证机构密钥标识(authorityKeyIdentifier)扩展项
- 验证机构密钥标识用于构造证书链的时候标识签发机构的证书和密钥,证书中的验证机构密钥标识包括三个部分:密钥ID(keyID)、验证机构DN和CA证书序列号。
- KeyID在验证机构使用多对密钥的时候能起到区别的作用。在OpenSSL中,authorityKeyIdentifier扩展项的参数值域有两个:keyid和issure,两个参数都可选的取值为“always”。下面是一些应用形式:
authorityKeyIdentifier = keyid,issure:always
authorityKeyIdentifier = keyid:always
- 如果给定keyid,那么CA签发证书的时候就会复制CA证书的主体密钥标识到新签发的证书中,如果keyid取值always,则复制失败的时候就拒绝签发证书。
- 给定issure将告诉CA签发证书的时候复制CA证书的DN和序列号到新签发证书中,一般来说,虽然给定了issure值,但是只有keyid出显但复制主体密钥失败或者keyid没有给定的情况下才会执行CA证书的DN和序列号的复制操作。
- 如果isure给定了always值,则不论在什么请求下都执行其定义操作。
3.基本限制(basicConstraints)扩展项
- basicConstraints是PKIX定义的证书扩展项,在OpenSSL配置文件中,它是一个多值型扩展项,它的值域包括两部分:CA参数和pathlen参数。
- CA参数的值为TURE或者FALSE,如果CA参数值为TRUE,指明该证书是一个CA证书,即可以用来签发别的证书的证书;
- 如果CA的值为FALSE,那么表示证书是一个最终用户证书,不能作为CA证书。下面的配置表明是一个CA证书:
basicConstraints = CA:TRUE
- pathlen参数值是整数,只对CA证书有效,也就是说,只有CA值为TRUE的时候才有效。它指明了从该CA开始下面还可以出现多少级CA证书。所以,如果你设定一个CA 证 书 的 pathlen 为 0, 设置如下 : basicConstraints = CA:TRUE,pathlen = 0 那么表明该CA证书只能签发最终用户证书而不能签发更低级别的CA证书。
- PKIX规定该扩展项必须标记为关键选项,当然,你理论上也可以不这样做。有效的CA证书一般应该标记为CA值为TRUE,而最终用户证书CA值一定不能设置为TRUE,否则就很容易导致混乱。
- 下面的例子是一个标记该扩展项为关键扩展项的最终用户配置例子:
basicConstraints = critical,CA:FALSE
4.密钥用途(keyUsage)扩展项
- keyUsage是PKIX定义的用于限制证书中密钥用途的扩展项
- 事实上,它跟nsCertType的功能基本上是相同的,都是为了限制证书用途。
- keyUsage可选值包括:digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgrement,keyCertSign,cRLSign,encipherOnly和decipherOnly。
- keyUsage的值可以为上述值的一个或者多个,值域中各个参数的意义详见表
- 例如,如果要指定证书中的密钥只能用来进行加密操作,并且标记该扩展项为关键扩展项,则设置应该如下:
keyUsage = critical,keyEncipherment,encipherOnly
4.自签名CA证书链
自签名CA证书链涉及到CA签发的证书,CA为第三方官方机构,为了自测,需要添加相应的环境;
1、CA根证书
(1)制作root.ext文件,内容填写如下:
[v3_ca]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
(2)制作根秘钥
openssl ecparam -genkey -name SM2 -text -out Root.key
(3)制作根秘钥申请文件
openssl req -key Root.key -new -out Root.req
涉及到填写申请信息,Country=CN,Common Name要独特不重复,其他随意填写就可以
(4)制作根CA证书
openssl x509 -req -days 3650 -extfile root.ext -extensions v3_ca -in Root.req -signkey Root.key -out RootCA.crt
查看Root.crt:
openssl x509 -text -noout -in RootCA.crt
有CA:TRUE即为CA签发证书成功
2、中间证书
(1)制作ca_intermediate.ext 文件,内容填写如下
[v3_intermediate_ca]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
(2)制作中间证书秘钥及申请文件
openssl ecparam -genkey -name SM2 -text -out middle.key openssl req -new -key middle.key -out middle.req
(3)根证书签发中间证书
openssl x509 -req -days 3650 -extfile ca_intermediate.ext -extensions v3_intermediate_ca -CA RootCA.crt -CAkey Root.key -CAcreateserial -in middle.req -out MiddleCA.crt
查看中间证书:
openssl x509 -text -noout -in MiddleCA.crt
有CA:TRUE 为成功,pathlen:0保证在中间CA下面不能有其他证书颁发机构
(4)根证书验证中间证书
openssl verify -CAfile RootCA.crt MiddleCA.crt
3、客户端证书
(1)制作 server.ext,内容如下
[ v3_server ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
(2)制作端证书秘钥及申请文件
openssl ecparam -genkey -name SM2 -text -out Client.key openssl req -new -key Client.key -out Client.req
(3)中间证书签发端证书
openssl x509 -req -days 3650 -extfile server.ext -extensions v3_server -CA MiddleCA.crt -CAkey middle.key -CAcreateserial -in Client.req -out ClientCA.crt
(4)查看端证书
openssl x509 -text -noout -in ClientCA.crt
4、测试
(1)中间证书验证端证书
openssl verify -partial_chain -trusted MiddleCA.crt ClientCA.crt
(2)合为证书链
cat MiddleCA.crt RootCA.crt > middle-chain.crt
(3)证书链验证端证书
openssl verify -CAfile middle-chain.crt ClientCA.crt
5、涉及openssl 1.1.1源码修改
sm2 签名相关,openssl源码修改;原因:由于 SM2 签名算法需要与 SM3 杂凑算法配合使用,但是该版本还不支持sm2签名中的sm3相关,所以要添加对应的判断,但是这个添加只是增加了代码不报错,不是真正的解决办法,要想sm2签名,可以切换到gmssl,gmss v2.5.4经测试是可以使用的。
修改openssl 源码, 在openssl/crypto/ec/ec_pmeth.c 中添加
6.代码
1、获取密钥对
/**
* @fn gmReadKey
* @brief 从文件中读取秘钥。
* @param[int] key_file : 公钥或私钥或证书文件名
* @param[int] type : 读取类型 0.公钥 1.私钥
* @return
* @retval
* @retval 秘钥
*/
static EVP_PKEY* gmOpensslReadKey(const char* key_file, const int type)
{
BIO* bio = NULL;
EVP_PKEY* key = NULL;
BIO *priio = NULL;
X509* cert = NULL;
do{
if (0 == type)
{
if((strstr(key_file, ".csr")) ||
(strstr(key_file, ".crt")) ||
(strstr(key_file, ".cert")))
{
if ((priio = BIO_new_file(key_file, "rb")) == NULL) {
printf("failed to open public cert: %s\n", key_file);
break;
}
// 解析证书
if ((cert = PEM_read_bio_X509(priio, NULL, NULL, NULL)) == NULL) {
printf("failed to get x509 public cert: %s\n", key_file);
break;
}
// 提取证书的公钥
key = X509_get_pubkey(cert);
}
else
{
if(!(bio = BIO_new_file(key_file, "r"))){
printf("BIO_new_file erro!\n");
break;
}
if(!(key = EVP_PKEY_new())){
printf("EVP_PKEY_new erro!\n");
break;
}
key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
}
}
else
{
if(!(bio = BIO_new_file(key_file, "r"))){
printf("BIO_new_file erro!\n");
break;
}
if(!(key = EVP_PKEY_new())){
printf("EVP_PKEY_new erro!\n");
break;
}
key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
}
}while(0);
if(bio != NULL)
BIO_free(bio);
return key;
}
2、释放密钥对
void gmFreeKey()
{
if(pgPubKey)
{
EVP_PKEY_free(pgPubKey);
pgPubKey = NULL;
}
if(pgPriKey)
{
EVP_PKEY_free(pgPriKey);
pgPriKey = NULL;
}
}
3、私钥签名
static int gmSdSign(EVP_PKEY* pkey, const unsigned char* message, const size_t message_len, unsigned char* sig, size_t* sig_len)
{
int ret = 1;
EVP_MD_CTX* ctx = NULL;
char *p = NULL;
unsigned long err;
char buff[1024] = {0};
do{
if(!(ctx = EVP_MD_CTX_new())){
sipPrintfl(SIP_FATAL, "EVP_MD_CTX_new erro!\n");
ret = -1;
break;
}
EVP_MD_CTX_init(ctx);
if((ret = EVP_DigestSignInit(ctx, NULL, EVP_sm3(), NULL, pkey)) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_DigestVerifyInit erro! ret:%d %s\n", ret, p);
break;
}
EVP_DigestSignUpdate(ctx, message, message_len);
if((ret = EVP_DigestSignFinal(ctx, NULL, sig_len)) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_DigestSignFinal erro! ret:%d %s\n", ret, p);
break;
}
sig = (unsigned char*)realloc(sig, *sig_len);
if((ret = EVP_DigestSignFinal(ctx, sig, sig_len)) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_DigestSignFinal erro! ret:%d %s\n", ret, p);
break;
}
}while(0);
return ret;
}
5、公钥验签
static int gmDsVerify(EVP_PKEY* pkey, const unsigned char* message, const size_t message_len, unsigned char* sig, size_t sig_len)
{
int ret = 1;
EVP_MD_CTX* ctx_verify = NULL;
char *p = NULL;
unsigned long err;
char buff[1024] = {0};
do{
if(!(ctx_verify = EVP_MD_CTX_new())){
sipPrintfl(SIP_FATAL, "EVP_MD_CTX_new erro!\n");
ret = -1;
break;
}
EVP_MD_CTX_init(ctx_verify);
if((ret = EVP_DigestVerifyInit(ctx_verify, NULL, EVP_sm3(), NULL, pkey)) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_DigestVerifyInit erro! ret:%d %s\n", ret, p);
break;
}
EVP_DigestVerifyUpdate(ctx_verify, message, message_len);
if((ret = EVP_DigestVerifyFinal(ctx_verify, sig, sig_len)) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_PKEY_set_alias_type erro! ret:%d %s\n", ret, p);
break;
}
}while(0);
if(ctx_verify)
EVP_MD_CTX_free(ctx_verify);
return ret;
}
6、加密解密
(1)公钥加密
static size_t gmDoEncrypt(EVP_PKEY* key, const unsigned char* in, size_t inlen, unsigned char* out)
{
size_t outLen = 0;
EVP_PKEY_CTX* ctx = NULL;
unsigned char *pDst = NULL;
do{
if(EVP_PKEY_set_alias_type(key, EVP_PKEY_SM2) != 1){
sipPrintfl(SIP_FATAL, "EVP_PKEY_set_alias_type erro!\n");
outLen = -1;
break;
}
if(!(ctx = EVP_PKEY_CTX_new(key, NULL))){
sipPrintfl(SIP_FATAL, "EVP_PKEY_CTX_new erro!\n");
break;
}
if(EVP_PKEY_encrypt_init(ctx) != 1){
sipPrintfl(SIP_FATAL, "EVP_PKEY_encrypt_init erro!\n");
break;
}
if ((EVP_PKEY_encrypt(ctx, NULL, &outLen, (const unsigned char*)in, inlen)) != 1){
sipPrintfl(SIP_FATAL, "EVP_PKEY_decrypt erro!\n");
break;
}
if (!(pDst = (unsigned char*)malloc(outLen))){
sipPrintfl(SIP_FATAL, "malloc pDst erro!\n");
break;
}
memset(pDst, 0x0, outLen);
if(EVP_PKEY_encrypt(ctx, pDst, &outLen, in, inlen) != 1){
sipPrintfl(SIP_FATAL, "EVP_PKEY_encrypt erro!\n");
break;
}
memcpy(out, pDst,outLen);
}while(0);
if(ctx != NULL)
EVP_PKEY_CTX_free(ctx);
if(pDst != NULL)
{
free(pDst);
pDst = NULL;
}
return outLen;
}
(2)私钥解密
static size_t gmDoDecrypt(EVP_PKEY* key, const unsigned char* in, size_t inlen, unsigned char* out, int dstLen)
{
size_t outLen = 0;
EVP_PKEY_CTX* ctx = NULL;
unsigned char *pDst = NULL;
char *p = NULL;
unsigned long err;
char buff[1024] = {0};
do{
if(EVP_PKEY_set_alias_type(key, EVP_PKEY_SM2) != 1){
sipPrintfl(SIP_FATAL, "EVP_PKEY_set_alias_type erro!\n");
outLen = -1;
break;
}
if(!(ctx = EVP_PKEY_CTX_new(key, NULL))){
sipPrintfl(SIP_FATAL, "EVP_PKEY_CTX_new erro!\n");
outLen = -1;
break;
}
if(EVP_PKEY_decrypt_init(ctx) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_PKEY_decrypt_init erro! %s\n", p);
outLen = -1;
break;
}
if ((EVP_PKEY_decrypt(ctx, NULL, &outLen, (const unsigned char*)in, inlen)) != 1){
sipPrintfl(SIP_FATAL, "EVP_PKEY_decrypt erro!\n");
break;
}
if (!(pDst = (unsigned char*)malloc(outLen))){
sipPrintfl(SIP_FATAL, "malloc pDst erro!\n");
outLen = -1;
break;
}
memset(pDst, 0x0, outLen);
if((EVP_PKEY_decrypt(ctx, pDst, &outLen, in, inlen)) != 1){
err=ERR_get_error();
p = ERR_error_string(err, buff);
sipPrintfl(SIP_FATAL, "EVP_PKEY_decrypt erro! %s\n", p);
outLen = -1;
break;
}
if(outLen > dstLen){
sipPrintfl(SIP_FATAL, "out buff small !!\n");
outLen = -1;
break;
}
memcpy(out, pDst,outLen);
}while(0);
if(ctx != NULL)
EVP_PKEY_CTX_free(ctx);
if(pDst != NULL)
{
free(pDst);
pDst = NULL;
}
return outLen;
}
7、Base64编解码
(1)编码
/**
* @fn gmBase64Encode
* @brief base64编码
* @param[in] pSrc : 待编码数据
* @param[in] srcLen : 待编码数据长度
* @param[in] Dst : Base64编码数据
* @param[in] pDstLen : Base64编码数据长度
* @return
* @retval
* @retval 0.成功 other.失败
*/
int gmBase64Encode(unsigned char *pSrc, int srcLen, char *Dst, unsigned int *pDstLen)
{
int ret = 0;
if(pSrc==NULL)
return -1;
ret = EVP_EncodeBlock((unsigned char *)Dst, pSrc, srcLen);
if(ret <= 0)
{
printf("Base64 encode fial!\n");
ret = -1;
goto EXIT;
}
*pDstLen = ret;
ret = 0;
EXIT:
return ret;
}
(2)解码
/**
* @fn gmBase64Decode
* @brief base64解码
* @param[in] pSrc : 待解码数据
* @param[in] srcLen : 待解码数据长度
* @param[in] Dst : 解码数据
* @param[in] pDstLen : 解码数据长度
* @return
* @retval
* @retval 0.成功 other.失败
*/
int gmBase64Decode(char *pSrc, int srcLen, unsigned char *Dst, unsigned int *pDstLen)
{
int ret = 0;
int signLenTmp = srcLen;
int signBase64Diff = 0;
int decodedLen = 0;
char *p = NULL;
unsigned char *pBase64Dec = NULL;
if(pSrc==NULL)
return -1;
decodedLen = 3 * ((srcLen + 3) / 4);
pBase64Dec = malloc(decodedLen);
memset(pBase64Dec, 0x0, decodedLen);
p = pSrc;
p = p + strlen((char *)pSrc);
// EVP_DecodeBlock 结果可能比原始数据要长,需要去掉等字节数的尾部“=”
do{
if(*(p-signLenTmp) == '=')
signBase64Diff++;
}while(signLenTmp--);
ret = EVP_DecodeBlock(pBase64Dec, (unsigned char *)pSrc, srcLen);
if(ret<=0)
{
printf("Base64 decode fial!\n");
ret = -1;
goto EXIT;
}
*pDstLen = ret - signBase64Diff;
memcpy((char *)Dst, pBase64Dec, *pDstLen);
ret = 0;
EXIT:
if(pBase64Dec)
{
free(pBase64Dec);
pBase64Dec = NULL;
}
return ret;
}