首页 > 解决方案 > 收到致命警报:连接到 MySQL >= 8.0.19 时出现 bad_certificate

问题描述

我尝试通过带有 TLS 的MySQL JDBC 驱动程序(版本 8.0.27)连接到在 docker 中运行的 MySQL 实例。虽然我的设置适用于 8.0.19 之前的所有版本,但对于每个较新版本的连接都失败,并出现以下异常:

Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate at 
    java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) at 
    java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)

我根据官方文档创建了证书:

# Create CA certificate
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 3600 \
-key ca-key.pem -out ca.pem

# Create server certificate, remove passphrase, and sign it
# server-cert.pem = public key, server-key.pem = private key
openssl req -newkey rsa:2048 -days 3600 \
-nodes -keyout server-key.pem -out server-req.pem
openssl rsa -in server-key.pem -out server-key.pem
openssl x509 -req -in server-req.pem -days 3600 \
-CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem

# Create client certificate, remove passphrase, and sign it
# client-cert.pem = public key, client-key.pem = private key
openssl req -newkey rsa:2048 -days 3600 \
-nodes -keyout client-key.pem -out client-req.pem
openssl rsa -in client-key.pem -out client-key.pem
openssl x509 -req -in client-req.pem -days 3600 \
-CA ca.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem

openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem -out keystore.p12 -name test -CAfile ca.pem -caname root

keytool -importkeystore -deststorepass changeme -destkeypass changeme -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12 -srcstorepass "" -alias test

我创建了一个设置来验证问题确实只发生在 MySQL 版本 >=8.0.19 上:

public class MySQLTest {

    private @Nullable MySQLContainerSSL mySQLContainerSSL;

    @AfterEach
    void tearDown() {
        if (mySQLContainerSSL != null) {
            mySQLContainerSSL.stop();
        }
    }

    @ParameterizedTest
    @ValueSource(strings = {
            "8.0.0",
            "8.0.18",
            "8.0.19",
            "8.0.27"
    })
    void test(final @NotNull String version) throws Exception {

        Class.forName("com.mysql.jdbc.Driver");

        mySQLContainerSSL = new MySQLContainerSSL(version);
        mySQLContainerSSL.start();

        final Properties properties = new Properties();
        properties.put("sslMode", "REQUIRED");
        properties.put("useSSL", "true");
        properties.put("user", mySQLContainerSSL.getPassword());
        properties.put("password", mySQLContainerSSL.getUsername());
        properties.put("clientCertificateKeyStoreUrl", new File("src/test/resources/mysql/keystore.jks").toURI().toURL().toString());
        properties.put("clientCertificateKeyStorePassword", "changeme");

        final Connection con = DriverManager.getConnection(mySQLContainerSSL.getJdbcUrl(), properties);

        assertNotNull(con);
        assertFalse(con.isClosed());
    }


    public static class MySQLContainerSSL extends MySQLContainer<MySQLContainerSSL> {

        public MySQLContainerSSL(final @NotNull String version) {
            super(getAdHocImageName(version));
        }

        @Override
        public void stop() {
            super.stop();
            final String dockerImageName = getDockerImageName();
            DockerClientFactory.instance().client().removeImageCmd(dockerImageName).withForce(true).exec();
        }

        public static @NotNull DockerImageName getAdHocImageName(final @NotNull String mysqlVersion) {
            final String s = new ImageFromDockerfile().withDockerfile(Path.of("src/test/resources/mysql/Dockerfile"))
                    .withBuildArg("MYSQL_VERSION", mysqlVersion)
                    .get();
            return DockerImageName.parse(s).asCompatibleSubstituteFor("mysql");
        }
    }
}

Dockerfile:

ARG MYSQL_VERSION
FROM mysql:${MYSQL_VERSION}

COPY server-key.pem /var/ssl/server-key.pem
COPY server-cert.pem /var/ssl/server-cert.pem
COPY ca.pem /var/ssl/ca/ca.pem
COPY my.cnf /etc/mysql/my.cnf
COPY my.cnf /var/lib/mysql-files/my.cnf

RUN chown mysql:mysql /etc/mysql/my.cnf \
                     /var/ssl/ \
                    /var/ssl/server-key.pem \
                    /var/ssl/ca/ca.pem \
                    /var/ssl/server-cert.pem && \
    chmod 644 /var/ssl/server-key.pem \
                /var/ssl/server-cert.pem \
                /var/ssl/ca/ca.pem

我的.cnf:

[mysqld]
ssl_key=/var/ssl/server-key.pem
ssl_cert=/var/ssl/server-cert.pem
ssl_ca=/var/ssl/ca/ca.pem

我的证书似乎是正确的,因为它们适用于旧版本,还是有我没有发现的错误?是否有我不知道的从 8.0.18 到 8.0.19 的行为变化?还有什么我想念的吗?

标签: mysqldockerssljdbc

解决方案


推荐阅读