OpenSSL SSL:CERTIFCATE_VERIFY_FAILED - python-3.x

I'm trying to issue a get request to a https url which has a restful api that will respond back with an image that I wish to save off. I'm using python 3.5 on windows and the requests lib to make the https request.
I'm on a network in which every user is issued a pcks#12(.p12) cert and must install it in their browser to visit pretty much any of the webpages on the network including the site I'm targeting. Through my browser I am able to access the target website and download the image as desired. However, doing so programmatically has been a challenge.
I keep getting a SSL: CERTIFICATE_VERIFY_FAILED error. I can't copy the whole trace (its on a different computer) but below is a gist of what gets spit out in python console.
ssl.py, line 628, in do_handshake ssl.SSLError [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:645)
During handling of the above exception, another exception occurred:
requests\adapters.py, line 447, in send*
raise SSLError(e, request=request)
requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:645)
Here is my code. I'm converting from .p12 to .pem because I believe that is what the requests library requires.
Password = bytes(r'blahblahblah','utf-8')
P12 = OpenSSL.crypto.load_pkcs12(open("C:/Temp/certs/mypki.p12",'rb').read(),password)
key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYP_PEM, p12.get_privatekey())
mycert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYP_PEM, p12.get_certificate())
#len(get_ca_certificates) return 2 so I know I've got all of them
cert1 = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYP_PEM, p12.get_ca_certificates()[0])
cert2 = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYP_PEM, p12.get_ca_certificates()[1])
At one point I was getting errors saying that I had a key mismatch. This code led me to the discovery that the get_certificate() method returns a certificate not found in the tuple returned by the get_ca_certificate() method. Which was the matching public key to my private key. I'm just including this so people know I got a matching key pair.
k = OpenSSl.crypto.Load_privatekey(OpenSSL.crypto.FILETYPE_PEM,key)
c = OpenSSl.crypto.Load_certificate(OpenSSL.crypto.FILETYPE_PEM,mycert)
context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_1_METHOD)
context.use_privatekey(k)
context.use_certificate(c)
try:
context.check_privatekey()
return True
except OpenSSL.SSL.Error:
return FALSE
I've decided to create a .pem file with my key and certs all in one file. I've also tried separating the key from certs in different files but get the same error when doing so. I'm not 100% clear on the differences between .pem,.crt, etc and when to use which but I think I'm doing it correctly...?
pem_file = open("C:/Temp/Certs/cert.pem","wb")
pem_file.write(key)
pem_file.write(mycert)
pem_file.write(cert1)
pem_file.write(cert2)
pem_file.close()
response = request.get("https://somewebsite.com",proxies={"http": None,
"https:": None}, cert=("C:/Temp/Certs/cert.pem"))
For the sake of completeness this is the code I used when I tried writing out the key and certs to separate files.
response = request.get("https://somewebsite.com",proxies={"http": None,
"https:": None}, cert=("C:/Temp/Certs/cert.crt" , "C:/Temp/Certs/key.key"))

So the issue ended up being I was using the wrong pcks#12 file. I was given a digital_signature.p12 and a encryption_key.p12 (or something like that) and the website was expecting the private key and certs from the digital_signature.p12 instead of the other one I was using.

Related

Specify SNI server_hostname when performing request with asyncio/aiohttp

Hello fellow developers ! I'm stuck in a corner case and I'm starting to be out of hairs to pull... Here is the plot :
load-balancer.example.com:443 (TCP passthrough)
/\
/ \
/ \
/ \
s1.example.com:443 s2.example.com:443
(SSL/SNI) (SSL/SNI)
The goal is to stress-test the upstreams s1 and s2 directly using aiohttp with certificate-validation enable. Since the load-balancer does not belong to me I don't want to do the stress-test over it.
the code is not supposed to run on other platforms than GNU Linux with at least Python-v3.7 (but I can use any recent version if needed)
all servers serve a valid certificate for load-balancer.example.com
openssl validates the certificate from the upstreams when using openssl s_connect s1.example.com:443 -servername load-balancer.example.com
cURL needs curl 'https://load-balancer.example.com/' --resolve s1.example.com:443:load-balancer.example.com and also validates successfully
I am able to launch a huge batch of async ClientSession.get requests on both upstreams in parallel but for each request I need to somehow tell asyncio or aiohttp to use load-balancer.example.com as server_hostname, otherwise the SSL handshake fails.
Is there an easy way to setup the ClientSession to use a specific server_hostname when setting up the SSL socket ?
Does someone have already done something like that ?
EDIT : here is the most simple snippet with just a single request :
import aiohttp
import asyncio
async def main_async(host, port, uri, params=[], headers={}, sni_hostname=None):
if sni_hostname is not None:
print('Setting SNI server_name field ')
#
# THIS IS WHERE I DON'T KNOW HOW TO TELL aiohttp
# TO SET THE server_name FIELD TO sni_hostname
# IN THE SSL SOCKET BEFORE PERFORMING THE SSL HANDSHAKE
#
try:
async with aiohttp.ClientSession(raise_for_status=True) as session:
async with session.get(f'https://{host}:{port}/{uri}', params=params, headers=headers) as r:
body = await r.read()
print(body)
except Exception as e:
print(f'Exception while requesting ({e}) ')
if __name__ == "__main__":
asyncio.run(main_async(host='s1.example.com', port=443,
uri='/api/some/endpoint',
params={'apikey': '0123456789'},
headers={'Host': 'load-balancer.example.com'},
sni_hostname='load-balancer.example.com'))
When running it with real hosts, it throws
Cannot connect to host s1.example.com:443 ssl:True
[SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] '
certificate verify failed: certificate has expired (_ssl.c:1131)')])
Note that the error certificate has expired indicates that the certificate proposed to the client is the default certificate since the SNI hostname is s1.example.com which is unknow by the webserver running there.
When running it against the load-balancer it works just fine, the SSL handshake happens with the upstreams which serve the certificate and everything is valid.
Also note that
sni_callback does not help since it is called after the handshake has started and the certificate was received (and at this point server_hostname is a read-only property anyway)
it does not seem to be possible to set server_hostname when creating an SSLContext allthough SSLContext.wrap_socket does support server_hostname but I was not able to make that work
I hope someone knows how to fill the comment block in that snippet ;-]

Python requests module SSLError(CertificateError("hostname 'x.x.x.x' doesn't match 'x.x.x.x")))

I'm using the "requests" module for getting JSON from my web service, using the next code:
import requests
import SSL
# With or without this line of code below, the output is the same
ssl.match_hostname = lambda cert, hostname: True
response = requests.get("MY_URL", cert=("client.pem", "client-key.pem"), verify="CAcert.cer")
When the SSL step seems to fail with the following message:
HTTPSConnectionPool(host='x.x.x.x', port=443): Max retries exceeded with url: {WEBSERVICE_URL_PATTERN} (Caused by SSLError(CertificateError("hostname 'x.x.x.x' doesn't match 'x.x.x.x'")))
I'm using Python 3.10.5 with the latest version of the "requests" module.
Does anyone know what could cause this kind of error and how to fix it?
I assume you've redacted actual names which are in fact different, because if you really did have a host named x.x.x.x using a cert with the same name it would match (unless it wasn't really the same because the CA, or a potentially-bogus 'subject'/'subscriber', used lookalike characters).
From the documentation of match_hostname
Changed in version 3.7: The function is no longer used to TLS connections. Hostname matching is now performed by OpenSSL. ...
Deprecated since version 3.7.
At the python.ssl level, or http.client or urllib.requests, you can still turn off only hostname checking with check_hostname=False in the SSLContext. However AFAIK requests doesn't give you access to the SSL level except for setting the cert(s) as you do or the sledgehammer option of turning off all verification with verify=False.
If at all possible, you should try to use a hostname and a host cert that do match. Note changing either the name you request or the cert can accomplish this.
The problem was solved, using a Subject Alternative Name (SAN) for the server, with a value of its own IP address.
I've found out that we use Simple-CA, and the request of getting a signed certificate from it was with a Common Name (CN), when we don't have a domain name.
After changing the signing action to SAN instead of CN, the problem was solved.
Thanks for the helpers!

Python - [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)

I have an Amazon linux 2 VM and I am making a python requests from this VM. For making the request I am using self signed certificate.
I have appended the self signed certificate file content to the file "/etc/pki/tls/certs/ca-bundle.crt".
The CURL command works fine, however when making requests using python's requests method it throws below error.
ERROR : (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)'))
I tried using "verify" parameter for Python requests method by providing path of both "MyAppcert.crt" as well as "ca_bundle.crt" files, however both approach fails.
import requests
requests.get("https://<my-endpoint>:8888/", verify="/home/ec2-user/ssl_cert/MyAppcert.crt")
This same use case works fine on Windows server.
Any help will be appreciated.
Regards,
Rahul Kumbhar
#SteffenUllrich Thank you for the response. I verified my certificate using "openssl x509 -in file.pem -text" and found that "keyUsage = Certificate Sign" was missing. After creating new certificate with "keyUsage = Certificate Sign" the issue was resolved.
you need to fake the SSL when you send the socket.
Try this in your code:
import ssl
# Ignore SSL certificate errors
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
or
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
Both working very well for me, for example - https://twitter.com/bro_dev_/status/1447598426120720384?s=20 I have run this code today and it worked.
from:
https://github.com/webprice/python-twitter-examples/blob/f8ad6f69f423afdcbd83d89cc7e17e2f61d92ed4/bs4_SSL

Download documents from https site Error: sslv3 alert handshake failure

Anaconda - Python 3.6
OpenSSL 1.0.2
Operating System: Windows 7
Phase 1 (Completed): Using selenium: launched, navigated, and extracted various data elements including a table from site. Extracted Hyperlinks contained in table that are direct links to documents.
Phase 2: Taking extracted hyperlink from table I need to download the files to a specified folder on the shared drive.
Tried:
import urllib.request
url = 'tts website/test.doc'
urllib.request.urlretrieve(url,'C:\Users\User\Desktop\')
Error I get is sslv3 alert handshake failure
With the site opened, I have clicked on the Lock icon and clicked "Install Certificate". I have saved the certificate to my "Trusted Root Certification Authorities" in the Certificate store.
I can see the certificate name (when i installed certificate) from the above step in the 58 CA Certificates shown by running the following code:
import socket
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.verify_mode = ssl.CERT_REQUIRED
context.load_default_certs()
ca_certs = context.get_ca_certs()
print('{} CA Certificates loaded: ' .format(len(ca_certs)))
for cert_dict in ca_certs:
print(cert_dict['subject'])
print()
I can't figure out how to secure a SSL connection to the website/server in order to download the file from each of the hyperlinks?? This website uses Single Sign On(SSO) and automatically logins me in when I first launch the website.
I have tried to use server server.net 443 to connect to server, but can't seem to get the scripting right to connect and retrieve the document.
I have connected directly to the server and abstracted the certificate details shown here:
HOST, PORT = server.net, 443
ctx = ssl.create_default_context()
s = ctx.wrap_socket(socket.socket(), server_hostname=HOST)
c.connect((HOST, PORT))
cert = s.getpeercert()
print(cert)
When i run urlretrieve i am still getting the same error: handshake. When reviewing my ca certificates i see there is a Personal certificate for my Windows login (username) listed there, that must be how it is automatically logging me in using SSO. How do i take all of this information, connect to the website using my SSO, and retrieve the documents?
Latest UPDATE:
I am finding pycurl to be promising, however I feel like I need a little assistance making a few tweaks to get it working.
import pycurl
fp = open('Test.doc','wb')
curl = pycurl.Curl()
curl.setopt(pycurl.URL, url) # see url link to go to word doc
curl.setopt(pycurl.FOLLOWLOCATION, 1)
curl.setopt(pycurl.MAXREDIRS, 5)
curl.setopt(pycurl.CONNECTTIMEOUT,30)
curl.setopt(pycurl.TIMEOUT, 300)
try:
curl.setopt(pycurl.WRITEDATA, fp)
curl.perform()
except:
import traceback
traceback.print_exc(file=sys.stderr)
sys.stderr.flush()
curl.close()
fp.close()
This code yields no error, however the created word doc contains an error displaying a print screen of the log on page of the website.
Main Problem: HTTPS connection using Single Signon connection behind a corporate network proxy server.
I have been trying to get this to work to validate cacert, but I have been getting this error message now:
curl.setopt(pycurl.SSL_VERIFYPEER, 1)
curl.setopt(pycurl.SSL_VERIFYPEER, 2)
curl.setopt(pycurl.CAINFO, certifi.where())
but now i am getting ERROR: 51, CERT_TRUST_IS_UNTRUSTED_ROOT
How do i add proxy if that is causing the error? and Secondly, how do i attach the ca certificate file directly?

How to protect data protection key files with a certificate on Asp.Net Core 2 on debian/linux

I'm trying to configuring data protection and to use the certificate to protect key files. Here is the MS documentation Configuring data protection
Here is what I'm trying to do:
services
.AddDataProtection()
.SetApplicationName("test server")
.PersistKeysToFileSystem("/home/www-data/config")
.ProtectKeysWithCertificate(
new X509Certificate2("/home/www-data/config/"keyprotection.pfx);
When I launch the application I get the following error on startup:
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
Creating key {71e2c23f-448b-49c9-984f-3c8d7227c904} with
creation date 2017-08-29 18:53:51Z, activation date 2017-08-29 18:53:51Z, and expiration date 2017-11-27 18:53:51Z.
info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
Writing data to file '/home/www-data/config/key-71e2c23f-448b-49c9-984f-3c8d7227c904.xml'.
fail: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[24]
An exception occurred while processing the key element '<key id="71e2c23f-448b-49c9-984f-3c8d7227c904" version="1" />'.
System.Security.Cryptography.CryptographicException: Unable to retrieve the decryption key.
at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[12]
Key {71e2c23f-448b-49c9-984f-3c8d7227c904} is ineligible to be the default key because its CreateEncryptor method failed.
System.Security.Cryptography.CryptographicException: Unable to retrieve the decryption key.
at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
at Microsoft.AspNetCore.DataProtection.KeyManagement.DeferredKey.<>c__DisplayClass1_0.<GetLazyDescriptorDelegate>b__0()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.get_Descriptor()
at Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CreateEncryptorInstance(IKey key)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.CreateEncryptor()
at Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver.CanCreateAuthenticatedEncryptor(IKey key)
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[12]
Key {71e2c23f-448b-49c9-984f-3c8d7227c904} is ineligible to be the default key because its CreateEncryptor method failed.
System.Security.Cryptography.CryptographicException: Unable to retrieve the decryption key.
at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
at Microsoft.AspNetCore.DataProtection.KeyManagement.DeferredKey.<>c__DisplayClass1_0.<GetLazyDescriptorDelegate>b__0()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
So the key is created and well encrypted. But it seems that somehow it doesn't know how to decrypt it as it says in the error:
System.Security.Cryptography.CryptographicException:
Unable to retrieve the decryption key.
If I understand it correctly, it uses the certificate I provided to encrypt the key. But it looks like it doesn't use the same cert for the decryption for some reason (It looks like it tries to retreive it from somewhere else [store?]).
What is going wrong ?
I also tried to put the cert into CA store as described here:
Create a Self-Signed Certificate and trust it on Ubuntu Linux
Then I tried to find them back from the code like this:
var cert = new CertificateResolver().ResolveCertificate(CertThumbprint);
But it didn't work (it cannot find it).
I also tried tried to find them using the following approach:
var store = new X509Store(StoreName.CertificateAuthority,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var collection = store.Certificates.Find(
X509FindType.FindByThumbprint,
CertThumbprint, false);
store.Close();
var x509Cert = collection.Count > 0 ? collection[0] : null;
But it didn't work neither.
So what is the right way ?
For reasons known only to Microsoft, the ProtectKeysWithCertificate overrides that accept actual certificates (PFX files or X509Certificate2 objects) are only able to encrypt DPAPI data. Decryption only works if the same certificate is stored in the machine's certificate store, which makes those overrides relatively pointless.
Why? Who knows. It isn't particularly useful information, but it's vaguely dismissed here as a "limitation of the underlying framework".
In this related discussion (which was just closed without any Microsoft assistance or engagement at all), a user shares custom persistence classes which aren't affected this mysterious "limitation." GitHub repo linked below, I know this is an old question, but maybe it'll help someone else.
https://github.com/tillig/DataProtection
Update: This will be fixed in the upcoming Core 2.1.0 release:
https://github.com/aspnet/Home/issues/2759#issuecomment-367157751

Resources