1/*	$OpenBSD: ssl_get_shared_ciphers.c,v 1.12 2024/03/20 10:38:05 jsing Exp $ */
2/*
3 * Copyright (c) 2021 Theo Buehler <tb@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <stdint.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22
23#include <openssl/bio.h>
24#include <openssl/crypto.h>
25#include <openssl/err.h>
26#include <openssl/ssl.h>
27
28struct peer_config {
29	const char *name;
30	int server;
31	uint16_t max_version;
32	uint16_t min_version;
33	const char *ciphers;
34};
35
36struct ssl_shared_ciphers_test_data {
37	const char *description;
38	struct peer_config client_config;
39	struct peer_config server_config;
40	const char *shared_ciphers;
41	const char *shared_ciphers_without_aesni;
42};
43
44char *server_cert;
45char *server_key;
46
47static const struct ssl_shared_ciphers_test_data ssl_shared_ciphers_tests[] = {
48	{
49		.description = "TLSv1.3 defaults",
50		.client_config = {
51			.name = "client",
52			.server = 0,
53			.max_version = TLS1_3_VERSION,
54			.min_version = TLS1_3_VERSION,
55			.ciphers =
56			    "TLS_AES_256_GCM_SHA384:"
57			    "TLS_CHACHA20_POLY1305_SHA256:"
58			    "TLS_AES_128_GCM_SHA256",
59		},
60		.server_config = {
61			.name = "server",
62			.server = 1,
63			.max_version = TLS1_3_VERSION,
64			.min_version = TLS1_3_VERSION,
65			.ciphers =
66			    "TLS_AES_256_GCM_SHA384:"
67			    "TLS_CHACHA20_POLY1305_SHA256:"
68			    "TLS_AES_128_GCM_SHA256",
69		},
70		.shared_ciphers =
71		    "TLS_AES_256_GCM_SHA384:"
72		    "TLS_CHACHA20_POLY1305_SHA256:"
73		    "TLS_AES_128_GCM_SHA256",
74	},
75
76	{
77		.description = "TLSv1.3, client without ChaCha",
78		.client_config = {
79			.name = "client",
80			.server = 0,
81			.max_version = TLS1_3_VERSION,
82			.min_version = TLS1_3_VERSION,
83			.ciphers =
84			    "TLS_AES_256_GCM_SHA384:"
85			    "TLS_AES_128_GCM_SHA256",
86		},
87		.server_config = {
88			.name = "server",
89			.server = 1,
90			.max_version = TLS1_3_VERSION,
91			.min_version = TLS1_3_VERSION,
92			.ciphers =
93			    "TLS_AES_256_GCM_SHA384:"
94			    "TLS_CHACHA20_POLY1305_SHA256:"
95			    "TLS_AES_128_GCM_SHA256",
96		},
97		.shared_ciphers =
98		    "TLS_AES_256_GCM_SHA384:"
99		    "TLS_AES_128_GCM_SHA256",
100	},
101
102	{
103		.description = "TLSv1.2",
104		.client_config = {
105			.name = "client",
106			.server = 0,
107			.max_version = TLS1_2_VERSION,
108			.min_version = TLS1_2_VERSION,
109			.ciphers =
110			    "ECDHE-RSA-AES256-GCM-SHA384:"
111			    "ECDHE-ECDSA-AES256-GCM-SHA384:"
112			    "ECDHE-RSA-AES256-SHA384:"
113			    "ECDHE-ECDSA-AES256-SHA384:"
114			    "ECDHE-RSA-AES256-SHA:"
115			    "ECDHE-ECDSA-AES256-SHA",
116		},
117		.server_config = {
118			.name = "server",
119			.server = 1,
120			.max_version = TLS1_2_VERSION,
121			.min_version = TLS1_2_VERSION,
122			.ciphers =
123			    "ECDHE-RSA-AES256-GCM-SHA384:"
124			    "ECDHE-ECDSA-AES256-GCM-SHA384:"
125			    "ECDHE-RSA-AES256-SHA384:"
126			    "ECDHE-ECDSA-AES256-SHA384:"
127			    "ECDHE-RSA-AES256-SHA:"
128			    "ECDHE-ECDSA-AES256-SHA",
129		},
130		.shared_ciphers =
131		    "ECDHE-RSA-AES256-GCM-SHA384:"
132		    "ECDHE-ECDSA-AES256-GCM-SHA384:"
133		    "ECDHE-RSA-AES256-SHA384:"
134		    "ECDHE-ECDSA-AES256-SHA384:"
135		    "ECDHE-RSA-AES256-SHA:"
136		    "ECDHE-ECDSA-AES256-SHA",
137	},
138
139	{
140		.description = "TLSv1.2, server without ECDSA",
141		.client_config = {
142			.name = "client",
143			.server = 0,
144			.max_version = TLS1_2_VERSION,
145			.min_version = TLS1_2_VERSION,
146			.ciphers =
147			    "ECDHE-RSA-AES256-GCM-SHA384:"
148			    "ECDHE-ECDSA-AES256-GCM-SHA384:"
149			    "ECDHE-RSA-AES256-SHA384:"
150			    "ECDHE-ECDSA-AES256-SHA384:"
151			    "ECDHE-RSA-AES256-SHA:"
152			    "ECDHE-ECDSA-AES256-SHA",
153		},
154		.server_config = {
155			.name = "server",
156			.server = 1,
157			.max_version = TLS1_2_VERSION,
158			.min_version = TLS1_2_VERSION,
159			.ciphers =
160			    "ECDHE-RSA-AES256-GCM-SHA384:"
161			    "ECDHE-RSA-AES256-SHA384:"
162			    "ECDHE-RSA-AES256-SHA",
163		},
164		.shared_ciphers =
165		    "ECDHE-RSA-AES256-GCM-SHA384:"
166		    "ECDHE-RSA-AES256-SHA384:"
167		    "ECDHE-RSA-AES256-SHA",
168	},
169
170	{
171		.description = "TLSv1.3 ciphers are prepended",
172		.client_config = {
173			.name = "client",
174			.server = 0,
175			.max_version = TLS1_3_VERSION,
176			.min_version = TLS1_2_VERSION,
177			.ciphers =
178			    "ECDHE-RSA-AES256-GCM-SHA384",
179		},
180		.server_config = {
181			.name = "server",
182			.server = 1,
183			.max_version = TLS1_3_VERSION,
184			.min_version = TLS1_2_VERSION,
185			.ciphers =
186			    "ECDHE-RSA-AES256-GCM-SHA384",
187		},
188		.shared_ciphers =
189		    "TLS_AES_256_GCM_SHA384:"
190		    "TLS_CHACHA20_POLY1305_SHA256:"
191		    "TLS_AES_128_GCM_SHA256:"
192		    "ECDHE-RSA-AES256-GCM-SHA384",
193		.shared_ciphers_without_aesni =
194		    "TLS_CHACHA20_POLY1305_SHA256:"
195		    "TLS_AES_256_GCM_SHA384:"
196		    "TLS_AES_128_GCM_SHA256:"
197		    "ECDHE-RSA-AES256-GCM-SHA384",
198	},
199};
200
201static const size_t N_SHARED_CIPHERS_TESTS =
202    sizeof(ssl_shared_ciphers_tests) / sizeof(ssl_shared_ciphers_tests[0]);
203
204static SSL_CTX *
205peer_config_to_ssl_ctx(const struct peer_config *config)
206{
207	SSL_CTX *ctx;
208
209	if ((ctx = SSL_CTX_new(TLS_method())) == NULL) {
210		fprintf(stderr, "SSL_CTX_new(%s) failed\n", config->name);
211		goto err;
212	}
213	if (!SSL_CTX_set_max_proto_version(ctx, config->max_version)) {
214		fprintf(stderr, "max_proto_version(%s) failed\n", config->name);
215		goto err;
216	}
217	if (!SSL_CTX_set_min_proto_version(ctx, config->min_version)) {
218		fprintf(stderr, "min_proto_version(%s) failed\n", config->name);
219		goto err;
220	}
221	if (!SSL_CTX_set_cipher_list(ctx, config->ciphers)) {
222		fprintf(stderr, "set_cipher_list(%s) failed\n", config->name);
223		goto err;
224	}
225
226	if (config->server) {
227		if (!SSL_CTX_use_certificate_file(ctx, server_cert,
228		    SSL_FILETYPE_PEM)) {
229			fprintf(stderr, "use_certificate_file(%s) failed\n",
230			    config->name);
231			goto err;
232		}
233		if (!SSL_CTX_use_PrivateKey_file(ctx, server_key,
234		    SSL_FILETYPE_PEM)) {
235			fprintf(stderr, "use_PrivateKey_file(%s) failed\n",
236			    config->name);
237			goto err;
238		}
239	}
240
241	return ctx;
242
243 err:
244	SSL_CTX_free(ctx);
245	return NULL;
246}
247
248/* Connect client and server via a pair of "nonblocking" memory BIOs. */
249static int
250connect_peers(SSL *client_ssl, SSL *server_ssl, const char *description)
251{
252	BIO *client_wbio = NULL, *server_wbio = NULL;
253	int ret = 0;
254
255	if ((client_wbio = BIO_new(BIO_s_mem())) == NULL) {
256		fprintf(stderr, "%s: failed to create client BIO\n",
257		    description);
258		goto err;
259	}
260	if ((server_wbio = BIO_new(BIO_s_mem())) == NULL) {
261		fprintf(stderr, "%s: failed to create server BIO\n",
262		    description);
263		goto err;
264	}
265	if (BIO_set_mem_eof_return(client_wbio, -1) <= 0) {
266		fprintf(stderr, "%s: failed to set client eof return\n",
267		    description);
268		goto err;
269	}
270	if (BIO_set_mem_eof_return(server_wbio, -1) <= 0) {
271		fprintf(stderr, "%s: failed to set server eof return\n",
272		    description);
273		goto err;
274	}
275
276	/* Avoid double free. SSL_set_bio() takes ownership of the BIOs. */
277	BIO_up_ref(client_wbio);
278	BIO_up_ref(server_wbio);
279
280	SSL_set_bio(client_ssl, server_wbio, client_wbio);
281	SSL_set_bio(server_ssl, client_wbio, server_wbio);
282	client_wbio = NULL;
283	server_wbio = NULL;
284
285	ret = 1;
286
287 err:
288	BIO_free(client_wbio);
289	BIO_free(server_wbio);
290
291	return ret;
292}
293
294static int
295push_data_to_peer(SSL *ssl, int *ret, int (*func)(SSL *), const char *func_name,
296    const char *description)
297{
298	int ssl_err = 0;
299
300	if (*ret == 1)
301		return 1;
302
303	/*
304	 * Do SSL_connect/SSL_accept/SSL_shutdown once and loop while hitting
305	 * WANT_WRITE.  If done or on WANT_READ hand off to peer.
306	 */
307
308	do {
309		if ((*ret = func(ssl)) <= 0)
310			ssl_err = SSL_get_error(ssl, *ret);
311	} while (*ret <= 0 && ssl_err == SSL_ERROR_WANT_WRITE);
312
313	/* Ignore erroneous error - see SSL_shutdown(3)... */
314	if (func == SSL_shutdown && ssl_err == SSL_ERROR_SYSCALL)
315		return 1;
316
317	if (*ret <= 0 && ssl_err != SSL_ERROR_WANT_READ) {
318		fprintf(stderr, "%s: %s failed\n", description, func_name);
319		ERR_print_errors_fp(stderr);
320		return 0;
321	}
322
323	return 1;
324}
325
326/*
327 * Alternate between loops of SSL_connect() and SSL_accept() as long as only
328 * WANT_READ and WANT_WRITE situations are encountered. A function is repeated
329 * until WANT_READ is returned or it succeeds, then it's the other function's
330 * turn to make progress. Succeeds if SSL_connect() and SSL_accept() return 1.
331 */
332static int
333handshake(SSL *client_ssl, SSL *server_ssl, const char *description)
334{
335	int loops = 0, client_ret = 0, server_ret = 0;
336
337	while (loops++ < 10 && (client_ret <= 0 || server_ret <= 0)) {
338		if (!push_data_to_peer(client_ssl, &client_ret, SSL_connect,
339		    "SSL_connect", description))
340			return 0;
341
342		if (!push_data_to_peer(server_ssl, &server_ret, SSL_accept,
343		    "SSL_accept", description))
344			return 0;
345	}
346
347	if (client_ret != 1 || server_ret != 1) {
348		fprintf(stderr, "%s: failed\n", __func__);
349		return 0;
350	}
351
352	return 1;
353}
354
355static int
356shutdown_peers(SSL *client_ssl, SSL *server_ssl, const char *description)
357{
358	int loops = 0, client_ret = 0, server_ret = 0;
359
360	while (loops++ < 10 && (client_ret <= 0 || server_ret <= 0)) {
361		if (!push_data_to_peer(client_ssl, &client_ret, SSL_shutdown,
362		    "client shutdown", description))
363			return 0;
364
365		if (!push_data_to_peer(server_ssl, &server_ret, SSL_shutdown,
366		    "server shutdown", description))
367			return 0;
368	}
369
370	if (client_ret != 1 || server_ret != 1) {
371		fprintf(stderr, "%s: failed\n", __func__);
372		return 0;
373	}
374
375	return 1;
376}
377
378/* from ssl_ciph.c */
379static inline int
380ssl_aes_is_accelerated(void)
381{
382#if defined(__i386__) || defined(__x86_64__)
383	return ((OPENSSL_cpu_caps() & (1ULL << 57)) != 0);
384#else
385	return (0);
386#endif
387}
388
389static int
390check_shared_ciphers(const struct ssl_shared_ciphers_test_data *test,
391    const char *got)
392{
393	const char *want = test->shared_ciphers;
394	int failed;
395
396	if (!ssl_aes_is_accelerated() &&
397	    test->shared_ciphers_without_aesni != NULL)
398		want = test->shared_ciphers_without_aesni;
399
400	failed = strcmp(want, got);
401
402	if (failed)
403		fprintf(stderr, "%s: want \"%s\", got \"%s\"\n",
404		    test->description, want, got);
405
406	return failed;
407}
408
409static int
410test_get_shared_ciphers(const struct ssl_shared_ciphers_test_data *test)
411{
412	SSL_CTX *client_ctx = NULL, *server_ctx = NULL;
413	SSL *client_ssl = NULL, *server_ssl = NULL;
414	char buf[4096];
415	int failed = 1;
416
417	if ((client_ctx = peer_config_to_ssl_ctx(&test->client_config)) == NULL)
418		goto err;
419	if ((server_ctx = peer_config_to_ssl_ctx(&test->server_config)) == NULL)
420		goto err;
421
422	if ((client_ssl = SSL_new(client_ctx)) == NULL) {
423		fprintf(stderr, "%s: failed to create client SSL\n",
424		    test->description);
425		goto err;
426	}
427	if ((server_ssl = SSL_new(server_ctx)) == NULL) {
428		fprintf(stderr, "%s: failed to create server SSL\n",
429		    test->description);
430		goto err;
431	}
432
433	if (!connect_peers(client_ssl, server_ssl, test->description))
434		goto err;
435
436	if (!handshake(client_ssl, server_ssl, test->description))
437		goto err;
438
439	if (SSL_get_shared_ciphers(server_ssl, buf, sizeof(buf)) == NULL) {
440		fprintf(stderr, "%s: failed to get shared ciphers\n",
441		    test->description);
442		goto err;
443	}
444
445	if (!shutdown_peers(client_ssl, server_ssl, test->description))
446		goto err;
447
448	failed = check_shared_ciphers(test, buf);
449
450 err:
451	SSL_CTX_free(client_ctx);
452	SSL_CTX_free(server_ctx);
453	SSL_free(client_ssl);
454	SSL_free(server_ssl);
455
456	return failed;
457}
458
459int
460main(int argc, char **argv)
461{
462	size_t i;
463	int failed = 0;
464
465	if (asprintf(&server_cert, "%s/server1-rsa.pem", CERTSDIR) == -1) {
466		fprintf(stderr, "asprintf server_cert failed\n");
467		failed = 1;
468		goto err;
469	}
470	server_key = server_cert;
471
472	for (i = 0; i < N_SHARED_CIPHERS_TESTS; i++)
473		failed |= test_get_shared_ciphers(&ssl_shared_ciphers_tests[i]);
474
475	if (failed == 0)
476		printf("PASS %s\n", __FILE__);
477
478 err:
479	free(server_cert);
480
481	return failed;
482}
483