问题
I am a Windows user. I use Python 3.6.5
and I import this version of OpenSSL OpenSSL 1.0.2k
.
I need to write a script for a python TLS client that I can customize in terms of the supported TLS versions and ciphersuites and other configurations. The client should be able to make connections with self-signed certificates. Therefore, I believe I should use: ssl.SSLContext()
to create my context and not ssl.create_default_context()
.
However, with the following script, I can never get the peer's certificate. Please, provide clear answers with code as otherwise I tried many solutions and looked at previous posts with no hope.
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()
The problem is that I do not receive the peer's certificate when I use ssl.SSLContext()
but when I use ssl.create_default_context()
it is received correctly. However, I need to be able to receive self-signed certificates (i.e. unverified certificates) that's why I have to use ssl.SSLContext()
.
Thanks for the solution posted. But I need to parse the certificate even if it is not verified (self-signed). I trust this certificate and I need its info. I looked at several posts including this one. I did these steps:
- I took the
.pem
content of my server's certificate. - I navigated to:
C:\Python36\Lib\site-packages\certifi
- I opened
cacert.pem
which is placed in the directory (step 2) - I added my server's cert .pem content which starts with:
-----BEGIN CERTIFICATE-----
and ends with-----END CERTIFICATE-----
I get this error:
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
回答1:
After a number of attempts, some failed, some partial successful, I found a way that should work (didn't test it with self signed certificates, though). Also, I wiped out everything from the previous attempts.
There are 2 necessary steps:
Get the server certificate using [Python 3.Docs]: (ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None), which returns it as a PEM encoded string (e.g.: ours - pretty printed):
'-----BEGIN CERTIFICATE-----' 'MIIIPjCCByagAwIBAgIICG/ofYt2G48wDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE` 'BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl' ... 'L2KuOvWZ40sTVCJdWPUMtT9VP7VHfLNTFft/IhR+bUPkr33xjOa0Idq6cL89oufn' '-----END CERTIFICATE-----'
Decode the certificate using (!!!undocumented!!!)
ssl._ssl._test_decode_cert
(present in Python 3 / Python 2)- Due to the fact that
ssl._ssl._test_decode_cert
can only read the certificate from a file, 2 additional steps are needed:- Save the certificate from #1. in a temporary file (before #2., obviously)
- Delete that file when done with it
I would like to emphasize [Python 3.Docs]: SSLSocket.getpeercert(binary_form=False), which contains lots of info (that I missed the last time(s)).
Also, I found out about ssl._ssl._test_decode_cert
, by looking at SSLSocket.getpeercert
implementation ("${PYTHON_SRC_DIR}/Modules/_ssl.c").
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:])
Notes:
- _get_tmp_cert_file_name: generates the temporary file name (located in the same dir as the script) that will store the certificate
- _decode_cert: saves the certificate in the file, then decodes the file and returns the resulting dict
- get_srv_cert_0: gets the certificate form server, then decodes it
- get_srv_cert_1: same thing that get_srv_cert_0 does, but "manually"
- Its advantage is controlling the SSL context creation / manipulation (which I think was the main point of the question)
- main:
- Gets the server certificate using one of the 2 methods above (based on an argument being / not being passed to the script)
- Prints certificate data (your code with some small corrections)
Output:
(py35x64_test) e:\Work\Dev\StackOverflow\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\StackOverflow\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
Check [SO]: How can I decode a SSL certificate using python? (@CristiFati's answer) for the decoding part only.
来源:https://stackoverflow.com/questions/50055935/cant-receive-peer-certificate-in-python-client-using-openssls-ssl-sslcontext