diff options
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls.c | 17 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls.h | 18 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_crypto.c | 296 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_crypto.h | 7 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_peer.c | 3 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_protection.c | 168 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_protection.h | 15 |
7 files changed, 485 insertions, 39 deletions
diff --git a/src/charon/plugins/eap_tls/tls/tls.c b/src/charon/plugins/eap_tls/tls/tls.c index 9181eacd1..8c94e42c6 100644 --- a/src/charon/plugins/eap_tls/tls/tls.c +++ b/src/charon/plugins/eap_tls/tls/tls.c @@ -116,6 +116,12 @@ METHOD(tls_t, build, status_t, return this->protection->build(this->protection, type, data); } +METHOD(tls_t, is_server, bool, + private_tls_t *this) +{ + return this->is_server; +} + METHOD(tls_t, get_version, tls_version_t, private_tls_t *this) { @@ -128,6 +134,13 @@ METHOD(tls_t, set_version, void, this->version = version; } +METHOD(tls_t, change_cipher, void, + private_tls_t *this, bool inbound, signer_t *signer, + crypter_t *crypter, chunk_t iv) +{ + this->protection->set_cipher(this->protection, inbound, signer, crypter, iv); +} + METHOD(tls_t, destroy, void, private_tls_t *this) { @@ -152,8 +165,10 @@ tls_t *tls_create(bool is_server, identification_t *server, .public = { .process = _process, .build = _build, + .is_server = _is_server, .get_version = _get_version, .set_version = _set_version, + .change_cipher = _change_cipher, .destroy = _destroy, }, .is_server = is_server, @@ -173,7 +188,7 @@ tls_t *tls_create(bool is_server, identification_t *server, } this->fragmentation = tls_fragmentation_create(this->handshake); this->compression = tls_compression_create(this->fragmentation); - this->protection = tls_protection_create(this->compression); + this->protection = tls_protection_create(&this->public, this->compression); return &this->public; } diff --git a/src/charon/plugins/eap_tls/tls/tls.h b/src/charon/plugins/eap_tls/tls/tls.h index a740f99be..b07516a94 100644 --- a/src/charon/plugins/eap_tls/tls/tls.h +++ b/src/charon/plugins/eap_tls/tls/tls.h @@ -155,6 +155,13 @@ struct tls_t { status_t (*build)(tls_t *this, tls_content_type_t *type, chunk_t *data); /** + * Check if TLS stack is acting as a server. + * + * @return TRUE if server, FALSE if peer + */ + bool (*is_server)(tls_t *this); + + /** * Get the negotiated TLS/SSL version. * * @return negotiated TLS version @@ -169,6 +176,17 @@ struct tls_t { void (*set_version)(tls_t *this, tls_version_t version); /** + * Change used cipher, including encryption and integrity algorithms. + * + * @param inbound TRUE to use cipher for inbound data, FALSE for outbound + * @param signer new signer to use + * @param crypter new crypter to use + * @param iv initial IV for crypter + */ + void (*change_cipher)(tls_t *this, bool inbound, signer_t *signer, + crypter_t *crypter, chunk_t iv); + + /** * Destroy a tls_t. */ void (*destroy)(tls_t *this); diff --git a/src/charon/plugins/eap_tls/tls/tls_crypto.c b/src/charon/plugins/eap_tls/tls/tls_crypto.c index 6b2038198..829d29a89 100644 --- a/src/charon/plugins/eap_tls/tls/tls_crypto.c +++ b/src/charon/plugins/eap_tls/tls/tls_crypto.c @@ -53,8 +53,112 @@ struct private_tls_crypto_t { * Connection state TLS PRF */ tls_prf_t *prf; + + /** + * Signer instance for inbound traffic + */ + signer_t *signer_in; + + /** + * Signer instance for outbound traffic + */ + signer_t *signer_out; + + /** + * Crypter instance for inbound traffic + */ + crypter_t *crypter_in; + + /** + * Crypter instance for outbound traffic + */ + crypter_t *crypter_out; + + /** + * IV for input decryption, if < TLSv1.2 + */ + chunk_t iv_in; + + /** + * IV for output decryption, if < TLSv1.2 + */ + chunk_t iv_out; }; +typedef struct { + tls_cipher_suite_t suite; + hash_algorithm_t hash; + pseudo_random_function_t prf; + integrity_algorithm_t mac; + encryption_algorithm_t encr; + size_t encr_size; +} suite_algs_t; + +/** + * Mapping suites to a set of algorithms + */ +static suite_algs_t suite_algs[] = { + { TLS_RSA_WITH_NULL_MD5, + HASH_MD5, + PRF_HMAC_MD5, + AUTH_HMAC_MD5_128, + ENCR_NULL, 0 + }, + { TLS_RSA_WITH_NULL_SHA, + HASH_SHA1, + PRF_HMAC_SHA1, + AUTH_HMAC_SHA1_160, + ENCR_NULL, 0 + }, + { TLS_RSA_WITH_NULL_SHA256, + HASH_SHA256, + PRF_HMAC_SHA2_256, + AUTH_HMAC_SHA2_256_256, + ENCR_NULL, 0 + }, + { TLS_RSA_WITH_AES_128_CBC_SHA, + HASH_SHA1, + PRF_HMAC_SHA1, + AUTH_HMAC_SHA1_160, + ENCR_AES_CBC, 16 + }, + { TLS_RSA_WITH_AES_256_CBC_SHA, + HASH_SHA1, + PRF_HMAC_SHA1, + AUTH_HMAC_SHA1_160, + ENCR_AES_CBC, 32 + }, + { TLS_RSA_WITH_3DES_EDE_CBC_SHA, + HASH_SHA1, + PRF_HMAC_SHA1, + AUTH_HMAC_SHA1_160, + ENCR_3DES, 0 + }, + { TLS_RSA_WITH_AES_128_CBC_SHA256, + HASH_SHA256, + PRF_HMAC_SHA2_256, + AUTH_HMAC_SHA2_256_256, + ENCR_AES_CBC, 16 + }, +}; + +/** + * Look up algoritms by a suite + */ +static suite_algs_t *find_suite(tls_cipher_suite_t suite) +{ + int i; + + for (i = 0; i < countof(suite_algs); i++) + { + if (suite_algs[i].suite == suite) + { + return &suite_algs[i]; + } + } + return NULL; +} + /** * Initialize the cipher suite list */ @@ -153,6 +257,68 @@ METHOD(tls_crypto_t, get_cipher_suites, int, return this->suite_count; } +/** + * Create crypto primitives + */ +static bool create_ciphers(private_tls_crypto_t *this, tls_cipher_suite_t suite) +{ + suite_algs_t *algs; + + algs = find_suite(suite); + if (!algs) + { + DBG1(DBG_IKE, "selected TLS suite not supported"); + return FALSE; + } + + DESTROY_IF(this->prf); + if (this->tls->get_version(this->tls) < TLS_1_2) + { + this->prf = tls_prf_create_10(); + } + else + { + this->prf = tls_prf_create_12(algs->prf); + } + if (!this->prf) + { + DBG1(DBG_IKE, "selected TLS PRF not supported"); + return FALSE; + } + + DESTROY_IF(this->signer_in); + DESTROY_IF(this->signer_out); + this->signer_in = lib->crypto->create_signer(lib->crypto, algs->mac); + this->signer_out = lib->crypto->create_signer(lib->crypto, algs->mac); + if (!this->signer_in || !this->signer_out) + { + DBG1(DBG_IKE, "selected TLS MAC %N not supported", + integrity_algorithm_names, algs->mac); + return FALSE; + } + + DESTROY_IF(this->crypter_in); + DESTROY_IF(this->crypter_out); + if (algs->encr == ENCR_NULL) + { + this->crypter_in = this->crypter_out = NULL; + } + else + { + this->crypter_in = lib->crypto->create_crypter(lib->crypto, + algs->encr, algs->encr_size); + this->crypter_out = lib->crypto->create_crypter(lib->crypto, + algs->encr, algs->encr_size); + if (!this->crypter_in || !this->crypter_out) + { + DBG1(DBG_IKE, "selected TLS crypter %N not supported", + encryption_algorithm_names, algs->encr); + return FALSE; + } + } + return TRUE; +} + METHOD(tls_crypto_t, select_cipher_suite, tls_cipher_suite_t, private_tls_crypto_t *this, tls_cipher_suite_t *suites, int count) { @@ -164,8 +330,11 @@ METHOD(tls_crypto_t, select_cipher_suite, tls_cipher_suite_t, { if (this->suites[i] == suites[j]) { - this->suite = this->suites[i]; - return this->suite; + if (create_ciphers(this, this->suites[i])) + { + this->suite = this->suites[i]; + return this->suite; + } } } } @@ -176,62 +345,120 @@ METHOD(tls_crypto_t, derive_master_secret, void, private_tls_crypto_t *this, chunk_t premaster, chunk_t client_random, chunk_t server_random) { - if (!this->prf) + char master[48]; + chunk_t seed, block, client_write, server_write; + int mks, eks = 0, ivs = 0; + + /* derive master secret */ + seed = chunk_cata("cc", client_random, server_random); + this->prf->set_key(this->prf, premaster); + this->prf->get_bytes(this->prf, "master secret", seed, + sizeof(master), master); + + this->prf->set_key(this->prf, chunk_from_thing(master)); + memset(master, 0, sizeof(master)); + + /* derive key block for key expansion */ + mks = this->signer_out->get_key_size(this->signer_out); + if (this->crypter_out) { + eks = this->crypter_out->get_key_size(this->crypter_out); if (this->tls->get_version(this->tls) < TLS_1_2) { - this->prf = tls_prf_create_10(); + ivs = this->crypter_out->get_block_size(this->crypter_out); + } + } + seed = chunk_cata("cc", server_random, client_random); + block = chunk_alloca((mks + eks + ivs) * 2); + this->prf->get_bytes(this->prf, "key expansion", seed, block.len, block.ptr); + + /* signer keys */ + client_write = chunk_create(block.ptr, mks); + block = chunk_skip(block, mks); + server_write = chunk_create(block.ptr, mks); + block = chunk_skip(block, mks); + if (this->tls->is_server(this->tls)) + { + this->signer_in->set_key(this->signer_in, client_write); + this->signer_out->set_key(this->signer_out, server_write); + } + else + { + this->signer_out->set_key(this->signer_out, client_write); + this->signer_in->set_key(this->signer_in, server_write); + } + + /* crypter keys, and IVs if < TLSv1.2 */ + if (this->crypter_out && this->crypter_in) + { + client_write = chunk_create(block.ptr, eks); + block = chunk_skip(block, eks); + server_write = chunk_create(block.ptr, eks); + block = chunk_skip(block, eks); + + if (this->tls->is_server(this->tls)) + { + this->crypter_in->set_key(this->crypter_in, client_write); + this->crypter_out->set_key(this->crypter_out, server_write); } else { - switch (this->suite) + this->crypter_out->set_key(this->crypter_out, client_write); + this->crypter_in->set_key(this->crypter_in, server_write); + } + if (ivs) + { + client_write = chunk_create(block.ptr, ivs); + block = chunk_skip(block, ivs); + server_write = chunk_create(block.ptr, ivs); + block = chunk_skip(block, ivs); + + if (this->tls->is_server(this->tls)) { - case TLS_RSA_WITH_NULL_MD5: - this->prf = tls_prf_create_12(PRF_HMAC_MD5); - break; - case TLS_RSA_WITH_AES_128_CBC_SHA: - case TLS_RSA_WITH_AES_256_CBC_SHA: - case TLS_RSA_WITH_3DES_EDE_CBC_SHA: - case TLS_RSA_WITH_NULL_SHA: - this->prf = tls_prf_create_12(PRF_HMAC_SHA1); - break; - case TLS_RSA_WITH_AES_128_CBC_SHA256: - case TLS_RSA_WITH_NULL_SHA256: - this->prf = tls_prf_create_12(PRF_HMAC_SHA2_256); - break; - default: - DBG1(DBG_IKE, "PRF for cipher suite unknown"); - break; + this->iv_in = chunk_clone(client_write); + this->iv_out = chunk_clone(server_write); + } + else + { + this->iv_out = chunk_clone(client_write); + this->iv_in = chunk_clone(server_write); } } } - if (this->prf) - { - char master[48]; - chunk_t seed; - - seed = chunk_cata("cc", client_random, server_random); - this->prf->set_key(this->prf, premaster); - this->prf->get_bytes(this->prf, "master secret", seed, - sizeof(master), master); +} - this->prf->set_key(this->prf, chunk_from_thing(master)); - memset(master, 0, sizeof(master)); +METHOD(tls_crypto_t, change_cipher, void, + private_tls_crypto_t *this, bool inbound) +{ + if (inbound) + { + this->tls->change_cipher(this->tls, TRUE, this->signer_in, + this->crypter_in, this->iv_in); + } + else + { + this->tls->change_cipher(this->tls, FALSE, this->signer_out, + this->crypter_out, this->iv_out); } } METHOD(tls_crypto_t, get_prf, tls_prf_t*, private_tls_crypto_t *this) { - return this->prf; } METHOD(tls_crypto_t, destroy, void, private_tls_crypto_t *this) { - free(this->suites); + DESTROY_IF(this->signer_in); + DESTROY_IF(this->signer_out); + DESTROY_IF(this->crypter_in); + DESTROY_IF(this->crypter_out); + free(this->iv_in.ptr); + free(this->iv_out.ptr); DESTROY_IF(this->prf); + free(this->suites); free(this); } @@ -247,6 +474,7 @@ tls_crypto_t *tls_crypto_create(tls_t *tls) .get_cipher_suites = _get_cipher_suites, .select_cipher_suite = _select_cipher_suite, .derive_master_secret = _derive_master_secret, + .change_cipher = _change_cipher, .get_prf = _get_prf, .destroy = _destroy, }, diff --git a/src/charon/plugins/eap_tls/tls/tls_crypto.h b/src/charon/plugins/eap_tls/tls/tls_crypto.h index c2cb1757f..672764369 100644 --- a/src/charon/plugins/eap_tls/tls/tls_crypto.h +++ b/src/charon/plugins/eap_tls/tls/tls_crypto.h @@ -60,6 +60,13 @@ struct tls_crypto_t { chunk_t client_random, chunk_t server_random); /** + * Change the cipher used at protection layer. + * + * @param inbound TRUE to change inbound cipher, FALSE for outbound + */ + void (*change_cipher)(tls_crypto_t *this, bool inbound); + + /** * Get the connection state PRF. * * @return PRF, NULL if not supported diff --git a/src/charon/plugins/eap_tls/tls/tls_peer.c b/src/charon/plugins/eap_tls/tls/tls_peer.c index 5f6888eaa..f742cafd0 100644 --- a/src/charon/plugins/eap_tls/tls/tls_peer.c +++ b/src/charon/plugins/eap_tls/tls/tls_peer.c @@ -508,10 +508,10 @@ static status_t send_certificate_verify(private_tls_peer_t *this, } writer->write_data16(writer, signature); free(signature.ptr); - chunk_free(&this->handshake); *type = TLS_CERTIFICATE_VERIFY; this->state = STATE_VERIFY_SENT; + append_handshake(this, *type, writer->get_buf(writer)); return NEED_MORE; } @@ -596,6 +596,7 @@ METHOD(tls_handshake_t, cipherspec_changed, bool, { if (this->state == STATE_VERIFY_SENT) { + this->crypto->change_cipher(this->crypto, FALSE); this->state = STATE_CIPHERSPEC_CHANGED; return TRUE; } diff --git a/src/charon/plugins/eap_tls/tls/tls_protection.c b/src/charon/plugins/eap_tls/tls/tls_protection.c index 51d0db601..d0bb1e030 100644 --- a/src/charon/plugins/eap_tls/tls/tls_protection.c +++ b/src/charon/plugins/eap_tls/tls/tls_protection.c @@ -30,33 +30,195 @@ struct private_tls_protection_t { tls_protection_t public; /** + * TLS context + */ + tls_t *tls; + + /** * Upper layer, TLS record compression */ tls_compression_t *compression; + + /** + * RNG if we generate IVs ourself + */ + rng_t *rng; + + /** + * Sequence number of incoming records + */ + u_int32_t seq_in; + + /** + * Sequence number for outgoing records + */ + u_int32_t seq_out; + + /** + * Signer instance for inbound traffic + */ + signer_t *signer_in; + + /** + * Signer instance for outbound traffic + */ + signer_t *signer_out; + + /** + * Crypter instance for inbound traffic + */ + crypter_t *crypter_in; + + /** + * Crypter instance for outbound traffic + */ + crypter_t *crypter_out; + + /** + * Current IV for input decryption + */ + chunk_t iv_in; + + /** + * Current IV for output decryption + */ + chunk_t iv_out; }; +/** + * Create the header to append to the record data to create the MAC + */ +static chunk_t sigheader(u_int32_t seq, u_int8_t type, + u_int16_t version, u_int16_t length) +{ + /* we only support 32 bit sequence numbers, but TLS uses 64 bit */ + u_int32_t seq_high = 0; + + seq = htonl(seq); + version = htons(version); + length = htons(length); + + return chunk_cat("ccccc", chunk_from_thing(seq_high), + chunk_from_thing(seq), chunk_from_thing(type), + chunk_from_thing(version), chunk_from_thing(length)); +} + METHOD(tls_protection_t, process, status_t, private_tls_protection_t *this, tls_content_type_t type, chunk_t data) { + this->seq_in++; return this->compression->process(this->compression, type, data); } METHOD(tls_protection_t, build, status_t, private_tls_protection_t *this, tls_content_type_t *type, chunk_t *data) { - return this->compression->build(this->compression, type, data); + status_t status; + + status = this->compression->build(this->compression, type, data); + if (*type == TLS_CHANGE_CIPHER_SPEC) + { + this->seq_out = 0; + return status; + } + + if (status == NEED_MORE) + { + if (this->signer_out) + { + chunk_t mac, header; + + header = sigheader(this->seq_out, *type, + this->tls->get_version(this->tls), data->len); + this->signer_out->get_signature(this->signer_out, header, NULL); + free(header.ptr); + this->signer_out->allocate_signature(this->signer_out, *data, &mac); + if (this->crypter_out) + { + chunk_t padding, iv; + u_int8_t bs, padding_length; + + bs = this->crypter_out->get_block_size(this->crypter_out); + padding_length = bs - ((data->len + mac.len + 1) % bs); + + padding = chunk_alloca(padding_length); + memset(padding.ptr, padding_length, padding.len); + + if (this->iv_out.len) + { /* < TLSv1.2 uses IV from key derivation/last block */ + iv = this->iv_out; + } + else + { /* TLSv1.2 uses random IVs, prepended to record */ + if (!this->rng) + { + DBG1(DBG_IKE, "no RNG supported to generate TLS IV"); + free(data->ptr); + return FAILED; + } + this->rng->allocate_bytes(this->rng, bs, &iv); + } + + *data = chunk_cat("mmcc", *data, mac, padding, + chunk_from_thing(padding_length)); + /* encrypt inline */ + this->crypter_out->encrypt(this->crypter_out, *data, + this->iv_out, NULL); + + if (this->iv_out.len) + { /* next record IV is last ciphertext block of this record */ + memcpy(this->iv_out.ptr, data->ptr - this->iv_out.len, + this->iv_out.len); + } + else + { /* prepend IV */ + *data = chunk_cat("mm", iv, *data); + } + } + else + { /* NULL encryption */ + *data = chunk_cat("mm", *data, mac); + } + } + } + this->seq_out++; + return status; +} + +METHOD(tls_protection_t, set_cipher, void, + private_tls_protection_t *this, bool inbound, signer_t *signer, + crypter_t *crypter, chunk_t iv) +{ + if (inbound) + { + this->signer_in = signer; + this->crypter_in = crypter; + this->iv_in = iv; + } + else + { + this->signer_out = signer; + this->crypter_out = crypter; + this->iv_out = iv; + if (!iv.len) + { /* generate IVs if none given */ + this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + } + } } METHOD(tls_protection_t, destroy, void, private_tls_protection_t *this) { + DESTROY_IF(this->rng); free(this); } /** * See header */ -tls_protection_t *tls_protection_create(tls_compression_t *compression) +tls_protection_t *tls_protection_create(tls_t *tls, + tls_compression_t *compression) { private_tls_protection_t *this; @@ -64,8 +226,10 @@ tls_protection_t *tls_protection_create(tls_compression_t *compression) .public = { .process = _process, .build = _build, + .set_cipher = _set_cipher, .destroy = _destroy, }, + .tls = tls, .compression = compression, ); diff --git a/src/charon/plugins/eap_tls/tls/tls_protection.h b/src/charon/plugins/eap_tls/tls/tls_protection.h index 98f432b93..fab913788 100644 --- a/src/charon/plugins/eap_tls/tls/tls_protection.h +++ b/src/charon/plugins/eap_tls/tls/tls_protection.h @@ -61,6 +61,17 @@ struct tls_protection_t { tls_content_type_t *type, chunk_t *data); /** + * Set a new cipher, including encryption and integrity algorithms. + * + * @param inbound TRUE to use cipher for inbound data, FALSE for outbound + * @param signer new signer to use, gets owned by protection layer + * @param crypter new crypter to use, gets owned by protection layer + * @param iv initial IV for crypter, gets owned by protection layer + */ + void (*set_cipher)(tls_protection_t *this, bool inbound, signer_t *signer, + crypter_t *crypter, chunk_t iv); + + /** * Destroy a tls_protection_t. */ void (*destroy)(tls_protection_t *this); @@ -69,9 +80,11 @@ struct tls_protection_t { /** * Create a tls_protection instance. * + * @param tls TLS context * @param compression compression layer of TLS stack * @return TLS protection layer. */ -tls_protection_t *tls_protection_create(tls_compression_t *compression); +tls_protection_t *tls_protection_create(tls_t *tls, + tls_compression_t *compression); #endif /** TLS_PROTECTION_H_ @}*/ |