使用Python验证SSL证书
我需要编写一个脚本,通过HTTPS连接到我们公司内部网上的多个站点,并验证它们的SSL证书是否有效;这些证书没有过期,是为正确的地址颁发的,等等。我们对这些网站使用我们自己的内部公司证书颁发机构,因此我们拥有CA的公钥来验证证书 默认情况下,Python在使用HTTPS时只接受并使用SSL证书,因此即使证书无效,诸如urllib2和Twisted之类的Python库也会乐于使用该证书 有没有一个好的库可以让我通过HTTPS连接到一个站点并用这种方式验证它的证书使用Python验证SSL证书,python,https,ssl-certificate,verification,Python,Https,Ssl Certificate,Verification,我需要编写一个脚本,通过HTTPS连接到我们公司内部网上的多个站点,并验证它们的SSL证书是否有效;这些证书没有过期,是为正确的地址颁发的,等等。我们对这些网站使用我们自己的内部公司证书颁发机构,因此我们拥有CA的公钥来验证证书 默认情况下,Python在使用HTTPS时只接受并使用SSL证书,因此即使证书无效,诸如urllib2和Twisted之类的Python库也会乐于使用该证书 有没有一个好的库可以让我通过HTTPS连接到一个站点并用这种方式验证它的证书 如何用Python验证证书?是Op
如何用Python验证证书?是OpenSSL库的接口。它应该提供您所需要的一切。您可以使用Twisted来验证证书。主API是,它可以作为
contextFactory
参数提供给各种函数,如和
不幸的是,Python和Twisted都没有提供实际执行HTTPS验证所需的一堆CA证书,也没有提供HTTPS验证逻辑。由于,您目前还不能完全正确地执行此操作,但由于几乎所有证书都包含一个主题commonName,因此您可以接近主题commonName
下面是一个简单的HTTPS客户端示例实现,它忽略了通配符和subjectAltName扩展,并在大多数Ubuntu发行版中使用“ca证书”包中的证书颁发机构证书。请使用您最喜欢的有效和无效证书站点进行尝试:)
可以。如果您愿意,也可以使用。Chandler桌面客户端,包括证书验证
根据Glyphs注释,似乎M2Crypto在默认情况下比当前使用pyOpenSSL进行的证书验证做得更好,因为M2Crypto也检查subjectAltName字段
我还写了一篇关于如何在Python中使用Mozilla Firefox并与Python SSL解决方案一起使用的博客。做得很好
下面是一个简短的例子。如果有可疑的东西,它会抛出一个pycurl.error
,其中会出现一个包含错误代码和人类可读消息的元组
import pycurl
curl = pycurl.Curl()
curl.setopt(pycurl.CAINFO, "myFineCA.crt")
curl.setopt(pycurl.SSL_VERIFYPEER, 1)
curl.setopt(pycurl.SSL_VERIFYHOST, 2)
curl.setopt(pycurl.URL, "https://internal.stuff/")
curl.perform()
您可能希望配置更多选项,例如结果存储在何处等,但无需将示例与非基本内容混在一起
可能提出的例外情况示例:
(60, 'Peer certificate cannot be authenticated with known CA certificates')
(51, "common name 'CN=something.else.stuff,O=Example Corp,C=SE' does not match 'internal.stuff'")
我发现一些有用的链接是setopt和getinfo的libcurl文档
- 下面是一个演示证书验证的示例脚本:
import httplib
import re
import socket
import sys
import urllib2
import ssl
class InvalidCertificateException(httplib.HTTPException, urllib2.URLError):
def __init__(self, host, cert, reason):
httplib.HTTPException.__init__(self)
self.host = host
self.cert = cert
self.reason = reason
def __str__(self):
return ('Host %s returned an invalid certificate (%s) %s\n' %
(self.host, self.reason, self.cert))
class CertValidatingHTTPSConnection(httplib.HTTPConnection):
default_port = httplib.HTTPS_PORT
def __init__(self, host, port=None, key_file=None, cert_file=None,
ca_certs=None, strict=None, **kwargs):
httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs)
self.key_file = key_file
self.cert_file = cert_file
self.ca_certs = ca_certs
if self.ca_certs:
self.cert_reqs = ssl.CERT_REQUIRED
else:
self.cert_reqs = ssl.CERT_NONE
def _GetValidHostsForCert(self, cert):
if 'subjectAltName' in cert:
return [x[1] for x in cert['subjectAltName']
if x[0].lower() == 'dns']
else:
return [x[0][1] for x in cert['subject']
if x[0][0].lower() == 'commonname']
def _ValidateCertificateHostname(self, cert, hostname):
hosts = self._GetValidHostsForCert(cert)
for host in hosts:
host_re = host.replace('.', '\.').replace('*', '[^.]*')
if re.search('^%s$' % (host_re,), hostname, re.I):
return True
return False
def connect(self):
sock = socket.create_connection((self.host, self.port))
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=self.cert_reqs,
ca_certs=self.ca_certs)
if self.cert_reqs & ssl.CERT_REQUIRED:
cert = self.sock.getpeercert()
hostname = self.host.split(':', 0)[0]
if not self._ValidateCertificateHostname(cert, hostname):
raise InvalidCertificateException(hostname, cert,
'hostname mismatch')
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
def __init__(self, **kwargs):
urllib2.AbstractHTTPHandler.__init__(self)
self._connection_args = kwargs
def https_open(self, req):
def http_class_wrapper(host, **kwargs):
full_kwargs = dict(self._connection_args)
full_kwargs.update(kwargs)
return CertValidatingHTTPSConnection(host, **full_kwargs)
try:
return self.do_open(http_class_wrapper, req)
except urllib2.URLError, e:
if type(e.reason) == ssl.SSLError and e.reason.args[0] == 1:
raise InvalidCertificateException(req.host, '',
e.reason.args[1])
raise
https_request = urllib2.HTTPSHandler.do_request_
if __name__ == "__main__":
if len(sys.argv) != 3:
print "usage: python %s CA_CERT URL" % sys.argv[0]
exit(2)
handler = VerifiedHTTPSHandler(ca_certs = sys.argv[1])
opener = urllib2.build_opener(handler)
print opener.open(sys.argv[2]).read()
我已经在Python包索引中添加了一个发行版,使Python 3.2
ssl
包中的match_hostname()
函数在Python的早期版本中可用
您可以通过以下方式安装它:
pip install backports.ssl_match_hostname
或者,您可以将其作为项目的setup.py
中列出的依赖项。无论哪种方式,都可以这样使用:
from backports.ssl_match_hostname import match_hostname, CertificateError
...
sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv3,
cert_reqs=ssl.CERT_REQUIRED, ca_certs=...)
try:
match_hostname(sslsock.getpeercert(), hostname)
except CertificateError, ce:
...
Jython在默认情况下确实执行证书验证,因此使用标准库模块(如httplib.HTTPSConnection等)和Jython将验证证书并给出失败的例外情况,即身份不匹配、证书过期等 事实上,您必须做一些额外的工作才能让jython表现得像cpython一样,即让jython不验证证书 我写了一篇关于如何在jython上禁用证书检查的博客文章,因为它在测试阶段非常有用,等等 在java和jython上安装完全信任的安全提供程序。
或使用图书馆让您的生活更轻松:
我也遇到了同样的问题,但希望尽量减少对第三方的依赖(因为这个一次性脚本将由许多用户执行)。我的解决方案是包装一个
curl
调用,并确保退出代码是0
。工作起来很有魅力。从2.7.9/3.4.3版开始,Python默认情况下尝试执行证书验证
PEP 467中提出了这一点,值得一读:
这些更改会影响所有相关的stdlib模块(urllib/urllib2、http、httplib)
相关文件:
默认情况下,此类现在执行所有必要的证书和主机名检查。要还原到上一个未验证的行为ssl,可以将\u create\u unverified\u context()传递给context参数
在版本3.4.3中更改:该类现在默认执行所有必要的证书和主机名检查。要还原到上一个未验证的行为ssl,可以将\u create\u unverified\u context()传递给context参数
请注意,新的内置验证基于系统提供的证书数据库。与此相反,该包提供自己的证书包。这两种方法的优缺点将在中讨论。以下代码允许您受益于所有SSL验证检查(例如日期有效性、CA证书链…),但可插入验证步骤除外,例如验证主机名或执行其他附加证书验证步骤
from httplib import HTTPSConnection
import ssl
def create_custom_HTTPSConnection(host):
def verify_cert(cert, host):
# Write your code here
# You can certainly base yourself on ssl.match_hostname
# Raise ssl.CertificateError if verification fails
print 'Host:', host
print 'Peer cert:', cert
class CustomHTTPSConnection(HTTPSConnection, object):
def connect(self):
super(CustomHTTPSConnection, self).connect()
cert = self.sock.getpeercert()
verify_cert(cert, host)
context = ssl.create_default_context()
context.check_hostname = False
return CustomHTTPSConnection(host=host, context=context)
if __name__ == '__main__':
# try expired.badssl.com or self-signed.badssl.com !
conn = create_custom_HTTPSConnection('badssl.com')
conn.request('GET', '/')
conn.getresponse().read()
您对Twisted的评论不正确:Twisted使用pyopenssl,而不是Python的内置SSL支持。虽然默认情况下它不会在HTTP客户端中验证HTTPS证书,但您可以使用“contextFactory”参数来获取页面和下载页面,以构造验证上下文工厂。相比之下,据我所知,无法说服内置的“ssl”模块进行证书验证。使用Python 2.6及更高版本中的ssl模块,您可以编写自己的证书验证器。不是最优的,但是可行的。情况改变了,Python现在默认验证证书。我在下面添加了一个新的答案;如果您使用或从版本14开始。
import requests
requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
from httplib import HTTPSConnection
import ssl
def create_custom_HTTPSConnection(host):
def verify_cert(cert, host):
# Write your code here
# You can certainly base yourself on ssl.match_hostname
# Raise ssl.CertificateError if verification fails
print 'Host:', host
print 'Peer cert:', cert
class CustomHTTPSConnection(HTTPSConnection, object):
def connect(self):
super(CustomHTTPSConnection, self).connect()
cert = self.sock.getpeercert()
verify_cert(cert, host)
context = ssl.create_default_context()
context.check_hostname = False
return CustomHTTPSConnection(host=host, context=context)
if __name__ == '__main__':
# try expired.badssl.com or self-signed.badssl.com !
conn = create_custom_HTTPSConnection('badssl.com')
conn.request('GET', '/')
conn.getresponse().read()