SSL in Python: Why it doesn't send certificate to server? - python

I'm writing some software that is supposed to acquire information from SSL-secured web page. Below there's piece of code I use to connect to server.
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = ssl.wrap_socket (
s,
ca_certs = '/home/stilz/Desktop/Certyfikaty/GLOWNE_CA.cer',
cert_reqs = ssl.CERT_REQUIRED,
ssl_version = ssl.PROTOCOL_SSLv3,
)
ssl_sock.connect ((HOST, PORT))
However, this doesn't work. It throws exception with message "Handshake alert failed". I captured some TCP packets that come out of my script and also corresponding packets from Internet Explorer and figured out that my script doesn't send certificate at all (server returns something like "Fatal: no certificate supplied") while IE sends it normally. As far as I know file ca.cer is x509 certificate (beginning with "-----BEGIN CERTIFICATE-----").
Please help me and tell what I'm doing wrong. If I've supplied to few information, please let me know.
Regards

First of all, you need to determine if you need to authenticate yourself on the server (certificate is sent to the server only in this case) or you need to validate server's authenticity (in which case the certificate is not sent to the server).
Now about issues in your code:
Case 1: you don't need to authenticate on the server. In this case you don't need to specify your certificate and nothing is sent to the server.
Case 2: you need to authenticate yourself on the server. In that case you need to provide your certificate and your private key file in keyfile and certfile parameters. Only then your certificate is sent to the server (and the private key is used in the handshake).
I guess that your have case 1 in fact. So first of all you need to check if the server provides a valid certificate by connecting to it with a web browser and inspecting site's certificate chain. It can happen that the site sends its certificate but omits intermediate CA certificates.
In any case I'd like to remind about the discussion about certificates in Python docs which I suggest you re-read.

If the server requests (and requires) a certificate to be sent by the client, you need to supply ssl.wrap_socket with the path to your certificate (certfile) and its matching private key (keyfile). The ca_certs parameter is only used for the client to verify the server's certificate against known CA certificates, it has nothing to do with sending a client-certificate.
You may need to export your client-certificate and private key from IE (and then convert them to PEM format).

Related

How to detect if a server is using SNI for HTTPS with python

I'm trying to detect if a server is using the Server Name Indication SSL extension for its HTTPS certificate on a website.
Reproducing this command line with python
openssl s_client -servername www.SERVERNAME.com -tlsextdebug -connect www.YOURSERVER.com:443
Python :
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=sni) as sslsock:
Code below return error
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'sni'. (_ssl.c:992)
But openssl return the certificate correct 💯
Unfortunately im not that experienced in python
Thanks!
with context.wrap_socket(sock, server_hostname=sni) as sslsock:
Check with server_hostname set to the real server name (to use SNI) and to None (to not use SNI). If the results differ (i.e. connection fails, different certificates) then the server requires SNI.To check the certificate use getpeercert.
Depending on the kind of server you might need to disable certificate validation, i.e. set CERT_NONE for the context. While disabling certificate is a bad idea in general, it is acceptable in this case since all you want is to check the servers capabilities and not actually transfer any application data:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
In this case you need to call socket.getpeercert(binary_form=True) since it otherwise will not return information. For comparison this binary form should be sufficient though.

paho-mqtt-python publish single with TLS ca_certs from server

I'm trying to connect to "mqtts://broker.emqx.io:8883" using paho mqtt but getting the invalid certificate error.
tls = { 'ca_certs': "certificate.pem", 'insecure': False, 'tls_version': ssl.PROTOCOL_TLS_CLIENT }
publish.single( topic="testtopic\", payload=payload, hostname="broker.emqx.io", auth={}, port=8883, tls=tls, protocol=mqtt.MQTTv311 )
I don't want it to be insecure and want to get the certificate from the server instead of providing a certificate file manually. It would be a great help if someone could guide me in a right direction.
The ca_certs file should not come from the server, it's suppose to contain the certs for the Certificate Authority chain that signed the certificate used by the broker.
So in this case it should hold1 the certificate chain from:
Sectigo RSA Domain Validation Secure Server CA
USERTrust RSA Certification Authority
AAA Certificate Services
You need to provide the full chain or a file containing all the trusted public CA certs.
1 found with openssl s_client see here

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.

Sending email without keyfile (only certfile) using Python smtplib

Trying to send email with a certificate file using the following script:
import smtplib
client = smtplib.SMTP(myhost, myport)
client.ehlo()
client.starttls(certfile=mycertfile)
client.ehlo()
client.login(myusername, mypassword)
client.sendmail(sender, receiver, Message)
client.quit()
I get the following error:
SSLError: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
I think documentations (smtplib.html and ssl.html) say I need to provide a private key. I only have the certificate file (base64 PEM format). My devops says that a private key is not required in this case because I do not need to identify the local side of the connection.
Question
Is there a way to send the email without providing the private key? If a private key is required, why?
There are two ways to use SSL/TLS: client authenticated and "basic" where the client is unauthenticated. In client authenticated connections, the both the server and the client send a certificate to the other. In "basic" only the server does.
If you pass neither a certificate nor a keyfile, smtplib will use a basic connection, where the client is authenticated.
If you use a certificate, it will be used for a client authenticated connection. In that case, the server will demand that the client shows it owns the certificate by signing a handshake message. For the client to be able to do that it also needs the private key, which can be either in the certificate file or as a separate keyfile.
Long story short, if you want to use a client certificate, you must also use a key. If not, you can just leave both empty.
OTOH, maybe you have a server certificate file or CA list you want to use with the connection?
In that case you need to pass it to ssl.wrap_socket in the ca_certs parameter. Since you use Python 2.6 there's no easy way to do that with smtplib (Python 3.3+ has a context argument to starttls).
How to solve this depends on your application. For example, if you do not need ssl for anything else, a hackish solution would be to monkey-patch ssl.wrap_socket with one that provides your ca_cert (as well as cert_reqs=CERT_REQUIRED, likely).
A more full blown solution would be to extend smtplib.SMTP with your own variant that does allow passing in those parameters.
Here's a monkey-patch taken from this page:
class SMTPExt(smtplib.SMTP):
"""
This class extends smtplib.SMTP and overrides the starttls method
allowing extra parameters and forwarding them to ssl.wrap_socket.
"""
def starttls(self, keyfile=None, certfile=None, **kwargs):
self.ehlo_or_helo_if_needed()
if not self.has_extn("starttls"):
raise SMTPException("STARTTLS extension not supported by server.")
(resp, reply) = self.docmd("STARTTLS")
if resp == 220:
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile, **kwargs)
self.file = SSLFakeFile(self.sock)
# RFC 3207:
# The client MUST discard any knowledge obtained from
# the server, such as the list of SMTP service extensions,
# which was not obtained from the TLS negotiation itself.
self.helo_resp = None
self.ehlo_resp = None
self.esmtp_features = {}
self.does_esmtp = 0
return (resp, reply)
Using the root cert from requests

LDAP Server_Down Error

I'm trying to set up ldap with Python. When I run ./manage.py syncldap I get:
SERVER_DOWN: {'info': 'TLS: hostname does not match CN in peer certificate',
'desc': "Can't contact LDAP server"}
Other people working on the same codebase on their local machines don't seem to be having any problem. Any thoughts?
Check the CN from Subject of server cert
openssl x509 -noout -text -in imsva_cert.pem | grep Subject
Subject: C=en, ST=xx, O=yy, OU=zz, CN=test.com
The "hostname" in the error is to say the host name in your command to access LDAP server, so please just use host test.com, e.g.
ldapsearch -H "ldaps://test.com:636" ...
Also don't forget add test.com in your DNS server to make sure ldapsearch can get the A record for host test.com. Specify the info in /etc/hosts is a easy way.
Your other folk, using the same codebase on their local machines, are they talking to the same LDAP server? The SSL certificate used to enable LDAP over SSL/TLS will have a name embedded in it. (Not editable, you would need to reissue a new cert with a new CN)
The message is saying the Hostname of the server is not the same as the hostname in the certificate (This is why Wildcard certs are so darn useful. No need for a new cert for every box, just use the wildcard and keep the domain the same).
I would examine the certificate, in whatever keystore it is held in, on the server side, and look at the hostname to see the differences..

Categories