[git commit] tls: fix cipher-id selection in server mode

Denys Vlasenko vda.linux at googlemail.com
Sun Feb 15 14:16:26 UTC 2026


commit: https://git.busybox.net/busybox/commit/?id=5a1bcdf036840ee3c70beb458c421e7f5a294b8d
branch: https://git.busybox.net/busybox/log/?h=master

ECDSA keys still don't work, and currently will be ignored

function                                             old     new   delta
tls_handshake_as_server                              824    1601    +777
.rodata                                           107764  108007    +243
set_cipher_parameters                                  -     161    +161
packed_usage                                       36072   36146     +74
static.BLOCK_NAMES                                     -      70     +70
client_hello_ciphers                                   -      32     +32
ssl_server_main                                      288     279      -9
load_rsa_priv_key                                    329     282     -47
tls_handshake                                       1676    1530    -146
------------------------------------------------------------------------------
(add/remove: 3/0 grow/shrink: 3/3 up/down: 1357/-202)        Total: 1155 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 include/libbb.h         |   3 +-
 networking/ssl_server.c |  41 +++-
 networking/tls.c        | 607 +++++++++++++++++++++++++++++++-----------------
 3 files changed, 430 insertions(+), 221 deletions(-)

diff --git a/include/libbb.h b/include/libbb.h
index 434a660bd..5f4ba6b0f 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -928,8 +928,7 @@ static inline tls_state_t *new_tls_state(void)
 }
 void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni);
 void FAST_FUNC tls_handshake_as_server(tls_state_t *tls,
-	const char *privkey_der_filename,
-	const char *cert_der_filename);
+	const char *pem_filename);
 #define TLSLOOP_EXIT_ON_LOCAL_EOF (1 << 0)
 void tls_run_copy_loop(tls_state_t *tls, unsigned flags) FAST_FUNC;
 
diff --git a/networking/ssl_server.c b/networking/ssl_server.c
index 665009aea..2a89dae6c 100644
--- a/networking/ssl_server.c
+++ b/networking/ssl_server.c
@@ -14,9 +14,35 @@
 //kbuild:lib-$(CONFIG_SSL_SERVER) += ssl_server.o
 
 //usage:#define ssl_server_trivial_usage
-//usage:       "[-vv] -p PRIVKEY.der -c CERT.der PROG ARGS"
+//usage:       "-f PRIVKEY_CERT.pem PROG ARGS"
 //usage:#define ssl_server_full_usage ""
-
+//usage:       "Inetd-style TLS server\n"
+//usage:     "\n	-f PEMFILE	HAProxy-style CRT file"
+/*
+# Generate RSA key and certificate
+openssl req -x509 -newkey rsa:4096 \
+	-keyout $HOSTNAME-rsa.key \
+	-out $HOSTNAME-rsa.crt \
+	-sha256 -days 9999 -nodes \
+	-subj /CN=$HOSTNAME \
+	-addext "subjectAltName=DNS:$HOSTNAME"
+# Generate ECDSA key and certificate
+openssl genpkey -algorithm EC \
+	-pkeyopt ec_paramgen_curve:prime256v1 \
+	-out $HOSTNAME-ecdsa.key
+fopenssl req -new -x509 \
+        -key $HOSTNAME-ecdsa.key \
+        -out $HOSTNAME-ecdsa.crt \
+        -sha256 -days 9999 \
+        -subj "/CN=$HOSTNAME" \
+        -addext "subjectAltName=DNS:$HOSTNAME"
+# Concatenate all these files into PRIVKEY_CERT.pem
+{	cat $HOSTNAME-rsa.key
+	cat $HOSTNAME-rsa.crt
+	cat $HOSTNAME-ecdsa.key
+	cat $HOSTNAME-ecdsa.crt
+} >PRIVKEY_CERT.pem
+*/
 #include "libbb.h"
 
 /* TLS server applet.
@@ -42,18 +68,17 @@ int ssl_server_main(int argc UNUSED_PARAM, char **argv)
 	struct fd_pair from_prog;
 	pid_t pid;
 	tls_state_t *tls;
-	const char *privkey_in_der_format;
-	const char *cert_in_der_format;
+	const char *pem_file;
 	unsigned opt;
 
 	tls = new_tls_state();
 
 	/* "+": stop on first non-option */
-	opt = getopt32(argv, "+""vp:c:",
-		&privkey_in_der_format, &cert_in_der_format
+	opt = getopt32(argv, "+""vf:",
+		&pem_file
 	);
 	argv += optind;
-	if (!argv[0] || (opt >> 1) == 0)
+	if (!argv[0] || !(opt & 2))
 		bb_show_usage();
 
 	/* In inetd mode, stdin/stdout are the socket.
@@ -66,7 +91,7 @@ int ssl_server_main(int argc UNUSED_PARAM, char **argv)
 	tls->ofd = 4;
 
 	/* This can abort on errors */
-	tls_handshake_as_server(tls, privkey_in_der_format, cert_in_der_format);
+	tls_handshake_as_server(tls, pem_file);
 
 	/* Run PROG, wrap its data in TLS and I/O to socket */
 	xpiped_pair(to_prog);
diff --git a/networking/tls.c b/networking/tls.c
index a27f0955a..2f0afe00a 100644
--- a/networking/tls.c
+++ b/networking/tls.c
@@ -189,6 +189,76 @@
 #define TLS_MAX_CRYPTBLOCK_SIZE 16
 #define TLS_MAX_OUTBUF          (1 << 14)
 
+/* Cipher suites we support, in preference order (best first) */
+#define NUM_CIPHERS (0 \
+	+ 4 * ENABLE_FEATURE_TLS_SHA1 \
+	+ ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 \
+	+ ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \
+	+ ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 \
+	+ ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
+	+ 2 * ENABLE_FEATURE_TLS_SHA1 \
+	+ ALLOW_RSA_WITH_AES_128_CBC_SHA256 \
+	+ ALLOW_RSA_WITH_AES_256_CBC_SHA256 \
+	+ ALLOW_RSA_WITH_AES_128_GCM_SHA256 \
+	+ ALLOW_RSA_NULL_SHA256 \
+	)
+static const uint8_t client_hello_ciphers[] = {
+	0x00,2 * (1 + NUM_CIPHERS), //len16_be
+	0x00,0xFF, //not a cipher - TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+	/* ^^^^^^ RFC 5746 Renegotiation Indication Extension - some servers will refuse to work with us otherwise */
+#if ENABLE_FEATURE_TLS_SHA1
+	0xC0,0x09, // 1 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ok: wget https://is.gd/
+	0xC0,0x0A, // 2 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - ok: wget https://is.gd/
+	0xC0,0x13, // 3 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA
+	0xC0,0x14, // 4 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES256-SHA (might fail with older openssl)
+//	0xC0,0x18, //   TLS_ECDH_anon_WITH_AES_128_CBC_SHA
+//	0xC0,0x19, //   TLS_ECDH_anon_WITH_AES_256_CBC_SHA
+#endif
+#if ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
+	0xC0,0x23, // 5 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - ok: wget https://is.gd/
+#endif
+//	0xC0,0x24, //   TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
+#if ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256
+	0xC0,0x27, // 6 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA256
+#endif
+//	0xC0,0x28, //   TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
+#if ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+	0xC0,0x2B, // 7 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - ok: wget https://is.gd/
+#endif
+//	0xC0,0x2C, //   TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - wget https://is.gd/: "TLS error from peer (alert code 20): bad MAC"
+//TODO: GCM_SHA384 ciphers can be supported, only need sha384-based PRF?
+#if ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+	0xC0,0x2F, // 8 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-GCM-SHA256
+#endif
+//	0xC0,0x30, //   TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher ECDHE-RSA-AES256-GCM-SHA384: "decryption failed or bad record mac"
+//possibly these too:
+#if ENABLE_FEATURE_TLS_SHA1
+//	0xC0,0x35, //   TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA
+//	0xC0,0x36, //   TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA
+#endif
+//	0xC0,0x37, //   TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256
+//	0xC0,0x38, //   TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
+#if ENABLE_FEATURE_TLS_SHA1
+	0x00,0x2F, // 9 TLS_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher AES128-SHA
+	0x00,0x35, //10 TLS_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher AES256-SHA
+#endif
+#if ALLOW_RSA_WITH_AES_128_CBC_SHA256
+	0x00,0x3C, //11 TLS_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher AES128-SHA256
+#endif
+#if ALLOW_RSA_WITH_AES_256_CBC_SHA256
+	0x00,0x3D, //12 TLS_RSA_WITH_AES_256_CBC_SHA256 - ok: openssl s_server ... -cipher AES256-SHA256
+#endif
+#if ALLOW_RSA_WITH_AES_128_GCM_SHA256
+	0x00,0x9C, //13 TLS_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher AES128-GCM-SHA256
+#endif
+//	0x00,0x9D, //   TLS_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher AES256-GCM-SHA384: "decryption failed or bad record mac"
+#if ALLOW_RSA_NULL_SHA256
+	0x00,0x3B, //   TLS_RSA_WITH_NULL_SHA256
+#endif
+	0x01,0x00, //not a cipher - comprtypes_len, comprtype
+};
+#define supported_ciphers (client_hello_ciphers + 4)
+
 enum {
 	AES128_KEYSIZE = 16,
 	AES256_KEYSIZE = 32,
@@ -246,6 +316,67 @@ enum {
 	ENCRYPTION_AESGCM      = 1 << 5, // else AES-SHA (or NULL-SHA if ALLOW_RSA_NULL_SHA256=1)
 };
 
+/* Note: return value matches KEY_RSA (0) / KEY_ECDSA (1) enum values */
+static int is_cipher_ECDSA(const uint8_t *cipherid)
+{
+	uint8_t cipher_lo;
+	if (cipherid[0] != 0xC0)
+		return 0;
+	/* ECDHE cipher - check if ECDSA or RSA */
+	cipher_lo = cipherid[1];
+	return  (cipher_lo == 0x09 || cipher_lo == 0x0A
+		|| cipher_lo == 0x23 || cipher_lo == 0x2B
+	);
+}
+
+/* Set cipher parameters based on selected cipher_id */
+static void set_cipher_parameters(tls_state_t *tls, const uint8_t *cipherid)
+{
+	uint8_t cipherid1 = cipherid[1];
+
+	tls->cipher_id = 0x100 * cipherid[0] + cipherid1;
+
+	/* Set defaults for RSA with AES-256 */
+	tls->key_size = AES256_KEYSIZE;
+	tls->MAC_size = SHA256_OUTSIZE;
+	tls->IV_size = 0;
+
+	if (cipherid[0] == 0xC0) {
+		/* All C0xx are ECDHE */
+		tls->flags |= NEED_EC_KEY;
+		if (cipherid1 & 1) {
+			/* Odd numbered C0xx use AES128 (even ones use AES256) */
+			tls->key_size = AES128_KEYSIZE;
+		}
+		if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x19) {
+			tls->MAC_size = SHA1_OUTSIZE;
+		} else
+		if (cipherid1 >= 0x2B && cipherid1 <= 0x30) {
+			/* C02B,2C,2F,30 are AES-GCM */
+			tls->flags |= ENCRYPTION_AESGCM;
+			tls->MAC_size = 0;
+			tls->IV_size = 4;
+		}
+	} else {
+		/* All 00xx are RSA */
+		if ((ENABLE_FEATURE_TLS_SHA1 && cipherid1 == 0x2F)
+		 || cipherid1 == 0x3C
+		 || cipherid1 == 0x9C
+		) {
+			tls->key_size = AES128_KEYSIZE;
+		}
+		if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x35) {
+			tls->MAC_size = SHA1_OUTSIZE;
+		} else
+		if (cipherid1 == 0x9C /*|| cipherid1 == 0x9D*/) {
+			/* 009C,9D are AES-GCM */
+			tls->flags |= ENCRYPTION_AESGCM;
+			tls->MAC_size = 0;
+			tls->IV_size = 4;
+		}
+	}
+}
+
 struct record_hdr {
 	uint8_t type;
 	uint8_t proto_maj, proto_min;
@@ -273,14 +404,22 @@ struct tls_handshake_data {
 	//uint8_t saved_client_hello[1];
 
 #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL
-	psRsaKey_t server_rsa_priv_key;
-	uint8_t   *server_cert_der;
-	size_t     server_cert_der_len;
+	/* Server certificate and key data */
+	char *keys[2];
+	char *certs[2];
+	unsigned keysize[2];
+	unsigned certsize[2];
+	int key_type_chosen;
+	psRsaKey_t rsa_priv_key;
 
 	///* For ECDHE: server's ephemeral EC private key */
 	//uint8_t ecc_priv_key32[32];
 #endif
 };
+enum {
+	KEY_RSA,
+	KEY_ECDSA,
+};
 
 static unsigned get24be(const uint8_t *p)
 {
@@ -1403,73 +1542,6 @@ static void *get_outbuf_fill_handshake_record(tls_state_t *tls, unsigned type, u
 
 static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni)
 {
-#define NUM_CIPHERS (0 \
-	+ 4 * ENABLE_FEATURE_TLS_SHA1 \
-	+ ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 \
-	+ ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \
-	+ ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 \
-	+ ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
-	+ 2 * ENABLE_FEATURE_TLS_SHA1 \
-	+ ALLOW_RSA_WITH_AES_128_CBC_SHA256 \
-	+ ALLOW_RSA_WITH_AES_256_CBC_SHA256 \
-	+ ALLOW_RSA_WITH_AES_128_GCM_SHA256 \
-	+ ALLOW_RSA_NULL_SHA256 \
-	)
-	static const uint8_t ciphers[] = {
-		0x00,2 * (1 + NUM_CIPHERS), //len16_be
-		0x00,0xFF, //not a cipher - TLS_EMPTY_RENEGOTIATION_INFO_SCSV
-		/* ^^^^^^ RFC 5746 Renegotiation Indication Extension - some servers will refuse to work with us otherwise */
-#if ENABLE_FEATURE_TLS_SHA1
-		0xC0,0x09, // 1 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ok: wget https://is.gd/
-		0xC0,0x0A, // 2 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - ok: wget https://is.gd/
-		0xC0,0x13, // 3 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA
-		0xC0,0x14, // 4 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES256-SHA (might fail with older openssl)
-	//	0xC0,0x18, //   TLS_ECDH_anon_WITH_AES_128_CBC_SHA
-	//	0xC0,0x19, //   TLS_ECDH_anon_WITH_AES_256_CBC_SHA
-#endif
-#if ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
-		0xC0,0x23, // 5 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - ok: wget https://is.gd/
-#endif
-	//	0xC0,0x24, //   TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
-#if ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256
-		0xC0,0x27, // 6 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA256
-#endif
-	//	0xC0,0x28, //   TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
-#if ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
-		0xC0,0x2B, // 7 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - ok: wget https://is.gd/
-#endif
-	//	0xC0,0x2C, //   TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - wget https://is.gd/: "TLS error from peer (alert code 20): bad MAC"
-//TODO: GCM_SHA384 ciphers can be supported, only need sha384-based PRF?
-#if ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256
-		0xC0,0x2F, // 8 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-GCM-SHA256
-#endif
-	//	0xC0,0x30, //   TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher ECDHE-RSA-AES256-GCM-SHA384: "decryption failed or bad record mac"
-	//possibly these too:
-#if ENABLE_FEATURE_TLS_SHA1
-	//	0xC0,0x35, //   TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA
-	//	0xC0,0x36, //   TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA
-#endif
-	//	0xC0,0x37, //   TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256
-	//	0xC0,0x38, //   TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
-#if ENABLE_FEATURE_TLS_SHA1
-		0x00,0x2F, // 9 TLS_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher AES128-SHA
-		0x00,0x35, //10 TLS_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher AES256-SHA
-#endif
-#if ALLOW_RSA_WITH_AES_128_CBC_SHA256
-		0x00,0x3C, //11 TLS_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher AES128-SHA256
-#endif
-#if ALLOW_RSA_WITH_AES_256_CBC_SHA256
-		0x00,0x3D, //12 TLS_RSA_WITH_AES_256_CBC_SHA256 - ok: openssl s_server ... -cipher AES256-SHA256
-#endif
-#if ALLOW_RSA_WITH_AES_128_GCM_SHA256
-		0x00,0x9C, //13 TLS_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher AES128-GCM-SHA256
-#endif
-	//	0x00,0x9D, //   TLS_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher AES256-GCM-SHA384: "decryption failed or bad record mac"
-#if ALLOW_RSA_NULL_SHA256
-		0x00,0x3B, //   TLS_RSA_WITH_NULL_SHA256
-#endif
-		0x01,0x00, //not a cipher - comprtypes_len, comprtype
-	};
 	struct client_hello {
 		uint8_t type;
 		uint8_t len24_hi, len24_mid, len24_lo;
@@ -1556,8 +1628,8 @@ static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni)
 		memset(record->rand32, 0x11, sizeof(record->rand32));
 	/* record->session_id_len = 0; - already is */
 
-	BUILD_BUG_ON(sizeof(ciphers) != 2 * (1 + 1 + NUM_CIPHERS + 1));
-	memcpy(&record->cipherid_len16_hi, ciphers, sizeof(ciphers));
+	BUILD_BUG_ON(sizeof(client_hello_ciphers) != 2 * (1 + 1 + NUM_CIPHERS + 1));
+	memcpy(&record->cipherid_len16_hi, client_hello_ciphers, sizeof(client_hello_ciphers));
 
 	ptr = (void*)(record + 1);
 	*ptr++ = ext_len >> 8;
@@ -1612,7 +1684,6 @@ static void get_server_hello(tls_state_t *tls)
 
 	struct server_hello *hp;
 	uint8_t *cipherid;
-	uint8_t cipherid1;
 	int len, len24;
 
 	len = tls_xread_handshake_block(tls, 74 - 32);
@@ -1651,74 +1722,7 @@ static void get_server_hello(tls_state_t *tls)
 
 	memcpy(tls->hsd->client_and_server_rand32 + 32, hp->rand32, sizeof(hp->rand32));
 
-	/* Set up encryption params based on selected cipher */
-#if 0
-		0xC0,0x09, // 1 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ok: wget https://is.gd/
-		0xC0,0x0A, // 2 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - ok: wget https://is.gd/
-		0xC0,0x13, // 3 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA
-		0xC0,0x14, // 4 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES256-SHA (might fail with older openssl)
-	//	0xC0,0x18, //   TLS_ECDH_anon_WITH_AES_128_CBC_SHA
-	//	0xC0,0x19, //   TLS_ECDH_anon_WITH_AES_256_CBC_SHA
-		0xC0,0x23, // 5 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - ok: wget https://is.gd/
-	//	0xC0,0x24, //   TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
-		0xC0,0x27, // 6 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA256
-	//	0xC0,0x28, //   TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
-		0xC0,0x2B, // 7 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - ok: wget https://is.gd/
-	//	0xC0,0x2C, //   TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - wget https://is.gd/: "TLS error from peer (alert code 20): bad MAC"
-		0xC0,0x2F, // 8 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-GCM-SHA256
-	//	0xC0,0x30, //   TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher ECDHE-RSA-AES256-GCM-SHA384: "decryption failed or bad record mac"
-	//possibly these too:
-	//	0xC0,0x35, //   TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA
-	//	0xC0,0x36, //   TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA
-	//	0xC0,0x37, //   TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256
-	//	0xC0,0x38, //   TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet
-		0x00,0x2F, // 9 TLS_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher AES128-SHA
-		0x00,0x35, //10 TLS_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher AES256-SHA
-		0x00,0x3C, //11 TLS_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher AES128-SHA256
-		0x00,0x3D, //12 TLS_RSA_WITH_AES_256_CBC_SHA256 - ok: openssl s_server ... -cipher AES256-SHA256
-		0x00,0x9C, //13 TLS_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher AES128-GCM-SHA256
-	//	0x00,0x9D, //   TLS_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher AES256-GCM-SHA384: "decryption failed or bad record mac"
-		0x00,0x3B, //   TLS_RSA_WITH_NULL_SHA256
-#endif
-	cipherid1 = cipherid[1];
-	tls->cipher_id = 0x100 * cipherid[0] + cipherid1;
-	tls->key_size = AES256_KEYSIZE;
-	tls->MAC_size = SHA256_OUTSIZE;
-	/*tls->IV_size = 0; - already is */
-	if (cipherid[0] == 0xC0) {
-		/* All C0xx are ECDHE */
-		tls->flags |= NEED_EC_KEY;
-		if (cipherid1 & 1) {
-			/* Odd numbered C0xx use AES128 (even ones use AES256) */
-			tls->key_size = AES128_KEYSIZE;
-		}
-		if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x19) {
-			tls->MAC_size = SHA1_OUTSIZE;
-		} else
-		if (cipherid1 >= 0x2B && cipherid1 <= 0x30) {
-			/* C02B,2C,2F,30 are AES-GCM */
-			tls->flags |= ENCRYPTION_AESGCM;
-			tls->MAC_size = 0;
-			tls->IV_size = 4;
-		}
-	} else {
-		/* All 00xx are RSA */
-		if ((ENABLE_FEATURE_TLS_SHA1 && cipherid1 == 0x2F)
-		 || cipherid1 == 0x3C
-		 || cipherid1 == 0x9C
-		) {
-			tls->key_size = AES128_KEYSIZE;
-		}
-		if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x35) {
-			tls->MAC_size = SHA1_OUTSIZE;
-		} else
-		if (cipherid1 == 0x9C /*|| cipherid1 == 0x9D*/) {
-			/* 009C,9D are AES-GCM */
-			tls->flags |= ENCRYPTION_AESGCM;
-			tls->MAC_size = 0;
-			tls->IV_size = 4;
-		}
-	}
+	set_cipher_parameters(tls, cipherid);
 	dbg("server chose cipher %04x", tls->cipher_id);
 	dbg("key_size:%u MAC_size:%u IV_size:%u", tls->key_size, tls->MAC_size, tls->IV_size);
 
@@ -2424,6 +2428,7 @@ static void get_client_hello(tls_state_t *tls)
 		uint8_t session_id_len;
 		/* followed by session_id, cipher suites, compression methods, extensions */
 	};
+	unsigned i, j;
 	struct client_hello *hp;
 	uint8_t *p;
 	unsigned cipher_list_len;
@@ -2434,14 +2439,13 @@ static void get_client_hello(tls_state_t *tls)
 	hp = (void*)(tls->inbuf + RECHDR_LEN);
 	if (hp->type != HANDSHAKE_CLIENT_HELLO
 	 || hp->len24_hi  != 0
-	 || hp->len24_mid != 0
-	 /* hp->len24_lo checked later */
+	 /* hp->len24_mid,lo checked later */
 	 || hp->proto_maj != TLS_MAJ
 ///?	 || hp->proto_min != TLS_MIN
 	) {
 		bad_record_die(tls, "'client hello'", len);
 	}
-	dbg("<< CLIENT_HELLO len:%d len24:%d", len, hp->len24_lo);
+	dbg("<< CLIENT_HELLO len:%d len24:%d", len, (hp->len24_mid << 8) | hp->len24_lo);
 
 	/* Save client random */
 	memcpy(tls->hsd->client_and_server_rand32, hp->rand32, 32);
@@ -2469,15 +2473,49 @@ static void get_client_hello(tls_state_t *tls)
 		bb_simple_error_msg_and_die("malformed ClientHello");
 	}
 
-//FIXME: proper cipher suite selection - see get_server_hello()
-	tls->cipher_id = TLS_RSA_WITH_AES_128_CBC_SHA256;
-	//tls->flags = 0; /* RSA mode, no ECDHE */
-	/* Set cipher parameters for TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003C) */
-	tls->key_size = AES128_KEYSIZE;  /* 16 bytes */
-	tls->MAC_size = SHA256_OUTSIZE;  /* 32 bytes */
-	//tls->IV_size = 0; /* For CBC mode, IV is sent with each encrypted record */
+	/* Select cipher + cert pair from client's list, preferring our ciphers in order */
+	for (i = 0; i < NUM_CIPHERS*2; i += 2) {
+		const uint8_t *our_cipher = &supported_ciphers[i];
+		int key_type;
+
+		/* Skip all ECDHE ciphers (0xC0xx) - we don't support server-side ephemeral
+		 * EC key generation yet. ECDHE_RSA uses RSA certificates (which we have)
+		 * but still requires generating ephemeral EC keys for the key exchange.
+		 * We only support plain RSA key exchange (0x00xx) on the server side.
+		 */
+		if (our_cipher[0] == 0xC0) {
+			//TODO: implement server-side ECDHE
+			continue;
+		}
+
+		/* Determine required key type for this cipher */
+		key_type = is_cipher_ECDSA(our_cipher);
+		if (key_type == KEY_ECDSA) {
+			if (!tls->hsd->keys[KEY_ECDSA])
+				/* No ECDSA cert configured, can't choose this */
+				continue;
+			//TODO: ECDSA not supported yet at all
+			continue;
+		} else {
+			if (!tls->hsd->keys[KEY_RSA])
+				/* No RSA cert configured, can't choose this */
+				continue;
+			/* We _can_ choose this! */
+		}
 
-	dbg("Selected cipher: %04x", tls->cipher_id);
+		/* Check if this cipher is in client's list */
+		for (j = 0; j < cipher_list_len; j += 2) {
+			if (p[j] == our_cipher[0] && p[j + 1] == our_cipher[1]) {
+				/* Found a match! */
+				set_cipher_parameters(tls, our_cipher);
+				dbg("Selected cipher: %04x", tls->cipher_id);
+				tls->hsd->key_type_chosen = key_type;
+				return;
+			}
+		}
+		/* try our next cipherid */
+	}
+	bb_simple_error_msg_and_die("no common cipher suites");
 }
 
 static void send_server_hello(tls_state_t *tls)
@@ -2510,7 +2548,7 @@ static void send_server_hello(tls_state_t *tls)
 
 	/* Selected cipher suite */
 	record->cipherid_hi = tls->cipher_id >> 8;
-	record->cipherid_lo = tls->cipher_id & 0xff;
+	record->cipherid_lo = tls->cipher_id; /* & 0xff implicit */
 
 	/* No compression */
 	//record->comprtype = 0;
@@ -2532,37 +2570,14 @@ static void send_server_hello(tls_state_t *tls)
 
 static void send_server_certificate(tls_state_t *tls)
 {
-	struct certificate_msg {
-		uint8_t type;
-		uint8_t len24_hi, len24_mid, len24_lo;
-		uint8_t cert_chain_len24_hi, cert_chain_len24_mid, cert_chain_len24_lo;
-		uint8_t cert1_len24_hi, cert1_len24_mid, cert1_len24_lo;
-		/* followed by certificate DER data */
-	};
-	struct certificate_msg *record;
-	unsigned total_len, cert_len, chain_len;
-
-	cert_len = tls->hsd->server_cert_der_len;
-	total_len = sizeof(*record) + cert_len;
-
-	record = get_outbuf_fill_handshake_record(tls, HANDSHAKE_CERTIFICATE, total_len);
-
-	/* Certificate chain length (just one cert for now) */
-	chain_len = cert_len + 3; /* 3 bytes for cert length */
-	record->cert_chain_len24_hi = chain_len >> 16;
-	record->cert_chain_len24_mid = (chain_len >> 8) & 0xff;
-	record->cert_chain_len24_lo = chain_len & 0xff;
-
-	/* First certificate length */
-	record->cert1_len24_hi = cert_len >> 16;
-	record->cert1_len24_mid = (cert_len >> 8) & 0xff;
-	record->cert1_len24_lo = cert_len & 0xff;
-
-	/* Copy certificate DER data */
-	memcpy(record + 1, tls->hsd->server_cert_der, cert_len);
+	void *record;
+	int n = tls->hsd->key_type_chosen;
+	int sz = tls->hsd->certsize[n];
 
-	dbg(">> CERTIFICATE (len=%u)", cert_len);
-	xwrite_and_update_handshake_hash(tls, total_len);
+	record = tls_get_outbuf(tls, sz);
+	memcpy(record, tls->hsd->certs[n], sz);
+	dbg(">> CERTIFICATE");
+	xwrite_and_update_handshake_hash(tls, sz);
 }
 
 static void send_server_hello_done(tls_state_t *tls)
@@ -2615,9 +2630,10 @@ static void get_client_key_exchange(tls_state_t *tls)
 	{
 		int32 ret;
 		uint32 premaster_len;
+		psRsaKey_t *key = &tls->hsd->rsa_priv_key;
 
 		premaster_len = RSA_PREMASTER_SIZE;
-		ret = psRsaDecryptPriv(NULL, &tls->hsd->server_rsa_priv_key,
+		ret = psRsaDecryptPriv(NULL, key,
 		                       encrypted_premaster, enckey_len,
 		                       premaster, premaster_len, NULL);
 
@@ -2665,16 +2681,10 @@ static void get_client_key_exchange(tls_state_t *tls)
  * }
  */
 static NOINLINE /* don't inline - large stack use */
-void load_rsa_priv_key(psRsaKey_t *key, const char *filename)
+void load_rsa_priv_key(psRsaKey_t *key, uint8_t *buf, ssize_t sz)
 {
-	uint8_t buf[4*1024]; /* DER key files are usually ~1kbyte */
-	ssize_t sz;
 	uint8_t *der, *end;
 
-	sz = open_read_close(filename, buf, sizeof(buf));
-	if (sz < 0)
-		bb_perror_msg_and_die("can't read '%s'", filename);
-
 	der = buf;
 	end = der + sz;
 
@@ -2690,15 +2700,12 @@ void load_rsa_priv_key(psRsaKey_t *key, const char *filename)
 
 	if (*der == 0x30) {
 		/* PKCS#8 format - skip AlgorithmIdentifier and enter OCTET STRING */
-		dbg("Detected PKCS#8 private key format");
 		der = skip_der_item(der, end); /* Skip AlgorithmIdentifier */
 		der = enter_der_item(der, &end); /* Enter OCTET STRING containing PKCS#1 key */
 		der = enter_der_item(der, &end); /* Enter the PKCS#1 SEQUENCE */
 		der = skip_der_item(der, end); /* Skip version again */
-	} else {
-		/* PKCS#1 format - we already skipped the version */
-		dbg("Detected PKCS#1 private key format");
 	}
+	/* else: PKCS#1 format - we already skipped the version */
 
 	/* Read the key components */
 	der_binary_to_pstm(&key->N, der, end);    /* modulus */
@@ -2726,26 +2733,203 @@ void load_rsa_priv_key(psRsaKey_t *key, const char *filename)
 
 	key->size = pstm_unsigned_bin_size(&key->N);
 	key->optimized = 1; /* We have CRT parameters */
+}
 
-	dbg("Loaded RSA private key, size:%d", key->size);
+static char *decode_base64_or_die(char *dst, const char *src)
+{
+	char *dst_end = decode_base64(dst, &src);
+	if (*src != '\0')
+		bb_error_msg_and_die("base64 decode error");
+	return dst_end;
 }
 
-void FAST_FUNC tls_handshake_as_server(tls_state_t *tls,
-	const char *privkey_der_filename,
-	const char *cert_der_filename)
+/* Parse PEM file and extract key + cert chain pairs
+ * Returns number of pairs loaded
+ */
+static void load_pem_key_cert_pairs(tls_state_t *tls, const char *pem_filename)
 {
-	dbg("Starting TLS server handshake");
+	static const char BLOCK_NAMES[] ALIGN1 =
+		"EC PARAMETERS"  "\0"
+		"CERTIFICATE"    "\0"
+		"EC PRIVATE KEY" "\0"
+		"PRIVATE KEY"    "\0"
+		"RSA PRIVATE KEY""\0"
+	;
+	enum {
+		str_EC_PARAMETERS = 0,
+		str_CERTIFICATE = 1,
+		str_EC_KEY = 2,
+	};
+	char *p;
+	char *pem_data;
+	size_t pem_size;
+	char *der_data;
+	unsigned der_size;
+	int keyidx;
+
+	/* Read PEM file */
+	pem_size = 64 * 1024; /* sanity limit */
+	pem_data = xmalloc_xopen_read_close(pem_filename, &pem_size);
+
+	der_data = NULL;
+	der_size = 0;
+	keyidx = -1; /* "we did not see any KEY yet" */
+
+	p = pem_data;
+	while (1) {
+		unsigned n;
+		char *block_end;
+		char *block_type_end;
+
+		/* Find next PEM block */
+		p = skip_whitespace(p);
+		if (*p == '\0')
+			break; /* end of file */
+		p = is_prefixed_with(p, "-----BEGIN ");
+		if (!p)
+			goto err;
+		block_type_end = strstr(p, "-----\n");
+		if (!block_type_end)
+			goto err;
+		block_type_end += 5;
+		block_end = strstr(block_type_end, "\n-----END ");
+		if (!block_end)
+			goto err;
+		*block_end = '\0';
+		block_end += 10;
+//-----BEGIN PRIVATE KEY-----\n
+//           ^p              ^block_type_end
+//BASE64HERE-BASE64HERE
+//-----END PRIVATE KEY-----
+//         ^block_end
+		/* The headers must match */
+		*block_type_end = '\0';
+		if (!is_prefixed_with(block_end, p))
+			goto err;
+		/* Truncate trailing dashes from block type name */
+		block_type_end[-5] = '\0';
+
+		block_end += (block_type_end - p);
+		block_type_end++;
+//BASE64HERE-BASE64HERE
+//^block_type_end
+//-----END PRIVATE KEY-----
+//                         ^block_end
+		n = index_in_strings(BLOCK_NAMES, p);
+		if ((int)n < 0)
+			bb_error_msg_and_die("'%s': unknown PEM block '%s'", pem_filename, p);
+
+		/* Note: may point to "\n" or even NUL */
+		p = block_end;
+
+		/* What block do we see? */
+
+		if (n == str_EC_PARAMETERS) {
+			/* "openssl ecparam -genkey" generates these, skip silently */
+			continue; /* skip */
+		}
 
+		if (n == str_CERTIFICATE) {
+			struct certificate_msg {
+				uint8_t type;
+				uint8_t len24_hi, len24_mid, len24_lo;
+				uint8_t cert_chain_len24_hi, cert_chain_len24_mid, cert_chain_len24_lo;
+				uint8_t cert1_len24_hi, cert1_len24_mid, cert1_len24_lo;
+				/* followed by certificate DER data */
+				/* followed by cert2_len24, cert2 DER data, ... */
+			};
+			struct certificate_msg *cert_msg;
+			unsigned start;
+
+			if (keyidx < 0)
+				bb_error_msg_and_die("'%s': certificate must be after key", pem_filename);
+
+			/* We create or update a full HANDSHAKE_CERTIFICATE message */
+			if (der_size == 0) {
+				der_size = sizeof(*cert_msg);
+				der_data = xzalloc(der_size);
+				cert_msg = (void*)der_data;
+				cert_msg->type = HANDSHAKE_CERTIFICATE;
+			} else {
+				/* We are here when we decode second or later cert */
+				der_size += 3; /* for len24 */
+			}
+
+			/* Decode BASE64 */
+			start = der_size;
+			der_size += block_end - block_type_end; /* worst case size */
+			der_data = xrealloc(der_data, der_size);
+			der_size = decode_base64_or_die(der_data + start, block_type_end) - der_data;
+			der_data = xrealloc(der_data, der_size);
+
+			/* Fill last cert's len24 */
+			n = der_size - start;
+			der_data[start - 3] = n >> 16;
+			der_data[start - 2] = n >> 8;
+			der_data[start - 1] = n;
+			/* Update sizes in header */
+			cert_msg = (void*)der_data;
+			n = der_size - 4;
+			cert_msg->len24_hi  = n >> 16;
+			cert_msg->len24_mid = n >> 8;
+			cert_msg->len24_lo  = n;
+			n -= 3;
+			cert_msg->cert_chain_len24_hi  = n >> 16;
+			cert_msg->cert_chain_len24_mid = n >> 8;
+			cert_msg->cert_chain_len24_lo  = n;
+
+			tls->hsd->certs[keyidx] = der_data;
+			tls->hsd->certsize[keyidx] = der_size;
+			continue;
+		}
+
+		/* We see a key */
+
+		/* Decode BASE64 */
+		der_size = block_end - block_type_end; /* worst case size */
+		der_data = xmalloc(der_size);
+		der_size = decode_base64_or_die(der_data, block_type_end) - der_data;
+		der_data = xrealloc(der_data, der_size);
+
+		keyidx = (n == str_EC_KEY) ? KEY_ECDSA : KEY_RSA;
+		if (tls->hsd->keys[keyidx])
+			bb_error_msg_and_die("'%s': more than one key", pem_filename);
+		tls->hsd->keys[keyidx] = der_data;
+		tls->hsd->keysize[keyidx] = der_size;
+
+		der_data = NULL;
+		der_size = 0;
+	} /* while (parsing PEM) */
+	free(pem_data);
+
+	if (!tls->hsd->keys[KEY_RSA] && !tls->hsd->keys[KEY_ECDSA])
+		bb_error_msg_and_die("'%s': no private keys", pem_filename);
+
+	if (tls->hsd->keys[KEY_RSA]) {
+		if (!tls->hsd->certs[KEY_RSA])
+			bb_error_msg_and_die("'%s': key with no cert", pem_filename);
+		/* Parse RSA key from DER */
+		load_rsa_priv_key(&tls->hsd->rsa_priv_key, (uint8_t*)tls->hsd->keys[KEY_RSA], tls->hsd->keysize[KEY_RSA]);
+	}
+	if (tls->hsd->keys[KEY_ECDSA]) {
+		if (!tls->hsd->certs[KEY_ECDSA])
+			bb_error_msg_and_die("'%s': key with no cert", pem_filename);
+		bb_error_msg("'%s': ECDSA keys not supported", pem_filename);
+	}
+
+	return;
+ err:
+	bb_error_msg_and_die("malformed PEM file at '%.*s'", (int)(skip_whitespace(p) - p), p);
+}
+
+void FAST_FUNC tls_handshake_as_server(tls_state_t *tls,
+	const char *pem_filename)
+{
 	/* Allocate handshake data */
 	tls->hsd = xzalloc(sizeof(*tls->hsd));
 
-	/* Load server private key */
-	tls->hsd->server_cert_der_len = 64*1024; /* sanity limit (don't load multi-megabyte "certificates") */
-	tls->hsd->server_cert_der = xmalloc_xopen_read_close(cert_der_filename, &tls->hsd->server_cert_der_len);
-
-	/* Load server certificate */
-	load_rsa_priv_key(&tls->hsd->server_rsa_priv_key, privkey_der_filename);
-	dbg("Loaded private key: %d bytes", tls->hsd->server_rsa_priv_key.size);
+	/* Load server key(s) and certificate(s) from PEM file */
+	load_pem_key_cert_pairs(tls, pem_filename);
 
 	sha256_begin(&tls->hsd->handshake_hash_ctx);
 	tls->expecting_first_packet = 1;
@@ -2776,13 +2960,14 @@ void FAST_FUNC tls_handshake_as_server(tls_state_t *tls,
 	send_change_cipher_spec(tls);
 	send_finished(tls, "server finished");
 
-	dbg("Server handshake complete");
-
 	/* application data can be sent/received */
 
 	/* free handshake data */
-	psRsaKey_clear(&tls->hsd->server_rsa_priv_key);
-	free(tls->hsd->server_cert_der);
+	psRsaKey_clear(&tls->hsd->rsa_priv_key);
+	free(tls->hsd->keys[0]);
+	free(tls->hsd->keys[1]);
+	free(tls->hsd->certs[0]);
+	free(tls->hsd->certs[1]);
 //	if (PARANOIA)
 //		memset(tls->hsd, 0, sizeof(*tls->hsd));
 	free(tls->hsd);


More information about the busybox-cvs mailing list