本文内容大部分是从网络上学习得到的,所以总结后回馈给网络。如果你从中发现了错误,或者有更好的方法,非常欢迎评论,或发消息给我。
之所以要探索证书链,是因为在至简网格的开发中要用到自签名证书。至简网格是一个端&云结合的小微企业开发框架,如果每部署一次都向机构申请一个证书,首先是很麻烦,其次是费用,至简网格的服务都是免费使用的,反而证书却出现收费。综上所述,将证书链摸索了一遍,实现了生成根证书,然后签名二级证书,二级证书分成服务器证书、应用签名证书,服务器证书、应用签名证书就是三级证书。
顺便打个小广告,至简网格的业务代码已经逐步开源,欢迎访问码云或CSDN查看,GitHub上也有。
目录
1. 基础
1.1. SSL
证书通常使用在SSL(Secure Sockets Layer安全套接字协议)传输协议中,用于实现端侧与服务侧的密钥交换。现在应该叫它"TLS",但是由于习惯的原因,还是常叫它"SSL"。SSL使用场景最多的是在网站上,用它实现安全的HTTP传输。
HTTP协议传输是不加密内容的,这样,内容在传播的时候,就可能被人监听。在安全性要求较高的场景中,必须加密传输,比如,登录时传输密码的场景。
HTTPS就是带加密的HTTP协议,而HTTPS的加密传输是基于SSL实现的,使得,即使在网络传输中的某个节点别人监听,也不会泄露数据。SSL是在HTTP的下层实现加解密,加解密对用户和开发者来说,都是透明的。也就是说,在加密前,你的服务器是怎样实现的,加密后,不用做任何改变。
还有一个名词就是OpenSSL。简单地说,SSL是一种规范,而OpenSSL是SSL的一个实现,提供了一堆强大的工具,强大到90%我们都用不到。理论上来说,目前的技术水平很难破解SSL,但是SSL的实现却可能有漏洞,如著名的”心脏出血“。
1.2. 什么是证书
SSL中用到证书,那么证书是什么?
证书可以理解为一个包含了签发方信息、拥有者信息、公钥、由签发方私钥产生的签名等信息的文档,当然还包括其他一些信息。校验用户证书是否可信,实际上就是检验该证书是否由合法的机构签发的。校验时,通过该证书中的结构信息找到对应机构的证书,利用机构证书中的公钥去校验用户证书中的签名是否正确。
从上述校验方法可以看出,证书是否可信,是由其签发方证书来校验的,而机构的证书是否可信,是由上一层机构的证书来校验的,如此就形成一条证书链,最顶层机构的证书的就是常说的根证书。
在网站的场景中,网站产生密钥库,记录网站的公钥、私钥及其他一些信息;然后从机构申请证书。生成的证书中包括了公钥等信息,同时包括了证书链,用来发布给网站的用户。用户访问网站时,先下载网站证书,校验证书链,然后使用其中的公钥与网站交换密钥。
1.3. 证书的种类
一种是自签证书,另一种是由机构签发的证书。
自签证书,也就是颁发者是自己,使用自己的私钥来对证书的信息进行签名。根证书就是自签证书。客户端一般预置了知名机构的根证书,并且信任由这些根证书签发的证书。
颁发证书,就是通过这些机构签发证书,可能是根级机构,也可能是二级机构,使用机构的私钥来对证书的信息进行签名。为了保护根证书,通常机构都会让二级、三级机构颁发证书。
1.4. 证书的生成
可以通过openssl或者jdk提供的keytool来生成证书、以及证书对应的密钥库。本文使用keytool举例。理解了证书生成的过程后,可以很容易对应到openssl的生成方法。
1.5. 基本概念
随便搜一下TLS证书,会出现一大堆关键字:X.509、p12、pfx、pkcs、pem、csr、cer、crt、jks、crl等,如此繁杂,能让人很快从入门到放弃。所以,先把这些关键词理一理。
1.5.1. X.509
X.509就是一个广泛应用的数字证书标准,简单来说就是定义了数字证书里面包含哪些字段、如何存储,可以参照RFC5280。PKCS7和PKCS12是X509规范中涉及的两种封装形式,包括的内容不同。PKCS7用于签名或加密,里面没有签名或加密内容;PKCS12含有私钥、公钥,有口令保护,相对较安全。
X509协议中定义的参数都是采用DER编码(Distinguished Encoding Rules,X.690)。DER编码可以理解为一种TLV(Tag Length Value)格式编码,以一个实际的证书内容为例:
Certificate:
Data:
Version: 3 (0x2) //表示为X509 v3版本证书
Serial Number: 1 (0x1) //序列号,签发时需保证同一签发方的每个证书都唯一的Serial Number
Signature Algorithm: sha256WithRSAEncryption //签名算法,先计算SHA256摘要,再使用签发方私钥进行RSA加密
Issuer: C=CN,ST=JS, L=NJ, O=Dreamer, OU=Dreamer, CN=Dreamer //签发方DN(Distingushed Name),见后续说明
Validity //证书有效期
Not Before: Jul 29 14:02:13 2018 GMT
Not After : Jul 26 14:02:13 2028 GMT
Subject: C=CN, ST=JS, O=Dreamer, OU=JM, CN=*.dreamer.com //证书拥有方DN(Distingushed Name)
Subject Public Key Info: //证书公钥信息
Public Key Algorithm: rsaEncryption
Public-Key: (1024 bit)
Modulus:
00:c5:b8:68:a2:9c:bd:11:0c:83:34:a2:97:a5:8e:
72:75:2a:bc:f4:75:fc:d0:a3:47:7d:e4:6b:4f:ed:
dd:79:7c:0f:ce:6e:e7:d2:7d:10:cd:e8:07:56:34:
58:e3:2b:2e:c9:e3:7f:ae:27:2d:f7:a3:17:6f:dd:
65:d7:f8:4f:d0:be:9c:3b:9b:ea:ed:86:d2:19:67:
81:60:53:64:c9:d1:be:17:7d:5d:7f:cc:58:1d:b6:
e1:51:0d:ba:32:ac:4d:73:a4:fc:8f:6a:79:f9:44:
25:03:b6:1c:3e:0f:e9:b8:36:b1:07:07:59:54:40:
d7:2c:52:ab:68:fe:ed:e2:6f
Exponent: 65537 (0x10001)
X509v3 extensions: //v3版本扩展信息
X509v3 Basic Constraints:
CA:FALSE //表示证书拥有方是否是CA机构,可签发证书,如为True,则还可添加一个pathLengthConstraint来显示签发链的长度
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier: //证书拥有方密钥ID,CA: TRUE时需要加入此扩展
67:30:EE:FB:39:A4:92:56:9C:1A:E8:94:10:A4:3B:EA:EC:2E:04:9E
X509v3 Authority Key Identifier: //证书签发方对应的密钥ID,用于查找签发方公钥(特别是存在多个密钥时)
DirName:/C=CN/ST=JS/L=NJ/O=Dreamer/OU=Dreamer/CN=Dreamer
serial:A0:09:E3:A9:D2:C1:86:7C
X509v3 Subject Alternative Name: //证书拥有方的别名,用于多域名证书的签发
DNS:*.example.com, DNS:*.jm.com
Signature Algorithm: sha256WithRSAEncryption //签发机构对证书的签名信息
91:db:9b:0c:9b:6e:68:24:d3:2f:3a:67:b5:c0:6c:f0:c8:4c:
f8:87:86:93:eb:fc:dc:ef:dc:7b:2e:2c:0e:7b:52:23:4d:de:
d9:69:a8:ee:ae:aa:14:04:ca:1a:03:87:fe:11:60:fe:16:8f:
87:9d:9e:d0:3a:be:33:03:f6:25:8a:10:37:f8:90:9d:67:5c:
36:a6:1e:3c:59:d9:8f:eb:22:0e:f7:3c:7d:47:10:9b:0b:03:
f0:8c:70:b0:3c:40:c6:5d:cc:6b:ba:40:ce:89:04:c7:3c:be:
af:bd:1d:94:6b:83:39:29:74:de:12:fc:63:0d:0f:39:31:3b:
48:fd
除了注释中的说明外,还需要补充以下几点:
- Subject和Issuer均为DN格式,常见格式为:C=国家,ST=省市, L=区县市, O=组织机构, OU=组织单位, CN=通用名称;
- DN是证书链查找的关键,验证时会根据Issuer的DN去匹配机构的证书,具体的匹配方法是比较DN的数量以及各DN参数值;
- DN中的CN通用名称一般为域名,如需支持子域名,例如server.com、bcd.server.com,可使用泛域名形式*.server.com;
- Subject Alternative Name扩展字段可用于多域名证书。
1.5.2. 证书编码格式(DER&PEM)
按照网上普遍说法,X.509有DER和PEM(Privacy Enhanced Mail)两种存储格式,其实这种说法有些不妥,其实这两种编码格式不在一个层面上。前面说过DER是一种TLV格式的二进制编码协议,X.509中说明证书各个字段编码均能用DER格式的编码。而PEM并没有去理解X.509的内部参数,而是在X.509进行DER编码之后,对二进制数据做了一次BASE64编码,然后加上文件头尾,如下所示:
-----BEGIN CERTIFICATE-----
MIICMTCCAZoCCQCgCeOp0sGGfDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD
TjELMAkGA1UECAwCSlMxCzAJBgNVBAcMAk5KMRAwDgYDVQQKDAdEcmVhbWVyMRAw
DgYDVQQLDAdEcmVhbWVyMRAwDgYDVQQDDAdEcmVhbWVyMB4XDTE4MDcyODE1NTEx
NFoXDTI4MDcyNTE1NTExNFowXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkpTMQsw
CQYDVQQHDAJOSjEQMA4GA1UECgwHRHJlYW1lcjEQMA4GA1UECwwHRHJlYW1lcjEQ
MA4GA1UEAwwHRHJlYW1lcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwjDr
aM8SubYIN/dqmJLCHYWBet7yQ80H3VPcbeYPja2Fq1VPb0vKMXzfd8BdaJ3roown
ZCJlfxCFPqN/z8/a0BS+ukmknOcYeYoN+vpVg9Oq3fH0iy+TRg+ydOVwmyAXJk0D
GS7WFjHv6DYRlH/xgKXuHGXwytNpQHZdDzq6bV0CAwEAATANBgkqhkiG9w0BAQsF
AAOBgQCaoAdYiWpGKcvc89ZPwL/Zd0KgSLnAln/38a69N5LqtDgWD9a6PDjHHmTF
/cN/p8hJ3LdEXfPGtFj06+KaG6OVOAo5RSqOHc5DcMs1nAImqIAuLt2rOCmsY+li
T9tweI2raih6OMTKAeIW5m46T28oPlNgeEMy2Uj2CevS6tCaLQ==
-----END CERTIFICATE-----
区分DER、PEM的方法很简单,打开有乱码的是DER格式,打开类似上面格式的就是PEM格式。通常而言,存放DER的文件扩展名为“.cer”,是二进制数据,而存放PEM的文件扩展名为“.crt”,是ASCII码的文本文件。
DER/PEM文件可以使用以下命令查看:
openssl x509 -in certificate.der -inform der -text -noout
openssl x509 -in certificate.pem -text -noout
1.5.3. 证书相关的扩展名
扩展名是比较容易误导人的地方,除了.der或.pem后缀外,还有以下常见后缀:
crt | 应该是certificate的缩写,常见于*nix系统,大多是PEM编码,也可能是DER编码 |
cer | Certificate Signing Request,常见于Windows系统,大多是DER编码,也可能是PEM编码 |
key | 用户存放密钥信息,可能是DER编码,也可能是PEM编码。可参照PKCS#1(RFC8017)查看其具体字段和定义;但密钥明文存储方式有危险,一般使用PKCS#8格式(RFC5958)加密存储,即设置一个提取密钥 |
csr | 证书签发请求,其实与证书内容相似,但不包含签发方信息,签发方根据csr并添加自身的签发信息,从而生成证书文件,详情可参照(PKCS#10 RFC2314) |
pfx/p12 | 将证书和私钥一并打包成一个文件,并且设置“提取密码”; 【注意】PKCS12不支持设置密钥库条目密码,默认与密钥库密码一致 |
jks keystore truststore | jks(Java Key Storage)常见于JAVA相关应用,实际上和PKCS12类似,将证书和私钥一并打包并设置“提取密码”。至于keystore和truststore只是概念上的区别,keystore一般表示用户或服务器证书,而truststore一般表示CA证书 |
想要深入研究,可以看一下PKCS协议族,上述的key、csr、pfx/p12等都是该协议族中定义的扩展。
1.6. 查看证书
1.6.1. Windows
在开始菜单的运行中,输入certlm.msc管理本地计算机的证书,或certmgr.msc管理当前用户的证书。
1.6.2. Android
以荣耀为例,其他类型手机操作都类似。在设置中搜索“证书”,可以找到“安装证书”与“受信任的凭据”。在受信任的凭据中可以看到“系统”与“当前用户”两类证书,两个列表中可以打开或关闭相应的证书。
在安装证书中可以安装自己的证书。这个操作难度较大,不推荐让用户自己操作。
2. 生成根证书
2.1. 生成根证书密钥库
根证书是一张自签名的证书,使用者和颁发者都是自己。使用下面的命令生成根证书的密钥库(如果不指定keystore文件路径,则默认存在用户目录下的.keystore中),输入密钥库的密码,填写根证书的信息。
keytool -genkeypair -validity 36500 -keyalg EC -keypass xxxxxx -storepass xxxxxx -dname "CN=njhx,C=CN,OU=njhx" -alias rootca -keystore root.jks
此命令使用椭圆曲线secp256r1算法,产生一个256位的EC密钥对,并保存到njhx_root.jks中。使用以下命令查看密钥库的信息,会发现发布者与所有者都是njhx:
keytool -list -keystore root.jks -keypass xxxxxx -storepass xxxxxx -v
2.2. 从根证书密钥库导出根证书
使用keytool的导出功能,从密钥库中导出根证书,输入密钥库的密码,导出的证书文件为rootca.cer命令如下:
keytool -exportcert -validity 36500 -alias rootca -file root.cer -keystore root.jks -keypass xxxxxx -storepass xxxxxx
此时的颁发者与使用者都是njhx
3. 生成二级证书
3.1. 生成二级证书密钥库
二级证书需要由根证书签发。
- 首先使用keytool生成二级证书的密钥库,但是此时还是自签的,我们需要从中生成一个二级证书请求(其中包含了二级证书的公钥);
- 然后将证书请求发送到rootca签发二级证书;
- 最后,我们将rootca签发的二级证书导入到证书密钥库中,完成二级证书的生成。
下面命令是生成二级证书密钥库,密钥库的名称为subca,此时仍是自签的,证书颁发者和使用者都是自已:
keytool -genkeypair -validity 36500 -keyalg EC -keypass yyyyyy -storepass yyyyyy -dname "CN=njhx.com,C=CN,OU=njhx" -alias subca -keystore sub.jks
3.2. 从二级证书密钥库中生成证书请求
从二级CA密钥库中导出证书请求,下面是导出证书请求的命令,subca是上面生成的二级CA密钥库的名字,最终导出证书请求文件为subca.csr:
keytool -certreq -alias subca -file sub.csr -keystore sub.jks -keypass yyyyyy -storepass yyyyyy
3.3. 使用根证书签发证书
通过keytool工具的签发证书功能,使用rootca对二级证书请求(subca.csr)签发一张二级证书。命令如下,alias指定证书的颁发者,infile指定证书请求文件,outfile是二级证书的文件名:
keytool -gencert -validity 36500 -alias rootca -infile sub.csr -outfile sub.cer -keystore root.jks -keypass xxxxxx -storepass xxxxxx
可以看到该证书的颁发者已经是njhx,使用者为njhx.com
3.4. 导入二级证书到密钥库中
因为本地生成的密钥库仍然是自签名的,此时需要将根证书签发的二级证书导入密钥库中,导入前,要先将自签名的根证书导入密钥库,这点非常重要,否则会报“无法从回复中建立链”,命令如下:
keytool -importcert -alias rootca -file root.cer -keystore sub.jks -storepass yyyyyy -keypass yyyyyy
然后,才能导入sub.cer到密钥库,alias subca就是之前用来生成密钥库或证书请求时用到的秘钥对别名:
keytool -importcert -alias subca -file sub.cer -keystore sub.jks -storepass yyyyyy -keypass yyyyyy
此时查看一下密钥库中的subca的秘钥对的证书的颁发者是rootca中的njhx了。
keytool -list -alias subca -v -keystore sub.jks -storepass yyyyyy
4. 使用二级证书签发用户证书
签发用户CA的流程与产生二级CA的过程是一样的。下面我们产生用户user的CA,将生成二级证书操作中的rootca改为二级证书的subca,将alias都变为user。
4.1. 生成用户证书密钥库
先生成userca 的密钥库,命令如下:
keytool -genkeypair -alias userca -keyalg EC -validity 36500 -keystore user.jks -keypass zzzzzz -storepass zzzzzz -dname "CN=*.mesh.njhx.com,C=CN,OU=njhx"
4.2. 生成用户证书请求
keytool -certreq -alias userca -file user.csr -keystore user.jks -keypass zzzzzz -storepass zzzzzz
4.3. 用二级证书签发用户证书
keytool -gencert -alias subca -infile user.csr -outfile user.cer -keystore sub.jks -keypass yyyyyy -storepass yyyyyy
4.4. 导入用户证书到密钥库中
导入用户证书时,还需要将rootca也导入,否则在客户端使用时,会导致校验失败。
keytool -importcert -alias userca -file user.cer -keystore user.jks -storepass zzzzzz
keytool -importcert -alias rootca -file root.cer -keystore user.jks -storepass zzzzzz
查看密钥库时,显示证书链长度为3。
4.5. 查看用户证书链
需要先将根证书和二级证书安装到本地电脑的证书信任库中,双击user.cer才能看到该证书是已被信任,而且能够看到证书链关系。
4.6. 将JKS转为BKS
因为安卓上不支持JKS(安卓底层使用的是BouncyCastle),所以需要将jks文件转为BKS,在转换前,需要先下载BouncyCastle的jar包,比如下载“bcprov-jdk18on-1.71.1.jar”,在keytool中指定provider及这个jar包的路径,使用如下命令转换:
keytool -importkeystore -alias userca -srckeystore user.jks -destkeystore user.bks -srcstoretype JKS -deststoretype BKS -srcstorepass zzzzzz -srckeypass zzzzzz -deststorepass zzzzzz -destkeypass zzzzzz -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath path_to_bcprov-jdk18on-1.71.1.jar
使用如下命令查看user.bks证书,确认证书链长度是否为3:
keytool -list -v -keystore user.bks -storepass zzzzzz -keypass zzzzzz -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath path_to_bcprov-jdk18on-1.71.1.jar
5. 运用
5.1. 在Java中使用自签名证书
5.1.1. Netty中使用自签名证书
首先是使用用户密钥仓库创建SSLContext:
private SSLContext createSslContext() {
try (InputStream inputStream = new FileInputStream(“path to user.jks”)) {
ks = KeyStore.getInstance(“JKS”);
//加载keytool生成的密钥库,其中包括证书链
ks.load(inputStream, “zzzzzz”.toCharArray());
} catch(Exception e) {
LOG.warn("Fail to load keystore,can't support https", e);
return null;
}
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(this.serverKeyStore.keystore, this.serverKeyStore.keypass);
sslContext.init(kmf.getKeyManagers(), null, null);
} catch(Exception e) {
LOG.error("Fail to create ssl context", e);
}
return sslContext;
}
然后,在netty初始化时,在第一个位置添加SslHandler
SSLContext sslContext = createSslContext();
if(sslContext != null) {
SSLEngine sslEngine = sslContext .createSSLEngine();
sslEngine.setUseClientMode(false);
pl.addLast("ssl", new SslHandler(sslEngine));
}
需要特别注意,如果项目中使用了BouncyCastle,在添加Provider时,需要把他添加到最后面,否则会提示密码错误,尽管密码确信无疑是正确的。
Security.insertProviderAt(new BouncyCastleProvider(), Security.getProviders().length + 1);
5.1.2. OkHttp中使用自签名证书
如果不想实现信任根证书,可以将user.cer导入到KeyStore;同时在hostnameVerifier中实现信任user.cer中设置的域名。
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
try(InputStream certificate = new FileInputStream("path to user.cer")) {
keyStore.setCertificateEntry("userca", certificateFactory.generateCertificate(certificate));
}
//使用包含自签证书信息的keyStore构建一个X509TrustManager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm()
);
keyManagerFactory.init(keyStore, null);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
);
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
创建一个trustmanager,只信任自建的keystore
SSLContext sslContext = SSLContext.getInstance("TLS");
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager)trustManagers[0])
.hostnameVerifier((hostname, session) -> {
try {
Certificate[] certs = session.getPeerCertificates();
if(certs != null && certs.length > 0) {
X509Certificate cert = (X509Certificate)certs[0];
String peer = cert.getSubjectX500Principal().getName();
if(peer.contains(".mesh.njhx.com")) {
return true;
}
LOG.error("Fail to verify cert host name {}", peer);
}
} catch (SSLPeerUnverifiedException e) {
LOG.error("Fail to get cert host name", e);
}
return false;
});
推荐的实现时信任根证书root.cer,这时需要自己实现X509TrustManager:
private static class RootTruster implements X509TrustManager {
private final X509Certificate certificate;
public RootTruster(X509Certificate certificate) {
this.certificate = certificate;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 双向认证才会用到
}
// 校验服务端证书是否合法
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (chain == null || chain.length == 0) {
throw new IllegalArgumentException("checkServerTrusted:x509Certificate array isnull");
}
X509Certificate c = chain[chain.length - 1]; //最后一个就是根证书
if(c.equals(rootCer)) {
return; //信任链的根证书就是预置的根证书,则不必抛出异常
}
throw new CertificateException("Root not supported,chain.length=" + chain.length);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {certificate};
}
}
然后再将RootTruster放到TrustManager列表中
// 加载根证书
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(“stream ofroot.cer”);
TrustManager[] trustManagers;
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);
trustManagers = new TrustManager[tmf.getTrustManagers().length + 1];
// 创建一个trustmanager,只信任自建的keystore
trustManagers[0] = new RootTruster(certificate);
int i = 1;
for (TrustManager trustManager : tmf.getTrustManagers()) {
trustManagers[i++] = trustManager;
}
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, SecureUtil.getRandom());
builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]);
5.2. 在C#中使用自签名证书
因为项目中Windows客户端使用C#实现,所以需要在C#中信任自建的根证书。通过重载ServerCertificateValidationCallback,在其中判断根证书是root,cer即可。
//受.net版本限制,此处最高只支持TLS1.2版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
WebRequestHandler handler = new WebRequestHandler();
X509Certificate2 rootCert = new X509Certificate2(“path to root.cer”); //本地根证书对象
handler.ServerCertificateValidationCallback += (sender, cert, chain, error) => {
if (error == SslPolicyErrors.None) {
return true;
}
if (!cert.Subject.Contains(CERT_DOMAIN)) {
return false;
}
//最后一个证书是根证书
X509Certificate2 lastCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
//与本地根证书比较
if (rootCert.Equals(lastCert)) {
return true;
}
return false;
};
5.3. 在安卓中使用自签名证书
在安卓中,无论是作为服务端还是客户端,实现方式都与Java中一样。唯一不同的地方是服务端加载server.jks时需要将它转为server.bks。
5.4. 在IE中使用自签名证书
5.4.1. Windows
双击根证书,在弹出选择安装证书,在存储一页中选择“受信任的根证书颁发机构”。导入成功后,sub.cer就显示正常了,否则会有一个感叹号。
最终没有试验成功,因为本次未使用浏览器,也没有继续尝试下去。在Edge浏览器中打开总是因为安全问题拒绝。
6. 总结
整个摸索过程耗时约一周,搞一遍之后,对证书这块有了一个较为全面的认识。通常的项目不会用到本文讲述的内容。
在摸索本文的内容时,需要对RSA、EC算法有基本了解,对TLS/SSL相关的知识,特别是密钥交换的过程,要搞清楚。否则,在面对那么多的选择时,会不知所措。