[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