| /* |
| * Embedded Linux library |
| * |
| * Copyright (C) 2015 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <time.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <strings.h> |
| |
| #include "util.h" |
| #include "private.h" |
| #include "tls.h" |
| #include "checksum.h" |
| #include "cipher.h" |
| #include "random.h" |
| #include "queue.h" |
| #include "pem.h" |
| #include "cert.h" |
| #include "cert-private.h" |
| #include "tls-private.h" |
| #include "key.h" |
| #include "asn1-private.h" |
| #include "strv.h" |
| #include "missing.h" |
| #include "string.h" |
| |
| bool tls10_prf(const void *secret, size_t secret_len, |
| const char *label, |
| const void *seed, size_t seed_len, |
| uint8_t *out, size_t out_len) |
| { |
| uint8_t p_hash2[out_len]; |
| uint8_t l_s1 = (secret_len + 1) / 2; |
| unsigned int i; |
| |
| /* |
| * RFC2246 section 5: |
| * S1 and S2 are the two halves of the secret, and each is the same |
| * length. S1 is taken from the first half of the secret, S2 from the |
| * second half. Their length is created by rounding up the length of |
| * the overall secret, divided by two; thus, if the original secret is |
| * an odd number of bytes long, the last byte of S1 will be the same as |
| * the first byte of S2. |
| */ |
| |
| if (!tls12_prf(L_CHECKSUM_MD5, secret, l_s1, |
| label, seed, seed_len, |
| out, out_len)) |
| return false; |
| |
| if (secret_len > 0) |
| secret += secret_len - l_s1; |
| |
| if (!tls12_prf(L_CHECKSUM_SHA1, secret, l_s1, |
| label, seed, seed_len, |
| p_hash2, out_len)) |
| return false; |
| |
| for (i = 0; i < out_len; i++) |
| out[i] ^= p_hash2[i]; |
| |
| return true; |
| } |
| |
| bool tls12_prf(enum l_checksum_type type, |
| const void *secret, size_t secret_len, |
| const char *label, |
| const void *seed, size_t seed_len, |
| uint8_t *out, size_t out_len) |
| { |
| struct l_checksum *hmac = l_checksum_new_hmac(type, secret, secret_len); |
| size_t a_len, chunk_len, prfseed_len = strlen(label) + seed_len; |
| uint8_t a[64 + prfseed_len], prfseed[prfseed_len]; |
| |
| if (!hmac) |
| return false; |
| |
| /* Generate the hash seed or A(0) as label + seed */ |
| memcpy(prfseed, label, strlen(label)); |
| memcpy(prfseed + strlen(label), seed, seed_len); |
| |
| memcpy(a, prfseed, prfseed_len); |
| a_len = prfseed_len; |
| |
| while (out_len) { |
| /* Generate A(i) */ |
| l_checksum_reset(hmac); |
| l_checksum_update(hmac, a, a_len); |
| a_len = l_checksum_get_digest(hmac, a, sizeof(a)); |
| |
| /* Append seed & generate output */ |
| memcpy(a + a_len, prfseed, prfseed_len); |
| l_checksum_reset(hmac); |
| l_checksum_update(hmac, a, a_len + prfseed_len); |
| |
| chunk_len = l_checksum_get_digest(hmac, out, out_len); |
| out += chunk_len; |
| out_len -= chunk_len; |
| } |
| |
| l_checksum_free(hmac); |
| return true; |
| } |
| |
| static bool tls_prf_get_bytes(struct l_tls *tls, |
| const void *secret, size_t secret_len, |
| const char *label, |
| const void *seed, size_t seed_len, |
| uint8_t *buf, size_t len) |
| { |
| if (tls->negotiated_version >= L_TLS_V12) |
| return tls12_prf(tls->prf_hmac->l_id, |
| secret, secret_len, label, |
| seed, seed_len, buf, len); |
| else |
| return tls10_prf(secret, secret_len, label, seed, seed_len, |
| buf, len); |
| } |
| |
| LIB_EXPORT bool l_tls_prf_get_bytes(struct l_tls *tls, bool use_master_secret, |
| const char *label, uint8_t *buf, size_t len) |
| { |
| uint8_t seed[64]; |
| bool r; |
| |
| if (unlikely(!tls || !tls->prf_hmac)) |
| return false; |
| |
| memcpy(seed + 0, tls->pending.client_random, 32); |
| memcpy(seed + 32, tls->pending.server_random, 32); |
| |
| if (use_master_secret) |
| r = tls_prf_get_bytes(tls, tls->pending.master_secret, 48, |
| label, seed, 64, buf, len); |
| else |
| r = tls_prf_get_bytes(tls, "", 0, label, seed, 64, buf, len); |
| |
| explicit_bzero(seed, 64); |
| |
| return r; |
| } |
| |
| static void tls_write_random(uint8_t *buf) |
| { |
| l_put_be32(time(NULL), buf); |
| |
| l_getrandom(buf + 4, 28); |
| } |
| |
| static void tls_drop_handshake_hash(struct l_tls *tls, |
| enum handshake_hash_type hash) |
| { |
| if (tls->handshake_hash[hash]) { |
| l_checksum_free(tls->handshake_hash[hash]); |
| |
| tls->handshake_hash[hash] = NULL; |
| } |
| } |
| |
| static void tls_reset_handshake(struct l_tls *tls) |
| { |
| enum handshake_hash_type hash; |
| |
| explicit_bzero(tls->pending.key_block, sizeof(tls->pending.key_block)); |
| |
| if (tls->pending.cipher_suite && |
| tls->pending.cipher_suite->key_xchg->free_params) |
| tls->pending.cipher_suite->key_xchg->free_params(tls); |
| |
| l_cert_free(tls->peer_cert); |
| l_key_free(tls->peer_pubkey); |
| |
| tls->peer_cert = NULL; |
| tls->peer_pubkey = NULL; |
| tls->peer_pubkey_size = 0; |
| tls->negotiated_curve = NULL; |
| tls->negotiated_ff_group = NULL; |
| |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| tls_drop_handshake_hash(tls, hash); |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START); |
| tls->cert_requested = 0; |
| tls->cert_sent = 0; |
| } |
| |
| static void tls_cleanup_handshake(struct l_tls *tls) |
| { |
| explicit_bzero(tls->pending.client_random, 32); |
| explicit_bzero(tls->pending.server_random, 32); |
| explicit_bzero(tls->pending.master_secret, 48); |
| } |
| |
| static bool tls_change_cipher_spec(struct l_tls *tls, bool txrx, |
| const char **error) |
| { |
| struct tls_bulk_encryption_algorithm *enc; |
| struct tls_mac_algorithm *mac; |
| int key_offset; |
| static char error_buf[200]; |
| |
| if (tls->cipher_type[txrx] == TLS_CIPHER_AEAD) { |
| if (tls->aead_cipher[txrx]) { |
| l_aead_cipher_free(tls->aead_cipher[txrx]); |
| tls->aead_cipher[txrx] = NULL; |
| } |
| } else { |
| if (tls->cipher[txrx]) { |
| l_cipher_free(tls->cipher[txrx]); |
| tls->cipher[txrx] = NULL; |
| } |
| } |
| |
| tls->cipher_type[txrx] = TLS_CIPHER_STREAM; |
| |
| if (tls->mac[txrx]) { |
| l_checksum_free(tls->mac[txrx]); |
| tls->mac[txrx] = NULL; |
| } |
| |
| tls->mac_length[txrx] = 0; |
| tls->block_length[txrx] = 0; |
| tls->record_iv_length[txrx] = 0; |
| |
| if (tls->fixed_iv_length[txrx]) { |
| explicit_bzero(tls->fixed_iv[txrx], tls->fixed_iv_length[txrx]); |
| tls->fixed_iv_length[txrx] = 0; |
| } |
| |
| tls->auth_tag_length[txrx] = 0; |
| tls->seq_num[txrx] = 0; |
| |
| tls->cipher_suite[txrx] = tls->pending.cipher_suite; |
| if (!tls->cipher_suite[txrx]) |
| return true; |
| |
| key_offset = 0; |
| |
| if (tls->cipher_suite[txrx]->mac) { |
| mac = tls->cipher_suite[txrx]->mac; |
| |
| /* Server write / client read is 2nd in the key block */ |
| if ((tls->server && txrx) || (!tls->server && !txrx)) |
| key_offset += mac->mac_length; |
| |
| tls->mac[txrx] = l_checksum_new_hmac(mac->hmac_type, |
| tls->pending.key_block + |
| key_offset, mac->mac_length); |
| |
| /* Wipe out the now unneeded part of the key block */ |
| explicit_bzero(tls->pending.key_block + key_offset, |
| mac->mac_length); |
| |
| if (!tls->mac[txrx]) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Can't create %s's %s HMAC", |
| tls->cipher_suite[txrx]->name, |
| txrx ? "Tx" : "Rx"); |
| } |
| |
| return false; |
| } |
| |
| tls->mac_length[txrx] = mac->mac_length; |
| |
| key_offset = 2 * mac->mac_length; |
| } |
| |
| if (tls->cipher_suite[txrx]->encryption) { |
| void *cipher; |
| |
| enc = tls->cipher_suite[txrx]->encryption; |
| |
| /* Server write / client read is 4th in the key block */ |
| if ((tls->server && txrx) || (!tls->server && !txrx)) |
| key_offset += enc->key_length; |
| |
| if (enc->cipher_type == TLS_CIPHER_AEAD) { |
| cipher = l_aead_cipher_new(enc->l_aead_id, |
| tls->pending.key_block + |
| key_offset, enc->key_length, |
| enc->auth_tag_length); |
| tls->aead_cipher[txrx] = cipher; |
| } else { |
| cipher = l_cipher_new(enc->l_id, |
| tls->pending.key_block + |
| key_offset, enc->key_length); |
| tls->cipher[txrx] = cipher; |
| } |
| |
| /* Wipe out the now unneeded part of the key block */ |
| explicit_bzero(tls->pending.key_block + key_offset, |
| enc->key_length); |
| |
| if (!cipher) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Can't create %s's %s cipher", |
| tls->cipher_suite[txrx]->name, |
| txrx ? "Tx" : "Rx"); |
| } |
| |
| return false; |
| } |
| |
| tls->cipher_type[txrx] = enc->cipher_type; |
| tls->record_iv_length[txrx] = enc->iv_length - |
| enc->fixed_iv_length; |
| tls->block_length[txrx] = enc->block_length; |
| tls->auth_tag_length[txrx] = enc->auth_tag_length; |
| |
| if ((tls->server && txrx) || (!tls->server && !txrx)) |
| key_offset += enc->key_length; |
| else |
| key_offset += 2 * enc->key_length; |
| } |
| |
| if (tls->negotiated_version <= L_TLS_V10 && |
| tls->cipher_suite[txrx]->encryption && |
| tls->cipher_suite[txrx]->encryption->cipher_type == |
| TLS_CIPHER_BLOCK) { |
| enc = tls->cipher_suite[txrx]->encryption; |
| |
| /* Server write / client read is 6th in the key block */ |
| if ((tls->server && txrx) || (!tls->server && !txrx)) |
| key_offset += enc->iv_length; |
| |
| l_cipher_set_iv(tls->cipher[txrx], tls->pending.key_block + |
| key_offset, enc->iv_length); |
| |
| /* Wipe out the now unneeded part of the key block */ |
| explicit_bzero(tls->pending.key_block + key_offset, |
| enc->iv_length); |
| } else if (tls->cipher_suite[txrx]->encryption && |
| tls->cipher_suite[txrx]->encryption->fixed_iv_length) { |
| enc = tls->cipher_suite[txrx]->encryption; |
| |
| /* Server write / client read is 6th in the key block */ |
| if ((tls->server && txrx) || (!tls->server && !txrx)) |
| key_offset += enc->fixed_iv_length; |
| |
| tls->fixed_iv_length[txrx] = enc->fixed_iv_length; |
| memcpy(tls->fixed_iv[txrx], tls->pending.key_block + key_offset, |
| enc->fixed_iv_length); |
| |
| /* Wipe out the now unneeded part of the key block */ |
| explicit_bzero(tls->pending.key_block + key_offset, |
| enc->fixed_iv_length); |
| } |
| |
| return true; |
| } |
| |
| static void tls_reset_cipher_spec(struct l_tls *tls, bool txrx) |
| { |
| /* Reset everything to the TLS_NULL_WITH_NULL_NULL state */ |
| |
| tls->pending.cipher_suite = NULL; |
| |
| tls_change_cipher_spec(tls, txrx, NULL); |
| } |
| |
| bool tls_cipher_suite_is_compatible(struct l_tls *tls, |
| const struct tls_cipher_suite *suite, |
| const char **error) |
| { |
| static char error_buf[200]; |
| struct l_cert *leaf; |
| enum l_tls_version min_version = |
| tls->negotiated_version ?: tls->min_version; |
| enum l_tls_version max_version = |
| tls->negotiated_version ?: tls->max_version; |
| |
| if (suite->encryption && |
| suite->encryption->cipher_type == TLS_CIPHER_AEAD) { |
| if (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 |
| " was negotiated or is the max " |
| "version allowed", suite->name, |
| TLS_VER_ARGS(tls->max_version)); |
| } |
| |
| return false; |
| } |
| |
| if (!l_aead_cipher_is_supported(suite->encryption->l_aead_id)) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Cipher suite %s's AEAD cipher " |
| "algorithm not supported by " |
| "the kernel", suite->name); |
| } |
| |
| return false; |
| } |
| } else if (suite->encryption) { /* Block or stream cipher */ |
| if (!l_cipher_is_supported(suite->encryption->l_id)) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Cipher suite %s's block/stream" |
| " cipher algorithm not " |
| "supported by the kernel", |
| suite->name); |
| } |
| |
| return false; |
| } |
| } |
| |
| if (suite->mac && |
| !l_checksum_is_supported(suite->mac->hmac_type, true)) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Cipher suite %s's HMAC algorithm not " |
| "supported by the kernel", suite->name); |
| } |
| |
| return false; |
| } |
| |
| if ( |
| (max_version < L_TLS_V12 && |
| (!l_checksum_is_supported(L_CHECKSUM_MD5, true) || |
| !l_checksum_is_supported(L_CHECKSUM_SHA1, true))) || |
| (min_version >= L_TLS_V12 && |
| !l_checksum_is_supported( |
| suite->prf_hmac != L_CHECKSUM_NONE ? |
| suite->prf_hmac : L_CHECKSUM_SHA256, |
| true))) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Cipher suite %s's PRF algorithm not " |
| "supported by the kernel", suite->name); |
| } |
| |
| return false; |
| } |
| |
| if (suite->key_xchg->need_ffdh && |
| !l_key_is_supported(L_KEY_FEATURE_DH)) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Cipher suite %s's key exchange " |
| "mechanism needs kernel DH support", |
| suite->name); |
| } |
| |
| return false; |
| } |
| |
| /* |
| * If the certificate is compatible with the signature algorithm it |
| * also must be compatible with the key exchange mechanism because |
| * the cipher suites are defined so that the same certificates can |
| * be used by both. |
| */ |
| leaf = l_certchain_get_leaf(tls->cert); |
| if (leaf && suite->signature && |
| !suite->signature->validate_cert_key_type(leaf)) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "Local certificate has key type " |
| "incompatible with cipher suite %s's " |
| "signature algorithm", suite->name); |
| } |
| |
| return false; |
| } |
| |
| /* |
| * On the server we know what elliptic curve we'll be using as soon |
| * as we've processed the ClientHello so for EC-based key exchange |
| * methods require that a curve has been selected. |
| */ |
| if (suite->key_xchg->need_ecc && tls->server && |
| !tls->negotiated_curve) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "No common supported elliptic curves " |
| "with the client, can't use %s", |
| suite->name); |
| } |
| |
| return false; |
| } |
| |
| /* Similarly for FF DH groups */ |
| if (suite->key_xchg->need_ffdh && tls->server && |
| !tls->negotiated_ff_group) { |
| if (error) { |
| *error = error_buf; |
| snprintf(error_buf, sizeof(error_buf), |
| "No common supported finite-field " |
| "groups with the client, can't use %s", |
| suite->name); |
| } |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static struct tls_cipher_suite *tls_find_cipher_suite(const uint8_t *id) |
| { |
| struct tls_cipher_suite **suite; |
| |
| for (suite = tls_cipher_suite_pref; *suite; suite++) |
| if ((*suite)->id[0] == id[0] && (*suite)->id[1] == id[1]) |
| return *suite; |
| |
| return NULL; |
| } |
| |
| static struct tls_compression_method tls_compression_pref[] = { |
| { |
| 0, |
| "CompressionMethod.null", |
| }, |
| }; |
| |
| static struct tls_compression_method *tls_find_compression_method( |
| const uint8_t id) |
| { |
| int i; |
| |
| for (i = 0; i < (int) L_ARRAY_SIZE(tls_compression_pref); i++) |
| if (tls_compression_pref[i].id == id) |
| return &tls_compression_pref[i]; |
| |
| return NULL; |
| } |
| |
| const struct tls_hash_algorithm tls_handshake_hash_data[] = { |
| [HANDSHAKE_HASH_SHA384] = { 5, L_CHECKSUM_SHA384, "SHA384" }, |
| [HANDSHAKE_HASH_SHA256] = { 4, L_CHECKSUM_SHA256, "SHA256" }, |
| [HANDSHAKE_HASH_MD5] = { 1, L_CHECKSUM_MD5, "MD5" }, |
| [HANDSHAKE_HASH_SHA1] = { 2, L_CHECKSUM_SHA1, "SHA1" }, |
| }; |
| |
| 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); |
| goto err; |
| } |
| |
| tls->handshake_hash[hash] = l_checksum_new( |
| tls_handshake_hash_data[hash].l_id); |
| |
| if (!tls->handshake_hash[hash]) { |
| TLS_DEBUG("Can't create %s hash", |
| tls_handshake_hash_data[hash].name); |
| goto err; |
| } |
| } |
| |
| return true; |
| err: |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| tls_drop_handshake_hash(tls, hash); |
| |
| return false; |
| } |
| |
| static const struct tls_hash_algorithm *tls_set_prf_hmac(struct l_tls *tls) |
| { |
| enum handshake_hash_type hash; |
| |
| if (tls->pending.cipher_suite->prf_hmac == L_CHECKSUM_NONE) { |
| tls->prf_hmac = &tls_handshake_hash_data[HANDSHAKE_HASH_SHA256]; |
| return tls->prf_hmac; |
| } |
| |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| if (tls_handshake_hash_data[hash].l_id == |
| tls->pending.cipher_suite->prf_hmac) { |
| tls->prf_hmac = &tls_handshake_hash_data[hash]; |
| return tls->prf_hmac; |
| } |
| |
| return NULL; |
| } |
| |
| static bool tls_domain_match_mask(const char *name, size_t name_len, |
| const char *mask, size_t mask_len) |
| { |
| bool at_start = true; |
| |
| while (1) { |
| const char *name_seg_end = memchr(name, '.', name_len); |
| const char *mask_seg_end = memchr(mask, '.', mask_len); |
| size_t name_seg_len = name_seg_end ? |
| (size_t) (name_seg_end - name) : name_len; |
| size_t mask_seg_len = mask_seg_end ? |
| (size_t) (mask_seg_end - mask) : mask_len; |
| |
| if (mask_seg_len == 1 && mask[0] == '*') { |
| /* |
| * A * at the beginning of the mask matches any |
| * number of labels. |
| */ |
| if (at_start && name_seg_end && |
| tls_domain_match_mask(name_seg_end + 1, |
| name_len - name_seg_len - 1, |
| mask, mask_len)) |
| return true; |
| |
| goto ok_next; |
| } |
| |
| if (name_seg_len != mask_seg_len || |
| memcmp(name, mask, name_seg_len)) |
| return false; |
| |
| ok_next: |
| /* If either string ends here both must end here */ |
| if (!name_seg_end || !mask_seg_end) |
| return !name_seg_end && !mask_seg_end; |
| |
| at_start = false; |
| name = name_seg_end + 1; |
| name_len -= name_seg_len + 1; |
| mask = mask_seg_end + 1; |
| mask_len -= mask_seg_len + 1; |
| } |
| } |
| |
| static const struct asn1_oid subject_alt_name_oid = |
| { 3, { 0x55, 0x1d, 0x11 } }; |
| static const struct asn1_oid dn_common_name_oid = |
| { 3, { 0x55, 0x04, 0x03 } }; |
| |
| #define SAN_DNS_NAME_ID ASN1_CONTEXT_IMPLICIT(2) |
| |
| static bool tls_cert_domains_match_mask(struct l_tls *tls, struct l_cert *cert, |
| char **mask) |
| { |
| const uint8_t *san, *dn, *end; |
| size_t san_len, dn_len; |
| uint8_t san_tag; |
| const char *cn = NULL; |
| size_t cn_len; |
| char **i; |
| bool dns_name_present = false; |
| |
| /* |
| * Locate SubjectAltName (RFC5280 Section 4.2.1.6) and descend into |
| * the sole SEQUENCE element, check if any DNSName matches. |
| */ |
| san = cert_get_extension(cert, &subject_alt_name_oid, NULL, &san_len); |
| if (san) { |
| san = asn1_der_find_elem(san, san_len, 0, &san_tag, &san_len); |
| if (unlikely(!san || san_tag != ASN1_ID_SEQUENCE)) |
| return false; |
| |
| end = san + san_len; |
| while (san < end) { |
| const uint8_t *value; |
| uint8_t tag; |
| size_t len; |
| |
| value = asn1_der_find_elem(san, end - san, |
| SAN_DNS_NAME_ID, |
| &tag, &len); |
| if (!value) |
| break; |
| |
| /* Type is implicitly IA5STRING */ |
| |
| for (i = mask; *i; i++) { |
| TLS_DEBUG("Trying to match DNSName: '%.*s'" |
| " against mask: '%s'", |
| (int) len, value, *i); |
| |
| if (tls_domain_match_mask((const char *) value, |
| len, *i, strlen(*i))) |
| return true; |
| } |
| |
| san = value + len; |
| dns_name_present = true; |
| } |
| } |
| |
| /* |
| * Retrieve the Common Name from the Subject DN and check if it |
| * matches. |
| * |
| * We look at the Common Name only if no DNSNames were present in |
| * the certificate, following Wi-Fi Alliance's Hotspot 2.0 |
| * Specification v3.1 section 7.3.3.2 step 2: |
| * "Verify in the AAA server certificate that the domain name from |
| * the FQDN [...] is a suffix match of the domain name in at least |
| * one of the DNSName SubjectAltName extensions. If a SubjectAltName |
| * of type DNSName is not present, then the domain name from the |
| * FQDN shall be a suffix match to the CommonName portion of the |
| * SubjectName. If neither of these conditions holds, then |
| * verification fails." |
| */ |
| if (unlikely(dns_name_present)) |
| return false; |
| |
| dn = l_cert_get_dn(cert, &dn_len); |
| if (unlikely(!dn)) |
| return false; |
| |
| end = dn + dn_len; |
| while (dn < end) { |
| const uint8_t *set, *seq, *oid, *name; |
| uint8_t tag; |
| size_t len, oid_len, name_len; |
| |
| set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len); |
| if (unlikely(!set || tag != ASN1_ID_SET)) |
| return false; |
| |
| dn = set + len; |
| |
| seq = asn1_der_find_elem(set, len, 0, &tag, &len); |
| if (unlikely(!seq || tag != ASN1_ID_SEQUENCE)) |
| return false; |
| |
| oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len); |
| if (unlikely(!oid || tag != ASN1_ID_OID)) |
| return false; |
| |
| name = asn1_der_find_elem(seq, len, 1, &tag, &name_len); |
| if (unlikely(!name || (tag != ASN1_ID_PRINTABLESTRING && |
| tag != ASN1_ID_UTF8STRING && |
| tag != ASN1_ID_IA5STRING))) |
| continue; |
| |
| if (asn1_oid_eq(&dn_common_name_oid, oid_len, oid)) { |
| cn = (const char *) name; |
| cn_len = name_len; |
| break; |
| } |
| } |
| |
| if (unlikely(!cn)) |
| return false; |
| |
| for (i = mask; *i; i++) { |
| TLS_DEBUG("Trying to match CN: '%.*s' against mask: '%s'", |
| (int) cn_len, cn, *i); |
| |
| if (tls_domain_match_mask(cn, cn_len, *i, strlen(*i))) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| #define SWITCH_ENUM_TO_STR(val) \ |
| case (val): \ |
| return L_STRINGIFY(val); |
| |
| static const char *tls_handshake_type_to_str(enum tls_handshake_type type) |
| { |
| static char buf[100]; |
| |
| switch (type) { |
| SWITCH_ENUM_TO_STR(TLS_HELLO_REQUEST) |
| SWITCH_ENUM_TO_STR(TLS_CLIENT_HELLO) |
| SWITCH_ENUM_TO_STR(TLS_SERVER_HELLO) |
| SWITCH_ENUM_TO_STR(TLS_CERTIFICATE) |
| SWITCH_ENUM_TO_STR(TLS_SERVER_KEY_EXCHANGE) |
| SWITCH_ENUM_TO_STR(TLS_CERTIFICATE_REQUEST) |
| SWITCH_ENUM_TO_STR(TLS_SERVER_HELLO_DONE) |
| SWITCH_ENUM_TO_STR(TLS_CERTIFICATE_VERIFY) |
| SWITCH_ENUM_TO_STR(TLS_CLIENT_KEY_EXCHANGE) |
| SWITCH_ENUM_TO_STR(TLS_FINISHED) |
| } |
| |
| snprintf(buf, sizeof(buf), "tls_handshake_type(%i)", type); |
| return buf; |
| } |
| |
| static void tls_send_alert(struct l_tls *tls, bool fatal, |
| enum l_tls_alert_desc alert_desc) |
| { |
| uint8_t buf[2]; |
| |
| TLS_DEBUG("Sending a %s Alert: %s", fatal ? "Fatal" : "Warning", |
| l_tls_alert_to_str(alert_desc)); |
| |
| buf[0] = fatal ? 2 : 1; |
| buf[1] = alert_desc; |
| |
| tls_tx_record(tls, TLS_CT_ALERT, buf, 2); |
| } |
| |
| /* |
| * Callers make sure this is about the last function before returning |
| * from the stack frames up to the exported library call so that the |
| * user-supplied disconnected callback here is free to use l_tls_free |
| * for example. |
| */ |
| void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc, |
| enum l_tls_alert_desc local_desc) |
| { |
| tls_send_alert(tls, true, desc); |
| |
| tls_reset_handshake(tls); |
| tls_cleanup_handshake(tls); |
| |
| tls_reset_cipher_spec(tls, 0); |
| tls_reset_cipher_spec(tls, 1); |
| |
| tls->negotiated_version = 0; |
| tls->ready = false; |
| |
| tls->disconnected(local_desc ?: desc, local_desc && !desc, |
| tls->user_data); |
| } |
| |
| void tls_tx_handshake(struct l_tls *tls, int type, uint8_t *buf, size_t length) |
| { |
| int i; |
| |
| TLS_DEBUG("Sending a %s of %zi bytes", |
| tls_handshake_type_to_str(type), |
| length - TLS_HANDSHAKE_HEADER_SIZE); |
| |
| /* Fill in the handshake header */ |
| |
| buf[0] = type; |
| buf[1] = (length - TLS_HANDSHAKE_HEADER_SIZE) >> 16; |
| buf[2] = (length - TLS_HANDSHAKE_HEADER_SIZE) >> 8; |
| buf[3] = (length - TLS_HANDSHAKE_HEADER_SIZE) >> 0; |
| |
| for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) |
| if (tls->handshake_hash[i]) |
| l_checksum_update(tls->handshake_hash[i], buf, length); |
| |
| tls_tx_record(tls, TLS_CT_HANDSHAKE, buf, length); |
| } |
| |
| static ssize_t tls_append_hello_extensions(struct l_tls *tls, |
| struct l_queue *extensions, |
| uint8_t *buf, size_t len) |
| { |
| uint8_t *ptr = buf; |
| uint8_t *extensions_len_ptr = ptr; |
| bool client_hello = !tls->server; |
| unsigned int i = 0; |
| const struct l_queue_entry *entry = l_queue_get_entries(extensions); |
| |
| if (len < 2) |
| return -ENOSPC; |
| |
| ptr += 2; |
| len -= 2; |
| |
| while (1) { |
| const struct tls_hello_extension *extension; |
| ssize_t ext_len; |
| ssize_t (*ext_write)(struct l_tls *tls, |
| uint8_t *buf, size_t len); |
| |
| if (client_hello) { |
| extension = &tls_extensions[i++]; |
| if (!extension->name) |
| break; |
| |
| ext_write = extension->client_write; |
| } else { |
| uint16_t ext_id; |
| |
| if (!entry) |
| break; |
| |
| ext_id = L_PTR_TO_UINT(entry->data); |
| entry = entry->next; |
| |
| for (i = 0; tls_extensions[i].name; i++) |
| if (tls_extensions[i].id == ext_id) |
| break; |
| |
| extension = &tls_extensions[i]; |
| if (!extension->name) |
| continue; |
| |
| ext_write = extension->server_write; |
| } |
| |
| /* |
| * Note: could handle NULL client_write with non-NULL |
| * server_handle or server_handle_absent as "server-oriented" |
| * extension (7.4.1.4) and write empty extension_data and |
| * simliarly require empty extension_data in |
| * tls_handle_client_hello if client_handle NULL. |
| */ |
| if (!ext_write) |
| continue; |
| |
| if (len < 4) |
| return -ENOSPC; |
| |
| ext_len = ext_write(tls, ptr + 4, len - 4); |
| if (ext_len == -ENOMSG) |
| continue; |
| |
| if (ext_len < 0) { |
| TLS_DEBUG("%s extension's %s_write: %s", |
| extension->name, |
| client_hello ? "client" : "server", |
| strerror(-ext_len)); |
| return ext_len; |
| } |
| |
| l_put_be16(extension->id, ptr + 0); |
| l_put_be16(ext_len, ptr + 2); |
| ptr += 4 + ext_len; |
| len -= 4 + ext_len; |
| } |
| |
| if (ptr > extensions_len_ptr + 2) |
| l_put_be16(ptr - (extensions_len_ptr + 2), extensions_len_ptr); |
| else /* Skip the length if no extensions */ |
| ptr = extensions_len_ptr; |
| |
| return ptr - buf; |
| } |
| |
| static bool tls_send_client_hello(struct l_tls *tls) |
| { |
| uint8_t buf[1024 + L_ARRAY_SIZE(tls_compression_pref)]; |
| uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; |
| uint8_t *len_ptr; |
| unsigned int i; |
| ssize_t r; |
| struct tls_cipher_suite **suite; |
| |
| /* Fill in the Client Hello body */ |
| |
| *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); |
| ptr += 32; |
| |
| *ptr++ = 0; /* No SessionID */ |
| |
| len_ptr = ptr; |
| ptr += 2; |
| |
| for (suite = tls->cipher_suite_pref_list; *suite; suite++) { |
| const char *error; |
| |
| if (!tls_cipher_suite_is_compatible(tls, *suite, &error)) { |
| TLS_DEBUG("non-fatal: %s", error); |
| continue; |
| } |
| |
| *ptr++ = (*suite)->id[0]; |
| *ptr++ = (*suite)->id[1]; |
| } |
| |
| if (ptr == len_ptr + 2) { |
| TLS_DEBUG("No compatible cipher suites, check kernel config, " |
| "certificate's key type and TLS version range"); |
| return false; |
| } |
| |
| l_put_be16((ptr - len_ptr - 2), len_ptr); |
| *ptr++ = L_ARRAY_SIZE(tls_compression_pref); |
| |
| for (i = 0; i < L_ARRAY_SIZE(tls_compression_pref); i++) |
| *ptr++ = tls_compression_pref[i].id; |
| |
| r = tls_append_hello_extensions(tls, NULL, |
| ptr, buf + sizeof(buf) - ptr); |
| if (r < 0) |
| return false; |
| |
| ptr += r; |
| |
| tls_tx_handshake(tls, TLS_CLIENT_HELLO, buf, ptr - buf); |
| return true; |
| } |
| |
| static bool tls_send_server_hello(struct l_tls *tls, struct l_queue *extensions) |
| { |
| uint8_t buf[1024]; |
| uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; |
| ssize_t r; |
| |
| /* Fill in the Server Hello body */ |
| |
| *ptr++ = tls->negotiated_version >> 8; |
| *ptr++ = tls->negotiated_version >> 0; |
| |
| tls_write_random(tls->pending.server_random); |
| memcpy(ptr, tls->pending.server_random, 32); |
| ptr += 32; |
| |
| *ptr++ = 0; /* Sessions are not cached */ |
| |
| *ptr++ = tls->pending.cipher_suite->id[0]; |
| *ptr++ = tls->pending.cipher_suite->id[1]; |
| |
| *ptr++ = tls->pending.compression_method->id; |
| |
| r = tls_append_hello_extensions(tls, extensions, |
| ptr, buf + sizeof(buf) - ptr); |
| if (r < 0) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "Error appending extensions: %s", |
| strerror(-r)); |
| return false; |
| } |
| |
| ptr += r; |
| |
| tls_tx_handshake(tls, TLS_SERVER_HELLO, buf, ptr - buf); |
| return true; |
| } |
| |
| static bool tls_cert_list_add_size(struct l_cert *cert, void *user_data) |
| { |
| size_t *total = user_data; |
| size_t der_len; |
| |
| l_cert_get_der_data(cert, &der_len); |
| *total += 3 + der_len; |
| |
| return false; |
| } |
| |
| static bool tls_cert_list_append(struct l_cert *cert, void *user_data) |
| { |
| uint8_t **ptr = user_data; |
| const uint8_t *der; |
| size_t der_len; |
| |
| der = l_cert_get_der_data(cert, &der_len); |
| *(*ptr)++ = der_len >> 16; |
| *(*ptr)++ = der_len >> 8; |
| *(*ptr)++ = der_len >> 0; |
| memcpy(*ptr, der, der_len); |
| *ptr += der_len; |
| |
| return false; |
| } |
| |
| static bool tls_send_certificate(struct l_tls *tls) |
| { |
| uint8_t *buf, *ptr; |
| size_t total; |
| |
| if (tls->server && !tls->cert) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, TLS_ALERT_BAD_CERT, |
| "Certificate needed in server mode"); |
| return false; |
| } |
| |
| /* |
| * TODO: check that the certificate is compatible with hash and |
| * signature algorithms lists supplied to us in the Client Hello |
| * extensions (if we're a 1.2+ server) or in the Certificate Request |
| * (if we act as a 1.2+ client). |
| * |
| * - for the hash and signature_algorithms list, check all |
| * certs in the cert chain. |
| * |
| * - also if !cipher_suite->key_xchg->key_exchange_msg, check that the |
| * end entity certificate's key type matches and is usable with some |
| * hash/signature pair. |
| * |
| * - on client check if any of the supplied DNs (if any) match |
| * anything in our cert chain. |
| */ |
| |
| total = 0; |
| l_certchain_walk_from_leaf(tls->cert, tls_cert_list_add_size, &total); |
| |
| buf = l_malloc(128 + total); |
| ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; |
| |
| /* Fill in the Certificate body */ |
| |
| *ptr++ = total >> 16; |
| *ptr++ = total >> 8; |
| *ptr++ = total >> 0; |
| l_certchain_walk_from_leaf(tls->cert, tls_cert_list_append, &ptr); |
| |
| tls_tx_handshake(tls, TLS_CERTIFICATE, buf, ptr - buf); |
| |
| l_free(buf); |
| |
| if (tls->cert) |
| tls->cert_sent = true; |
| |
| return true; |
| } |
| |
| /* |
| * Note: ClientCertificateType.rsa_sign value coincides with the |
| * SignatureAlgorithm.rsa value but other values in those enum are |
| * different so we don't mix them, can't extract them from |
| * tls->pending.cipher_suite->signature. |
| */ |
| static uint8_t tls_cert_type_pref[] = { |
| 1, /* RSA_sign */ |
| }; |
| |
| static bool tls_send_certificate_request(struct l_tls *tls) |
| { |
| uint8_t *buf, *ptr, *dn_ptr; |
| size_t len; |
| const struct l_queue_entry *entry; |
| unsigned int i; |
| size_t dn_total = 0; |
| |
| for (entry = l_queue_get_entries(tls->ca_certs); entry; |
| entry = entry->next) { |
| struct l_cert *ca_cert = entry->data; |
| size_t dn_size; |
| |
| if (l_cert_get_dn(ca_cert, &dn_size)) |
| dn_total += 10 + dn_size; |
| } |
| |
| len = 256 + L_ARRAY_SIZE(tls_cert_type_pref) + dn_total; |
| buf = l_malloc(len); |
| ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; |
| |
| /* Fill in the Certificate Request body */ |
| |
| *ptr++ = L_ARRAY_SIZE(tls_cert_type_pref); |
| for (i = 0; i < L_ARRAY_SIZE(tls_cert_type_pref); i++) |
| *ptr++ = tls_cert_type_pref[i]; |
| |
| if (tls->negotiated_version >= L_TLS_V12) { |
| ssize_t ret = tls_write_signature_algorithms(tls, ptr, |
| buf + len - ptr); |
| |
| if (ret < 0) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "tls_write_signature_algorithms: %s", |
| strerror(-ret)); |
| l_free(buf); |
| return false; |
| } |
| |
| ptr += ret; |
| } |
| |
| dn_ptr = ptr; |
| ptr += 2; /* Leave space for the total DN size */ |
| |
| for (entry = l_queue_get_entries(tls->ca_certs); entry; |
| entry = entry->next) { |
| struct l_cert *ca_cert = entry->data; |
| size_t dn_size; |
| const uint8_t *dn = l_cert_get_dn(ca_cert, &dn_size); |
| uint8_t *cur_dn_ptr = ptr; |
| |
| if (!dn) |
| continue; |
| |
| ptr += 2; /* Leave space for current DN size */ |
| *ptr++ = ASN1_ID_SEQUENCE; /* DER outer SEQUENCE tag */ |
| asn1_write_definite_length(&ptr, dn_size); /* length */ |
| memcpy(ptr, dn, dn_size); /* value */ |
| ptr += dn_size; |
| l_put_be16(ptr - cur_dn_ptr - 2, cur_dn_ptr); |
| } |
| |
| l_put_be16(ptr - dn_ptr - 2, dn_ptr); /* DistinguishedNames size */ |
| |
| tls_tx_handshake(tls, TLS_CERTIFICATE_REQUEST, buf, ptr - buf); |
| |
| l_free(buf); |
| |
| return true; |
| } |
| |
| static void tls_send_server_hello_done(struct l_tls *tls) |
| { |
| uint8_t buf[32]; |
| |
| /* No body */ |
| |
| tls_tx_handshake(tls, TLS_SERVER_HELLO_DONE, buf, |
| TLS_HANDSHAKE_HEADER_SIZE); |
| } |
| |
| void tls_generate_master_secret(struct l_tls *tls, |
| const uint8_t *pre_master_secret, |
| int pre_master_secret_len) |
| { |
| uint8_t seed[64]; |
| int key_block_size; |
| |
| memcpy(seed + 0, tls->pending.client_random, 32); |
| memcpy(seed + 32, tls->pending.server_random, 32); |
| |
| tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len, |
| "master secret", seed, 64, |
| tls->pending.master_secret, 48); |
| |
| /* Directly generate the key block while we're at it */ |
| key_block_size = 0; |
| |
| if (tls->pending.cipher_suite->encryption) |
| key_block_size += 2 * |
| tls->pending.cipher_suite->encryption->key_length; |
| |
| if (tls->pending.cipher_suite->mac) |
| key_block_size += 2 * |
| tls->pending.cipher_suite->mac->mac_length; |
| |
| if (tls->pending.cipher_suite->encryption && |
| tls->negotiated_version <= L_TLS_V10 && |
| tls->pending.cipher_suite->encryption->cipher_type == |
| TLS_CIPHER_BLOCK) |
| key_block_size += 2 * |
| tls->pending.cipher_suite->encryption->iv_length; |
| |
| if (tls->pending.cipher_suite->encryption) |
| key_block_size += 2 * tls->pending.cipher_suite->encryption-> |
| fixed_iv_length; |
| |
| /* Reverse order from the master secret seed */ |
| memcpy(seed + 0, tls->pending.server_random, 32); |
| memcpy(seed + 32, tls->pending.client_random, 32); |
| |
| tls_prf_get_bytes(tls, tls->pending.master_secret, 48, |
| "key expansion", seed, 64, |
| tls->pending.key_block, key_block_size); |
| |
| explicit_bzero(seed, 64); |
| } |
| |
| static void tls_get_handshake_hash(struct l_tls *tls, |
| enum handshake_hash_type type, |
| uint8_t *out) |
| { |
| struct l_checksum *hash = l_checksum_clone(tls->handshake_hash[type]); |
| |
| if (!hash) |
| return; |
| |
| l_checksum_get_digest(hash, out, l_checksum_digest_length( |
| tls_handshake_hash_data[type].l_id)); |
| l_checksum_free(hash); |
| } |
| |
| static bool tls_get_handshake_hash_by_type(struct l_tls *tls, |
| enum handshake_hash_type type, |
| const uint8_t *data, size_t data_len, |
| uint8_t *out, size_t *out_len) |
| { |
| if (!tls->handshake_hash[type]) |
| return false; |
| |
| if (out_len) |
| *out_len = l_checksum_digest_length( |
| tls_handshake_hash_data[type].l_id); |
| |
| tls_get_handshake_hash(tls, type, out); |
| return true; |
| } |
| |
| static bool tls_send_certificate_verify(struct l_tls *tls) |
| { |
| uint8_t buf[2048]; |
| int i; |
| ssize_t sign_len; |
| |
| /* Fill in the Certificate Verify body */ |
| |
| sign_len = tls->pending.cipher_suite->signature->sign(tls, |
| buf + TLS_HANDSHAKE_HEADER_SIZE, |
| 2048 - TLS_HANDSHAKE_HEADER_SIZE, |
| tls_get_handshake_hash_by_type, |
| NULL, 0); |
| |
| if (sign_len < 0) |
| return false; |
| |
| /* Stop maintaining handshake message hashes other than the PRF hash */ |
| 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); |
| |
| tls_tx_handshake(tls, TLS_CERTIFICATE_VERIFY, buf, |
| sign_len + TLS_HANDSHAKE_HEADER_SIZE); |
| |
| return true; |
| } |
| |
| static void tls_send_change_cipher_spec(struct l_tls *tls) |
| { |
| uint8_t buf = 1; |
| |
| tls_tx_record(tls, TLS_CT_CHANGE_CIPHER_SPEC, &buf, 1); |
| } |
| |
| static void tls_send_finished(struct l_tls *tls) |
| { |
| uint8_t buf[512]; |
| uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; |
| uint8_t seed[HANDSHAKE_HASH_MAX_SIZE * 2]; |
| size_t seed_len; |
| |
| if (tls->negotiated_version >= L_TLS_V12) { |
| /* Same hash type as that used for the PRF (usually SHA256) */ |
| enum handshake_hash_type hash; |
| |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| if (&tls_handshake_hash_data[hash] == tls->prf_hmac) |
| break; |
| |
| tls_get_handshake_hash(tls, hash, seed); |
| seed_len = l_checksum_digest_length(tls->prf_hmac->l_id); |
| } else { |
| tls_get_handshake_hash(tls, HANDSHAKE_HASH_MD5, seed + 0); |
| tls_get_handshake_hash(tls, HANDSHAKE_HASH_SHA1, seed + 16); |
| seed_len = 36; |
| } |
| |
| tls_prf_get_bytes(tls, tls->pending.master_secret, 48, |
| tls->server ? "server finished" : |
| "client finished", |
| seed, seed_len, |
| ptr, tls->cipher_suite[1]->verify_data_length); |
| ptr += tls->cipher_suite[1]->verify_data_length; |
| |
| tls_tx_handshake(tls, TLS_FINISHED, buf, ptr - buf); |
| } |
| |
| static bool tls_verify_finished(struct l_tls *tls, const uint8_t *received, |
| size_t len) |
| { |
| uint8_t expected[tls->cipher_suite[0]->verify_data_length]; |
| uint8_t *seed; |
| size_t seed_len; |
| |
| if (len != (size_t) tls->cipher_suite[0]->verify_data_length) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "TLS_FINISHED length not %i", |
| tls->cipher_suite[0]->verify_data_length); |
| |
| return false; |
| } |
| |
| if (tls->negotiated_version >= L_TLS_V12) { |
| enum handshake_hash_type hash; |
| |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| if (&tls_handshake_hash_data[hash] == tls->prf_hmac) |
| break; |
| |
| seed = tls->prev_digest[hash]; |
| seed_len = l_checksum_digest_length(tls->prf_hmac->l_id); |
| } else { |
| seed = alloca(36); |
| memcpy(seed + 0, tls->prev_digest[HANDSHAKE_HASH_MD5], 16); |
| memcpy(seed + 16, tls->prev_digest[HANDSHAKE_HASH_SHA1], 20); |
| seed_len = 36; |
| } |
| |
| tls_prf_get_bytes(tls, tls->pending.master_secret, 48, |
| tls->server ? "client finished" : |
| "server finished", |
| seed, seed_len, |
| expected, |
| tls->cipher_suite[0]->verify_data_length); |
| |
| if (memcmp(received, expected, len)) { |
| TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, |
| "TLS_FINISHED contents don't match"); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool tls_ptr_match(const void *a, const void *b) |
| { |
| return a == b; |
| } |
| |
| static bool tls_handle_hello_extensions(struct l_tls *tls, |
| const uint8_t *buf, size_t len, |
| struct l_queue *seen) |
| { |
| unsigned int i; |
| const struct tls_hello_extension *extension; |
| bool client_hello = tls->server; |
| uint16_t extensions_size; |
| |
| if (!len) |
| return true; |
| |
| if (len < 2 || len > 2 + 65535) |
| goto decode_error; |
| |
| extensions_size = l_get_be16(buf); |
| len -= 2; |
| buf += 2; |
| |
| if (len != extensions_size) |
| goto decode_error; |
| |
| while (len) { |
| uint16_t ext_id; |
| size_t ext_len; |
| bool (*handler)(struct l_tls *tls, |
| const uint8_t *buf, size_t len); |
| |
| if (len < 4) |
| goto decode_error; |
| |
| ext_id = l_get_be16(buf + 0); |
| ext_len = l_get_be16(buf + 2); |
| buf += 4; |
| len -= 4; |
| |
| if (ext_len > len) |
| goto decode_error; |
| |
| /* |
| * RFC 5246, Section 7.4.1.4: "There MUST NOT be more than |
| * one extension of the same type." |
| */ |
| if (l_queue_find(seen, tls_ptr_match, L_UINT_TO_PTR(ext_id))) { |
| TLS_DEBUG("Duplicate extension %u", ext_id); |
| goto decode_error; |
| } |
| |
| l_queue_push_tail(seen, L_UINT_TO_PTR(ext_id)); |
| |
| extension = NULL; |
| |
| for (i = 0; tls_extensions[i].name; i++) |
| if (tls_extensions[i].id == ext_id) { |
| extension = &tls_extensions[i]; |
| break; |
| } |
| |
| if (!extension) |
| goto next; |
| |
| handler = client_hello ? |
| extension->client_handle : extension->server_handle; |
| |
| /* |
| * RFC 5246, Section 7.4.1.4: "If a client receives an |
| * extension type in ServerHello that it did not request in |
| * the associated ClientHello, it MUST abort the handshake |
| * with an unsupported_extension fatal alert." |
| * There are however servers that include an unsolicited |
| * Supported Point Format extension where the handshake |
| * still completes fine if the extension is ignored so we |
| * do this instead. |
| */ |
| if (!client_hello && !handler) { |
| TLS_DEBUG("non-fatal: %s extension not expected in " |
| "a ServerHello", extension->name); |
| goto next; |
| } |
| |
| if (!handler(tls, buf, ext_len)) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "Hello %s extension parse error", |
| extension->name); |
| return false; |
| } |
| |
| next: |
| buf += ext_len; |
| len -= ext_len; |
| } |
| |
| /* |
| * Trigger any actions needed when an extension is missing and its |
| * handler has not been called yet. |
| */ |
| for (i = 0; tls_extensions[i].name; i++) { |
| bool (*handler)(struct l_tls *tls); |
| |
| extension = &tls_extensions[i]; |
| handler = client_hello ? |
| extension->client_handle_absent : |
| extension->server_handle_absent; |
| |
| if (!handler) |
| continue; |
| |
| if (l_queue_find(seen, tls_ptr_match, |
| L_UINT_TO_PTR(extension->id))) |
| continue; |
| |
| if (!handler(tls)) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "Hello %s extension missing", |
| extension->name); |
| return false; |
| } |
| } |
| |
| return true; |
| |
| decode_error: |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "Hello extensions decode error"); |
| return false; |
| } |
| |
| static void tls_handle_client_hello(struct l_tls *tls, |
| const uint8_t *buf, size_t len) |
| { |
| uint16_t cipher_suites_size; |
| uint8_t session_id_size, compression_methods_size; |
| const uint8_t *cipher_suites; |
| const uint8_t *compression_methods; |
| int i; |
| struct l_queue *extensions_offered = NULL; |
| enum l_tls_alert_desc alert_desc = TLS_ALERT_HANDSHAKE_FAIL; |
| |
| /* Do we have enough for ProtocolVersion + Random + SessionID size? */ |
| if (len < 2 + 32 + 1) |
| goto decode_error; |
| |
| memcpy(tls->pending.client_random, buf + 2, 32); |
| session_id_size = buf[34]; |
| len -= 35; |
| |
| /* |
| * Do we have enough to hold the actual session ID + 2 byte field for |
| * cipher_suite len + minimum of a single cipher suite identifier |
| */ |
| if (len < (size_t) session_id_size + 4) |
| goto decode_error; |
| |
| len -= session_id_size + 2; |
| |
| cipher_suites_size = l_get_be16(buf + 35 + session_id_size); |
| cipher_suites = buf + 37 + session_id_size; |
| |
| /* |
| * Check that size is not odd, more than 0 and we have enough |
| * data in the packet for cipher_suites_size + 2 bytes for |
| * compression_methods_size + a single compression method |
| */ |
| if (len < (size_t) cipher_suites_size + 2 || |
| (cipher_suites_size & 1) || cipher_suites_size == 0) |
| goto decode_error; |
| |
| len -= cipher_suites_size + 1; |
| |
| compression_methods_size = cipher_suites[cipher_suites_size]; |
| compression_methods = cipher_suites + cipher_suites_size + 1; |
| |
| if (len < (size_t) compression_methods_size || |
| compression_methods_size == 0) |
| goto decode_error; |
| |
| len -= compression_methods_size; |
| |
| extensions_offered = l_queue_new(); |
| |
| if (!tls_handle_hello_extensions(tls, compression_methods + |
| compression_methods_size, |
| len, extensions_offered)) |
| goto cleanup; |
| |
| /* |
| * Note: if the client is supplying a SessionID we know it is false |
| * because our server implementation never generates any SessionIDs |
| * yet so either the client is attempting something strange or was |
| * trying to connect somewhere else. We might want to throw an error. |
| */ |
| |
| /* Save client_version for Premaster Secret verification */ |
| tls->client_version = l_get_be16(buf); |
| |
| if (tls->client_version < tls->min_version) { |
| TLS_DISCONNECT(TLS_ALERT_PROTOCOL_VERSION, 0, |
| "Client version too low: %02x", |
| tls->client_version); |
| goto cleanup; |
| } |
| |
| 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 < 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); |
| |
| TLS_DEBUG("Negotiated TLS " TLS_VER_FMT, |
| TLS_VER_ARGS(tls->negotiated_version)); |
| |
| if (!tls->cipher_suite_pref_list) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "No usable cipher suites"); |
| goto cleanup; |
| } |
| |
| /* Select a cipher suite according to client's preference list */ |
| while (cipher_suites_size) { |
| struct tls_cipher_suite *suite = |
| tls_find_cipher_suite(cipher_suites); |
| struct tls_cipher_suite **iter; |
| const char *error; |
| |
| for (iter = tls->cipher_suite_pref_list; *iter; iter++) |
| if (*iter == suite) |
| break; |
| |
| if (!suite) |
| TLS_DEBUG("non-fatal: Cipher suite %04x unknown", |
| l_get_be16(cipher_suites)); |
| else if (!tls_cipher_suite_is_compatible(tls, suite, &error)) |
| TLS_DEBUG("non-fatal: %s", error); |
| else if (!*iter) { |
| /* |
| * We have at least one matching compatible suite but |
| * it is not allowed in this security profile. If the |
| * handshake ends up failing then we blame the security |
| * profile. |
| */ |
| alert_desc = TLS_ALERT_INSUFFICIENT_SECURITY; |
| TLS_DEBUG("non-fatal: Cipher suite %s disallowed " |
| "by config", suite->name); |
| } else { |
| tls->pending.cipher_suite = suite; |
| break; |
| } |
| |
| cipher_suites += 2; |
| cipher_suites_size -= 2; |
| } |
| |
| if (!cipher_suites_size) { |
| TLS_DISCONNECT(alert_desc, 0, |
| "No common cipher suites matching negotiated " |
| "TLS version and our certificate's key type"); |
| goto cleanup; |
| } |
| |
| if (!tls_set_prf_hmac(tls)) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "Error selecting the PRF HMAC"); |
| goto cleanup; |
| } |
| |
| TLS_DEBUG("Negotiated %s", tls->pending.cipher_suite->name); |
| |
| /* Select a compression method */ |
| |
| /* CompressionMethod.null must be present in the vector */ |
| if (!memchr(compression_methods, 0, compression_methods_size)) { |
| TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, |
| "No common compression methods"); |
| goto cleanup; |
| } |
| |
| while (compression_methods_size) { |
| tls->pending.compression_method = |
| tls_find_compression_method(*compression_methods); |
| |
| if (tls->pending.compression_method) |
| break; |
| |
| compression_methods++; |
| compression_methods_size--; |
| } |
| |
| TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); |
| |
| if (!tls_send_server_hello(tls, extensions_offered)) |
| goto cleanup; |
| |
| l_queue_destroy(extensions_offered, NULL); |
| |
| if (tls->pending.cipher_suite->signature && tls->cert) |
| if (!tls_send_certificate(tls)) |
| return; |
| |
| if (tls->pending.cipher_suite->key_xchg->send_server_key_exchange) |
| if (!tls->pending.cipher_suite->key_xchg-> |
| send_server_key_exchange(tls)) |
| return; |
| |
| /* TODO: don't bother if configured to not authenticate client */ |
| if (tls->pending.cipher_suite->signature && tls->ca_certs) |
| if (!tls_send_certificate_request(tls)) |
| return; |
| |
| tls_send_server_hello_done(tls); |
| |
| if (tls->pending.cipher_suite->signature && tls->ca_certs) |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE); |
| else |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); |
| |
| return; |
| |
| decode_error: |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "ClientHello decode error"); |
| |
| cleanup: |
| l_queue_destroy(extensions_offered, NULL); |
| } |
| |
| static void tls_handle_server_hello(struct l_tls *tls, |
| const uint8_t *buf, size_t len) |
| { |
| uint8_t session_id_size, cipher_suite_id[2], compression_method_id; |
| const char *error; |
| struct tls_cipher_suite **iter; |
| int i; |
| struct l_queue *extensions_seen; |
| bool result; |
| |
| /* Do we have enough for ProtocolVersion + Random + SessionID len ? */ |
| if (len < 2 + 32 + 1) |
| goto decode_error; |
| |
| memcpy(tls->pending.server_random, buf + 2, 32); |
| session_id_size = buf[34]; |
| len -= 35; |
| |
| /* Do we have enough for SessionID + CipherSuite ID + Compression ID */ |
| if (len < (size_t) session_id_size + 2 + 1) |
| goto decode_error; |
| |
| cipher_suite_id[0] = buf[35 + session_id_size + 0]; |
| cipher_suite_id[1] = buf[35 + session_id_size + 1]; |
| compression_method_id = buf[35 + session_id_size + 2]; |
| len -= session_id_size + 2 + 1; |
| |
| extensions_seen = l_queue_new(); |
| result = tls_handle_hello_extensions(tls, buf + 38 + session_id_size, |
| len, extensions_seen); |
| l_queue_destroy(extensions_seen, NULL); |
| |
| if (!result) |
| return; |
| |
| tls->negotiated_version = l_get_be16(buf); |
| |
| 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", |
| tls->negotiated_version); |
| return; |
| } |
| |
| /* Stop maintaining handshake message hashes other than MD1 and SHA. */ |
| 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); |
| |
| TLS_DEBUG("Negotiated TLS " TLS_VER_FMT, |
| TLS_VER_ARGS(tls->negotiated_version)); |
| |
| /* Set the new cipher suite and compression method structs */ |
| tls->pending.cipher_suite = tls_find_cipher_suite(cipher_suite_id); |
| if (!tls->pending.cipher_suite) { |
| TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, |
| "Unknown cipher suite %04x", |
| l_get_be16(cipher_suite_id)); |
| return; |
| } |
| |
| for (iter = tls->cipher_suite_pref_list; *iter; iter++) |
| if (*iter == tls->pending.cipher_suite) |
| break; |
| if (!*iter) { |
| TLS_DISCONNECT(TLS_ALERT_INSUFFICIENT_SECURITY, 0, |
| "Selected cipher suite %s disallowed by config", |
| tls->pending.cipher_suite->name); |
| return; |
| } |
| |
| if (!tls_cipher_suite_is_compatible(tls, tls->pending.cipher_suite, |
| &error)) { |
| TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, |
| "Selected cipher suite not compatible: %s", |
| error); |
| return; |
| } |
| |
| if (!tls_set_prf_hmac(tls)) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "Error selecting the PRF HMAC"); |
| return; |
| } |
| |
| TLS_DEBUG("Negotiated %s", tls->pending.cipher_suite->name); |
| |
| tls->pending.compression_method = |
| tls_find_compression_method(compression_method_id); |
| if (!tls->pending.compression_method) { |
| TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, |
| "Unknown compression method %i", |
| compression_method_id); |
| return; |
| } |
| |
| TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); |
| |
| if (tls->pending.cipher_suite->signature) |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE); |
| else |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); |
| |
| return; |
| |
| decode_error: |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "ServerHello decode error"); |
| } |
| |
| static void tls_handle_certificate(struct l_tls *tls, |
| const uint8_t *buf, size_t len) |
| { |
| size_t total; |
| struct l_certchain *certchain = NULL; |
| struct l_cert *leaf; |
| size_t der_len; |
| const uint8_t *der; |
| bool dummy; |
| const char *error_str; |
| |
| if (len < 3) |
| goto decode_error; |
| |
| /* Length checks */ |
| total = *buf++ << 16; |
| total |= *buf++ << 8; |
| total |= *buf++ << 0; |
| if (total + 3 != len) |
| goto decode_error; |
| |
| if (tls_parse_certificate_list(buf, total, &certchain) < 0) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "Error decoding peer certificate chain"); |
| |
| goto done; |
| } |
| |
| /* |
| * "Note that a client MAY send no certificates if it does not have any |
| * appropriate certificate to send in response to the server's |
| * authentication request." -- for now we unconditionally accept |
| * an empty certificate chain from the client. Later on we need to |
| * make this configurable, if we don't want to authenticate the |
| * client then also don't bother sending a Certificate Request. |
| */ |
| if (!certchain) { |
| if (!tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, |
| "Server sent no certificate chain"); |
| |
| goto done; |
| } |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); |
| |
| goto done; |
| } |
| |
| /* |
| * Validate the certificate chain's consistency and validate it |
| * against our CAs if we have any. |
| */ |
| if (!l_certchain_verify(certchain, tls->ca_certs, &error_str)) { |
| TLS_DISCONNECT(TLS_ALERT_BAD_CERT, 0, |
| "Peer certchain verification failed " |
| "consistency check%s: %s", tls->ca_certs ? |
| " or against local CA certs" : "", error_str); |
| |
| goto done; |
| } |
| |
| /* |
| * RFC5246 7.4.2: |
| * "The end entity certificate's public key (and associated |
| * restrictions) MUST be compatible with the selected key exchange |
| * algorithm." |
| */ |
| leaf = l_certchain_get_leaf(certchain); |
| if (!tls->pending.cipher_suite->signature-> |
| validate_cert_key_type(leaf)) { |
| TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, |
| "Peer certificate key type incompatible with " |
| "pending cipher suite %s", |
| tls->pending.cipher_suite->name); |
| |
| goto done; |
| } |
| |
| if (tls->subject_mask && !tls_cert_domains_match_mask(tls, leaf, |
| tls->subject_mask)) { |
| char *mask = l_strjoinv(tls->subject_mask, '|'); |
| |
| TLS_DISCONNECT(TLS_ALERT_BAD_CERT, 0, |
| "Peer certificate's subject domain " |
| "doesn't match %s", mask); |
| l_free(mask); |
| |
| goto done; |
| } |
| |
| /* Save the end-entity certificate and free the chain */ |
| der = l_cert_get_der_data(leaf, &der_len); |
| tls->peer_cert = l_cert_new_from_der(der, der_len); |
| |
| tls->peer_pubkey = l_cert_get_pubkey(tls->peer_cert); |
| if (!tls->peer_pubkey) { |
| TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, |
| "Error loading peer public key to kernel"); |
| |
| goto done; |
| } |
| |
| if (!l_key_get_info(tls->peer_pubkey, L_KEY_RSA_PKCS1_V1_5, |
| L_CHECKSUM_NONE, &tls->peer_pubkey_size, |
| &dummy)) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "Can't l_key_get_info for peer public key"); |
| |
| goto done; |
| } |
| |
| tls->peer_pubkey_size /= 8; |
| |
| if (tls->server || tls->pending.cipher_suite->key_xchg-> |
| handle_server_key_exchange) |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); |
| else |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO_DONE); |
| |
| goto done; |
| |
| decode_error: |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "TLS_CERTIFICATE decode error"); |
| |
| done: |
| l_certchain_free(certchain); |
| } |
| |
| static void tls_handle_certificate_request(struct l_tls *tls, |
| const uint8_t *buf, size_t len) |
| { |
| unsigned int cert_type_len, dn_len, i; |
| |
| tls->cert_requested = 1; |
| |
| cert_type_len = *buf++; |
| if (len < 1 + cert_type_len + 2) |
| goto decode_error; |
| |
| for (i = 0; i < sizeof(tls_cert_type_pref); i++) |
| if (memchr(buf, tls_cert_type_pref[i], cert_type_len)) |
| break; |
| |
| if (i == sizeof(tls_cert_type_pref)) { |
| TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, |
| "Requested certificate types not supported"); |
| return; |
| } |
| |
| buf += cert_type_len; |
| len -= 1 + cert_type_len; |
| |
| /* |
| * TODO: parse and save certificate_types, |
| * supported_signature_algorithms and certificate_authorities |
| * lists for use in tls_send_certificate. |
| */ |
| |
| if (tls->negotiated_version >= L_TLS_V12) { |
| enum handshake_hash_type hash; |
| ssize_t ret = tls_parse_signature_algorithms(tls, buf, len); |
| |
| if (ret == -ENOTSUP) { |
| TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, |
| "No supported signature hash type"); |
| return; |
| } |
| |
| if (ret < 0) |
| goto decode_error; |
| |
| len -= ret; |
| buf += ret; |
| |
| /* |
| * We can now safely stop maintaining handshake message |
| * hashes other than the PRF hash and the one selected for |
| * signing. |
| */ |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| if (&tls_handshake_hash_data[hash] != tls->prf_hmac && |
| hash != tls->signature_hash) |
| tls_drop_handshake_hash(tls, hash); |
| } |
| |
| dn_len = l_get_be16(buf); |
| if (2 + dn_len != len) |
| goto decode_error; |
| |
| return; |
| |
| decode_error: |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "CertificateRequest decode error"); |
| } |
| |
| static void tls_handle_server_hello_done(struct l_tls *tls, |
| const uint8_t *buf, size_t len) |
| { |
| const char *error; |
| |
| if (len) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "ServerHello not empty"); |
| return; |
| } |
| |
| if (tls->cert_requested) |
| if (!tls_send_certificate(tls)) |
| return; |
| |
| if (!tls->pending.cipher_suite->key_xchg->send_client_key_exchange(tls)) |
| return; |
| |
| if (tls->cert_sent) |
| if (!tls_send_certificate_verify(tls)) |
| return; |
| |
| tls_send_change_cipher_spec(tls); |
| |
| if (!tls_change_cipher_spec(tls, 1, &error)) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "change_cipher_spec: %s", error); |
| return; |
| } |
| |
| tls_send_finished(tls); |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); |
| } |
| |
| static bool tls_get_prev_digest_by_type(struct l_tls *tls, |
| enum handshake_hash_type type, |
| const uint8_t *data, size_t data_len, |
| uint8_t *out, size_t *out_len) |
| { |
| size_t len; |
| |
| if (!tls->handshake_hash[type]) |
| return false; |
| |
| len = l_checksum_digest_length(tls_handshake_hash_data[type].l_id); |
| memcpy(out, tls->prev_digest[type], len); |
| |
| if (out_len) |
| *out_len = len; |
| |
| return 0; |
| } |
| |
| static void tls_handle_certificate_verify(struct l_tls *tls, |
| const uint8_t *buf, size_t len) |
| { |
| int i; |
| |
| if (!tls->pending.cipher_suite->signature->verify(tls, buf, len, |
| tls_get_prev_digest_by_type, |
| NULL, 0)) |
| return; |
| |
| /* Stop maintaining handshake message hashes other than the PRF hash */ |
| 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); |
| |
| /* |
| * The client's certificate is now verified based on the following |
| * logic: |
| * - If we received an (expected) Certificate Verify, we must have |
| * sent a Certificate Request. |
| * - If we sent a Certificate Request that's because |
| * tls->ca_certs is non-NULL. |
| * - If tls->ca_certs is non-NULL then tls_handle_certificate |
| * will have checked the whole certificate chain to be valid and |
| * additionally trusted by our CAs if known. |
| * - Additionally cipher_suite->signature->verify has just confirmed |
| * that the peer owns the end-entity certificate because it was |
| * able to sign the contents of the handshake messages and that |
| * signature could be verified with the public key from that |
| * certificate. |
| */ |
| tls->peer_authenticated = true; |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); |
| } |
| |
| struct dn_element_info { |
| const char *str; |
| const struct asn1_oid oid; |
| }; |
| |
| static const struct dn_element_info dn_elements[] = { |
| { "CN", { 3, { 0x55, 0x04, 0x03 } } }, |
| { "SN", { 3, { 0x55, 0x04, 0x04 } } }, |
| { "serialNumber", { 3, { 0x55, 0x04, 0x05 } } }, |
| { "C", { 3, { 0x55, 0x04, 0x06 } } }, |
| { "ST", { 3, { 0x55, 0x04, 0x07 } } }, |
| { "L", { 3, { 0x55, 0x04, 0x08 } } }, |
| { "street", { 3, { 0x55, 0x04, 0x09 } } }, |
| { "O", { 3, { 0x55, 0x04, 0x0a } } }, |
| { "OU", { 3, { 0x55, 0x04, 0x0b } } }, |
| { "title", { 3, { 0x55, 0x04, 0x0c } } }, |
| { "telephoneNumber", { 3, { 0x55, 0x04, 0x14 } } }, |
| { "givenName", { 3, { 0x55, 0x04, 0x2a } } }, |
| { "initials", { 3, { 0x55, 0x04, 0x2b } } }, |
| { "emailAddress", { |
| 9, |
| { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 } |
| } }, |
| { "domainComponent", { |
| 10, |
| { 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19 } |
| } }, |
| {} |
| }; |
| |
| static void tls_str_escape_append(struct l_string *out, char *str, size_t len) |
| { |
| while (len--) { |
| switch (*str) { |
| case '\\': |
| case '/': |
| case '=': |
| l_string_append_c(out, '\\'); |
| l_string_append_c(out, *str); |
| break; |
| default: |
| l_string_append_c(out, *str); |
| break; |
| } |
| |
| str++; |
| } |
| } |
| |
| static char *tls_get_peer_identity_str(struct l_cert *cert) |
| { |
| const uint8_t *dn, *end; |
| size_t dn_size; |
| struct l_string *id_str; |
| |
| if (!cert) |
| return NULL; |
| |
| dn = l_cert_get_dn(cert, &dn_size); |
| if (!dn) |
| return NULL; |
| |
| id_str = l_string_new(200); |
| |
| end = dn + dn_size; |
| while (dn < end) { |
| const uint8_t *set, *seq, *oid, *name; |
| uint8_t tag; |
| size_t len, oid_len, name_len; |
| const struct dn_element_info *info; |
| |
| set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len); |
| if (!set || tag != ASN1_ID_SET) |
| goto error; |
| |
| dn = set + len; |
| |
| seq = asn1_der_find_elem(set, len, 0, &tag, &len); |
| if (!seq || tag != ASN1_ID_SEQUENCE) |
| goto error; |
| |
| oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len); |
| if (!oid || tag != ASN1_ID_OID) |
| goto error; |
| |
| name = asn1_der_find_elem(seq, len, 1, &tag, &name_len); |
| if (!name || (tag != ASN1_ID_PRINTABLESTRING && |
| tag != ASN1_ID_UTF8STRING && |
| tag != ASN1_ID_IA5STRING)) |
| continue; |
| |
| for (info = dn_elements; info->str; info++) |
| if (asn1_oid_eq(&info->oid, oid_len, oid)) |
| break; |
| if (!info->str) |
| continue; |
| |
| l_string_append_c(id_str, '/'); |
| l_string_append(id_str, info->str); |
| l_string_append_c(id_str, '='); |
| tls_str_escape_append(id_str, (char *) name, name_len); |
| } |
| |
| return l_string_unwrap(id_str); |
| |
| error: |
| l_string_free(id_str); |
| return NULL; |
| } |
| |
| static void tls_finished(struct l_tls *tls) |
| { |
| char *peer_identity = NULL; |
| |
| if (tls->peer_authenticated) { |
| peer_identity = tls_get_peer_identity_str(tls->peer_cert); |
| if (!peer_identity) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "tls_get_peer_identity_str failed"); |
| return; |
| } |
| } |
| |
| /* Free up the resources used in the handshake */ |
| tls_reset_handshake(tls); |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_DONE); |
| tls->ready = true; |
| |
| tls->in_callback = true; |
| tls->ready_handle(peer_identity, tls->user_data); |
| tls->in_callback = false; |
| l_free(peer_identity); |
| |
| tls_cleanup_handshake(tls); |
| } |
| |
| static void tls_handle_handshake(struct l_tls *tls, int type, |
| const uint8_t *buf, size_t len) |
| { |
| TLS_DEBUG("Handling a %s of %zi bytes", |
| tls_handshake_type_to_str(type), len); |
| |
| switch (type) { |
| case TLS_HELLO_REQUEST: |
| if (tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in server mode"); |
| break; |
| } |
| |
| if (len != 0) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "HelloRequest not empty"); |
| break; |
| } |
| |
| /* |
| * May be sent by the server at any time but "SHOULD be ignored |
| * by the client if it arrives in the middle of a handshake" |
| * and "MAY be ignored by the client if it does not wish to |
| * renegotiate a session". |
| */ |
| |
| break; |
| |
| case TLS_CLIENT_HELLO: |
| if (!tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in client mode"); |
| break; |
| } |
| |
| if (tls->state != TLS_HANDSHAKE_WAIT_HELLO && |
| tls->state != TLS_HANDSHAKE_DONE) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| tls_handle_client_hello(tls, buf, len); |
| |
| break; |
| |
| case TLS_SERVER_HELLO: |
| if (tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in server mode"); |
| break; |
| } |
| |
| if (tls->state != TLS_HANDSHAKE_WAIT_HELLO) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| tls_handle_server_hello(tls, buf, len); |
| |
| break; |
| |
| case TLS_CERTIFICATE: |
| if (tls->state != TLS_HANDSHAKE_WAIT_CERTIFICATE) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| tls_handle_certificate(tls, buf, len); |
| |
| break; |
| |
| case TLS_SERVER_KEY_EXCHANGE: |
| if (tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in server mode"); |
| break; |
| } |
| |
| if (tls->state != TLS_HANDSHAKE_WAIT_KEY_EXCHANGE) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO_DONE); |
| |
| tls->pending.cipher_suite->key_xchg->handle_server_key_exchange( |
| tls, buf, len); |
| |
| break; |
| |
| case TLS_CERTIFICATE_REQUEST: |
| if (tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in server mode"); |
| break; |
| } |
| |
| /* |
| * Server sends this optionally so in the WAIT_HELLO_DONE |
| * state we accept either this or a Server Hello Done (below). |
| */ |
| if (tls->state != TLS_HANDSHAKE_WAIT_HELLO_DONE || |
| tls->cert_requested || |
| !tls->pending.cipher_suite->signature) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in current state " |
| "or certificate check not supported " |
| "in pending cipher suite"); |
| break; |
| } |
| |
| tls_handle_certificate_request(tls, buf, len); |
| |
| break; |
| |
| case TLS_SERVER_HELLO_DONE: |
| if (tls->state != TLS_HANDSHAKE_WAIT_HELLO_DONE) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| tls_handle_server_hello_done(tls, buf, len); |
| |
| break; |
| |
| case TLS_CERTIFICATE_VERIFY: |
| if (tls->state != TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| tls_handle_certificate_verify(tls, buf, len); |
| |
| break; |
| |
| case TLS_CLIENT_KEY_EXCHANGE: |
| if (!tls->server) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in client mode"); |
| break; |
| } |
| |
| if (tls->state != TLS_HANDSHAKE_WAIT_KEY_EXCHANGE) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| /* |
| * If we accepted a client Certificate message with a |
| * certificate that has signing capability (TODO: check |
| * usage bitmask), Certificate Verify is received next. It |
| * sounds as if this is mandatory for the client although |
| * this isn't 100% clear. |
| */ |
| if (tls->peer_pubkey) |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY); |
| else |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); |
| |
| tls->pending.cipher_suite->key_xchg->handle_client_key_exchange( |
| tls, buf, len); |
| |
| break; |
| |
| case TLS_FINISHED: |
| if (tls->state != TLS_HANDSHAKE_WAIT_FINISHED) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Message invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| break; |
| } |
| |
| if (!tls_verify_finished(tls, buf, len)) |
| break; |
| |
| if (tls->server) { |
| const char *error; |
| |
| tls_send_change_cipher_spec(tls); |
| if (!tls_change_cipher_spec(tls, 1, &error)) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "change_cipher_spec: %s", |
| error); |
| break; |
| } |
| tls_send_finished(tls); |
| } |
| |
| /* |
| * On the client, the server's certificate is now verified |
| * regardless of the key exchange method, based on the |
| * following logic: |
| * |
| * - tls->ca_certs is non-NULL so tls_handle_certificate |
| * (always called on the client) must have veritifed the |
| * server's certificate chain to be valid and additionally |
| * trusted by our CA. |
| * |
| * - the peer owns the end-entity certificate because: |
| * either: |
| * |
| * * (RSA key exchange algorithm case) the correct |
| * receival of this Finished message confirms the |
| * posession of the master secret, it is verified by |
| * both the successful decryption and the MAC of this |
| * message (either should be enough) because we entered |
| * the TLS_HANDSHAKE_WAIT_FINISHED state only after |
| * encryption and MAC were enabled in ChangeCipherSpec. |
| * To obtain the master secret the server must have been |
| * able to decrypt the pre_master_secret which we had |
| * encrypted with the public key from that certificate. |
| * |
| * * (ECDHE and DHE key exchange algorithms) server was |
| * able to sign the client random together with the |
| * ServerKeyExchange parameters using its certified key |
| * pair. |
| */ |
| if (!tls->server && tls->cipher_suite[0]->signature && |
| tls->ca_certs) |
| tls->peer_authenticated = true; |
| |
| tls_finished(tls); |
| |
| break; |
| |
| default: |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Invalid message"); |
| } |
| } |
| |
| LIB_EXPORT struct l_tls *l_tls_new(bool server, |
| l_tls_write_cb_t app_data_handler, |
| l_tls_write_cb_t tx_handler, |
| l_tls_ready_cb_t ready_handler, |
| l_tls_disconnect_cb_t disconnect_handler, |
| void *user_data) |
| { |
| struct l_tls *tls; |
| |
| if (!l_key_is_supported(L_KEY_FEATURE_CRYPTO)) |
| return NULL; |
| |
| tls = l_new(struct l_tls, 1); |
| tls->server = server; |
| tls->rx = app_data_handler; |
| tls->tx = tx_handler; |
| tls->ready_handle = ready_handler; |
| tls->disconnected = disconnect_handler; |
| tls->user_data = user_data; |
| tls->cipher_suite_pref_list = tls_cipher_suite_pref; |
| tls->min_version = TLS_MIN_VERSION; |
| tls->max_version = TLS_MAX_VERSION; |
| |
| /* If we're the server wait for the Client Hello already */ |
| if (tls->server) |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO); |
| else |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START); |
| |
| return tls; |
| } |
| |
| LIB_EXPORT void l_tls_free(struct l_tls *tls) |
| { |
| enum handshake_hash_type hash; |
| |
| if (unlikely(!tls)) |
| return; |
| |
| if (tls->in_callback) { |
| tls->pending_destroy = true; |
| return; |
| } |
| |
| l_tls_set_cacert(tls, NULL); |
| l_tls_set_auth_data(tls, NULL, NULL); |
| l_tls_set_domain_mask(tls, NULL); |
| |
| tls_reset_handshake(tls); |
| tls_cleanup_handshake(tls); |
| |
| tls_reset_cipher_spec(tls, 0); |
| tls_reset_cipher_spec(tls, 1); |
| |
| if (tls->record_buf) |
| l_free(tls->record_buf); |
| |
| if (tls->message_buf) |
| l_free(tls->message_buf); |
| |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) |
| tls_drop_handshake_hash(tls, hash); |
| |
| if (tls->debug_destroy) |
| tls->debug_destroy(tls->debug_data); |
| |
| if (tls->cipher_suite_pref_list != tls_cipher_suite_pref) |
| l_free(tls->cipher_suite_pref_list); |
| |
| l_free(tls); |
| } |
| |
| LIB_EXPORT void l_tls_write(struct l_tls *tls, const uint8_t *data, size_t len) |
| { |
| if (unlikely(!tls->ready)) { |
| return; |
| } |
| |
| tls_tx_record(tls, TLS_CT_APPLICATION_DATA, data, len); |
| } |
| |
| bool tls_handle_message(struct l_tls *tls, const uint8_t *message, |
| int len, enum tls_content_type type, uint16_t version) |
| { |
| enum handshake_hash_type hash; |
| const char *error; |
| |
| switch (type) { |
| case TLS_CT_CHANGE_CIPHER_SPEC: |
| if (len != 1 || message[0] != 0x01) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "ChangeCipherSpec msg decode error"); |
| |
| return false; |
| } |
| |
| if (tls->state != TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "ChangeCipherSpec invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| |
| return false; |
| } |
| |
| if (!tls_change_cipher_spec(tls, 0, &error)) { |
| TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, |
| "change_cipher_spec: %s", error); |
| |
| return false; |
| } |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_FINISHED); |
| |
| return true; |
| |
| case TLS_CT_ALERT: |
| /* Verify AlertLevel */ |
| if (message[0] != 0x01 && message[0] != 0x02) { |
| TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, |
| "Received bad AlertLevel %i", |
| message[0]); |
| |
| return false; |
| } |
| |
| /* |
| * On a fatal alert we are obligated to respond with a |
| * fatal alert and disconnect but also not complain if |
| * the connection has been torn down by the peer before |
| * we were able to send our alert. However on a non-fatal |
| * alert (warning) we're also allowed to panic and send |
| * a fatal alert, then disconnect, so we do that |
| * regardless of the alert level. |
| */ |
| TLS_DISCONNECT(TLS_ALERT_CLOSE_NOTIFY, message[1], |
| "Peer sent a %s Alert: %s", |
| message[0] == 0x02 ? "Fatal" : "Warning", |
| l_tls_alert_to_str(message[1])); |
| |
| return false; |
| |
| case TLS_CT_HANDSHAKE: |
| /* Start hashing the handshake contents on first message */ |
| if (tls->server && message[0] == TLS_CLIENT_HELLO && |
| (tls->state == TLS_HANDSHAKE_WAIT_HELLO || |
| tls->state != TLS_HANDSHAKE_DONE)) |
| if (!tls_init_handshake_hash(tls)) |
| return false; |
| |
| /* |
| * Corner case: When handling a Certificate Verify or a |
| * Finished message we need access to the messages hash from |
| * before this message was transmitted on the Tx side so we |
| * can verify it matches the hash the sender included in the |
| * message. We save it here for that purpose. Everywhere |
| * else we need to update the hash before handling the new |
| * message because 1. we may need the new hash to build our |
| * own Certificate Verify or Finished messages, and 2. we |
| * update the message hash with newly transmitted messages |
| * inside tls_tx_handshake which may be called as part of |
| * handling incoming message, and if we didn't call |
| * l_checksum_update before, the calls would end up being |
| * out of order. |
| */ |
| if (message[0] == TLS_CERTIFICATE_VERIFY || |
| message[0] == TLS_FINISHED) |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) { |
| if (!tls->handshake_hash[hash]) |
| continue; |
| |
| tls_get_handshake_hash(tls, hash, |
| tls->prev_digest[hash]); |
| } |
| |
| /* |
| * RFC 5246, Section 7.4.1.1: |
| * This message MUST NOT be included in the message hashes |
| * that are maintained throughout the handshake and used in |
| * the Finished messages and the certificate verify message. |
| */ |
| if (message[0] != TLS_HELLO_REQUEST) |
| for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) { |
| if (!tls->handshake_hash[hash]) |
| continue; |
| |
| l_checksum_update(tls->handshake_hash[hash], |
| message, len); |
| } |
| |
| tls_handle_handshake(tls, message[0], |
| message + TLS_HANDSHAKE_HEADER_SIZE, |
| len - TLS_HANDSHAKE_HEADER_SIZE); |
| |
| if (tls->pending_destroy) { |
| l_tls_free(tls); |
| return false; |
| } |
| |
| return true; |
| |
| case TLS_CT_APPLICATION_DATA: |
| if (!tls->ready) { |
| TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, |
| "Application data message before " |
| "handshake finished"); |
| |
| return false; |
| } |
| |
| if (!len) |
| return true; |
| |
| tls->in_callback = true; |
| tls->rx(message, len, tls->user_data); |
| tls->in_callback = false; |
| |
| if (tls->pending_destroy) { |
| l_tls_free(tls); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_tls_start(struct l_tls *tls) |
| { |
| if (tls->max_version < tls->min_version) |
| return false; |
| |
| if (!tls->cipher_suite_pref_list) |
| return false; |
| |
| /* This is a nop in server mode */ |
| if (tls->server) |
| return true; |
| |
| if (tls->state != TLS_HANDSHAKE_WAIT_START) { |
| TLS_DEBUG("Call invalid in state %s", |
| tls_handshake_state_to_str(tls->state)); |
| return false; |
| } |
| |
| if (!tls_init_handshake_hash(tls)) |
| return false; |
| |
| if (!tls_send_client_hello(tls)) |
| return false; |
| |
| TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO); |
| return true; |
| } |
| |
| LIB_EXPORT void l_tls_close(struct l_tls *tls) |
| { |
| TLS_DISCONNECT(TLS_ALERT_CLOSE_NOTIFY, 0, "Closing session"); |
| } |
| |
| LIB_EXPORT bool l_tls_set_cacert(struct l_tls *tls, struct l_queue *ca_certs) |
| { |
| if (tls->ca_certs) { |
| l_queue_destroy(tls->ca_certs, |
| (l_queue_destroy_func_t) l_cert_free); |
| tls->ca_certs = NULL; |
| } |
| |
| if (ca_certs) { |
| if (!l_key_is_supported(L_KEY_FEATURE_RESTRICT)) { |
| TLS_DEBUG("keyctl restrict support missing, " |
| "check kernel configuration"); |
| return false; |
| } |
| |
| tls->ca_certs = ca_certs; |
| } |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_tls_set_auth_data(struct l_tls *tls, |
| struct l_certchain *certchain, |
| struct l_key *priv_key) |
| { |
| if (tls->cert) { |
| l_certchain_free(tls->cert); |
| tls->cert = NULL; |
| } |
| |
| if (tls->priv_key) { |
| l_key_free(tls->priv_key); |
| tls->priv_key = NULL; |
| tls->priv_key_size = 0; |
| } |
| |
| if (certchain) |
| tls->cert = certchain; |
| |
| if (priv_key) { |
| bool is_public = true; |
| |
| tls->priv_key = priv_key; |
| |
| if (!l_key_get_info(tls->priv_key, L_KEY_RSA_PKCS1_V1_5, |
| L_CHECKSUM_NONE, &tls->priv_key_size, |
| &is_public) || is_public) { |
| TLS_DEBUG("Not a private key or l_key_get_info failed"); |
| tls->cert = NULL; |
| tls->priv_key = NULL; |
| tls->priv_key_size = 0; |
| return false; |
| } |
| |
| tls->priv_key_size /= 8; |
| } |
| |
| return true; |
| } |
| |
| bool tls_set_cipher_suites(struct l_tls *tls, const char **suite_list) |
| { |
| struct tls_cipher_suite **suite; |
| |
| if (tls->cipher_suite_pref_list != tls_cipher_suite_pref) |
| l_free(tls->cipher_suite_pref_list); |
| |
| if (!suite_list) { |
| /* Use our default cipher suite preference list */ |
| tls->cipher_suite_pref_list = tls_cipher_suite_pref; |
| return true; |
| } |
| |
| tls->cipher_suite_pref_list = l_new(struct tls_cipher_suite *, |
| l_strv_length((char **) suite_list) + 1); |
| suite = tls->cipher_suite_pref_list; |
| |
| for (; *suite_list; suite_list++) { |
| unsigned int i; |
| |
| for (i = 0; tls_cipher_suite_pref[i]; i++) |
| if (!strcmp(tls_cipher_suite_pref[i]->name, |
| *suite_list)) |
| break; |
| |
| if (tls_cipher_suite_pref[i]) |
| *suite++ = tls_cipher_suite_pref[i]; |
| else |
| TLS_DEBUG("Cipher suite %s is not supported", |
| *suite_list); |
| } |
| |
| if (suite > tls->cipher_suite_pref_list) |
| return true; |
| |
| TLS_DEBUG("None of the supplied suite names is supported"); |
| l_free(suite); |
| tls->cipher_suite_pref_list = NULL; |
| return false; |
| } |
| |
| LIB_EXPORT void l_tls_set_version_range(struct l_tls *tls, |
| enum l_tls_version min_version, |
| enum l_tls_version 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; |
| } |
| |
| /** |
| * l_tls_set_domain_mask: |
| * @tls: TLS object being configured |
| * @mask: NULL-terminated array of domain masks |
| * |
| * Sets a mask for domain names contained in the peer certificate |
| * (eg. the subject Common Name) to be matched against. If none of the |
| * domains match the any mask, authentication will fail. At least one |
| * domain has to match at least one mask from the list. |
| * |
| * The masks are each split into segments at the dot characters and each |
| * segment must match the corresponding label of the domain name -- |
| * a domain name is a sequence of labels joined by dots. An asterisk |
| * segment in the mask matches any label. An asterisk segment at the |
| * beginning of the mask matches one or more consecutive labels from |
| * the beginning of the domain string. |
| */ |
| LIB_EXPORT void l_tls_set_domain_mask(struct l_tls *tls, char **mask) |
| { |
| l_strv_free(tls->subject_mask); |
| |
| tls->subject_mask = l_strv_copy(mask); |
| } |
| |
| LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc) |
| { |
| switch (desc) { |
| case TLS_ALERT_CLOSE_NOTIFY: |
| return "close_notify"; |
| case TLS_ALERT_UNEXPECTED_MESSAGE: |
| return "unexpected_message"; |
| case TLS_ALERT_BAD_RECORD_MAC: |
| return "bad_record_mac"; |
| case TLS_ALERT_DECRYPT_FAIL_RESERVED: |
| return "decryption_failure_RESERVED"; |
| case TLS_ALERT_RECORD_OVERFLOW: |
| return "record_overflow"; |
| case TLS_ALERT_DECOMPRESS_FAIL: |
| return "decompression_failure"; |
| case TLS_ALERT_HANDSHAKE_FAIL: |
| return "handshake_failure"; |
| case TLS_ALERT_NO_CERT_RESERVED: |
| return "no_certificate_RESERVED"; |
| case TLS_ALERT_BAD_CERT: |
| return "bad_certificate"; |
| case TLS_ALERT_UNSUPPORTED_CERT: |
| return "unsupported_certificate"; |
| case TLS_ALERT_CERT_REVOKED: |
| return "certificate_revoked"; |
| case TLS_ALERT_CERT_EXPIRED: |
| return "certificate_expired"; |
| case TLS_ALERT_CERT_UNKNOWN: |
| return "certificate_unknown"; |
| case TLS_ALERT_ILLEGAL_PARAM: |
| return "illegal_parameter"; |
| case TLS_ALERT_UNKNOWN_CA: |
| return "unknown_ca"; |
| case TLS_ALERT_ACCESS_DENIED: |
| return "access_denied"; |
| case TLS_ALERT_DECODE_ERROR: |
| return "decode_error"; |
| case TLS_ALERT_DECRYPT_ERROR: |
| return "decrypt_error"; |
| case TLS_ALERT_EXPORT_RES_RESERVED: |
| return "export_restriction_RESERVED"; |
| case TLS_ALERT_PROTOCOL_VERSION: |
| return "protocol_version"; |
| case TLS_ALERT_INSUFFICIENT_SECURITY: |
| return "insufficient_security"; |
| case TLS_ALERT_INTERNAL_ERROR: |
| return "internal_error"; |
| case TLS_ALERT_USER_CANCELED: |
| return "user_canceled"; |
| case TLS_ALERT_NO_RENEGOTIATION: |
| return "no_renegotiation"; |
| case TLS_ALERT_UNSUPPORTED_EXTENSION: |
| return "unsupported_extension"; |
| } |
| |
| return NULL; |
| } |
| |
| const char *tls_handshake_state_to_str(enum tls_handshake_state state) |
| { |
| static char buf[100]; |
| |
| switch (state) { |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_START) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_HELLO) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_CERTIFICATE) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_HELLO_DONE) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_FINISHED) |
| SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_DONE) |
| } |
| |
| snprintf(buf, sizeof(buf), "tls_handshake_state(%i)", state); |
| return buf; |
| } |
| |
| int tls_parse_certificate_list(const void *data, size_t len, |
| struct l_certchain **out_certchain) |
| { |
| const uint8_t *buf = data; |
| struct l_certchain *chain = NULL; |
| |
| while (len) { |
| struct l_cert *cert; |
| size_t cert_len; |
| |
| if (len < 3) |
| goto decode_error; |
| |
| cert_len = *buf++ << 16; |
| cert_len |= *buf++ << 8; |
| cert_len |= *buf++ << 0; |
| |
| if (cert_len + 3 > len) |
| goto decode_error; |
| |
| cert = l_cert_new_from_der(buf, cert_len); |
| if (!cert) |
| goto decode_error; |
| |
| if (!chain) { |
| chain = certchain_new_from_leaf(cert); |
| if (!chain) |
| goto decode_error; |
| } else |
| certchain_link_issuer(chain, cert); |
| |
| buf += cert_len; |
| len -= cert_len + 3; |
| } |
| |
| if (out_certchain) |
| *out_certchain = chain; |
| else |
| l_certchain_free(chain); |
| |
| return 0; |
| |
| decode_error: |
| l_certchain_free(chain); |
| return -EBADMSG; |
| } |
| |
| LIB_EXPORT bool l_tls_set_debug(struct l_tls *tls, l_tls_debug_cb_t function, |
| void *user_data, l_tls_destroy_cb_t destroy) |
| { |
| if (unlikely(!tls)) |
| return false; |
| |
| if (tls->debug_destroy) |
| tls->debug_destroy(tls->debug_data); |
| |
| tls->debug_handler = function; |
| tls->debug_destroy = destroy; |
| tls->debug_data = user_data; |
| |
| return true; |
| } |