]>
Commit | Line | Data |
---|---|---|
f1a74a3c | 1 | /* |
5e579ebe PK |
2 | * Copyright (C) 2020 Pascal Knecht |
3 | * Copyright (C) 2020 Tobias Brunner | |
4 | * HSR Hochschule fuer Technik Rapperswil | |
5 | * | |
f1a74a3c MW |
6 | * Copyright (C) 2010 Martin Willi |
7 | * Copyright (C) 2010 revosec AG | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify it | |
10 | * under the terms of the GNU General Public License as published by the | |
11 | * Free Software Foundation; either version 2 of the License, or (at your | |
12 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, but | |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
16 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
17 | * for more details. | |
18 | */ | |
19 | ||
20 | #include <unistd.h> | |
21 | #include <stdio.h> | |
22 | #include <sys/types.h> | |
23 | #include <sys/socket.h> | |
24 | #include <getopt.h> | |
6a5c86b7 MW |
25 | #include <errno.h> |
26 | #include <string.h> | |
f1a74a3c MW |
27 | |
28 | #include <library.h> | |
f05b4272 | 29 | #include <utils/debug.h> |
f1a74a3c | 30 | #include <tls_socket.h> |
2e7cc07e | 31 | #include <networking/host.h> |
f1a74a3c MW |
32 | #include <credentials/sets/mem_cred.h> |
33 | ||
34 | /** | |
35 | * Print usage information | |
36 | */ | |
37 | static void usage(FILE *out, char *cmd) | |
38 | { | |
39 | fprintf(out, "usage:\n"); | |
299cc800 | 40 | fprintf(out, " %s --connect <address> --port <port> [--key <key] [--cert <file>] [--cacert <file>]+ [--times <n>]\n", cmd); |
82116dba | 41 | fprintf(out, " %s --listen <address> --port <port> --key <key> --cert <file> [--cacert <file>]+ [--auth-optional] [--times <n>]\n", cmd); |
299cc800 PK |
42 | fprintf(out, "\n"); |
43 | fprintf(out, "options:\n"); | |
44 | fprintf(out, " --help print help and exit\n"); | |
45 | fprintf(out, " --connect <address> connect to a server on dns name or ip address\n"); | |
46 | fprintf(out, " --listen <address> listen on dns name or ip address\n"); | |
47 | fprintf(out, " --port <port> specify the port to use\n"); | |
48 | fprintf(out, " --cert <file> certificate to authenticate itself\n"); | |
49 | fprintf(out, " --key <file> private key to authenticate itself\n"); | |
50 | fprintf(out, " --cacert <file> certificate to verify other peer\n"); | |
42704f6a | 51 | fprintf(out, " --identity <id> optional remote identity to enforce\n"); |
82116dba | 52 | fprintf(out, " --auth-optional don't enforce client authentication\n"); |
299cc800 PK |
53 | fprintf(out, " --times <n> specify the amount of repeated connection establishments\n"); |
54 | fprintf(out, " --ipv4 use IPv4\n"); | |
55 | fprintf(out, " --ipv6 use IPv6\n"); | |
56 | fprintf(out, " --min-version <version> specify the minimum TLS version, supported versions:\n"); | |
57 | fprintf(out, " 1.0 (default), 1.1, 1.2 and 1.3\n"); | |
58 | fprintf(out, " --max-version <version> specify the maximum TLS version, supported versions:\n"); | |
59 | fprintf(out, " 1.0, 1.1, 1.2 and 1.3 (default)\n"); | |
60 | fprintf(out, " --version <version> set one specific TLS version to use, supported versions:\n"); | |
61 | fprintf(out, " 1.0, 1.1, 1.2 and 1.3\n"); | |
62 | fprintf(out, " --debug <debug level> set debug level, default is 1\n"); | |
f1a74a3c MW |
63 | } |
64 | ||
3f4300ed MW |
65 | /** |
66 | * Check, as client, if we have a client certificate with private key | |
67 | */ | |
68 | static identification_t *find_client_id() | |
69 | { | |
70 | identification_t *client = NULL, *keyid; | |
71 | enumerator_t *enumerator; | |
72 | certificate_t *cert; | |
73 | public_key_t *pubkey; | |
74 | private_key_t *privkey; | |
75 | chunk_t chunk; | |
76 | ||
77 | enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, | |
78 | CERT_X509, KEY_ANY, NULL, FALSE); | |
79 | while (enumerator->enumerate(enumerator, &cert)) | |
80 | { | |
81 | pubkey = cert->get_public_key(cert); | |
82 | if (pubkey) | |
83 | { | |
84 | if (pubkey->get_fingerprint(pubkey, KEYID_PUBKEY_SHA1, &chunk)) | |
85 | { | |
86 | keyid = identification_create_from_encoding(ID_KEY_ID, chunk); | |
87 | privkey = lib->credmgr->get_private(lib->credmgr, | |
88 | pubkey->get_type(pubkey), keyid, NULL); | |
89 | keyid->destroy(keyid); | |
90 | if (privkey) | |
91 | { | |
92 | client = cert->get_subject(cert); | |
93 | client = client->clone(client); | |
94 | privkey->destroy(privkey); | |
95 | } | |
96 | } | |
97 | pubkey->destroy(pubkey); | |
98 | } | |
99 | if (client) | |
100 | { | |
101 | break; | |
102 | } | |
103 | } | |
104 | enumerator->destroy(enumerator); | |
105 | ||
106 | return client; | |
107 | } | |
108 | ||
f1a74a3c MW |
109 | /** |
110 | * Client routine | |
111 | */ | |
3f4300ed | 112 | static int run_client(host_t *host, identification_t *server, |
8e35b1f1 | 113 | identification_t *client, int times, tls_cache_t *cache, |
82116dba TB |
114 | tls_version_t min_version, tls_version_t max_version, |
115 | tls_flag_t flags) | |
f1a74a3c MW |
116 | { |
117 | tls_socket_t *tls; | |
6a5c86b7 | 118 | int fd, res; |
f1a74a3c | 119 | |
6a5c86b7 | 120 | while (times == -1 || times-- > 0) |
f1a74a3c | 121 | { |
4099035a TB |
122 | DBG2(DBG_TLS, "connecting to %#H", host); |
123 | fd = socket(host->get_family(host), SOCK_STREAM, 0); | |
6a5c86b7 MW |
124 | if (fd == -1) |
125 | { | |
126 | DBG1(DBG_TLS, "opening socket failed: %s", strerror(errno)); | |
127 | return 1; | |
128 | } | |
129 | if (connect(fd, host->get_sockaddr(host), | |
130 | *host->get_sockaddr_len(host)) == -1) | |
131 | { | |
4099035a TB |
132 | DBG1(DBG_TLS, "connecting to %#H failed: %s", host, strerror(errno)); |
133 | close(fd); | |
134 | return 1; | |
6a5c86b7 | 135 | } |
8e35b1f1 | 136 | tls = tls_socket_create(FALSE, server, client, fd, cache, min_version, |
82116dba | 137 | max_version, flags); |
6a5c86b7 MW |
138 | if (!tls) |
139 | { | |
140 | close(fd); | |
141 | return 1; | |
142 | } | |
f8b29069 | 143 | res = tls->splice(tls, 0, 1) ? 0 : 1; |
6a5c86b7 MW |
144 | tls->destroy(tls); |
145 | close(fd); | |
146 | if (res) | |
147 | { | |
148 | break; | |
149 | } | |
f1a74a3c | 150 | } |
f1a74a3c MW |
151 | return res; |
152 | } | |
153 | ||
154 | /** | |
155 | * Server routine | |
156 | */ | |
299cc800 | 157 | static int serve(host_t *host, identification_t *server, identification_t *client, |
8e35b1f1 | 158 | int times, tls_cache_t *cache, tls_version_t min_version, |
82116dba | 159 | tls_version_t max_version, tls_flag_t flags) |
f1a74a3c MW |
160 | { |
161 | tls_socket_t *tls; | |
6a5c86b7 | 162 | int fd, cfd; |
f1a74a3c | 163 | |
6a5c86b7 MW |
164 | fd = socket(AF_INET, SOCK_STREAM, 0); |
165 | if (fd == -1) | |
166 | { | |
167 | DBG1(DBG_TLS, "opening socket failed: %s", strerror(errno)); | |
168 | return 1; | |
169 | } | |
f1a74a3c MW |
170 | if (bind(fd, host->get_sockaddr(host), |
171 | *host->get_sockaddr_len(host)) == -1) | |
172 | { | |
6a5c86b7 MW |
173 | DBG1(DBG_TLS, "binding to %#H failed: %s", host, strerror(errno)); |
174 | close(fd); | |
f1a74a3c MW |
175 | return 1; |
176 | } | |
177 | if (listen(fd, 1) == -1) | |
178 | { | |
6a5c86b7 MW |
179 | DBG1(DBG_TLS, "listen to %#H failed: %m", host, strerror(errno)); |
180 | close(fd); | |
f1a74a3c MW |
181 | return 1; |
182 | } | |
183 | ||
6a5c86b7 | 184 | while (times == -1 || times-- > 0) |
f1a74a3c MW |
185 | { |
186 | cfd = accept(fd, host->get_sockaddr(host), host->get_sockaddr_len(host)); | |
187 | if (cfd == -1) | |
188 | { | |
6a5c86b7 MW |
189 | DBG1(DBG_TLS, "accept failed: %s", strerror(errno)); |
190 | close(fd); | |
f1a74a3c MW |
191 | return 1; |
192 | } | |
6a5c86b7 | 193 | DBG1(DBG_TLS, "%#H connected", host); |
f1a74a3c | 194 | |
299cc800 | 195 | tls = tls_socket_create(TRUE, server, client, cfd, cache, min_version, |
82116dba | 196 | max_version, flags); |
f1a74a3c MW |
197 | if (!tls) |
198 | { | |
6a5c86b7 | 199 | close(fd); |
f1a74a3c MW |
200 | return 1; |
201 | } | |
f8b29069 | 202 | tls->splice(tls, 0, 1); |
6a5c86b7 | 203 | DBG1(DBG_TLS, "%#H disconnected", host); |
f1a74a3c MW |
204 | tls->destroy(tls); |
205 | } | |
6a5c86b7 | 206 | close(fd); |
f1a74a3c MW |
207 | |
208 | return 0; | |
209 | } | |
210 | ||
211 | /** | |
212 | * In-Memory credential set | |
213 | */ | |
214 | static mem_cred_t *creds; | |
215 | ||
216 | /** | |
217 | * Load certificate from file | |
218 | */ | |
219 | static bool load_certificate(char *filename) | |
220 | { | |
221 | certificate_t *cert; | |
222 | ||
223 | cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, | |
224 | BUILD_FROM_FILE, filename, BUILD_END); | |
225 | if (!cert) | |
226 | { | |
6a5c86b7 | 227 | DBG1(DBG_TLS, "loading certificate from '%s' failed", filename); |
f1a74a3c MW |
228 | return FALSE; |
229 | } | |
230 | creds->add_cert(creds, TRUE, cert); | |
231 | return TRUE; | |
232 | } | |
233 | ||
234 | /** | |
235 | * Load private key from file | |
236 | */ | |
237 | static bool load_key(char *filename) | |
238 | { | |
239 | private_key_t *key; | |
240 | ||
5e579ebe PK |
241 | key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, |
242 | BUILD_FROM_FILE, filename, BUILD_END); | |
f1a74a3c MW |
243 | if (!key) |
244 | { | |
6a5c86b7 | 245 | DBG1(DBG_TLS, "loading key from '%s' failed", filename); |
f1a74a3c MW |
246 | return FALSE; |
247 | } | |
248 | creds->add_key(creds, key); | |
249 | return TRUE; | |
250 | } | |
251 | ||
fd0bde9a MW |
252 | /** |
253 | * TLS debug level | |
254 | */ | |
255 | static level_t tls_level = 1; | |
256 | ||
257 | static void dbg_tls(debug_t group, level_t level, char *fmt, ...) | |
258 | { | |
259 | if ((group == DBG_TLS && level <= tls_level) || level <= 1) | |
260 | { | |
261 | va_list args; | |
262 | ||
263 | va_start(args, fmt); | |
264 | vfprintf(stderr, fmt, args); | |
265 | fprintf(stderr, "\n"); | |
266 | va_end(args); | |
267 | } | |
268 | } | |
269 | ||
f1a74a3c MW |
270 | /** |
271 | * Cleanup | |
272 | */ | |
273 | static void cleanup() | |
274 | { | |
275 | lib->credmgr->remove_set(lib->credmgr, &creds->set); | |
276 | creds->destroy(creds); | |
277 | library_deinit(); | |
278 | } | |
279 | ||
280 | /** | |
281 | * Initialize library | |
282 | */ | |
283 | static void init() | |
284 | { | |
06aad98f TB |
285 | char *plugins; |
286 | ||
34d3bfcf | 287 | library_init(NULL, "tls_test"); |
fd0bde9a MW |
288 | |
289 | dbg = dbg_tls; | |
290 | ||
06aad98f TB |
291 | plugins = getenv("PLUGINS") ?: PLUGINS; |
292 | lib->plugins->load(lib->plugins, plugins); | |
f1a74a3c MW |
293 | |
294 | creds = mem_cred_create(); | |
295 | lib->credmgr->add_set(lib->credmgr, &creds->set); | |
296 | ||
297 | atexit(cleanup); | |
298 | } | |
299 | ||
300 | int main(int argc, char *argv[]) | |
301 | { | |
302 | char *address = NULL; | |
6a5c86b7 | 303 | bool listen = FALSE; |
4099035a | 304 | int port = 0, times = -1, res, family = AF_UNSPEC; |
42704f6a | 305 | identification_t *server, *client = NULL, *identity = NULL; |
663969dd | 306 | tls_version_t min_version = TLS_SUPPORTED_MIN, max_version = TLS_SUPPORTED_MAX; |
82116dba | 307 | tls_flag_t flags = TLS_FLAG_ENCRYPTION_OPTIONAL; |
6a5c86b7 | 308 | tls_cache_t *cache; |
f1a74a3c MW |
309 | host_t *host; |
310 | ||
311 | init(); | |
312 | ||
313 | while (TRUE) | |
314 | { | |
315 | struct option long_opts[] = { | |
82116dba TB |
316 | {"help", no_argument, NULL, 'h' }, |
317 | {"connect", required_argument, NULL, 'c' }, | |
318 | {"listen", required_argument, NULL, 'l' }, | |
319 | {"port", required_argument, NULL, 'p' }, | |
320 | {"cert", required_argument, NULL, 'x' }, | |
321 | {"key", required_argument, NULL, 'k' }, | |
322 | {"cacert", required_argument, NULL, 'f' }, | |
323 | {"times", required_argument, NULL, 't' }, | |
324 | {"ipv4", no_argument, NULL, '4' }, | |
325 | {"ipv6", no_argument, NULL, '6' }, | |
326 | {"min-version", required_argument, NULL, 'm' }, | |
327 | {"max-version", required_argument, NULL, 'M' }, | |
328 | {"version", required_argument, NULL, 'v' }, | |
329 | {"auth-optional", no_argument, NULL, 'n' }, | |
42704f6a | 330 | {"identity", required_argument, NULL, 'i' }, |
82116dba | 331 | {"debug", required_argument, NULL, 'd' }, |
f1a74a3c MW |
332 | {0,0,0,0 } |
333 | }; | |
334 | switch (getopt_long(argc, argv, "", long_opts, NULL)) | |
335 | { | |
336 | case EOF: | |
337 | break; | |
338 | case 'h': | |
339 | usage(stdout, argv[0]); | |
340 | return 0; | |
341 | case 'x': | |
342 | if (!load_certificate(optarg)) | |
343 | { | |
344 | return 1; | |
345 | } | |
346 | continue; | |
347 | case 'k': | |
348 | if (!load_key(optarg)) | |
349 | { | |
350 | return 1; | |
351 | } | |
352 | continue; | |
299cc800 PK |
353 | case 'f': |
354 | if (!load_certificate(optarg)) | |
355 | { | |
356 | return 1; | |
357 | } | |
358 | client = identification_create_from_encoding(ID_ANY, chunk_empty); | |
359 | continue; | |
42704f6a TB |
360 | case 'i': |
361 | identity = identification_create_from_string(optarg); | |
362 | if (!identity) | |
363 | { | |
364 | return 1; | |
365 | } | |
366 | continue; | |
f1a74a3c MW |
367 | case 'l': |
368 | listen = TRUE; | |
369 | /* fall */ | |
370 | case 'c': | |
371 | if (address) | |
372 | { | |
373 | usage(stderr, argv[0]); | |
374 | return 1; | |
375 | } | |
376 | address = optarg; | |
377 | continue; | |
378 | case 'p': | |
379 | port = atoi(optarg); | |
380 | continue; | |
6a5c86b7 MW |
381 | case 't': |
382 | times = atoi(optarg); | |
f1a74a3c | 383 | continue; |
fd0bde9a MW |
384 | case 'd': |
385 | tls_level = atoi(optarg); | |
386 | continue; | |
4099035a TB |
387 | case '4': |
388 | family = AF_INET; | |
389 | continue; | |
390 | case '6': | |
391 | family = AF_INET6; | |
392 | continue; | |
8e35b1f1 | 393 | case 'm': |
663969dd TB |
394 | if (!enum_from_name(tls_numeric_version_names, optarg, |
395 | &min_version)) | |
8e35b1f1 TB |
396 | { |
397 | fprintf(stderr, "unknown minimum TLS version: %s\n", optarg); | |
398 | return 1; | |
399 | } | |
400 | continue; | |
401 | case 'M': | |
663969dd TB |
402 | if (!enum_from_name(tls_numeric_version_names, optarg, |
403 | &max_version)) | |
8e35b1f1 TB |
404 | { |
405 | fprintf(stderr, "unknown maximum TLS version: %s\n", optarg); | |
406 | return 1; | |
407 | } | |
408 | continue; | |
409 | case 'v': | |
663969dd TB |
410 | if (!enum_from_name(tls_numeric_version_names, optarg, |
411 | &min_version)) | |
8e35b1f1 TB |
412 | { |
413 | fprintf(stderr, "unknown TLS version: %s\n", optarg); | |
414 | return 1; | |
415 | } | |
416 | max_version = min_version; | |
417 | continue; | |
82116dba TB |
418 | case 'n': |
419 | flags |= TLS_FLAG_CLIENT_AUTH_OPTIONAL; | |
420 | continue; | |
f1a74a3c MW |
421 | default: |
422 | usage(stderr, argv[0]); | |
423 | return 1; | |
424 | } | |
425 | break; | |
426 | } | |
427 | if (!port || !address) | |
428 | { | |
429 | usage(stderr, argv[0]); | |
430 | return 1; | |
431 | } | |
4099035a | 432 | host = host_create_from_dns(address, family, port); |
f1a74a3c MW |
433 | if (!host) |
434 | { | |
4099035a TB |
435 | DBG1(DBG_TLS, "resolving hostname %s failed", address); |
436 | return 1; | |
f1a74a3c MW |
437 | } |
438 | server = identification_create_from_string(address); | |
6a5c86b7 | 439 | cache = tls_cache_create(100, 30); |
f1a74a3c MW |
440 | if (listen) |
441 | { | |
42704f6a | 442 | res = serve(host, server, identity ?: client, times, cache, min_version, |
82116dba | 443 | max_version, flags); |
f1a74a3c MW |
444 | } |
445 | else | |
446 | { | |
299cc800 | 447 | DESTROY_IF(client); |
3f4300ed | 448 | client = find_client_id(); |
42704f6a | 449 | res = run_client(host, identity ?: server, client, times, cache, min_version, |
82116dba | 450 | max_version, flags); |
3f4300ed | 451 | DESTROY_IF(client); |
f1a74a3c | 452 | } |
6a5c86b7 | 453 | cache->destroy(cache); |
f1a74a3c MW |
454 | host->destroy(host); |
455 | server->destroy(server); | |
42704f6a | 456 | DESTROY_IF(identity); |
f1a74a3c MW |
457 | return res; |
458 | } |