Unable to connect sockets with SSL certifcate signed by CA - python

I have a Socket Server running on Ubuntu 18.04 which I am trying to run with SSL/TLS. First I used a self-signed certificate and the code works fine but now I am trying to use a 90-day free SSL certificate signed by ZeroSSL and it doesn't work. Below are the error messages I received on the server and client side upon connection request.
Server side error: ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:852)
Client side error: ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1076)
Server Code:
import ssl
import socket
IP = "0.0.0.0"
PORT = 2021
server_cert = "/home/ubuntu/chandral/ssl_socket_test/Certificates/Ubuntu/certificate.crt"
#server_cert = "/home/ubuntu/chandral/ssl_socket_test/Certificates/Ubuntu/ca_bundle.crt"
server_key = "/home/ubuntu/chandral/SSL/server.key"
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile=server_cert, keyfile=server_key)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
secure_server_socket = context.wrap_socket(server_socket, server_side=True)
secure_server_socket.bind((IP, PORT))
secure_server_socket.listen(5)
print("listening for connections")
while True:
client_socket, address = secure_server_socket.accept()
print("IP/URL of client:", address)
Client Code:
import ssl
import socket
IP = "192.168.1.1" # IP changed in this post for confidentiality
PORT = 2021
server_sni_hostname = "example.com" # Domain changed in this post for confidentiality
context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
secure_client_socket = context.wrap_socket(client_socket, server_side=False, server_hostname=server_sni_hostname)
secure_client_socket.connect((IP, PORT))
secure_client_socket.send(b"Hello World")

server_cert = "/home/ubuntu/chandral/ssl_socket_test/Certificates/Ubuntu/certificate.crt"
#server_cert = "/home/ubuntu/chandral/ssl_socket_test/Certificates/Ubuntu/ca_bundle.crt"
ZeroSSL provides three files: certificate.crt is the leaf certificate of the server, private.key the private key (server.key in your code) and ca_bundle.crt the intermediate certificates and the root certificates.
A server must provide both the leaf certificate and all chain certificates so that the client can build the trust chain to the local root CA certificate. To create such setup you need to concatenate the contents of certificate.crt with ca_bundle.crt and use the result with context.load_cert_chain(...). Note that it might be necessary to add an explicite newline after the certificate.crt in case this is missing at the end of the file. Note also that the self-signed root CA (the last certificate in ca_bundle.crt) does not need to be included since it will be ignored by the client anyway and instead the local root CA will be used. It is actually recommended to leave this out.

Related

python ssl - how to list CA certificates actually used?

I am trying to list the certificates that Python SSL is actually using to verify a remote server, but clearly don't understand how this works.
The following code works but seems to contradict itself.
A self-signed cert is installed in Windows: Local Computer\Trusted Root Certification Authorities\Certificates and works nicely in Chrome etc., but is not present in the certificate list that ssl has loaded.
import ssl
import socket
from pprint import pprint, pformat
hostname = 'myhost.mydom.tld' # self-signed cert
port = 443
context = ssl.create_default_context()
print(context.verify_mode) # VerifyMode.CERT_REQUIRED
# connect to server and fetch cert (verified to fail on invalid cert)
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
cert = ssock.getpeercert()
pprint(cert) # the expected self-signed cert
print('-' * 60)
# print certs we verified against
pprint(context.get_ca_certs()) # self-signed cert not present
print('-' * 60)
# just make extra sure
for cert in context.get_ca_certs():
if 'MYHOST' in pformat(cert).upper():
pprint(cert) # never reached
So why am I not finding the expected self-signed cert in the list when it is in fact verified?

Python : certificate verify failed: unable to get local issuer certificate when using SSL wrapper

In Python, I'm writing a simple FTP server and client that I want to secure with TLS.
For that, I use the TLS/SSL wrapper offered by Python.
My problem is that I always get the following error at the moment the client runs socket.connect :
Exception has occurred: SSLCertVerificationError [SSL:
CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123) File
"/work/Python/hsireportClient/hsireportClient.py", line 167, in
f_socket.connect(f_server)
This is how I start listening on the server side :
from socketserver import ThreadingTCPServer
from app.mod_report.controlersTCP import tcpRequestHandler
import ssl
...
sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslcontext.load_cert_chain('/work/Python/hoaReport/ssl/hoizey_net.crt','/work/Python/hoaReport/ssl/certificat.key')
f_listen = ('0.0.0.0', 6667) # TODO : Paraméter le port en base de données
f_server=ThreadingTCPServer(f_listen, tcpRequestHandler)
f_server.socket=sslcontext.wrap_socket(f_server.socket, server_side=True)
f_server.serve_forever()
and how I connect to the server on the client side :
from socket import socket, AF_INET, SOCK_STREAM, IPPROTO_TCP, TCP_NODELAY
from ssl import SSLContext, PROTOCOL_TLS_CLIENT, CERT_REQUIRED
from certifi import where as certifi_where
...
# Creates SSL Context
f_context = SSLContext(PROTOCOL_TLS_CLIENT)
f_context.verify_mode=CERT_REQUIRED
f_context.load_verify_locations(cafile=path.relpath(certifi_where()), capath=None, cadata=None)
# Opens the socket and connets to the server
f_socket=socket(AF_INET, SOCK_STREAM)
f_socket=f_context.wrap_socket(f_socket, server_hostname=f_serverHost)
f_serverPort=int(f_serverPort)
f_server=(f_serverHost, f_serverPort)
f_socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
f_socket.setblocking(True)
f_socket.connect(f_server)
My server certificate has been signed by Comodo, and I checked the CA root certificate exists in the file certifi.where() points to.
Of course, everything works fine if I remove SSL wrapper.
I searched a lot about this problem, and I noticed it occurs quite frequently, but I couldn't find a solution, and I have to admit I don't understand what goes wrong.
Please, does anybody can help with this ?
Thanks a lot.
With the help of Steffen Ullrich, I manage to get my certificates orking as expected.
I just had to add all intermediary certificate in my certificate file and that did the job.
Thanks a lot for your help

Python SSL to trust self signed certificates [duplicate]

I am trying to get Certificate issuer information (Common Name), but the code in the link doesn't work with some URLs.
How can i get Certificate issuer information in python?
import ssl, socket
hostname = 'google.com'
ctx = ssl.create_default_context()
s = ctx.wrap_socket(socket.socket(), server_hostname=hostname)
s.connect((hostname, 443))
cert = s.getpeercert()
subject = dict(x[0] for x in cert['subject'])
issued_to = subject['commonName']
>>> issued_to
u'*.google.com'
For example, I tried hostname "cds.ca", it says
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:661)
but I could still get the Common name using Internet Explorer (*.cds.ca)
So I think I should use my own certificate(.cer) instead of using getpeercert(), then how should I change that line?
Or, is there any other way to achieve CN with my own certificate file?
If you just want to get the CN or other certificate details no matter if certificate validation succeeds or not, you have to disable the verification. Unfortunately a simple sock.getpeercert() will by design only return an empty dictionary if certificate validation is disabled. That's why one has to use sock.getpeercert(True) to get the binary representation of the certificate and extract the CN using OpenSSL.crypto from it:
import socket
import ssl
import OpenSSL.crypto as crypto
dst = ('cds.ca',443)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(dst)
# upgrade the socket to SSL without checking the certificate
# !!!! don't transfer any sensitive data over this socket !!!!
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
s = ctx.wrap_socket(s, server_hostname=dst[0])
# get certificate
cert_bin = s.getpeercert(True)
x509 = crypto.load_certificate(crypto.FILETYPE_ASN1,cert_bin)
print("CN=" + x509.get_subject().CN)

How to allow python to trust my server's TLS self-signed certificate: ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

This is not a duplicate for this post. I tried the solutions there and nothing works in my case.
I am using Windows and Python 3.6.5. I have a python script for a TLS client. The server I need to connect to uses a self-signed certificate. When I try to connect to it using my script, I get this error:
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
I need to parse the certificate. I tried to add my server's certificate .pem content to file named: cacert.pem which is in: C:\Python36\Lib\site-packages\certifi and run the program again. Nothing change. Here is my scripot. Please help me to make the client make exception for this server as I trust its certificate.
import socket, ssl
import itertools
context = ssl.SSLContext()
context.verify_mode = ssl.CERT_OPTIONAL
context.check_hostname = False
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
domain="192.168.56.3" # my server
ssl_sock = context.wrap_socket(s, server_hostname=domain)
ssl_sock.connect((domain, 443))
print("====== peer's certificate ======")
try:
cert = ssl_sock.getpeercert()
print(cert)
except SSLError as e:
print("Error: ",e)
ssl_sock.close()
This question has been idle for a while, but in case somebody is still struggling with connecting to a server with a self-signed certificate via Python ssl library:
You can use the load_verify_locations method of SSLContext to specify custom self-signed root certs (see Python Docs for load_verify_locations).
The forementioned code could be extended as follows:
...
context = ssl.SSLContext()
context.verify_mode = ssl.CERT_OPTIONAL
context.check_hostname = False
context.load_verify_locations(cafile='/path/to/your/cacert.pem')
...
Be aware that you also need to include public root certs in case you want to connect to other servers with public certificates with the same client/context. (you could for example append your cacert.pem content to the certifi root certificates and reference that folder / path instead).
See also this Python docs paragraph for more information: client-side operations.

User can't communicate with proxy

I'm implementing an IMAP proxy which securely communicates with a client.However, I have a problem when handshaking.
The code of my proxy is:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((host, port))
ssock, addr = self.sock.accept()
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
self.conn_client = context.wrap_socket(ssock)
And I receive the error:
ssl.SSLError: [SSL: UNEXPECTED_MESSAGE] unexpected message (_ssl.c:833)
The code of my tests is:
M = imaplib.IMAP4_SSL(IP_PROXY)
And I receive the error:
ssl.SSLError: [SSL: UNKNOWN_PROTOCOL] unknown protocol (_ssl.c:777)
However, when the code of the proxy is:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((host, port))
ssock, addr = self.sock.accept()
self.conn_client = ssl.wrap_socket(ssock, certfile=CERT, server_side= True)
It correctly works but I don't want to use certificate.
Thank you
It correctly works but I don't want to use certificate.
SSL/TLS is almost everywhere used with a certificate to make sure that the client is talking to the expected server and not to some man in the middle. If you don't want to use a certificate you need to either use a different kind of authentication (like PSK) or use no authentication at all ("anonymous authentication" - very bad idea).
In any way you would need to set the relevant ciphers to enable this alternative authentication on both client and server. This can be done with the ciphers attribute to wrap_socket on the server side and in your client code it could probably be done by constructed a SSLContext with the necessary ciphers and using the ssl_context argument to specific the context to be used in IMAP4_SSL.
But this is only for your specific Python based IMAP client. Don't expect that you will be able to configure commonly used IMAP clients like Thunderbird or Outlook to be usable with a server without certificates. And like I said, it is a bad idea in the first place.

Categories