[Python] 验证服务器 SSL 证书域名匹配

作者 huhamhire,暂无评论,2014年11月23日 19:30 程序实践

前段时间,在 hosts 问答网站上有用户提到了需要在检测服务器时,验证服务器的 SSL 证书。于是,昨天抽出了两个小时时间研究了一下用 Python 如何处理这个问题。

原本打算依旧用 Python 2 来做的,研究了一下文档以后,发现 Python 2 提供的 API 实现起来略坑,竟然起了是时候转战 Python 3 的想法 :-P 毕竟咱也没必要坚守 Python 2 这个 Python 中的 XP。看来以后可以多尝试下写点 Python 3 的内容。

我使用了 Python 3.4 版本,其 ssl 模块提供了 SSLContext 类用于处理 SSL 连接的配置操作,同时还可以对较为底层的 socket 进行封装,可以在较大程度上简化建立 SSL 连接时的配置。

根据官方文当中的相关说明,在加载本地的可信任 CA 证书链之后,便可以很方便的进行对目标服务器的证书域名匹配验证操作。演示程序如下:


 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 import socket
 5 import ssl
 6 
 7 import pprint
 8 
 9 ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
10 ctx.verify_mode = ssl.CERT_REQUIRED
11 ctx.check_hostname = True
12 ctx.load_verify_locations(
13     "./ca-bundle.crt",
14     "./ca-bundle.trust.crt")
15 socket.setdefaulttimeout(10)
16 
17 conn = ctx.wrap_socket(
18     socket.socket(),
19     server_hostname="www.google.com")
20 
21 conn.connect(("74.125.200.197", 443))
22 pprint.pprint(conn.getpeercert())
23 conn.close()

上面给出了一段用于验证 Google 服务器域名与证书匹配情况的演示代码,结构很简单。在初始化 SSLContext 时,指定了默认版本兼容 SSL v2 和 v3(自从上次的漏洞门以后,SSL v3 已经被不少大公司弃用了)。因为需要验证证书有效性,所以还必须对 SSLContext 进行证书验证的配置。通过 SSLContext 的 load_verify_locations 方法,可以引入本地存储的受信 CA 证书链,比如我这里引入的就是 CentOS 自带的两个证书集合文件。

注意 如果不加载本地的可信根证书链的话,Python 将无法完成对服务器证书的解析,以及后续的验证操作。

通过配置 SSLContext.check_hostname = True,则在建立于服务器的 SSL 连接时,会自动检查目标服务器证书与域名的匹配情况。匹配规则遵循 RFC 6125 的 6.4.3 节,不对多个通配符的情况提供支持。

将 SSLContext 对 socket 进行包装可以得到连接对象。由于指定了检查域名的配置,包装过程必须指定待检查域名。

配置完成之后,建立 SSL 连接,相关证书的检查就会自动完成。如遇到证书不匹配的情况,则会抛出 ssl.CertificateError 的异常。

在连接建立后,通过 getpeercert 方法可以得到匹配到的服务器证书数据。数据格式类似下面这样:


 1 {'OCSP': ('http://clients1.google.com/ocsp',),
 2  'caIssuers': ('http://pki.google.com/GIAG2.crt',),
 3  'crlDistributionPoints': ('http://pki.google.com/GIAG2.crl',),
 4  'issuer': ((('countryName', 'US'),),
 5             (('organizationName', 'Google Inc'),),
 6             (('commonName', 'Google Internet Authority G2'),)),
 7  'notAfter': 'Feb  3 00:00:00 2015 GMT',
 8  'notBefore': 'Nov  5 12:22:42 2014 GMT',
 9  'serialNumber': '7C58699CC45A9F5F',
10  'subject': ((('countryName', 'US'),),
11              (('stateOrProvinceName', 'California'),),
12              (('localityName', 'Mountain View'),),
13              (('organizationName', 'Google Inc'),),
14              (('commonName', 'www.google.com'),)),
15  'subjectAltName': (('DNS', 'www.google.com'),),
16  'version': 3}

可见,在 Python 3 中,完成服务器 SSL 证书的验证工作确实很简单。下面再来回头说说 Python 2,前面已经说到了 Python 2 实现起来略坑,不过其实也还好,只是暂时还没有相关的方法可以直接调用而已。在目前的 Python 2.7.8 中应该可以采用类似下面这样的方法,首先解析出服务器的证书信息:


 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 import ssl
 5 import pprint
 6 from socket import socket
 7 
 8 soc = ssl.SSLSocket(socket(),
 9                     ca_certs='./ca-bundle.crt',
10                     cert_reqs=ssl.CERT_REQUIRED)
11 soc.connect(("74.125.200.197", 443))
12 cert = soc.getpeercert()
13 soc.close()
14 
15 pprint.pprint(cert)

在获取到了证书的信息之后,需要域名与证书中允许的域名按规则进行匹配。

其实目前的 Python 2 中,除了没有自带一个比较规范的域名检查工具以外,其他功能还是挺全的。不过有个好消息是,再过两个礼拜,等过了感恩节发布的 Python 2.7.9 中,应该就会增加提供 Python 3 中相关的类以及方法,包括应该会 SSLContext 这样较为实用的工具。至少,从目前已经更新的 Python 2 文档上来看已是如此。

关键词:Python , SSL , 工具 DIY , 网络测试
登录后进行评论