I can get the standard certificate information for an SSL connection in Python 3.3 via the getpeercert() method on the SSL socket. However, it doesn't seem to provide the chain like OpenSSL's "s_client" tool does.
Is there some way I can get this so that I can see if my IA certificate was configured properly?
s_client command-line:
openssl s_client -connect google.com:443
s_client result (just the first few lines):
$ openssl s_client -connect google.com:443
CONNECTED(00000003)
depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.google.com
i:/C=US/O=Google Inc/CN=Google Internet Authority G2
1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---
Python 3.3 code:
import socket
from ssl import SSLContext # Modern SSL?
from ssl import HAS_SNI # Has SNI?
from pprint import pprint
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ca_certs=None, server_hostname=None,
ssl_version=None):
context = SSLContext(ssl_version)
context.verify_mode = cert_reqs
if ca_certs:
try:
context.load_verify_locations(ca_certs)
# Py32 raises IOError
# Py33 raises FileNotFoundError
except Exception as e: # Reraise as SSLError
raise ssl.SSLError(e)
if certfile:
# FIXME: This block needs a test.
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
return context.wrap_socket(sock, server_hostname=server_hostname)
return context.wrap_socket(sock)
hostname = 'www.google.com'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((hostname, 443))
sslSocket = ssl_wrap_socket(s,
ssl_version=2,
cert_reqs=2,
ca_certs='/usr/local/lib/python3.3/dist-packages/requests/cacert.pem',
server_hostname=hostname)
pprint(sslSocket.getpeercert())
s.close()
Code result:
{'issuer': ((('countryName', 'US'),),
(('organizationName', 'Google Inc'),),
(('commonName', 'Google Internet Authority G2'),)),
'notAfter': 'Sep 25 15:09:31 2014 GMT',
'notBefore': 'Sep 25 15:09:31 2013 GMT',
'serialNumber': '13A87ADB3E733D3B',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'California'),),
(('localityName', 'Mountain View'),),
(('organizationName', 'Google Inc'),),
(('commonName', 'www.google.com'),)),
'subjectAltName': (('DNS', 'www.google.com'),),
'version': 3}
Thanks to the contributing answer by Aleksi, I found a bug/feature request that already requested this very thing: http://bugs.python.org/issue18233. Though the changes haven't been finalized, yet, they do have a patch that makes this available:
This is the test code which I've stolen from some forgotten source and reassembled:
import socket
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
from ssl import SSLContext, SSLError # Modern SSL?
from ssl import HAS_SNI # Has SNI?
from pprint import pprint
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ca_certs=None, server_hostname=None,
ssl_version=None):
context = SSLContext(ssl_version)
context.verify_mode = cert_reqs
if ca_certs:
try:
context.load_verify_locations(ca_certs)
# Py32 raises IOError
# Py33 raises FileNotFoundError
except Exception as e: # Reraise as SSLError
raise SSLError(e)
if certfile:
# FIXME: This block needs a test.
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
return (context, context.wrap_socket(sock, server_hostname=server_hostname))
return (context, context.wrap_socket(sock))
hostname = 'www.google.com'
print("Hostname: %s" % (hostname))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((hostname, 443))
(context, ssl_socket) = ssl_wrap_socket(s,
ssl_version=2,
cert_reqs=2,
ca_certs='/usr/local/lib/python3.3/dist-packages/requests/cacert.pem',
server_hostname=hostname)
pprint(ssl_socket.getpeercertchain())
s.close()
Output:
Hostname: www.google.com
({'issuer': ((('countryName', 'US'),),
(('organizationName', 'Google Inc'),),
(('commonName', 'Google Internet Authority G2'),)),
'notAfter': 'Sep 11 11:04:38 2014 GMT',
'notBefore': 'Sep 11 11:04:38 2013 GMT',
'serialNumber': '50C71E48BCC50676',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'California'),),
(('localityName', 'Mountain View'),),
(('organizationName', 'Google Inc'),),
(('commonName', 'www.google.com'),)),
'subjectAltName': (('DNS', 'www.google.com'),),
'version': 3},
{'issuer': ((('countryName', 'US'),),
(('organizationName', 'GeoTrust Inc.'),),
(('commonName', 'GeoTrust Global CA'),)),
'notAfter': 'Apr 4 15:15:55 2015 GMT',
'notBefore': 'Apr 5 15:15:55 2013 GMT',
'serialNumber': '023A69',
'subject': ((('countryName', 'US'),),
(('organizationName', 'Google Inc'),),
(('commonName', 'Google Internet Authority G2'),)),
'version': 3},
{'issuer': ((('countryName', 'US'),),
(('organizationName', 'Equifax'),),
(('organizationalUnitName',
'Equifax Secure Certificate Authority'),)),
'notAfter': 'Aug 21 04:00:00 2018 GMT',
'notBefore': 'May 21 04:00:00 2002 GMT',
'serialNumber': '12BBE6',
'subject': ((('countryName', 'US'),),
(('organizationName', 'GeoTrust Inc.'),),
(('commonName', 'GeoTrust Global CA'),)),
'version': 3},
{'issuer': ((('countryName', 'US'),),
(('organizationName', 'Equifax'),),
(('organizationalUnitName',
'Equifax Secure Certificate Authority'),)),
'notAfter': 'Aug 22 16:41:51 2018 GMT',
'notBefore': 'Aug 22 16:41:51 1998 GMT',
'serialNumber': '35DEF4CF',
'subject': ((('countryName', 'US'),),
(('organizationName', 'Equifax'),),
(('organizationalUnitName',
'Equifax Secure Certificate Authority'),)),
'version': 3})
The answer above did not work out of the box.
After going through many options, I found this to be the simplest approach which requires minimum 3rd party libraries.
pip install pyopenssl certifi
import socket
from OpenSSL import SSL
import certifi
hostname = 'www.google.com'
port = 443
context = SSL.Context(method=SSL.TLSv1_METHOD)
context.load_verify_locations(cafile=certifi.where())
conn = SSL.Connection(context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM))
conn.settimeout(5)
conn.connect((hostname, port))
conn.setblocking(1)
conn.do_handshake()
conn.set_tlsext_host_name(hostname.encode())
for (idx, cert) in enumerate(conn.get_peer_cert_chain()):
print(f'{idx} subject: {cert.get_subject()}')
print(f' issuer: {cert.get_issuer()})')
print(f' fingerprint: {cert.digest("sha1")}')
conn.close()
Here is a link to the original idea
https://gist.github.com/brandond/f3d28734a40c49833176207b17a44786
Here is a reference which brought me here How to get response SSL certificate from requests in python?
I'm not sure, but I think that part of the OpenSSL API just isn't available in Python's ssl-module.
It seems that the function SSL_get_peer_cert_chain is used to access the certificate chain in OpenSSL. See, for example, the section of openssl s_client that prints the output you included. On the other hand, grepping the source of Python's ssl-module for SSL_get_peer_cert_chain yields no matches.
M2Crypto and pyOpenSSL both seem to include a get_peer_cert_chain function, if you're willing to look at other (and non-stdlib) libraries. I can't vouch for them personally, though, since I haven't used them much.
This is a follow up to oglops answer as my server didn't support the standard method:
import socket
import sys
from OpenSSL import SSL
import certifi
hostname = "www.google.com"
port = 443
methods = [
(SSL.SSLv2_METHOD,"SSL.SSLv2_METHOD"),
(SSL.SSLv3_METHOD,"SSL.SSLv3_METHOD"),
(SSL.SSLv23_METHOD,"SSL.SSLv23_METHOD"),
(SSL.TLSv1_METHOD,"SSL.TLSv1_METHOD"),
(SSL.TLSv1_1_METHOD,"SSL.TLSv1_1_METHOD"),
(SSL.TLSv1_2_METHOD,"SSL.TLSv1_2_METHOD"),
]
for method,method_name in methods:
try:
print(f"\n-- Method {method_name}")
context = SSL.Context(method=method)
context.load_verify_locations(cafile=certifi.where())
conn = SSL.Connection(
context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
)
conn.settimeout(5)
conn.connect((hostname, port))
conn.setblocking(1)
conn.do_handshake()
conn.set_tlsext_host_name(hostname.encode())
for (idx, cert) in enumerate(conn.get_peer_cert_chain()):
print(f"{idx} subject: {cert.get_subject()}")
print(f" issuer: {cert.get_issuer()})")
print(f' fingerprint: {cert.digest("sha1")}')
conn.close()
except:
print(f"<><> Method {method_name} failed due to {sys.exc_info()[0]}")
Related
Here's my webserver, most parts not included in favor of being concise.
server.js
const https = require('https');
const app = require('express')();
const port = 1000;
const options = {
key : getKey(),
cert: getCert(),
ciphers: getCiphers(),
passphrase: "abcd",
rejectUnauthorized: true,
requestCert: true
};
const server = https.createServer(options, app);
server.on('clientError', function (err) {
console.log('received client error');
console.log({err});
})
server.on('connection', function (err) {
console.log('client connected');
})
server.listen(port);
To test my server, I run a connection commands with openssl
openssl s_client -connect localhost:1000 \
-servername localhost \
-CAfile etc/root-cert/ca.cert.pem \
-cert etc/certs/client.cert.pem \
-key etc/private/client.key.pem
This returns the following output, which indicates that the TLS connection was successful (as far as I'm aware):
CONNECTED(00000005)
depth=2 C = US, ST = California, O = Hackysack, CN = Hackysack Root CA
verify return:1
depth=1 C = US, ST = California, O = Hackysack, CN = Hackysack Intermediate CA
verify return:1
depth=0 C = US, ST = California, O = Hackysack, CN = localhost
verify return:1
write W BLOCK
---
Certificate chain
0 s:/C=US/ST=California/O=Hackysack/CN=localhost
i:/C=US/ST=California/O=Hackysack/CN=Hackysack Intermediate CA
1 s:/C=US/ST=California/O=Hackysack/CN=Hackysack Intermediate CA
i:/C=US/ST=California/O=Hackysack/CN=Hackysack Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
[Long cert file here]
-----END CERTIFICATE-----
subject=/C=US/ST=California/O=Hackysack/CN=localhost
issuer=/C=US/ST=California/O=Hackysack/CN=Hackysack Intermediate CA
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 3400 bytes and written 2061 bytes
---
New, TLSv1/SSLv3, Cipher is AEAD-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.3
Cipher : AEAD-AES128-GCM-SHA256
Session-ID:
Session-ID-ctx:
Master-Key:
Start Time: 1674855496
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
read:errno=0
However, in my server terminal logs I see that I receive a clientError when trying to make this call via HTTPS request.
{
err: Error: socket hang up
at connResetException (internal/errors.js:639:14)
at TLSSocket.onSocketClose (_tls_wrap.js:1063:23)
at TLSSocket.emit (events.js:412:35)
at net.js:686:12
at Socket.done (_tls_wrap.js:564:7)
at Object.onceWrapper (events.js:520:26)
at Socket.emit (events.js:400:28)
at TCP.<anonymous> (net.js:686:12) {
code: 'ECONNRESET'
}
}
Here's my client request:
const https = require('https');
const options = {
hostname: 'localhost',
port: 1000,
method: "POST",
path: "/test",
cert: getClientCert(),
key: getClientKey(),
passphrase: "abcd",
ciphers: getCiphers()
ca: getCA(),
}
const request = https.request(options);
I noticed this error only started happening when I included requestCert: true when I created the server. I think something must be going wrong with my client side for this to be the case. The main problem with debugging this is that I'm not receiving a good error when it fails the handshake.
How can I better debug this issue?
I am currently working on a VSCode extension that internally makes some API calls to a HTTPS protected endpoint. On Linux and Mac OS the extension works as expected but on Windows machines axios, the internal HTTP client used to make the API calls, is rejecting those requests due to the certificates being expired. When I access the API endpoint though via Firefox, Chrome and even Edge the certificates seems find.
I have upgraded Node to 16.14.0 and also to 17.6.0 but the problem still remains. As the API is only accessible through our VPN, with my VPN activated of course, I used testssl.sh to verify that the whole trust-chain is still valid:
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 not offered
TLS 1.1 not offered
TLS 1.2 offered (OK)
TLS 1.3 offered (OK): final
NPN/SPDY not offered
ALPN/HTTP2 h2, http/1.1 (offered)
Testing cipher categories
NULL ciphers (no encryption) not offered (OK)
Anonymous NULL Ciphers (no authentication) not offered (OK)
Export ciphers (w/o ADH+NULL) not offered (OK)
LOW: 64 Bit + DES, RC[2,4] (w/o export) not offered (OK)
Triple DES Ciphers / IDEA not offered
Obsolete CBC ciphers (AES, ARIA etc.) offered
Strong encryption (AEAD ciphers) offered (OK)
Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4
PFS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA
DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES128-SHA DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA
Elliptic curves offered: prime256v1 secp384r1 secp521r1 X25519 X448
DH group offered: HAProxy (2048 bits)
Testing server preferences
Has server cipher order? yes (OK) -- TLS 1.3 and below
Negotiated protocol TLSv1.3
Negotiated cipher TLS_AES_256_GCM_SHA384, 253 bit ECDH (X25519)
Cipher order
TLSv1.2: ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA
TLSv1.3: TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256
...
Certificate Validity (UTC) 65 >= 30 days (2022-02-01 08:18 --> 2022-05-02 08:18)
...
The other certificates in the trustchain are also valid and not expired at all.
Axios is used within an own HttpClient class that looks like this:
export class HttpClient {
private readonly instance: AxiosInstance;
constructor() {
this.instance = axios.create();
}
async asyncGet<T>(url: string): Promise<Response<T>> {
return this.instance.get<T>(url)
.then(axiosResponse => {
...
})
.catch(error => {
console.error(error);
throw error;
});
}
}
I also tried the https
const options: https:RequestOptions = {
hostname: '...',
port: 443,
path: '/...',
method: 'GET'
};
const request = https.request(url, options, (res: IncomingMessage) => {
console.log(`statusCode: ${res.statusCode ? res.statusCode : 'undefined'}`);
res.on('data', d => {
process.stdout.write(d as string);
}
});
request.on('error', error => {
console.error(error);
};
request.end();
analog to the samples given in the NodeJS documentation and tls
const socket = tls.connect(443, hostname, undefined, () => {
console.log('Connected to ' + hostname);
console.log('TLS.connect', socket.authorized ? 'authorized' : 'unauthorized');
console.log('Cipher: ' + JSON.stringify(socket.getCipher()));
console.log('Protocol: ' + JSON.stringify(socket.getProtocol()));
const peerCert: tls.DetailedPeerCertificate = socket.getPeerCertificate(true);
console.log(`Peer-Cert ${JSON.stringify(peerCert.subject)} - valid from: ${peerCert ? peerCert.valid_from : 'invalid'} valid till: ${peerCert ? peerCert.valid_to : 'invalid'}`);
const issuerCert = peerCert.issuerCertificate;
console.log(`issuer-Cert ${JSON.stringify(issuerCert.subject)} - valid from: ${issuerCert ? issuerCert.valid_from : 'invalid'} valid till: ${issuerCert ? issuerCert.valid_to : 'invalid'}`);
const rootCert = issuerCert.issuerCertificate;
console.log(`root-Cert ${JSON.stringify(rootCert.subject)} - valid from: ${rootCert ? rootCert.valid_from : 'invalid'} valid till: ${rootCert ? rootCert.valid_to : 'invalid'}`);
});
as proposed in this answer.
Both axios and https return an error like this:
{
"message": "certificate has expired",
"name": "Error",
"stack": "Error: certificate has expired\n\tat TLSSocket.onConnectSecure (_tls_wrap.js:1497:34)\n\tat TLSSocket.emit (events.js:315:20)\n\tat TLSSocket._finishInit (_tls_wrap.js:932:8)\n\tat TLSWrap.onhandshakedone (_tls_wrap.js:706:12)\n\tat TLSWrap.callbackTrampoline (internal/async_hooks.js:131:14)",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"transformRequest": [
"null"
],
"transformResponse": [
"null"
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"headers": {
"Accept": "application/json, text/plain, */*",
"user-Agent": "axios/0.26.0"
},
"method": "get",
"url": "https://..."
},
"code": "CERT_HAS_EXPIRED",
"status": null
}
with a more human-readable stacktrace:
Error: certificate has expired
at TLSSocket.onConnectSecure (_tls_wrap.js:1497:34)
at TLSSocket.emit (events.js:315:20)
at TLSSocket._finishInit (_tls_wrap.js:932:8)
at TLSWrap.onhandshakedone (_tls_wrap.js:706:12)
at TLSWrap.callbackTrampoline (internal/async_hooks.js:131:14)
while for tls I get the following output:
Connected to ...
TLS.connect authorized
Cipher: {"name":"TLS_CHACHA20_POLY1305_SHA256","standardName":"TLS_CHACHA20_POLY1305_SHA256","version":"TLSv1/SSLv3"}
Protocol: "TLSv1.3"
Peer-Cert {"CN": "*...."} - valid from: Feb 1 08:18:23 2022 GMT valid till: May 2 08:18:22 2022 GMT
issuer-Cert {"C":"US","O":"Let's Encrypt","CN":"R3"} - valid from Sep 4 00:00:00 2020 GMT valid till: Sep 15 16:00:00 2025 GMT
root-Cert {"C":"US","O":"Internet Security Research Group","CN":"ISRG Root X1"} - valid from: Jan 20 19:14:03 2021 GMT valid till: Sep 30 18:14:03 2024 GMT
So tls seems to be able to connect to the API server and perform the SSL/TLS handshake, but https and axios somehow fail.
I also stumbled upon this question here, which seems to be related, but as I am already on the latest NodeJS release (as well as any dependency used in the extension is on the most recent version) and this error only occurs on Windows (mostly 10, unsure if and how many users actually use Windows 11) machines I think the question deserves its own spot here on SO.
In order to rule out a lack of common supported ciphers between the Windows and Node.js based tls implementation and the nginx managed server side, I also checked the available ciphers in Node via node -p crypto.constants.defaultCoreCipherList which returns a list like this:
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES256-GCM-SHA384
DHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA256
DHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
DHE-RSA-AES256-SHA384
ECDHE-RSA-AES256-SHA256
DHE-RSA-AES256-SHA256
HIGH
!aNULL
!eNULL
!EXPORT
!DES
!RC4
!MD5
!PSK
!SRP
!CAMELLIA
which indicates that enough ciphers would overlap between client and server.
Why do I still get a certificate expired error on Windows machines with axios/https
when Linux and MacOS work just fine with these settings and tls is able to connect to the remote API sucessfully even on Windows machines?
Since I was getting SSL errors (CERT_HAS_EXPIRED) for domains like https://www.dampfer-board.de/ I was trying to add current CAs to my application with
NODE_EXTRA_CA_CERTS=/var/www/xxx/cacert.pem
cacert.pem is the following file: https://curl.haxx.se/ca/cacert.pem
The env variable seems to be set correctly (confirmed by console.log(process.env.NODE_EXTRA_CA_CERTS)), but I still receive the same ssl error.
The code snippet looks like this:
request.post({url: apiUrl, ecdhCurve: 'auto', headers: {
'User-Agent': config.userAgent
}});
Any idea whats wrong?
Running openssl s_client -connect dampfer-board.de:443 -showcerts | openssl pkcs8 in the terminal returns this output.
depth=1 C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
verify error:num=10:certificate has expired
notAfter=May 30 10:48:38 2020 GMT
verify return:0
depth=1 C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
verify error:num=10:certificate has expired
notAfter=May 30 10:48:38 2020 GMT
verify return:0
depth=3 C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
verify error:num=10:certificate has expired
notAfter=May 30 10:48:38 2020 GMT
verify return:0
In the server certificate chain there is an expired certificate for AddTrust External CA Root.
I resolved this for openssl by removing the certificate from the list of certificates trusted by openssl.
I reckon that a similar fix can be applied for Node.
There is baked in the Node source this certificate as a trusted root. It's about time trust list got updated as the certificate expired since May 30, 2020.
NODE_EXTRA_CA_CERTS environment variable extends the trusted roots baked in the source whereas the ca option replaces it.
Modify your request to be
request.post({
url: apiUrl,
ecdhCurve: 'auto',
headers: {
'User-Agent': config.userAgent
},
agentOptions: {
ca: fs.readFileSync('./var/www/xxx/cacert.pem')
}
});
I have a code that connects with Jira using jira module.
Unfortunately Jira server only supports SSLv3 and TLS1.
I know they are old protocols, host will accept new ones before the end of this year.
But until there I need my python code to connect on Jira using TLS1.
With Python 3.6 it worked fine, but with Python 3.8 it doesn't work, it shows me the error message below.
Python 3.8.2 (default, Apr 27 2020, 15:53:34)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from jira import JIRA
>>> import urllib3
>>> urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
>>>
>>> options = {"server": "https://jira.mycompany.com/", "verify": False}
>>> jira = JIRA(options, auth=("user", "pass"))
WARNING:root:HTTPSConnectionPool(host='jira.mycompany.com', port=443): Max retries exceeded with url: /rest/auth/1/session (Caused by SSLError(SSLError(1, '[SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:1108)'))) while doing POST https://jira.mycompany.com/rest/auth/1/session [{'data': '{"username": "user", "password": "pass"}', 'headers': {'User-Agent': 'python-requests/2.23.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json,*.*;q=0.9', 'Connection': 'keep-alive', 'Cache-Control': 'no-cache', 'Content-Type': 'application/json', 'X-Atlassian-Token': 'no-check'}}]
WARNING:root:Got ConnectionError [HTTPSConnectionPool(host='jira.mycompany.com', port=443): Max retries exceeded with url: /rest/auth/1/session (Caused by SSLError(SSLError(1, '[SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:1108)')))] errno:None on POST https://jira.mycompany.com/rest/auth/1/session
{'response': None, 'request': <PreparedRequest [POST]>}\{'response': None, 'request': <PreparedRequest [POST]>}
WARNING:root:Got recoverable error from POST https://jira.mycompany.com/rest/auth/1/session, will retry [1/3] in 7.597192960254091s. Err: HTTPSConnectionPool(host='jira.mycompany.com', port=443): Max retries exceeded with url: /rest/auth/1/session (Caused by SSLError(SSLError(1, '[SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:1108)')))
I already checked and OpenSSL supports TLS1.
$ openssl s_client -help 2>&1 > /dev/null | egrep "\-(ssl|tls)[^a-z]"
-ssl_config val Use specified configuration file
-tls1 Just use TLSv1
-tls1_1 Just use TLSv1.1
-tls1_2 Just use TLSv1.2
-tls1_3 Just use TLSv1.3
-ssl_client_engine val Specify engine to be used for client certificate operations
Using only requests it gives me the same result.
python3 -c "import requests; requests.get('https://jira.mycompany.com/')"
Traceback (most recent call last):
File "/home/lazize/repos/myproj/venv/lib/python3.8/site-packages/urllib3/connectionpool.py", line 670, in urlopen
httplib_response = self._make_request(
File "/home/lazize/repos/myproj/venv/lib/python3.8/site-packages/urllib3/connectionpool.py", line 381, in _make_request
self._validate_conn(conn)
File "/home/lazize/repos/myproj/venv/lib/python3.8/site-packages/urllib3/connectionpool.py", line 976, in _validate_conn
conn.connect()
File "/home/lazize/repos/myproj/venv/lib/python3.8/site-packages/urllib3/connection.py", line 361, in connect
self.sock = ssl_wrap_socket(
File "/home/lazize/repos/myproj/venv/lib/python3.8/site-packages/urllib3/util/ssl_.py", line 377, in ssl_wrap_socket
return context.wrap_socket(sock, server_hostname=server_hostname)
File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket
return self.sslsocket_class._create(
File "/usr/lib/python3.8/ssl.py", line 1040, in _create
self.do_handshake()
File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:1108)
How can I use Python 3.8 with TLS1?
I solved the issue installing python package below.
In this way it installed pyOpenSSL.
Let me quote documentation:
If you install urllib3 with the secure extra, all required packages
for certificate verification will be installed.
pip install urllib3[secure]
If I understood correct Python comes with its own implementation of SSL via module ssl.
Installing urllib3 in this way it will force Python to use OpenSSL implementation via pyOpenSSL.
import ssl
sc = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
sc.load_verify_locations(cafile='./server-ca.pem')
sc.load_cert_chain(certfile='./client-cert.pem', keyfile='./client-key.pem')
#sc.check_hostname = False
async with aiomysql.create_pool(
host=host,
port=port,
user=user,
password=password,
db=db,
ssl=sc
)
I'm getting an error like this.
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: IP address mismatch, certificate is not
valid for 'ip_address'. (_ssl.c:1108)
server-ca.pem, client-cert.pem, and client-key.pem are exported from the connection tab of GCP Cloud SQL.
#mysql --ssl-ca=./server-ca.pem --ssl-cert=./client-cert.pem --ssl-key=./client-key.pem --host=host --user=user --password
This mysql command can be used to access.
I would like you to tell me what the problem is.
Just encountered the same problem. You need to set the parameter check_hostname=False, such that SSL doesn't try to verify it.
ssl = {
'cert': ...,
'key': ...,
'ca': ...,
'check_hostname': False,
}
I'm using PyMySQL. You might look into how that applies to SSLContext in your case.