Содержание страницы
ssl – SSL-обёртка для объектов сокетов¶ssl – SSL wrapper for socket objects
Модуль предоставляет доступ к шифрованию и аутентификации партнёра по протоколу TLS (Transport Layer Security – безопасность транспортного уровня, часто называемому «Secure Sockets Layer») для сетевых сокетов как на стороне клиента, так и на стороне сервера. Модуль использует библиотеку OpenSSL. Он доступен на всех современных Unix-системах, Windows, Mac OS X и, вероятно, на других платформах, если на них установлен OpenSSL.
Примечание
Некоторые особенности поведения могут зависеть от платформы, поскольку выполняются вызовы системных сокетных API. Установленная версия OpenSSL также может вызывать различия в поведении.
В этом разделе описаны объекты и функции модуля ssl; более общие сведения о TLS, SSL и сертификатах можно найти в документах, указанных в разделе «См. также» внизу.
Модуль предоставляет класс ssl.SSLSocket, производный от типа socket.socket и представляющий собой обёртку, похожую на сокет, которая также шифрует и расшифровывает данные, передаваемые через сокет, с помощью SSL. Он поддерживает дополнительные методы read() и write(), а также метод getpeercert() для получения сертификата другой стороны соединения и метод cipher() для получения используемого шифра в защищённом соединении.
Функции, константы и исключения¶Functions, Constants, and Exceptions
- exception ssl.SSLError¶
- Вызывается для сигнализации об ошибке в низкоуровневой SSL-реализации. Это указывает на проблему на более высоком уровне шифрования и аутентификации, который накладывается на базовое сетевое соединение. Эта ошибка является подтипом socket.error, который, в свою очередь, является подтипом IOError.
- ssl.wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True)¶
Принимает экземпляр sock класса socket.socket и возвращает экземпляр ssl.SSLSocket, подтип socket.socket, который оборачивает базовый сокет в SSL-контекст. Для клиентских сокетов построение контекста отложено: если базовый сокет ещё не подключён, контекст будет создан после вызова connect() на сокете. Для серверных сокетов, если у сокета нет удалённого подключающегося, он считается слушающим, и серверная SSL-обёртка автоматически применяется к клиентским соединениям, принятым через метод accept(). wrap_socket() может возбуждать SSLError.
Параметры keyfile и certfile задают необязательные файлы, содержащие сертификат для идентификации локальной стороны соединения. Дополнительные сведения о том, как сертификат хранится в файле certfile, см. в обсуждении Сертификатов.
Часто закрытый ключ хранится в том же файле, что и сертификат; в этом случае достаточно передать только параметр certfile. Если закрытый ключ хранится в отдельном файле, необходимо использовать оба параметра. Если закрытый ключ хранится в файле certfile, он должен предшествовать первому сертификату в цепочке сертификатов:
-----BEGIN RSA PRIVATE KEY----- ... (private key in base64 encoding) ... -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- ... (certificate in base64 PEM encoding) ... -----END CERTIFICATE-----
Параметр server_side – логическое значение, указывающее, ожидается ли от данного сокета поведение серверной или клиентской стороны.
Параметр cert_reqs задаёт, требуется ли сертификат от другой стороны соединения и будет ли он проверен, если предоставлен. Он должен принимать одно из трёх значений: CERT_NONE (сертификаты игнорируются), CERT_OPTIONAL (не требуется, но проверяется, если предоставлен) или CERT_REQUIRED (требуется и проверяется). Если значение этого параметра не равно CERT_NONE, то параметр ca_certs должен указывать на файл с сертификатами удостоверяющих центров.
Файл ca_certs содержит набор объединённых сертификатов «удостоверяющих центров», которые используются для проверки сертификатов, переданных с другого конца соединения. Дополнительные сведения о том, как организовать сертификаты в этом файле, см. в обсуждении Сертификатов.
Параметр ssl_version определяет, какая версия протокола SSL будет использоваться. Обычно сервер выбирает конкретную версию протокола, и клиент должен подстраиваться под выбор сервера. Большинство версий несовместимы друг с другом. Если параметр не указан, для клиентской стороны по умолчанию используется SSLv3; для серверной – SSLv23. Эти варианты выбора обеспечивают наибольшую совместимость с другими версиями.
Ниже приведена таблица, показывающая, какие версии на клиенте (сбоку) могут соединяться с какими версиями на сервере (вверху):
клиент / сервер SSLv2 SSLv3 SSLv23 TLSv1 SSLv2 да нет да* нет SSLv3 да да да нет SSLv23 да нет да нет TLSv1 нет нет да да В некоторых старых версиях OpenSSL (например, 0.9.7l на OS X 10.4) клиент SSLv2 не мог подключиться к серверу SSLv23.
Параметр do_handshake_on_connect задаёт, выполнять ли SSL-рукопожатие автоматически после вызова socket.connect(), или же приложение будет вызывать его явно методом SSLSocket.do_handshake(). Явный вызов SSLSocket.do_handshake() даёт программе контроль над блокирующим поведением сокетного ввода-вывода, участвующего в рукопожатии.
Параметр suppress_ragged_eofs задаёт, как метод SSLSocket.read() должен сигнализировать о неожиданном конце файла (EOF) с другого конца соединения. Если указано True (по умолчанию), он возвращает обычный EOF в ответ на неожиданные ошибки EOF от базового сокета; если False, исключения передаются обратно вызывающему.
- ssl.RAND_status()¶
- Возвращает True, если генератор псевдослучайных чисел SSL был инициализирован «достаточным» количеством случайности, и False в противном случае. Для увеличения случайности генератора псевдослучайных чисел можно использовать ssl.RAND_egd() и ssl.RAND_add().
- ssl.RAND_egd(path)¶
Если где-то запущен демон сбора энтропии (EGD), и path – это имя файла сокетного соединения, открытого к нему, то функция прочитает 256 байт случайности из сокета и добавит их в SSL-генератор псевдослучайных чисел, чтобы повысить безопасность создаваемых секретных ключей. Обычно это требуется только в системах, где нет более качественных источников случайности.
См. http://egd.sourceforge.net/ или http://prngd.sourceforge.net/ для получения информации об источниках демонов сбора энтропии.
- ssl.RAND_add(bytes, entropy)¶
- Смешивает переданные bytes с SSL-генератором псевдослучайных чисел. Параметр entropy (число с плавающей точкой) задаёт нижнюю границу энтропии, содержащейся в строке (поэтому всегда можно использовать 0.0). Дополнительную информацию об источниках энтропии см. в RFC 1750.
- ssl.cert_time_to_seconds(timestring)¶
Возвращает число с плавающей точкой, содержащее обычное время в секундах после эпохи, по строке времени, представляющей дату "notBefore" или "notAfter" из сертификата.
Вот пример:
>>> import ssl >>> ssl.cert_time_to_seconds("May 9 00:00:00 2007 GMT") 1178694000.0 >>> import time >>> time.ctime(ssl.cert_time_to_seconds("May 9 00:00:00 2007 GMT")) 'Wed May 9 00:00:00 2007' >>>
- ssl.get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None)¶
- По адресу addr SSL-защищённого сервера, заданному в виде пары (hostname, port-number), извлекает сертификат сервера и возвращает его в виде строки в PEM-кодировке. Если указан ssl_version, используется эта версия SSL-протокола для попытки подключения к серверу. Если указан ca_certs, он должен быть файлом со списком корневых сертификатов в том же формате, что используется для этого же параметра в wrap_socket(). Вызов попытается проверить сертификат сервера по этому набору корневых сертификатов и завершится ошибкой, если проверка не удастся.
- ssl.DER_cert_to_PEM_cert(DER_cert_bytes)¶
- Принимая сертификат в виде набора байтов в кодировке DER, возвращает версию того же сертификата в виде строки в кодировке PEM.
- ssl.PEM_cert_to_DER_cert(PEM_cert_string)¶
- Принимает сертификат в виде ASCII PEM-строки и возвращает последовательность байтов в DER-кодировке для того же сертификата.
- ssl.CERT_NONE¶
- Значение, которое передаётся параметру cert_reqs функции sslobject(), когда от другой стороны сокетного соединения не требуется и не проверяется ни один сертификат.
- ssl.CERT_OPTIONAL¶
- Значение, передаваемое параметру cert_reqs функции sslobject(), когда от другой стороны сокетного соединения сертификаты не требуются, но если они предоставлены, то проверяются. Обратите внимание: использование этого параметра требует, чтобы в параметре ca_certs также был передан корректный файл для проверки сертификатов.
- ssl.CERT_REQUIRED¶
- Значение, передаваемое параметру cert_reqs функции sslobject(), когда от другой стороны сокетного соединения требуются сертификаты. Обратите внимание: использование этого параметра требует, чтобы в параметре ca_certs также был передан корректный файл для проверки сертификатов.
- ssl.PROTOCOL_SSLv2¶
- Выбирает SSL версии 2 в качестве протокола шифрования канала.
- ssl.PROTOCOL_SSLv23¶
- Выбирает SSL версии 2 или 3 в качестве протокола шифрования канала. Этот параметр используется с серверами для максимальной совместимости с другой стороной SSL-соединения, но может привести к выбору шифров довольно низкого качества.
- ssl.PROTOCOL_SSLv3¶
- Выбирает SSL версии 3 в качестве протокола шифрования канала. Для клиентов это максимально совместимый вариант SSL.
- ssl.PROTOCOL_TLSv1¶
- Выбирает TLS версии 1 в качестве протокола шифрования канала. Это самая современная версия и, вероятно, лучший выбор для максимальной защиты, если обе стороны её поддерживают.
Объекты SSLSocket¶SSLSocket Objects
- SSLSocket.read(nbytes=1024, buffer=None)¶
- Читает до nbytes байт из SSL-зашифрованного канала и возвращает их. Если указан buffer, то читает в буфер минимум из размера буфера и nbytes, если он задан. Если буфер не указан, выделяется и возвращается неизменяемый буфер с прочитанными из сокета данными.
- SSLSocket.write(data)¶
- Записывает data на другую сторону соединения, используя SSL-канал для шифрования. Возвращает количество записанных байт.
- SSLSocket.do_handshake()¶
Выполняет начальное рукопожатие SSL. Если сокет неблокирующий, этот метод может возбудить SSLError, причём значение args[0] экземпляра исключения будет равно либо SSL_ERROR_WANT_READ, либо SSL_ERROR_WANT_WRITE, и его следует вызывать снова, пока эти исключения не перестанут возникать. Вот пример того, как это сделать:
while True: try: sock.do_handshake() break except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([sock], [], []) elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: select.select([], [sock], []) else: raise
- SSLSocket.unwrap()¶
- Выполняет завершающее рукопожатие SSL, которое удаляет уровень TLS из нижележащего сокета и возвращает объект нижележащего сокета. Это позволяет перейти от шифрованного режима работы по соединению к незашифрованному. Возвращённый сокет следует всегда использовать для дальнейшего обмена с другой стороной соединения вместо исходного сокета.
- SSLSocket.getpeercert(binary_form=False)¶
Если для удалённой стороны на другом конце соединения нет сертификата, возвращается None.
Если параметр binary_form равен False и от однорангового узла был получен сертификат, этот метод возвращает экземпляр dict. Если сертификат не был проверен, словарь пуст. Если сертификат проверен, возвращается словарь с ключами subject (субъект, для которого выдан сертификат) и notAfter (время, после которого сертификату не следует доверять). Сертификат уже проверен, поэтому поля notBefore и issuer не возвращаются. Если сертификат содержит экземпляр расширения альтернативное имя субъекта (см. RFC 3280), в словаре также будет ключ subjectAltName.
Поле «subject» представляет собой кортеж, содержащий последовательность относительных отличительных имён (RDN), указанных в структуре данных сертификата для субъекта, и каждый RDN является последовательностью пар имя-значение:
{'notAfter': 'Feb 16 16:54:50 2013 GMT', 'subject': ((('countryName', u'US'),), (('stateOrProvinceName', u'Delaware'),), (('localityName', u'Wilmington'),), (('organizationName', u'Python Software Foundation'),), (('organizationalUnitName', u'SSL'),), (('commonName', u'somemachine.python.org'),))}
Если параметр binary_form равен True и сертификат был предоставлен, этот метод возвращает DER-кодированную форму всего сертификата в виде последовательности байтов, либо None, если одноранговый узел не предоставил сертификат. Это возвращаемое значение не зависит от проверки; если проверка требовалась (CERT_OPTIONAL или CERT_REQUIRED), сертификат будет проверен, но если для установки соединения использовалось значение CERT_NONE, сертификат (если он присутствует) не будет проверен.
- SSLSocket.cipher()¶
- Возвращает кортеж из трёх значений, содержащий имя используемого шифра, версию протокола SSL, определяющую его использование, и количество используемых секретных битов. Если соединение не установлено, возвращает None.
- SSLSocket.unwrap()
- Выполняет завершающее рукопожатие SSL, которое удаляет слой TLS из нижележащего сокета и возвращает объект нижележащего сокета. Это можно использовать для перехода от шифрованного взаимодействия по соединению к незашифрованному. Возвращённый сокет всегда следует использовать для дальнейшей связи с другой стороной соединения вместо исходного сокета.
Сертификаты¶Certificates
Сертификаты в целом являются частью системы открытых и закрытых ключей. В этой системе каждому субъекту (которым может быть машина, человек или организация) назначается уникальный двухчастный ключ шифрования. Одна часть ключа является открытой и называется открытым ключом; другая часть хранится в секрете и называется закрытым ключом. Эти две части связаны: если зашифровать сообщение одной частью, его можно расшифровать другой частью, причём только другой частью.
Сертификат содержит информацию о двух субъектах. Он содержит имя субъекта и открытый ключ субъекта. Также он содержит заявление второго субъекта – эмитента, о том, что субъект является тем, за кого себя выдаёт, и что это действительно открытый ключ субъекта. Заявление эмитента подписано закрытым ключом эмитента, который известен только эмитенту. Однако любой может проверить заявление эмитента, найдя открытый ключ эмитента, расшифровав им заявление и сравнив его с другой информацией в сертификате. Сертификат также содержит информацию о периоде времени, в течение которого он действителен. Это выражается двумя полями, называемыми «notBefore» и «notAfter».
При использовании сертификатов в Python клиент или сервер могут использовать сертификат для подтверждения своей подлинности. От другой стороны сетевого соединения также может потребоваться предоставить сертификат, и этот сертификат может быть проверен в соответствии с требованиями клиента или сервера, запрашивающего такую проверку. Попытка соединения может быть настроена на возбуждение исключения в случае неудачи проверки. Проверка выполняется автоматически, нижележащим фреймворком OpenSSL; приложению не нужно вникать в её механику. Но приложение обычно должно предоставлять наборы сертификатов, чтобы этот процесс мог состояться.
Python использует файлы для хранения сертификатов. Они должны быть отформатированы как «PEM» (см. RFC 1422), что представляет собой форму кодирования base-64, обрамлённую строкой заголовка и строкой подвала:
-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----
Файлы Python, содержащие сертификаты, могут содержать последовательность сертификатов, иногда называемую цепочкой сертификатов. Эта цепочка должна начинаться с конкретного сертификата для субъекта, который является клиентом или сервером, затем сертификат эмитента этого сертификата, затем сертификат эмитента этого сертификата и так далее по цепочке до сертификата, который является самоподписанным, то есть сертификатом, имеющим одинаковые субъект и эмитента, иногда называемым корневым сертификатом. Сертификаты должны быть просто объединены в файле сертификата. Например, предположим, у нас есть цепочка из трёх сертификатов: от сертификата нашего сервера до сертификата удостоверяющего центра, подписавшего наш сертификат сервера, до корневого сертификата организации, выпустившей сертификат удостоверяющего центра:
-----BEGIN CERTIFICATE-----
... (certificate for your server)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the certificate for the CA)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the root certificate for the CA's issuer)...
-----END CERTIFICATE-----
Если требуется проверка сертификата другой стороны соединения, необходимо предоставить файл «CA certs», заполненный цепочками сертификатов для каждого эмитента, которому предполагается доверять. Опять же, этот файл просто содержит эти цепочки, объединённые вместе. Для проверки Python будет использовать первую найденную в файле цепочку, которая соответствует. Некоторые «стандартные» корневые сертификаты доступны от различных удостоверяющих центров: CACert.org, Thawte, Verisign, Positive SSL (используется python.org), Equifax и GeoTrust.
В общем случае, если используются SSL3 или TLS1, нет необходимости помещать полную цепочку в файл «CA certs»; достаточно только корневых сертификатов, а удалённый одноранговый узел должен предоставить остальные сертификаты, необходимые для построения цепочки от его сертификата до корневого сертификата. См. RFC 4158 для более подробного обсуждения способов построения цепочек сертификатов.
Если планируется создать сервер, предоставляющий услуги SSL-шифрованного соединения, потребуется получить сертификат для этого сервиса. Существует много способов получить подходящие сертификаты, например, купить его у удостоверяющего центра. Другая распространённая практика – создать самоподписанный сертификат. Самый простой способ сделать это – использовать пакет OpenSSL, примерно следующим образом:
% openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
Generating a 1024 bit RSA private key
.......++++++
.............................++++++
writing new private key to 'cert.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:MyState
Locality Name (eg, city) []:Some City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc.
Organizational Unit Name (eg, section) []:My Group
Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com
Email Address []:ops@myserver.mygroup.myorganization.com
%
Недостаток самоподписанного сертификата в том, что он является собственным корневым сертификатом, и никто другой не будет иметь его в своём кеше известных (и доверенных) корневых сертификатов.
Примеры¶Examples
Проверка поддержки SSL¶Testing for SSL support
Чтобы проверить наличие поддержки SSL в установке Python, пользовательский код должен использовать следующий идиоматический приём:
try:
import ssl
except ImportError:
pass
else:
[ do something that requires SSL support ]
Операции на стороне клиента¶Client-side operation
Этот пример подключается к SSL-серверу, выводит адрес и сертификат сервера, отправляет несколько байтов и читает часть ответа:
import socket, ssl, pprint
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# требовать сертификат от сервера
ssl_sock = ssl.wrap_socket(s,
ca_certs="/etc/ca_certs_file",
cert_reqs=ssl.CERT_REQUIRED)
ssl_sock.connect(('www.verisign.com', 443))
print(repr(ssl_sock.getpeername()))
pprint.pprint(ssl_sock.getpeercert())
print(pprint.pformat(ssl_sock.getpeercert()))
# Установить простой HTTP-запрос – в реальном коде используйте http.client.
ssl_sock.write("""GET / HTTP/1.0\r
Host: www.verisign.com\r\n\r\n""")
# Прочитать порцию данных. Не обязательно
# прочитать все данные, возвращённые сервером.
data = ssl_sock.read()
# закрытие SSLSocket также закроет нижележащий сокет
ssl_sock.close()
По состоянию на 6 сентября 2007 года сертификат, выведенный этой программой, выглядел так:
{'notAfter': 'May 8 23:59:59 2009 GMT',
'subject': ((('serialNumber', u'2497886'),),
(('1.3.6.1.4.1.311.60.2.1.3', u'US'),),
(('1.3.6.1.4.1.311.60.2.1.2', u'Delaware'),),
(('countryName', u'US'),),
(('postalCode', u'94043'),),
(('stateOrProvinceName', u'California'),),
(('localityName', u'Mountain View'),),
(('streetAddress', u'487 East Middlefield Road'),),
(('organizationName', u'VeriSign, Inc.'),),
(('organizationalUnitName',
u'Production Security Services'),),
(('organizationalUnitName',
u'Terms of use at www.verisign.com/rpa (c)06'),),
(('commonName', u'www.verisign.com'),))}
which is a fairly poorly-formed subject field.
Операции на стороне сервера¶Server-side operation
Для работы сервера обычно требуется иметь сертификат сервера и закрытый ключ, каждый в отдельном файле. Нужно открыть сокет, привязать его к порту, вызвать на нём listen(), а затем начать ожидание подключения клиентов:
import socket, ssl
bindsocket = socket.socket()
bindsocket.bind(('myaddr.mydomain.com', 10023))
bindsocket.listen(5)
Когда один из них подключался, необходимо вызвать accept() на сокете, чтобы получить новый сокет с другого конца, и использовать wrap_socket() для создания для него серверного SSL-контекста:
while True:
newsocket, fromaddr = bindsocket.accept()
connstream = ssl.wrap_socket(newsocket,
server_side=True,
certfile="mycertfile",
keyfile="mykeyfile",
ssl_version=ssl.PROTOCOL_TLSv1)
deal_with_client(connstream)
Затем следует читать данные из connstream и что-то с ними делать, пока не завершится работа с клиентом (или клиент не завершит работу):
def deal_with_client(connstream):
data = connstream.read()
# null-данные означают, что клиент завершил работу с нами
while data:
if not do_something(connstream, data):
# будем считать, что do_something возвращает False
# когда мы закончили с клиентом
break
data = connstream.read()
# закончили с клиентом
connstream.close()
И вернуться к ожиданию новых подключений клиентов.
См. также
- Класс socket.socket
- Документация базового класса socket
- Introducing SSL and Certificates using OpenSSL
- Frederick J. Hirsch
- RFC 1422: Улучшение конфиденциальности для интернет-электронной почты: Часть II: Управление ключами на основе сертификатов
- Steve Kent
- RFC 1750: Рекомендации по случайности для безопасности
- D. Eastlake et. al.
- RFC 3280: Инфраструктура открытых ключей Internet X.509 – профиль сертификатов и списков отзыва
- Housley et. al.