Allow user to set custom min and max TLS version limits, this can be
used to ensure we comply with a specific security profile.
---
ell/ell.sym | 1 +
ell/tls-private.h | 10 ++---
ell/tls-record.c | 14 +++----
ell/tls.c | 99 +++++++++++++++++++++++++++++++++--------------
ell/tls.h | 8 +++-
5 files changed, 87 insertions(+), 45 deletions(-)
diff --git a/ell/ell.sym b/ell/ell.sym
index 7d7a5e4..2ff7d30 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -417,6 +417,7 @@ global:
l_tls_close;
l_tls_set_cacert;
l_tls_set_auth_data;
+ l_tls_set_version_range;
l_tls_alert_to_str;
l_tls_set_debug;
/* uintset */
diff --git a/ell/tls-private.h b/ell/tls-private.h
index 8e6c277..e2ec014 100644
--- a/ell/tls-private.h
+++ b/ell/tls-private.h
@@ -20,13 +20,8 @@
*
*/
-/* Only TLS 1.2 supported */
-#define TLS_V12 ((3 << 8) | 3)
-#define TLS_V11 ((3 << 8) | 2)
-#define TLS_V10 ((3 << 8) | 1)
-
-#define TLS_VERSION TLS_V12
-#define TLS_MIN_VERSION TLS_V10
+#define TLS_MAX_VERSION L_TLS_V12
+#define TLS_MIN_VERSION L_TLS_V10
enum tls_cipher_type {
TLS_CIPHER_STREAM,
@@ -145,6 +140,7 @@ struct l_tls {
l_tls_debug_cb_t debug_handler;
l_tls_destroy_cb_t debug_destroy;
void *debug_data;
+ uint16_t min_version, max_version;
struct l_queue *ca_certs;
struct l_certchain *cert;
diff --git a/ell/tls-record.c b/ell/tls-record.c
index bffa413..bdab0c4 100644
--- a/ell/tls-record.c
+++ b/ell/tls-record.c
@@ -135,14 +135,14 @@ static void tls_tx_record_plaintext(struct l_tls *tls,
offset = 0;
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
l_getrandom(ciphertext, tls->record_iv_length[1]);
l_cipher_set_iv(tls->cipher[1], ciphertext,
tls->record_iv_length[1]);
offset = tls->record_iv_length[1];
- } else if (tls->negotiated_version >= TLS_V11) {
+ } else if (tls->negotiated_version >= L_TLS_V11) {
l_getrandom(iv, tls->record_iv_length[1]);
l_cipher_encrypt(tls->cipher[1], iv, ciphertext,
@@ -223,7 +223,7 @@ void tls_tx_record(struct l_tls *tls, enum tls_content_type type,
TX_RECORD_TAILROOM];
uint8_t *fragment, *plaintext;
uint16_t fragment_len;
- uint16_t version = tls->negotiated_version ?: TLS_MIN_VERSION;
+ uint16_t version = tls->negotiated_version ?: tls->min_version;
if (type == TLS_CT_ALERT)
tls->record_flush = true;
@@ -393,7 +393,7 @@ static bool tls_handle_ciphertext(struct l_tls *tls)
if ((tls->negotiated_version && tls->negotiated_version != version) ||
(!tls->negotiated_version &&
- tls->record_buf[1] != 0x03 /* Appending E.1 */)) {
+ tls->record_buf[1] != 0x03 /* Appendix E.1 */)) {
TLS_DISCONNECT(TLS_ALERT_PROTOCOL_VERSION, 0,
"Record version mismatch: %02x", version);
return false;
@@ -444,7 +444,7 @@ static bool tls_handle_ciphertext(struct l_tls *tls)
case TLS_CIPHER_BLOCK:
i = 0;
- if (tls->negotiated_version >= TLS_V11)
+ if (tls->negotiated_version >= L_TLS_V11)
i = tls->record_iv_length[0];
if (fragment_len <= tls->mac_length[0] + i) {
@@ -470,7 +470,7 @@ static bool tls_handle_ciphertext(struct l_tls *tls)
return false;
}
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
if (!l_cipher_set_iv(tls->cipher[0],
tls->record_buf + 5,
tls->record_iv_length[0])) {
@@ -478,7 +478,7 @@ static bool tls_handle_ciphertext(struct l_tls *tls)
"Setting fragment IV failed");
return false;
}
- } else if (tls->negotiated_version >= TLS_V11)
+ } else if (tls->negotiated_version >= L_TLS_V11)
if (!l_cipher_decrypt(tls->cipher[0],
tls->record_buf + 5, iv,
tls->record_iv_length[0])) {
diff --git a/ell/tls.c b/ell/tls.c
index 8099e76..8abbabd 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -130,7 +130,7 @@ static bool tls_prf_get_bytes(struct l_tls *tls,
const void *seed, size_t seed_len,
uint8_t *buf, size_t len)
{
- if (tls->negotiated_version >= TLS_V12)
+ if (tls->negotiated_version >= L_TLS_V12)
return tls12_prf(tls->prf_hmac->l_id, tls->prf_hmac->length,
secret, secret_len, label,
seed, seed_len, buf, len);
@@ -332,7 +332,7 @@ static bool tls_change_cipher_spec(struct l_tls *tls, bool txrx,
key_offset += 2 * enc->key_length;
}
- if (tls->negotiated_version <= TLS_V10 &&
+ if (tls->negotiated_version <= L_TLS_V10 &&
tls->cipher_suite[txrx]->encryption &&
tls->cipher_suite[txrx]->encryption->cipher_type ==
TLS_CIPHER_BLOCK) {
@@ -532,10 +532,24 @@ static bool tls_cipher_suite_is_compatible(struct l_tls *tls,
{
static char error_buf[200];
struct l_cert *leaf;
+ uint16_t negotiated = tls->negotiated_version;
if (suite->encryption &&
suite->encryption->cipher_type == TLS_CIPHER_AEAD) {
- uint16_t negotiated = tls->negotiated_version;
+ if (tls->max_version < L_TLS_V12) {
+ if (error) {
+ *error = error_buf;
+ snprintf(error_buf, sizeof(error_buf),
+ "Cipher suite %s uses an AEAD "
+ "cipher (TLS 1.2+) but "
+ TLS_VER_FMT
+ " is the max version allowed",
+ suite->name,
+ TLS_VER_ARGS(tls->max_version));
+ }
+
+ return false;
+ }
if (negotiated && negotiated < L_TLS_V12) {
if (error) {
@@ -589,10 +603,13 @@ static bool tls_cipher_suite_is_compatible(struct l_tls *tls,
return false;
}
- if ((tls->negotiated_version && tls->negotiated_version < TLS_V12
&&
- (!l_checksum_is_supported(L_CHECKSUM_MD5, true) ||
- !l_checksum_is_supported(L_CHECKSUM_SHA1, true))) ||
- (tls->negotiated_version >= TLS_V12 &&
+ if (
+ ((tls->max_version < L_TLS_V12 ||
+ (negotiated && negotiated < L_TLS_V12)) &&
+ (!l_checksum_is_supported(L_CHECKSUM_MD5, true) ||
+ !l_checksum_is_supported(L_CHECKSUM_SHA1, true))) ||
+ ((tls->min_version >= L_TLS_V12 ||
+ tls->negotiated_version >= L_TLS_V12) &&
!l_checksum_is_supported(
suite->prf_hmac != L_CHECKSUM_NONE ?
suite->prf_hmac : L_CHECKSUM_SHA256,
@@ -664,8 +681,14 @@ static const struct tls_hash_algorithm tls_handshake_hash_data[] = {
static bool tls_init_handshake_hash(struct l_tls *tls)
{
enum handshake_hash_type hash;
+ bool tls10 = tls->max_version < L_TLS_V12;
for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) {
+ /* Skip hash types we already know we won't need */
+ if (tls10 && hash != HANDSHAKE_HASH_SHA1 &&
+ hash != HANDSHAKE_HASH_MD5)
+ continue;
+
if (tls->handshake_hash[hash]) {
TLS_DEBUG("Handshake hash %s already exists",
tls_handshake_hash_data[hash].name);
@@ -820,8 +843,8 @@ static bool tls_send_client_hello(struct l_tls *tls)
/* Fill in the Client Hello body */
- *ptr++ = (uint8_t) (TLS_VERSION >> 8);
- *ptr++ = (uint8_t) (TLS_VERSION >> 0);
+ *ptr++ = (uint8_t) (tls->max_version >> 8);
+ *ptr++ = (uint8_t) (tls->max_version >> 0);
tls_write_random(tls->pending.client_random);
memcpy(ptr, tls->pending.client_random, 32);
@@ -843,7 +866,8 @@ static bool tls_send_client_hello(struct l_tls *tls)
}
if (ptr == len_ptr + 2) {
- TLS_DEBUG("No compatible cipher suites, check kernel config");
+ TLS_DEBUG("No compatible cipher suites, check kernel config, "
+ "certificate's key type and TLS version range");
return false;
}
@@ -1034,7 +1058,7 @@ static bool tls_send_certificate_request(struct l_tls *tls)
* affect both of these steps so revisit which set we're passing
* here.
*/
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
signature_hash_ptr = ptr;
ptr += 2;
@@ -1112,7 +1136,7 @@ static void tls_generate_master_secret(struct l_tls *tls,
tls->pending.cipher_suite->mac->mac_length;
if (tls->pending.cipher_suite->encryption &&
- tls->negotiated_version <= TLS_V10 &&
+ tls->negotiated_version <= L_TLS_V10 &&
tls->pending.cipher_suite->encryption->cipher_type ==
TLS_CIPHER_BLOCK)
key_block_size += 2 *
@@ -1147,8 +1171,9 @@ static bool tls_send_rsa_client_key_xchg(struct l_tls *tls)
return false;
}
- pre_master_secret[0] = (uint8_t) (TLS_VERSION >> 8);
- pre_master_secret[1] = (uint8_t) (TLS_VERSION >> 0);
+ /* Must match the version in tls_send_client_hello */
+ pre_master_secret[0] = (uint8_t) (tls->max_version >> 8);
+ pre_master_secret[1] = (uint8_t) (tls->max_version >> 0);
l_getrandom(pre_master_secret + 2, 46);
if (tls->peer_pubkey_size + 32 > (int) sizeof(buf)) {
@@ -1198,7 +1223,7 @@ static ssize_t tls_rsa_sign(struct l_tls *tls, uint8_t *out, size_t
len,
return -ENOKEY;
}
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
const struct tls_hash_algorithm *hash_type =
&tls_handshake_hash_data[tls->signature_hash];
@@ -1250,7 +1275,7 @@ static bool tls_rsa_verify(struct l_tls *tls, const uint8_t *in,
size_t len,
/* 2 bytes for SignatureAndHashAlgorithm if version >= 1.2 */
offset = 2;
- if (tls->negotiated_version < TLS_V12)
+ if (tls->negotiated_version < L_TLS_V12)
offset = 0;
if (len < offset + 2 ||
@@ -1271,7 +1296,7 @@ static bool tls_rsa_verify(struct l_tls *tls, const uint8_t *in,
size_t len,
return false;
}
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
/* Only RSA supported */
if (in[1] != 1 /* RSA_sign */) {
TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0,
@@ -1392,7 +1417,7 @@ static bool tls_send_certificate_verify(struct l_tls *tls)
return false;
/* Stop maintaining handshake message hashes other than the PRF hash */
- if (tls->negotiated_version >= TLS_V12)
+ if (tls->negotiated_version >= L_TLS_V12)
for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++)
if (&tls_handshake_hash_data[i] != tls->prf_hmac)
tls_drop_handshake_hash(tls, i);
@@ -1417,7 +1442,7 @@ static void tls_send_finished(struct l_tls *tls)
uint8_t seed[HANDSHAKE_HASH_MAX_SIZE * 2];
size_t seed_len;
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
/* Same hash type as that used for the PRF (usually SHA256) */
enum handshake_hash_type hash;
@@ -1458,7 +1483,7 @@ static bool tls_verify_finished(struct l_tls *tls, const uint8_t
*received,
return false;
}
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
enum handshake_hash_type hash;
for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++)
@@ -1574,18 +1599,18 @@ static void tls_handle_client_hello(struct l_tls *tls,
/* Save client_version for Premaster Secret verification */
tls->client_version = l_get_be16(buf);
- if (tls->client_version < TLS_MIN_VERSION) {
+ if (tls->client_version < tls->min_version) {
TLS_DISCONNECT(TLS_ALERT_PROTOCOL_VERSION, 0,
"Client version too low: %02x",
tls->client_version);
return;
}
- tls->negotiated_version = TLS_VERSION < tls->client_version ?
- TLS_VERSION : tls->client_version;
+ tls->negotiated_version = tls->client_version > tls->max_version ?
+ tls->max_version : tls->client_version;
/* Stop maintaining handshake message hashes other than MD1 and SHA. */
- if (tls->negotiated_version < TLS_V12)
+ if (tls->negotiated_version < L_TLS_V12)
for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++)
if (i != HANDSHAKE_HASH_SHA1 && i != HANDSHAKE_HASH_MD5)
tls_drop_handshake_hash(tls, i);
@@ -1703,9 +1728,9 @@ static void tls_handle_server_hello(struct l_tls *tls,
tls->negotiated_version = l_get_be16(buf);
- if (tls->negotiated_version < TLS_MIN_VERSION ||
- tls->negotiated_version > TLS_VERSION) {
- TLS_DISCONNECT(tls->negotiated_version < TLS_MIN_VERSION ?
+ if (tls->negotiated_version < tls->min_version ||
+ tls->negotiated_version > tls->max_version) {
+ TLS_DISCONNECT(tls->negotiated_version < tls->min_version ?
TLS_ALERT_PROTOCOL_VERSION :
TLS_ALERT_ILLEGAL_PARAM, 0,
"Unsupported version %02x",
@@ -1714,7 +1739,7 @@ static void tls_handle_server_hello(struct l_tls *tls,
}
/* Stop maintaining handshake message hashes other than MD1 and SHA. */
- if (tls->negotiated_version < TLS_V12)
+ if (tls->negotiated_version < L_TLS_V12)
for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++)
if (i != HANDSHAKE_HASH_SHA1 && i != HANDSHAKE_HASH_MD5)
tls_drop_handshake_hash(tls, i);
@@ -1911,7 +1936,7 @@ static void tls_handle_certificate_request(struct l_tls *tls,
* lists for use in tls_send_certificate.
*/
- if (tls->negotiated_version >= TLS_V12) {
+ if (tls->negotiated_version >= L_TLS_V12) {
/*
* This only makes sense as a variable-length field, assume
* there's a typo in RFC5246 7.4.4 here.
@@ -2125,7 +2150,7 @@ static void tls_handle_certificate_verify(struct l_tls *tls,
return;
/* Stop maintaining handshake message hashes other than the PRF hash */
- if (tls->negotiated_version >= TLS_V12)
+ if (tls->negotiated_version >= L_TLS_V12)
for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++)
if (&tls_handshake_hash_data[i] != tls->prf_hmac)
tls_drop_handshake_hash(tls, i);
@@ -2458,6 +2483,8 @@ LIB_EXPORT struct l_tls *l_tls_new(bool server,
tls->ready_handle = ready_handler;
tls->disconnected = disconnect_handler;
tls->user_data = user_data;
+ tls->min_version = TLS_MIN_VERSION;
+ tls->max_version = TLS_MAX_VERSION;
tls->signature_hash = HANDSHAKE_HASH_SHA256;
@@ -2751,6 +2778,18 @@ LIB_EXPORT bool l_tls_set_auth_data(struct l_tls *tls, const char
*cert_path,
return true;
}
+LIB_EXPORT void l_tls_set_version_range(struct l_tls *tls,
+ uint16_t min_version,
+ uint16_t max_version)
+{
+ tls->min_version =
+ (min_version && min_version > TLS_MIN_VERSION) ?
+ min_version : TLS_MIN_VERSION;
+ tls->max_version =
+ (max_version && max_version < TLS_MAX_VERSION) ?
+ max_version : TLS_MAX_VERSION;
+}
+
LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc)
{
switch (desc) {
diff --git a/ell/tls.h b/ell/tls.h
index fb33404..5b2f398 100644
--- a/ell/tls.h
+++ b/ell/tls.h
@@ -25,6 +25,10 @@
extern "C" {
#endif
+#define L_TLS_V12 ((3 << 8) | 3)
+#define L_TLS_V11 ((3 << 8) | 2)
+#define L_TLS_V10 ((3 << 8) | 1)
+
struct l_tls;
enum l_tls_alert_desc {
@@ -63,7 +67,6 @@ typedef void (*l_tls_disconnect_cb_t)(enum l_tls_alert_desc reason,
typedef void (*l_tls_debug_cb_t)(const char *str, void *user_data);
typedef void (*l_tls_destroy_cb_t)(void *user_data);
-
/*
* app_data_handler gets called with newly received decrypted data.
* tx_handler gets called to send TLS payloads off to remote end.
@@ -107,6 +110,9 @@ bool l_tls_set_auth_data(struct l_tls *tls, const char *cert_path,
const char *priv_key_path,
const char *priv_key_passphrase);
+void l_tls_set_version_range(struct l_tls *tls,
+ uint16_t min_version, uint16_t max_version);
+
const char *l_tls_alert_to_str(enum l_tls_alert_desc desc);
enum l_checksum_type;
--
2.19.1