这两天,遇到了一个SSL双向认证的问题,感觉挺有意思,结合以前看到的一个应用例子,这里一起做个总结。
SSL双向认证,就是说服务器可以发送certificate request报文来请求客户端的证书,以验证客户端身份。双向认证要求给每一个用户颁发证书和密钥,用户还需要将证书安装在浏览器上,这样的要求使部署和使用都有点麻烦,因此大部分的https应用都是单向认证的。不过,在一些安全性要求较高的场景,双向认证还是不可或缺的。
那么如何让双向认证的用户体验较好一点?以下就是两个例子。
基于HTTP请求启用SSL双向认证
即便是安全性要求较高,需要使用双向认证,一般也不会将所有的资源都设置成需要双向认证。话说得有点拗口,举个例子来解释,比如一个企业网站,使用https,用户请求一些关键的资源,需要使用双向认证,访问不关键的资源,则不需要双向认证,例如portal页面本身就不需要双向认证。这样除了节省资源以外,还有个好处,就是即便用户的证书有问题,他也可以连接到网站看帮助信息,不会出现用户什么都访问不了,也没人给个提示的情况。
还可以对特定的用户设置双向认证。比如用户要登陆场景,有的用户登陆,只需要用户名和口令即可,而有些用户登陆,除了用户名口令之外,还需要证书。那么这个给用户填用户名口令的页面,应当使用单向认证,然后由服务器根据客户端POST过来的用户名来判断是否要双向认证。
这两种情形在技术上,都是根据请求来决定是否要双向认证。那么做起来也很简单,利用SSL的重协商。浏览器发了一个请求,里边带有要访问的资源,或者用户名,服务器发现需要进行双向认证,就先不着急响应这个HTTP请求,而是发起SSL重协商,并且设置需要客户端认证(使用SSL_do_handshak和SSL_VERIFY_PEER,不细说),重协商完成后,在新的SSL隧道中给出HTTP response。
这个例子生动地展示了网络的分层结构:SSL的重协商,不会对HTTP事务产生影响,让子弹飞一会,咱再握个手先。
当然,这个层面的事情,只有在自己写HTTP服务的时候,才会用到。
Apache的一个用户友好性考虑
这个例子和上边的不同,Apache是别人写好的HTTP服务,使用起来无需关注SSL什么时候握手,请读者也稍稍转变一下思路,这一节我们说的是Apache的一个配置项。
如果服务器发起了双向认证,而此时用户没有证书,或者有多本证书然而他选错了,服务端应该怎么办?两种选择:一种是关掉连接,另一种是给用户提供一个提示页面,指导用户怎么做。
显然是第二种更好。Apache就提供了一个这样的功能,它可以发出证书请求,如果对端没有正确地配合客户端认证,则返回一个特定的页面。这个功能通过将Apache配置文件的SSLVerifyClient 字段设置为“optional”来实现。例如下边的这个配置例子:在客户端认证失败的情况下,返回ssl-client-auth-requied.html这个文件。详细每个字段的含义,网上搜索即可,不细讲。
那么SSL是如何支持这个特性的呢?其实早就安排好了。RFC5246 《TLS 1.2》 7.4.6章节规定:
1、 如果服务器发起一个certificate request,则客户端必须(MUST)回复一个客户端证书消息,自己实在没有证书,也要回一个空的证书消息;
2、 服务器拿到证书消息:
a) 如果证书消息里边没有证书,服务器可以(MAY)选择不进行客户端认证继续握手,或者发送一个handshake failure alert来关闭连接;
b) 如果证书消息里有证书,但是验证不通过,服务器还是可以(MAY)选择不进行客户端认证继续握手,或者发送alert关闭连接。
这里可以看到,RFC给了双方很大的自由度,这使得应用层有灵活处理的空间。估计当初设计协议的时候,就有这个考虑了,很有先见之明啊。
至于Openssl怎么实现这个feature,没有更进一步看,有需要了再说吧。
另外再补充一句,客户端确实发送了证书的情况下,客户端证书验证不通过,服务器可以发送alert关闭连接,这难道不是一种oracle么?安全上是不是有隐患?SSL协议的implementation确实是这么做的么?这个问题没有深入考虑,有缘再说吧。