无法使用 OpenSSL 的 ssl.SSLContext() 在 Python 客户端中接收对等证书
Can't receive peer certificate in Python client using OpenSSL's ssl.SSLContext()
我是 Windows 用户。我使用 Python 3.6.5
并导入此版本的 OpenSSL OpenSSL 1.0.2k
.
我需要为 python TLS 客户端编写一个脚本,我可以根据支持的 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()
.
感谢您发布的解决方案。但是我需要解析证书,即使它没有经过验证(自签名)。我信任这个证书,我需要它的信息。我看了几个帖子,包括 。我做了这些步骤:
- 我获取了服务器证书的
.pem
内容。
- 我导航到:
C:\Python36\Lib\site-packages\certifi
- 我打开了
cacert.pem
,放在目录中(第2步)
- 我添加了我服务器的 cert .pem 内容,它以
-----BEGIN CERTIFICATE-----
开头并以 -----END CERTIFICATE-----
结尾
我收到这个错误:
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
经过多次尝试,有些失败了,有些部分成功了,我找到了一种应该可行的方法(虽然没有使用自签名证书对其进行测试)。另外,我清除了之前尝试的所有内容。
有 2 个必要步骤:
使用 [Python 3.Docs]: (ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None) 获取服务器证书,returns 它作为 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.Docs]: SSLSocket.getpeercert(binary_form=False),其中包含很多信息(我上次错过了)。
此外,我通过查看 SSLSocket.getpeercert
实现 ("${PYTHON_SRC_DIR}/Modules/_ssl.c" 了解了 ssl._ssl._test_decode_cert
。 =84=]).
code00.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:将证书保存在文件中,然后对文件进行解码,returns得到dict
- get_srv_cert_0: 获取证书表单服务器,然后对其进行解码
- get_srv_cert_1:与 get_srv_cert_0 相同,但 "manually"
- 它的优点是控制 SSL 上下文创建/操作(我认为这是问题的要点)
- 主要:
- 使用上述两种方法之一获取服务器证书(基于传递/未传递给脚本的参数)
- 打印证书数据(您的代码稍作更正)
输出:
(py35x64_test) e:\Work\Dev\Whosebug\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.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\Whosebug\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.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]: How can I decode a SSL certificate using python? (@CristiFati's answer) 解码部分。
这是从任何 url.
中获取证书数据的 9 行代码片段
import ssl
import socket
def getcertmeta(url="",port=443):
hostname = socket.gethostbyaddr(url)[0]
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
return ssock.context.get_ca_certs()
锡纸注意:这个证书有问题。它们是各种问题的根源,但科技公司却对它们着迷。它们没有提供任何安全优势,每个证书颁发机构都被黑客攻击(LetsEncrypt iirc 除外)这是无可辩驳的,可以在 idk 5 分钟内在线查找和找到,还有其他事情正在发生,但它们非常复杂且不必要地复杂,以至于它非常很难弄清楚是什么。在我看来,它们与英特尔 ME 一起用于将服务器数据后门传送到中央位置(例如 wileaks v7 揭示的黑客攻击),我的 phone 上的证书包括 3-4 个政府,直接说是日本政府或中国政府政府和它有一堆英特尔代理公司,比如 'starlight technologies' 在我看来,这一切都非常阴暗。
EDIT2:上面的代码片段是错误的,它只获取证书颁发机构的证书而不是实际的 url,这里是一个 13 行代码片段来获取实际的 url.
import socket,ssl
from contextlib import contextmanager
@contextmanager
def fetch_certificate(url="", port=443, timeout=0.5):
cxt = ssl.create_default_context()
sslctxsock = cxt.wrap_socket(socket.socket(), server_hostname=hostname)
sslctxsock.settimeout(timeout)
sslctxsock.connect((url,port))
cert = sslctxsock.getpeercert()
try:
yield cert
finally:
sslctxsock.close()
这样使用:
with fetch_certificate(url=url) as cert:
print(cert)
当 ssl.CERT_NONE
设置为 SSLContext
的默认值时,Python 似乎没有 parse/store 客户端证书
import socket
import ssl
dest = ("www.google.com", 443)
sock = socket.socket(socket.AF_INET)
ctx = ssl.SSLContext()
# You will either need to add the self-signed certs to the OS cert store (varies by OS)
# See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations for loading non-defaults
ctx.set_default_verify_paths()
ctx.verify_mode = ssl.CERT_REQUIRED
ssock = ctx.wrap_socket(sock, server_hostname=dest[0])
result = ssock.connect(dest)
print(ssock.getpeercert())
见https://github.com/python/cpython/blob/main/Modules/_ssl.c#L1823
它按位执行 &
,因此当验证模式为 CERT_NONE (0
) 时,代码块将被跳过。证书 parsing/populating 需要 CERT_OPTIONAL=1 或 CERT_REQUIRED=2 才能工作(否则它只会设置一个空字典)
对于您的自签名证书,您应该将它们导入 OS 证书颁发机构存储或使用方法 SSLContext.load_verify_locations
加载自签名证书(因为它是自签名的,您可以加载 server/leaf 证书作为 CA 证书)
如果您选择 OS-store 路径,您可能只使用默认上下文。我认为 Windows 上有一个针对每个用户的 CA 存储(虽然不是肯定的)。我认为 Linux 上不存在这样的东西,因此您需要在系统范围内添加它。不确定 macOS 或其他操作系统
我是 Windows 用户。我使用 Python 3.6.5
并导入此版本的 OpenSSL OpenSSL 1.0.2k
.
我需要为 python TLS 客户端编写一个脚本,我可以根据支持的 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()
.
感谢您发布的解决方案。但是我需要解析证书,即使它没有经过验证(自签名)。我信任这个证书,我需要它的信息。我看了几个帖子,包括
- 我获取了服务器证书的
.pem
内容。 - 我导航到:
C:\Python36\Lib\site-packages\certifi
- 我打开了
cacert.pem
,放在目录中(第2步) - 我添加了我服务器的 cert .pem 内容,它以
-----BEGIN CERTIFICATE-----
开头并以-----END CERTIFICATE-----
结尾
我收到这个错误:
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
经过多次尝试,有些失败了,有些部分成功了,我找到了一种应该可行的方法(虽然没有使用自签名证书对其进行测试)。另外,我清除了之前尝试的所有内容。
有 2 个必要步骤:
使用 [Python 3.Docs]: (ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None) 获取服务器证书,returns 它作为 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.Docs]: SSLSocket.getpeercert(binary_form=False),其中包含很多信息(我上次错过了)。
此外,我通过查看 SSLSocket.getpeercert
实现 ("${PYTHON_SRC_DIR}/Modules/_ssl.c" 了解了 ssl._ssl._test_decode_cert
。 =84=]).
code00.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:将证书保存在文件中,然后对文件进行解码,returns得到dict
- get_srv_cert_0: 获取证书表单服务器,然后对其进行解码
- get_srv_cert_1:与 get_srv_cert_0 相同,但 "manually"
- 它的优点是控制 SSL 上下文创建/操作(我认为这是问题的要点)
- 主要:
- 使用上述两种方法之一获取服务器证书(基于传递/未传递给脚本的参数)
- 打印证书数据(您的代码稍作更正)
输出:
(py35x64_test) e:\Work\Dev\Whosebug\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.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\Whosebug\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.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]: How can I decode a SSL certificate using python? (@CristiFati's answer) 解码部分。
这是从任何 url.
中获取证书数据的 9 行代码片段import ssl
import socket
def getcertmeta(url="",port=443):
hostname = socket.gethostbyaddr(url)[0]
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
return ssock.context.get_ca_certs()
锡纸注意:这个证书有问题。它们是各种问题的根源,但科技公司却对它们着迷。它们没有提供任何安全优势,每个证书颁发机构都被黑客攻击(LetsEncrypt iirc 除外)这是无可辩驳的,可以在 idk 5 分钟内在线查找和找到,还有其他事情正在发生,但它们非常复杂且不必要地复杂,以至于它非常很难弄清楚是什么。在我看来,它们与英特尔 ME 一起用于将服务器数据后门传送到中央位置(例如 wileaks v7 揭示的黑客攻击),我的 phone 上的证书包括 3-4 个政府,直接说是日本政府或中国政府政府和它有一堆英特尔代理公司,比如 'starlight technologies' 在我看来,这一切都非常阴暗。
EDIT2:上面的代码片段是错误的,它只获取证书颁发机构的证书而不是实际的 url,这里是一个 13 行代码片段来获取实际的 url.
import socket,ssl
from contextlib import contextmanager
@contextmanager
def fetch_certificate(url="", port=443, timeout=0.5):
cxt = ssl.create_default_context()
sslctxsock = cxt.wrap_socket(socket.socket(), server_hostname=hostname)
sslctxsock.settimeout(timeout)
sslctxsock.connect((url,port))
cert = sslctxsock.getpeercert()
try:
yield cert
finally:
sslctxsock.close()
这样使用:
with fetch_certificate(url=url) as cert:
print(cert)
当 ssl.CERT_NONE
设置为 SSLContext
import socket
import ssl
dest = ("www.google.com", 443)
sock = socket.socket(socket.AF_INET)
ctx = ssl.SSLContext()
# You will either need to add the self-signed certs to the OS cert store (varies by OS)
# See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations for loading non-defaults
ctx.set_default_verify_paths()
ctx.verify_mode = ssl.CERT_REQUIRED
ssock = ctx.wrap_socket(sock, server_hostname=dest[0])
result = ssock.connect(dest)
print(ssock.getpeercert())
见https://github.com/python/cpython/blob/main/Modules/_ssl.c#L1823
它按位执行 &
,因此当验证模式为 CERT_NONE (0
) 时,代码块将被跳过。证书 parsing/populating 需要 CERT_OPTIONAL=1 或 CERT_REQUIRED=2 才能工作(否则它只会设置一个空字典)
对于您的自签名证书,您应该将它们导入 OS 证书颁发机构存储或使用方法 SSLContext.load_verify_locations
加载自签名证书(因为它是自签名的,您可以加载 server/leaf 证书作为 CA 证书)
如果您选择 OS-store 路径,您可能只使用默认上下文。我认为 Windows 上有一个针对每个用户的 CA 存储(虽然不是肯定的)。我认为 Linux 上不存在这样的东西,因此您需要在系统范围内添加它。不确定 macOS 或其他操作系统