本文内容大部分是从网络上学习得到的,所以总结后回馈给网络。如果你从中发现了错误,或者有更好的方法,非常欢迎评论,或发消息给我。
之所以要探索证书链,是因为在至简网格的开发中要用到自签名证书。至简网格是一个端&云结合的小微企业开发框架,如果每部署一次都向机构申请一个证书,首先是很麻烦,其次是费用,至简网格的服务都是免费使用的,反而证书却出现收费。综上所述,将证书链摸索了一遍,实现了生成根证书,然后签名二级证书,二级证书分成服务器证书、应用签名证书,服务器证书、应用签名证书就是三级证书。
顺便打个小广告,至简网格的业务代码已经逐步开源,欢迎访问码云或CSDN查看,GitHub上也有。
目录
1. 基础
1.1. SSL
1.2. 什么是证书
1.3. 证书的种类
1.4. 证书的生成
1.5. 基本概念
1.5.1. X.509
1.5.2. 证书编码格式(DER&PEM)
1.5.3. 证书相关的扩展名
1.6. 查看证书
1.6.1. Windows
1.6.2. Android
2. 生成根证书
2.1. 生成根证书密钥库
2.2. 从根证书密钥库导出根证书
3. 生成二级证书
3.1. 生成二级证书密钥库
3.2. 从二级证书密钥库中生成证书请求
3.4. 导入二级证书到密钥库中
4. 使用二级证书签发用户证书
4.1. 生成用户证书密钥库
4.2. 生成用户证书请求
4.3. 用二级证书签发用户证书
4.4. 导入用户证书到密钥库中
4.5. 查看用户证书链
4.6. 将JKS转为BKS
5. 运用
5.1. 在Java中使用自签名证书
5.1.1. Netty中使用自签名证书
5.1.2. OkHttp中使用自签名证书
5.2. 在C#中使用自签名证书
5.3. 在安卓中使用自签名证书
5.4. 在IE中使用自签名证书
5.4.1. Windows
6. 总结
证书通常使用在SSL(Secure Sockets Layer安全套接字协议)传输协议中,用于实现端侧与服务侧的密钥交换。现在应该叫它"TLS",但是由于习惯的原因,还是常叫它"SSL"。SSL使用场景最多的是在网站上,用它实现安全的HTTP传输。
HTTP协议传输是不加密内容的,这样,内容在传播的时候,就可能被人监听。在安全性要求较高的场景中,必须加密传输,比如,登录时传输密码的场景。
HTTPS就是带加密的HTTP协议,而HTTPS的加密传输是基于SSL实现的,使得,即使在网络传输中的某个节点别人监听,也不会泄露数据。SSL是在HTTP的下层实现加解密,加解密对用户和开发者来说,都是透明的。也就是说,在加密前,你的服务器是怎样实现的,加密后,不用做任何改变。
还有一个名词就是OpenSSL。简单地说,SSL是一种规范,而OpenSSL是SSL的一个实现,提供了一堆强大的工具,强大到90%我们都用不到。理论上来说,目前的技术水平很难破解SSL,但是SSL的实现却可能有漏洞,如著名的”心脏出血“。
SSL中用到证书,那么证书是什么?
证书可以理解为一个包含了签发方信息、拥有者信息、公钥、由签发方私钥产生的签名等信息的文档,当然还包括其他一些信息。校验用户证书是否可信,实际上就是检验该证书是否由合法的机构签发的。校验时,通过该证书中的结构信息找到对应机构的证书,利用机构证书中的公钥去校验用户证书中的签名是否正确。
从上述校验方法可以看出,证书是否可信,是由其签发方证书来校验的,而机构的证书是否可信,是由上一层机构的证书来校验的,如此就形成一条证书链,最顶层机构的证书的就是常说的根证书。
在网站的场景中,网站产生密钥库,记录网站的公钥、私钥及其他一些信息;然后从机构申请证书。生成的证书中包括了公钥等信息,同时包括了证书链,用来发布给网站的用户。用户访问网站时,先下载网站证书,校验证书链,然后使用其中的公钥与网站交换密钥。
一种是自签证书,另一种是由机构签发的证书。
自签证书,也就是颁发者是自己,使用自己的私钥来对证书的信息进行签名。根证书就是自签证书。客户端一般预置了知名机构的根证书,并且信任由这些根证书签发的证书。
颁发证书,就是通过这些机构签发证书,可能是根级机构,也可能是二级机构,使用机构的私钥来对证书的信息进行签名。为了保护根证书,通常机构都会让二级、三级机构颁发证书。
可以通过openssl或者jdk提供的keytool来生成证书、以及证书对应的密钥库。本文使用keytool举例。理解了证书生成的过程后,可以很容易对应到openssl的生成方法。
随便搜一下TLS证书,会出现一大堆关键字:X.509、p12、pfx、pkcs、pem、csr、cer、crt、jks、crl等,如此繁杂,能让人很快从入门到放弃。所以,先把这些关键词理一理。
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
除了注释中的说明外,还需要补充以下几点:
按照网上普遍说法,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
扩展名是比较容易误导人的地方,除了.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等都是该协议族中定义的扩展。
在开始菜单的运行中,输入certlm.msc管理本地计算机的证书,或certmgr.msc管理当前用户的证书。
以荣耀为例,其他类型手机操作都类似。在设置中搜索“证书”,可以找到“安装证书”与“受信任的凭据”。在受信任的凭据中可以看到“系统”与“当前用户”两类证书,两个列表中可以打开或关闭相应的证书。
在安装证书中可以安装自己的证书。这个操作难度较大,不推荐让用户自己操作。
根证书是一张自签名的证书,使用者和颁发者都是自己。使用下面的命令生成根证书的密钥库(如果不指定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
使用keytool的导出功能,从密钥库中导出根证书,输入密钥库的密码,导出的证书文件为rootca.cer命令如下:
keytool -exportcert -validity 36500 -alias rootca -file root.cer -keystore root.jks -keypass xxxxxx -storepass xxxxxx
此时的颁发者与使用者都是njhx
二级证书需要由根证书签发。
下面命令是生成二级证书密钥库,密钥库的名称为subca,此时仍是自签的,证书颁发者和使用者都是自已:
keytool -genkeypair -validity 36500 -keyalg EC -keypass yyyyyy -storepass yyyyyy -dname "CN=njhx.com,C=CN,OU=njhx" -alias subca -keystore sub.jks
从二级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
因为本地生成的密钥库仍然是自签名的,此时需要将根证书签发的二级证书导入密钥库中,导入前,要先将自签名的根证书导入密钥库,这点非常重要,否则会报“无法从回复中建立链”,命令如下:
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
签发用户CA的流程与产生二级CA的过程是一样的。下面我们产生用户user的CA,将生成二级证书操作中的rootca改为二级证书的subca,将alias都变为user。
先生成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"
keytool -certreq -alias userca -file user.csr -keystore user.jks -keypass zzzzzz -storepass zzzzzz
keytool -gencert -alias subca -infile user.csr -outfile user.cer -keystore sub.jks -keypass yyyyyy -storepass yyyyyy
导入用户证书时,还需要将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。
需要先将根证书和二级证书安装到本地电脑的证书信任库中,双击user.cer才能看到该证书是已被信任,而且能够看到证书链关系。
因为安卓上不支持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
首先是使用用户密钥仓库创建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);
如果不想实现信任根证书,可以将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]);
因为项目中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;
};
在安卓中,无论是作为服务端还是客户端,实现方式都与Java中一样。唯一不同的地方是服务端加载server.jks时需要将它转为server.bks。
双击根证书,在弹出选择安装证书,在存储一页中选择“受信任的根证书颁发机构”。导入成功后,sub.cer就显示正常了,否则会有一个感叹号。
最终没有试验成功,因为本次未使用浏览器,也没有继续尝试下去。在Edge浏览器中打开总是因为安全问题拒绝。
整个摸索过程耗时约一周,搞一遍之后,对证书这块有了一个较为全面的认识。通常的项目不会用到本文讲述的内容。
在摸索本文的内容时,需要对RSA、EC算法有基本了解,对TLS/SSL相关的知识,特别是密钥交换的过程,要搞清楚。否则,在面对那么多的选择时,会不知所措。