diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/charon/plugins/eap_tls/eap_tls.c | 2 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls.c | 11 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls.h | 5 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_fragmentation.c | 11 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_handshake.h | 12 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_peer.c | 331 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_peer.h | 5 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_reader.c | 7 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_reader.h | 7 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_server.c | 29 | ||||
-rw-r--r-- | src/charon/plugins/eap_tls/tls/tls_server.h | 5 |
11 files changed, 406 insertions, 19 deletions
diff --git a/src/charon/plugins/eap_tls/eap_tls.c b/src/charon/plugins/eap_tls/eap_tls.c index 1232b14b2..589096df0 100644 --- a/src/charon/plugins/eap_tls/eap_tls.c +++ b/src/charon/plugins/eap_tls/eap_tls.c @@ -408,7 +408,7 @@ static eap_tls_t *eap_tls_create(identification_t *server, .peer = peer->clone(peer), .server = server->clone(server), .is_server = is_server, - .tls = tls_create(is_server), + .tls = tls_create(is_server, server, peer), ); return &this->public; diff --git a/src/charon/plugins/eap_tls/tls/tls.c b/src/charon/plugins/eap_tls/tls/tls.c index 85c68e408..407438a4b 100644 --- a/src/charon/plugins/eap_tls/tls/tls.c +++ b/src/charon/plugins/eap_tls/tls/tls.c @@ -143,7 +143,8 @@ METHOD(tls_t, destroy, void, /** * See header */ -tls_t *tls_create(bool is_server) +tls_t *tls_create(bool is_server, identification_t *server, + identification_t *peer) { private_tls_t *this; @@ -162,13 +163,13 @@ tls_t *tls_create(bool is_server) if (is_server) { - this->handshake = &tls_server_create(&this->public, - this->crypto)->handshake; + this->handshake = &tls_server_create(&this->public, this->crypto, + server, peer)->handshake; } else { - this->handshake = &tls_peer_create(&this->public, - this->crypto)->handshake; + this->handshake = &tls_peer_create(&this->public, this->crypto, + peer, server)->handshake; } this->fragmentation = tls_fragmentation_create(this->handshake); this->compression = tls_compression_create(this->fragmentation); diff --git a/src/charon/plugins/eap_tls/tls/tls.h b/src/charon/plugins/eap_tls/tls/tls.h index f46756e0e..a740f99be 100644 --- a/src/charon/plugins/eap_tls/tls/tls.h +++ b/src/charon/plugins/eap_tls/tls/tls.h @@ -178,8 +178,11 @@ struct tls_t { * 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); +tls_t *tls_create(bool is_server, identification_t *server, + identification_t *peer); #endif /** TLS_H_ @}*/ diff --git a/src/charon/plugins/eap_tls/tls/tls_fragmentation.c b/src/charon/plugins/eap_tls/tls/tls_fragmentation.c index b9d3a75a3..83295e229 100644 --- a/src/charon/plugins/eap_tls/tls/tls_fragmentation.c +++ b/src/charon/plugins/eap_tls/tls/tls_fragmentation.c @@ -141,8 +141,8 @@ METHOD(tls_fragmentation_t, process, status_t, switch (type) { case TLS_CHANGE_CIPHER_SPEC: - /* TODO: handle ChangeCipherSpec */ - status = FAILED; + this->handshake->change_cipherspec(this->handshake); + status = NEED_MORE; break; case TLS_ALERT: /* TODO: handle Alert */ @@ -171,6 +171,13 @@ METHOD(tls_fragmentation_t, build, status_t, 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); diff --git a/src/charon/plugins/eap_tls/tls/tls_handshake.h b/src/charon/plugins/eap_tls/tls/tls_handshake.h index 7031158d4..e32f3e5ee 100644 --- a/src/charon/plugins/eap_tls/tls/tls_handshake.h +++ b/src/charon/plugins/eap_tls/tls/tls_handshake.h @@ -60,6 +60,18 @@ struct tls_handshake_t { 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. + */ + void (*change_cipherspec)(tls_handshake_t *this); + + /** * Destroy a tls_handshake_t. */ void (*destroy)(tls_handshake_t *this); diff --git a/src/charon/plugins/eap_tls/tls/tls_peer.c b/src/charon/plugins/eap_tls/tls/tls_peer.c index 3828ead29..77e1f1a7f 100644 --- a/src/charon/plugins/eap_tls/tls/tls_peer.c +++ b/src/charon/plugins/eap_tls/tls/tls_peer.c @@ -25,6 +25,11 @@ typedef enum { STATE_INIT, STATE_HELLO_SENT, STATE_HELLO_DONE, + STATE_CERT_SENT, + STATE_KEY_EXCHANGE_SENT, + STATE_VERIFY_SENT, + STATE_CIPHERSPEC_CHANGED, + STATE_FINISHED_SENT, } peer_state_t; /** @@ -48,12 +53,56 @@ struct private_tls_peer_t { tls_crypto_t *crypto; /** + * Peer identity + */ + identification_t *peer; + + /** + * Server identity + */ + identification_t *server; + + /** * State we are in */ peer_state_t state; + + /** + * All handshake data concatentated + */ + chunk_t handshake; + + /** + * 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; }; /** + * Append a handshake message to the handshake data buffer + */ +static void append_handshake(private_tls_peer_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); +} + +/** * Process a server hello message */ static status_t process_server_hello(private_tls_peer_t *this, @@ -64,6 +113,8 @@ static status_t process_server_hello(private_tls_peer_t *this, u_int32_t gmt; chunk_t random, session, ext = chunk_empty; + append_handshake(this, TLS_SERVER_HELLO, reader->peek(reader)); + if (!reader->read_uint16(reader, &version) || !reader->read_uint32(reader, &gmt) || !reader->read_data(reader, 28, &random) || @@ -91,6 +142,9 @@ static status_t process_certificate(private_tls_peer_t *this, certificate_t *cert; tls_reader_t *certs; chunk_t data; + bool first = TRUE; + + append_handshake(this, TLS_CERTIFICATE, reader->peek(reader)); if (!reader->read_data24(reader, &data)) { @@ -105,11 +159,28 @@ static status_t process_certificate(private_tls_peer_t *this, return FAILED; } cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_BLOB_ASN1_DER, data, BUILD_END); + BUILD_BLOB_ASN1_DER, data, BUILD_END); if (cert) { - DBG1(DBG_IKE, "got certificate: %Y", cert->get_subject(cert)); - cert->destroy(cert); + if (first) + { + this->server_auth->add(this->server_auth, + AUTH_RULE_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_RULE_IM_CERT, cert); + } + } + else + { + DBG1(DBG_IKE, "parsing TLS certificate failed, skipped"); } } certs->destroy(certs); @@ -124,6 +195,9 @@ 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; + + append_handshake(this, TLS_CERTIFICATE_REQUEST, reader->peek(reader)); if (!reader->read_data8(reader, &types)) { @@ -135,6 +209,7 @@ static status_t process_certreq(private_tls_peer_t *this, tls_reader_t *reader) { return FAILED; } + /* TODO: store supported hashsig algorithms */ } if (!reader->read_data16(reader, &data)) { @@ -149,13 +224,34 @@ static status_t process_certreq(private_tls_peer_t *this, tls_reader_t *reader) return FAILED; } id = identification_create_from_encoding(ID_DER_ASN1_DN, data); - DBG1(DBG_IKE, "received certificate request for %Y", id); + cert = charon->credentials->get_cert(charon->credentials, + 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); return NEED_MORE; } +/** + * Process Hello Done message + */ +static status_t process_hello_done(private_tls_peer_t *this, + tls_reader_t *reader) +{ + append_handshake(this, TLS_SERVER_HELLO_DONE, reader->peek(reader)); + this->state = STATE_HELLO_DONE; + return NEED_MORE; +} + METHOD(tls_handshake_t, process, status_t, private_tls_peer_t *this, tls_handshake_type_t type, tls_reader_t *reader) { @@ -171,8 +267,7 @@ METHOD(tls_handshake_t, process, status_t, case TLS_CERTIFICATE_REQUEST: return process_certreq(this, reader); case TLS_SERVER_HELLO_DONE: - this->state = STATE_HELLO_DONE; - return NEED_MORE; + return process_hello_done(this, reader); default: break; } @@ -223,6 +318,192 @@ static status_t send_hello(private_tls_peer_t *this, *type = TLS_CLIENT_HELLO; this->state = STATE_HELLO_SENT; + append_handshake(this, *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 = charon->credentials->get_private(charon->credentials, + 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) + { + DBG1(DBG_IKE, "sending TLS peer certificate '%Y'", + cert->get_subject(cert)); + data = cert->get_encoding(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) + { + DBG1(DBG_IKE, "sending TLS intermediate certificate '%Y'", + cert->get_subject(cert)); + data = cert->get_encoding(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; + append_handshake(this, *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); + + enumerator = charon->credentials->create_public_enumerator( + charon->credentials, 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; + append_handshake(this, *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) +{ + chunk_t signature; + + if (!this->private) + { + return FAILED; + } + + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + if (!this->private->sign(this->private, SIGN_RSA_EMSA_PKCS1_SHA1, + this->handshake, &signature)) + { + DBG1(DBG_IKE, "creating TLS Certificate Verify signature failed"); + return FAILED; + } + /* TODO: signature scheme to hashsign algorithm mapping */ + writer->write_uint8(writer, 2); /* sha1 */ + writer->write_uint8(writer, 1); /* RSA */ + } + 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, "unable to sign %N Verify, MD5 not supported", + tls_version_names, this->tls->get_version(this->tls)); + return FAILED; + } + md5->get_hash(md5, this->handshake, buf); + md5->destroy(md5); + sha1 = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!sha1) + { + DBG1(DBG_IKE, "unable to sign %N Verify, SHA1 not supported", + tls_version_names, this->tls->get_version(this->tls)); + return FAILED; + } + sha1->get_hash(sha1, this->handshake, buf + HASH_SIZE_MD5); + sha1->destroy(sha1); + + if (!this->private->sign(this->private, SIGN_RSA_EMSA_PKCS1_NULL, + chunk_from_thing(buf), &signature)) + { + DBG1(DBG_IKE, "creating TLS Certificate Verify signature failed"); + return FAILED; + } + } + writer->write_data16(writer, signature); + free(signature.ptr); + chunk_free(&this->handshake); + + *type = TLS_CERTIFICATE_VERIFY; + this->state = STATE_VERIFY_SENT; + return NEED_MORE; +} + +/** + * Send Finished + */ +static status_t send_finished(private_tls_peer_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + *type = TLS_FINISHED; + this->state = STATE_FINISHED_SENT; + /* TODO: finished message */ return NEED_MORE; } @@ -233,21 +514,51 @@ METHOD(tls_handshake_t, build, status_t, { case STATE_INIT: return send_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: + 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->state = STATE_CIPHERSPEC_CHANGED; + return TRUE; + } + return FALSE; +} + +METHOD(tls_handshake_t, change_cipherspec, void, + private_tls_peer_t *this) +{ + +} + METHOD(tls_handshake_t, destroy, void, private_tls_peer_t *this) { + DESTROY_IF(this->private); + free(this->handshake.ptr); + 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) +tls_peer_t *tls_peer_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *peer, identification_t *server) { private_tls_peer_t *this; @@ -255,11 +566,17 @@ tls_peer_t *tls_peer_create(tls_t *tls, tls_crypto_t *crypto) .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/charon/plugins/eap_tls/tls/tls_peer.h b/src/charon/plugins/eap_tls/tls/tls_peer.h index 1243a5a24..7857d3261 100644 --- a/src/charon/plugins/eap_tls/tls/tls_peer.h +++ b/src/charon/plugins/eap_tls/tls/tls_peer.h @@ -26,6 +26,8 @@ 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. */ @@ -40,6 +42,7 @@ struct tls_peer_t { /** * Create a tls_peer instance. */ -tls_peer_t *tls_peer_create(tls_t *tls, tls_crypto_t *crypto); +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/charon/plugins/eap_tls/tls/tls_reader.c b/src/charon/plugins/eap_tls/tls/tls_reader.c index a1911d49b..b21eb04f3 100644 --- a/src/charon/plugins/eap_tls/tls/tls_reader.c +++ b/src/charon/plugins/eap_tls/tls/tls_reader.c @@ -41,6 +41,12 @@ METHOD(tls_reader_t, remaining, u_int32_t, 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) { @@ -175,6 +181,7 @@ tls_reader_t *tls_reader_create(chunk_t data) INIT(this, .public = { .remaining = _remaining, + .peek = _peek, .read_uint8 = _read_uint8, .read_uint16 = _read_uint16, .read_uint24 = _read_uint24, diff --git a/src/charon/plugins/eap_tls/tls/tls_reader.h b/src/charon/plugins/eap_tls/tls/tls_reader.h index 9ea4527d6..a8917dfb6 100644 --- a/src/charon/plugins/eap_tls/tls/tls_reader.h +++ b/src/charon/plugins/eap_tls/tls/tls_reader.h @@ -38,6 +38,13 @@ struct tls_reader_t { 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 diff --git a/src/charon/plugins/eap_tls/tls/tls_server.c b/src/charon/plugins/eap_tls/tls/tls_server.c index 0828e81a1..a5dcdd061 100644 --- a/src/charon/plugins/eap_tls/tls/tls_server.c +++ b/src/charon/plugins/eap_tls/tls/tls_server.c @@ -38,6 +38,16 @@ struct private_tls_server_t { * TLS crypto context */ tls_crypto_t *crypto; + + /** + * Server identity + */ + identification_t *server; + + /** + * Peer identity + */ + identification_t *peer; }; @@ -53,6 +63,18 @@ METHOD(tls_handshake_t, build, status_t, return INVALID_STATE; } +METHOD(tls_handshake_t, cipherspec_changed, bool, + private_tls_server_t *this) +{ + return FALSE; +} + +METHOD(tls_handshake_t, change_cipherspec, void, + private_tls_server_t *this) +{ + +} + METHOD(tls_handshake_t, destroy, void, private_tls_server_t *this) { @@ -62,7 +84,8 @@ METHOD(tls_handshake_t, destroy, void, /** * See header */ -tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto) +tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *server, identification_t *peer) { private_tls_server_t *this; @@ -70,10 +93,14 @@ tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto) .public.handshake = { .process = _process, .build = _build, + .cipherspec_changed = _cipherspec_changed, + .change_cipherspec = _change_cipherspec, .destroy = _destroy, }, .tls = tls, .crypto = crypto, + .server = server, + .peer = peer, ); return &this->public; diff --git a/src/charon/plugins/eap_tls/tls/tls_server.h b/src/charon/plugins/eap_tls/tls/tls_server.h index 182c4b1ac..3fddea225 100644 --- a/src/charon/plugins/eap_tls/tls/tls_server.h +++ b/src/charon/plugins/eap_tls/tls/tls_server.h @@ -26,6 +26,8 @@ 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. */ @@ -40,6 +42,7 @@ struct tls_server_t { /** * Create a tls_server instance. */ -tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto); +tls_server_t *tls_server_create(tls_t *tls, tls_crypto_t *crypto, + identification_t *server, identification_t *peer); #endif /** TLS_SERVER_H_ @}*/ |