comparison tests/TLSTester.java @ 834:5aa19bbed0d6 monetdbs

Comments and formatting
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Wed, 13 Dec 2023 15:39:47 +0100 (17 months ago)
parents 2fee4b71baac
children 157dcb2d6516
comparison
equal deleted inserted replaced
813:a71afa48f269 834:5aa19bbed0d6
9 import java.sql.DriverManager; 9 import java.sql.DriverManager;
10 import java.sql.SQLException; 10 import java.sql.SQLException;
11 import java.util.HashMap; 11 import java.util.HashMap;
12 import java.util.HashSet; 12 import java.util.HashSet;
13 import java.util.Properties; 13 import java.util.Properties;
14 import java.util.stream.Collectors;
15 14
16 public class TLSTester { 15 public class TLSTester {
17 int verbose = 0; 16 final HashMap<String, File> fileCache = new HashMap<>();
18 String serverHost = null; 17 int verbose = 0;
19 String altHost = null; 18 String serverHost = null;
20 int serverPort = -1; 19 String altHost = null;
21 boolean enableTrusted = false; 20 int serverPort = -1;
22 File tempDir = null; 21 boolean enableTrusted = false;
23 final HashMap<String, File> fileCache = new HashMap<>(); 22 File tempDir = null;
24 private HashSet<String> preparedButNotRun = new HashSet<>(); 23 private final HashSet<String> preparedButNotRun = new HashSet<>();
25 24
26 public TLSTester(String[] args) { 25 public TLSTester(String[] args) {
27 for (int i = 0; i < args.length; i++) { 26 for (int i = 0; i < args.length; i++) {
28 String arg = args[i]; 27 String arg = args[i];
29 if (arg.equals("-v")) { 28 if (arg.equals("-v")) {
30 verbose = 1; 29 verbose = 1;
31 } else if (arg.equals("-a")) { 30 } else if (arg.equals("-a")) {
32 altHost = args[++i]; 31 altHost = args[++i];
33 } else if (arg.equals("-t")) { 32 } else if (arg.equals("-t")) {
34 enableTrusted = true; 33 enableTrusted = true;
35 } else if (!arg.startsWith("-") && serverHost == null) { 34 } else if (!arg.startsWith("-") && serverHost == null) {
36 int idx = arg.indexOf(':'); 35 int idx = arg.indexOf(':');
37 if (idx > 0) { 36 if (idx > 0) {
38 serverHost = arg.substring(0, idx); 37 serverHost = arg.substring(0, idx);
39 try { 38 try {
40 serverPort = Integer.parseInt(arg.substring(idx + 1)); 39 serverPort = Integer.parseInt(arg.substring(idx + 1));
41 if (serverPort > 0 && serverPort < 65536) 40 if (serverPort > 0 && serverPort < 65536)
42 continue; 41 continue;
43 } catch (NumberFormatException ignored) { 42 } catch (NumberFormatException ignored) {
44 } 43 }
45 } 44 }
46 // if we get here it wasn't very valid 45 // if we get here it wasn't very valid
47 throw new IllegalArgumentException("Invalid argument: " + arg); 46 throw new IllegalArgumentException("Invalid argument: " + arg);
48 } else { 47 } else {
49 throw new IllegalArgumentException("Unexpected argument: " + arg); 48 throw new IllegalArgumentException("Unexpected argument: " + arg);
50 } 49 }
51 } 50 }
52 } 51 }
53 52
54 public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException { 53 public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
55 Class.forName("org.monetdb.jdbc.MonetDriver"); 54 Class.forName("org.monetdb.jdbc.MonetDriver");
56 TLSTester main = new TLSTester(args); 55 TLSTester main = new TLSTester(args);
57 main.run(); 56 main.run();
58 } 57 }
59 58
60 private HashMap<String,Integer> loadPortMap(String testName) throws IOException { 59 private HashMap<String, Integer> loadPortMap(String testName) throws IOException {
61 HashMap<String,Integer> portMap = new HashMap<>(); 60 HashMap<String, Integer> portMap = new HashMap<>();
62 InputStream in = fetchData("/?test=" + testName); 61 InputStream in = fetchData("/?test=" + testName);
63 BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 62 BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
64 for (String line = br.readLine(); line != null; line = br.readLine()) { 63 for (String line = br.readLine(); line != null; line = br.readLine()) {
65 int idx = line.indexOf(':'); 64 int idx = line.indexOf(':');
66 String service = line.substring(0, idx); 65 String service = line.substring(0, idx);
67 int port; 66 int port;
68 try { 67 try {
69 port = Integer.parseInt(line.substring(idx + 1)); 68 port = Integer.parseInt(line.substring(idx + 1));
70 } catch (NumberFormatException e) { 69 } catch (NumberFormatException e) {
71 throw new RuntimeException("Invalid port map line: " + line); 70 throw new RuntimeException("Invalid port map line: " + line);
72 } 71 }
73 portMap.put(service, port); 72 portMap.put(service, port);
74 } 73 }
75 return portMap; 74 return portMap;
76 } 75 }
77 76
78 private File resource(String resource) throws IOException { 77 private File resource(String resource) throws IOException {
79 if (!fileCache.containsKey(resource)) 78 if (!fileCache.containsKey(resource))
80 fetchResource(resource); 79 fetchResource(resource);
81 return fileCache.get(resource); 80 return fileCache.get(resource);
82 } 81 }
83 82
84 private void fetchResource(String resource) throws IOException { 83 private void fetchResource(String resource) throws IOException {
85 if (!resource.startsWith("/")) { 84 if (!resource.startsWith("/")) {
86 throw new IllegalArgumentException("Resource must start with slash: " + resource); 85 throw new IllegalArgumentException("Resource must start with slash: " + resource);
87 } 86 }
88 if (tempDir == null) { 87 if (tempDir == null) {
89 tempDir = Files.createTempDirectory("tlstest").toFile(); 88 tempDir = Files.createTempDirectory("tlstest").toFile();
90 tempDir.deleteOnExit(); 89 tempDir.deleteOnExit();
91 } 90 }
92 File outPath = new File(tempDir, resource.substring(1)); 91 File outPath = new File(tempDir, resource.substring(1));
93 try (InputStream in = fetchData(resource); FileOutputStream out = new FileOutputStream(outPath)) { 92 try (InputStream in = fetchData(resource); FileOutputStream out = new FileOutputStream(outPath)) {
94 byte[] buffer = new byte[12]; 93 byte[] buffer = new byte[12];
95 while (true) { 94 while (true) {
96 int n = in.read(buffer); 95 int n = in.read(buffer);
97 if (n <= 0) 96 if (n <= 0)
98 break; 97 break;
99 out.write(buffer, 0, n); 98 out.write(buffer, 0, n);
100 } 99 }
101 } 100 }
102 fileCache.put(resource, outPath); 101 fileCache.put(resource, outPath);
103 } 102 }
104 103
105 private byte[] fetchBytes(String resource) throws IOException { 104 private byte[] fetchBytes(String resource) throws IOException {
106 ByteArrayOutputStream out = new ByteArrayOutputStream(); 105 ByteArrayOutputStream out = new ByteArrayOutputStream();
107 try (InputStream in = fetchData(resource)) { 106 try (InputStream in = fetchData(resource)) {
108 byte[] buffer = new byte[22]; 107 byte[] buffer = new byte[22];
109 while (true) { 108 while (true) {
110 int nread = in.read(buffer); 109 int nread = in.read(buffer);
111 if (nread <= 0) 110 if (nread <= 0)
112 break; 111 break;
113 out.write(buffer, 0, nread); 112 out.write(buffer, 0, nread);
114 } 113 }
115 return out.toByteArray(); 114 return out.toByteArray();
116 } 115 }
117 } 116 }
118 117
119 private InputStream fetchData(String resource) throws IOException { 118 private InputStream fetchData(String resource) throws IOException {
120 URL url = new URL("http://" + serverHost + ":" + serverPort + resource); 119 URL url = new URL("http://" + serverHost + ":" + serverPort + resource);
121 URLConnection conn = url.openConnection(); 120 URLConnection conn = url.openConnection();
122 conn.connect(); 121 conn.connect();
123 return conn.getInputStream(); 122 return conn.getInputStream();
124 } 123 }
125 124
126 private void run() throws IOException, SQLException { 125 private void run() throws IOException, SQLException {
127 test_connect_plain(); 126 test_connect_plain();
128 test_connect_tls(); 127 test_connect_tls();
129 test_refuse_no_cert(); 128 test_refuse_no_cert();
130 test_refuse_wrong_cert(); 129 test_refuse_wrong_cert();
131 test_refuse_wrong_host(); 130 test_refuse_wrong_host();
132 test_refuse_tlsv12(); 131 test_refuse_tlsv12();
133 test_refuse_expired(); 132 test_refuse_expired();
134 // test_connect_client_auth1(); 133 // test_connect_client_auth1();
135 // test_connect_client_auth2(); 134 // test_connect_client_auth2();
136 test_fail_tls_to_plain(); 135 test_fail_tls_to_plain();
137 test_fail_plain_to_tls(); 136 test_fail_plain_to_tls();
138 test_connect_server_name(); 137 test_connect_server_name();
139 test_connect_alpn_mapi9(); 138 test_connect_alpn_mapi9();
140 test_connect_trusted(); 139 test_connect_trusted();
141 test_refuse_trusted_wrong_host(); 140 test_refuse_trusted_wrong_host();
142 141
143 // did we forget to call expectSucceed and expectFailure somewhere? 142 // did we forget to call expectSucceed and expectFailure somewhere?
144 if (!preparedButNotRun.isEmpty()) { 143 if (!preparedButNotRun.isEmpty()) {
145 String names = String.join(", ", preparedButNotRun); 144 String names = String.join(", ", preparedButNotRun);
146 throw new RuntimeException("Not all tests called expectSuccess/expectFailure: " + names); 145 throw new RuntimeException("Not all tests called expectSuccess/expectFailure: " + names);
147 } 146 }
148 } 147 }
149 148
150 private void test_connect_plain() throws IOException, SQLException { 149 private void test_connect_plain() throws IOException, SQLException {
151 attempt("connect_plain", "plain").with(Parameter.TLS, false).expectSuccess(); 150 attempt("connect_plain", "plain").with(Parameter.TLS, false).expectSuccess();
152 } 151 }
153 152
154 private void test_connect_tls() throws IOException, SQLException { 153 private void test_connect_tls() throws IOException, SQLException {
155 Attempt attempt = attempt("connect_tls", "server1"); 154 Attempt attempt = attempt("connect_tls", "server1");
156 attempt.withFile(Parameter.CERT, "/ca1.crt").expectSuccess(); 155 attempt.withFile(Parameter.CERT, "/ca1.crt").expectSuccess();
157 } 156 }
158 157
159 private void test_refuse_no_cert() throws IOException, SQLException { 158 private void test_refuse_no_cert() throws IOException, SQLException {
160 attempt("refuse_no_cert", "server1").expectFailure("PKIX path building failed"); 159 attempt("refuse_no_cert", "server1").expectFailure("PKIX path building failed");
161 } 160 }
162 161
163 private void test_refuse_wrong_cert() throws IOException, SQLException { 162 private void test_refuse_wrong_cert() throws IOException, SQLException {
164 Attempt attempt = attempt("refuse_wrong_cert", "server1"); 163 Attempt attempt = attempt("refuse_wrong_cert", "server1");
165 attempt.withFile(Parameter.CERT, "/ca2.crt").expectFailure("PKIX path building failed"); 164 attempt.withFile(Parameter.CERT, "/ca2.crt").expectFailure("PKIX path building failed");
166 } 165 }
167 166
168 private void test_refuse_wrong_host() throws IOException, SQLException { 167 private void test_refuse_wrong_host() throws IOException, SQLException {
169 Attempt attempt = attempt("refuse_wrong_host", "server1").with(Parameter.HOST, altHost); 168 Attempt attempt = attempt("refuse_wrong_host", "server1").with(Parameter.HOST, altHost);
170 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("No subject alternative DNS name"); 169 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("No subject alternative DNS name");
171 } 170 }
172 171
173 private void test_refuse_tlsv12() throws IOException, SQLException { 172 private void test_refuse_tlsv12() throws IOException, SQLException {
174 Attempt attempt = attempt("refuse_tlsv12", "tls12"); 173 Attempt attempt = attempt("refuse_tlsv12", "tls12");
175 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("protocol_version"); 174 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("protocol_version");
176 } 175 }
177 176
178 private void test_refuse_expired() throws IOException, SQLException { 177 private void test_refuse_expired() throws IOException, SQLException {
179 Attempt attempt = attempt("refuse_expired", "expiredcert"); 178 Attempt attempt = attempt("refuse_expired", "expiredcert");
180 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("PKIX path validation failed"); 179 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("PKIX path validation failed");
181 } 180 }
182 181
183 private void test_connect_client_auth1() throws IOException, SQLException { 182 private void test_connect_client_auth1() throws IOException, SQLException {
184 attempt("connect_client_auth1", "clientauth") 183 attempt("connect_client_auth1", "clientauth")
185 .withFile(Parameter.CERT, "/ca1.crt") 184 .withFile(Parameter.CERT, "/ca1.crt")
186 .withFile(Parameter.CLIENTKEY, "/client2.keycrt") 185 .withFile(Parameter.CLIENTKEY, "/client2.keycrt")
187 .expectSuccess(); 186 .expectSuccess();
188 } 187 }
189 188
190 private void test_connect_client_auth2() throws IOException, SQLException { 189 private void test_connect_client_auth2() throws IOException, SQLException {
191 attempt("connect_client_auth2", "clientauth") 190 attempt("connect_client_auth2", "clientauth")
192 .withFile(Parameter.CERT, "/ca1.crt") 191 .withFile(Parameter.CERT, "/ca1.crt")
193 .withFile(Parameter.CLIENTKEY, "/client2.key") 192 .withFile(Parameter.CLIENTKEY, "/client2.key")
194 .withFile(Parameter.CLIENTCERT, "/client2.crt") 193 .withFile(Parameter.CLIENTCERT, "/client2.crt")
195 .expectSuccess(); 194 .expectSuccess();
196 } 195 }
197 196
198 private void test_fail_tls_to_plain() throws IOException, SQLException { 197 private void test_fail_tls_to_plain() throws IOException, SQLException {
199 Attempt attempt = attempt("fail_tls_to_plain", "plain"); 198 Attempt attempt = attempt("fail_tls_to_plain", "plain");
200 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure(""); 199 attempt.withFile(Parameter.CERT, "/ca1.crt").expectFailure("");
201 200
202 } 201 }
203 202
204 private void test_fail_plain_to_tls() throws IOException, SQLException { 203 private void test_fail_plain_to_tls() throws IOException, SQLException {
205 attempt("fail_plain_to_tls", "server1").with(Parameter.TLS, false).expectFailure("Cannot connect"); 204 attempt("fail_plain_to_tls", "server1").with(Parameter.TLS, false).expectFailure("Cannot connect");
206 } 205 }
207 206
208 private void test_connect_server_name() throws IOException, SQLException { 207 private void test_connect_server_name() throws IOException, SQLException {
209 Attempt attempt = attempt("connect_server_name", "sni"); 208 Attempt attempt = attempt("connect_server_name", "sni");
210 attempt.withFile(Parameter.CERT, "/ca1.crt").expectSuccess(); 209 attempt.withFile(Parameter.CERT, "/ca1.crt").expectSuccess();
211 } 210 }
212 211
213 private void test_connect_alpn_mapi9() throws IOException, SQLException { 212 private void test_connect_alpn_mapi9() throws IOException, SQLException {
214 attempt("connect_alpn_mapi9", "alpn_mapi9") 213 attempt("connect_alpn_mapi9", "alpn_mapi9").withFile(Parameter.CERT, "/ca1.crt").expectSuccess();
215 .withFile(Parameter.CERT, "/ca1.crt") 214 }
216 .expectSuccess(); 215
217 } 216 private void test_connect_trusted() throws IOException, SQLException {
218 217 attempt("connect_trusted", null)
219 private void test_connect_trusted() throws IOException, SQLException { 218 .with(Parameter.HOST, "monetdb.ergates.nl")
220 attempt("connect_trusted", null) 219 .with(Parameter.PORT, 50000)
221 .with(Parameter.HOST, "monetdb.ergates.nl") 220 .expectSuccess();
222 .with(Parameter.PORT, 50000) 221 }
223 .expectSuccess(); 222
224 } 223 private void test_refuse_trusted_wrong_host() throws IOException, SQLException {
225 224 attempt("test_refuse_trusted_wrong_host", null)
226 private void test_refuse_trusted_wrong_host() throws IOException, SQLException { 225 .with(Parameter.HOST, "monetdbxyz.ergates.nl")
227 attempt("test_refuse_trusted_wrong_host", null) 226 .with(Parameter.PORT, 50000)
228 .with(Parameter.HOST, "monetdbxyz.ergates.nl") 227 .expectFailure("No subject alternative DNS name");
229 .with(Parameter.PORT, 50000) 228 }
230 .expectFailure("No subject alternative DNS name"); 229
231 } 230 private Attempt attempt(String testName, String portName) throws IOException {
232 231 preparedButNotRun.add(testName);
233 private Attempt attempt(String testName, String portName) throws IOException { 232 return new Attempt(testName, portName);
234 preparedButNotRun.add(testName); 233 }
235 return new Attempt(testName, portName); 234
236 } 235 private class Attempt {
237 236 private final String testName;
238 private class Attempt { 237 private final Properties props = new Properties();
239 private final String testName; 238 boolean disabled = false;
240 private final Properties props = new Properties(); 239
241 boolean disabled = false; 240 public Attempt(String testName, String portName) throws IOException {
242 241 HashMap<String, Integer> portMap = loadPortMap(testName);
243 public Attempt(String testName, String portName) throws IOException { 242
244 HashMap<String, Integer> portMap = loadPortMap(testName); 243 this.testName = testName;
245 244 with(Parameter.TLS, true);
246 this.testName = testName; 245 with(Parameter.HOST, serverHost);
247 with(Parameter.TLS, true); 246 with(Parameter.SO_TIMEOUT, 3000);
248 with(Parameter.HOST, serverHost); 247 if (portName != null) {
249 with(Parameter.SO_TIMEOUT, 3000); 248 Integer port = portMap.get(portName);
250 if (portName != null) { 249 if (port != null) {
251 Integer port = portMap.get(portName); 250 with(Parameter.PORT, port);
252 if (port != null) { 251 } else {
253 with(Parameter.PORT, port); 252 throw new RuntimeException("Unknown port name: " + portName);
254 } else { 253 }
255 throw new RuntimeException("Unknown port name: " + portName); 254 }
256 } 255 }
257 } 256
258 } 257 private Attempt with(Parameter parm, String value) {
259 258 props.setProperty(parm.name, value);
260 private Attempt with(Parameter parm, String value) { 259 return this;
261 props.setProperty(parm.name, value); 260 }
262 return this; 261
263 } 262 private Attempt with(Parameter parm, int value) {
264 263 props.setProperty(parm.name, Integer.toString(value));
265 private Attempt with(Parameter parm, int value) { 264 return this;
266 props.setProperty(parm.name, Integer.toString(value)); 265 }
267 return this; 266
268 } 267 private Attempt with(Parameter parm, boolean value) {
269 268 props.setProperty(parm.name, value ? "true" : "false");
270 private Attempt with(Parameter parm, boolean value) { 269 return this;
271 props.setProperty(parm.name, value ? "true" : "false"); 270 }
272 return this; 271
273 } 272 private Attempt withFile(Parameter parm, String certResource) throws IOException {
274 273 File certFile = resource(certResource);
275 private Attempt withFile(Parameter parm, String certResource) throws IOException { 274 String path = certFile.getPath();
276 File certFile = resource(certResource); 275 with(parm, path);
277 String path = certFile.getPath(); 276 return this;
278 with(parm, path); 277 }
279 return this; 278
280 } 279 public void expectSuccess() throws SQLException {
281 280 preparedButNotRun.remove(testName);
282 public void expectSuccess() throws SQLException { 281 if (disabled)
283 preparedButNotRun.remove(testName); 282 return;
284 if (disabled) 283 try {
285 return; 284 Connection conn = DriverManager.getConnection("jdbc:monetdb:", props);
286 try { 285 conn.close();
287 Connection conn = DriverManager.getConnection("jdbc:monetdb:", props); 286 } catch (SQLException e) {
288 conn.close(); 287 if (e.getMessage().startsWith("Sorry, this is not a real MonetDB instance")) {
289 } catch (SQLException e) { 288 // it looks like a failure but this is actually our success scenario
290 if (e.getMessage().startsWith("Sorry, this is not a real MonetDB instance")) { 289 // because this is what the TLS Tester does when the connection succeeds.
291 // it looks like a failure but this is actually our success scenario 290 return;
292 // because this is what the TLS Tester does when the connection succeeds. 291 }
293 return; 292 // other exceptions ARE errors and should be reported.
294 } 293 throw e;
295 // other exceptions ARE errors and should be reported. 294 }
296 throw e; 295 }
297 } 296
298 } 297 public void expectFailure(String... expectedMessages) throws SQLException {
299 298 if (disabled)
300 public void expectFailure(String... expectedMessages) throws SQLException { 299 return;
301 if (disabled) 300 try {
302 return; 301 expectSuccess();
303 try { 302 throw new RuntimeException("Expected test " + testName + " to throw an exception but it didn't");
304 expectSuccess(); 303 } catch (SQLException e) {
305 throw new RuntimeException("Expected test " + testName + " to throw an exception but it didn't"); 304 for (String expected : expectedMessages)
306 } catch (SQLException e) { 305 if (e.getMessage().contains(expected))
307 for (String expected : expectedMessages) 306 return;
308 if (e.getMessage().contains(expected)) 307 String message = "Test " + testName + " threw the wrong exception: " + e.getMessage() + '\n' + "Expected:\n <" + String.join(">\n <", expectedMessages) + ">";
309 return; 308 throw new RuntimeException(message);
310 String message = "Test " + testName + " threw the wrong exception: " + e.getMessage() + '\n' + "Expected:\n <" + String.join(">\n <", expectedMessages) + ">"; 309
311 throw new RuntimeException(message); 310 }
312 311 }
313 } 312
314 } 313 }
315
316 }
317 } 314 }