1

Was server certificate pinning broken in mysql-connector-java versions 5.1.42 onwards?

This is the conclusion I'm reaching, but 5.1.42 was released in April 2017 and something like this should likely affect more people. The lack of information I'm finding suggests there's something I'm overlooking, or:

Background

Prior to version 5.1.42 (up until 5.1.41), given a server certificate chain such as this:

chain[0]: Certificate of MySQL server
 +- chain[1]: Intermediate CA
     +- chain[2]: Root CA

I could put "Certificate of MySQL server" (and it only) in a keystore, save it as a .jks file, and specify this file to the trustCertificateKeyStoreUrl property when connecting. This ensured I connected to the server described by "Certificate of MySQL server" and it only.

This was performed by mysql-connector-java by using the default implementation of X509TrustManager returned by TrustManagerFactory.getTrustManagers(). The internal implementation of sun.security.ssl.X509TrustManagerImpl uses sun.security.validator.PKIXValidator.validate(...) which correctly builds the certificate chain from the truststore before validating it.

This handles any of these cases:

  • {chain[0]*} <- chain[1] <- chain[2]
  • {chain[0] <- chain[1]*} <- chain[2]
  • {chain[0] <- chain[1] <- chain[2]*}
  • {chain[0] <- chain[1] <- chain[2] <- *}

Where chain[0..2] is the chain provided by the server during the handshake. The * marks the certificate matching anything in the truststore. The {} encloses the resulting chain that should be validated. And <- describes the cert <- issuer cert relationship.

What changed

From 5.1.42 onwards to the currently latest 8.0.17, mysql-connector-java fails to connect to the server using the .jks file described above due to a javax.net.ssl.SSLHandshakeException with the following cause:

Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
    at com.mysql.cj.protocol.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:382)
    at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1091)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
    ... 28 more
Caused by: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
    at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:154)
    at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:80)
    at java.security.cert.CertPathValidator.validate(CertPathValidator.java:292)
    at com.mysql.cj.protocol.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:375)
    ... 30 more

Looking at the code shows mysql-connector-java is still using sun.security.ssl.X509TrustManagerImpl for the main verification, but before that there is an extra check which is where the exception is thrown.

This code was probably added because development wanted to also check the validity of any certificates used in the truststore. (As mentioned in the release note of 5.1.42 citing Bug #20515688.)

However, the implementation for determining the certificate in the truststore used doesn't look sound. Rather than building a chain by combining the truststore with the server provided chain, it takes the whole chain provided by the server as is then validates that as a built chain.

More precisely, the implementation uses the provided chain in a java.security.cert.CertPath whose specifications states:

By convention, X.509 CertPaths (consisting of X509Certificates), are ordered starting with the target certificate and ending with a certificate issued by the trust anchor. That is, the issuer of one certificate is the subject of the following one. The certificate representing the TrustAnchor should not be included in the certification path.

Then calls sun.security.provider.certpath.PKIXCertPathValidator.validate(...) to validate the above statement or else throws java.security.cert.CertPathValidatorException.

The side-effect of this likely unintended exception effectively restricts the chain verification that can be performed from 5.1.42 onwards like so:

  • chain[0]* <- chain[1] <- chain[2] -- broken
  • chain[0] <- chain[1]* <- chain[2] -- broken
  • chain[0] <- chain[1] <- chain[2]* -- broken
  • chain[0] <- chain[1] <- chain[2] <- * -- OK

Due to this, the usefulness of trustCertificateKeyStoreUrl is greatly reduced as the only certificate now acceptable is the one issuer beyond the chain provided by the server.

Related posts

I've only been able to find these posts describing issues that might share the same cause:

antak
  • 19,481
  • 9
  • 72
  • 80
  • Curious. I don't see why MySQL developers should take it on themselves to modify JSSE, or change the semantics of SSL. – user207421 Sep 10 '19 at 06:02

0 Answers0