blob: 891cf69aaaa99c632d7b813a0682d00f4198163b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.avatica;
import org.apache.calcite.avatica.jdbc.JdbcMeta;
import org.apache.calcite.avatica.remote.Driver;
import org.apache.calcite.avatica.remote.LocalService;
import org.apache.calcite.avatica.server.HttpServer;
import org.apache.calcite.avatica.util.DateTimeUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x500.style.RFC4519Style;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
/**
* Test case for Avatica with TLS connectors.
*/
@RunWith(Parameterized.class)
public class SslDriverTest {
private static final Logger LOG = LoggerFactory.getLogger(SslDriverTest.class);
private static final String KEYSTORE_PASSWORD = "avaticasecret";
private static final ConnectionSpec CONNECTION_SPEC = ConnectionSpec.HSQLDB;
private static final List<HttpServer> SERVERS_TO_STOP = new ArrayList<>();
@Parameters public static List<Object[]> parameters() throws Exception {
final ArrayList<Object[]> parameters = new ArrayList<>();
// Create a self-signed cert
File target = new File(System.getProperty("user.dir"), "target");
File keystore = new File(target, "avatica-test.jks");
if (keystore.isFile()) {
assertTrue("Failed to delete keystore: " + keystore, keystore.delete());
}
new CertTool().createSelfSignedCert(keystore, "avatica", KEYSTORE_PASSWORD);
// Create a LocalService around HSQLDB
final JdbcMeta jdbcMeta = new JdbcMeta(CONNECTION_SPEC.url,
CONNECTION_SPEC.username, CONNECTION_SPEC.password);
final LocalService localService = new LocalService(jdbcMeta);
for (Driver.Serialization serialization : new Driver.Serialization[] {
Driver.Serialization.JSON, Driver.Serialization.PROTOBUF}) {
// Build and start the server, using TLS
HttpServer httpServer = new HttpServer.Builder()
.withPort(0)
.withTLS(keystore, KEYSTORE_PASSWORD, keystore, KEYSTORE_PASSWORD)
.withHandler(localService, serialization)
.build();
httpServer.start();
SERVERS_TO_STOP.add(httpServer);
final String url = "jdbc:avatica:remote:url=https://localhost:" + httpServer.getPort()
+ ";serialization=" + serialization + ";truststore=" + keystore.getAbsolutePath()
+ ";truststore_password=" + KEYSTORE_PASSWORD;
LOG.info("JDBC URL {}", url);
parameters.add(new Object[] {url});
}
return parameters;
}
@BeforeClass public static void setupClass() {
// Skip TLS testing on IBM Java due the combination of:
// - Jetty 9.4.12+ ignores SSL_* ciphers due to security - eclipse/jetty.project#2807
// - IBM uses SSL_* cipher names for ALL ciphers not following RFC cipher names
// See eclipse/jetty.project#2807 for details
assumeFalse(System.getProperty("java.vendor").contains("IBM"));
}
@AfterClass public static void stopKdc() {
for (HttpServer server : SERVERS_TO_STOP) {
server.stop();
}
}
private final String jdbcUrl;
public SslDriverTest(String jdbcUrl) {
this.jdbcUrl = Objects.requireNonNull(jdbcUrl);
}
@Test
public void testReadWrite() throws Exception {
final String tableName = "testReadWrite";
try (Connection conn = DriverManager.getConnection(jdbcUrl);
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
assertFalse(stmt.execute("CREATE TABLE " + tableName + "(pk integer)"));
assertEquals(1, stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"));
assertEquals(1, stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(2)"));
assertEquals(1, stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(3)"));
ResultSet results = stmt.executeQuery("SELECT count(1) FROM " + tableName);
assertTrue(results.next());
assertEquals(3, results.getInt(1));
}
}
/**
* Utility class for creating certificates for testing.
*/
private static class CertTool {
private static final String SIGNING_ALGORITHM = "SHA256WITHRSA";
private static final String ENC_ALGORITHM = "RSA";
static {
Security.addProvider(new BouncyCastleProvider());
}
private void createSelfSignedCert(File targetKeystore, String keyName,
String keystorePassword) {
if (targetKeystore.exists()) {
throw new RuntimeException("Keystore already exists: " + targetKeystore);
}
try {
KeyPair kp = generateKeyPair();
X509Certificate cert = generateCert(keyName, kp, true, kp.getPublic(),
kp.getPrivate());
char[] password = keystorePassword.toCharArray();
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null, null);
keystore.setCertificateEntry(keyName + "Cert", cert);
keystore.setKeyEntry(keyName + "Key", kp.getPrivate(), password, new Certificate[] {cert});
try (FileOutputStream fos = new FileOutputStream(targetKeystore)) {
keystore.store(fos, password);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator gen = KeyPairGenerator.getInstance(ENC_ALGORITHM);
gen.initialize(2048);
return gen.generateKeyPair();
}
private X509Certificate generateCert(String keyName, KeyPair kp, boolean isCertAuthority,
PublicKey signerPublicKey, PrivateKey signerPrivateKey)
throws IOException, OperatorCreationException, CertificateException,
NoSuchAlgorithmException {
Calendar startDate = DateTimeUtils.calendar();
Calendar endDate = DateTimeUtils.calendar();
endDate.add(Calendar.YEAR, 100);
BigInteger serialNumber = BigInteger.valueOf(startDate.getTimeInMillis());
X500Name issuer = new X500Name(
IETFUtils.rDNsFromString("cn=localhost", RFC4519Style.INSTANCE));
JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(issuer,
serialNumber, startDate.getTime(), endDate.getTime(), issuer, kp.getPublic());
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
certGen.addExtension(Extension.subjectKeyIdentifier, false,
extensionUtils.createSubjectKeyIdentifier(kp.getPublic()));
certGen.addExtension(Extension.basicConstraints, false,
new BasicConstraints(isCertAuthority));
certGen.addExtension(Extension.authorityKeyIdentifier, false,
extensionUtils.createAuthorityKeyIdentifier(signerPublicKey));
if (isCertAuthority) {
certGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
}
X509CertificateHolder certificateHolder = certGen.build(
new JcaContentSignerBuilder(SIGNING_ALGORITHM).build(signerPrivateKey));
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
}
}
}
// End SslDriverTest.java