TLS Handshake fails with Hubject PKI due to Extended Key Usage in root certificates
acnixvkh opened this issue · 4 comments
I use this implementation as part of the car simulator in EVerest (version 2024.6.0). When trying to use the Hubject PKI, TLS Handshake fails with error message:
2024-09-07 00:05:35.813476 [ERRO] iso15118_car pybind11_init_everestpy(pybind11::module_&)::<lambda(const string&)> :: SSLCertVerificationError when trying to connect to host fe80::e6fd:45ff:fefa:e20e and port 64109
Traceback (most recent call last):
File "{EVerest}/checkout/everest-workspace/everest-core/build/dist/libexec/everest/modules/PyEvJosev/../../3rd_party/josev/iso15118/evcc/comm_session_handler.py", line 420, in start_comm_session
self.tcp_client = await TCPClient.create(
File "{EVerest}/checkout/everest-workspace/everest-core/build/dist/libexec/everest/modules/PyEvJosev/../../3rd_party/josev/iso15118/evcc/transport/tcp_client.py", line 62, in create
raise exc
File "{EVerest}/checkout/everest-workspace/everest-core/build/dist/libexec/everest/modules/PyEvJosev/../../3rd_party/josev/iso15118/evcc/transport/tcp_client.py", line 53, in create
self.reader, self.writer = await asyncio.open_connection(
File "/usr/lib/python3.10/asyncio/streams.py", line 48, in open_connection
transport, _ = await loop.create_connection(
File "/usr/lib/python3.10/asyncio/base_events.py", line 1103, in create_connection
transport, protocol = await self._create_connection_transport(
File "/usr/lib/python3.10/asyncio/base_events.py", line 1133, in _create_connection_transport
await waiter
File "/usr/lib/python3.10/asyncio/sslproto.py", line 534, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/usr/lib/python3.10/asyncio/sslproto.py", line 188, in feed_ssldata
self._sslobj.do_handshake()
File "/usr/lib/python3.10/ssl.py", line 975, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unsupported certificate purpose (_ssl.c:1007)
I am slightly confused, because I thought the implementation had been tested against Hubject. Maybe I am just missing some configuration?
Explanation
Hubject uses X509v3 extension "Extended Key Usage" (EKU) in their root certificates (see link and certificate excerpt below). The only EKU their certificates provide is "OCSP Signing".
I do not understand the reason why Hubject adds this EKU. In principle, "Extended Key Usage" extension is not allowed for V2G root certificates by ISO15118-2. But Hubject is the de facto standard. So it would be good to be compliant.
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
bb:bb:bc:1a:c4:5a:d2:df:e5:b1:d8:73:c3:5c:84
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = DE, O = Hubject GmbH, DC = V2G, CN = V2G Root CA G2
Validity
Not Before: Jan 10 12:54:26 2019 GMT
Not After : Jan 9 19:00:00 2059 GMT
Subject: C = DE, O = Hubject GmbH, DC = V2G, CN = V2G Root CA G2
Subject Public Key Info:
…
X509v3 extensions:
X509v3 Extended Key Usage:
OCSP Signing
…
It seems like OpenSSL does additional checks during TLS Handshake when the EKU extension is present. Especially, it does check whether the purpose (another word for EKU) "SSL Server" is present. This is not true for Hubject V2G root certificates. Therefore, the verification fails.
OpenSSL does not check the purposes when the extension is not available at all. That's why it does not complain when using self-generated PKI (based on create_certs.sh).
I found very little information on this. The best source I found is this question on StackExchange.
Reproducing the issue
If you do not have certificates from Hubject available, the issue can be reproduced by adapting v2gRootCACert.cnf as follows:
--- a/iso15118/shared/pki/configs/v2gRootCACert.cnf
+++ b/iso15118/shared/pki/configs/v2gRootCACert.cnf
@@ -12,4 +12,5 @@ domainComponent = V2G
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
+extendedKeyUsage = OCSPSigning
Generate a new PKI (with create_certs.sh) and verify that OpenSSL check for "SSL Server" purpose fails:
{iso15118}/iso15118/shared/pki/iso15118_2/certs/ $ openssl verify \
-purpose sslserver \
-CAfile ca/v2g/V2G_ROOT_CA.pem \
-untrusted ca/cso/CPO_SUB_CA1.pem \
-untrusted ca/cso/CPO_SUB_CA2.pem \
client/cso/SECC_LEAF.pem
CN = V2GRootCA, O = EVerest, C = DE, DC = V2G
error 26 at 3 depth lookup: unsupported certificate purpose
error client/cso/SECC_LEAF.pem: verification failed
Whereas verification of the chain without checking for specific purpose is successful:
{iso15118}/iso15118/shared/pki/iso15118_2/certs/ $ openssl verify \
-CAfile ca/v2g/V2G_ROOT_CA.pem \
-untrusted ca/cso/CPO_SUB_CA1.pem \
-untrusted ca/cso/CPO_SUB_CA2.pem \
client/cso/SECC_LEAF.pem
client/cso/SECC_LEAF.pem: OK
Using this adapted PKI will cause TLS handshake failing with the same error message as with Hubject certificates.
Simple Workaround
Certificate verification can be deactivated completely by enforcing ssl_context.verify_mode = VerifyMode.CERT_NONE
in security.py.
But I did not find a simple way to only deactivate checking of the EKU...
Apologies for the super late response. Maybe you have already fixed the issue. :)
As a test, could you use the PKI and try running a session with the secc and evcc from this repo? That would help ensure the PKI has everything needed. The bits that are of interest on the SECC side are:
iso15118_2/certs/v2gRootCACert.pem
iso15118_2/certs/cpoCertChain.pem
iso15118-2/private_keys/seccLeaf.key
iso15118-2/private_keys/seccLeafPassword.txt
On the EV side, we would need the v2g root ca cert.
This stack has been tested against both the prod and dev environments from Hubject (US and EU). Is your chain derived from one of these? It maybe worth switching environment and retrying if you haven't one of the two yet.
Going by the error code, it would be good to list the purposes of the certificate just to be sure that the root the chain was derived from supports TLS handshakes. Could you share that as well?
openssl x509 --in v2gRootCACert.pem -purpose -noout -text
Going by the error, the SSL client/server purposes of either party seem to be unavailable.
The sslServer purpose is required to be able to use the cert for TLS handshake. Changing the verify mode isn't desired as that would undermine the security checks in place. If you were using the EU test environment for your tests, could you try switching to the US test environment and see if you get the same result?
Thanks for having a look into this!
In my original issue report, I provided a simple way to reproduce the problem without Hubject certificates. Did you try this?
If you can't reproduce the issue this way, it might be due to the specific version of the SSL library on my system. (I am using Ubuntu 22.04.4 on WSL2.)
I can reproduce the issue using code from this repository by following the README.md to run both SECC and EVCC on the same machine.
I cannot switch to the US test environment, but I can still answer your question. The V2G roots in Hubject's US PKIs do not have any Extended Key Usage (EKU), so the issue would not occur... It seems only "V2G Root CA G2" and "V2G Root CA QA G1" use EKU.