I have a Spring Boot application from which I am trying to connect to a MySQL on AWS RDS. However I am having issues with the following error:
localhost-startStop-1, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca
After enabling debug for the ssl handshake, I see that for the CertificateRequest
step the Cert Authorities:
are empty.
Based on my understanding this is the part where the client (spring boot app) needs to present a cert to the server (mysql db).
Certificate Request
step - The server will issue a certificate request from the client.*** Certificate chain
, which is the certificate the client is sending to the server. In this case for me, it is sending the content of keyStore_cert.jks
.What I think the issue so far:
The server (mySQL db) does not know about this certificate (my keyStore_cert.jks) that the client (my app) is sending.
But, I was under the impression that the client certs are not required unless you set REQUIRE X509
for the user.
The questions:
keyStore_cert.jks
/trustStore_cert.jks
?These are what my settings and what I have tried.
jdbc:mysql://<host>:<port>/<db_name>?useLegacyDatetimeCode=false&verifyServerCertificate=true&useSSL=true&requireSSL=true
ALTER USER '<my_db_user>'@'%' require SSL;
GRANT USAGE ON <db_name>.* TO '<my_db_user>'@'%' REQUIRE SSL;
keytool -import -keystore trustStore_cert.jks -storepass <trustStore_password> -alias "awsrds-us-east1" -file rds-ca-2015-us-east-1.pem
-Djavax.net.ssl.keyStore=path/keyStore_cert.jks
-Djavax.net.ssl.keyStorePassword=<keyStore_password>
-Djavax.net.ssl.trustStore=path/trustStore_cert.jks
-Djavax.net.ssl.trustStorePassword=<trustStore_password>
Host: <host>
Port: <port>
User: <user>
SSL: enabled with DHE-RSA-AES128-SHA
SSL connection error: CA certificate is required if ssl-mode is VERIFY_CA or VERIFY_IDENTITY
. Which makes sense since i am not specifying anything for the CA file.Steps for the handshake (will omit some logs.):
*** ClientHello, TLSv1.1 (seems okay)
***
*** ServerHello, TLSv1.1 (seems okay)
***
*** Certificate chain (has the root key)
chain [0] = [
[
Version: V3
Subject: C=US, ST=Washington, L=Seattle, O=Amazon.com, OU=RDS, CN=**<my_rds_name>abcd.us-east-1.rds.amazonaws.com**
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
...
...
chain [1] = [ (has the us-east-1 key)
[
Version: V3
Subject: CN=Amazon RDS us-east-1 CA, OU=Amazon RDS, O="Amazon Web Services, Inc.", L=Seattle, ST=Washington, C=US
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
...
...
***
Found trusted certificate:
[
[
Version: V3
Subject: CN=Amazon RDS us-east-1 CA, OU=Amazon RDS, O="Amazon Web Services, Inc.", L=Seattle, ST=Washington, C=US
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
...
...
*** CertificateRequest (the Authorities are empty)
Cert Types: RSA, DSS, ECDSA
Cert Authorities:
<Empty>
*** ServerHelloDone
matching alias: <my keyStoreAlias>
*** Certificate chain (seems entries from my own key Store)
chain [0] = [
[
Version: V3
Subject: CN=<the CN on my keyStore>, OU=Web Servers, O=Company , C=US
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
***
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1.1 (seems okay)
*** CertificateVerify (seem okay)
*** Finished
verify_data: { 50, 89, 33, 202, 193, 158, 226, 114, 128, 50, 198, 250 }
***
After working with AWS RDS Support I got an explanation why the issue and how to fix it.
Just to clarify, this issue is mainly presented only if you pass your own keystore as part of JVM arguments. Something like this:
-Djavax.net.ssl.keyStore=path/keyStore_cert.jks
-Djavax.net.ssl.keyStorePassword=<keyStore_password>
RDS is a managed service and they do not have a way (currently) to load specific client certificates into the server. Meaning they can’t pass a specific certificates in DB configuration level. Usually this would be possible if you were to standup your own MY SQL server. In the config file for that server you can specify the client/server certs. So, RDS can’t verify the certificate provided by client.
If you were to pass a keystore, which has a key-pair entry (as JVM argument or otherwise), during SSL handshake, the client authentications step would fail. This is expected behavior in database. RDS can’t restrict itself to verify (or not verify) the file loaded in client field for the client during the initial connection. So, if a keystore is passed, server will try to match the certificate keys with existing CA file and if doesn’t match it will not allow the connections.
The solution is to either not pass a keystore at all or pass a blank keystore (one that does not have a key-pair with just Trusted Certificate Entry or one that is just blank).
if you choose to not pass a keystore, then do not specify these properties
-Djavax.net.ssl.keyStore= & -Djavax.net.ssl.keyStorePassword=
. And construct the DB connection URL like this:-Ddb_jdbc_url=jdbc:mysql://<host>:<port>/<db_name>?useLegacyDatetimeCode=false&verifyServerCertificate=true&useSSL=true&requireSSL=true
. You still need to provide the truststore. See at the bottom for more.
Pass a black keystore or (or pass the trust store in the keystore field) You can pass what ever you want for the keystore and trustore JVM params. And for the URL you construct it like this:
-Ddb_jdbc_url=jdbc:mysql://<host>:<port>/<db_name>?
useLegacyDatetimeCode=false&verifyServerCertificate=true&useSSL=true&requireSSL=true
&clientCertificateKeyStoreUrl=file:/user/documents/projects/trust-store-rds.jks
&clientCertificateKeyStorePassword=<password>
&clientCertificateKeyStoreType=JKS
&trustCertificateKeyStoreUrl=file:/user/documents/projects/trust-store-rds.jks
&trustCertificateKeyStorePassword=<password>
&trustCertificateKeyStoreType=JKS
Note that for both trustCertificateKeyStoreUrl
and clientCertificateKeyStoreUrl
I am passing the same file.
Note, you need to also configure all the previous steps:
CREATE USER 'my_user'@'%' IDENTIFIED BY 'my_password';
ALTER USER 'my_user'@'%' REQUIRE SSL;
GRANT USAGE ON *.* TO 'my_user'@'%' REQUIRE SSL ;
keytool -import -keystore trust-store-rds.jks -storepass changeit -noprompt alias "aws-rds-root" -file rds-ca-2015-root.pem
keytool -import -keystore trust-store-rds.jks -storepass changeit -noprompt -alias "aws-rds-us-east-1" -file rds-ca-2015-us-east-1.pem
Since version 8.0.22 of the MySQL Connector/J driver you can now use the fallbackToSystemKeyStore property set to false to ignore any system level keystore rather than having to create a dummy one.
I've used this method successfully against an RDS MySQL 5.7 instance and where I initially saw the same "unknown_ca" error it now connects perfectly.
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-security.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With