package org.mariadb.jdbc;
import com.sun.jna.Platform;
import org.junit.*;
import java.io.*;
import java.lang.reflect.Field;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.sql.*;
import java.util.Collection;
import java.util.Properties;
import java.util.UUID;
import static org.junit.Assert.*;
public class SslTest extends BaseTest {
String serverCertificatePath;
String clientKeystorePath;
String clientKeystorePassword;
/**
* Enable Crypto.
*/
@BeforeClass
public static void enableCrypto() throws Exception {
Assume.assumeFalse("MAXSCALE".equals(System.getenv("TYPE")));
try {
Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
field.setAccessible(true);
field.set(null, Boolean.FALSE);
} catch (Exception ex) {
}
}
/**
* Check requirement.
*
* @throws SQLException exception exception
*/
@Before
public void checkSsl() throws SQLException {
boolean isJava7 = System.getProperty("java.version").contains("1.7.");
cancelForVersion(5, 6, 36); //has SSL issues with client authentication.
Assume.assumeTrue(haveSsl(sharedConnection));
//Skip SSL test on java 7 since SSL stream size JDK-6521495).
Assume.assumeFalse(isJava7);
if (System.getProperty("serverCertificatePath") == null) {
try (ResultSet rs = sharedConnection.createStatement().executeQuery("select @@ssl_cert")) {
rs.next();
serverCertificatePath = rs.getString(1);
}
} else {
serverCertificatePath = System.getProperty("serverCertificatePath");
}
clientKeystorePath = System.getProperty("keystorePath");
clientKeystorePassword = System.getProperty("keystorePassword");
Statement stmt = sharedConnection.createStatement();
try {
stmt.execute("DROP USER 'ssltestUser'@'%'");
} catch (SQLException e) {
}
stmt.execute("CREATE USER 'ssltestUser'@'%'");
stmt.execute("GRANT ALL PRIVILEGES ON *.* TO 'ssltestUser'@'%' REQUIRE SSL");
}
@Test
public void useSsl() throws Exception {
Assume.assumeTrue(haveSsl(sharedConnection));
//Skip SSL test on java 7 since SSL stream size JDK-6521495).
Assume.assumeFalse(System.getProperty("java.version").contains("1.7."));
try (Connection connection = setConnection("&useSSL=true&trustServerCertificate=true")) {
connection.createStatement().execute("select 1");
}
}
protected void useSslForceTls(String tls) throws Exception {
useSslForceTls(tls, null);
}
/**
* Helper method when checking/enabling secure connections for a specific TLS protocol suite.
**/
protected void useSslForceTls(String tls, String ciphers) throws Exception {
Assume.assumeTrue(haveSsl(sharedConnection));
//Skip SSL test on java 7 since SSL stream size JDK-6521495).
Assume.assumeFalse(System.getProperty("java.version").contains("1.7."));
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustServerCertificate", "true");
info.setProperty("enabledSslProtocolSuites", tls);
if (ciphers != null) info.setProperty("enabledSslCipherSuites", ciphers);
try (Connection connection = setConnection(info)) {
connection.createStatement().execute("select 1");
}
}
@Test
public void testBadOptionEnabledSslProtocolSuites() throws Exception {
try {
useSslForceTls("TLSv1,TLSv1.5");
fail("Must have thrown error since protocol unknown");
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Unsupported SSL protocol 'TLSv1.5'. Supported protocols : "));
}
}
@Test
public void testUnknownProtocol() throws Exception {
try {
useSslForceTls("TLSv0");
fail("Must have thrown error since protocol not set");
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Unsupported SSL protocol 'TLSv0'. Supported protocols : "));
}
}
@Test
public void testServerRefuseProtocol() throws Exception {
try {
useSslForceTls("SSLv3");
fail("Must have thrown error since protocol is refused by server");
} catch (SQLNonTransientConnectionException e) {
assertTrue(e.getMessage().contains("No appropriate protocol "
+ "(protocol is disabled or cipher suites are inappropriate)"));
}
}
@Test
public void useSslForceTlsV1() throws Exception {
useSslForceTls("TLSv1");
}
@Test
public void useSslForceTlsV11() throws Exception {
// must either be mariadb or mysql version 5.7.10
if (isMariadbServer() || minVersion(5, 7)) useSslForceTls("TLSv1.1");
}
@Test
public void useSslForceTlsV12() throws Exception {
Assume.assumeFalse(Platform.isWindows());
// Only test with MariaDB since MySQL community is compiled with yaSSL
if (isMariadbServer()) useSslForceTls("TLSv1.2");
}
@Test
public void useSslForceTlsV12AndCipher() throws Exception {
Assume.assumeFalse(Platform.isWindows());
// Only test with MariaDB since MySQL community is compiled with yaSSL
if (isMariadbServer()) {
useSslForceTls("TLSv1.2", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256");
}
}
@Test
public void wrongCipher() throws Exception {
// Only test with MariaDB since MySQL community is compiled with yaSSL
try {
if (isMariadbServer()) {
useSslForceTls("TLSv1.2", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, UNKNOWN_CIPHER");
fail("Must have thrown error since cipher is refused by server");
}
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Unsupported SSL cipher 'UNKNOWN_CIPHER'."));
}
}
@Test
public void wrongCipherForTls11() throws Exception {
// Only test with MariaDB since MySQL community is compiled with yaSSL
try {
if (isMariadbServer()) {
useSslForceTls("TLSv1.1", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256");
fail("Must have thrown error since cipher aren't TLSv1.1 ciphers");
}
} catch (SQLException e) {
assertTrue(e.getMessage().contains("No appropriate protocol (protocol is disabled or cipher suites are inappropriate)"));
}
}
@Test
public void wrongCipherMysqlOptionCompatibility() throws Exception {
// Only test with MariaDB since MySQL community is compiled with yaSSL
try {
if (isMariadbServer()) {
Assume.assumeTrue(haveSsl(sharedConnection));
//Skip SSL test on java 7 since SSL stream size JDK-6521495).
Assume.assumeFalse(System.getProperty("java.version").contains("1.7."));
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustServerCertificate", "true");
info.setProperty("enabledSslProtocolSuites", "TLSv1.1");
//enabledSSLCipherSuites, not enabledSslCipherSuites (different case)
info.setProperty("enabledSSLCipherSuites", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256");
try (Connection connection = setConnection(info)) {
connection.createStatement().execute("select 1");
fail("Must have thrown error since cipher aren't TLSv1.1 ciphers");
}
}
} catch (SQLException e) {
assertTrue(e.getMessage().contains("No appropriate protocol (protocol is disabled or cipher suites are inappropriate)"));
}
}
@Test
public void useSslForceTlsCombination() throws Exception {
if (isMariadbServer() && !Platform.isWindows()) {
useSslForceTls("TLSv1,TLSv1.1,TLSv1.2");
} else {
useSslForceTls("TLSv1,TLSv1");
}
}
@Test
public void useSslForceTlsCombinationWithSpace() throws Exception {
if (isMariadbServer() && !Platform.isWindows()) {
useSslForceTls("TLSv1, TLSv1.1, TLSv1.2");
} else {
useSslForceTls("TLSv1, TLSv1");
}
}
@Test
public void useSslForceTlsCombinationWithOnlySpace() throws Exception {
if (isMariadbServer() && !Platform.isWindows()) {
useSslForceTls("TLSv1 TLSv1.1 TLSv1.2");
} else {
useSslForceTls("TLSv1 TLSv1");
}
}
private String getServerCertificate() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(serverCertificatePath)))) {
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void saveToFile(String path, String contents) {
try (PrintWriter out = new PrintWriter(new FileOutputStream(path))) {
out.print(contents);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Connection createConnection(Properties info) throws SQLException {
return createConnection(info, username, password);
}
private Connection createConnection(Properties info, String user, String pwd) throws SQLException {
String jdbcUrl = connUri;
Properties connProps = new Properties(info);
connProps.setProperty("user", user);
if (pwd != null) {
connProps.setProperty("password", pwd);
}
return openNewConnection(jdbcUrl, connProps);
}
/**
* Test connection.
*
* @param info connection properties
* @param sslExpected is SSL expected
* @throws SQLException exception
*/
public void testConnect(Properties info, boolean sslExpected) throws SQLException {
testConnect(info, sslExpected, "ssltestUser", "");
}
/**
* Test connection.
*
* @param info connection properties
* @param sslExpected is SSL expected
* @param user user
* @param pwd password
* @throws SQLException if exception occur
*/
public void testConnect(Properties info, boolean sslExpected, String user, String pwd) throws SQLException {
try (Connection conn = createConnection(info, user, pwd)) {
// First do a basic select test:
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT 1")) {
assertTrue(rs.next());
assertTrue(rs.getInt(1) == 1);
}
// Then check if SSL matches what is expected
try (ResultSet rs = stmt.executeQuery("SHOW STATUS LIKE 'Ssl_cipher'")) {
assertTrue(rs.next());
String sslCipher = rs.getString(2);
boolean sslActual = sslCipher != null && sslCipher.length() > 0;
assertEquals("sslExpected does not match", sslExpected, sslActual);
}
}
}
}
@Test
public void testConnectNonSsl() throws SQLException {
Properties info = new Properties();
try {
testConnect(info, false);
fail("Must fail since user require SSL");
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Access denied for user 'ssltestUser'"));
}
}
@Test
public void testTrustedServer() throws SQLException {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustServerCertificate", "true");
testConnect(info, true);
}
@Test
public void testServerCertString() throws SQLException {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", getServerCertificate());
testConnect(info, true);
}
@Test(expected = SQLException.class)
public void testBadServerCertString() throws SQLException {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", "foobar");
testConnect(info, true);
}
@Test
public void testServerCertFile() throws SQLException {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", serverCertificatePath);
testConnect(info, true);
}
@Test
public void testServerCertClasspathFile() throws SQLException {
Assume.assumeTrue(new File("target/classes").isDirectory());
Properties info = new Properties();
info.setProperty("useSSL", "true");
// Copy the valid server certificate to a known location on the classpath:
String classpathFilename = "server-ssl-cert.pem";
saveToFile("target/classes/" + classpathFilename, getServerCertificate());
info.setProperty("serverSslCert", "classpath:" + classpathFilename);
testConnect(info, true);
}
@Test(expected = SQLException.class)
public void testWrongServerCert() throws Throwable {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", "classpath:ssl/wrong-server.crt");
testConnect(info, true);
}
@Test
public void conc71() throws SQLException {
try {
Properties info = new Properties();
info.setProperty("serverSslCert", getServerCertificate());
info.setProperty("useSSL", "true");
try (Connection conn = createConnection(info)) {
assertEquals("testj", conn.getCatalog());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void testTruststore() throws SQLException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
// generate a truststore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, null);
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustStore", "file://" + keystorePath);
testConnect(info, true);
} catch (SQLNonTransientConnectionException nonTransient) {
//java 9 doesn't accept empty keystore
} finally {
tempKeystore.delete();
}
}
@Test
public void testTrustStoreWithPassword() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, SQLException {
// generate a truststore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, "mysecret");
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustStore", "file://" + keystorePath);
info.setProperty("trustStorePassword", "mysecret");
testConnect(info, true);
} finally {
tempKeystore.delete();
}
}
@Test
public void testTrustStoreWithPasswordProperties() throws Exception {
// generate a truststore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
String initialTrustStore = System.getProperty("javax.net.ssl.trustStore");
String initialTrustStorePwd = System.getProperty("javax.net.ssl.trustStorePassword");
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, "mysecret");
System.setProperty("javax.net.ssl.trustStore", keystorePath);
System.setProperty("javax.net.ssl.trustStorePassword", "mysecret");
Properties info = new Properties();
info.setProperty("useSSL", "true");
testConnect(info, true);
} finally {
if (initialTrustStore == null) {
System.clearProperty("javax.net.ssl.trustStore");
} else {
System.setProperty("javax.net.ssl.trustStore", initialTrustStore);
}
if (initialTrustStorePwd == null) {
System.clearProperty("javax.net.ssl.trustStorePassword");
} else {
System.setProperty("javax.net.ssl.trustStorePassword", initialTrustStorePwd);
}
tempKeystore.delete();
}
}
@Test(expected = SQLException.class)
public void testTruststoreWithWrongPassword() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException,
SQLException {
// generate a truststore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, "mysecret");
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustStore", "file://" + keystorePath);
info.setProperty("trustStorePassword", "notthepassword");
testConnect(info, true);
} finally {
tempKeystore.delete();
}
}
@Test(expected = SQLException.class)
public void testTruststoreWithWrongCert() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, SQLException {
// generate a truststore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
try {
generateKeystoreFromFile("classpath:ssl/wrong-server.crt", keystorePath, "mysecret");
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustStore", "file://" + keystorePath);
info.setProperty("trustStorePassword", "mysecret");
testConnect(info, true);
} finally {
tempKeystore.delete();
}
}
@Test
public void testTruststoreAndClientKeystore() throws SQLException, IOException, KeyStoreException, CertificateException,
NoSuchAlgorithmException {
// This test only runs if a client keystore and password have been passed in as properties (-DkeystorePath and -DkeystorePassword)
// You can create a keystore as follows:
// echo "kspass" | openssl pkcs12 -export -in "${clientCertFile}" -inkey "${clientKeyFile}" -out "${clientKeystoreFile}"
// -name "mysqlAlias" -pass stdin
Assume.assumeTrue(clientKeystorePathDefined());
String testUser = "testTsAndKs";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
// generate a truststore from the canned server certificate
File tempTruststore = File.createTempFile("truststore", ".tmp");
String truststorePath = tempTruststore.getAbsolutePath();
try {
generateKeystoreFromFile(serverCertificatePath, truststorePath, null);
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustStore", "file://" + truststorePath);
info.setProperty("keyStore", "file://" + clientKeystorePath);
info.setProperty("keyStorePassword", clientKeystorePassword);
testConnect(info, true, testUser, "ssltestpassword");
} catch (SQLNonTransientConnectionException nonTransient) {
//java 9 doesn't accept empty keystore
} finally {
tempTruststore.delete();
deleteSslTestUser(testUser);
}
}
@Test
public void testAliases() throws SQLException, IOException, KeyStoreException, CertificateException,
NoSuchAlgorithmException {
// This test only runs if a client keystore and password have been passed in as properties (-DkeystorePath and -DkeystorePassword)
// You can create a keystore as follows:
// echo "kspass" | openssl pkcs12 -export -in "${clientCertFile}" -inkey "${clientKeyFile}" -out "${clientKeystoreFile}"
// -name "mysqlAlias" -pass stdin
Assume.assumeTrue(clientKeystorePathDefined());
String testUser = "testTsAndKs";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
// generate a truststore from the canned server certificate
File tempTruststore = File.createTempFile("truststore", ".tmp");
String truststorePath = tempTruststore.getAbsolutePath();
try {
generateKeystoreFromFile(serverCertificatePath, truststorePath, "trustPwd");
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustCertificateKeyStoreUrl", "file://" + truststorePath);
info.setProperty("trustCertificateKeyStorePassword", "trustPwd");
info.setProperty("clientCertificateKeyStoreUrl", "file://" + clientKeystorePath);
info.setProperty("clientCertificateKeyStorePassword", clientKeystorePassword);
testConnect(info, true, testUser, "ssltestpassword");
} finally {
tempTruststore.delete();
deleteSslTestUser(testUser);
}
}
@Test
public void testClientKeystore() throws SQLException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
// This test only runs if a client keystore and password have been passed in as properties (-DkeystorePath and -DkeystorePassword)
// You can create a keystore as follows:
// echo "kspass" | openssl pkcs12 -export -in "${clientCertFile}" -inkey "${clientKeyFile}" -out "${clientKeystoreFile}"
// -name "mysqlAlias" -pass stdin
Assume.assumeTrue(clientKeystorePathDefined());
String testUser = "testKeystore";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
try {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", serverCertificatePath);
info.setProperty("keyStore", "file://" + clientKeystorePath);
info.setProperty("keyStorePassword", clientKeystorePassword);
testConnect(info, true, testUser, "ssltestpassword");
} finally {
deleteSslTestUser(testUser);
}
}
/**
* Verification when private key password differ from keyStore password.
*
* @throws Exception if error occur
*/
@Test
public void testClientKeyStoreWithPrivateKeyPwd() throws Exception {
String clientKeyStore2Path = System.getProperty("keystore2Path");
String clientKeyStore2Password = System.getProperty("keystore2Password");
String clientKeyPassword = System.getProperty("keyPassword");
Assume.assumeTrue(clientKeyPassword != null
&& clientKeyStore2Password != null
&& clientKeyStore2Path != null);
String testUser = "testKeystore";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
//without keyPassword
try {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", serverCertificatePath);
info.setProperty("keyStore", "file://" + clientKeyStore2Path);
info.setProperty("keyStorePassword", clientKeyStore2Password);
testConnect(info, true, testUser, "ssltestpassword");
fail("Must have Error since client private key is protected with a password different than keystore");
} catch (SQLException sqle) {
sqle.printStackTrace();
assertTrue(sqle.getMessage().contains("Access denied for user"));
}
try {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", serverCertificatePath);
info.setProperty("keyStore", "file://" + clientKeyStore2Path);
info.setProperty("keyStorePassword", clientKeyStore2Password);
info.setProperty("keyPassword", clientKeyPassword);
testConnect(info, true, testUser, "ssltestpassword");
} finally {
deleteSslTestUser(testUser);
}
}
/**
* Verification when private key password differ from keyStore password.
*
* @throws Exception if error occur
*/
@Test
public void testClientKeyStorePkcs12() throws Exception {
String clientKeyStore2Path = System.getProperty("keystore2PathP12");
String clientKeyStore2Password = System.getProperty("keystore2Password");
Assume.assumeTrue(clientKeyStore2Password != null && clientKeyStore2Path != null);
String testUser = "testKeystore";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
try {
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("serverSslCert", serverCertificatePath);
info.setProperty("keyStore", "file://" + clientKeyStore2Path);
info.setProperty("keyStorePassword", clientKeyStore2Password);
testConnect(info, true, testUser, "ssltestpassword");
} finally {
deleteSslTestUser(testUser);
}
}
@Test
public void testKeyStoreWithProperties() throws Exception {
Assume.assumeNotNull(clientKeystorePath);
// generate a trustStore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
String initialTrustStore = System.getProperty("javax.net.ssl.trustStore");
String initialTrustStorePwd = System.getProperty("javax.net.ssl.trustStorePassword");
String initialKeyStore = System.getProperty("javax.net.ssl.keyStore");
String initialKeyStorePwd = System.getProperty("javax.net.ssl.keyStorePassword");
String testUser = "testKeystore";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, "mysecret");
System.setProperty("javax.net.ssl.trustStore", keystorePath);
System.setProperty("javax.net.ssl.trustStorePassword", "mysecret");
System.setProperty("javax.net.ssl.keyStore", clientKeystorePath);
System.setProperty("javax.net.ssl.keyStorePassword", clientKeystorePassword);
Properties info = new Properties();
info.setProperty("useSSL", "true");
testConnect(info, true, testUser, "ssltestpassword");
} finally {
if (initialTrustStore == null) {
System.clearProperty("javax.net.ssl.trustStore");
} else {
System.setProperty("javax.net.ssl.trustStore", initialTrustStore);
}
if (initialTrustStorePwd == null) {
System.clearProperty("javax.net.ssl.trustStorePassword");
} else {
System.setProperty("javax.net.ssl.trustStorePassword", initialTrustStorePwd);
}
if (initialKeyStore != null) {
System.setProperty("javax.net.ssl.keyStore", initialKeyStore);
} else {
System.clearProperty("javax.net.ssl.keyStore");
}
if (initialKeyStorePwd != null) {
System.setProperty("javax.net.ssl.keyStorePassword", initialKeyStorePwd);
} else {
System.clearProperty("javax.net.ssl.keyStorePassword");
}
tempKeystore.delete();
}
}
@Test
public void testKeyStoreWhenServerTrustedWithProperties() throws Exception {
Assume.assumeNotNull(clientKeystorePath);
// generate a trustStore from the canned serverCertificate
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
String initialTrustStore = System.getProperty("javax.net.ssl.trustStore");
String initialTrustStorePwd = System.getProperty("javax.net.ssl.trustStorePassword");
String initialKeyStore = System.getProperty("javax.net.ssl.keyStore");
String initialKeyStorePwd = System.getProperty("javax.net.ssl.keyStorePassword");
String testUser = "testKeystore";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, "mysecret");
System.clearProperty("javax.net.ssl.trustStore");
System.clearProperty("javax.net.ssl.trustStorePassword");
System.setProperty("javax.net.ssl.keyStore", clientKeystorePath);
System.setProperty("javax.net.ssl.keyStorePassword", clientKeystorePassword);
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustServerCertificate", "true");
testConnect(info, true, testUser, "ssltestpassword");
} finally {
if (initialTrustStore != null) {
System.setProperty("javax.net.ssl.trustStore", initialTrustStore);
}
if (initialTrustStorePwd != null) {
System.setProperty("javax.net.ssl.trustStorePassword", initialTrustStorePwd);
}
if (initialKeyStore != null) {
System.setProperty("javax.net.ssl.keyStore", initialKeyStore);
} else {
System.clearProperty("javax.net.ssl.keyStore");
}
if (initialKeyStorePwd != null) {
System.setProperty("javax.net.ssl.keyStorePassword", initialKeyStorePwd);
} else {
System.clearProperty("javax.net.ssl.keyStorePassword");
}
tempKeystore.delete();
}
}
@Test
public void testClientKeyStoreProperties() throws SQLException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
// This test only runs if a client keystore and password have been passed in as properties (-DkeystorePath and -DkeystorePassword)
// You can create a keystore as follows:
// echo "kspass" | openssl pkcs12 -export -in "${clientCertFile}" -inkey "${clientKeyFile}" -out "${clientKeystoreFile}"
// -name "mysqlAlias" -pass stdin
Assume.assumeTrue(clientKeystorePathDefined());
File tempKeystore = File.createTempFile("keystore", ".tmp");
String keystorePath = tempKeystore.getAbsolutePath();
String testUser = "testKeystore";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
String initialTrustStore = System.getProperty("javax.net.ssl.trustStore");
String initialTrustStorePwd = System.getProperty("javax.net.ssl.trustStorePassword");
try {
generateKeystoreFromFile(serverCertificatePath, keystorePath, "mysecret");
System.setProperty("javax.net.ssl.trustStore", keystorePath);
System.setProperty("javax.net.ssl.trustStorePassword", "mysecret");
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("keyStore", "file://" + clientKeystorePath);
info.setProperty("keyStorePassword", clientKeystorePassword);
testConnect(info, true, testUser, "ssltestpassword");
} finally {
if (initialTrustStore == null) {
System.clearProperty("javax.net.ssl.trustStore");
} else {
System.setProperty("javax.net.ssl.trustStore", initialTrustStore);
}
if (initialTrustStorePwd == null) {
System.clearProperty("javax.net.ssl.trustStorePassword");
} else {
System.setProperty("javax.net.ssl.trustStorePassword", initialTrustStorePwd);
}
deleteSslTestUser(testUser);
}
}
@Test(expected = SQLException.class)
public void testTruststoreAndClientKeystoreWrongPassword() throws SQLException, IOException, KeyStoreException, CertificateException,
NoSuchAlgorithmException {
// This test only runs if a client keystore and password have been passed in as properties (-DkeystorePath and -DkeystorePassword)
// You can create a keystore as follows:
// echo "kspass" | openssl pkcs12 -export -in "${clientCertFile}" -inkey "${clientKeyFile}"
// -out "${clientKeystoreFile}" -name "mysqlAlias" -pass stdin
Assume.assumeTrue(clientKeystorePathDefined());
String testUser = "testWrongPwd";
// For this testcase, the testUser must be configured with ssl_type=X509
createSslTestUser(testUser);
// generate a truststore from the canned server certificate
File tempTruststore = File.createTempFile("truststore", ".tmp");
String truststorePath = tempTruststore.getAbsolutePath();
try {
generateKeystoreFromFile(serverCertificatePath, truststorePath, null);
Properties info = new Properties();
info.setProperty("useSSL", "true");
info.setProperty("trustStore", "file://" + truststorePath);
info.setProperty("keyStore", "file://" + clientKeystorePath);
info.setProperty("keyStorePassword", "notthekeystorepass");
testConnect(info, true, testUser, "ssltestpassword");
} finally {
tempTruststore.delete();
deleteSslTestUser(testUser);
}
}
private boolean clientKeystorePathDefined() throws SQLException {
return clientKeystorePath != null && !clientKeystorePath.isEmpty() && clientKeystorePassword != null && !clientKeystorePassword.isEmpty();
}
private void createSslTestUser(String user) throws SQLException {
Statement st = sharedConnection.createStatement();
st.execute("grant all privileges on *.* to '" + user + "'@'%' identified by 'ssltestpassword' REQUIRE X509");
}
private void deleteSslTestUser(String user) throws SQLException {
Statement st = sharedConnection.createStatement();
st.execute("drop user '" + user + "'@'%'");
}
private void generateKeystoreFromFile(String certificateFile, String keystoreFile, String password)
throws KeyStoreException, CertificateException,
IOException, NoSuchAlgorithmException {
InputStream inStream = null;
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// generate a keystore from the provided cert
if (certificateFile.startsWith("classpath:")) {
// Load it from a classpath relative file
String classpathFile = certificateFile.substring("classpath:".length());
inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(classpathFile);
} else {
inStream = new FileInputStream(certificateFile);
}
ks = KeyStore.getInstance(KeyStore.getDefaultType());
try {
// Note: KeyStore requires it be loaded even if you don't load anything into it:
ks.load(null);
} catch (Exception e) {
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> caList = cf.generateCertificates(inStream);
inStream.close();
for (Certificate ca : caList) {
ks.setCertificateEntry(UUID.randomUUID().toString(), ca);
}
try (ByteArrayOutputStream keyStoreOut = new ByteArrayOutputStream()) {
ks.store(keyStoreOut, password == null ? new char[0] : password.toCharArray());
// write the key to the file system
try (FileOutputStream keyStoreStream = new FileOutputStream(keystoreFile)) {
keyStoreStream.write(keyStoreOut.toByteArray());
}
}
}
}