SSL协议中的服务端认证

  在SSL协议中,客户端软件(下文简称客户端)总是需要对服务端进行认证。比如,浏览器需要对其请求的服务端站点进行验证。在这个过程中,服务端会向客户端发送一个公钥证书。在我的文章《HTTPS协议为什么是安全的》中,对应的是SSL握手协议中的步骤4。那么客户端应该验证哪些内容呢?为什么它要验证这些内容呢?这片文章咱们就来盘一盘服务端认证的那些事。

  为了验证证书中的公钥和持有证书的服务器之间的持有关系,客户端必须要验证以下五个方面:

  证书的有效期证书的颁发机构证书的签名证书的持有者证书撤销列表下图是一个典型的公钥证书的结构,咱们就结合这张图来详细的说明。

  典型的公钥证书的结构客户端必须要验证证书的有效期,以确保当前时间在有效期之内。否则,验证过程将以失败告终。那么,如果不验证有效期会有什么安全问题呢?

  假设客户端收到了一个过期的证书,这代表证书的持有者已经不再维护其中的公钥,进而也不再会花心思保护公钥对应的私钥。这代表私钥泄露的风险是很高的。

  在SSL握手协议中,如果客户端继续使用过期证书中的公钥加密预备主密钥[1]。攻击者就可能用被泄露的私钥解密监听到的加密预备主密钥,进而得到预备主密钥的明文。有了预备主密钥,攻击者就可以计算出后续SSL通信中所使用的一切密钥。这就相当于SSL加密通信被攻破了。

  可见,客户端必须要验证证书的有效期。如果当前时间不在证书的有效期之内,验证过程应该以失败终止。

  客户端必须要验证证书的颁发机构(Certificate Authority,简称CA),以确保证书的颁发机构是可被信任的。通常,客户端会维护一个CA列表,这个列表中包含所有被客户端信任的CA。比如DigiCert、GlobalSign和IdenTrust等,都是知名的值得被信任的证书颁发机构。下表是国际知名的CA列表及其市场份额[2]。

  知名的证书颁发机构及其市场份额,图片引用自Wiki如果服务端证书的颁发机构不在客户端的信任列表中,且服务端证书的证书链上的证书颁发机构也不在客户端的信任列表中,那么客户端的验证过程将以失败告终。验证颁发机构是否可信有什么意义呢?

  假设客户端收到了未知机构颁发的证书,证书中宣称Bob持有公钥PK1。客户端在验证过证书的签名之后,就直接使用PK1进行加密,以为经过PK1加密的预备主密钥就只能被Bob解密。但真实情况是,颁发该证书的未知机构是攻击者专门维护的,PK1并非是Bob持用的公钥,而是攻击者持有的。未知机构只是在颁发证书时将持有者信息设置成了Bob。通过这样的方式,攻击者让客户端误以为使用了接收方的公钥,但实际上使用的却是攻击者的公钥。

  如果客户端使用攻击者的公钥加密预备主密钥[1],那么,攻击者就可以用对应的私钥解密监听到的加密预备主密钥,进而得到预备主密钥的明文。有了预备主密钥,攻击者就可以计算出后续SSL通信中所使用的一切密钥。这就相当于SSL加密通信被攻破了。

  可见,客户端必须要验证证书的颁发机构是可信的CA。如果颁发该证书的CA不可信,验证过程应该以失败终止。

  如果一个证书宣称自己是被某个值得信任的CA(通常是国际知名CA)颁发的,客户端就需要验证其是否属实。比如某个攻击者颁发了一个证书,他使用自建CA去进行签名,却将证书的颁发者设置为GlobalSign机构。显然,这种证书是伪造的。客户端会使用GlobalSign的公钥去验证服务端证书的签名。如果签名验证通过,就说明服务端证书的宣称属实;否则,就说明服务端证书进行了虚假声明,客户端自然就不会信任该证书。

  那么,GlobalSign作为顶级的CA机构,客户端从哪里获取它的公钥呢?这就是根证书机制。有心的小伙伴可能会注意到,操作系统中都会有一个根证书管理的机制。下图是我的Mac电脑中信任的根证书列表:

  Mac电脑上的根证书列表证书是一种链式机制,通常分为三级。自顶向下依次为根证书(Root Certificate)、中间证书(Intermediate Certificate)、用户证书(End-User Certificate)。其中,用户证书由中间CA签发,中间证书由根CA(顶级CA)签发,根证书已经处于最顶层,所以它由根CA自己签发。也就是说,根CA是一种自签名证书(Self-signed Certificate)。

  A self-signed certificate is a certificate signed by the private key, which is the pair of the public key embedded in the certificate. When building a trust chain, this type of certificate can only appear at the root of the chain as it cannot have anything after it (since it is signed by itself).上面是对自签名证书的英文描述,可以帮助读者更好的理解。在自签名证书中,证书持有者是根CA自己,证书颁发者是根CA自己,证书中的公钥是根CA所持有,证书中的签名使用的是证书中的公钥对应的私钥。

  根证书是公钥密码学体系之所以安全的基石。因此,安装根证书一定要慎之又慎。一旦信任了伪造的根证书,构建在公钥密码学之上的安全体系便毫无安全性可言。

  注意:一定要到对应的官方网站去下载根证书,且下载根证书后要校验证书指纹。比如谷歌在这个网站 (https://pki.goog/repository/ ) 对外公布其根证书。用户从这个网站下载根证书后,计算已下载证书的证书指纹,以验证其是否匹配网站上公布的指纹。经过了前三个步骤的验证,说明证书是由合法的CA机构签发且处于有效期内。但这并不代表客户端就可以使用证书中的公钥信息了。客户端还需要验证证书的确是颁发给特定服务端的。即,验证服务端证书的颁发对象是否为当前在访问的站点。

  举个例子,下图是知乎网站的证书信息。可以看到,该证书是DigiCert这个机构颁发给*.http://zhihu.com站点的。客户端正在访问网站http://zhuanlan.zhihu.com,这说明客户端收到的服务端证书的颁发对象确是当前在访问的站点。

  知乎站点的服务端证书如果客户端不去验证证书的颁发对象,或者验证的不仔细会怎么样?假设攻击者通过合法的途径到DigiCert机构申请一个证书,证书的颁发对象叫做*.http://zihu.com(注意区分李逵和李鬼)。然后,在SSL协议的握手阶段,攻击者把服务端返回的合法证书替换成了其自己的“高仿”虚假证书。如果客户端验证不仔细,很可能会信任该虚假证书,进而使用其中的公钥加密预备主密钥。但实际上,经过加密的预备主密钥会被攻击者解密,因为“高仿”虚假证书中的公钥是被攻击者持有(自然私钥也是被攻击者持有)。一旦攻击者解密了预备主密钥,攻击者就可以计算出后续SSL通信中所使用的一切密钥。这就相当于SSL加密信道被攻破了。

  可见,客户端必须要验证证书的持有者信息。如果服务端证书的持有者信息和当前在访问的站点信息不匹配,验证过程应该以失败终止。

  证书撤销列表的英文名称是Certificate Revocation List(简称CRL),CA机构用它来公布被撤销的合法证书。简言之,CRL就是被CA撤销的所有证书的序列号的列表。比如某公司持有一对公私钥,并将公钥提交给CA机构以发布公钥证书。突然有一天,该公司由于运维不当导致私钥泄露了。该公司会立即通知CA机构撤销其证书,然后这个证书就会出现在CRL中。通常情况下,客户端软件会缓存CRL,并检查服务端发来的证书是否存在于CRL中。如果存在,则说明证书已经被撤销,不可以再继续使用。下图是chrome中对CRL的版本说明,参考这个方法。

  除了CRL证书列表,客户端软件还可以选择使用OCSP机制去核对证书的有效性,此处不再赘述。如果客户端不去验证证书撤销列表CRL,或者验证的不仔细会怎么样?CA机构撤销一些证书,代表着这些证书中的公钥对应的私钥已经不安全了。如果客户端不去验证CRL,则很可能会使用一个已经被撤销的证书中的公钥。如果客户端使用这种公钥去加密SSL握手协议中的预备主密钥,攻击者就可以用被泄露的私钥解密监听到的加密的预备主密钥,进而得到原始的预备主密钥。有了预备主密钥,攻击者就可以计算出后续SSL通信中所使用的一切密钥。这就相当于SSL加密信道被攻破了。

  可见,客户端必须要验证服务端证书是否存在于CRL中。如果存在,则验证过程应该以失败终止。

  本文中讲述了SSL的客户端在验证服务端证书时,需要具体验证的内容,及为什么要验证这些内容。通过阅读本文,读者可以对公钥证书机制有一个全面的了解。并且,可以通过这里例子了解整个PKI设置