具有指定证书的 Java MySQL SSL 连接

Posted

技术标签:

【中文标题】具有指定证书的 Java MySQL SSL 连接【英文标题】:Java MySQL SSL connection with specified cert 【发布时间】:2019-02-14 03:07:12 【问题描述】:

我正在尝试制作一个 Java 应用程序,它使用 jdbc (Connector/J) 来使用 SSL 加密连接到 mariadb 数据库。 我已经创建了一个自签名证书并将服务器配置为使用它。 当我尝试连接时,我得到一个异常,因为证书不受信任,因为它是自签名的并且没有添加到 Java 信任库中。 因为我想修改 TrustStore 或者为我的应用程序使用不同的,所以我搜索了一种方法来只告诉 jdbc 哪个证书是受信任的,结果发现“https://mariadb.com/kb/en/library/using-tlsssl-with-mariadb-connectorj/ 直接提供证书”

当我现在尝试连接时:

DriverManager.getConnection("jdbc:mysql://<hostname>:3306/<database>?useSSL=true&requireSSL=true&serverSslCert=/tmp/ssl/server-cert.pem", "<username>", "<password>");

在不再使用 serverSslCert 参数之前,我得到了这个异常:

Exception in thread "main" 

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 136 milliseconds ago.  The last packet sent successfully to the server was 129 milliseconds ago.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
    at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
    at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:203)
    at com.mysql.jdbc.MysqlIO.negotiateSSLConnection(MysqlIO.java:4901)
    at com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(MysqlIO.java:1659)
    at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1226)
    at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2188)
    at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2219)
    at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2014)
    at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:776)
    at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
    at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:386)
    at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
    at gymcash.server.mysql.DriverWrapper.connect(DriverWrapper.java:22)
    at java.sql.DriverManager.getConnection(DriverManager.java:664)
    at java.sql.DriverManager.getConnection(DriverManager.java:247)
    at gymcash.server.mysql.MySQL.connect(MySQL.java:48)
    at gymcash.server.mysql.MySQL.connect(MySQL.java:52)
    at gymcash.server.main.Main.main(Main.java:25)
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1964)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:328)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:322)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1614)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1052)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:987)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
    at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:188)
    ... 21 more
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
    at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:304)
    at 

sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:992)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1596)
    ... 29 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.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:297)
    ... 31 more

我不明白我做错了什么,因为文件存在,路径是绝对的,并且与服务器使用的相同。有人可以告诉我我做错了什么吗?

我正在使用 Java 8,mysql-connector-java-5.1.45

【问题讨论】:

【参考方案1】:

有几种方法可以做到这一点。将您的自签名证书或用于签署证书的证书颁发机构放入您用于连接的 JVM 使用的默认 java 密钥库中。您需要使用 Keytool 来执行此操作。但除非您管理企业 VM 包,否则您可能不想劫持安全包中的默认密钥库。

首先您需要了解关键工具的工作原理。

[Java Key 工具命令][1]https://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html

问题是您可能无法访问默认密钥库,并且它可能不接受您使用的证书格式。需要一些健身房工作来转换证书并尝试匹配支持的加密算法。

一般来说,除非你管理一个企业,否则最好

    生成新的密钥库文件 将您的证书导入此密钥库(或证书链) 确保将其标记为受信任 在运行时使用 -D 选项将密钥库传递给您的程序。

我通常只是将其添加到我的代码中并在连接到数据库之前设置属性

if (DB_SSL || SSL) 
            System.setProperty("javax.net.ssl.keyStore", KEYSTORE);
            System.setProperty("javax.net.ssl.keyStorePassword", KEYSTORE_PASS);
            System.setProperty("javax.net.ssl.trustStore", KEYSTORE);
            System.setProperty("javax.net.ssl.trustStorePassword", KEYSTORE_PASS);
        if (DEBUG_OPTION) 
            System.setProperty("javax.net.debug", "true");
        
    

private static String KEYSTORE = "/home/me/.keystore";
private static String KEYSTORE_PASS = "changeit";

【讨论】:

这或多或少是我不想做的事情。因为当我这样做时,我会覆盖(对于这个程序)trustStore 并且无法连接到我不知道 ca 的服务器。我需要一种在不更改默认 trustStore 或外部程序的情况下合并默认 trustStore 和自签名 ca 的方法。 也许我没有正确理解,但我相当确定 VM 仍然会在 jre/lib/security 中加载默认信任库,其中包含所有标准根 ca 证书。懒惰的方法是在此处添加您的自签名 ca,然后您在下一次 VM 更新之前签名的任何内容都将被信任。否则,您可以使用运行时婴儿车来扮演自己的角色。您甚至可以打包自己的密钥库和信任库。 好的,我以为这会覆盖当前的默认信任存储,但我会到时候试试 谢谢,现在可以使用了。我误解了这个属性的工作原理。

以上是关于具有指定证书的 Java MySQL SSL 连接的主要内容,如果未能解决你的问题,请参考以下文章

mysql加密连接二mysql_ssl_rsa_setup

通过 SSL 将 phpMyAdmin 连接到 MySQL 服务器

连接需要具有 ssl 模式的 laravel google cloud postgres 中的有效客户端证书

mysql加密连接三openssl 创建 SSL 证书和密钥

使用PHP / MySQLI / Apache时,在哪里安全地存储证书/密钥?

Mysql 5.7.18 加密连接mysql_ssl_rsa_setup 安装 mysql证书登陆