После нескольких попыток, некоторые из которых были неудачными, некоторые частично успешными, я нашел способ, который должен работать (хотя и не проверял самозаверяющие сертификаты). Кроме того, я уничтожил все с предыдущих попыток.
Есть 2 необходимых шага:
Получить сертификат сервера, используя [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-----'
Расшифруйте сертификат, используя ( !!! недокументированное !!! ) ssl._ssl._test_decode_cert
(присутствует в Python 3 / Python 2 )
- В связи с тем, что
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) только для части декодирования.