I'm using Python 3.5.2 to create a script to simply check if SSLv3 is enabled on a given host/port. Unfortunately, it seems ssl.SSLContext.wrap_socket() returns the same error
(SSLV3_ALERT_HANDSHAKE_FAILURE)
under the following two conditions:
SSLv3 is disabled on the host or
certificate hostname mismatch or other problems exist.
Is there a way to differentiate these? I don't care about the SNI type problems in the current application. Are there other conditions that will return the same error as well?
Related
I'm trying to figure out, why I'm having a problem that Python code is throwing a SSLCertVerificationError for valid LetsEncrypt certificates on a virtual host with multiple domains and certificates at the same IP If I delete all certificates except one it's fine, but with more than one certificate requests ignores the domain to which Python sent the request and pulls the most recent LetsEncrypt certificate, which is incorrect, causing the domain SSLCertVerificationError.
My understanding was that under SNI (Server Name Indication) requests should only pull the certificate for the domain to which the request is being made, not simply the most recent one. I have checked, and I'm running Python, 3.8, requests 2.5 under a version of Nginx that has been compiled with SNI support. I can suppress the error by turning off SSL validation, but that seems a poor workaround.
Any idea what is going on?
Why does SNI work fine when browsers requests page from Nginx, pullign the proper certificate, but fail under when the same is done under Python's requests package?
I have read everything I can find, and the docs say it should just work under the current builds of nginx, requests,OpenSSL, etc., but it clearly isn't here.
To replicate, I can do requests.get{'https://kedrosky.org') error-free from a local machine. But on scripts run at that server -- a hosted domain -- a newer certificate for the wrong domain is returned, causing an SSLCertVerificationError.
The problem is that the server configuration is likely only properly done for IPv4 even though the domain also resolved to an IPv6 address. With IPv4 it returns the correct certificate:
$ openssl s_client -connect kedrosky.org:443 -4
...
subject=CN = kedrosky.com
But with IPv6 it returns a different certificate (this needs IPv6 connectivity to the internet on your local machine):
$ openssl s_client -connect kedrosky.org:443 -6
...
subject=CN = paulandhoward.com
Likely this is because there is only a listen 443 but not listen [::]:443, the latter needed for IPv6. In this case virtual hosts only properly work for IPv4 but with IPv6 it will just return the default, i.e. usually the first certificate configured.
And the reason that you are seeing different results from different hosts is that one has only IPv4 connectivity while the other can do IPv6 too.
Avoiding trial-and-error, I would like to list which TLS/SSL protocols are available for use on the system (in a cross-platform way).
Python ssl module offers constants such as:
ssl.PROTOCOL_SSLv2
But these do not say anything about what the system actually supports when connecting to a server.
Additionally, is there a way to get from an SSLContext the supported protocols that will be used?
For example SSLv2 and SSLv3 are disabled when using ssl.create_default_contex(), would it be possible to somehow parse the SSLContext.options to list which protocols are supported?
EDIT:
For example on the latest Debian TLSv1 is disabled and connecting to TLSv1-only hosts will fail, yet ssl.PROTOCOL_TLSv1 is still available and also creating ssl.SSLContext(ssl.PROTOCOL_TLSv1) works.
This question already has answers here:
Troubleshooting "ssl certificate verify failed" error
(1 answer)
Python Urllib2 SSL error
(1 answer)
Closed 5 years ago.
Trying to make a Python request to GCM API but certificates aren't verified.
requests and certifi packages are updated
Python 2.7.6
certifi==2017.4.17
requests==2.18.1
pyOpenSSL==17.1.0
Tried to call other servers and return are OK:
r = requests.get('https://graph.facebook.com/spotify') [OK]
r = requests.get('https://graph.facebook.com/spotify', verify=certifi.where()) [OK]
r = requests.get('https://gcm-http.googleapis.com') [NOK]
r = requests.get('https://gcm-http.googleapis.com', verify=certifi.where()) [NOK]
Error message: bad handshake: Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],)
I came across some interesting results when researching this problem. It seems to me that this could be the problem. Let me know.
TL;DR
Try using certifi.old_where(). If that works, then you really should upgrade to a newer version of OpenSSL on your server.
Sources
GitHub:
https://github.com/certifi/python-certifi/issues/32
From #Lukasa
Can you confirm whether or not this is the same problem as #26? That is, try passing certifi.old_where() to the verify argument of requests.
...
To be clear, there is no fix for this from Python-land other than using certifi.old_where() or upgrading OpenSSL. The OpenSSL on your system is too old to properly verify cross-signed TLS certificates, and three is no way for that problem to be resolved on my end. Your system is being put at significant risk if you use certifi.old_where() because you are continuing to base your trust on 1024-bit RSA certificates, which have been being deprecated since 2012 and are subject to several known attacks already.
certifi Docs:
https://pypi.python.org/pypi/certifi
1024-bit Root Certificates
Browsers and certificate authorities have concluded that 1024-bit keys are unacceptably weak for certificates, particularly root certificates. For this reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) certificate from the same CA. Because Mozilla removed these certificates from its bundle, certifi removed them as well.
Unfortunately, old versions of OpenSSL (less than 1.0.2) sometimes fail to validate certificate chains that use the strong roots. For this reason, if you fail to validate a certificate using the certifi.where() mechanism, you can intentionally re-add the 1024-bit roots back into your bundle by calling certifi.old_where() instead. This is not recommended in production: if at all possible you should upgrade to a newer OpenSSL. However, if you have no other option, this may work for you.
I'm attempting to use requests to access a remote server over SSL. Unfortunately it's misconfigured such that it responds with the error TLSV1_UNRECOGNIZED_NAME during the SNI handshake, which is ignored by browsers but raises an exception in requests.
This appears to be the same issue as this question, but in Python rather than Java: SSL handshake alert: unrecognized_name error since upgrade to Java 1.7.0`
The connection works perfectly in Python 2, which doesn't support SNI. How can I disable SNI in Python 3 so that the connection works?
I couldn't find a way to disable SNI on the requests level, but I found a hack that will trick it into thinking SNI isn't supported. Add this code before importing requests (not after, or it will be too late):
import ssl
ssl.HAS_SNI = False
I'm having problem developing a "provider" in APNS. My server is trying to send messages using apns-client, it seems there are no problems occuring while sending messages, but the device isn't receiving any messages at all.
Recently I've changed the *.pem file to a new one. Messages were properly received while using the previous *.pem file, so I'm sure that there are no problems at server connections and sending script (written in Python). The reason is, probably, because the old *.pem file is valid but the new *.pem file is not.
I strongly desire to have an "error" response from the APNS server if the *.pem file is invalid, but it seems that the APNS server or apns-client library isn't returning any error signals even if *.pem file is invalid. I've proved this fact by adding one hundred 'a's to the line before before -----END RSA PRIVATE KEY----- in *.pem, and running the same python script. Yes, it still didn't receive any error messages.
Since APNS server is returning no error messages, it's nearly impossible to check if the *.pem file is valid... Aren't there any methods to check if the *.pem file is valid?
Here's some troubleshooting info suggested by Apple:
Problems Connecting to the Push Service
One possibility is that your server is unable to connect to the push
service. This can mean that you don't have the certificate chain
needed for TLS/SSL to validate the connection to the service. In
addition to the SSL identity (certificate and associated private key)
created by Member Center, you should also install the Entrust CA
(2048) root certificate on your provider. This allows TLS/SSL to
verify the full APNs server cert chain. If you need to get this root
certificate, you can download it from Entrust's site. Also verify that
these identities are installed in the correct location for your
provider and that your provider has permission to read them.
You can test the TLS/SSL handshake using the OpenSSL s_client command,
like this:
$ openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert
YourSSLCertAndPrivateKey.pem -debug -showcerts -CAfile
server-ca-cert.pem
where server-ca-cert.pem is the Entrust CA (2048) root certificate.
Be sure the SSL identity and the hostname are the correct ones for the
push environment you're testing. You can configure your App ID in
Member Center separately for the sandbox and production environment,
and you will be issued a separate identity for each environment.
Using the sandbox SSL identity to try to connect to the production
environment will return an error like this:
CRITICAL | 14:48:40.304061 | Exception creating ssl connection to
Apple: [Errno 1] _ssl.c:480: error:14094414:SSL
routines:SSL3_READ_BYTES:sslv3 alert certificate revoked
To test you PRODUCTION cert, open Terminal and do this:
openssl s_client -connect gateway.push.apple.com:2195 -cert PushProdCer.pem -key PushProdKey.pem
I am not familiar with the python-client you are using but surely there is a way to simply attempt opening a connection with Apple's PNS servers and detecting whether that connection failed or not. If the connection fails, then something is wrong with the PEM file - either the format or the certificate values themselves.
If you want to get an error message that's a little more explicative than "pass or fail," I recommend you look into 3rd party shell scripts that can return some basic information about the PEM file. This thread contains a few sample scripts.
Of course, you can also check for some basic format validations that are widely available. I provided one such example here but there are others.