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/tls_peer.c | |
parent | 0b71bc7af047f1a20bbad8a38d33b01452c35613 (diff) | |
download | strongswan-0f82a47063f05d8eeae64866ff4787edc8db6328.tar.bz2 strongswan-0f82a47063f05d8eeae64866ff4787edc8db6328.tar.xz |
Moved TLS stack to its own library
Diffstat (limited to 'src/libtls/tls_peer.c')
-rw-r--r-- | src/libtls/tls_peer.c | 625 |
1 files changed, 625 insertions, 0 deletions
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; +} |