Urllib и проверка сертификата сервера - PullRequest
14 голосов
/ 11 июля 2011

Я использую Python 2.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 / примеры для полной проверки сертификата?

Спасибо за вашу помощь

Ответы [ 2 ]

9 голосов
/ 14 января 2013

Вы можете создать средство открытия urllib2, которое может выполнить проверку для вас, используя пользовательский обработчик. Следующий код является примером, который работает с Python 2.7.3. Предполагается, что вы загрузили http://curl.haxx.se/ca/cacert.pem в ту же папку, в которой сохранен скрипт.

#!/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>
0 голосов
/ 11 августа 2011

Если у вас есть файл доверенного центра сертификации (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/')

Некоторые опасности с этим кодом:

  1. Вы должны доверять файлу CA из проекта cURL (http://curl.haxx.se/ca/cacert.pem),, который является кэшированной версией файла CA Mozilla. Он также по HTTP, поэтому возможна атака MITM. Лучше заменить get_ca_path с файлом, который возвращает ваш локальный CA-файл, который будет варьироваться от хоста к хосту.
  2. Не делается попытка проверить, был ли обновлен файл CA. В конечном итоге корневые сертификаты истекают или деактивируются, и добавляются новые. Хорошей идеей было бы использовать задание cron для удаления кэшированного файла CA, чтобы ежедневно загружался новый файл.
  3. Возможно, каждый раз проверять сертификаты излишне. Вы можете вручную проверить один раз за прогон или сохранить список «известных хороших» хостов в течение прогона. Или будь параноиком!
...