Spring Boot 连接到 AWS RDS MySQL - SSLHandshakeException:收到致命警报:unknown_ca

Posted

技术标签:

【中文标题】Spring Boot 连接到 AWS RDS MySQL - SSLHandshakeException:收到致命警报:unknown_ca【英文标题】:Spring Boot connection to AWS RDS MySQL - SSLHandshakeException: Received fatal alert: unknown_ca 【发布时间】:2019-07-06 20:28:48 【问题描述】:

我有一个 Spring Boot 应用程序,我试图从该应用程序连接到 AWS RDS 上的 mysql。 但是我遇到了以下错误:

localhost-startStop-1, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca

启用 ssl 握手调试后,我看到 CertificateRequest 步骤的 Cert Authorities: 为空。 根据我的理解,这是 client(spring boot 应用程序)需要向 server(mysql db)提供证书的部分。

Certificate Request 步骤 - 服务器将向客户端发出证书请求。 下一步是*** Certificate chain,这是客户端发送给服务器的证书。在这种情况下,对我来说,它正在发送keyStore_cert.jks 的内容。

到目前为止我认为的问题: 服务器(mySQL db)不知道客户端(我的应用程序)正在发送的这个证书(我的 keyStore_cert.jks)。 但是,我的印象是除非您为用户设置REQUIRE X509,否则不需要客户端证书。

问题:

我还可以查看其他内容以找到确切的问题吗? 如果出现上述问题,我该如何解决?如何禁用客户端验证? 或者我怎样才能保持客户端验证但它可以工作? 我需要向keyStore_cert.jks/trustStore_cert.jks 添加任何内容吗?

这些是我的设置和我尝试过的。

RDS 引擎版本:5.7.22 mysql-connector-java v8.0.13 连接网址:jdbc:mysql://<host>:<port>/<db_name>?useLegacyDatetimeCode=false&verifyServerCertificate=true&useSSL=true&requireSSL=true 对于我正在使用的用户,我在 DB 上执行了以下操作:
ALTER USER '<my_db_user>'@'%' require SSL;
GRANT USAGE ON <db_name>.* TO '<my_db_user>'@'%' REQUIRE SSL;
基于AWS documentation:我将那些证书(根证书和中间证书)导入到trustStore。做某事:keytool -import -keystore trustStore_cert.jks -storepass &lt;trustStore_password&gt; -alias "awsrds-us-east1" -file rds-ca-2015-us-east-1.pem 我使用我们自己的密钥库。此时未对 keyStore 进行任何操作。 应用程序 trustStore 和 keyStore 作为 JVM 参数传递,如下所示:
-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>
对于特定用户,当我使用 MySQL Workbench 并在 Use SSL 的连接设置中指定:If availableRequire。它与此消息相关联:
Host: <host>
Port: <port>
User: <user>
SSL: enabled with DHE-RSA-AES128-SHA
但是,如果我指定 要求并验证 CA要求并验证身份,它会给出以下消息:SSL connection error: CA certificate is required if ssl-mode is VERIFY_CA or VERIFY_IDENTITY。这是有道理的,因为我没有为 CA 文件指定任何内容。

握手步骤(将省略一些日志。):

*** 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 
***

【问题讨论】:

您解决了吗?面临类似问题“致命警报:unknown_ca” @user2677034 在与 AWS RDS 支持人员联系后,我确实找到了解决方案。不完全是我想要的,但它有效。我将在下面发布答案。 【参考方案1】:

在与 AWS RDS Support 合作后,我得到了一个解释为什么会出现问题以及如何解决它。

澄清一下,这个问题主要只在您将自己的密钥库作为 JVM 参数的一部分传递时才会出现。 像这样的:

 -Djavax.net.ssl.keyStore=path/keyStore_cert.jks
 -Djavax.net.ssl.keyStorePassword=<keyStore_password>

RDS 是一项托管服务,他们没有办法(当前)将特定客户端证书加载到服务器中。 这意味着他们无法通过数据库配置级别的特定证书。通常,如果您要建立自己的 MY SQL 服务器,这将是可能的。在该服务器的配置文件中,您可以指定客户端/服务器证书。 因此,RDS 无法验证客户端提供的证书。

如果您要在 SSL 握手期间传递具有密钥对条目(作为 JVM 参数或其他方式)的密钥库,则客户端身份验证步骤将失败。这是数据库中的预期行为。 RDS 不能限制自己在初始连接期间为客户端验证(或不验证)客户端字段中加载的文件。因此,如果传递了密钥库,服务器将尝试将证书密钥与现有 CA 文件匹配,如果不匹配,则不允许连接。

解决方案是要么根本不传递密钥库,要么传递一个空白密钥库(一个没有密钥对且只有受信任的证书条目,或者一个只是空白的)。

    如果您选择不传递密钥库,则不要指定这些属性 -Djavax.net.ssl.keyStore= &amp; -Djavax.net.ssl.keyStorePassword=。并像这样构造数据库连接 URL:-Ddb_jdbc_url=jdbc:mysql://&lt;host&gt;:&lt;port&gt;/&lt;db_name&gt;?useLegacyDatetimeCode=false&amp;verifyServerCertificate=true&amp;useSSL=true&amp;requireSSL=true。您仍然需要提供信任库。详情请见底部。

    传递黑色密钥库或(或传递密钥库字段中的信任库) 您可以为 keystore 和 trustore JVM 参数传递您想要的任何内容。 对于你这样构建的 URL:

    -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

请注意,对于trustCertificateKeyStoreUrlclientCertificateKeyStoreUrl,我传递的是同一个文件。

注意,你还需要配置前面的所有步骤:

    为数据库上的用户启用 SSL。
CREATE USER 'my_user'@'%' IDENTIFIED BY 'my_password';
ALTER USER 'my_user'@'%' REQUIRE SSL;
GRANT USAGE ON *.* TO 'my_user'@'%' REQUIRE SSL ;

    您需要将 AWS 根证书和区域证书导入您的信任库; 像这样:
     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
    如上构建 URL。

【讨论】:

【参考方案2】:

我们在与 AWS RDS 8.X 建立连接时遇到了这个问题 - “Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca”

只有在 JVM 级别设置密钥库时才会出现此错误。如果只在 JVM 级别设置了 trustore 而不是 keystore,则不会弹出此问题,因为 truststore 已经信任 RDS 证书 -Djavax.net.ssl.keyStore=/path/to/your/keystore.jks -Djavax.net.ssl.keyStorePassword=keystore_password

由于 RDS 是共享服务,RDS 中没有选项可以信任单个客户端的证书。如果密钥库通过,RDS 认为请求是针对 2 路 SSL 的,因此失败。另一方面,我们无法摆脱 JVM 级别的密钥库,因为 API Gateway 等其他后端启用了 2 路 SSL

解决方案:我们向 jdbc 提供了虚拟密钥库(信任库本身,因为它不包含任何私钥)以覆盖在 JVM 级别提供的密钥库,它的工作原理就像一个魅力!!

公共类 MySQLSSLTest

    private static final String DB_USER = "username";
    private static final String DB_PASSWORD = "password";
    // This key store has only the prod root ca.
    private static final String KEY_STORE_FILE_PATH = "file-path-to-keystore";
    private static final String KEY_STORE_PASS = "keystore-password";
        
    public static void test(String[] args) throws Exception 
        Class.forName("com.mysql.jdbc.Driver");
                
        System.setProperty("javax.net.ssl.trustStore", KEY_STORE_FILE_PATH);
        System.setProperty("javax.net.ssl.trustStorePassword", KEY_STORE_PASS);
        
        Properties properties = new Properties();
        properties.put("user", DB_USER);
        properties.put("password", DB_PASSWORD);
        properties.put("sslMode", "DISABLED");
        properties.put("useSSL", "false");
        properties.put("clientCertificateKeyStoreUrl", "file:/path/to/your/trust/store");
        properties.put("clientCertificateKeyStorePassword", "truststore_password");
        properties.put("clientCertificateKeyStoreType", "jks");
        
 
        Connection connection = null;
        Statement stmt = null;
        ResultSet rs = null;
        try 
            connection = DriverManager.getConnection("jdbc:mysql://mydatabase.123456789012.us-east-1.rds.amazonaws.com:3306",properties);
            stmt = connection.createStatement();
            rs=stmt.executeQuery("SELECT 1 from dual");
         finally 
            if (rs != null) 
                try 
                    rs.close();
                 catch (SQLException e) 
                
            
            if (stmt != null) 
               try 
                    stmt.close();
                 catch (SQLException e) 
               
            
            if (connection != null) 
                try 
                    connection.close();
                 catch (SQLException e) 
                    e.printStackTrace();
                
            
        
        return;
    


Hopefully this will help you!!!

【讨论】:

【参考方案3】:

从 MySQL 连接器/J 驱动程序的 8.0.22 版开始,您现在可以使用设置为 false 的 fallbackToSystemKeyStore 属性来忽略任何系统级密钥库,而不必创建一个虚拟密钥库。

我已经成功地针对 RDS MySQL 5.7 实例使用了这种方法,并且我最初看到相同的“unknown_ca”错误,现在它可以完美连接。

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-security.html

【讨论】:

以上是关于Spring Boot 连接到 AWS RDS MySQL - SSLHandshakeException:收到致命警报:unknown_ca的主要内容,如果未能解决你的问题,请参考以下文章

如何在弹性beantalk中将PostgreSQL RDS连接到spring boot?

spring-boot web 应用程序在一段时间后失去连接到 MySQL / RDS 的能力

无法通过 AWS.RDS.Signer 连接到 RDS

从 Spring Boot 连接到 AWS SQS 时出错

如何将 AWS Elasticache Redis 集群连接到 Spring Boot 应用程序?

从 MySQL 客户端通过 AWS 堡垒主机连接到 MySQL RDS 实例