Mercurial > hg > monetdb-java
view tests/TLSTester.java @ 970:f90d811e97eb default tip
Adjust getTableTypes() test for new table type: LOCAL TEMPORARY VIEW, added in 11.53.4 (Mar2025-SP1)
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 03 Apr 2025 15:01:33 +0200 (32 hours ago) |
parents | ff075ed5ce81 |
children |
line wrap: on
line source
/* * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2024, 2025 MonetDB Foundation; * Copyright August 2008 - 2023 MonetDB B.V.; * Copyright 1997 - July 2008 CWI. */ import org.monetdb.mcl.net.Parameter; import java.io.*; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.HashMap; import java.util.HashSet; import java.util.Properties; public final class TLSTester { final HashMap<String, File> fileCache = new HashMap<>(); int verbose = 0; String serverHost = null; String altHost = null; int serverPort = -1; boolean enableTrusted = false; File tempDir = null; private final HashSet<String> preparedButNotRun = new HashSet<>(); public TLSTester(String[] args) { for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-v")) { verbose = 1; } else if (arg.equals("-a")) { altHost = args[++i]; } else if (arg.equals("-t")) { enableTrusted = true; } else if (!arg.startsWith("-") && serverHost == null) { int idx = arg.indexOf(':'); if (idx > 0) { serverHost = arg.substring(0, idx); try { serverPort = Integer.parseInt(arg.substring(idx + 1)); if (serverPort > 0 && serverPort < 65536) continue; } catch (NumberFormatException ignored) { } } // if we get here it wasn't very valid throw new IllegalArgumentException("Invalid argument: " + arg); } else { throw new IllegalArgumentException("Unexpected argument: " + arg); } } } public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException { Class.forName("org.monetdb.jdbc.MonetDriver"); TLSTester main = new TLSTester(args); main.run(); } private HashMap<String, Integer> loadPortMap(String testName) throws IOException { HashMap<String, Integer> portMap = new HashMap<>(); InputStream in = fetchData("/?test=" + testName); BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); for (String line = br.readLine(); line != null; line = br.readLine()) { int idx = line.indexOf(':'); String service = line.substring(0, idx); int port; try { port = Integer.parseInt(line.substring(idx + 1)); } catch (NumberFormatException e) { throw new RuntimeException("Invalid port map line: " + line); } portMap.put(service, port); } return portMap; } private File resource(String resource) throws IOException { if (!fileCache.containsKey(resource)) fetchResource(resource); return fileCache.get(resource); } private void fetchResource(String resource) throws IOException { if (!resource.startsWith("/")) { throw new IllegalArgumentException("Resource must start with slash: " + resource); } if (tempDir == null) { tempDir = Files.createTempDirectory("tlstest").toFile(); tempDir.deleteOnExit(); } File outPath = new File(tempDir, resource.substring(1)); try (InputStream in = fetchData(resource); FileOutputStream out = new FileOutputStream(outPath)) { byte[] buffer = new byte[12]; while (true) { int n = in.read(buffer); if (n <= 0) break; out.write(buffer, 0, n); } } fileCache.put(resource, outPath); } private InputStream fetchData(String resource) throws IOException { String urlText = "http://" + serverHost + ":" + serverPort + resource; if (verbose > 0) { System.out.println("Fetching " + resource + " from " + urlText); } URL url = null; try { url = new java.net.URI(urlText).toURL(); URLConnection conn = url.openConnection(); conn.connect(); return conn.getInputStream(); } catch (URISyntaxException | IOException e) { throw new IOException("Cannot fetch resource " + resource + " from " + urlText + ": " + e, e); } } private void run() throws IOException, SQLException { test_connect_plain(); test_connect_tls(); test_refuse_no_cert(); test_refuse_wrong_cert(); test_refuse_wrong_host(); test_refuse_tlsv12(); test_refuse_expired(); // test_connect_client_auth1(); // test_connect_client_auth2(); test_fail_tls_to_plain(); test_fail_plain_to_tls(); test_connect_server_name(); test_connect_alpn_mapi9(); test_connect_trusted(); test_refuse_trusted_wrong_host(); // did we forget to call expectSucceed and expectFailure somewhere? if (!preparedButNotRun.isEmpty()) { String names = String.join(", ", preparedButNotRun); throw new RuntimeException("Not all tests called expectSuccess/expectFailure: " + names); } } private void test_connect_plain() throws IOException, SQLException { attempt("connect_plain", "plain").with(Parameter.TLS, false).expectSuccess(); } private void test_connect_tls() throws IOException, SQLException { Attempt attempt = attempt("connect_tls", "server1"); attempt.withFile(Parameter.CERT, "/ca1.crt").expectSuccess(); } private void test_refuse_no_cert() throws IOException, SQLException { attempt("refuse_no_cert", "server1").expectFailure("PKIX path building failed"); } private void test_refuse_wrong_cert() throws IOException, SQLException { Attempt attempt = attempt("refuse_wrong_cert", "server1"); attempt.withFile(Parameter.CERT, "/ca2.crt").expectFailure("PKIX path building failed"); } private void test_refuse_wrong_host() throws IOException, SQLException { if (altHost == null) return; Attempt attempt = attempt("refuse_wrong_host", "server1").with(Parameter.HOST, altHost); attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("No subject alternative DNS name"); } private void test_refuse_tlsv12() throws IOException, SQLException { Attempt attempt = attempt("refuse_tlsv12", "tls12"); attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("protocol_version"); } private void test_refuse_expired() throws IOException, SQLException { Attempt attempt = attempt("refuse_expired", "expiredcert"); attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("PKIX path validation failed"); } private void test_connect_client_auth1() throws IOException, SQLException { attempt("connect_client_auth1", "clientauth") .withFile(Parameter.CERT, "/ca1.crt") .withFile(Parameter.CLIENTKEY, "/client2.keycrt") .expectSuccess(); } private void test_connect_client_auth2() throws IOException, SQLException { attempt("connect_client_auth2", "clientauth") .withFile(Parameter.CERT, "/ca1.crt") .withFile(Parameter.CLIENTKEY, "/client2.key") .withFile(Parameter.CLIENTCERT, "/client2.crt") .expectSuccess(); } private void test_fail_tls_to_plain() throws IOException, SQLException { Attempt attempt = attempt("fail_tls_to_plain", "plain"); attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure(""); } private void test_fail_plain_to_tls() throws IOException, SQLException { attempt("fail_plain_to_tls", "server1").with(Parameter.TLS, false).expectFailure("Cannot connect", "Could not connect"); } private void test_connect_server_name() throws IOException, SQLException { Attempt attempt = attempt("connect_server_name", "sni"); attempt.withFile(Parameter.CERT, "/ca1.crt").expectSuccess(); } private void test_connect_alpn_mapi9() throws IOException, SQLException { attempt("connect_alpn_mapi9", "alpn_mapi9").withFile(Parameter.CERT, "/ca1.crt").expectSuccess(); } private void test_connect_trusted() throws IOException, SQLException { attempt("connect_trusted", null) .with(Parameter.HOST, "monetdb.ergates.nl") .with(Parameter.PORT, 50000) .expectSuccess(); } private void test_refuse_trusted_wrong_host() throws IOException, SQLException { attempt("test_refuse_trusted_wrong_host", null) .with(Parameter.HOST, "monetdbxyz.ergates.nl") .with(Parameter.PORT, 50000) .expectFailure("No subject alternative DNS name"); } private Attempt attempt(String testName, String portName) throws IOException { preparedButNotRun.add(testName); return new Attempt(testName, portName); } private class Attempt { private final String testName; private final Properties props = new Properties(); boolean disabled = false; public Attempt(String testName, String portName) throws IOException { HashMap<String, Integer> portMap = loadPortMap(testName); this.testName = testName; with(Parameter.TLS, true); with(Parameter.HOST, serverHost); with(Parameter.SO_TIMEOUT, 3000); if (portName != null) { Integer port = portMap.get(portName); if (port != null) { with(Parameter.PORT, port); } else { throw new RuntimeException("Unknown port name: " + portName); } } } private Attempt with(Parameter parm, String value) { props.setProperty(parm.name, value); return this; } private Attempt with(Parameter parm, int value) { props.setProperty(parm.name, Integer.toString(value)); return this; } private Attempt with(Parameter parm, boolean value) { props.setProperty(parm.name, value ? "true" : "false"); return this; } private Attempt withFile(Parameter parm, String certResource) throws IOException { File certFile = resource(certResource); String path = certFile.getPath(); with(parm, path); return this; } public void expectSuccess() throws SQLException { preparedButNotRun.remove(testName); if (disabled) return; startVerbose(); try { Connection conn = DriverManager.getConnection("jdbc:monetdb:", props); conn.close(); throw new RuntimeException("Test " + testName + " was supposed to throw an Exception saying 'Sorry, this is not a real MonetDB instance'"); } catch (SQLException e) { if (e.getMessage().startsWith("Sorry, this is not a real MonetDB instance")) { // it looks like a failure but this is actually our success scenario // because this is what the TLS Tester does when the connection succeeds. endVerbose("successful MAPI handshake, as expected"); return; } // other exceptions ARE errors and should be reported. throw e; } } public void expectFailure(String... expectedMessages) throws SQLException { preparedButNotRun.remove(testName); if (disabled) return; startVerbose(); try { Connection conn = DriverManager.getConnection("jdbc:monetdb:", props); conn.close(); throw new RuntimeException("Expected test " + testName + " to throw an exception but it didn't"); } catch (SQLException e) { for (String expected : expectedMessages) { if (e.getMessage().contains(expected)) { endVerbose("connection failed as expected, message: " + e.getMessage()); return; } } String message = "Test " + testName + " threw the wrong exception: " + e.getMessage() + '\n' + "Expected:\n <" + String.join(">\n <", expectedMessages) + ">"; throw new RuntimeException(message, e); } } private void startVerbose() { if (verbose == 0) return; System.out.println("Test " + testName + ":"); for (String key: props.stringPropertyNames()) { Object value = props.get(key); if (value == null) System.out.println(" " + key + " is null"); else System.out.println(" " + key + " = " + value.toString()); } } private void endVerbose(String message) { if (verbose > 0) { System.out.println(" -> " + message); System.out.println(); } } } }