diff options
Diffstat (limited to 'src/libtls/tls_server.c')
-rw-r--r-- | src/libtls/tls_server.c | 1032 |
1 files changed, 1032 insertions, 0 deletions
diff --git a/src/libtls/tls_server.c b/src/libtls/tls_server.c new file mode 100644 index 000000000..b0417f6cb --- /dev/null +++ b/src/libtls/tls_server.c @@ -0,0 +1,1032 @@ +/* + * 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> +#include <credentials/certificates/x509.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_KEY_EXCHANGE_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; + + /** + * TLS alert handler + */ + tls_alert_t *alert; + + /** + * Server identity + */ + identification_t *server; + + /** + * Peer identity, NULL for no client authentication + */ + 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; + + /** + * DHE exchange + */ + diffie_hellman_t *dh; + + /** + * Selected TLS cipher suite + */ + tls_cipher_suite_t suite; + + /** + * Offered TLS version of the client + */ + tls_version_t client_version; + + /** + * Hash and signature algorithms supported by peer + */ + chunk_t hashsig; + + /** + * Elliptic curves supported by peer + */ + chunk_t curves; + + /** + * Did we receive the curves from the client? + */ + bool curves_received; +}; + +/** + * Find a cipher suite and a server key + */ +static bool select_suite_and_key(private_tls_server_t *this, + tls_cipher_suite_t *suites, int count) +{ + private_key_t *key; + key_type_t type; + + key = lib->credmgr->get_private(lib->credmgr, KEY_ANY, this->server, + this->server_auth); + if (!key) + { + DBG1(DBG_TLS, "no usable TLS server certificate found for '%Y'", + this->server); + return FALSE; + } + this->suite = this->crypto->select_cipher_suite(this->crypto, + suites, count, key->get_type(key)); + if (!this->suite) + { /* no match for this key, try to find another type */ + if (key->get_type(key) == KEY_ECDSA) + { + type = KEY_RSA; + } + else + { + type = KEY_ECDSA; + } + key->destroy(key); + + this->suite = this->crypto->select_cipher_suite(this->crypto, + suites, count, type); + if (!this->suite) + { + DBG1(DBG_TLS, "received cipher suites inacceptable"); + return FALSE; + } + this->server_auth->destroy(this->server_auth); + this->server_auth = auth_cfg_create(); + key = lib->credmgr->get_private(lib->credmgr, type, this->server, + this->server_auth); + if (!key) + { + DBG1(DBG_TLS, "received cipher suites inacceptable"); + return FALSE; + } + } + this->private = key; + return TRUE; +} + +/** + * Process client hello message + */ +static status_t process_client_hello(private_tls_server_t *this, + tls_reader_t *reader) +{ + u_int16_t version, extension; + chunk_t random, session, ciphers, compression, ext = chunk_empty; + tls_reader_t *extensions; + 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_TLS, "received invalid ClientHello"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + return NEED_MORE; + } + + if (ext.len) + { + extensions = tls_reader_create(ext); + while (extensions->remaining(extensions)) + { + if (!extensions->read_uint16(extensions, &extension) || + !extensions->read_data16(extensions, &ext)) + { + DBG1(DBG_TLS, "received invalid ClientHello Extensions"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + extensions->destroy(extensions); + return NEED_MORE; + } + DBG1(DBG_TLS, "received TLS '%N' extension", + tls_extension_names, extension); + DBG3(DBG_TLS, "%B", &ext); + switch (extension) + { + case TLS_EXT_SIGNATURE_ALGORITHMS: + this->hashsig = chunk_clone(ext); + break; + case TLS_EXT_ELLIPTIC_CURVES: + this->curves_received = TRUE; + this->curves = chunk_clone(ext); + break; + default: + break; + } + } + extensions->destroy(extensions); + } + + memcpy(this->client_random, random.ptr, sizeof(this->client_random)); + + if (!this->tls->set_version(this->tls, version)) + { + DBG1(DBG_TLS, "negotiated version %N not supported", + tls_version_names, version); + this->alert->add(this->alert, TLS_FATAL, TLS_PROTOCOL_VERSION); + return NEED_MORE; + } + count = ciphers.len / sizeof(u_int16_t); + suites = alloca(count * sizeof(tls_cipher_suite_t)); + DBG2(DBG_TLS, "received %d TLS cipher suites:", count); + for (i = 0; i < count; i++) + { + suites[i] = untoh16(&ciphers.ptr[i * sizeof(u_int16_t)]); + DBG2(DBG_TLS, " %N", tls_cipher_suite_names, suites[i]); + } + + if (!select_suite_and_key(this, suites, count)) + { + this->alert->add(this->alert, TLS_FATAL, TLS_HANDSHAKE_FAILURE); + return NEED_MORE; + } + DBG1(DBG_TLS, "negotiated TLS version %N with suite %N", + tls_version_names, this->tls->get_version(this->tls), + tls_cipher_suite_names, this->suite); + this->client_version = version; + 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)) + { + DBG1(DBG_TLS, "certificate message header invalid"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + return NEED_MORE; + } + certs = tls_reader_create(data); + while (certs->remaining(certs)) + { + if (!certs->read_data24(certs, &data)) + { + DBG1(DBG_TLS, "certificate message invalid"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + certs->destroy(certs); + return NEED_MORE; + } + 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_TLS, "received TLS peer certificate '%Y'", + cert->get_subject(cert)); + first = FALSE; + } + else + { + DBG1(DBG_TLS, "received TLS intermediate certificate '%Y'", + cert->get_subject(cert)); + this->peer_auth->add(this->peer_auth, AUTH_HELPER_IM_CERT, cert); + } + } + else + { + DBG1(DBG_TLS, "parsing TLS certificate failed, skipped"); + this->alert->add(this->alert, TLS_WARNING, TLS_BAD_CERTIFICATE); + } + } + certs->destroy(certs); + this->state = STATE_CERT_RECEIVED; + return NEED_MORE; +} + +/** + * Process Client Key Exchange, using premaster encryption + */ +static status_t process_key_exchange_encrypted(private_tls_server_t *this, + tls_reader_t *reader) +{ + chunk_t encrypted, decrypted; + char premaster[48]; + rng_t *rng; + + this->crypto->append_handshake(this->crypto, + TLS_CLIENT_KEY_EXCHANGE, reader->peek(reader)); + + if (!reader->read_data16(reader, &encrypted)) + { + DBG1(DBG_TLS, "received invalid Client Key Exchange"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + return NEED_MORE; + } + + htoun16(premaster, this->client_version); + /* pre-randomize premaster for failure cases */ + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_TLS, "creating RNG failed"); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return NEED_MORE; + } + rng->get_bytes(rng, sizeof(premaster) - 2, premaster + 2); + rng->destroy(rng); + + if (this->private && + this->private->decrypt(this->private, + ENCRYPT_RSA_PKCS1, encrypted, &decrypted)) + { + if (decrypted.len == sizeof(premaster) && + untoh16(decrypted.ptr) == this->client_version) + { + memcpy(premaster + 2, decrypted.ptr + 2, sizeof(premaster) - 2); + } + else + { + DBG1(DBG_TLS, "decrypted premaster has invalid length/version"); + } + chunk_clear(&decrypted); + } + else + { + DBG1(DBG_TLS, "decrypting Client Key Exchange failed"); + } + + this->crypto->derive_secrets(this->crypto, chunk_from_thing(premaster), + chunk_from_thing(this->client_random), + chunk_from_thing(this->server_random)); + + this->state = STATE_KEY_EXCHANGE_RECEIVED; + return NEED_MORE; +} + +/** + * Process client key exchange, using DHE exchange + */ +static status_t process_key_exchange_dhe(private_tls_server_t *this, + tls_reader_t *reader) +{ + chunk_t premaster, pub; + bool ec; + + this->crypto->append_handshake(this->crypto, + TLS_CLIENT_KEY_EXCHANGE, reader->peek(reader)); + + ec = diffie_hellman_group_is_ec(this->dh->get_dh_group(this->dh)); + if ((ec && !reader->read_data8(reader, &pub)) || + (!ec && (!reader->read_data16(reader, &pub) || pub.len == 0))) + { + DBG1(DBG_TLS, "received invalid Client Key Exchange"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + return NEED_MORE; + } + + if (ec) + { + if (pub.ptr[0] != TLS_ANSI_UNCOMPRESSED) + { + DBG1(DBG_TLS, "DH point format '%N' not supported", + tls_ansi_point_format_names, pub.ptr[0]); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return NEED_MORE; + } + pub = chunk_skip(pub, 1); + } + this->dh->set_other_public_value(this->dh, pub); + if (this->dh->get_shared_secret(this->dh, &premaster) != SUCCESS) + { + DBG1(DBG_TLS, "calculating premaster from DH failed"); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return NEED_MORE; + } + + 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 Client Key Exchange + */ +static status_t process_key_exchange(private_tls_server_t *this, + tls_reader_t *reader) +{ + if (this->dh) + { + return process_key_exchange_dhe(this, reader); + } + return process_key_exchange_encrypted(this, reader); +} + +/** + * 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_TLS, "signature verification failed, trying another key"); + } + enumerator->destroy(enumerator); + + if (!verified) + { + DBG1(DBG_TLS, "no trusted certificate found for '%Y' to verify TLS peer", + this->peer); + this->alert->add(this->alert, TLS_FATAL, TLS_CERTIFICATE_UNKNOWN); + return NEED_MORE; + } + + 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_TLS, "received client finished too short"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR); + return NEED_MORE; + } + if (!this->crypto->calculate_finished(this->crypto, "client finished", buf)) + { + DBG1(DBG_TLS, "calculating client finished failed"); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return NEED_MORE; + } + if (!chunk_equals(received, chunk_from_thing(buf))) + { + DBG1(DBG_TLS, "received client finished invalid"); + this->alert->add(this->alert, TLS_FATAL, TLS_DECRYPT_ERROR); + return NEED_MORE; + } + + 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); + } + if (this->peer) + { + expected = TLS_CERTIFICATE; + break; + } + /* otherwise fall through to next state */ + 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); + } + if (this->peer) + { + expected = TLS_CERTIFICATE_VERIFY; + break; + } + else + { + return INVALID_STATE; + } + case STATE_CIPHERSPEC_CHANGED_IN: + if (type == TLS_FINISHED) + { + return process_finished(this, reader); + } + expected = TLS_FINISHED; + break; + default: + DBG1(DBG_TLS, "TLS %N not expected in current state", + tls_handshake_type_names, type); + this->alert->add(this->alert, TLS_FATAL, TLS_UNEXPECTED_MESSAGE); + return NEED_MORE; + } + DBG1(DBG_TLS, "TLS %N expected, but received %N", + tls_handshake_type_names, expected, tls_handshake_type_names, type); + this->alert->add(this->alert, TLS_FATAL, TLS_UNEXPECTED_MESSAGE); + return NEED_MORE; +} + +/** + * Send ServerHello message + */ +static status_t send_server_hello(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer) +{ + tls_version_t version; + rng_t *rng; + + htoun32(&this->server_random, time(NULL)); + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_TLS, "no suitable RNG found to generate server random"); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return FAILED; + } + rng->get_bytes(rng, sizeof(this->server_random) - 4, this->server_random + 4); + rng->destroy(rng); + + /* TLS version */ + version = this->tls->get_version(this->tls); + writer->write_uint16(writer, version); + 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 TLS cipher 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; + + /* 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_TLS, "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_TLS, "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, *supported; + enumerator_t *enumerator; + certificate_t *cert; + x509_t *x509; + identification_t *id; + + supported = tls_writer_create(4); + /* we propose both RSA and ECDSA */ + supported->write_uint8(supported, TLS_RSA_SIGN); + supported->write_uint8(supported, TLS_ECDSA_SIGN); + writer->write_data8(writer, supported->get_buf(supported)); + supported->destroy(supported); + if (this->tls->get_version(this->tls) >= TLS_1_2) + { + this->crypto->get_signature_algorithms(this->crypto, writer); + } + + authorities = tls_writer_create(64); + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + CERT_X509, KEY_RSA, NULL, TRUE); + while (enumerator->enumerate(enumerator, &cert)) + { + x509 = (x509_t*)cert; + if (x509->get_flags(x509) & X509_CA) + { + id = cert->get_subject(cert); + DBG1(DBG_TLS, "sending TLS cert request for '%Y'", id); + 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; +} + +/** + * Get the TLS curve of a given EC DH group + */ +static tls_named_curve_t ec_group_to_curve(private_tls_server_t *this, + diffie_hellman_group_t group) +{ + diffie_hellman_group_t current; + tls_named_curve_t curve; + enumerator_t *enumerator; + + enumerator = this->crypto->create_ec_enumerator(this->crypto); + while (enumerator->enumerate(enumerator, ¤t, &curve)) + { + if (current == group) + { + enumerator->destroy(enumerator); + return curve; + } + } + enumerator->destroy(enumerator); + return 0; +} + +/** + * Check if the peer supports a given TLS curve + */ +bool peer_supports_curve(private_tls_server_t *this, tls_named_curve_t curve) +{ + tls_reader_t *reader; + u_int16_t current; + + if (!this->curves_received) + { /* none received, assume yes */ + return TRUE; + } + reader = tls_reader_create(this->curves); + while (reader->remaining(reader) && reader->read_uint16(reader, ¤t)) + { + if (current == curve) + { + reader->destroy(reader); + return TRUE; + } + } + reader->destroy(reader); + return FALSE; +} + +/** + * Try to find a curve supported by both, client and server + */ +static bool find_supported_curve(private_tls_server_t *this, + tls_named_curve_t *curve) +{ + tls_named_curve_t current; + enumerator_t *enumerator; + + enumerator = this->crypto->create_ec_enumerator(this->crypto); + while (enumerator->enumerate(enumerator, NULL, ¤t)) + { + if (peer_supports_curve(this, current)) + { + *curve = current; + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + +/** + * Send Server key Exchange + */ +static status_t send_server_key_exchange(private_tls_server_t *this, + tls_handshake_type_t *type, tls_writer_t *writer, + diffie_hellman_group_t group) +{ + diffie_hellman_params_t *params = NULL; + tls_named_curve_t curve; + chunk_t chunk; + + if (diffie_hellman_group_is_ec(group)) + { + curve = ec_group_to_curve(this, group); + if (!curve || (!peer_supports_curve(this, curve) && + !find_supported_curve(this, &curve))) + { + DBG1(DBG_TLS, "no EC group supported by client and server"); + this->alert->add(this->alert, TLS_FATAL, TLS_HANDSHAKE_FAILURE); + return NEED_MORE; + } + DBG2(DBG_TLS, "selected ECDH group %N", tls_named_curve_names, curve); + writer->write_uint8(writer, TLS_ECC_NAMED_CURVE); + writer->write_uint16(writer, curve); + } + else + { + params = diffie_hellman_get_params(group); + if (!params) + { + DBG1(DBG_TLS, "no parameters found for DH group %N", + diffie_hellman_group_names, group); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return NEED_MORE; + } + DBG2(DBG_TLS, "selected DH group %N", diffie_hellman_group_names, group); + writer->write_data16(writer, params->prime); + writer->write_data16(writer, params->generator); + } + this->dh = lib->crypto->create_dh(lib->crypto, group); + if (!this->dh) + { + DBG1(DBG_TLS, "DH group %N not supported", + diffie_hellman_group_names, group); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + return NEED_MORE; + } + this->dh->get_my_public_value(this->dh, &chunk); + if (params) + { + writer->write_data16(writer, chunk); + } + else + { /* ECP uses 8bit length header only, but a point format */ + writer->write_uint8(writer, chunk.len + 1); + writer->write_uint8(writer, TLS_ANSI_UNCOMPRESSED); + writer->write_data(writer, chunk); + } + free(chunk.ptr); + + chunk = chunk_cat("ccc", chunk_from_thing(this->client_random), + chunk_from_thing(this->server_random), writer->get_buf(writer)); + if (!this->private || !this->crypto->sign(this->crypto, this->private, + writer, chunk, this->hashsig)) + { + DBG1(DBG_TLS, "signing DH parameters failed"); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + free(chunk.ptr); + return NEED_MORE; + } + free(chunk.ptr); + *type = TLS_SERVER_KEY_EXCHANGE; + this->state = STATE_KEY_EXCHANGE_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_TLS, "calculating server finished data failed"); + this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR); + 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) +{ + diffie_hellman_group_t group; + + 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: + group = this->crypto->get_dh_group(this->crypto); + if (group) + { + return send_server_key_exchange(this, type, writer, group); + } + /* otherwise fall through to next state */ + case STATE_KEY_EXCHANGE_SENT: + if (this->peer) + { + return send_certificate_request(this, type, writer); + } + /* otherwise fall through to next state */ + 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->peer && this->state == STATE_CERT_VERIFY_RECEIVED) || + (!this->peer && this->state == STATE_KEY_EXCHANGE_RECEIVED)) + { + this->crypto->change_cipher(this->crypto, TRUE); + this->state = STATE_CIPHERSPEC_CHANGED_IN; + return TRUE; + } + return FALSE; +} + +METHOD(tls_handshake_t, finished, bool, + private_tls_server_t *this) +{ + return this->state == STATE_FINISHED_SENT; +} + +METHOD(tls_handshake_t, destroy, void, + private_tls_server_t *this) +{ + DESTROY_IF(this->private); + DESTROY_IF(this->dh); + this->peer_auth->destroy(this->peer_auth); + this->server_auth->destroy(this->server_auth); + free(this->hashsig.ptr); + free(this->curves.ptr); + free(this); +} + +/** + * See header + */ +tls_server_t *tls_server_create(tls_t *tls, + tls_crypto_t *crypto, tls_alert_t *alert, + 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, + .finished = _finished, + .destroy = _destroy, + }, + }, + .tls = tls, + .crypto = crypto, + .alert = alert, + .server = server, + .peer = peer, + .state = STATE_INIT, + .peer_auth = auth_cfg_create(), + .server_auth = auth_cfg_create(), + ); + + return &this->public; +} |