EcoG-io/iso15118

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.