diff options
author | Martin Willi <martin@revosec.ch> | 2010-08-03 15:17:40 +0200 |
---|---|---|
committer | Martin Willi <martin@revosec.ch> | 2010-08-03 15:39:26 +0200 |
commit | 0f82a47063f05d8eeae64866ff4787edc8db6328 (patch) | |
tree | 80d2e1fc7d530dc205314b7abafeb25fec48cc73 /src/libtls | |
parent | 0b71bc7af047f1a20bbad8a38d33b01452c35613 (diff) | |
download | strongswan-0f82a47063f05d8eeae64866ff4787edc8db6328.tar.bz2 strongswan-0f82a47063f05d8eeae64866ff4787edc8db6328.tar.xz |
Moved TLS stack to its own library
Diffstat (limited to 'src/libtls')
-rw-r--r-- | src/libtls/Makefile.am | 15 | ||||
-rw-r--r-- | src/libtls/tls.c | 213 | ||||
-rw-r--r-- | src/libtls/tls.h | 170 | ||||
-rw-r--r-- | src/libtls/tls_compression.c | 71 | ||||
-rw-r--r-- | src/libtls/tls_compression.h | 77 | ||||
-rw-r--r-- | src/libtls/tls_crypto.c | 691 | ||||
-rw-r--r-- | src/libtls/tls_crypto.h | 188 | ||||
-rw-r--r-- | src/libtls/tls_fragmentation.c | 256 | ||||
-rw-r--r-- | src/libtls/tls_fragmentation.h | 77 | ||||
-rw-r--r-- | src/libtls/tls_handshake.h | 82 | ||||
-rw-r--r-- | src/libtls/tls_peer.c | 625 | ||||
-rw-r--r-- | src/libtls/tls_peer.h | 48 | ||||
-rw-r--r-- | src/libtls/tls_prf.c | 190 | ||||
-rw-r--r-- | src/libtls/tls_prf.h | 72 | ||||
-rw-r--r-- | src/libtls/tls_protection.c | 311 | ||||
-rw-r--r-- | src/libtls/tls_protection.h | 90 | ||||
-rw-r--r-- | src/libtls/tls_reader.c | 200 | ||||
-rw-r--r-- | src/libtls/tls_reader.h | 131 | ||||
-rw-r--r-- | src/libtls/tls_server.c | 607 | ||||
-rw-r--r-- | src/libtls/tls_server.h | 48 | ||||
-rw-r--r-- | src/libtls/tls_writer.c | 237 | ||||
-rw-r--r-- | src/libtls/tls_writer.h | 136 |
22 files changed, 4535 insertions, 0 deletions
diff --git a/src/libtls/Makefile.am b/src/libtls/Makefile.am new file mode 100644 index 000000000..d61cd8477 --- /dev/null +++ b/src/libtls/Makefile.am @@ -0,0 +1,15 @@ + +INCLUDES = -I$(top_srcdir)/src/libstrongswan + +noinst_LTLIBRARIES = libtls.la +libtls_la_SOURCES = \ + tls_protection.h tls_protection.c \ + tls_compression.h tls_compression.c \ + tls_fragmentation.h tls_fragmentation.c \ + tls_crypto.h tls_crypto.c \ + tls_prf.h tls_prf.c \ + tls_reader.h tls_reader.c \ + tls_writer.h tls_writer.c \ + tls_peer.h tls_peer.c \ + tls_server.h tls_server.c \ + tls_handshake.h tls.h tls.c diff --git a/src/libtls/tls.c b/src/libtls/tls.c new file mode 100644 index 000000000..4384c0749 --- /dev/null +++ b/src/libtls/tls.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls.h" + +#include "tls_protection.h" +#include "tls_compression.h" +#include "tls_fragmentation.h" +#include "tls_crypto.h" +#include "tls_server.h" +#include "tls_peer.h" + +ENUM_BEGIN(tls_version_names, SSL_2_0, SSL_2_0, + "SSLv2"); +ENUM_NEXT(tls_version_names, SSL_3_0, TLS_1_2, SSL_2_0, + "SSLv3", + "TLS 1.0", + "TLS 1.1", + "TLS 1.2"); +ENUM_END(tls_version_names, TLS_1_2); + +ENUM(tls_content_type_names, TLS_CHANGE_CIPHER_SPEC, TLS_APPLICATION_DATA, + "ChangeCipherSpec", + "Alert", + "Handshake", + "ApplicationData", +); + +ENUM_BEGIN(tls_handshake_type_names, TLS_HELLO_REQUEST, TLS_SERVER_HELLO, + "HelloRequest", + "ClientHello", + "ServerHello"); +ENUM_NEXT(tls_handshake_type_names, TLS_CERTIFICATE, TLS_CLIENT_KEY_EXCHANGE, TLS_SERVER_HELLO, + "Certificate", + "ServerKeyExchange", + "CertificateRequest", + "ServerHelloDone", + "CertificateVerify", + "ClientKeyExchange"); +ENUM_NEXT(tls_handshake_type_names, TLS_FINISHED, TLS_FINISHED, TLS_CLIENT_KEY_EXCHANGE, + "Finished"); +ENUM_END(tls_handshake_type_names, TLS_FINISHED); + + +typedef struct private_tls_t private_tls_t; + +/** + * Private data of an tls_protection_t object. + */ +struct private_tls_t { + + /** + * Public tls_t interface. + */ + tls_t public; + + /** + * Role this TLS stack acts as. + */ + bool is_server; + + /** + * Server identity + */ + identification_t *server; + + /** + * Peer identity + */ + identification_t *peer; + + /** + * Negotiated TLS version + */ + tls_version_t version; + + /** + * TLS record protection layer + */ + tls_protection_t *protection; + + /** + * TLS record compression layer + */ + tls_compression_t *compression; + + /** + * TLS record fragmentation layer + */ + tls_fragmentation_t *fragmentation; + + /** + * TLS crypto helper context + */ + tls_crypto_t *crypto; + + /** + * TLS handshake protocol handler + */ + tls_handshake_t *handshake; +}; + +METHOD(tls_t, process, status_t, + private_tls_t *this, tls_content_type_t type, chunk_t data) +{ + return this->protection->process(this->protection, type, data); +} + +METHOD(tls_t, build, status_t, + private_tls_t *this, tls_content_type_t *type, chunk_t *data) +{ + 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) +{ + return this->version; +} + +METHOD(tls_t, set_version, void, + private_tls_t *this, tls_version_t version) +{ + this->version = version; +} + +METHOD(tls_t, is_complete, bool, + private_tls_t *this) +{ + return this->crypto->get_eap_msk(this->crypto).len != 0; +} + +METHOD(tls_t, get_eap_msk, chunk_t, + private_tls_t *this) +{ + return this->crypto->get_eap_msk(this->crypto); +} + +METHOD(tls_t, destroy, void, + private_tls_t *this) +{ + this->protection->destroy(this->protection); + this->compression->destroy(this->compression); + this->fragmentation->destroy(this->fragmentation); + this->crypto->destroy(this->crypto); + this->handshake->destroy(this->handshake); + this->peer->destroy(this->peer); + this->server->destroy(this->server); + + free(this); +} + +/** + * See header + */ +tls_t *tls_create(bool is_server, identification_t *server, + identification_t *peer) +{ + private_tls_t *this; + + INIT(this, + .public = { + .process = _process, + .build = _build, + .is_server = _is_server, + .get_version = _get_version, + .set_version = _set_version, + .is_complete = _is_complete, + .get_eap_msk = _get_eap_msk, + .destroy = _destroy, + }, + .is_server = is_server, + .version = TLS_1_2, + .server = server->clone(server), + .peer = peer->clone(peer), + ); + + this->crypto = tls_crypto_create(&this->public); + if (is_server) + { + this->handshake = &tls_server_create(&this->public, this->crypto, + this->server, this->peer)->handshake; + } + else + { + this->handshake = &tls_peer_create(&this->public, this->crypto, + this->peer, this->server)->handshake; + } + this->fragmentation = tls_fragmentation_create(this->handshake); + this->compression = tls_compression_create(this->fragmentation); + this->protection = tls_protection_create(&this->public, this->compression); + this->crypto->set_protection(this->crypto, this->protection); + + return &this->public; +} diff --git a/src/libtls/tls.h b/src/libtls/tls.h new file mode 100644 index 000000000..67ee74230 --- /dev/null +++ b/src/libtls/tls.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup libtls libtls + * + * @addtogroup libtls + * TLS implementation on top of libstrongswan + * + * @defgroup tls tls + * @{ @ingroup libtls + */ + +#ifndef TLS_H_ +#define TLS_H_ + +typedef enum tls_version_t tls_version_t; +typedef enum tls_content_type_t tls_content_type_t; +typedef enum tls_handshake_type_t tls_handshake_type_t; +typedef struct tls_t tls_t; + +#include <library.h> + +/** + * TLS/SSL version numbers + */ +enum tls_version_t { + SSL_2_0 = 0x0200, + SSL_3_0 = 0x0300, + TLS_1_0 = 0x0301, + TLS_1_1 = 0x0302, + TLS_1_2 = 0x0303, +}; + +/** + * Enum names for tls_version_t + */ +extern enum_name_t *tls_version_names; + +/** + * TLS higher level content type + */ +enum tls_content_type_t { + TLS_CHANGE_CIPHER_SPEC = 20, + TLS_ALERT = 21, + TLS_HANDSHAKE = 22, + TLS_APPLICATION_DATA = 23, +}; + +/** + * Enum names for tls_content_type_t + */ +extern enum_name_t *tls_content_type_names; + +/** + * TLS handshake subtype + */ +enum tls_handshake_type_t { + TLS_HELLO_REQUEST = 0, + TLS_CLIENT_HELLO = 1, + TLS_SERVER_HELLO = 2, + TLS_CERTIFICATE = 11, + TLS_SERVER_KEY_EXCHANGE = 12, + TLS_CERTIFICATE_REQUEST = 13, + TLS_SERVER_HELLO_DONE = 14, + TLS_CERTIFICATE_VERIFY = 15, + TLS_CLIENT_KEY_EXCHANGE = 16, + TLS_FINISHED = 20, +}; + +/** + * Enum names for tls_handshake_type_t + */ +extern enum_name_t *tls_handshake_type_names; + +/** + * A bottom-up driven TLS stack, suitable for EAP implementations. + */ +struct tls_t { + + /** + * Process a TLS record, pass it to upper layers. + * + * @param type type of the TLS record to process + * @param data associated TLS record data + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if more invocations to process/build needed + */ + status_t (*process)(tls_t *this, tls_content_type_t type, chunk_t data); + + /** + * Query upper layer for TLS record, build protected record. + * + * @param type type of the built TLS record + * @param data allocated data of the built TLS record + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if upper layers have more records to send + * - INVALID_STATE if more input records required + */ + 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 + */ + tls_version_t (*get_version)(tls_t *this); + + /** + * Set the negotiated TLS/SSL version. + * + * @param version negotiated TLS version + */ + void (*set_version)(tls_t *this, tls_version_t version); + + /** + * Check if TLS negotiation completed successfully. + * + * @return TRUE if TLS negotation and authentication complete + */ + bool (*is_complete)(tls_t *this); + + /** + * Get the MSK for EAP-TLS. + * + * @return MSK, internal data + */ + chunk_t (*get_eap_msk)(tls_t *this); + + /** + * Destroy a tls_t. + */ + void (*destroy)(tls_t *this); +}; + +/** + * Create a tls instance. + * + * @param is_server TRUE to act as server, FALSE for client + * @param server server identity + * @param peer peer identity + * @return TLS stack + */ +tls_t *tls_create(bool is_server, identification_t *server, + identification_t *peer); + +#endif /** TLS_H_ @}*/ diff --git a/src/libtls/tls_compression.c b/src/libtls/tls_compression.c new file mode 100644 index 000000000..02a3578e3 --- /dev/null +++ b/src/libtls/tls_compression.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_compression.h" + +typedef struct private_tls_compression_t private_tls_compression_t; + +/** + * Private data of an tls_compression_t object. + */ +struct private_tls_compression_t { + + /** + * Public tls_compression_t interface. + */ + tls_compression_t public; + + /** + * Upper layer, TLS record fragmentation + */ + tls_fragmentation_t *fragmentation; +}; + +METHOD(tls_compression_t, process, status_t, + private_tls_compression_t *this, tls_content_type_t type, chunk_t data) +{ + return this->fragmentation->process(this->fragmentation, type, data); +} + +METHOD(tls_compression_t, build, status_t, + private_tls_compression_t *this, tls_content_type_t *type, chunk_t *data) +{ + return this->fragmentation->build(this->fragmentation, type, data); +} + +METHOD(tls_compression_t, destroy, void, + private_tls_compression_t *this) +{ + free(this); +} + +/** + * See header + */ +tls_compression_t *tls_compression_create(tls_fragmentation_t *fragmentation) +{ + private_tls_compression_t *this; + + INIT(this, + .public = { + .process = _process, + .build = _build, + .destroy = _destroy, + }, + .fragmentation = fragmentation, + ); + + return &this->public; +} diff --git a/src/libtls/tls_compression.h b/src/libtls/tls_compression.h new file mode 100644 index 000000000..bd27ab5d7 --- /dev/null +++ b/src/libtls/tls_compression.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_compression tls_compression + * @{ @ingroup libtls + */ + +#ifndef TLS_COMPRESSION_H_ +#define TLS_COMPRESSION_H_ + +typedef struct tls_compression_t tls_compression_t; + +#include <library.h> + +#include "tls.h" +#include "tls_fragmentation.h" + +/** + * TLS record protocol compression layer. + */ +struct tls_compression_t { + + /** + * Process a compressed TLS record, pass it to upper layers. + * + * @param type type of the TLS record to process + * @param data associated TLS record data + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if more invocations to process/build needed + */ + status_t (*process)(tls_compression_t *this, + tls_content_type_t type, chunk_t data); + + /** + * Query upper layer for TLS record, build compressed record. + * + * @param type type of the built TLS record + * @param data allocated data of the built TLS record + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if upper layers have more records to send + * - INVALID_STATE if more input records required + */ + status_t (*build)(tls_compression_t *this, + tls_content_type_t *type, chunk_t *data); + + /** + * Destroy a tls_compression_t. + */ + void (*destroy)(tls_compression_t *this); +}; + +/** + * Create a tls_compression instance. + * + * @param fragmentation fragmentation layer of TLS stack + * @return TLS compression layer. + */ +tls_compression_t *tls_compression_create(tls_fragmentation_t *fragmentation); + +#endif /** TLS_COMPRESSION_H_ @}*/ diff --git a/src/libtls/tls_crypto.c b/src/libtls/tls_crypto.c new file mode 100644 index 000000000..085c11e2d --- /dev/null +++ b/src/libtls/tls_crypto.c @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_crypto.h" + +#include <debug.h> + +typedef struct private_tls_crypto_t private_tls_crypto_t; + +/** + * Private data of an tls_crypto_t object. + */ +struct private_tls_crypto_t { + + /** + * Public tls_crypto_t interface. + */ + tls_crypto_t public; + + /** + * Protection layer + */ + tls_protection_t *protection; + + /** + * List of supported/acceptable cipher suites + */ + tls_cipher_suite_t *suites; + + /** + * Number of supported suites + */ + int suite_count; + + /** + * Selected cipher suite + */ + tls_cipher_suite_t suite; + + /** + * TLS context + */ + tls_t *tls; + + /** + * All handshake data concatentated + */ + chunk_t handshake; + + /** + * 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; + + /** + * EAP-TLS MSK + */ + chunk_t msk; +}; + +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 + */ +static void build_cipher_suite_list(private_tls_crypto_t *this) +{ + encryption_algorithm_t encr; + integrity_algorithm_t mac; + enumerator_t *encrs, *macs; + tls_cipher_suite_t supported[64], unique[64]; + int count = 0, i, j; + + /* we assume that we support RSA, but no DHE yet */ + macs = lib->crypto->create_signer_enumerator(lib->crypto); + while (macs->enumerate(macs, &mac)) + { + switch (mac) + { + case AUTH_HMAC_SHA1_160: + supported[count++] = TLS_RSA_WITH_NULL_SHA; + break; + case AUTH_HMAC_SHA2_256_256: + supported[count++] = TLS_RSA_WITH_NULL_SHA256; + break; + case AUTH_HMAC_MD5_128: + supported[count++] = TLS_RSA_WITH_NULL_MD5; + break; + default: + break; + } + encrs = lib->crypto->create_crypter_enumerator(lib->crypto); + while (encrs->enumerate(encrs, &encr)) + { + switch (encr) + { + case ENCR_AES_CBC: + switch (mac) + { + case AUTH_HMAC_SHA1_160: + supported[count++] = TLS_RSA_WITH_AES_128_CBC_SHA; + supported[count++] = TLS_RSA_WITH_AES_256_CBC_SHA; + break; + case AUTH_HMAC_SHA2_256_256: + supported[count++] = TLS_RSA_WITH_AES_128_CBC_SHA256; + supported[count++] = TLS_RSA_WITH_AES_128_CBC_SHA256; + break; + default: + break; + } + break; + case ENCR_3DES: + switch (mac) + { + case AUTH_HMAC_SHA1_160: + supported[count++] = TLS_RSA_WITH_3DES_EDE_CBC_SHA; + break; + default: + break; + } + break; + default: + break; + } + } + encrs->destroy(encrs); + } + macs->destroy(macs); + + /* remove duplicates */ + this->suite_count = 0; + for (i = 0; i < count; i++) + { + bool match = FALSE; + + for (j = 0; j < this->suite_count; j++) + { + if (supported[i] == unique[j]) + { + match = TRUE; + break; + } + } + if (!match) + { + unique[this->suite_count++] = supported[i]; + } + } + free(this->suites); + this->suites = malloc(sizeof(tls_cipher_suite_t) * this->suite_count); + memcpy(this->suites, unique, sizeof(tls_cipher_suite_t) * this->suite_count); +} + +METHOD(tls_crypto_t, get_cipher_suites, int, + private_tls_crypto_t *this, tls_cipher_suite_t **suites) +{ + *suites = this->suites; + 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) +{ + int i, j; + + for (i = 0; i < this->suite_count; i++) + { + for (j = 0; j < count; j++) + { + if (this->suites[i] == suites[j]) + { + if (create_ciphers(this, this->suites[i])) + { + this->suite = this->suites[i]; + return this->suite; + } + } + } + } + return 0; +} + +METHOD(tls_crypto_t, set_protection, void, + private_tls_crypto_t *this, tls_protection_t *protection) +{ + this->protection = protection; +} + +METHOD(tls_crypto_t, append_handshake, void, + private_tls_crypto_t *this, tls_handshake_type_t type, chunk_t data) +{ + u_int32_t header; + + /* reconstruct handshake header */ + header = htonl(data.len | (type << 24)); + this->handshake = chunk_cat("mcc", this->handshake, + chunk_from_thing(header), data); +} + +/** + * Create a hash of the stored handshake data + */ +static bool hash_handshake(private_tls_crypto_t *this, chunk_t *hash) +{ + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + hasher_t *hasher; + suite_algs_t *alg; + + alg = find_suite(this->suite); + if (!alg) + { + return FALSE; + } + hasher = lib->crypto->create_hasher(lib->crypto, alg->hash); + if (!hasher) + { + DBG1(DBG_IKE, "%N not supported", hash_algorithm_names, alg->hash); + return FALSE; + } + hasher->allocate_hash(hasher, this->handshake, hash); + hasher->destroy(hasher); + } + else + { + hasher_t *md5, *sha1; + char buf[HASH_SIZE_MD5 + HASH_SIZE_SHA1]; + + md5 = lib->crypto->create_hasher(lib->crypto, HASH_MD5); + if (!md5) + { + DBG1(DBG_IKE, "%N not supported", hash_algorithm_names, HASH_MD5); + return FALSE; + } + md5->get_hash(md5, this->handshake, buf); + md5->destroy(md5); + sha1 = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!sha1) + { + DBG1(DBG_IKE, "%N not supported", hash_algorithm_names, HASH_SHA1); + return FALSE; + } + sha1->get_hash(sha1, this->handshake, buf + HASH_SIZE_MD5); + sha1->destroy(sha1); + + *hash = chunk_clone(chunk_from_thing(buf)); + } + return TRUE; +} + +METHOD(tls_crypto_t, sign_handshake, bool, + private_tls_crypto_t *this, private_key_t *key, tls_writer_t *writer) +{ + chunk_t sig, hash; + + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + /* TODO: use supported algorithms instead of fixed SHA1/RSA */ + if (!key->sign(key, SIGN_RSA_EMSA_PKCS1_SHA1, this->handshake, &sig)) + { + return FALSE; + } + writer->write_uint8(writer, 2); + writer->write_uint8(writer, 1); + writer->write_data16(writer, sig); + free(sig.ptr); + } + else + { + if (!hash_handshake(this, &hash)) + { + return FALSE; + } + if (!key->sign(key, SIGN_RSA_EMSA_PKCS1_NULL, hash, &sig)) + { + free(hash.ptr); + return FALSE; + } + writer->write_data16(writer, sig); + free(hash.ptr); + free(sig.ptr); + } + return TRUE; +} + +METHOD(tls_crypto_t, verify_handshake, bool, + private_tls_crypto_t *this, public_key_t *key, tls_reader_t *reader) +{ + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + u_int8_t hash, alg; + chunk_t sig; + + if (!reader->read_uint8(reader, &hash) || + !reader->read_uint8(reader, &alg) || + !reader->read_data16(reader, &sig)) + { + DBG1(DBG_IKE, "received invalid Certificate Verify"); + return FALSE; + } + /* TODO: map received hash/sig alg to signature scheme */ + if (hash != 2 || alg != 1 || + !key->verify(key, SIGN_RSA_EMSA_PKCS1_SHA1, this->handshake, sig)) + { + return FALSE; + } + } + else + { + chunk_t sig, hash; + + if (!reader->read_data16(reader, &sig)) + { + DBG1(DBG_IKE, "received invalid Certificate Verify"); + return FALSE; + } + if (!hash_handshake(this, &hash)) + { + return FALSE; + } + if (!key->verify(key, SIGN_RSA_EMSA_PKCS1_NULL, hash, sig)) + { + free(hash.ptr); + return FALSE; + } + free(hash.ptr); + } + return TRUE; +} + +METHOD(tls_crypto_t, calculate_finished, bool, + private_tls_crypto_t *this, char *label, char out[12]) +{ + chunk_t seed; + + if (!this->prf) + { + return FALSE; + } + if (!hash_handshake(this, &seed)) + { + return FALSE; + } + this->prf->get_bytes(this->prf, label, seed, 12, out); + free(seed.ptr); + return TRUE; +} + +METHOD(tls_crypto_t, derive_secrets, void, + private_tls_crypto_t *this, chunk_t premaster, + chunk_t client_random, chunk_t server_random) +{ + 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_1) + { + 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 + { + 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)) + { + 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); + } + } + } +} + +METHOD(tls_crypto_t, change_cipher, void, + private_tls_crypto_t *this, bool inbound) +{ + if (this->protection) + { + if (inbound) + { + this->protection->set_cipher(this->protection, TRUE, + this->signer_in, this->crypter_in, this->iv_in); + } + else + { + this->protection->set_cipher(this->protection, FALSE, + this->signer_out, this->crypter_out, this->iv_out); + } + } +} + +METHOD(tls_crypto_t, derive_eap_msk, void, + private_tls_crypto_t *this, chunk_t client_random, chunk_t server_random) +{ + chunk_t seed; + + seed = chunk_cata("cc", client_random, server_random); + free(this->msk.ptr); + this->msk = chunk_alloc(64); + this->prf->get_bytes(this->prf, "client EAP encryption", seed, + this->msk.len, this->msk.ptr); +} + +METHOD(tls_crypto_t, get_eap_msk, chunk_t, + private_tls_crypto_t *this) +{ + return this->msk; +} + +METHOD(tls_crypto_t, destroy, void, + private_tls_crypto_t *this) +{ + 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); + free(this->handshake.ptr); + free(this->msk.ptr); + DESTROY_IF(this->prf); + free(this->suites); + free(this); +} + +/** + * See header + */ +tls_crypto_t *tls_crypto_create(tls_t *tls) +{ + private_tls_crypto_t *this; + + INIT(this, + .public = { + .get_cipher_suites = _get_cipher_suites, + .select_cipher_suite = _select_cipher_suite, + .set_protection = _set_protection, + .append_handshake = _append_handshake, + .sign_handshake = _sign_handshake, + .verify_handshake = _verify_handshake, + .calculate_finished = _calculate_finished, + .derive_secrets = _derive_secrets, + .change_cipher = _change_cipher, + .derive_eap_msk = _derive_eap_msk, + .get_eap_msk = _get_eap_msk, + .destroy = _destroy, + }, + .tls = tls, + ); + + build_cipher_suite_list(this); + + return &this->public; +} diff --git a/src/libtls/tls_crypto.h b/src/libtls/tls_crypto.h new file mode 100644 index 000000000..0d2588aa6 --- /dev/null +++ b/src/libtls/tls_crypto.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_crypto tls_crypto + * @{ @ingroup libtls + */ + +#ifndef TLS_CRYPTO_H_ +#define TLS_CRYPTO_H_ + +typedef struct tls_crypto_t tls_crypto_t; +typedef enum tls_cipher_suite_t tls_cipher_suite_t; + +#include "tls.h" +#include "tls_prf.h" +#include "tls_protection.h" + +#include <credentials/keys/private_key.h> + +/** + * TLS cipher suites + */ +enum tls_cipher_suite_t { + TLS_NULL_WITH_NULL_NULL = 0x00, + TLS_RSA_WITH_NULL_MD5 = 0x01, + TLS_RSA_WITH_NULL_SHA = 0x02, + TLS_RSA_WITH_NULL_SHA256 = 0x3B, + TLS_RSA_WITH_RC4_128_MD5 = 0x04, + TLS_RSA_WITH_RC4_128_SHA = 0x05, + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x0A, + TLS_RSA_WITH_AES_128_CBC_SHA = 0x2F, + TLS_RSA_WITH_AES_256_CBC_SHA = 0x35, + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x3C, + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x3D, + TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x0D, + TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x10, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x13, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x16, + TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x30, + TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x31, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x32, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x33, + TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x36, + TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x37, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x38, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x39, + TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x3E, + TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x3F, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x40, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x67, + TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x68, + TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x69, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x6A, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x6B, + TLS_DH_ANON_WITH_RC4_128_MD5 = 0x18, + TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA = 0x1B, + TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x34, + TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x3A, + TLS_DH_ANON_WITH_AES_128_CBC_SHA256 = 0x6C, + TLS_DH_ANON_WITH_AES_256_CBC_SHA256 = 0x6D, +}; + +/** + * TLS crypto helper functions. + */ +struct tls_crypto_t { + + /** + * Get a list of supported TLS cipher suites. + * + * @param suites list of suites, points to internal data + * @return number of suites returned + */ + int (*get_cipher_suites)(tls_crypto_t *this, tls_cipher_suite_t **suites); + + /** + * Select and store a cipher suite from a given list of candidates. + * + * @param suites list of candidates to select from + * @param count number of suites + * @return selected suite, 0 if none acceptable + */ + tls_cipher_suite_t (*select_cipher_suite)(tls_crypto_t *this, + tls_cipher_suite_t *suites, int count); + + /** + * Set the protection layer of the TLS stack to control it. + * + * @param protection protection layer to work on + */ + void (*set_protection)(tls_crypto_t *this, tls_protection_t *protection); + + /** + * Store exchanged handshake data, used for cryptographic operations. + * + * @param type handshake sub type + * @param data data to append to handshake buffer + */ + void (*append_handshake)(tls_crypto_t *this, + tls_handshake_type_t type, chunk_t data); + + /** + * Create a signature of the handshake data using a given private key. + * + * @param key private key to use for signature + * @param writer TLS writer to write signature to + * @return TRUE if signature create successfully + */ + bool (*sign_handshake)(tls_crypto_t *this, private_key_t *key, + tls_writer_t *writer); + + /** + * Verify the signature over handshake data using a given public key. + * + * @param key public key to verify signature with + * @param reader TLS reader to read signature from + * @return TRUE if signature valid + */ + bool (*verify_handshake)(tls_crypto_t *this, public_key_t *key, + tls_reader_t *reader); + + /** + * Calculate the data of a TLS finished message. + * + * @param label ASCII label to use for calculation + * @param out buffer to write finished data to + * @return TRUE if calculation successful + */ + bool (*calculate_finished)(tls_crypto_t *this, char *label, char out[12]); + + /** + * Derive the master secret, MAC and encryption keys. + * + * @param premaster premaster secret + * @param client_random random data from client hello + * @param server_random random data from server hello + */ + void (*derive_secrets)(tls_crypto_t *this, chunk_t premaster, + 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); + + /** + * Derive the EAP-TLS MSK. + * + * @param client_random random data from client hello + * @param server_random random data from server hello + */ + void (*derive_eap_msk)(tls_crypto_t *this, + chunk_t client_random, chunk_t server_random); + + /** + * Get the MSK to use in EAP-TLS. + * + * @return MSK, points to internal data + */ + chunk_t (*get_eap_msk)(tls_crypto_t *this); + + /** + * Destroy a tls_crypto_t. + */ + void (*destroy)(tls_crypto_t *this); +}; + +/** + * Create a tls_crypto instance. + */ +tls_crypto_t *tls_crypto_create(tls_t *tls); + +#endif /** TLS_CRYPTO_H_ @}*/ diff --git a/src/libtls/tls_fragmentation.c b/src/libtls/tls_fragmentation.c new file mode 100644 index 000000000..a8d8aa289 --- /dev/null +++ b/src/libtls/tls_fragmentation.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_fragmentation.h" + +#include "tls_reader.h" + +#include <debug.h> + +typedef struct private_tls_fragmentation_t private_tls_fragmentation_t; + +/** + * Private data of an tls_fragmentation_t object. + */ +struct private_tls_fragmentation_t { + + /** + * Public tls_fragmentation_t interface. + */ + tls_fragmentation_t public; + + /** + * Upper layer handshake protocol + */ + tls_handshake_t *handshake; + + /** + * Handshake input buffer + */ + chunk_t input; + + /** + * Position in input buffer + */ + size_t inpos; + + /** + * Currently processed handshake message type + */ + tls_handshake_type_t type; + + /** + * Handshake output buffer + */ + chunk_t output; +}; + +/** + * Maximum size of a TLS fragment + */ +#define MAX_TLS_FRAGMENT_LEN 16384 + +/** + * Maximum size of a TLS handshake message we accept + */ +#define MAX_TLS_HANDSHAKE_LEN 65536 + +/** + * Process TLS handshake protocol data + */ +static status_t process_handshake(private_tls_fragmentation_t *this, + tls_reader_t *reader) +{ + while (reader->remaining(reader)) + { + tls_reader_t *msg; + u_int8_t type; + u_int32_t len; + status_t status; + chunk_t data; + + if (reader->remaining(reader) > MAX_TLS_FRAGMENT_LEN) + { + DBG1(DBG_IKE, "TLS fragment has invalid length"); + return FAILED; + } + + if (this->input.len == 0) + { /* new handshake message */ + if (!reader->read_uint8(reader, &type) || + !reader->read_uint24(reader, &len)) + { + return FAILED; + } + this->type = type; + if (len > MAX_TLS_HANDSHAKE_LEN) + { + DBG1(DBG_IKE, "TLS handshake message exceeds maximum length"); + return FAILED; + } + chunk_free(&this->input); + this->inpos = 0; + if (len) + { + this->input = chunk_alloc(len); + } + } + + len = min(this->input.len - this->inpos, reader->remaining(reader)); + if (!reader->read_data(reader, len, &data)) + { + return FAILED; + } + memcpy(this->input.ptr + this->inpos, data.ptr, len); + this->inpos += len; + + if (this->input.len == this->inpos) + { /* message completely defragmented, process */ + msg = tls_reader_create(this->input); + status = this->handshake->process(this->handshake, this->type, msg); + msg->destroy(msg); + chunk_free(&this->input); + if (status != NEED_MORE) + { + return status; + } + } + } + return NEED_MORE; +} + +METHOD(tls_fragmentation_t, process, status_t, + private_tls_fragmentation_t *this, tls_content_type_t type, chunk_t data) +{ + tls_reader_t *reader; + status_t status; + + reader = tls_reader_create(data); + switch (type) + { + case TLS_CHANGE_CIPHER_SPEC: + if (this->handshake->change_cipherspec(this->handshake)) + { + status = NEED_MORE; + break; + } + status = FAILED; + break; + case TLS_ALERT: + /* TODO: handle Alert */ + status = FAILED; + break; + case TLS_HANDSHAKE: + status = process_handshake(this, reader); + break; + case TLS_APPLICATION_DATA: + /* skip application data */ + status = NEED_MORE; + break; + default: + DBG1(DBG_IKE, "received unknown TLS content type %d, ignored", type); + status = NEED_MORE; + break; + } + reader->destroy(reader); + return status; +} + +METHOD(tls_fragmentation_t, build, status_t, + private_tls_fragmentation_t *this, tls_content_type_t *type, chunk_t *data) +{ + tls_handshake_type_t hs_type; + tls_writer_t *writer, *msg; + status_t status; + + if (this->handshake->cipherspec_changed(this->handshake)) + { + *type = TLS_CHANGE_CIPHER_SPEC; + *data = chunk_clone(chunk_from_chars(0x01)); + return NEED_MORE; + } + + if (!this->output.len) + { + msg = tls_writer_create(64); + do + { + writer = tls_writer_create(64); + status = this->handshake->build(this->handshake, &hs_type, writer); + switch (status) + { + case NEED_MORE: + msg->write_uint8(msg, hs_type); + msg->write_data24(msg, writer->get_buf(writer)); + break; + case INVALID_STATE: + this->output = chunk_clone(msg->get_buf(msg)); + break; + default: + break; + } + writer->destroy(writer); + } + while (status == NEED_MORE); + + msg->destroy(msg); + if (status != INVALID_STATE) + { + return status; + } + } + + if (this->output.len) + { + *type = TLS_HANDSHAKE; + if (this->output.len <= MAX_TLS_FRAGMENT_LEN) + { + *data = this->output; + this->output = chunk_empty; + return NEED_MORE; + } + *data = chunk_create(this->output.ptr, MAX_TLS_FRAGMENT_LEN); + this->output = chunk_clone(chunk_skip(this->output, MAX_TLS_FRAGMENT_LEN)); + return NEED_MORE; + } + return status; +} + +METHOD(tls_fragmentation_t, destroy, void, + private_tls_fragmentation_t *this) +{ + free(this->input.ptr); + free(this->output.ptr); + free(this); +} + +/** + * See header + */ +tls_fragmentation_t *tls_fragmentation_create(tls_handshake_t *handshake) +{ + private_tls_fragmentation_t *this; + + INIT(this, + .public = { + .process = _process, + .build = _build, + .destroy = _destroy, + }, + .handshake = handshake, + ); + + return &this->public; +} diff --git a/src/libtls/tls_fragmentation.h b/src/libtls/tls_fragmentation.h new file mode 100644 index 000000000..e141a334b --- /dev/null +++ b/src/libtls/tls_fragmentation.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_fragmentation tls_fragmentation + * @{ @ingroup libtls + */ + +#ifndef TLS_FRAGMENTATION_H_ +#define TLS_FRAGMENTATION_H_ + +typedef struct tls_fragmentation_t tls_fragmentation_t; + +#include <library.h> + +#include "tls.h" +#include "tls_handshake.h" + +/** + * TLS record protocol fragmentation layer. + */ +struct tls_fragmentation_t { + + /** + * Process a fragmented TLS record, pass it to upper layers. + * + * @param type type of the TLS record to process + * @param data associated TLS record data + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if more invocations to process/build needed + */ + status_t (*process)(tls_fragmentation_t *this, + tls_content_type_t type, chunk_t data); + + /** + * Query upper layer for TLS messages, build fragmented records. + * + * @param type type of the built TLS record + * @param data allocated data of the built TLS record + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if upper layers have more records to send + * - INVALID_STATE if more input records required + */ + status_t (*build)(tls_fragmentation_t *this, + tls_content_type_t *type, chunk_t *data); + + /** + * Destroy a tls_fragmentation_t. + */ + void (*destroy)(tls_fragmentation_t *this); +}; + +/** + * Create a tls_fragmentation instance. + * + * @param handshake upper layer handshake protocol + * @return TLS fragmentation layer. + */ +tls_fragmentation_t *tls_fragmentation_create(tls_handshake_t *handshake); + +#endif /** TLS_FRAGMENTATION_H_ @}*/ diff --git a/src/libtls/tls_handshake.h b/src/libtls/tls_handshake.h new file mode 100644 index 000000000..c0798625e --- /dev/null +++ b/src/libtls/tls_handshake.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_handshake tls_handshake + * @{ @ingroup libtls + */ + +#ifndef TLS_HANDSHAKE_H_ +#define TLS_HANDSHAKE_H_ + +typedef struct tls_handshake_t tls_handshake_t; + +#include "tls.h" +#include "tls_reader.h" +#include "tls_writer.h" + +/** + * TLS handshake state machine interface. + */ +struct tls_handshake_t { + + /** + * Process received TLS handshake message. + * + * @param type TLS handshake message type + * @param reader TLS data buffer + * @return + * - SUCCESS if handshake complete + * - FAILED if handshake failed + * - NEED_MORE if another invocation of process/build needed + */ + status_t (*process)(tls_handshake_t *this, + tls_handshake_type_t type, tls_reader_t *reader); + + /** + * Build TLS handshake messages to send out. + * + * @param type type of created handshake message + * @param writer TLS data buffer to write to + * @return + * - SUCCESS if handshake complete + * - FAILED if handshake failed + * - NEED_MORE if more messages ready for delivery + * - INVALID_STATE if more input to process() required + */ + status_t (*build)(tls_handshake_t *this, + tls_handshake_type_t *type, tls_writer_t *writer); + + /** + * Check if the cipher spec for outgoing messages has changed. + * + * @return TRUE if cipher spec changed + */ + bool (*cipherspec_changed)(tls_handshake_t *this); + + /** + * Change the cipher spec for incoming messages. + * + * @return TRUE if cipher spec changed + */ + bool (*change_cipherspec)(tls_handshake_t *this); + + /** + * Destroy a tls_handshake_t. + */ + void (*destroy)(tls_handshake_t *this); +}; + +#endif /** TLS_HANDSHAKE_H_ @}*/ diff --git a/src/libtls/tls_peer.c b/src/libtls/tls_peer.c new file mode 100644 index 000000000..c87002fc7 --- /dev/null +++ b/src/libtls/tls_peer.c @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_peer.h" + +#include <debug.h> + +#include <time.h> + +typedef struct private_tls_peer_t private_tls_peer_t; + +typedef enum { + STATE_INIT, + STATE_HELLO_SENT, + STATE_HELLO_RECEIVED, + STATE_HELLO_DONE, + STATE_CERT_SENT, + STATE_CERT_RECEIVED, + STATE_CERTREQ_RECEIVED, + STATE_KEY_EXCHANGE_SENT, + STATE_VERIFY_SENT, + STATE_CIPHERSPEC_CHANGED_OUT, + STATE_FINISHED_SENT, + STATE_CIPHERSPEC_CHANGED_IN, + STATE_COMPLETE, +} peer_state_t; + +/** + * Private data of an tls_peer_t object. + */ +struct private_tls_peer_t { + + /** + * Public tls_peer_t interface. + */ + tls_peer_t public; + + /** + * TLS stack + */ + tls_t *tls; + + /** + * TLS crypto context + */ + tls_crypto_t *crypto; + + /** + * Peer identity + */ + identification_t *peer; + + /** + * Server identity + */ + identification_t *server; + + /** + * State we are in + */ + peer_state_t state; + + /** + * Hello random data selected by client + */ + char client_random[32]; + + /** + * Hello random data selected by server + */ + char server_random[32]; + + /** + * Auth helper for peer authentication + */ + auth_cfg_t *peer_auth; + + /** + * Auth helper for server authentication + */ + auth_cfg_t *server_auth; + + /** + * Peer private key + */ + private_key_t *private; +}; + +/** + * Process a server hello message + */ +static status_t process_server_hello(private_tls_peer_t *this, + tls_reader_t *reader) +{ + u_int8_t compression; + u_int16_t version, cipher; + chunk_t random, session, ext = chunk_empty; + tls_cipher_suite_t suite; + + this->crypto->append_handshake(this->crypto, + TLS_SERVER_HELLO, reader->peek(reader)); + + if (!reader->read_uint16(reader, &version) || + !reader->read_data(reader, sizeof(this->server_random), &random) || + !reader->read_data8(reader, &session) || + !reader->read_uint16(reader, &cipher) || + !reader->read_uint8(reader, &compression) || + (reader->remaining(reader) && !reader->read_data16(reader, &ext))) + { + DBG1(DBG_IKE, "received invalid ServerHello"); + return FAILED; + } + + memcpy(this->server_random, random.ptr, sizeof(this->server_random)); + + if (version < this->tls->get_version(this->tls)) + { + this->tls->set_version(this->tls, version); + } + suite = cipher; + if (!this->crypto->select_cipher_suite(this->crypto, &suite, 1)) + { + DBG1(DBG_IKE, "received cipher suite inacceptable"); + return FAILED; + } + this->state = STATE_HELLO_RECEIVED; + return NEED_MORE; +} + +/** + * Process a Certificate message + */ +static status_t process_certificate(private_tls_peer_t *this, + tls_reader_t *reader) +{ + certificate_t *cert; + tls_reader_t *certs; + chunk_t data; + bool first = TRUE; + + this->crypto->append_handshake(this->crypto, + TLS_CERTIFICATE, reader->peek(reader)); + + if (!reader->read_data24(reader, &data)) + { + return FAILED; + } + certs = tls_reader_create(data); + while (certs->remaining(certs)) + { + if (!certs->read_data24(certs, &data)) + { + certs->destroy(certs); + return FAILED; + } + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, data, BUILD_END); + if (cert) + { + if (first) + { + this->server_auth->add(this->server_auth, + AUTH_HELPER_SUBJECT_CERT, cert); + DBG1(DBG_IKE, "received TLS server certificate '%Y'", + cert->get_subject(cert)); + first = FALSE; + } + else + { + DBG1(DBG_IKE, "received TLS intermediate certificate '%Y'", + cert->get_subject(cert)); + this->server_auth->add(this->server_auth, + AUTH_HELPER_IM_CERT, cert); + } + } + else + { + DBG1(DBG_IKE, "parsing TLS certificate failed, skipped"); + } + } + certs->destroy(certs); + this->state = STATE_CERT_RECEIVED; + return NEED_MORE; +} + +/** + * Process a Certificate message + */ +static status_t process_certreq(private_tls_peer_t *this, tls_reader_t *reader) +{ + chunk_t types, hashsig, data; + tls_reader_t *authorities; + identification_t *id; + certificate_t *cert; + + this->crypto->append_handshake(this->crypto, + TLS_CERTIFICATE_REQUEST, reader->peek(reader)); + + if (!reader->read_data8(reader, &types)) + { + return FAILED; + } + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + if (!reader->read_data16(reader, &hashsig)) + { + return FAILED; + } + /* TODO: store supported hashsig algorithms */ + } + if (!reader->read_data16(reader, &data)) + { + return FAILED; + } + authorities = tls_reader_create(data); + while (authorities->remaining(authorities)) + { + if (!authorities->read_data16(authorities, &data)) + { + authorities->destroy(authorities); + return FAILED; + } + id = identification_create_from_encoding(ID_DER_ASN1_DN, data); + cert = lib->credmgr->get_cert(lib->credmgr, + CERT_X509, KEY_ANY, id, TRUE); + if (cert) + { + DBG1(DBG_IKE, "received cert request for '%Y", id); + this->peer_auth->add(this->peer_auth, AUTH_RULE_CA_CERT, cert); + } + else + { + DBG1(DBG_IKE, "received cert request for unknown CA '%Y'", id); + } + id->destroy(id); + } + authorities->destroy(authorities); + this->state = STATE_CERTREQ_RECEIVED; + return NEED_MORE; +} + +/** + * Process Hello Done message + */ +static status_t process_hello_done(private_tls_peer_t *this, + tls_reader_t *reader) +{ + this->crypto->append_handshake(this->crypto, + TLS_SERVER_HELLO_DONE, reader->peek(reader)); + this->state = STATE_HELLO_DONE; + return NEED_MORE; +} + +/** + * Process finished message + */ +static status_t process_finished(private_tls_peer_t *this, tls_reader_t *reader) +{ + chunk_t received; + char buf[12]; + + if (!reader->read_data(reader, sizeof(buf), &received)) + { + DBG1(DBG_IKE, "received server finished too short"); + return FAILED; + } + if (!this->crypto->calculate_finished(this->crypto, "server finished", buf)) + { + DBG1(DBG_IKE, "calculating server finished failed"); + return FAILED; + } + if (!chunk_equals(received, chunk_from_thing(buf))) + { + DBG1(DBG_IKE, "received server finished invalid"); + return FAILED; + } + this->state = STATE_COMPLETE; + this->crypto->derive_eap_msk(this->crypto, + chunk_from_thing(this->client_random), + chunk_from_thing(this->server_random)); + return NEED_MORE; +} + +METHOD(tls_handshake_t, process, status_t, + private_tls_peer_t *this, tls_handshake_type_t type, tls_reader_t *reader) +{ + tls_handshake_type_t expected; + + switch (this->state) + { + case STATE_HELLO_SENT: + if (type == TLS_SERVER_HELLO) + { + return process_server_hello(this, reader); + } + expected = TLS_SERVER_HELLO; + break; + case STATE_HELLO_RECEIVED: + if (type == TLS_CERTIFICATE) + { + return process_certificate(this, reader); + } + expected = TLS_CERTIFICATE; + break; + case STATE_CERT_RECEIVED: + if (type == TLS_CERTIFICATE_REQUEST) + { + return process_certreq(this, reader); + } + expected = TLS_CERTIFICATE_REQUEST; + break; + case STATE_CERTREQ_RECEIVED: + if (type == TLS_SERVER_HELLO_DONE) + { + return process_hello_done(this, reader); + } + expected = TLS_SERVER_HELLO_DONE; + break; + case STATE_CIPHERSPEC_CHANGED_IN: + if (type == TLS_FINISHED) + { + return process_finished(this, reader); + } + expected = TLS_FINISHED; + break; + default: + DBG1(DBG_IKE, "TLS %N not expected in current state", + tls_handshake_type_names, type); + return FAILED; + } + DBG1(DBG_IKE, "TLS %N expected, but received %N", + tls_handshake_type_names, expected, tls_handshake_type_names, type); + return FAILED; +} + +/** + * Send a client hello + */ +static status_t send_client_hello(private_tls_peer_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + tls_cipher_suite_t *suite; + int count, i; + rng_t *rng; + + htoun32(&this->client_random, time(NULL)); + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + return FAILED; + } + rng->get_bytes(rng, sizeof(this->client_random) - 4, this->client_random + 4); + rng->destroy(rng); + + writer->write_uint16(writer, this->tls->get_version(this->tls)); + writer->write_data(writer, chunk_from_thing(this->client_random)); + /* session identifier => none */ + writer->write_data8(writer, chunk_empty); + + count = this->crypto->get_cipher_suites(this->crypto, &suite); + writer->write_uint16(writer, count * 2); + for (i = 0; i < count; i++) + { + writer->write_uint16(writer, suite[i]); + } + /* NULL compression only */ + writer->write_uint8(writer, 1); + writer->write_uint8(writer, 0); + + *type = TLS_CLIENT_HELLO; + this->state = STATE_HELLO_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send Certificate + */ +static status_t send_certificate(private_tls_peer_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + enumerator_t *enumerator; + certificate_t *cert; + auth_rule_t rule; + tls_writer_t *certs; + chunk_t data; + + this->private = lib->credmgr->get_private(lib->credmgr, + KEY_ANY, this->peer, this->peer_auth); + if (!this->private) + { + DBG1(DBG_IKE, "no TLS peer certificate found for '%Y'", this->peer); + return FAILED; + } + + /* generate certificate payload */ + certs = tls_writer_create(256); + cert = this->peer_auth->get(this->peer_auth, AUTH_RULE_SUBJECT_CERT); + if (cert) + { + if (cert->get_encoding(cert, CERT_ASN1_DER, &data)) + { + DBG1(DBG_IKE, "sending TLS peer certificate '%Y'", + cert->get_subject(cert)); + certs->write_data24(certs, data); + free(data.ptr); + } + } + enumerator = this->peer_auth->create_enumerator(this->peer_auth); + while (enumerator->enumerate(enumerator, &rule, &cert)) + { + if (rule == AUTH_RULE_IM_CERT) + { + if (cert->get_encoding(cert, CERT_ASN1_DER, &data)) + { + DBG1(DBG_IKE, "sending TLS intermediate certificate '%Y'", + cert->get_subject(cert)); + certs->write_data24(certs, data); + free(data.ptr); + } + } + } + enumerator->destroy(enumerator); + + writer->write_data24(writer, certs->get_buf(certs)); + certs->destroy(certs); + + *type = TLS_CERTIFICATE; + this->state = STATE_CERT_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send client key exchange + */ +static status_t send_key_exchange(private_tls_peer_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + public_key_t *public = NULL, *current; + enumerator_t *enumerator; + auth_cfg_t *auth; + rng_t *rng; + char premaster[48]; + chunk_t encrypted; + + rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG); + if (!rng) + { + DBG1(DBG_IKE, "no suitable RNG found for TLS premaster secret"); + return FAILED; + } + rng->get_bytes(rng, sizeof(premaster) - 2, premaster + 2); + rng->destroy(rng); + htoun16(premaster, TLS_1_2); + + this->crypto->derive_secrets(this->crypto, chunk_from_thing(premaster), + chunk_from_thing(this->client_random), + chunk_from_thing(this->server_random)); + + enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, + KEY_ANY, this->server, this->server_auth); + while (enumerator->enumerate(enumerator, ¤t, &auth)) + { + public = current->get_ref(current); + break; + } + enumerator->destroy(enumerator); + + if (!public) + { + DBG1(DBG_IKE, "no TLS public key found for server '%Y'", this->server); + return FAILED; + } + if (!public->encrypt(public, chunk_from_thing(premaster), &encrypted)) + { + public->destroy(public); + DBG1(DBG_IKE, "encrypting TLS premaster secret failed"); + return FAILED; + } + + public->destroy(public); + + writer->write_data16(writer, encrypted); + free(encrypted.ptr); + + *type = TLS_CLIENT_KEY_EXCHANGE; + this->state = STATE_KEY_EXCHANGE_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send certificate verify + */ +static status_t send_certificate_verify(private_tls_peer_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + if (!this->private || + !this->crypto->sign_handshake(this->crypto, this->private, writer)) + { + DBG1(DBG_IKE, "creating TLS Certificate Verify signature failed"); + return FAILED; + } + + *type = TLS_CERTIFICATE_VERIFY; + this->state = STATE_VERIFY_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send Finished + */ +static status_t send_finished(private_tls_peer_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + char buf[12]; + + if (!this->crypto->calculate_finished(this->crypto, "client finished", buf)) + { + DBG1(DBG_IKE, "calculating client finished data failed"); + return FAILED; + } + + writer->write_data(writer, chunk_from_thing(buf)); + + *type = TLS_FINISHED; + this->state = STATE_FINISHED_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +METHOD(tls_handshake_t, build, status_t, + private_tls_peer_t *this, tls_handshake_type_t *type, tls_writer_t *writer) +{ + switch (this->state) + { + case STATE_INIT: + return send_client_hello(this, type, writer); + case STATE_HELLO_DONE: + return send_certificate(this, type, writer); + case STATE_CERT_SENT: + return send_key_exchange(this, type, writer); + case STATE_KEY_EXCHANGE_SENT: + return send_certificate_verify(this, type, writer); + case STATE_CIPHERSPEC_CHANGED_OUT: + return send_finished(this, type, writer); + default: + return INVALID_STATE; + } +} + +METHOD(tls_handshake_t, cipherspec_changed, bool, + private_tls_peer_t *this) +{ + if (this->state == STATE_VERIFY_SENT) + { + this->crypto->change_cipher(this->crypto, FALSE); + this->state = STATE_CIPHERSPEC_CHANGED_OUT; + return TRUE; + } + return FALSE; +} + +METHOD(tls_handshake_t, change_cipherspec, bool, + private_tls_peer_t *this) +{ + if (this->state == STATE_FINISHED_SENT) + { + this->crypto->change_cipher(this->crypto, TRUE); + this->state = STATE_CIPHERSPEC_CHANGED_IN; + return TRUE; + } + return FALSE; +} + +METHOD(tls_handshake_t, destroy, void, + private_tls_peer_t *this) +{ + DESTROY_IF(this->private); + this->peer_auth->destroy(this->peer_auth); + this->server_auth->destroy(this->server_auth); + free(this); +} + +/** + * See header + */ +tls_peer_t *tls_peer_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *peer, identification_t *server) +{ + private_tls_peer_t *this; + + INIT(this, + .public.handshake = { + .process = _process, + .build = _build, + .cipherspec_changed = _cipherspec_changed, + .change_cipherspec = _change_cipherspec, + .destroy = _destroy, + }, + .state = STATE_INIT, + .tls = tls, + .crypto = crypto, + .peer = peer, + .server = server, + .peer_auth = auth_cfg_create(), + .server_auth = auth_cfg_create(), + ); + + return &this->public; +} diff --git a/src/libtls/tls_peer.h b/src/libtls/tls_peer.h new file mode 100644 index 000000000..eb97c97e4 --- /dev/null +++ b/src/libtls/tls_peer.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_peer tls_peer + * @{ @ingroup libtls + */ + +#ifndef TLS_PEER_H_ +#define TLS_PEER_H_ + +typedef struct tls_peer_t tls_peer_t; + +#include "tls_handshake.h" +#include "tls_crypto.h" + +#include <library.h> + +/** + * TLS handshake protocol handler as peer. + */ +struct tls_peer_t { + + /** + * Implements the TLS handshake protocol handler. + */ + tls_handshake_t handshake; +}; + +/** + * Create a tls_peer instance. + */ +tls_peer_t *tls_peer_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *peer, identification_t *server); + +#endif /** TLS_PEER_H_ @}*/ diff --git a/src/libtls/tls_prf.c b/src/libtls/tls_prf.c new file mode 100644 index 000000000..f181d01d3 --- /dev/null +++ b/src/libtls/tls_prf.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_prf.h" + +typedef struct private_tls_prf12_t private_tls_prf12_t; + +/** + * Private data of an tls_prf_t object. + */ +struct private_tls_prf12_t { + + /** + * Public tls_prf_t interface. + */ + tls_prf_t public; + + /** + * Underlying primitive PRF + */ + prf_t *prf; +}; + +METHOD(tls_prf_t, set_key12, void, + private_tls_prf12_t *this, chunk_t key) +{ + this->prf->set_key(this->prf, key); +} + +/** + * The P_hash function as in TLS 1.0/1.2 + */ +static void p_hash(prf_t *prf, char *label, chunk_t seed, size_t block_size, + size_t bytes, char *out) +{ + char buf[block_size], abuf[block_size]; + chunk_t a; + + /* seed = label + seed */ + seed = chunk_cata("cc", chunk_create(label, strlen(label)), seed); + /* A(0) = seed */ + a = seed; + + while (TRUE) + { + /* A(i) = HMAC_hash(secret, A(i-1)) */ + prf->get_bytes(prf, a, abuf); + a = chunk_from_thing(abuf); + /* HMAC_hash(secret, A(i) + seed) */ + prf->get_bytes(prf, a, NULL); + prf->get_bytes(prf, seed, buf); + + if (bytes <= block_size) + { + memcpy(out, buf, bytes); + break; + } + memcpy(out, buf, block_size); + out += block_size; + bytes -= block_size; + } +} + +METHOD(tls_prf_t, get_bytes12, void, + private_tls_prf12_t *this, char *label, chunk_t seed, + size_t bytes, char *out) +{ + p_hash(this->prf, label, seed, this->prf->get_block_size(this->prf), + bytes, out); +} + +METHOD(tls_prf_t, destroy12, void, + private_tls_prf12_t *this) +{ + this->prf->destroy(this->prf); + free(this); +} + +/** + * See header + */ +tls_prf_t *tls_prf_create_12(pseudo_random_function_t prf) +{ + private_tls_prf12_t *this; + + INIT(this, + .public = { + .set_key = _set_key12, + .get_bytes = _get_bytes12, + .destroy = _destroy12, + }, + .prf = lib->crypto->create_prf(lib->crypto, prf), + ); + if (!this->prf) + { + free(this); + return NULL; + } + return &this->public; +} + + +typedef struct private_tls_prf10_t private_tls_prf10_t; + +/** + * Private data of an tls_prf_t object. + */ +struct private_tls_prf10_t { + + /** + * Public tls_prf_t interface. + */ + tls_prf_t public; + + /** + * Underlying MD5 PRF + */ + prf_t *md5; + + /** + * Underlying SHA1 PRF + */ + prf_t *sha1; +}; + +METHOD(tls_prf_t, set_key10, void, + private_tls_prf10_t *this, chunk_t key) +{ + size_t len = key.len / 2 + key.len % 2; + + this->md5->set_key(this->md5, chunk_create(key.ptr, len)); + this->sha1->set_key(this->sha1, chunk_create(key.ptr + key.len - len, len)); +} + +METHOD(tls_prf_t, get_bytes10, void, + private_tls_prf10_t *this, char *label, chunk_t seed, + size_t bytes, char *out) +{ + char buf[bytes]; + + p_hash(this->md5, label, seed, this->md5->get_block_size(this->md5), + bytes, out); + p_hash(this->sha1, label, seed, this->sha1->get_block_size(this->sha1), + bytes, buf); + memxor(out, buf, bytes); +} + +METHOD(tls_prf_t, destroy10, void, + private_tls_prf10_t *this) +{ + DESTROY_IF(this->md5); + DESTROY_IF(this->sha1); + free(this); +} + +/** + * See header + */ +tls_prf_t *tls_prf_create_10(pseudo_random_function_t prf) +{ + private_tls_prf10_t *this; + + INIT(this, + .public = { + .set_key = _set_key10, + .get_bytes = _get_bytes10, + .destroy = _destroy10, + }, + .md5 = lib->crypto->create_prf(lib->crypto, PRF_HMAC_MD5), + .sha1 = lib->crypto->create_prf(lib->crypto, PRF_HMAC_SHA1), + ); + if (!this->md5 || !this->sha1) + { + destroy10(this); + return NULL; + } + return &this->public; +} diff --git a/src/libtls/tls_prf.h b/src/libtls/tls_prf.h new file mode 100644 index 000000000..811ce2d05 --- /dev/null +++ b/src/libtls/tls_prf.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_prf tls_prf + * @{ @ingroup libtls + */ + +#ifndef TLS_PRF_H_ +#define TLS_PRF_H_ + +typedef struct tls_prf_t tls_prf_t; + +#include <crypto/prfs/prf.h> + +/** + * The PRF function specified on TLS, based on HMAC. + */ +struct tls_prf_t { + + /** + * Set the key of the PRF function. + * + * @param key key to set + */ + void (*set_key)(tls_prf_t *this, chunk_t key); + + /** + * Generate a series of bytes using a label and a seed. + * + * @param label ASCII input label + * @param seed seed input value + * @param bytes number of bytes to get + * @param out buffer receiving bytes + */ + void (*get_bytes)(tls_prf_t *this, char *label, chunk_t seed, + size_t bytes, char *out); + + /** + * Destroy a tls_prf_t. + */ + void (*destroy)(tls_prf_t *this); +}; + +/** + * Create a tls_prf instance with specific algorithm as in TLS 1.2. + * + * @param hash underlying PRF function to use + * @return TLS PRF algorithm + */ +tls_prf_t *tls_prf_create_12(pseudo_random_function_t prf); + +/** + * Create a tls_prf instance with XOred SHA1/MD5 as in TLS 1.0/1.1. + * + * @return TLS PRF algorithm + */ +tls_prf_t *tls_prf_create_10(); + +#endif /** TLS_PRF_H_ @}*/ diff --git a/src/libtls/tls_protection.c b/src/libtls/tls_protection.c new file mode 100644 index 000000000..5ea0a8312 --- /dev/null +++ b/src/libtls/tls_protection.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_protection.h" + +#include <debug.h> + +typedef struct private_tls_protection_t private_tls_protection_t; + +/** + * Private data of an tls_protection_t object. + */ +struct private_tls_protection_t { + + /** + * Public tls_protection_t interface. + */ + 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) +{ + if (this->crypter_in) + { + chunk_t iv, next_iv = chunk_empty; + u_int8_t bs, padding_length; + + bs = this->crypter_in->get_block_size(this->crypter_in); + if (data.len < bs || data.len % bs) + { + DBG1(DBG_IKE, "encrypted TLS record not multiple of block size"); + return FAILED; + } + if (this->iv_in.len) + { /* < TLSv1.1 uses IV from key derivation/last block */ + iv = this->iv_in; + next_iv = chunk_clone(chunk_create(data.ptr + data.len - bs, bs)); + } + else + { /* TLSv1.1 uses random IVs, prepended to record */ + iv = chunk_create(data.ptr, bs); + data = chunk_skip(data, bs); + if (data.len < bs) + { + DBG1(DBG_IKE, "TLS record too short to decrypt"); + return FAILED; + } + } + this->crypter_in->decrypt(this->crypter_in, data, iv, NULL); + + if (next_iv.len) + { /* next record IV is last ciphertext block of this record */ + memcpy(this->iv_in.ptr, next_iv.ptr, next_iv.len); + free(next_iv.ptr); + } + + padding_length = data.ptr[data.len - 1]; + if (padding_length >= data.len) + { + DBG1(DBG_IKE, "invalid TLS record padding"); + return FAILED; + } + data.len -= padding_length + 1; + } + if (this->signer_in) + { + chunk_t mac, macdata, header; + u_int8_t bs; + + bs = this->signer_in->get_block_size(this->signer_in); + if (data.len <= bs) + { + DBG1(DBG_IKE, "TLS record too short to verify MAC"); + return FAILED; + } + mac = chunk_skip(data, data.len - bs); + data.len -= bs; + + header = sigheader(this->seq_in, type, + this->tls->get_version(this->tls), data.len); + macdata = chunk_cat("mc", header, data); + if (!this->signer_in->verify_signature(this->signer_in, macdata, mac)) + { + DBG1(DBG_IKE, "TLS record MAC verification failed"); + free(macdata.ptr); + return FAILED; + } + free(macdata.ptr); + } + + if (type == TLS_CHANGE_CIPHER_SPEC) + { + this->seq_in = 0; + } + else + { + 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) +{ + 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.1 uses IV from key derivation/last block */ + iv = this->iv_out; + } + else + { /* TLSv1.1 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, iv, 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_t *tls, + tls_compression_t *compression) +{ + private_tls_protection_t *this; + + INIT(this, + .public = { + .process = _process, + .build = _build, + .set_cipher = _set_cipher, + .destroy = _destroy, + }, + .tls = tls, + .compression = compression, + ); + + return &this->public; +} diff --git a/src/libtls/tls_protection.h b/src/libtls/tls_protection.h new file mode 100644 index 000000000..aa7681bd5 --- /dev/null +++ b/src/libtls/tls_protection.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_protection tls_protection + * @{ @ingroup libtls + */ + +#ifndef TLS_PROTECTION_H_ +#define TLS_PROTECTION_H_ + +typedef struct tls_protection_t tls_protection_t; + +#include <library.h> + +#include "tls.h" +#include "tls_compression.h" + +/** + * TLS record protocol protection layer. + */ +struct tls_protection_t { + + /** + * Process a protected TLS record, pass it to upper layers. + * + * @param type type of the TLS record to process + * @param data associated TLS record data + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if more invocations to process/build needed + */ + status_t (*process)(tls_protection_t *this, + tls_content_type_t type, chunk_t data); + + /** + * Query upper layer for TLS record, build protected record. + * + * @param type type of the built TLS record + * @param data allocated data of the built TLS record + * @return + * - SUCCESS if TLS negotiation complete + * - FAILED if TLS handshake failed + * - NEED_MORE if upper layers have more records to send + * - INVALID_STATE if more input records required + */ + status_t (*build)(tls_protection_t *this, + 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); +}; + +/** + * 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_t *tls, + tls_compression_t *compression); + +#endif /** TLS_PROTECTION_H_ @}*/ diff --git a/src/libtls/tls_reader.c b/src/libtls/tls_reader.c new file mode 100644 index 000000000..ee537be71 --- /dev/null +++ b/src/libtls/tls_reader.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_reader.h" + +#include <debug.h> + +typedef struct private_tls_reader_t private_tls_reader_t; + +/** + * Private data of an tls_reader_t object. + */ +struct private_tls_reader_t { + + /** + * Public tls_reader_t interface. + */ + tls_reader_t public; + + /** + * Remaining data to process + */ + chunk_t buf; +}; + +METHOD(tls_reader_t, remaining, u_int32_t, + private_tls_reader_t *this) +{ + return this->buf.len; +} + +METHOD(tls_reader_t, peek, chunk_t, + private_tls_reader_t *this) +{ + return this->buf; +} + +METHOD(tls_reader_t, read_uint8, bool, + private_tls_reader_t *this, u_int8_t *res) +{ + if (this->buf.len < 1) + { + DBG1(DBG_IKE, "%d bytes insufficient to parse uint%d TLS data", + this->buf.len, 8); + return FALSE; + } + *res = this->buf.ptr[0]; + this->buf = chunk_skip(this->buf, 1); + return TRUE; +} + +METHOD(tls_reader_t, read_uint16, bool, + private_tls_reader_t *this, u_int16_t *res) +{ + if (this->buf.len < 2) + { + DBG1(DBG_IKE, "%d bytes insufficient to parse uint%d TLS data", + this->buf.len, 16); + return FALSE; + } + *res = untoh16(this->buf.ptr); + this->buf = chunk_skip(this->buf, 2); + return TRUE; +} + +METHOD(tls_reader_t, read_uint24, bool, + private_tls_reader_t *this, u_int32_t *res) +{ + if (this->buf.len < 3) + { + DBG1(DBG_IKE, "%d bytes insufficient to parse uint%d TLS data", + this->buf.len, 24); + return FALSE; + } + *res = untoh32(this->buf.ptr) >> 8; + this->buf = chunk_skip(this->buf, 3); + return TRUE; +} + +METHOD(tls_reader_t, read_uint32, bool, + private_tls_reader_t *this, u_int32_t *res) +{ + if (this->buf.len < 4) + { + DBG1(DBG_IKE, "%d bytes insufficient to parse uint%d TLS data", + this->buf.len, 32); + return FALSE; + } + *res = untoh32(this->buf.ptr); + this->buf = chunk_skip(this->buf, 4); + return TRUE; +} + +METHOD(tls_reader_t, read_data, bool, + private_tls_reader_t *this, u_int32_t len, chunk_t *res) +{ + if (this->buf.len < len) + { + DBG1(DBG_IKE, "%d bytes insufficient to parse %d bytes TLS data", + this->buf.len, len); + return FALSE; + } + *res = chunk_create(this->buf.ptr, len); + this->buf = chunk_skip(this->buf, len); + return TRUE; +} + +METHOD(tls_reader_t, read_data8, bool, + private_tls_reader_t *this, chunk_t *res) +{ + u_int8_t len; + + if (!read_uint8(this, &len)) + { + return FALSE; + } + return read_data(this, len, res); +} + +METHOD(tls_reader_t, read_data16, bool, + private_tls_reader_t *this, chunk_t *res) +{ + u_int16_t len; + + if (!read_uint16(this, &len)) + { + return FALSE; + } + return read_data(this, len, res); +} + +METHOD(tls_reader_t, read_data24, bool, + private_tls_reader_t *this, chunk_t *res) +{ + u_int32_t len; + + if (!read_uint24(this, &len)) + { + return FALSE; + } + return read_data(this, len, res); +} + +METHOD(tls_reader_t, read_data32, bool, + private_tls_reader_t *this, chunk_t *res) +{ + u_int32_t len; + + if (!read_uint32(this, &len)) + { + return FALSE; + } + return read_data(this, len, res); +} + +METHOD(tls_reader_t, destroy, void, + private_tls_reader_t *this) +{ + free(this); +} + +/** + * See header + */ +tls_reader_t *tls_reader_create(chunk_t data) +{ + private_tls_reader_t *this; + + INIT(this, + .public = { + .remaining = _remaining, + .peek = _peek, + .read_uint8 = _read_uint8, + .read_uint16 = _read_uint16, + .read_uint24 = _read_uint24, + .read_uint32 = _read_uint32, + .read_data = _read_data, + .read_data8 = _read_data8, + .read_data16 = _read_data16, + .read_data24 = _read_data24, + .read_data32 = _read_data32, + .destroy = _destroy, + }, + .buf = data, + ); + + return &this->public; +} diff --git a/src/libtls/tls_reader.h b/src/libtls/tls_reader.h new file mode 100644 index 000000000..a8978b486 --- /dev/null +++ b/src/libtls/tls_reader.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_reader tls_reader + * @{ @ingroup libtls + */ + +#ifndef TLS_READER_H_ +#define TLS_READER_H_ + +typedef struct tls_reader_t tls_reader_t; + +#include <library.h> + +/** + * TLS record parser. + */ +struct tls_reader_t { + + /** + * Get the number of remaining bytes. + * + * @return number of remaining bytes in buffer + */ + u_int32_t (*remaining)(tls_reader_t *this); + + /** + * Peek the remaining data, not consuming any bytes. + * + * @return remaining data + */ + chunk_t (*peek)(tls_reader_t *this); + + /** + * Read a 8-bit integer from the buffer, advance. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint8)(tls_reader_t *this, u_int8_t *res); + + /** + * Read a 16-bit integer from the buffer, advance. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint16)(tls_reader_t *this, u_int16_t *res); + + /** + * Read a 24-bit integer from the buffer, advance. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint24)(tls_reader_t *this, u_int32_t *res); + + /** + * Read a 32-bit integer from the buffer, advance. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint32)(tls_reader_t *this, u_int32_t *res); + + /** + * Read a chunk of len bytes, advance. + * + * @param len number of bytes to read + * @param res pointer to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data)(tls_reader_t *this, u_int32_t len, chunk_t *res); + + /** + * Read a chunk of bytes with a 8-bit length header, advance. + * + * @param res pointer to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data8)(tls_reader_t *this, chunk_t *res); + + /** + * Read a chunk of bytes with a 16-bit length header, advance. + * + * @param res pointer to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data16)(tls_reader_t *this, chunk_t *res); + + /** + * Read a chunk of bytes with a 24-bit length header, advance. + * + * @param res pointer to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data24)(tls_reader_t *this, chunk_t *res); + + /** + * Read a chunk of bytes with a 32-bit length header, advance. + * + * @param res pointer to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data32)(tls_reader_t *this, chunk_t *res); + + /** + * Destroy a tls_reader_t. + */ + void (*destroy)(tls_reader_t *this); +}; + +/** + * Create a tls_reader instance. + */ +tls_reader_t *tls_reader_create(chunk_t data); + +#endif /** tls_reader_H_ @}*/ diff --git a/src/libtls/tls_server.c b/src/libtls/tls_server.c new file mode 100644 index 000000000..c0c0cc45f --- /dev/null +++ b/src/libtls/tls_server.c @@ -0,0 +1,607 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_server.h" + +#include <time.h> + +#include <debug.h> + +typedef struct private_tls_server_t private_tls_server_t; + + +typedef enum { + STATE_INIT, + STATE_HELLO_RECEIVED, + STATE_HELLO_SENT, + STATE_CERT_SENT, + STATE_CERTREQ_SENT, + STATE_HELLO_DONE, + STATE_CERT_RECEIVED, + STATE_KEY_EXCHANGE_RECEIVED, + STATE_CERT_VERIFY_RECEIVED, + STATE_CIPHERSPEC_CHANGED_IN, + STATE_FINISHED_RECEIVED, + STATE_CIPHERSPEC_CHANGED_OUT, + STATE_FINISHED_SENT, +} server_state_t; + +/** + * Private data of an tls_server_t object. + */ +struct private_tls_server_t { + + /** + * Public tls_server_t interface. + */ + tls_server_t public; + + /** + * TLS stack + */ + tls_t *tls; + + /** + * TLS crypto context + */ + tls_crypto_t *crypto; + + /** + * Server identity + */ + identification_t *server; + + /** + * Peer identity + */ + identification_t *peer; + + /** + * State we are in + */ + server_state_t state; + + /** + * Hello random data selected by client + */ + char client_random[32]; + + /** + * Hello random data selected by server + */ + char server_random[32]; + + /** + * Auth helper for peer authentication + */ + auth_cfg_t *peer_auth; + + /** + * Auth helper for server authentication + */ + auth_cfg_t *server_auth; + + /** + * Peer private key + */ + private_key_t *private; + + /** + * Selected TLS cipher suite + */ + tls_cipher_suite_t suite; +}; + +/** + * Process client hello message + */ +static status_t process_client_hello(private_tls_server_t *this, + tls_reader_t *reader) +{ + u_int16_t version; + chunk_t random, session, ciphers, compression, ext = chunk_empty; + tls_cipher_suite_t *suites; + int count, i; + + this->crypto->append_handshake(this->crypto, + TLS_CLIENT_HELLO, reader->peek(reader)); + + if (!reader->read_uint16(reader, &version) || + !reader->read_data(reader, sizeof(this->client_random), &random) || + !reader->read_data8(reader, &session) || + !reader->read_data16(reader, &ciphers) || + !reader->read_data8(reader, &compression) || + (reader->remaining(reader) && !reader->read_data16(reader, &ext))) + { + DBG1(DBG_IKE, "received invalid ClientHello"); + return FAILED; + } + + memcpy(this->client_random, random.ptr, sizeof(this->client_random)); + + if (version < this->tls->get_version(this->tls)) + { + this->tls->set_version(this->tls, version); + } + count = ciphers.len / sizeof(u_int16_t); + suites = alloca(count * sizeof(tls_cipher_suite_t)); + for (i = 0; i < count; i++) + { + suites[i] = untoh16(&ciphers.ptr[i * sizeof(u_int16_t)]); + } + this->suite = this->crypto->select_cipher_suite(this->crypto, suites, count); + if (!this->suite) + { + DBG1(DBG_IKE, "received cipher suite inacceptable"); + return FAILED; + } + this->state = STATE_HELLO_RECEIVED; + return NEED_MORE; +} + +/** + * Process certificate + */ +static status_t process_certificate(private_tls_server_t *this, + tls_reader_t *reader) +{ + certificate_t *cert; + tls_reader_t *certs; + chunk_t data; + bool first = TRUE; + + this->crypto->append_handshake(this->crypto, + TLS_CERTIFICATE, reader->peek(reader)); + + if (!reader->read_data24(reader, &data)) + { + return FAILED; + } + certs = tls_reader_create(data); + while (certs->remaining(certs)) + { + if (!certs->read_data24(certs, &data)) + { + certs->destroy(certs); + return FAILED; + } + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, data, BUILD_END); + if (cert) + { + if (first) + { + this->peer_auth->add(this->peer_auth, + AUTH_HELPER_SUBJECT_CERT, cert); + DBG1(DBG_IKE, "received TLS peer certificate '%Y'", + cert->get_subject(cert)); + first = FALSE; + } + else + { + DBG1(DBG_IKE, "received TLS intermediate certificate '%Y'", + cert->get_subject(cert)); + this->peer_auth->add(this->peer_auth, AUTH_HELPER_IM_CERT, cert); + } + } + else + { + DBG1(DBG_IKE, "parsing TLS certificate failed, skipped"); + } + } + certs->destroy(certs); + this->state = STATE_CERT_RECEIVED; + return NEED_MORE; +} + +/** + * Process Client Key Exchange + */ +static status_t process_key_exchange(private_tls_server_t *this, + tls_reader_t *reader) +{ + chunk_t encrypted, premaster; + + this->crypto->append_handshake(this->crypto, + TLS_CLIENT_KEY_EXCHANGE, reader->peek(reader)); + + if (!reader->read_data16(reader, &encrypted)) + { + DBG1(DBG_IKE, "received invalid Client Key Exchange"); + return FAILED; + } + + if (!this->private || + !this->private->decrypt(this->private, encrypted, &premaster)) + { + DBG1(DBG_IKE, "decrypting Client Key Exchange data failed"); + return FAILED; + } + this->crypto->derive_secrets(this->crypto, premaster, + chunk_from_thing(this->client_random), + chunk_from_thing(this->server_random)); + chunk_clear(&premaster); + + this->state = STATE_KEY_EXCHANGE_RECEIVED; + return NEED_MORE; +} + +/** + * Process Certificate verify + */ +static status_t process_cert_verify(private_tls_server_t *this, + tls_reader_t *reader) +{ + bool verified = FALSE; + enumerator_t *enumerator; + public_key_t *public; + auth_cfg_t *auth; + tls_reader_t *sig; + + enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, + KEY_ANY, this->peer, this->peer_auth); + while (enumerator->enumerate(enumerator, &public, &auth)) + { + sig = tls_reader_create(reader->peek(reader)); + verified = this->crypto->verify_handshake(this->crypto, public, sig); + sig->destroy(sig); + if (verified) + { + break; + } + DBG1(DBG_IKE, "signature verification failed, trying another key"); + } + enumerator->destroy(enumerator); + + if (!verified) + { + DBG1(DBG_IKE, "no trusted certificate found for '%Y' to verify TLS peer", + this->peer); + return FAILED; + } + + this->crypto->append_handshake(this->crypto, + TLS_CERTIFICATE_VERIFY, reader->peek(reader)); + this->state = STATE_CERT_VERIFY_RECEIVED; + return NEED_MORE; +} + +/** + * Process finished message + */ +static status_t process_finished(private_tls_server_t *this, + tls_reader_t *reader) +{ + chunk_t received; + char buf[12]; + + if (!reader->read_data(reader, sizeof(buf), &received)) + { + DBG1(DBG_IKE, "received client finished too short"); + return FAILED; + } + if (!this->crypto->calculate_finished(this->crypto, "client finished", buf)) + { + DBG1(DBG_IKE, "calculating client finished failed"); + return FAILED; + } + if (!chunk_equals(received, chunk_from_thing(buf))) + { + DBG1(DBG_IKE, "received client finished invalid"); + return FAILED; + } + + this->crypto->append_handshake(this->crypto, TLS_FINISHED, received); + this->state = STATE_FINISHED_RECEIVED; + return NEED_MORE; +} + +METHOD(tls_handshake_t, process, status_t, + private_tls_server_t *this, tls_handshake_type_t type, tls_reader_t *reader) +{ + tls_handshake_type_t expected; + + switch (this->state) + { + case STATE_INIT: + if (type == TLS_CLIENT_HELLO) + { + return process_client_hello(this, reader); + } + expected = TLS_CLIENT_HELLO; + break; + case STATE_HELLO_DONE: + if (type == TLS_CERTIFICATE) + { + return process_certificate(this, reader); + } + expected = TLS_CERTIFICATE; + break; + case STATE_CERT_RECEIVED: + if (type == TLS_CLIENT_KEY_EXCHANGE) + { + return process_key_exchange(this, reader); + } + expected = TLS_CLIENT_KEY_EXCHANGE; + break; + case STATE_KEY_EXCHANGE_RECEIVED: + if (type == TLS_CERTIFICATE_VERIFY) + { + return process_cert_verify(this, reader); + } + expected = TLS_CERTIFICATE_VERIFY; + break; + case STATE_CIPHERSPEC_CHANGED_IN: + if (type == TLS_FINISHED) + { + return process_finished(this, reader); + } + expected = TLS_FINISHED; + break; + default: + DBG1(DBG_IKE, "TLS %N not expected in current state", + tls_handshake_type_names, type); + return FAILED; + } + DBG1(DBG_IKE, "TLS %N expected, but received %N", + tls_handshake_type_names, expected, tls_handshake_type_names, type); + return FAILED; +} + +/** + * Send ServerHello message + */ +static status_t send_server_hello(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + rng_t *rng; + + htoun32(&this->server_random, time(NULL)); + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + return FAILED; + } + rng->get_bytes(rng, sizeof(this->server_random) - 4, this->server_random + 4); + rng->destroy(rng); + + writer->write_uint16(writer, this->tls->get_version(this->tls)); + writer->write_data(writer, chunk_from_thing(this->server_random)); + /* session identifier => none, we don't support session resumption */ + writer->write_data8(writer, chunk_empty); + /* add selected suite */ + writer->write_uint16(writer, this->suite); + /* NULL compression only */ + writer->write_uint8(writer, 0); + + *type = TLS_SERVER_HELLO; + this->state = STATE_HELLO_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send Certificate + */ +static status_t send_certificate(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + enumerator_t *enumerator; + certificate_t *cert; + auth_rule_t rule; + tls_writer_t *certs; + chunk_t data; + + this->private = lib->credmgr->get_private(lib->credmgr, + KEY_ANY, this->server, this->server_auth); + if (!this->private) + { + DBG1(DBG_IKE, "no TLS server certificate found for '%Y'", this->server); + return FAILED; + } + + /* generate certificate payload */ + certs = tls_writer_create(256); + cert = this->server_auth->get(this->server_auth, AUTH_RULE_SUBJECT_CERT); + if (cert) + { + if (cert->get_encoding(cert, CERT_ASN1_DER, &data)) + { + DBG1(DBG_IKE, "sending TLS server certificate '%Y'", + cert->get_subject(cert)); + certs->write_data24(certs, data); + free(data.ptr); + } + } + enumerator = this->server_auth->create_enumerator(this->server_auth); + while (enumerator->enumerate(enumerator, &rule, &cert)) + { + if (rule == AUTH_RULE_IM_CERT) + { + if (cert->get_encoding(cert, CERT_ASN1_DER, &data)) + { + DBG1(DBG_IKE, "sending TLS intermediate certificate '%Y'", + cert->get_subject(cert)); + certs->write_data24(certs, data); + free(data.ptr); + } + } + } + enumerator->destroy(enumerator); + + writer->write_data24(writer, certs->get_buf(certs)); + certs->destroy(certs); + + *type = TLS_CERTIFICATE; + this->state = STATE_CERT_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send Certificate Request + */ +static status_t send_certificate_request(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + tls_writer_t *authorities; + enumerator_t *enumerator; + certificate_t *cert; + identification_t *id; + + /* currently only RSA signatures are supported */ + writer->write_data8(writer, chunk_from_chars(1)); + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + /* enforce RSA with SHA1 signatures */ + writer->write_data16(writer, chunk_from_chars(2, 1)); + } + + authorities = tls_writer_create(64); + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + CERT_X509, KEY_RSA, NULL, TRUE); + while (enumerator->enumerate(enumerator, &cert)) + { + id = cert->get_subject(cert); + authorities->write_data16(authorities, id->get_encoding(id)); + } + enumerator->destroy(enumerator); + writer->write_data16(writer, authorities->get_buf(authorities)); + authorities->destroy(authorities); + + *type = TLS_CERTIFICATE_REQUEST; + this->state = STATE_CERTREQ_SENT; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send Hello Done + */ +static status_t send_hello_done(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + *type = TLS_SERVER_HELLO_DONE; + this->state = STATE_HELLO_DONE; + this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer)); + return NEED_MORE; +} + +/** + * Send Finished + */ +static status_t send_finished(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + char buf[12]; + + if (!this->crypto->calculate_finished(this->crypto, "server finished", buf)) + { + DBG1(DBG_IKE, "calculating server finished data failed"); + return FAILED; + } + + writer->write_data(writer, chunk_from_thing(buf)); + + *type = TLS_FINISHED; + this->state = STATE_FINISHED_SENT; + this->crypto->derive_eap_msk(this->crypto, + chunk_from_thing(this->client_random), + chunk_from_thing(this->server_random)); + return NEED_MORE; +} + +METHOD(tls_handshake_t, build, status_t, + private_tls_server_t *this, tls_handshake_type_t *type, tls_writer_t *writer) +{ + switch (this->state) + { + case STATE_HELLO_RECEIVED: + return send_server_hello(this, type, writer); + case STATE_HELLO_SENT: + return send_certificate(this, type, writer); + case STATE_CERT_SENT: + return send_certificate_request(this, type, writer); + case STATE_CERTREQ_SENT: + return send_hello_done(this, type, writer); + case STATE_CIPHERSPEC_CHANGED_OUT: + return send_finished(this, type, writer); + case STATE_FINISHED_SENT: + return INVALID_STATE; + default: + return INVALID_STATE; + } +} + +METHOD(tls_handshake_t, cipherspec_changed, bool, + private_tls_server_t *this) +{ + if (this->state == STATE_FINISHED_RECEIVED) + { + this->crypto->change_cipher(this->crypto, FALSE); + this->state = STATE_CIPHERSPEC_CHANGED_OUT; + return TRUE; + } + return FALSE; +} + +METHOD(tls_handshake_t, change_cipherspec, bool, + private_tls_server_t *this) +{ + if (this->state == STATE_CERT_VERIFY_RECEIVED) + { + this->crypto->change_cipher(this->crypto, TRUE); + this->state = STATE_CIPHERSPEC_CHANGED_IN; + return TRUE; + } + return FALSE; +} + +METHOD(tls_handshake_t, destroy, void, + private_tls_server_t *this) +{ + DESTROY_IF(this->private); + this->peer_auth->destroy(this->peer_auth); + this->server_auth->destroy(this->server_auth); + free(this); +} + +/** + * See header + */ +tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *server, identification_t *peer) +{ + private_tls_server_t *this; + + INIT(this, + .public.handshake = { + .process = _process, + .build = _build, + .cipherspec_changed = _cipherspec_changed, + .change_cipherspec = _change_cipherspec, + .destroy = _destroy, + }, + .tls = tls, + .crypto = crypto, + .server = server, + .peer = peer, + .state = STATE_INIT, + .peer_auth = auth_cfg_create(), + .server_auth = auth_cfg_create(), + ); + + return &this->public; +} diff --git a/src/libtls/tls_server.h b/src/libtls/tls_server.h new file mode 100644 index 000000000..6dc26cd3f --- /dev/null +++ b/src/libtls/tls_server.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_server tls_server + * @{ @ingroup libtls + */ + +#ifndef TLS_SERVER_H_ +#define TLS_SERVER_H_ + +typedef struct tls_server_t tls_server_t; + +#include "tls_handshake.h" +#include "tls_crypto.h" + +#include <library.h> + +/** + * TLS handshake protocol handler as peer. + */ +struct tls_server_t { + + /** + * Implements the TLS handshake protocol handler. + */ + tls_handshake_t handshake; +}; + +/** + * Create a tls_server instance. + */ +tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *server, identification_t *peer); + +#endif /** TLS_SERVER_H_ @}*/ diff --git a/src/libtls/tls_writer.c b/src/libtls/tls_writer.c new file mode 100644 index 000000000..235dc2cdf --- /dev/null +++ b/src/libtls/tls_writer.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +#include "tls_writer.h" + +typedef struct private_tls_writer_t private_tls_writer_t; + +/** + * Private data of an tls_writer_t object. + */ +struct private_tls_writer_t { + + /** + * Public tls_writer_t interface. + */ + tls_writer_t public; + + /** + * Allocated buffer + */ + chunk_t buf; + + /** + * Used bytes in buffer + */ + size_t used; + + /** + * Number of bytes to increase buffer size + */ + size_t increase; +}; + +/** + * Increase buffer size + */ +static void increase(private_tls_writer_t *this) +{ + this->buf.len += this->increase; + this->buf.ptr = realloc(this->buf.ptr, this->buf.len); +} + +METHOD(tls_writer_t, write_uint8, void, + private_tls_writer_t *this, u_int8_t value) +{ + if (this->used + 1 > this->buf.len) + { + increase(this); + } + this->buf.ptr[this->used] = value; + this->used += 1; +} + +METHOD(tls_writer_t, write_uint16, void, + private_tls_writer_t *this, u_int16_t value) +{ + if (this->used + 2 > this->buf.len) + { + increase(this); + } + htoun16(this->buf.ptr + this->used, value); + this->used += 2; +} + +METHOD(tls_writer_t, write_uint24, void, + private_tls_writer_t *this, u_int32_t value) +{ + if (this->used + 3 > this->buf.len) + { + increase(this); + } + value = htonl(value); + memcpy(this->buf.ptr + this->used, ((char*)&value) + 1, 3); + this->used += 3; +} + +METHOD(tls_writer_t, write_uint32, void, + private_tls_writer_t *this, u_int32_t value) +{ + if (this->used + 4 > this->buf.len) + { + increase(this); + } + htoun32(this->buf.ptr + this->used, value); + this->used += 4; +} + +METHOD(tls_writer_t, write_data, void, + private_tls_writer_t *this, chunk_t value) +{ + while (this->used + value.len > this->buf.len) + { + increase(this); + } + memcpy(this->buf.ptr + this->used, value.ptr, value.len); + this->used += value.len; +} + +METHOD(tls_writer_t, write_data8, void, + private_tls_writer_t *this, chunk_t value) +{ + write_uint8(this, value.len); + write_data(this, value); +} + +METHOD(tls_writer_t, write_data16, void, + private_tls_writer_t *this, chunk_t value) +{ + write_uint16(this, value.len); + write_data(this, value); +} + +METHOD(tls_writer_t, write_data24, void, + private_tls_writer_t *this, chunk_t value) +{ + write_uint24(this, value.len); + write_data(this, value); +} + +METHOD(tls_writer_t, write_data32, void, + private_tls_writer_t *this, chunk_t value) +{ + write_uint32(this, value.len); + write_data(this, value); +} + +METHOD(tls_writer_t, wrap8, void, + private_tls_writer_t *this) +{ + if (this->used + 1 > this->buf.len) + { + increase(this); + } + memmove(this->buf.ptr + 1, this->buf.ptr, 1); + this->buf.ptr[0] = this->used; + this->used += 1; +} + +METHOD(tls_writer_t, wrap16, void, + private_tls_writer_t *this) +{ + if (this->used + 2 > this->buf.len) + { + increase(this); + } + memmove(this->buf.ptr + 2, this->buf.ptr, 2); + htoun16(this->buf.ptr, this->used); + this->used += 2; +} + +METHOD(tls_writer_t, wrap24, void, + private_tls_writer_t *this) +{ + u_int32_t len; + + if (this->used + 3 > this->buf.len) + { + increase(this); + } + memmove(this->buf.ptr + 3, this->buf.ptr, 3); + + len = htonl(this->used); + memcpy(this->buf.ptr, ((char*)&len) + 1, 3); + this->used += 3; +} + +METHOD(tls_writer_t, wrap32, void, + private_tls_writer_t *this) +{ + if (this->used + 4 > this->buf.len) + { + increase(this); + } + memmove(this->buf.ptr + 4, this->buf.ptr, 4); + htoun32(this->buf.ptr, this->used); + this->used += 4; +} + +METHOD(tls_writer_t, get_buf, chunk_t, + private_tls_writer_t *this) +{ + return chunk_create(this->buf.ptr, this->used); +} + +METHOD(tls_writer_t, destroy, void, + private_tls_writer_t *this) +{ + free(this->buf.ptr); + free(this); +} + +/** + * See header + */ +tls_writer_t *tls_writer_create(u_int32_t bufsize) +{ + private_tls_writer_t *this; + + INIT(this, + .public = { + .write_uint8 = _write_uint8, + .write_uint16 = _write_uint16, + .write_uint24 = _write_uint24, + .write_uint32 = _write_uint32, + .write_data = _write_data, + .write_data8 = _write_data8, + .write_data16 = _write_data16, + .write_data24 = _write_data24, + .write_data32 = _write_data32, + .wrap8 = _wrap8, + .wrap16 = _wrap16, + .wrap24 = _wrap24, + .wrap32 = _wrap32, + .get_buf = _get_buf, + .destroy = _destroy, + }, + .increase = bufsize ?: 32, + ); + if (bufsize) + { + this->buf = chunk_alloc(bufsize); + } + + return &this->public; +} diff --git a/src/libtls/tls_writer.h b/src/libtls/tls_writer.h new file mode 100644 index 000000000..e6522dbfb --- /dev/null +++ b/src/libtls/tls_writer.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup tls_writer tls_writer + * @{ @ingroup libtls + */ + +#ifndef TLS_WRITER_H_ +#define TLS_WRITER_H_ + +typedef struct tls_writer_t tls_writer_t; + +#include <library.h> + +/** + * TLS record generator. + */ +struct tls_writer_t { + + /** + * Append a 8-bit integer to the buffer. + * + * @param value value to append + */ + void (*write_uint8)(tls_writer_t *this, u_int8_t value); + + /** + * Append a 16-bit integer to the buffer. + * + * @param value value to append + */ + void (*write_uint16)(tls_writer_t *this, u_int16_t value); + + /** + * Append a 24-bit integer to the buffer. + * + * @param value value to append + */ + void (*write_uint24)(tls_writer_t *this, u_int32_t value); + + /** + * Append a 32-bit integer to the buffer. + * + * @param value value to append + */ + void (*write_uint32)(tls_writer_t *this, u_int32_t value); + + /** + * Append a chunk of data without a length header. + * + * @param value value to append + */ + void (*write_data)(tls_writer_t *this, chunk_t value); + + /** + * Append a chunk of data with a 16-bit length header. + * + * @param value value to append + */ + void (*write_data8)(tls_writer_t *this, chunk_t value); + + /** + * Append a chunk of data with a 8-bit length header. + * + * @param value value to append + */ + void (*write_data16)(tls_writer_t *this, chunk_t value); + + /** + * Append a chunk of data with a 24-bit length header. + * + * @param value value to append + */ + void (*write_data24)(tls_writer_t *this, chunk_t value); + + /** + * Append a chunk of data with a 32-bit length header. + * + * @param value value to append + */ + void (*write_data32)(tls_writer_t *this, chunk_t value); + + /** + * Prepend a 8-bit length header to existing data. + */ + void (*wrap8)(tls_writer_t *this); + + /** + * Prepend a 16-bit length header to existing data. + */ + void (*wrap16)(tls_writer_t *this); + + /** + * Prepend a 24-bit length header to existing data. + */ + void (*wrap24)(tls_writer_t *this); + + /** + * Prepend a 32-bit length header to existing data. + */ + void (*wrap32)(tls_writer_t *this); + + /** + * Get the encoded data buffer. + * + * @return chunk to internal buffer + */ + chunk_t (*get_buf)(tls_writer_t *this); + + /** + * Destroy a tls_writer_t. + */ + void (*destroy)(tls_writer_t *this); +}; + +/** + * Create a tls_writer instance. + * + * @param bufsize initially allocated buffer size + */ +tls_writer_t *tls_writer_create(u_int32_t bufsize); + +#endif /** TLS_WRITER_H_ @}*/ |