Пользовательское средство открытия urllib, использующее клиентские сертификаты.

У меня есть API, с которым мне нужно работать. API защищен HTTPS и использует взаимную аутентификацию/клиентские сертификаты. У меня есть файл PEM и файл CRT.

Когда я регулярно подключаюсь к серверу, используя PyOpenSSL, у меня нет проблем, вот код:

import settings
from OpenSSL import SSL
import socket

def verify(conn, cert, errnum, depth, ok):
    # This obviously has to be updated
    print 'Got certificate: %s' % cert.get_subject()
    return ok

def password_callback(maxlen, verify, extra):
        print (maxlen, verify, extra)
        return settings.DEPOSIT_CODE

context = SSL.Context(SSL.SSLv23_METHOD)
context.set_verify(SSL.VERIFY_NONE, verify)
context.set_passwd_cb(password_callback)
context.use_certificate_file(settings.CLIENT_CERT_FILE)
context.use_privatekey_file(settings.PEM_FILE)

sock = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
sock.connect(("someserver.com",443))

http_get_request = """
GET / HTTP/1.1

"""
sock.write(http_get_request)
print sock.recv(1000)

Но, поскольку это HTTPS API с клиентским сертификатом, я реализовал для него открывалку, как-то измененный код находится здесь:

import settings
import socket
import urllib2

def verify(conn, cert, errnum, depth, ok):
    # This obviously has to be updated
    print 'Got certificate: %s' % cert.get_subject()
    return ok

def password_callback(maxlen, verify, extra):
        print (maxlen, verify, extra)
        return settings.DEPOSIT_CODE

class MyHTTPSConnection(httplib.HTTPSConnection):
    def connect(self):
        context = SSL.Context(SSL.SSLv23_METHOD)
        context.set_passwd_cb(password_callback)
        context.use_certificate_file(settings.CLIENT_CERT_FILE)
        context.set_verify(SSL.VERIFY_NONE, verify)
        context.use_privatekey_file(settings.PEM_FILE)
        self.sock = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))

class MyHTTPSHandler(urllib2.HTTPSHandler):
    def https_open(self,req):
        return self.do_open(MyHTTPSConnection,req)

opener = urllib2.build_opener(urllib2.HTTPHandler,MyCHTTPSHandler)
urllib2.install_opener(opener)

f = urllib2.urlopen("https://sampleapiserver.com")
print f.code

но когда я запускаю второй код, я получаю следующую ошибку:

  File "/usr/lib/python2.6/urllib2.py", line 126, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib/python2.6/urllib2.py", line 391, in open
    response = self._open(req, data)
  File "/usr/lib/python2.6/urllib2.py", line 409, in _open
    '_open', req)
  File "/usr/lib/python2.6/urllib2.py", line 369, in _call_chain
    result = func(*args)
  File "network.py", line 37, in https_open
    return self.do_open(IRNICHTTPSConnection,req)
  File "/usr/lib/python2.6/urllib2.py", line 1142, in do_open
    h.request(req.get_method(), req.get_selector(), req.data, headers)
  File "/usr/lib/python2.6/httplib.py", line 914, in request
    self._send_request(method, url, body, headers)
  File "/usr/lib/python2.6/httplib.py", line 951, in _send_request
    self.endheaders()
  File "/usr/lib/python2.6/httplib.py", line 908, in endheaders
    self._send_output()
  File "/usr/lib/python2.6/httplib.py", line 780, in _send_output
    self.send(msg)
  File "/usr/lib/python2.6/httplib.py", line 759, in send
    self.sock.sendall(str)
OpenSSL.SSL.Error: [('SSL routines', 'SSL_write', 'uninitialized')]

Наконец, я делаю что-то не так? Если нет, помогите понять ошибку...

Ваше здоровье.


person Hosane    schedule 18.04.2011    source источник
comment
Не могли бы вы уточнить ту часть, где вы говорите, что это HTTP API, но хотите избавиться от протокола HTTP?   -  person Keith    schedule 18.04.2011


Ответы (2)


Я не уверен, но мне кажется, что вам не хватает вызова connect() в методе connect():

self.sock.connect(("someserver.com",443))

Также обработка https httplib имеет классы-оболочки для сокета SSL, поэтому, возможно, они необходимы для его работы?

person Douglas Leeder    schedule 18.04.2011
comment
Да, это именно то, что я сделал вчера, и это было исправлено :) - person Hosane; 20.04.2011

Похоже, вы добавляете много сложностей, которые вам на самом деле не нужны. Если вы просто выполняете простую аутентификацию клиентского сертификата, вы, вероятно, можете обойтись без следующего фрагмента (источник) :

import httplib
import urllib2

# HTTPS Client Auth solution for urllib2, inspired by
# http://bugs.python.org/issue3466
# and improved by David Norton of Three Pillar Software. In this
# implementation, we use properties passed in rather than static module
# fields.
class HTTPSClientAuthHandler(urllib2.HTTPSHandler):
    def __init__(self, key, cert):
        urllib2.HTTPSHandler.__init__(self)
        self.key = key
        self.cert = cert
    def https_open(self, req):
        #Rather than pass in a reference to a connection class, we pass in
        # a reference to a function which, for all intents and purposes,
        # will behave as a constructor
        return self.do_open(self.getConnection, req)
    def getConnection(self, host):
        return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert)


cert_handler = HTTPSClientAuthHandler(settings.PEMFILE, settings.CLIENT_CERT_FILE)
opener = urllib2.build_opener(cert_handler)
urllib2.install_opener(opener)

f = urllib2.urlopen("https://sampleapiserver.com")
print f.code

Источник использовался в контексте предоставления средства открытия URL с проверкой подлинности сертификата для конструктора Suds Client, поэтому я удалил это и сделало это прямым открытием.

person jathanism    schedule 18.04.2011
comment
Единственная проблема заключается в том, что вы не можете установить обратный вызов пароля (встроенная в Python поддержка SSL не имела такой возможности до версии 3.3 или около того), поэтому, если ключ клиента защищен паролем, нет способа предотвратить базовой библиотеке SSL от попыток запросить пользователя на терминале. - person Walter Mundt; 13.02.2012