KeyTool生成证书链及在java、c#中的运用

        本文内容大部分是从网络上学习得到的,所以总结后回馈给网络。如果你从中发现了错误,或者有更好的方法,非常欢迎评论,或发消息给我。

        之所以要探索证书链,是因为在至简网格的开发中要用到自签名证书。至简网格是一个端&云结合的小微企业开发框架,如果每部署一次都向机构申请一个证书,首先是很麻烦,其次是费用,至简网格的服务都是免费使用的,反而证书却出现收费。综上所述,将证书链摸索了一遍,实现了生成根证书,然后签名二级证书,二级证书分成服务器证书、应用签名证书,服务器证书、应用签名证书就是三级证书。

        顺便打个小广告,至简网格的业务代码已经逐步开源,欢迎访问码云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. 总结


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相关的知识,特别是密钥交换的过程,要搞清楚。否则,在面对那么多的选择时,会不知所措。

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值