Не удается получить сертификат партнера в клиенте Python, используя ssl.SSLContext () OpenSSL - PullRequest
0 голосов
/ 27 апреля 2018

Я пользователь Windows. Я использую Python 3.6.5 и импортирую эту версию OpenSSL OpenSSL 1.0.2k.

Мне нужно написать скрипт для TLS-клиента Python, который я могу настроить в соответствии с поддерживаемыми версиями TLS и комплектами шифров и другими конфигурациями. Клиент должен иметь возможность устанавливать соединения с самозаверяющими сертификатами. Поэтому я считаю, что я должен использовать: ssl.SSLContext() для создания своего контекста, а не ssl.create_default_context().

Однако, используя следующий скрипт, я никогда не смогу получить сертификат партнера. Пожалуйста, предоставьте четкие ответы с кодом, так как в противном случае я пробовал много решений и смотрел предыдущие посты без надежды.

context = ssl.SSLContext() # ssl.create_default_context() 
#context.verify_mode = ssl.CERT_NONE
#context.check_hostname = True
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
domain="google.com"
ssl_sock = context.wrap_socket(s, server_hostname=domain)
ssl_sock.connect((domain, 443))

print("====== peer's certificate ======")
try:
    cert = ssl_sock.getpeercert()
    print ("issued to:", dict(itertools.chain(*cert["subject"]))["commonName"])
    print ("issued by:", dict(itertools.chain(*cert["issuer"]))["commonName"])
    print("issuance date:", cert["notBefore"])
    print("expairy date: ", cert["notAfter"])
    if (cert == None):
        print("no certificate")

except Exception as e:
    print("Error:",e)
ssl_sock.close()

Проблема в том, что я не получаю сертификат партнера, когда использую ssl.SSLContext(), но когда я использую ssl.create_default_context(), он получен правильно. Однако мне нужно иметь возможность получать самозаверяющие сертификаты (то есть непроверенные сертификаты), поэтому я должен использовать ssl.SSLContext().

Спасибо за опубликованное решение. Но мне нужно разобрать сертификат, даже если он не проверен (подписан самостоятельно). Я доверяю этому сертификату и мне нужна его информация. Я просмотрел несколько постов, включая этот . Я сделал эти шаги:

  1. Я взял .pem содержимое сертификата моего сервера.
  2. Я перешел к: C:\Python36\Lib\site-packages\certifi
  3. Я открыл cacert.pem, который находится в каталоге (шаг 2)
  4. Я добавил сертификат моего сервера .pem, который начинается с -----BEGIN CERTIFICATE----- и заканчивается -----END CERTIFICATE-----

Я получаю эту ошибку:

ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)

1 Ответ

0 голосов
/ 27 апреля 2018

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

Есть 2 необходимых шага:

  1. Получить сертификат сервера, используя [Python 3]: (ssl. get_server_certificate ( addr, ssl_version = PROTOCOL_TLS, ca_certs = None ) , который возвращает его в виде PEM строки (например, наша - довольно напечатанная ):

    '-----BEGIN CERTIFICATE-----'
    'MIIIPjCCByagAwIBAgIICG/ofYt2G48wDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE`
    'BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl'
    ...
    'L2KuOvWZ40sTVCJdWPUMtT9VP7VHfLNTFft/IhR+bUPkr33xjOa0Idq6cL89oufn'
    '-----END CERTIFICATE-----'
    
  2. Расшифруйте сертификат, используя ( !!! недокументированное !!! ) ssl._ssl._test_decode_cert (присутствует в Python 3 / Python 2 )

  3. В связи с тем, что ssl._ssl._test_decode_cert может только читать сертификат из файла, необходимо выполнить 2 дополнительных шага:
    • Сохранить сертификат из # 1. во временном файле (до # 2. , очевидно)
    • Удалить этот файл, когда закончите с ним

Я хотел бы подчеркнуть [Python 3]: SSLSocket.getpeercert ( binary_form = False ) , который содержит много информации (которую я пропустил последний раз (ы)).
Кроме того, я узнал о ssl._ssl._test_decode_cert, просмотрев реализацию SSLSocket.getpeercert ( "$ {PYTHON_SRC_DIR} /Modules/_ssl.c" ).

code.py

#!/usr/bin/env python3

import sys
import os
import socket
import ssl
import itertools


def _get_tmp_cert_file_name(host, port):
    return os.path.join(os.path.dirname(os.path.abspath(__file__)), "_".join(("cert", host, str(port), str(os.getpid()), ".crt")))


def _decode_cert(cert_pem, tmp_cert_file_name):
    #print(tmp_cert_file_name)
    with open(tmp_cert_file_name, "w") as fout:
        fout.write(cert_pem)
    try:
        return ssl._ssl._test_decode_cert(tmp_cert_file_name)
    except Exception as e:
        print("Error decoding certificate:", e)
        return dict()
    finally:
        os.unlink(tmp_cert_file_name)


def get_srv_cert_0(host, port=443):
    try:
        cert_pem = ssl.get_server_certificate((host, port))
    except Exception as e:
        print("Error getting certificate:", e)
        return dict()
    tmp_cert_file_name = _get_tmp_cert_file_name(host, port)
    return _decode_cert(cert_pem, tmp_cert_file_name)


def get_srv_cert_1(host, port=443):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    context = ssl.SSLContext()
    ssl_sock = context.wrap_socket(sock, server_hostname=host)
    try:
        ssl_sock.connect((host, port))
    except Exception as e:
        print("Error connecting:\n", e)
        return dict()
    try:
        cert_der = ssl_sock.getpeercert(True)  # NOTE THE ARGUMENT!!!
    except Exception as e:
        print("Error getting cert:\n", e)
        return dict()
    tmp_cert_file_name = _get_tmp_cert_file_name(host, port)
    return _decode_cert(ssl.DER_cert_to_PEM_cert(cert_der), tmp_cert_file_name)


def main(argv):
    domain = "google.com"
    if argv:
        print("Using custom method")
        get_srv_cert_func = get_srv_cert_1
    else:
        print("Using regular method")
        get_srv_cert_func = get_srv_cert_0

    cert = get_srv_cert_func(domain)
    print("====== peer's certificate ======")
    try:
        print("Issued To:", dict(itertools.chain(*cert["subject"]))["commonName"])
        print("Issued By:", dict(itertools.chain(*cert["issuer"]))["commonName"])
        print("Valid From:", cert["notBefore"])
        print("Valid To:", cert["notAfter"])
        if (cert == None):
            print("no certificate")
    except Exception as e:
        print("Error getting certificate:", e)


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main(sys.argv[1:])

Примечания

  • _get_tmp_cert_file_name : генерирует временное имя файла (находится в том же каталоге, что и скрипт), в котором будет храниться сертификат
  • _decode_cert : сохраняет сертификат в файле, затем декодирует файл и возвращает полученный dict
  • get_srv_cert_0 : получает сервер формы сертификата, затем декодирует его
  • get_srv_cert_1 : то же самое, что get_srv_cert_0 , но "вручную"
    • Его преимущество заключается в управлении созданием / манипулированием контекстом SSL (который, я думаю, был основным пунктом вопроса)
  • основной :
    • Получает сертификат сервера, используя один из 2 методов выше (на основе аргумента, который передается / не передается в сценарий)
    • Печатает данные сертификата (ваш код с некоторыми небольшими исправлениями)

выход

(py35x64_test) e:\Work\Dev\StackOverflow\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

Using regular method
====== peer's certificate ======
Issued To: *.google.com
Issued By: Google Internet Authority G2
Valid From: Apr 10 18:58:05 2018 GMT
Valid To: Jul  3 18:33:00 2018 GMT

(py35x64_test) e:\Work\Dev\StackOverflow\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.py 1
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

Using custom method
====== peer's certificate ======
Issued To: *.google.com
Issued By: Google Internet Authority G2
Valid From: Apr 10 18:55:13 2018 GMT
Valid To: Jul  3 18:33:00 2018 GMT

Проверка [SO]: Как я могу декодировать SSL-сертификат, используя python? (@ Ответ CristiFati) только для части декодирования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...