PKI(HTTPS)体系详解

PKI是什么

百度百科:

PKI是Public Key Infrastructure的首字母缩写,翻译过来就是公钥基础设施;PKI是一种遵循标准的利用公钥加密技术为电子商务的开展提供一套安全基础平台的技术和规范。

X.509标准中,为了区别于权限管理基础设施(Privilege Management Infrastructure,简称PMI),将PKI定义为支持公开密钥管理并能支持认证、加密、完整性和可追究性服务的基础设施]。这个概念与第一个概念相比,不仅仅叙述PKI能提供的安全服务,更强调PKI必须支持公开密钥的管理。也就是说,仅仅使用公钥技术还不能叫做PKI,还应该提供公开密钥的管理。因为PMI仅仅使用公钥技术但并不管理公开密钥,所以,PMI就可以单独进行描述了而不至于跟公钥证书等概念混淆。X.509中从概念上分清PKI和PMI有利于标准的叙述。然而,由于PMI使用了公钥技术,PMI的使用和建立必须先有PKI的密钥管理支持。也就是说,PMI不得不把自己与PKI绑定在一起。当我们把两者合二为一时,PMI+PKI就完全落在X.509标准定义的PKI范畴内。根据X.509的定义,PMI+PKI仍旧可以叫做PKI,而PMI完全可以看成PKI的一个部分。

PKI 既不是一个协议,也不是一个软件,它是一个标准,在这个标准之下发展出的为了实现安全基础服务目的的技术统称为 PKI。

PKI的组成部分

百度百科:

PKI(Public Key Infrastructure)公钥基础设施是提供公钥加密和数字签名服务的系统或平台,目的是为了管理密钥和证书。一个机构通过采用PKI 框架管理密钥和证书可以建立一个安全的网络环境。PKI 主要包括四个部分:X.509 格式的证书(X.509 V3)和证书废止列表CRL(X.509 V2);CA 操作协议;CA 管理协议;CA 政策制定。一个典型、完整、有效的PKI 应用系统至少应具有以下五个部分:

  1) 认证中心CA CA 是PKI 的核心,CA负责管理PKI 结构下的所有用户(包括各种应用程序)的证书,把用户的公钥和用户的其他信息捆绑在一起,在网上验证用户的身份,CA 还要负责用户证书的黑名单登记和黑名单发布,后面有CA 的详细描述。
  2) X.500 目录服务器 X.500 目录服务器用于发布用户的证书和黑名单信息,用户可通过标准的LDAP 协议查询自己或其他人的证书和下载黑名单信息。
  3) 具有高强度密码算法(SSL) 的安全WWW服务器 Secure socket layer(SSL)协议最初由Netscape 企业发展,现已成为网络用来鉴别网站和网页浏览者身份,以及在浏览器使用者及网页服务器之间进行加密通讯的全球化标准。
  4) Web(安全通信平台) Web 有Web Client 端和Web Server 端两部分,分别安装在客户端和服务器端,通过具有高强度密码算法的SSL协议保证客户端和服务器端数据的机密性、完整性、身份验证。
  5) 自开发安全应用系统 自开发安全应用系统是指各行业自开发的各种具体应用系统,例如银行、证券的应用系统等。

完整的PKI 包括认证政策的制定(包括遵循的技术标准、各CA 之间的上下级或同级关系、安全策略、安全程度、服务对象、管理原则和框架等)、认证规则、运作制度的制定、所涉及的各方法律关系内容以及技术的实现等。

作为WEB开发者其实只需要关注pki组成部分中的后三部分,高强度密码算法SSL目前已经包含在https中,下面对https有专门的讲解;web安全通信平台可以理解为我们的浏览器和服务器,即可以建立https连接的两个端点;自开发安全应用系统即是我们开发的支持pki验证的系统。

HTTPS 详解

HTTP VS HTTPS

使用HTTP有以下风险:

  1. 窃听风险(eavesdropping): 通信使用明文(不加密),内容可能会被窃听
  2. 篡改风险(tampering):不验证通信方的身份,因此有可能遭遇伪装
  3. 冒充风险(pretending):无法证明报文的完整性,所以有可能已遭篡改

当然,相对于HTTPS的安全性更高,HTTP也有自己的优势,即性能更好。

简单来说,HTTPS比HTTP增加了SSL握手和对于传输数据加密解密的过程,握手部分的耗时差不多为建立TCP连接耗时的3倍到6倍(取决于数字证书的加密强度),另外对于传输数据的加密解密也会占用设备一部分的性能。

HTTPS/HTTP的区别和联系

HTTPS = HTTP+SSL / TLS

对称加密和非对称加密

对称加密

所谓的“对称加密技术”,意思就是说:“加密”和“解密”使用相同的密钥。就好比用 7zip 或 WinRAR 创建一个带密码(口令)的加密压缩包。当你下次要把这个压缩文件解开的时候,你需要输入同样的密码。在这个例子中,密 码/口令就如同刚才说的“密钥”。

缺陷:对称加密的密钥如何传输,第一次传输的时候肯定是明文,那么对称密钥还是会存在被窃取的风险,以及客户端如何确认服务端的身份。

对称加密经典算法:AES,DES,3DES,TDEA,Blowfish,RC5,IDEA。

非对称加密

百度百科

非对称加密算法需要两个密钥:公开密钥(public key)和私有密钥(private key)。

公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其 它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另 一把专用密钥对加密后的信息进行解密。另一方面,甲方可以使用乙方的公钥对机密信息进行签名后再发送 给乙方;甲方再用自己的私匙对乙方发送回来的数据进行验签。

非对称加密经典算法:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。

缺陷:非对称加密可以解决对称加密被窃取的问题,但依然无法验证服务器的身份信息。

非对称加密被劫持情况

对称加密和非对称加密算法效率对比

对称加密(AES):

  1. AES加密的时间与被加密文件的大小正线性增长,加密1G的文件大概需要4分多钟,加密速度较快。

  2. 加密后的文件大小是原始文件大小的两倍。

  3. 解密文件所需时间是加密时间的两倍(这个应该是加密文件是原始文件大小两倍造成的)。

非对称加密(RSA):

  1. RSA加密算法加密时间很短,基本可以忽略不计。但是,在解密时,RSA显的比较慢,解密时间与解密文件 的大小呈现线性增长趋势。加密1M的文件大概需要5秒,但是解密却需要4分钟。

  2. 加密后的文件与原始文件的大小基本相同。

  3. 解密的效率远低于加密效率,按照这个时间去计算,加密1G的文件需要1分钟,但是解密却需要65小时。

AES和RSA加密算法效率对比

CA 认证

无论是对称加密还是非对称加密,都遗留了一个问题没有解决,那就是如何证明我们访问的网站就是我们要访问的网站,而不是他人伪造的,即中间人攻击和信息抵赖的问题,这里就用到了CA证书。


百度百科

CA认证,即电子认证服务 ,是指为电子签名相关各方提供真实性、可靠性验证的活动。

证书颁发机构(CA, Certificate Authority)即颁发数字证书的机构。是负责发放和管理数字证书的权威机构,并作为电子商务交易中受信任的第三方,承担公钥体系中公钥的合法性检验的责任。

CA中心为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥 有证书中列出的公开密钥。CA机构的数字签名使得攻击者不能伪造和篡改证书。在SET交易中,CA不仅对持 卡人、商户发放证书,还要对获款的银行、网关发放证书。

CA是证书的签发机构,它是PKI的核心。CA是负责签发证书、认证证书、管理已颁发证书的机关。它要制 定政策和具体步骤来验证、识别用户身份,并对用户证书进行签名,以确保证书持有者的身份和公钥的拥有 权。

CA 也拥有一个证书(内含公钥)和私钥。网上的公众用户通过验证 CA 的签字从而信任 CA ,任何人都可以得到 CA的证书(含公钥),用以验证它所签发的证书。

如果用户想得到一份属于自己的证书,他应先向 CA 提出申请。在 CA 判明申请者的身份后,便为他分配一个公钥,并且 CA 将该公钥与申请者的身份信息绑在一起,并为之签字后,便形成证书发给申请者。

如果一个用户想鉴别另一个证书的真伪,他就用 CA 的公钥对那个证书上的签字进行验证,一旦验证通过,该证书就被认为是有效的。

为保证用户之间在网上传递信息的安全性、真实性、可靠性、完整性和不可抵赖性,不仅需要对用户的 身份真实性进行验证,也需要有一个具有权威性、公正性、唯一性的机构,负责向电子商务的各个主体颁发 并管理符合国内、国际安全电子交易协议标准的电子商务安全证,并负责管理所有参与网上交易的个体所需 的数字证书,因此是安全电子交易的核心环节。

解决上述身份验证问题的关键是确保获取的公钥途径是合法的,能够验证服务器的身份信息,为此需要引入权威的第三方机构CA(如沃通CA)。CA 负责核实公钥的拥有者的信息,并颁发认证"证书",同时能够为使用者提供证书验证服务,即PKI体系(PKI基础知识)。
基本的原理为,CA负责审核信息,然后对关键信息利用私钥进行"签名",公开对应的公钥,客户端可以利用公钥验证签名。CA也可以吊销已经签发的证书,基本的方式包括两类 CRL 文件和 OCSP。CA使用具体的流程如下:

1. 服务方S向第三方机构CA提交公钥、组织信息、个人信息(域名)等信息并申请认证;

2. CA通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等;

3. 如信息审核通过,CA会向申请者签发认证文件-证书。

   证书包含以下信息:申请者公钥、申请者的组织信息和个人信息、签发机构CA的信息、有效时间、证书序列号等信息的明文,同时包含一个签名;

   签名的产生算法:首先,使用散列函数计算公开的明文信息的信息摘要,然后,采用CA的私钥对信息摘要进行加密,密文即签名;

4. 客户端 C 向服务器 S 发出请求时,S 返回证书文件;

5. 客户端 C 读取证书中的相关的明文信息,采用相同的散列函数计算得到信息摘要,然后,利用对应CA的公钥解密签名数据,对比证书的信息摘要,如果一致,则可以确认证书的合法性,即公钥合法;

6. 客户端然后验证证书相关的域名信息、有效时间等信息;

7. 客户端会内置信任CA的证书信息(包含公钥),如果CA不被信任,则找不到对应 CA的证书,证书也会被判定非法。

8. 在这个过程注意几点:

   a.申请证书不需要提供私钥,确保私钥永远只能服务器掌握;

   b.证书的合法性仍然依赖于非对称加密算法,证书主要是增加了服务器信息以及签名;

   c.内置 CA 对应的证书称为根证书,颁发者和使用者相同,自己为自己签名,即自签名证书(为什么说"部署自签SSL证书非常不安全")

   d.证书=公钥+申请者与颁发者信息+签名;

即便有人截取服务器证书,再发给客户端,想冒充服务器,也无法实现。因为证书和url的域名是绑定的。

免费获取CA证书的几种方式

  1. 从相关商业机构申请,以阿里云为例:

  1. 使用证书工具生成(Keytool或者Openssl): 详见实操部分

SSL/TLS的联系与区别

SSL/TLS的背景

1994年,NetScape 公司设计了 SSL 协议的1.0版,但是未发布。

1995年,NetScape 公司发布 SSL 2.0版,很快发现有严重漏洞。

1996年,SSL 3.0 版问世,得到大规模应用。

1999年,互联网标准化组织 ISOC 接替 NetScape 公司,发布了 SSL 的升级版 TLS 1.0 版。

2006年和2008年,TLS 进行了两次升级,分别为 TLS 1.1 版和 TLS 1.2 版。最新的变动是2011年 TLS 1.2的修订版。

对于二者的关系,可以说是同一事物在不同时期的表现。TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2 为SSL 3.3。

SSL(Secure Socket Layer,安全套接字层)

SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层,SSL记录协议层(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。SSL握手协议层(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算 法、交换加密密钥等。

SSL协议提供的服务主要有:
​ 1)认证用户和服务器,确保数据发送到正确的客户机和服务器;

2)加密数据以防止数据中途被窃取;

3)维护数据的完整性,确保数据在传输过程中不被改变。

SSL协议的握手流程:

以上工作流程分为五步:

第一步,爱丽丝给出协议版本号、一个客户端生成的随机数1(Client random),以及客户端支持的加密方法。

第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数2(Server random)。

第三步,爱丽丝确认数字证书有效,然后生成一个新的随机数3(Premaster secret),并使用数字证书中的公钥,
加密这个随机数,发给鲍勃。同时依据三个随机数根据指定算法生成对称密钥。另如果开启了双向认证,那么在这一步客户端会将自己的证书的发送给服务器。

第四步,鲍勃使用自己的私钥,获取爱丽丝发来的随机数(即Premaster secret)。服务器开始根据约定的加密方
法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。

第五步,爱丽丝和鲍勃用对话密钥将对话加密后开始传输。

TLS(Transport Layer Security Protocol,传输层安全协议)

是IETE(工程任务组)指定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,也用于在两个通信应用程序之间提供保密性和数据完整性。

该协议也由两部分组成:TLS记录协议(TLS Record)和TLS握手协议(TLS Handshake)。

TLS与SSL的差异:

1)版本号:TLS记录格式与SSL记录格式相同,但版本号的值不同,TLS的版本1.0使用的版本号为SSLv3.1。

2)报文鉴别码:SSLv3.0和TLS的MAC算法及MAC计算的范围不同。TLS使用RFC-2104定义的HMAC算 法。SSLv3.0使用了相似的算法,两者差别在于SSLv3.0中,填充字节与密钥之间采用的是连接运算,而HMAC算法 采用的异或运算。但是两者的安全程度是相同的。

3)伪随机函数:TLS使用了称为PRF的伪随机函数来将密钥扩展成数据块,是更安全的方式。

4)报警代码:TLS支持几乎所有的SSLv3.0报警代码,而且TLS还补充定义了很多报警代码,如解密失败(decryption_failed)、记录溢出(record_overflow)、未知CA(unknown_ca)、拒绝访问(access_denied)等。

5)密文族和客户证书:SSLv3.0和TLS存在少量差别,即TLS不支持Fortezza密钥交换、加密算法和客户证书。

6)certificate_verify和finished消息:SSLv3.0和TLS在用certificate_verify和finished消息计算MD5和SHA-1散列码时,计算的输入有少许差别,但安全性相当。

7)加密计算:TLS和SSLv3.0在计算主密值(master secret)时采用的方式不同。

8)填充:用户数据加密之前需要增加的填充字节。在SSL中,填充后的数据长度哟啊达到密文快长度的最 小整数倍。而在TLS中,填充后的数据长度可以是密文块长度的任意整数倍(但填充的最大长度为255字节),这 种方式可以防止基于对报文长度进行分析的攻击。

TLS的主要增强内容:

TLS的主要目标是使SSL更安全,并使协议的规范更精确和完善。TLS在SSL v3.0的基础上,提供了以下增加内容:

1)更安全的MAC算法

2)更严密的警报

3)“灰色区域”规范的更明确的定义

TLS对于安全性的改进:

1)对于消息认证使用密钥散列法:TLS使用“消息认证代码的密钥散列法”(HMAC),当记录在开放的网络(如因特网)上传送时,该代码确保记录不会被变更。SSLv3.0还提供键控消息认证,但HMAC比SSLv3.0使用(消息认证代码)MAC功能更安全。

2)增强的伪随机功能(PRF):PRF生成密钥数据。在TLS中,HMAC定义PRF。PRF使用两种散列算法保 证其安全性。如果任一算法暴露了,只要第二种算法未暴露,则数据仍然是安全的。

3)改进的已完成消息验证:TLS和SSLv3.0都对两个端点提供已完成的消息,该消息认证交换的消息没有 被变更。然而,TLS将此已完成消息基于PRF和HMAC值之上,这也比SSLv3.0更安全。

4)一致证书处理:与SSLv3.0不同,TLS试图指定必须在TLS之间实现交换的证书类型。

5)特定警报消息:TLS提供更多的特定和附加警报,以指示任一会话端点检测到的问题。TLS还对何时应 该发送某些警报进行记录。

综上所述,SSL和TLS 没有本质区别,二者的工作流程基本一致,只是TLS在SSL的基础上对于安全性做了一些增强。

双向认证和单向认证

单向认证是指客户端根据服务器传过来的证书信息校验服务器的合法性,上文的SSL握手流程就是单向认证。

服务器的合法性包括:

1.证书是否过期。
2.发行服务器证书的CA是否可靠。
3.发行者证书的公钥能否正确解开服务器证书的"发行者的数字签名"。
4.服务器证书上的域名是否和服务器的实际域名相匹配。

如果合法性验证没有通过,则浏览器会给用户一个警告提示访问网站不安全,让用户选择是继续访问还是终止访问。

服务器合法性通过,以淘宝为例:

服务器合法性校验不通过,原因是这个证书是自己签发,CA不受信:

双向认证是在单向认证的基础上,增加了一步服务器校验客户端证书的动作,这一步骤存在于SSL握手流程的第三步。一般在对用户身份信息比较敏感的网站上会使用,比如银行系统的U盾,以及此次龙湾项目的PKI登陆。如果服务器校验客户端证书成功,则正常访问,如果失败,浏览器则会直接提示无法访问该网站。

服务器获取客户端证书:

服务器校验客户端证书失败(服务器开启了强制校验,没有证书或者证书不符合):

服务器证书和客户端证书的区别与联系

服务器证书和客户端证书没有本质区别,都是一个包含公钥和私钥的文件。但是并不建议二者交换使用,在上文的单向认证中的服务器合法性中会校验服务器证书上的域名和服务器的实际域名是否相匹配,而这个域名信息是服务器证书里面独有的,客户端证书中独有的信息是则是用户身份信息,如果二者交换使用,浏览器会提示服务器证书域名不匹配,那么该服务器证书则会不受信,也就失去了使用服务器证书的意义。

服务器证书和客户端证书如何建立信任关系

在了解这个问题过程中我曾经进入一个误区,以为服务器校验客户端证书是依赖于将每一个客户端证书的公钥导入服务器证书的证书库(即服务器证书文件)中。

而实际上服务器校验客户端证书是否合法是取决于服务器证书是否信任客户端证书的证书链。也可以理解为服务器证书信任颁发客户端证书的CA。在操作层面这个建立这个信任关系也是将签发客户端证书的CA的公钥导入服务器证书,但从原理层面来说,相较于上面的误区,信任证书链的话只需要导入一次CA的公钥即可,而不用单独将每个客户端证书的公钥导入服务器证书。

我在开发过程中,甲方只给我一个服务器证书,并没有给客户端证书,为了模拟测试只能自己签发客户端证书,但自己签发的证书没有CA信息通过校验证书链肯定是不行的,这时候只能使用上面的导入客户端公钥的办法了,即先将自己生成的客户端证书中的公钥导出,然后将该公钥导入服务器证书中使服务器证书信任该公钥,这一点可以看后面的操作部分。

另外还有一点就是校验客户端证书时并没有做业务层面的判断,假如某CA签发了N个客户端证书,而服务器证书又信任这个CA的证书链,那么对于服务器证书来说,这些客户端证书是没有任何区别(因为都是受信的),如果想建立证书与用户之间的关系,还需要在代码层面来操作。

关于服务器证书和客户端证书建立信任关系的讨论

证书链

百度百科:

证书链(certificate chain)

证书链可以有任意环节的长度,所以在三节的链中,信任锚证书CA 环节可以对中间证书签名;中间证书的所有者可以用自己的私钥对另一个证书签名。CertPath API 可以用来遍历证书链以验证有效性,也可以用来构造这些信任链。

Web 浏览器已预先配置了一组浏览器自动信任的根 CA 证书。来自其他证书授权机构的所有证书都必须附带证书链,以检验这些证书的有效性。证书链是由一系列 CA 证书发出的证书序列,最终以根 CA 证书结束。

证书最初生成时是一个自签名证书。自签名证书是其签发者(签名者)与主题(其公共密钥由该证书进行验证的实体)相同的证书。如果拥有者向 CA 发送证书签名请求 (CSR),然后输入响应,自签名证书将被证书链替换。链的底部是由 CA 发布的、用于验证主题的公共密钥的证书(回复)。链中的下一个证书是验证 CA 的公共密钥的证书。通常,这是一个自签名证书(即,来自 CA、用于验证其自身的公共密钥的证书)并且是链中的最后一个证书。

在其他情况下,CA 可能会返回一个证书链。在此情况下,链的底部证书是相同的(由 CA 签发的证书,用于验证密钥条目的公共密钥),但是链中的第二个证书是由其他 CA 签发的证书,用于验证您向其发送了 CSR 的 CA 的公共密钥。然后,链中的下一个证书是用于验证第二个 CA 的密钥的证书,依此类推,直至到达自签名的根证书。因此,链中的每个证书(第一个证书之后的证书)都需要验证链中前一个证书的签名者的公共密钥。

Windows系统中的证书链:

服务器证书、中间证书与根证书在一起组合成一条合法的证书链,证书链的验证是自下而上的信任传递的过程。
​ 二级证书结构存在的优势:
    a.减少根证书结构的管理工作量,可以更高效的进行证书的审核与签发;
     b.根证书一般内置在客户端中,私钥一般离线存储,一旦私钥泄露,则吊销过程非常困难,无法及时补救;
     c.中间证书结构的私钥泄露,则可以快速在线吊销,并重新为用户签发新的证书;
     d.证书链四级以内一般不会对 HTTPS 的性能造成明显影响。

证书链有以下特点:
     a.同一本服务器证书可能存在多条合法的证书链。因为证书的生成和验证基础是公钥和私钥对,如果采用相同的公钥和私钥生成不同的中间证书,针对被签发者而言,该签发机构都是合法的 CA,不同的是中间证书的签发机构不同;
     b.不同证书链的层级不一定相同,可能二级、三级或四级证书链。 中间证书的签发机构可能是根证书机构也可能是另一个中间证书机构,所以证书链层级不一定相同。

证书格式

根据不同的服务器以及服务器的版本,我们需要用到不同的证书格式,就市面上主流的服务器来说,大概有以下格式:

  • DER/CER,文件是二进制格式,只保存证书,不保存私钥。

  • CRT,可以是二进制格式,可以是文本格式,与DER格式相同,不保存私钥。

  • PEM,一般是文本格式,可保存证书,可保存私钥。

  • PFX/P12,二进制格式,同时包含证书和私钥,一般有密码保护。

  • JKS,二进制格式,同时包含证书和私钥,一般有密码保护。

DER

该格式是二进制文件内容,Java 和 Windows 服务器偏向于使用这种编码格式。

OpenSSL 查看:
openssl x509 -in certificate.der -inform der -text -noout
转换为 PEM:
openssl x509 -in cert.crt -inform der -outform pem -out cert.pem

PEM

Privacy Enhanced Mail,一般为文本格式,以 -----BEGIN----- 开头,以-----END----- 结尾。中间的内容是BASE64 编码。这种格式可以保存证书和私钥,有时我们也把PEM 格式的私钥的后缀改为 .key 以区别证书与私钥。具体你可以看文件的内容。这种格式常用于 Apache和 Nginx服务器。

OpenSSL 查看:
openssl x509 -in certificate.pem -text -noout
转换为 DER:
openssl x509 -in cert.crt -outform der -out cert.der

CRT

Certificate 的简称,有可能是 PEM 编码格式,也有可能是 DER 编码格式。如何查看请参考前两种格式。

PFX/P12

Predecessor of PKCS#12,这种格式是二进制格式,且证书和私钥存在一个 PFX 文件中。一般用于 Windows上的 IIS 服务器,window上可以直接安装。改格式的文件一般会有一个密码用于保证私钥的安全。

OpenSSL 查看:
openssl pkcs12 -in for-iis.pfx
转换为 PEM:
openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes

JKS/KEYSOTRE/TRUSTSTORE

Java Key Storage,这是 JAVA的专属格式,利用 JAVA 的一个叫 keytool 的 工具可以进行格式转换。一般用于 Tomcat 服务器。

实操部分

生成证书

自己签发证书可以通过JDK自带的工具Keytool或者Linux上的openssl来进行,二者都很容易百度到相关资料,本文只对keytool方式进行简单讲解。

生成服务器证书

keytool -genkey -v -alias tomcat -keyalg RSA -keystore
F:\baidu\keystore\tomcat.keystore -validity 36500
-alias 证书别名,可选,任意,但建议取有意义的值
-keyalg 生成证书所使用的算法
-keystore 生成的证书文件保存位置
-validity 证书有效期,36500表示100年,默认值是90天

注意:

  1. 名字与姓氏即为服务器的域名或者ip,如果是合法机构签发的证书,域名或者ip匹配不上会提示不受信,虽然我们自己签发的证书本来就是不受信的,但还是建议这里填有意义的域名或者ip。

  2. 密钥库口令需要记住,无论是配置https还是向密钥库中添加信任的公钥时都会用到。

生成客户端证书

keytool -genkey -v -alias mykey -keyalg RSA -storetype PKCS12 -keystore
F:\baidu\keystore\client.key.p12

生成证书后在windows系统中可以直接双击文件进行安装。

让服务器信任客户端证书

将客户端证书导出为一个单独的CER文件。

keytool -export -alias mykey -keystore F:\baidu\keystore\client.key.p12 -storetype
PKCS12 -storepass password -rfc -file F:\baidu\keystore\client.key.cer

注:password为客户端证书的密码,即第一步生成服务器证书时的密钥库口令。

将CER文件导入到服务器的证书库。

让客户端信任服务器证书

把服务器证书导出为CER文件,在windows中可以直接双击安装.cer文件。

keytool -keystore F:\baidu\keystore\\tomcat.keystore -export -alias tomcat -file
F:\baidu\keystore\tomcat.cer

Windows 系统中查看已安装的证书

Win+R 打开运行窗口,输入certmgr.msc

如果上文中生成的客户端证书(文件扩展名为.p12)成功安装,那么在个人证书可以看到,如果没有,则可能默认证书没有安装到个人证书文件夹下,可以重新安装一遍并在证书安装位置时手动将其修改为"个人",如下是我的系统中安装的证书:

对于自己签发的证书,颁发给和颁发者都是相同的(这两个值就是生成客户端证书时填入的名字和姓氏),而对于CA签发的则是不同的,下图中的第一个证书是我在阿里云上申请的免费的证书,可以看到颁发者是商业机构。

对于这些证书可以通过右键来导出公钥或者私钥(如果有并且允许导出),导出私钥时会提示输入密钥库的密码。

单机 TOMCAT 配置HTTPS

修改 server.xml 配置文件

在server.xml中增加

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="D:\services\apache-tomcat-8.5.34\cert\1618145_www.showsouth.top_tomcat\1618145_www.showsouth.top.pfx"
keystoreType="PKCS12"
keystorePass="xxxxxx" URIEncoding="UTF-8"/>
属性 描述
clientAuth 如果设为true,表示Tomcat要求所有的SSL客户出示安全证书,对SSL客户进行身份验证
keystoreFile 指定keystore文件的存放位置,可以指定绝对路径,也可以指定相对于<CATALINA_HOME>(Tomcat安装目录)环境变量 的相对路径。如果此项没有设定,默认情况下,Tomcat将从当前操作系统用户的用户目录下读取名为“.keystore”的文件。
keystorePass 指定keystore的密码,如果此项没有设定,在默认情况下,Tomcat将使用“changeit”作为默认密码。
sslProtocol 指定套接字(Socket)使用的加密/解密协议,默认值为TLS,用户不应该修改这个默认值。
ciphers 指定套接字可用的用于加密的密码清单,多个密码间以逗号(,)分隔。如果此项没有设定,在默认情况下,套接字可以使用任意一个可用的密码。

启动tomcat,访问对应的url:

另因为我的这个阿里云申请的证书是授权给www.showsouth.top的这个域名,然后为了校验通过,修改本机hosts文件,将www.showsouth.top映射到127.0.0.1。

分以下几种情况访问:

1.域名和证书内域名匹配:

访问https://www.showsouth.top:8443 , 如下,可以看到浏览器提示安全(firefox的提示更加友好),如果是自己生成的证书,即便二者匹配,浏览器也会提示不安全。

2.域名和证书内域名不匹配:

访问https://127.0.0.1:8443,firefox会提示服务器域名与证书中的域名不匹配的警告信息(这个提示信息只有Firefox有,chrome没有,只会提示网站不安全),让用户选择是否继续访问。

通过以上两种访问方式,即可以验证上文提到的服务器合法性的第四条。

继续选择访问之后会地址栏会提示不安全,如下:

3:以HTTP访问(可选):

开启HTTPS后,对于HTTP请求有以下三种处理方式:

​ a.仅以https访问,关闭http;

​ b.https/http同时都可以访问;

​ c.http端口依然开放,但将所有的http请求重定向至https。

POSTMAN 访问HTTPS请求

单向认证

postman访问的接口如果是单向认证并且证书可以成功校验通过,则https或者http对于用户来说没有区别,但如果使用了https并且服务器证书不受信,则postman会直接报错,如下,这种情况可以通过在设置种关闭ssl证书校验解决。

双向认证

postman配置双向认证比较麻烦,需要将客户端证书分离成公钥和私钥,格式貌似只支持pem格式,然后再设置的证书选项中配置即可。

Spring Boot 配置HTTPS

springboot配置https讲三种方式,第一种是springboot原生的配置文件配置,第二种和第三种都是通过代码配置,但分别针对tomcat容器和jetty容器。第一种和第二种springboot官方文档都有详细讲解,下面会直接引用官方文档内容,jetty容器的会单独贴代码。

通过Spring Boot 配置文件配置HTTPS

spring boot官方文档 配置https

核心配置:
server.port=8443
server.ssl.key-store=classpath:tomcat.keystore
server.ssl.key-store-password=123456
server.ssl.keyStoreType=PKCS12
server.ssl.client-auth=want (仅双向认证时需要)

可选配置(意思就是我也不是很懂):
server.ssl.trust-store=classpath:tomcat.keystore
server.ssl.trust-store-password=123456
server.ssl.protocol=
server.ssl.enabled=false
server.ssl.key-password=
server.ssl.ciphers=
server.ssl.enabled-protocols=
server.ssl.trust-store-provider=
server.ssl.key-alias=
server.ssl.key-store-provider=
server.ssl.key-store-type=
server.ssl.trust-store-type=

核心配置的各参数含义可以参考单机tomcat配置中的各参数含义理解。

主要讲下核心配置中的server.ssl.client-auth=want,这个参数有在双向认证时需要配置,其有两个值need和want(后文中的needClientAuth/wantClientAuth 含义与这两个值相同)。下面分别讲解:

need:开启双向认证,强制验证客户端证书。如果用户没有证书或者拒绝提供用户证书(浏览器层面的操作),则SSL连接会直接断开,浏览器显示效果与服务器校验客户端证书失败(服务器开启了强制校验,没有证书或者证书不符合)的第二张图相同,必须在用户提供证书的前提下才能和服务器建立SSL连接。

want:开启双向认证,但校验客户端证书的操作是可选的,如果用户没有证书或拒绝提供证书,SSL连接依然进行,但在服务器端读到的客户端证书为空。

使用配置文件配置HTTPS的优点是简单,并且不用对代码进行改动。缺点是通过配置文件配置对于HTTP/HTTPS仅能二选一,如果需要同时开启HTTP和HTTPS,则必须要通过代码层面配置。

Using configuration such as the preceding example means the application no longer supports a plain HTTP connector at port 8080. Spring Boot does not support the configuration of both an HTTP connector and an HTTPS connector through application.properties. If you want to have both, you need to configure one of them programmatically. We recommend using application.properties to configure HTTPS, as the HTTP connector is the easier of the two to configure programmatically. See the spring-boot-sample-tomcat-multi-connectors sample project for an example.

Spring boot tomcat 容器代码配置同时支持HTTP和HTTPS

在配置文件中配置https,同上,然后在代码中添加支持http的配置。

代码如下:

    /**
     * 配置一个TomcatEmbeddedServletContainerFactory bean
     *
     * @return
     */
    @Bean
    public EmbeddedServletContainerFactory servletContainer() {

        TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {

            @Override
            protected void postProcessContext(Context context) {

                SecurityConstraint securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        tomcat.addAdditionalTomcatConnectors(initiateHttpConnector());
        return tomcat;
    }

    /**
     * 让我们的应用支持HTTP是个好想法,但是需要重定向到HTTPS,
     * 但是不能同时在application.properties中同时配置两个connector,
     * 所以要以编程的方式配置HTTP connector,然后重定向到HTTPS connector
     *
     * @return Connector
     */
    private Connector initiateHttpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        // http端口
        connector.setPort(8080);
        connector.setSecure(false);
        // https端口
        connector.setRedirectPort(7443);
        return connector;
    }

}

结合单机tomcat配置https的配置文件,可以看到配置HTTP或者HTTPS连接的核心都是Connector,下面的jetty容器配置的核心也是添加多个Connector。

Spring Boot Jetty 容器同时配置HTTP和HTTPS

使用jetty配置时就没有结合配置文件使用了,而是直接在Spring Boot启动类中添加两个Connector,分别支持HTTP和HTTPS。

代码如下:

@Bean    
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory(
            JettyServerCustomizer jettyServerCustomizer) {
        JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
        factory.addServerCustomizers(jettyServerCustomizer);
        return factory;
    }


@Bean
public JettyServerCustomizer jettyServerCustomizer() {
        return server -> {
            // Tweak the connection config used by Jetty to handle incoming HTTP
            // connections
            final QueuedThreadPool threadPool = server.getBean(QueuedThreadPool.class);
            threadPool.setMaxThreads(500);
            threadPool.setMinThreads(50);

            List<Connector> connectors = new ArrayList<>();

            //创建HTTP Connector
            ServerConnector connector = new ServerConnector(server);
            //HTTP port
            connector.setPort(ServerSetting.getPort());
            connectors.add(connector);

            if (ServerSetting.getApiType().equals(ApiTypeEnum.SERVICE.getValue()) && ServerSetting.getPkiServerRun()) {
                // 创建HTTPS Connector,以下各参数和单机tomcat中各参数含义相同
                SslContextFactory sslContextFactory = new SslContextFactory();
                sslContextFactory.setKeyStorePath(ServerSetting.getKeystoreFilePath());
            sslContextFactory.setKeyStorePassword(ServerSetting.getKeystorePassword());
                sslContextFactory.setWantClientAuth(ServerSetting.getWantClientAuth());

                HttpConfiguration config = new HttpConfiguration();
                config.setSecureScheme(HttpScheme.HTTPS.asString());
                config.addCustomizer(new SecureRequestCustomizer());

                ServerConnector sslConnector = new ServerConnector(
                        server,
                        new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
                        new HttpConnectionFactory(config));
                // HTTPS port
                sslConnector.setPort(ServerSetting.getHttpsPort());
                connectors.add(sslConnector);
            }

            server.setConnectors(connectors.toArray(new Connector[]{}));

        };
    }

最后说一个小问题,Spring Boot启动时会先读取 application.properties中的端口server.port及协议配置,如果没有,则默认以HTTP 8080端口启动,这意味着上面的Jetty容器的配置方式在启动时日志会提示以8080端口初始化,日志如下:

2019-01-04 12:14:37 CST INFO  [main] org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory.getEmbeddedServletContainer(JettyEmbeddedServletContainerFactory.java:149) - Server initialized with port: 8080

这个是正常的,但如果在代码中进行的相应的端口配置,则会自动覆盖上面的初始化配置。

从如下日志中可以看到代码中设置的端口及协议重新初始化:

2019-01-04 11:42:16 CST INFO  [main] org.eclipse.jetty.server.AbstractConnector.doStart(AbstractConnector.java:266) - Started ServerConnector@5ab0260{HTTP/1.1}{0.0.0.0:8083}
2019-01-04 11:42:16 CST INFO  [main] org.eclipse.jetty.server.AbstractConnector.doStart(AbstractConnector.java:266) - Started ServerConnector@6603902b{SSL-HTTP/1.1}{0.0.0.0:8443}
2019-01-04 11:42:16 CST INFO  [main] org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainer.start(JettyEmbeddedServletContainer.java:121) - Jetty started on port(s) 8083 (http/1.1), 8443 (ssl-http/1.1, http/1.1)

JAVA 代码读取客户端证书信息

在双向认证开启并且服务器信任客户端证书的前提下,在服务端是可以通过代码来读取客户端证书信息的,通过debug可以看出证书信息是保存在request对象中,如下图示例:

此次开发过程中用户信息是保存在证书的CN字段中,具体情况依据证书的不同有区别,但都会保存在这个X509Certificate对象中。

完整代码如下:

    String scheme = request.getScheme();
    if (scheme.equalsIgnoreCase(HttpScheme.HTTP.asString())) {
        response.sendError(401, "当前请求只允许使用HTTPS方式,请联系管理员");
        return;
    }
    X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
    if (certs == null) {
        LOG.info("从request对象中未获取到证书信息");
        response.sendError(401, "证书不存在或者未选择证书!请插入数字证书,重启浏览器后再点击访问!");
        return;
    }
    X509Certificate gaX509Cert = certs[0];
    gaX509Cert.checkValidity(new Date());

    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(gaX509Cert.getEncoded());
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate newGaX509Cert = (X509Certificate) cf.generateCertificate(byteArrayInputStream);
    byteArrayInputStream.close();

    String dn = newGaX509Cert.getSubjectDN().toString();
    LOG.info("证书DN项包括姓名、身份证号码等信息:" + dn);
    String cn = dn.substring(3, dn.indexOf(','));
    String[] nameAndCid = cn.split(" ");
    String name = nameAndCid[0];
    String cid = nameAndCid[1];

参考: 一篇文章看明白 HTTP,HTTPS,SSL/TSL 之间的关系
参考: HTTPS加密协议详解(三):PKI 体系(沃通Wosign证书官网FAQ,链接放不了,有兴趣可以自己直接进官网看)

posted on 2020-07-28 14:04  precedeforetime  阅读(8668)  评论(0编辑  收藏  举报

导航