Python Urllib和服务器证书验证

Python Urllib和服务器证书验证,python,ssl,ssl-certificate,urllib,Python,Ssl,Ssl Certificate,Urllib,我使用Python2.6并请求Facebook API(https)。我想我的服务可能是中间人攻击的目标。 今天早上,我再次阅读urllib模块文档时发现: 引文: Warning : When opening HTTPS URLs, it is not attempted to validate the server certificate. Use at your own risk! 您是否有提示/url/示例来完成完整的证书验证 感谢您的帮助如果您有受信任的证书颁发机构(CA)文件,您可

我使用Python2.6并请求Facebook API(https)。我想我的服务可能是中间人攻击的目标。 今天早上,我再次阅读urllib模块文档时发现: 引文:

Warning : When opening HTTPS URLs, it is not attempted to validate the server certificate. Use at your own risk!
您是否有提示/url/示例来完成完整的证书验证


感谢您的帮助

如果您有受信任的证书颁发机构(CA)文件,您可以使用Python 2.6和更高版本的
ssl
库来验证证书。下面是一些代码:

import os.path
import ssl
import sys
import urlparse
import urllib

def get_ca_path():
    '''Download the Mozilla CA file cached by the cURL project.

    If you have a trusted CA file from your OS, return the path
    to that instead.
    '''
    cafile_local = 'cacert.pem'
    cafile_remote = 'http://curl.haxx.se/ca/cacert.pem'
    if not os.path.isfile(cafile_local):
        print >> sys.stderr, "Downloading %s from %s" % (
            cafile_local, cafile_remote)
    urllib.urlretrieve(cafile_remote, cafile_local)
    return cafile_local

def check_ssl(hostname, port=443):
    '''Check that an SSL certificate is valid.'''
    print >> sys.stderr, "Validating SSL cert at %s:%d" % (
        hostname, port)

    cafile_local = get_ca_path()
    try:
        server_cert = ssl.get_server_certificate((hostname, port),
            ca_certs=cafile_local)
    except ssl.SSLError:
        print >> sys.stderr, "SSL cert at %s:%d is invalid!" % (
            hostname, port)
        raise 

class CheckedSSLUrlOpener(urllib.FancyURLopener):
    '''A URL opener that checks that SSL certificates are valid

    On SSL error, it will raise ssl.
    '''

    def open(self, fullurl, data = None):
        urlbits = urlparse.urlparse(fullurl)
        if urlbits.scheme == 'https':
            if ':' in urlbits.netloc:
                hostname, port = urlbits.netloc.split(':')
            else:
                hostname = urlbits.netloc
                if urlbits.port is None:
                    port = 443
                else:
                    port = urlbits.port
            check_ssl(hostname, port)
        return urllib.FancyURLopener.open(self, fullurl, data)

# Plain usage - can probably do once per day
check_ssl('www.facebook.com')

# URL Opener
opener = CheckedSSLUrlOpener()
opener.open('https://www.facebook.com/find-friends/browser/')

# Make it the default
urllib._urlopener = opener
urllib.urlopen('https://www.facebook.com/find-friends/browser/')
此代码存在一些危险:

  • 您必须信任cURL项目()中的CA文件,它是Mozilla CA文件的缓存版本。它也通过HTTP,因此存在潜在的MITM攻击。最好将
    get\u ca\u path
    替换为返回本地ca文件的路径,该路径因主机而异
  • 没有尝试查看CA文件是否已更新。最终,根证书将过期或停用,并添加新的根证书。一个好主意是使用cron作业删除缓存的CA文件,以便每天下载一个新的CA文件
  • 每次都检查证书可能太过分了。您可以在每次运行时手动检查一次,或者在运行过程中保留“已知良好”主机的列表。或者,多疑
    您可以创建一个urllib2 opener,它可以使用自定义处理程序为您执行验证。下面的代码是一个使用Python 2.7.3的示例。它假定您已下载到保存脚本的同一文件夹

    #!/usr/bin/env python
    import urllib2
    import httplib
    import ssl
    import socket
    import os
    
    CERT_FILE = os.path.join(os.path.dirname(__file__), 'cacert.pem')
    
    
    class ValidHTTPSConnection(httplib.HTTPConnection):
            "This class allows communication via SSL."
    
            default_port = httplib.HTTPS_PORT
    
            def __init__(self, *args, **kwargs):
                httplib.HTTPConnection.__init__(self, *args, **kwargs)
    
            def connect(self):
                "Connect to a host on a given (SSL) port."
    
                sock = socket.create_connection((self.host, self.port),
                                                self.timeout, self.source_address)
                if self._tunnel_host:
                    self.sock = sock
                    self._tunnel()
                self.sock = ssl.wrap_socket(sock,
                                            ca_certs=CERT_FILE,
                                            cert_reqs=ssl.CERT_REQUIRED)
    
    
    class ValidHTTPSHandler(urllib2.HTTPSHandler):
    
        def https_open(self, req):
                return self.do_open(ValidHTTPSConnection, req)
    
    opener = urllib2.build_opener(ValidHTTPSHandler)
    
    
    def test_access(url):
        print "Acessing", url
        page = opener.open(url)
        print page.info()
        data = page.read()
        print "First 100 bytes:", data[0:100]
        print "Done accesing", url
        print ""
    
    # This should work
    test_access("https://www.google.com")
    
    # Accessing a page with a self signed certificate should not work
    # At the time of writing, the following page uses a self signed certificate
    test_access("https://tidia.ita.br/")
    
    运行此脚本时,您应该会看到如下输出:

    Acessing https://www.google.com
    Date: Mon, 14 Jan 2013 14:19:03 GMT
    Expires: -1
    ...
    
    First 100 bytes: <!doctype html><html itemscope="itemscope" itemtype="http://schema.org/WebPage"><head><meta itemprop
    Done accesing https://www.google.com
    
    Acessing https://tidia.ita.br/
    Traceback (most recent call last):
      File "https_validation.py", line 54, in <module>
        test_access("https://tidia.ita.br/")
      File "https_validation.py", line 42, in test_access
        page = opener.open(url)
      ...
      File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 1177, in do_open
        raise URLError(err)
    urllib2.URLError: <urlopen error [Errno 1] _ssl.c:504: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed>
    
    Acessinghttps://www.google.com
    日期:2013年1月14日星期一14:19:03 GMT
    过期:-1
    ...
    
    前100个字节:您可能对这个问题感兴趣:请参阅您正在使用此代码检查来自的CA列表。该连接不在SSL之上,所以有人可以在该站点中间做个人来发布他们自己的根CAS,相对于这个代码,并为他们自己的证书注册脸谱网或您想验证的任何站点,只要稍微多一些,就不能远程检索CA列表,必须提供本地存储。即使您使用了(通过ssl),您将如何验证这一点?所有有效点。如果证书文件在本地不可用,此代码将从internet下载该文件。如果服务器上安装了浏览器(我通常不安装),则可以在文件系统中找到浏览器的证书文件后使用该文件。当然,除非你开车去山景城,否则你可能也在通过互联网下载你的浏览器。在某个时候,你必须信任某人。你可以信任你的操作系统供应商,比如Ubuntu。他们的isos是通过GPG密钥签署的,GPG密钥是众所周知的,并插入到信任网络中,你可以通过参加本地Ubuntu本地活动并会见签署该密钥的人来轻松验证。从Ubuntu中,您可以得到一个维护良好的已知可信CA证书列表。此外,这会进行两个单独的连接来验证证书。一个聪明的MITM将通过第一个证书,然后再通过第二个证书。
    def_uuuinit_uuu(self,*args,**kwargs):httplib.HTTPConnection。u init_uu(self,*args,**kwargs)
    这东西对我来说似乎没用