Description
When reporting an issue, please include:
- Dart 3.8.1
- Linux
Why is this needed?
At present TLS certificates issued by popular public Certificate Authorities (CAs) such as LetsEncrypt and ZeroSSL [1] come with Extended Key Usage (EKU) attributes for Server Auth
and Client Auth
. This means that services can present their certificates to each other for mutual transport layer security (mTLS) and all the relevant checks pass.
The Google Chrome team have announced a change in their CA policy to Promote use of Dedicated TLS Server Authentication PKI Hierarchies that forces CAs to only issue certs with the Server Auth
EKU (id-kp-serverAuth). As a result LetsEncrypt have announced "Ending TLS Client Authentication Certificate Support in 2026". Meaning that certificates will no longer have the Client Auth EKU from 13 May 2026. It's reasonable to expect that other CAs will follow a similar process, and in a similar time frame to meet the Chrome policy deadline of 15 June 2026.
What happens in Dart today?
When the client side of an mTLS connection (between two peer services) presents a certificate that only contains the Server Auth
EKU a HandshakeException is thrown:
HandshakeException: Handshake error in server (OS Error:
CERTIFICATE_VERIFY_FAILED: unsupported certificate purpose(handshake.cc:295))
This happens irrespective of whether requireClientCertificate:
is set to true
or false
. When requestClientCertificate: true
and a client cert is presented then BoringSSL checks the EKUs, and if it just finds Server Auth
it throws an error [2].
There is no way to override this behaviour as the exception is thrown before the SecureSocket object is returned.
Requested change
By setting requireClientEKUVerify: false
it should be possible for the securityContext object to pass a flag to BoringSSL such that the EKU check is not required.
Example code
The cpswan/dart_mtls repo contains an example tls_socket_server.dart
and tls_socket_client.dart
implementation along with (self signed) certificates with various EKUs:
- client.crt:
Server Auth
andClient Auth
- client.noext.crt: none
- client.web.crt:
Server Auth
The service address atsign.test
is expected to resolve to localhost (127.0.0.1).
Notes
[1] Google's own public CA defaults to issuing certificates with just the Server Auth
EKU, but (for now) it's trivial to modify the Certificate Signing Request (CSR) to get a certificate that also has the Client Auth
EKU.
[2] This check isn't super rigorous, so if a cert with no EKUs is presented that passes the test. So it's not precisely that it's ensuring that Client Auth
is present but rather that if there are EKUs then Client Auth
must be one of them.