安全相关:https之CA证书

前沿

最近在研究http,https协议;学习https协议当然绕不过SSL/TLS, CA相关知识,本文试图解析CA证书相关知识点,包括生成CA证书,CA机构增信,https加载CA证书,不安全链接由来等

目标

使用openssl生成一个CA根证书,用此根证书颁发子证书server和client

前置条件

  • ubuntu 14.04

  • sudo apt install openssl

  • sudo apt install openjdk-7-jre-headless(可能使用 keytool工具)

不同类型文件介绍

  • .pem: 文件一般是文本格式的,可以放证书或者私钥,或者两者都有;如果仅仅包含私钥,则建议使用.key作为扩展名

  • .csr: 证书签发申请文件

  • .cer: csr文件经CA认证机构签发后的证书,单个X509证书文件,不含私钥,可以是二进制和Base64格式

  • .pfx/p12: PKCS#12格式的证书文件,可以包含一个或者多个X509证书,含有私钥,一般有密码保护

生成证书实验过程

设置实验相关目录

root@SZX1000348502:/etc/ssl/demoCA# pwd
/etc/ssl/demoCA
root@SZX1000348502:/etc/ssl/demoCA# ls -al
total 36
drwxr-xr-x 6 root root 4096 12月 14 16:37 .
drwxr-xr-x 5 root root 4096 12月 14 10:20 ..
-rw-r--r-- 1 root root   17 12月 14 11:33 ca.srl  
drwxr-xr-x 2 root root 4096 12月 14 11:39 certs     # 存放已颁发的证书
drwxr-xr-x 2 root root 4096 12月 14 10:22 crl       # 存放已吊销的证书
-rw-r--r-- 1 root root    0 12月 14 10:22 index.txt # OpenSSL定义的已签发证书的文本数据库文件
drwxr-xr-x 2 root root 4096 12月 14 10:21 newcerts  # 存放CA指令生成的新证书
drwxr-xr-x 2 root root 4096 12月 14 11:30 private   # 存放私钥
-rw-r--r-- 1 root root    1 12月 14 10:22 serial

生成随机数

生成证书前,需要先生成一个随机数

openssl rand -out private/.rand 1000

生成根证书

通过本段,相当于自己搭建了一个CA认证机构,可以给人签发证书(相当于给别人的证书增信),但当然自己搭建的CA认证机构本身没有信用,当然就谈不上增信的作用

生成根证书私钥(pem文件)

OpenSSL通常使用PEM(Privacy Enbanced Mail)格式来保存私钥

openssl genrsa -aes256 -out private/cakey.pem 1024
  • genrsa: 使用RSA算法生成私钥

  • -aes256: 使用256位密钥的AES算法对私钥进行加密

  • -out: 输入私钥文件路径

生成根证书签发文件(csr文件)

openssl req -new -key private/cakey.pem -out private/ca.csr -subj \
"/C=CN/ST=GuangZhou/L=ShenZhen/O=HuaWei/OU=IT/CN=IT"
  • req: 执行证书签发命令

  • new: 新证书签发请求

  • key: 指定私钥路径

  • out: 输出的csr文件的路径

  • subj: 证书相关的用户信息(subject的缩写)

自签发根证书(cer文件)

一般的csr文件生成后,可以将其发送给CA认证机构进行签发;现在我们是CA认证机构,当然是自签发

openssl x509 -req -days 365 -sha1 -extensions v3_ca -signkey \
private/cakey.pem -in private/ca.csr -out certs/ca.cer
  • x509: 生成x509格式证书

  • req: 输入csr文件

  • days: 证书的有效期(天)

  • sha1: 证书摘要采用sha1算法

  • extensions: 按照openssl.cnf文件中配置的v3_ca项添加扩展

  • signkey: 签发证书的私钥

  • in: 要输入的csr文件

  • out: 输出的cer证书文件

  • -extensions v3_ca: v3_ca为openssl.cnf文件中的段名,该段默认指定basicConstraints的值为CA:TRUE,表示该证书是颁发给CA机构的证书

用根证书签发server端证书

用自己搭建的CA认证机构给server端证书认证,server端的证书可以在别的设备上生成,发给CA认证机构认证后,再还给server端

生成服务端私钥

openssl genrsa -aes256 -out private/server-key.pem 1024

生成证书请求文件

openssl req -new -key private/server-key.pem -out private/server.csr -subj \
"/C=CN/ST=GuangDong/L=ShenZhen/O=HuaWei/OU=BMC/CN=*.hmm.com"
  • 注意CN为自己网站的域名!

使用根证书签发server端证书

对要认证的csr文件进行认证,认证后生成x509证书文件(.cer),不含私钥

openssl x509 -req -days 365 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/cakey.pem \
-CAserial ca.srl -CAcreateserial -in private/server.csr -out certs/server.cer
  • CA: 指定CA证书的路径

  • CAkey: 指定CA证书的私钥路径

  • CAserial: 指定证书序列号文件的路径

  • CAcreateserial: 表示创建证书序列号文件(即上方提到的serial文件),创建的序列号文件默认名称为-CA,指定的证书名称后加上.srl后缀

  • 这里指定的-extensions的值为v3_req

合并出服务端证书

对经过认证的cer文件,可以合并入服务端私钥;生成的pfx文件可以给web服务器加载后,生效为https证书

openssl pkcs12 -export -clcerts -name myserver -inkey \
private/server-key.pem -in certs/server.cer -out certs/server.pfx
  • pkcs12: 用来处理pkcs#12格式的证书

  • export: 执行的是导出操作

  • clcerts: 导出的是客户端证书,-cacerts则表示导出的是ca证书

  • name: 导出的证书别名

  • inkey: 要并入的私钥路径

  • in: 要导出的证书的路径

  • out: 输出的密钥库文件的路径

解构证书实验过程

对于嵌入式设备,如果要导入一个额外的证书,作为嵌入式设备的网站证书,则需要对证书pfx文件以及证书pfx密码;可以对pfx文件进行解构

解出秘钥证书文件(.pem)

该文件即含私钥也含有证书,对比pfx仅仅去除了加密

openssl pkcs12 -in certs/server.pfx -nodes -outs private/server_1.cer -password ****

校验pem秘钥的强度是否满足要求

/* 对证书进行强度校验
 * 1. 从pem文件中提取秘钥对算法、长度,并进行校验
 * 2. 校验本地common name是否和pem文件一致
*/
static VOS_UINT32 verify_public_certicate_content(VOS_UINT32 chassisid, const VOS_CHAR * certfile)
{
    VOS_UINT32 ret = VOS_OK;
	BIO      *certbio = NULL;
	X509     *cert = NULL;
	X509_NAME    *certsubject = NULL;
	char cert_subject_cn[256] = {0};
	char dev_subject_cn[CERT_COMMON_NAME_MAXLEN] = {0};
	VOS_UINT32 flag_err = VOS_OK;

    if (!certfile)
    {
        return VOS_ERR;
    }
	/* 解码p12文件 */
	certbio = BIO_new(BIO_s_file());  
	ret = BIO_read_filename(certbio, certfile);	
	if (ret <= 0)
	{
		Printf("Error loading cert %s ret= %d.\n", certfile, ret);
		return VOS_ERR;
	}
	
	cert = PEM_read_bio_X509(certbio, NULL, 0, NULL);
	if (NULL == cert)
	{
		Printf("Error loading cert %s\n", certfile);
		BIO_free_all(certbio);	
		return VOS_ERR;
	}
	/* 从x509中获得秘钥加密算法及长度,并进行强度校验 */
	ret = verify_certicate_algorithm(cert, &flag_err);	
	if (VOS_ERR == ret)
	{
	    Printf("Error verify certicate algorithm.\n");
		X509_free(cert);
		BIO_free_all(certbio);	
		return ret;
	}
	/* 从x509中获得证书使用者,即common name */
	certsubject = X509_get_subject_name(cert);
	if (NULL == certsubject)
	{	
	    Printf("Error getting subject name from certificate.\n");
		X509_free(cert);
		BIO_free_all(certbio);	
		return VOS_ERR;
	}
	/* 获取的common name是X509_NAME类型,转成ASCII码 */
	(void)X509_NAME_get_text_by_NID(certsubject, NID_commonName, cert_subject_cn, 256);
	

	Printf("dev cn: %s\n", dev_subject_cn);
	
	/* 根据具体需求,对证书中的common name进行校验 */
	
	if (flag_err)
	{		
		ret = ERR_CERT + flag_err;
	}

	X509_free(cert);
	BIO_free_all(certbio);	
 
    return ret;
}

static VOS_UINT32 verify_certicate_algorithm(X509 *cert, VOS_UINT32 *alg_err)
{
    VOS_UINT32 ret = VOS_OK;
	EVP_PKEY *pkey = NULL;
	VOS_UINT32 key_bits = 0;
	VOS_INT sig_nid = 0;
	VOS_INT md_nid=0; 
	VOS_UINT32 alg_flag = 0;
   
	if ((NULL == cert) || (NULL == alg_err))
	{
		return VOS_ERR;
	}
	/* 从x509中获取证书相关信息,包含
	 * 证书加密算法及算法加密长度 
	 * 签名算法
	 */
	pkey = X509_get_pubkey(cert);	
	if (NULL == pkey)
	{
	    Printf("Error getting public key from certificate\n");	
		return VOS_ERR;
	}
	
	key_bits = EVP_PKEY_bits(pkey);	/* 证书算法加密长度 */
	/* 目前证书算法最常见的是RSA、ECC(SM2), 其它算法本设备不支持 */
	switch (pkey->type)
	{
		case EVP_PKEY_RSA:
			Printf("%d bit RSA Key\n", key_bits);
			alg_flag |= (key_bits < CERT_RSA_KEY_MIN_LEN)?CERT_RSA_KEY_LEN_MASK:VOS_OK;
		break;	
		case EVP_PKEY_EC:
			Printf("%d bit ECC Key\n", key_bits);
			alg_flag |= (key_bits < CERT_ECC_KEY_MIN_LEN)?CERT_ECC_KEY_LEN_MASK:VOS_OK;
		break;
		default:
			Printf("%d bit non-RSA/ECC Key\n", EVP_PKEY_bits(pkey));
			alg_flag |= CERT_PUBLIC_KEY_ALG_MASK;
		break;
	} 	

	EVP_PKEY_free(pkey);	
	
	sig_nid = OBJ_obj2nid(cert->sig_alg->algorithm);/* 获取证书签名算法 */
	Printf("Signature Algorithm: %s Length: %d Bytes\n", OBJ_nid2ln(sig_nid), cert->signature->length);

	ret = OBJ_find_sigid_algs(OBJ_obj2nid(cert->sig_alg->algorithm), &md_nid, NULL);
	if (!ret)
	{
	    Printf("Error getting digest algorithm from certificate\n");	
		return VOS_ERR;
	}

	switch (md_nid) 
	{ 			
		case NID_sha512: 
			Printf("digest algorithm sha512\n");
			break;			
		case NID_sha384: 
			Printf("digest algorithm sha384\n");
			break; 
		case NID_sha256:
			Printf("digest algorithm sha256\n");
			break; 
		case NID_sha224: 
			Printf("digest algorithm sha224\n");
			alg_flag |= CERT_SIGNATURE_ALG_MASK;
			break;
		case NID_sha1: 
			Printf("digest algorithm sha1\n");
			alg_flag |= CERT_SIGNATURE_ALG_MASK;
			break;
		default: 
			Printf("digest algorithm non-sha\n");
			alg_flag |= CERT_SIGNATURE_ALG_MASK;
			break;
	}

	*alg_err = alg_flag;
	
    return VOS_OK;
}

对pem文件进行再加密后,交给web服务器使用

校验了私钥是否符合要求后,可以对证书的有效期再进行校验下;最后是将pem文件再加密,然后使用

先生成256位随机密码

生成随机数的方法就不在累述,建议使用真随机算法;生成的随机数会作为加密pem文件的密码

使用随机密码加密pem文件

openssl pkcs12 -export -out server_1.pfx -in server_1.pem -password pass:XXXXX

server_1.pfx文件即可作为web服务器的证书文件,由openssl导入生效

进一步,可以对随机密码文件进行加密后保存

浏览器访问嵌入式设备报不安全链接

一般的不安全链接报错有以下几种

DLG_FLAGS_INVALID_CA

对于这种错误都是由于浏览器无法识别嵌入式设备的证书文件引起的

  • 在windows10双击ca.cer文件, 导入根证书文件ca.cer文件到受信任的根证书颁发机构

DLG_FLAGS_SEC_CERT_CN_INVALID

对于这种错误都是由于域名和证书中的CN(common name)字段不符合引起,比如我们申请的server端证书填的是CN=*.hmm.com,则只要域名符合这个名字即可

  • 短期解决方法:修改windows 10的host文件 C:\Windows\System32\drivers\etc,增加一行170.47.45.2 panzehua.com

  • 长期解决方法,在公司DNS服务器中增加IP和域名的映射

参考资料

openssl生成根证书CA及签发子证书