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