This prevents a crash due to a null-pointer dereference when processing an empty ECDH public key. The previous length check only applied in the `!ec` case, so in the `ec` case, the access to `pub.ptr[0]` was unguarded. If a crafted TLS record ends with an empty ClientKeyExchange, then `read_data8` sets `pub` to `chunk_empty`, causing a null-pointer dereference. Note that if some data follows the empty ClientKeyExchange, this just causes a 1-byte out-of-bounds read that has no further effect as the TLS session is aborted immediately. Either because the read value doesn't equal TLS_ANSI_UNCOMPRESSED or because the empty public key is rejected by `set_public_key()`. The referenced commit that introduced the pointer access, added the check for `pub.len` specifically to the `!ec` case, while the pointer access was initially unconditional (probably because the code was just copied from `tls_peer.c` which processes ECDH public keys in a separate function, so there was no `ec` flag). The latter was fixed a couple of days later with7b3c01845f("Read the compression type byte for EC groups, only"). However, that commit didn't change the length check. Anyway, it's possible that the original intention was to add the check to the `ec` case on the previous line, or that there was some confusion with the parenthesis and something like the current code was intended to begin with. Fixes:e6cce7ff0d("Prepend point format to ECDH public key") Fixes: CVE-2026-35332
1905 lines
49 KiB
C
1905 lines
49 KiB
C
/*
|
|
* Copyright (C) 2020-2021 Pascal Knecht
|
|
* Copyright (C) 2010 Martin Willi
|
|
*
|
|
* Copyright (C) secunet Security Networks 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 <utils/debug.h>
|
|
#include <credentials/certificates/x509.h>
|
|
#include <collections/array.h>
|
|
|
|
typedef struct private_tls_server_t private_tls_server_t;
|
|
|
|
/**
|
|
* Size of a session ID
|
|
*/
|
|
#define SESSION_ID_SIZE 16
|
|
|
|
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,
|
|
/* new states in TLS 1.3 */
|
|
STATE_ENCRYPTED_EXTENSIONS_SENT,
|
|
STATE_CERT_VERIFY_SENT,
|
|
STATE_KEY_UPDATE_REQUESTED,
|
|
STATE_KEY_UPDATE_SENT,
|
|
STATE_FINISHED_SENT_KEY_SWITCHED,
|
|
} 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
|
|
*/
|
|
key_exchange_t *dh;
|
|
|
|
/**
|
|
* Requested DH group
|
|
*/
|
|
tls_named_group_t requested_curve;
|
|
|
|
/**
|
|
* Selected TLS cipher suite
|
|
*/
|
|
tls_cipher_suite_t suite;
|
|
|
|
/**
|
|
* Offered TLS version of the client
|
|
*/
|
|
tls_version_t client_version;
|
|
|
|
/**
|
|
* TLS session identifier
|
|
*/
|
|
chunk_t session;
|
|
|
|
/**
|
|
* Do we resume a session?
|
|
*/
|
|
bool resume;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Whether to include CAs in CertificateRequest messages
|
|
*/
|
|
bool send_certreq_authorities;
|
|
};
|
|
|
|
/**
|
|
* Find a trusted public key to encrypt/verify key exchange data
|
|
*/
|
|
public_key_t *tls_find_public_key(auth_cfg_t *peer_auth, identification_t *id)
|
|
{
|
|
public_key_t *public = NULL, *current;
|
|
certificate_t *cert, *found;
|
|
key_type_t key_type = KEY_ANY;
|
|
enumerator_t *enumerator;
|
|
auth_cfg_t *auth;
|
|
|
|
cert = peer_auth->get(peer_auth, AUTH_HELPER_SUBJECT_CERT);
|
|
if (cert)
|
|
{
|
|
current = cert->get_public_key(cert);
|
|
if (current)
|
|
{
|
|
key_type = current->get_type(current);
|
|
current->destroy(current);
|
|
}
|
|
enumerator = lib->credmgr->create_public_enumerator(lib->credmgr,
|
|
key_type, id, peer_auth, TRUE);
|
|
while (enumerator->enumerate(enumerator, ¤t, &auth))
|
|
{
|
|
found = auth->get(auth, AUTH_RULE_SUBJECT_CERT);
|
|
if (found && cert->equals(cert, found))
|
|
{
|
|
public = current->get_ref(current);
|
|
peer_auth->merge(peer_auth, auth, FALSE);
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
return public;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
tls_version_t version_min, version_max;
|
|
private_key_t *key;
|
|
auth_cfg_t *auth;
|
|
enumerator_t *enumerator;
|
|
|
|
version_min = this->tls->get_version_min(this->tls);
|
|
version_max = this->tls->get_version_max(this->tls);
|
|
enumerator = tls_create_private_key_enumerator(version_min, version_max,
|
|
this->hashsig, this->server);
|
|
if (!enumerator)
|
|
{
|
|
DBG1(DBG_TLS, "no common signature algorithms found");
|
|
return FALSE;
|
|
}
|
|
if (!enumerator->enumerate(enumerator, &key, &auth))
|
|
{
|
|
DBG1(DBG_TLS, "no usable TLS server certificate found for '%Y'",
|
|
this->server);
|
|
enumerator->destroy(enumerator);
|
|
return FALSE;
|
|
}
|
|
|
|
if (version_max >= TLS_1_3)
|
|
{
|
|
this->suite = this->crypto->select_cipher_suite(this->crypto, suites,
|
|
count, KEY_ANY);
|
|
}
|
|
else
|
|
{
|
|
this->suite = this->crypto->select_cipher_suite(this->crypto, suites,
|
|
count, key->get_type(key));
|
|
while (!this->suite &&
|
|
enumerator->enumerate(enumerator, &key, &auth))
|
|
{ /* find a key and cipher suite for one of the remaining key types */
|
|
this->suite = this->crypto->select_cipher_suite(this->crypto,
|
|
suites, count,
|
|
key->get_type(key));
|
|
}
|
|
}
|
|
if (!this->suite)
|
|
{
|
|
DBG1(DBG_TLS, "received cipher suites or signature schemes unacceptable");
|
|
enumerator->destroy(enumerator);
|
|
return FALSE;
|
|
}
|
|
DBG1(DBG_TLS, "using key of type %N", key_type_names, key->get_type(key));
|
|
DESTROY_IF(this->private);
|
|
this->private = key->get_ref(key);
|
|
this->server_auth->purge(this->server_auth, FALSE);
|
|
this->server_auth->merge(this->server_auth, auth, FALSE);
|
|
enumerator->destroy(enumerator);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Check if the peer supports a given TLS curve
|
|
*/
|
|
static bool peer_supports_curve(private_tls_server_t *this,
|
|
tls_named_group_t curve)
|
|
{
|
|
bio_reader_t *reader;
|
|
uint16_t current;
|
|
|
|
if (!this->curves_received)
|
|
{ /* none received, assume yes */
|
|
return TRUE;
|
|
}
|
|
reader = bio_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;
|
|
}
|
|
|
|
/**
|
|
* TLS 1.3 key exchange key share
|
|
*/
|
|
typedef struct {
|
|
uint16_t curve;
|
|
chunk_t key_share;
|
|
} key_share_t;
|
|
|
|
/**
|
|
* Check if peer sent a key share of a given TLS named DH group
|
|
*/
|
|
static bool peer_offered_curve(array_t *key_shares, tls_named_group_t curve,
|
|
key_share_t *out)
|
|
{
|
|
key_share_t peer;
|
|
int i;
|
|
|
|
for (i = 0; i < array_count(key_shares); i++)
|
|
{
|
|
array_get(key_shares, i, &peer);
|
|
if (curve == peer.curve)
|
|
{
|
|
if (out)
|
|
{
|
|
*out = peer;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Check if client is currently retrying to connect to the server.
|
|
*/
|
|
static bool retrying(private_tls_server_t *this)
|
|
{
|
|
return this->state == STATE_INIT && this->requested_curve;
|
|
}
|
|
|
|
/**
|
|
* Process client hello message
|
|
*/
|
|
static status_t process_client_hello(private_tls_server_t *this,
|
|
bio_reader_t *reader)
|
|
{
|
|
uint16_t legacy_version = 0, version = 0, extension_type = 0;
|
|
chunk_t random, session, ciphers, versions = chunk_empty, compression;
|
|
chunk_t ext = chunk_empty, key_shares = chunk_empty;
|
|
key_share_t peer = {0};
|
|
chunk_t extension_data = chunk_empty;
|
|
bio_reader_t *extensions, *extension;
|
|
tls_version_t original_version_max;
|
|
int count, i;
|
|
rng_t *rng;
|
|
|
|
this->crypto->append_handshake(this->crypto,
|
|
TLS_CLIENT_HELLO, reader->peek(reader));
|
|
|
|
if (!reader->read_uint16(reader, &legacy_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;
|
|
}
|
|
|
|
/* before we do anything version-related, determine our supported suites
|
|
* as that might change the min./max. versions */
|
|
this->crypto->get_cipher_suites(this->crypto, NULL);
|
|
|
|
extensions = bio_reader_create(ext);
|
|
while (extensions->remaining(extensions))
|
|
{
|
|
if (!extensions->read_uint16(extensions, &extension_type) ||
|
|
!extensions->read_data16(extensions, &extension_data))
|
|
{
|
|
DBG1(DBG_TLS, "received invalid ClientHello Extensions");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
extensions->destroy(extensions);
|
|
return NEED_MORE;
|
|
}
|
|
extension = bio_reader_create(extension_data);
|
|
DBG2(DBG_TLS, "received TLS '%N' extension",
|
|
tls_extension_names, extension_type);
|
|
DBG3(DBG_TLS, "%B", &extension_data);
|
|
switch (extension_type)
|
|
{
|
|
case TLS_EXT_SIGNATURE_ALGORITHMS:
|
|
if (!extension->read_data16(extension, &extension_data))
|
|
{
|
|
DBG1(DBG_TLS, "invalid %N extension",
|
|
tls_extension_names, extension_type);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
extensions->destroy(extensions);
|
|
extension->destroy(extension);
|
|
return NEED_MORE;
|
|
}
|
|
chunk_free(&this->hashsig);
|
|
this->hashsig = chunk_clone(extension_data);
|
|
break;
|
|
case TLS_EXT_SUPPORTED_GROUPS:
|
|
if (!extension->read_data16(extension, &extension_data))
|
|
{
|
|
DBG1(DBG_TLS, "invalid %N extension",
|
|
tls_extension_names, extension_type);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
extensions->destroy(extensions);
|
|
extension->destroy(extension);
|
|
return NEED_MORE;
|
|
}
|
|
chunk_free(&this->curves);
|
|
this->curves_received = TRUE;
|
|
this->curves = chunk_clone(extension_data);
|
|
break;
|
|
case TLS_EXT_SUPPORTED_VERSIONS:
|
|
if (!extension->read_data8(extension, &versions))
|
|
{
|
|
DBG1(DBG_TLS, "invalid %N extension",
|
|
tls_extension_names, extension_type);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
extensions->destroy(extensions);
|
|
extension->destroy(extension);
|
|
return NEED_MORE;
|
|
}
|
|
break;
|
|
case TLS_EXT_KEY_SHARE:
|
|
if (!extension->read_data16(extension, &key_shares))
|
|
{
|
|
DBG1(DBG_TLS, "invalid %N extension",
|
|
tls_extension_names, extension_type);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
extensions->destroy(extensions);
|
|
extension->destroy(extension);
|
|
return NEED_MORE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
extension->destroy(extension);
|
|
}
|
|
extensions->destroy(extensions);
|
|
|
|
if (this->tls->get_version_max(this->tls) >= TLS_1_3 && !this->hashsig.len)
|
|
{
|
|
DBG1(DBG_TLS, "no %N extension received", tls_extension_names,
|
|
TLS_EXT_SIGNATURE_ALGORITHMS);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_MISSING_EXTENSION);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
memcpy(this->client_random, random.ptr, sizeof(this->client_random));
|
|
|
|
htoun32(&this->server_random, time(NULL));
|
|
rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
|
|
if (!rng ||
|
|
!rng->get_bytes(rng, sizeof(this->server_random) - 4,
|
|
this->server_random + 4))
|
|
{
|
|
DBG1(DBG_TLS, "failed to generate server random");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
DESTROY_IF(rng);
|
|
return NEED_MORE;
|
|
}
|
|
rng->destroy(rng);
|
|
|
|
original_version_max = this->tls->get_version_max(this->tls);
|
|
|
|
if (versions.len)
|
|
{
|
|
bio_reader_t *client_versions;
|
|
|
|
client_versions = bio_reader_create(versions);
|
|
while (client_versions->remaining(client_versions))
|
|
{
|
|
if (client_versions->read_uint16(client_versions, &version))
|
|
{
|
|
if (this->tls->set_version(this->tls, version, version))
|
|
{
|
|
this->client_version = version;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
client_versions->destroy(client_versions);
|
|
}
|
|
else
|
|
{
|
|
version = legacy_version;
|
|
if (this->tls->set_version(this->tls, version, version))
|
|
{
|
|
this->client_version = version;
|
|
}
|
|
}
|
|
|
|
/* downgrade protection (see RFC 8446, section 4.1.3) */
|
|
if ((original_version_max == TLS_1_3 && version < TLS_1_3) ||
|
|
(original_version_max == TLS_1_2 && version < TLS_1_2))
|
|
{
|
|
chunk_t downgrade_protection = tls_downgrade_protection_tls11;
|
|
|
|
if (version == TLS_1_2)
|
|
{
|
|
downgrade_protection = tls_downgrade_protection_tls12;
|
|
}
|
|
memcpy(&this->server_random[24], downgrade_protection.ptr,
|
|
downgrade_protection.len);
|
|
}
|
|
|
|
if (!this->client_version)
|
|
{
|
|
DBG1(DBG_TLS, "proposed version %N not supported", tls_version_names,
|
|
version);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_PROTOCOL_VERSION);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
this->suite = this->crypto->resume_session(this->crypto, session,
|
|
this->peer,
|
|
chunk_from_thing(this->client_random),
|
|
chunk_from_thing(this->server_random));
|
|
}
|
|
|
|
if (this->suite && !retrying(this))
|
|
{
|
|
this->session = chunk_clone(session);
|
|
this->resume = TRUE;
|
|
DBG1(DBG_TLS, "resumed %N using suite %N",
|
|
tls_version_names, this->tls->get_version_max(this->tls),
|
|
tls_cipher_suite_names, this->suite);
|
|
}
|
|
else
|
|
{
|
|
tls_cipher_suite_t original_suite = this->suite;
|
|
tls_cipher_suite_t *suites;
|
|
|
|
count = ciphers.len / sizeof(uint16_t);
|
|
suites = malloc(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(uint16_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);
|
|
free(suites);
|
|
return NEED_MORE;
|
|
}
|
|
free(suites);
|
|
if (retrying(this) && original_suite != this->suite)
|
|
{
|
|
DBG1(DBG_TLS, "selected %N instead of %N during retry",
|
|
tls_cipher_suite_names, this->suite, tls_cipher_suite_names,
|
|
original_suite);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_ILLEGAL_PARAMETER);
|
|
return NEED_MORE;
|
|
}
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
|
|
if (!rng ||
|
|
!rng->allocate_bytes(rng, SESSION_ID_SIZE, &this->session))
|
|
{
|
|
DBG1(DBG_TLS, "generating TLS session identifier failed, skipped");
|
|
}
|
|
DESTROY_IF(rng);
|
|
}
|
|
else
|
|
{
|
|
chunk_free(&this->session);
|
|
this->session = chunk_clone(session);
|
|
}
|
|
DBG1(DBG_TLS, "negotiated %N using suite %N",
|
|
tls_version_names, this->tls->get_version_max(this->tls),
|
|
tls_cipher_suite_names, this->suite);
|
|
}
|
|
|
|
if (this->tls->get_version_max(this->tls) >= TLS_1_3)
|
|
{
|
|
key_exchange_method_t group;
|
|
tls_named_group_t curve, requesting_curve = 0;
|
|
enumerator_t *enumerator;
|
|
array_t *peer_key_shares;
|
|
|
|
peer_key_shares = array_create(sizeof(key_share_t), 1);
|
|
extension = bio_reader_create(key_shares);
|
|
while (extension->remaining(extension))
|
|
{
|
|
if (!extension->read_uint16(extension, &peer.curve) ||
|
|
!extension->read_data16(extension, &peer.key_share) ||
|
|
!peer.key_share.len)
|
|
{
|
|
DBG1(DBG_TLS, "invalid %N extension",
|
|
tls_extension_names, extension_type);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
extension->destroy(extension);
|
|
array_destroy(peer_key_shares);
|
|
return NEED_MORE;
|
|
}
|
|
array_insert(peer_key_shares, ARRAY_TAIL, &peer);
|
|
}
|
|
extension->destroy(extension);
|
|
|
|
enumerator = this->crypto->create_ec_enumerator(this->crypto);
|
|
while (enumerator->enumerate(enumerator, &group, &curve))
|
|
{
|
|
if (!requesting_curve &&
|
|
peer_supports_curve(this, curve) &&
|
|
!peer_offered_curve(peer_key_shares, curve, NULL))
|
|
{
|
|
requesting_curve = curve;
|
|
}
|
|
if (peer_supports_curve(this, curve) &&
|
|
peer_offered_curve(peer_key_shares, curve, &peer))
|
|
{
|
|
DBG1(DBG_TLS, "using key exchange %N",
|
|
tls_named_group_names, curve);
|
|
this->dh = lib->crypto->create_ke(lib->crypto, group);
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
array_destroy(peer_key_shares);
|
|
|
|
if (!this->dh)
|
|
{
|
|
if (retrying(this))
|
|
{
|
|
DBG1(DBG_TLS, "already replied with a hello retry request");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_UNEXPECTED_MESSAGE);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
if (!requesting_curve)
|
|
{
|
|
DBG1(DBG_TLS, "no mutual supported group in client hello");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_ILLEGAL_PARAMETER);
|
|
return NEED_MORE;
|
|
}
|
|
this->requested_curve = requesting_curve;
|
|
|
|
if (!this->crypto->hash_handshake(this->crypto, NULL))
|
|
{
|
|
DBG1(DBG_TLS, "failed to hash handshake messages");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (peer.key_share.len &&
|
|
peer.curve != TLS_CURVE25519 &&
|
|
peer.curve != TLS_CURVE448)
|
|
{ /* classic format (see RFC 8446, section 4.2.8.2) */
|
|
if (peer.key_share.ptr[0] != TLS_ANSI_UNCOMPRESSED)
|
|
{
|
|
DBG1(DBG_TLS, "DH point format '%N' not supported",
|
|
tls_ansi_point_format_names, peer.key_share.ptr[0]);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
peer.key_share = chunk_skip(peer.key_share, 1);
|
|
}
|
|
if (!peer.key_share.len ||
|
|
!this->dh->set_public_key(this->dh, peer.key_share))
|
|
{
|
|
DBG1(DBG_TLS, "DH key derivation failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_HANDSHAKE_FAILURE);
|
|
return NEED_MORE;
|
|
}
|
|
this->requested_curve = 0;
|
|
}
|
|
}
|
|
|
|
this->state = STATE_HELLO_RECEIVED;
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/**
|
|
* Process certificate
|
|
*/
|
|
static status_t process_certificate(private_tls_server_t *this,
|
|
bio_reader_t *reader)
|
|
{
|
|
certificate_t *cert;
|
|
bio_reader_t *certs;
|
|
chunk_t data;
|
|
bool first = TRUE;
|
|
|
|
this->crypto->append_handshake(this->crypto,
|
|
TLS_CERTIFICATE, reader->peek(reader));
|
|
|
|
if (this->tls->get_version_max(this->tls) > TLS_1_2)
|
|
{
|
|
if (!reader->read_data8(reader, &data))
|
|
{
|
|
DBG1(DBG_TLS, "certificate request context invalid");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
}
|
|
|
|
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 = bio_reader_create(data);
|
|
if (!certs->remaining(certs))
|
|
{
|
|
if (this->tls->get_flags(this->tls) & TLS_FLAG_CLIENT_AUTH_OPTIONAL)
|
|
{
|
|
/* client authentication is not required so we clear the identity */
|
|
DESTROY_IF(this->peer);
|
|
this->peer = NULL;
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_TLS, "no certificate sent by peer");
|
|
this->alert->add(this->alert, TLS_FATAL,
|
|
this->tls->get_version_max(this->tls) > TLS_1_2 ?
|
|
TLS_CERTIFICATE_REQUIRED : TLS_HANDSHAKE_FAILURE);
|
|
return NEED_MORE;
|
|
}
|
|
}
|
|
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;
|
|
if (this->peer && this->peer->get_type(this->peer) == ID_ANY)
|
|
{
|
|
this->peer->destroy(this->peer);
|
|
this->peer = cert->get_subject(cert);
|
|
this->peer = this->peer->clone(this->peer);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
if (this->tls->get_version_max(this->tls) > TLS_1_2)
|
|
{
|
|
if (!certs->read_data16(certs, &data))
|
|
{
|
|
DBG1(DBG_TLS, "failed to read extensions of CertificateEntry");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
bio_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 || !rng->get_bytes(rng, sizeof(premaster) - 2, premaster + 2))
|
|
{
|
|
DBG1(DBG_TLS, "failed to generate premaster secret");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
DESTROY_IF(rng);
|
|
return NEED_MORE;
|
|
}
|
|
rng->destroy(rng);
|
|
|
|
if (this->private &&
|
|
this->private->decrypt(this->private, ENCRYPT_RSA_PKCS1, NULL,
|
|
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");
|
|
}
|
|
|
|
if (!this->crypto->derive_secrets(this->crypto, chunk_from_thing(premaster),
|
|
this->session, this->peer,
|
|
chunk_from_thing(this->client_random),
|
|
chunk_from_thing(this->server_random)))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
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,
|
|
bio_reader_t *reader)
|
|
{
|
|
chunk_t premaster, pub;
|
|
key_exchange_method_t group;
|
|
bool ec;
|
|
|
|
this->crypto->append_handshake(this->crypto,
|
|
TLS_CLIENT_KEY_EXCHANGE, reader->peek(reader));
|
|
|
|
group = this->dh->get_method(this->dh);
|
|
ec = key_exchange_is_ecdh(group);
|
|
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 &&
|
|
group != CURVE_25519 &&
|
|
group != CURVE_448)
|
|
{
|
|
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);
|
|
}
|
|
if (!this->dh->set_public_key(this->dh, pub))
|
|
{
|
|
DBG1(DBG_TLS, "applying DH public value failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
if (!this->dh->get_shared_secret(this->dh, &premaster))
|
|
{
|
|
DBG1(DBG_TLS, "calculating premaster from DH failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
if (!this->crypto->derive_secrets(this->crypto, premaster,
|
|
this->session, this->peer,
|
|
chunk_from_thing(this->client_random),
|
|
chunk_from_thing(this->server_random)))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
chunk_clear(&premaster);
|
|
return NEED_MORE;
|
|
}
|
|
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,
|
|
bio_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,
|
|
bio_reader_t *reader)
|
|
{
|
|
public_key_t *public;
|
|
chunk_t msg;
|
|
|
|
public = tls_find_public_key(this->peer_auth, this->peer);
|
|
if (!public)
|
|
{
|
|
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;
|
|
}
|
|
|
|
msg = reader->peek(reader);
|
|
if (!this->crypto->verify_handshake(this->crypto, public, reader))
|
|
{
|
|
public->destroy(public);
|
|
DBG1(DBG_TLS, "signature verification failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECRYPT_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
public->destroy(public);
|
|
this->state = STATE_CERT_VERIFY_RECEIVED;
|
|
this->crypto->append_handshake(this->crypto, TLS_CERTIFICATE_VERIFY, msg);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/**
|
|
* Process finished message
|
|
*/
|
|
static status_t process_finished(private_tls_server_t *this,
|
|
bio_reader_t *reader)
|
|
{
|
|
chunk_t received, verify_data;
|
|
u_char buf[12];
|
|
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
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_legacy(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;
|
|
}
|
|
verify_data = chunk_from_thing(buf);
|
|
}
|
|
else
|
|
{
|
|
received = reader->peek(reader);
|
|
if (!this->crypto->calculate_finished(this->crypto, FALSE, &verify_data))
|
|
{
|
|
DBG1(DBG_TLS, "calculating client finished failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
this->crypto->change_cipher(this->crypto, TRUE);
|
|
}
|
|
|
|
if (!chunk_equals_const(received, verify_data))
|
|
{
|
|
DBG1(DBG_TLS, "received client finished invalid");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECRYPT_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
if (verify_data.ptr != buf)
|
|
{
|
|
chunk_free(&verify_data);
|
|
}
|
|
|
|
this->crypto->append_handshake(this->crypto, TLS_FINISHED, received);
|
|
this->state = STATE_FINISHED_RECEIVED;
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/**
|
|
* Process KeyUpdate message
|
|
*/
|
|
static status_t process_key_update(private_tls_server_t *this,
|
|
bio_reader_t *reader)
|
|
{
|
|
uint8_t update_requested;
|
|
|
|
if (!reader->read_uint8(reader, &update_requested) ||
|
|
update_requested > 1)
|
|
{
|
|
DBG1(DBG_TLS, "received invalid KeyUpdate");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
if (!this->crypto->update_app_keys(this->crypto, TRUE))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
this->crypto->change_cipher(this->crypto, TRUE);
|
|
|
|
if (update_requested)
|
|
{
|
|
DBG1(DBG_TLS, "client requested KeyUpdate");
|
|
this->state = STATE_KEY_UPDATE_REQUESTED;
|
|
}
|
|
return NEED_MORE;
|
|
}
|
|
|
|
METHOD(tls_handshake_t, process, status_t,
|
|
private_tls_server_t *this, tls_handshake_type_t type, bio_reader_t *reader)
|
|
{
|
|
tls_handshake_type_t expected DBG_UNUSED;
|
|
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (this->state)
|
|
{
|
|
case STATE_INIT:
|
|
if (type == TLS_CLIENT_HELLO)
|
|
{
|
|
return process_client_hello(this, reader);
|
|
}
|
|
expected = TLS_CLIENT_HELLO;
|
|
break;
|
|
case STATE_CIPHERSPEC_CHANGED_IN:
|
|
case STATE_FINISHED_SENT:
|
|
case STATE_FINISHED_SENT_KEY_SWITCHED:
|
|
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_CERTIFICATE_VERIFY)
|
|
{
|
|
return process_cert_verify(this, reader);
|
|
}
|
|
if (this->peer)
|
|
{
|
|
expected = TLS_CERTIFICATE_VERIFY;
|
|
break;
|
|
}
|
|
/* otherwise fall through to next state */
|
|
case STATE_CERT_VERIFY_RECEIVED:
|
|
if (type == TLS_FINISHED)
|
|
{
|
|
return process_finished(this, reader);
|
|
}
|
|
return NEED_MORE;
|
|
case STATE_FINISHED_RECEIVED:
|
|
if (type == TLS_KEY_UPDATE)
|
|
{
|
|
return process_key_update(this, reader);
|
|
}
|
|
return INVALID_STATE;
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Write public key into key share extension
|
|
*/
|
|
bool tls_write_key_share(bio_writer_t **key_share, key_exchange_t *dh)
|
|
{
|
|
bio_writer_t *writer;
|
|
tls_named_group_t curve;
|
|
chunk_t pub;
|
|
|
|
if (!dh)
|
|
{
|
|
return FALSE;
|
|
}
|
|
curve = tls_ec_group_to_curve(dh->get_method(dh));
|
|
if (!curve || !dh->get_public_key(dh, &pub))
|
|
{
|
|
return FALSE;
|
|
}
|
|
*key_share = writer = bio_writer_create(pub.len + 7);
|
|
writer->write_uint16(writer, curve);
|
|
if (curve == TLS_CURVE25519 ||
|
|
curve == TLS_CURVE448)
|
|
{
|
|
writer->write_data16(writer, pub);
|
|
}
|
|
else
|
|
{ /* classic format (see RFC 8446, section 4.2.8.2) */
|
|
writer->write_uint16(writer, pub.len + 1);
|
|
writer->write_uint8(writer, TLS_ANSI_UNCOMPRESSED);
|
|
writer->write_data(writer, pub);
|
|
}
|
|
free(pub.ptr);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Send ServerHello message
|
|
*/
|
|
static status_t send_server_hello(private_tls_server_t *this,
|
|
tls_handshake_type_t *type, bio_writer_t *writer)
|
|
{
|
|
bio_writer_t *key_share, *extensions;
|
|
tls_version_t version;
|
|
|
|
version = this->tls->get_version_max(this->tls);
|
|
|
|
/* cap legacy version at TLS 1.2 for middlebox compatibility */
|
|
writer->write_uint16(writer, min(TLS_1_2, version));
|
|
|
|
if (this->requested_curve)
|
|
{
|
|
writer->write_data(writer, tls_hello_retry_request_magic);
|
|
}
|
|
else
|
|
{
|
|
writer->write_data(writer, chunk_from_thing(this->server_random));
|
|
}
|
|
|
|
/* session identifier if we have one */
|
|
writer->write_data8(writer, this->session);
|
|
|
|
/* add selected TLS cipher suite */
|
|
writer->write_uint16(writer, this->suite);
|
|
|
|
/* NULL compression only */
|
|
writer->write_uint8(writer, 0);
|
|
|
|
if (version >= TLS_1_3)
|
|
{
|
|
extensions = bio_writer_create(32);
|
|
|
|
DBG2(DBG_TLS, "sending extension: %N",
|
|
tls_extension_names, TLS_EXT_SUPPORTED_VERSIONS);
|
|
extensions->write_uint16(extensions, TLS_EXT_SUPPORTED_VERSIONS);
|
|
extensions->write_uint16(extensions, 2);
|
|
extensions->write_uint16(extensions, version);
|
|
|
|
DBG2(DBG_TLS, "sending extension: %N",
|
|
tls_extension_names, TLS_EXT_KEY_SHARE);
|
|
extensions->write_uint16(extensions, TLS_EXT_KEY_SHARE);
|
|
if (this->requested_curve)
|
|
{
|
|
DBG1(DBG_TLS, "requesting key exchange with %N",
|
|
tls_named_group_names, this->requested_curve);
|
|
extensions->write_uint16(extensions, 2);
|
|
extensions->write_uint16(extensions, this->requested_curve);
|
|
}
|
|
else
|
|
{
|
|
if (!tls_write_key_share(&key_share, this->dh))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
extensions->destroy(extensions);
|
|
return NEED_MORE;
|
|
}
|
|
extensions->write_data16(extensions, key_share->get_buf(key_share));
|
|
key_share->destroy(key_share);
|
|
}
|
|
|
|
writer->write_data16(writer, extensions->get_buf(extensions));
|
|
extensions->destroy(extensions);
|
|
}
|
|
|
|
*type = TLS_SERVER_HELLO;
|
|
this->state = this->requested_curve ? STATE_INIT : STATE_HELLO_SENT;
|
|
this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer));
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/**
|
|
* Send encrypted extensions message
|
|
*/
|
|
static status_t send_encrypted_extensions(private_tls_server_t *this,
|
|
tls_handshake_type_t *type,
|
|
bio_writer_t *writer)
|
|
{
|
|
chunk_t shared_secret = chunk_empty;
|
|
|
|
if (!this->dh->get_shared_secret(this->dh, &shared_secret) ||
|
|
!this->crypto->derive_handshake_keys(this->crypto, shared_secret))
|
|
{
|
|
DBG1(DBG_TLS, "DH key derivation failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_HANDSHAKE_FAILURE);
|
|
chunk_clear(&shared_secret);
|
|
return NEED_MORE;
|
|
}
|
|
chunk_clear(&shared_secret);
|
|
|
|
this->crypto->change_cipher(this->crypto, TRUE);
|
|
this->crypto->change_cipher(this->crypto, FALSE);
|
|
|
|
/* currently no extensions are supported */
|
|
writer->write_uint16(writer, 0);
|
|
|
|
*type = TLS_ENCRYPTED_EXTENSIONS;
|
|
this->state = STATE_ENCRYPTED_EXTENSIONS_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, bio_writer_t *writer)
|
|
{
|
|
enumerator_t *enumerator;
|
|
certificate_t *cert;
|
|
auth_rule_t rule;
|
|
bio_writer_t *certs;
|
|
chunk_t data;
|
|
|
|
/* certificate request context as described in RFC 8446, section 4.4.2 */
|
|
if (this->tls->get_version_max(this->tls) > TLS_1_2)
|
|
{
|
|
writer->write_uint8(writer, 0);
|
|
}
|
|
|
|
/* generate certificate payload */
|
|
certs = bio_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);
|
|
|
|
/* extensions see RFC 8446, section 4.4.2 */
|
|
if (this->tls->get_version_max(this->tls) > TLS_1_2)
|
|
{
|
|
certs->write_uint16(certs, 0);
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
|
|
/* extensions see RFC 8446, section 4.4.2 */
|
|
if (this->tls->get_version_max(this->tls) > TLS_1_2)
|
|
{
|
|
certs->write_uint16(certs, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 Verify
|
|
*/
|
|
static status_t send_certificate_verify(private_tls_server_t *this,
|
|
tls_handshake_type_t *type,
|
|
bio_writer_t *writer)
|
|
{
|
|
if (!this->crypto->sign_handshake(this->crypto, this->private, writer,
|
|
this->hashsig))
|
|
{
|
|
DBG1(DBG_TLS, "signature generation failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
*type = TLS_CERTIFICATE_VERIFY;
|
|
this->state = STATE_CERT_VERIFY_SENT;
|
|
this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer));
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/*
|
|
* Write all available certificate authorities to output writer
|
|
*/
|
|
static void write_certificate_authorities(bio_writer_t *writer)
|
|
{
|
|
bio_writer_t *authorities;
|
|
enumerator_t *enumerator;
|
|
certificate_t *cert;
|
|
x509_t *x509;
|
|
identification_t *id;
|
|
|
|
authorities = bio_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);
|
|
}
|
|
|
|
/**
|
|
* Send Certificate Request
|
|
*/
|
|
static status_t send_certificate_request(private_tls_server_t *this,
|
|
tls_handshake_type_t *type,
|
|
bio_writer_t *writer)
|
|
{
|
|
bio_writer_t *authorities, *supported, *extensions;
|
|
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
supported = bio_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_max(this->tls) >= TLS_1_2)
|
|
{
|
|
this->crypto->get_signature_algorithms(this->crypto, writer, TRUE);
|
|
}
|
|
|
|
if (this->send_certreq_authorities)
|
|
{
|
|
write_certificate_authorities(writer);
|
|
}
|
|
else
|
|
{
|
|
writer->write_data16(writer, chunk_empty);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* certificate request context as described in RFC 8446, section 4.3.2 */
|
|
writer->write_uint8(writer, 0);
|
|
|
|
extensions = bio_writer_create(32);
|
|
|
|
if (this->send_certreq_authorities)
|
|
{
|
|
DBG2(DBG_TLS, "sending extension: %N",
|
|
tls_extension_names, TLS_EXT_CERTIFICATE_AUTHORITIES);
|
|
authorities = bio_writer_create(64);
|
|
write_certificate_authorities(authorities);
|
|
extensions->write_uint16(extensions, TLS_EXT_CERTIFICATE_AUTHORITIES);
|
|
extensions->write_data16(extensions, authorities->get_buf(authorities));
|
|
authorities->destroy(authorities);
|
|
}
|
|
|
|
DBG2(DBG_TLS, "sending extension: %N",
|
|
tls_extension_names, TLS_EXT_SIGNATURE_ALGORITHMS);
|
|
extensions->write_uint16(extensions, TLS_EXT_SIGNATURE_ALGORITHMS);
|
|
supported = bio_writer_create(32);
|
|
this->crypto->get_signature_algorithms(this->crypto, supported, TRUE);
|
|
extensions->write_data16(extensions, supported->get_buf(supported));
|
|
supported->destroy(supported);
|
|
writer->write_data16(writer, extensions->get_buf(extensions));
|
|
extensions->destroy(extensions);
|
|
}
|
|
|
|
*type = TLS_CERTIFICATE_REQUEST;
|
|
this->state = STATE_CERTREQ_SENT;
|
|
this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer));
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/**
|
|
* Try to find a curve/group supported by both, client and server
|
|
*/
|
|
static bool find_supported_curve(private_tls_server_t *this,
|
|
tls_named_group_t *curve,
|
|
key_exchange_method_t *group)
|
|
{
|
|
tls_named_group_t current;
|
|
key_exchange_method_t current_group;
|
|
enumerator_t *enumerator;
|
|
|
|
enumerator = this->crypto->create_ec_enumerator(this->crypto);
|
|
while (enumerator->enumerate(enumerator, ¤t_group, ¤t))
|
|
{
|
|
if (peer_supports_curve(this, current))
|
|
{
|
|
*curve = current;
|
|
*group = current_group;
|
|
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, bio_writer_t *writer,
|
|
key_exchange_method_t group)
|
|
{
|
|
diffie_hellman_params_t *params = NULL;
|
|
tls_named_group_t curve;
|
|
chunk_t chunk;
|
|
|
|
if (key_exchange_is_ecdh(group))
|
|
{
|
|
curve = tls_ec_group_to_curve(group);
|
|
if (!curve || (!peer_supports_curve(this, curve) &&
|
|
!find_supported_curve(this, &curve, &group)))
|
|
{
|
|
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_group_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",
|
|
key_exchange_method_names, group);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
DBG2(DBG_TLS, "selected DH group %N", key_exchange_method_names, group);
|
|
writer->write_data16(writer, params->prime);
|
|
writer->write_data16(writer, params->generator);
|
|
}
|
|
this->dh = lib->crypto->create_ke(lib->crypto, group);
|
|
if (!this->dh)
|
|
{
|
|
DBG1(DBG_TLS, "DH group %N not supported",
|
|
key_exchange_method_names, group);
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
if (!this->dh->get_public_key(this->dh, &chunk))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
if (params)
|
|
{
|
|
writer->write_data16(writer, chunk);
|
|
}
|
|
else if (group != CURVE_25519 &&
|
|
group != CURVE_448)
|
|
{ /* 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);
|
|
}
|
|
else
|
|
{ /* ECPoint uses an 8-bit length header only */
|
|
writer->write_data8(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, bio_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, bio_writer_t *writer)
|
|
{
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
char buf[12];
|
|
|
|
if (!this->crypto->calculate_finished_legacy(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));
|
|
}
|
|
else
|
|
{
|
|
chunk_t verify_data;
|
|
|
|
if (!this->crypto->calculate_finished(this->crypto, TRUE, &verify_data))
|
|
{
|
|
DBG1(DBG_TLS, "calculating server finished data failed");
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
|
|
writer->write_data(writer, verify_data);
|
|
chunk_free(&verify_data);
|
|
}
|
|
|
|
*type = TLS_FINISHED;
|
|
this->state = STATE_FINISHED_SENT;
|
|
this->crypto->append_handshake(this->crypto, *type, writer->get_buf(writer));
|
|
|
|
return NEED_MORE;
|
|
}
|
|
|
|
/**
|
|
* Send KeyUpdate message
|
|
*/
|
|
static status_t send_key_update(private_tls_server_t *this,
|
|
tls_handshake_type_t *type, bio_writer_t *writer)
|
|
{
|
|
*type = TLS_KEY_UPDATE;
|
|
|
|
/* we currently only send this as reply, so we never request an update */
|
|
writer->write_uint8(writer, 0);
|
|
|
|
this->state = STATE_KEY_UPDATE_SENT;
|
|
return NEED_MORE;
|
|
}
|
|
|
|
METHOD(tls_handshake_t, build, status_t,
|
|
private_tls_server_t *this, tls_handshake_type_t *type, bio_writer_t *writer)
|
|
{
|
|
key_exchange_method_t group;
|
|
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (this->state)
|
|
{
|
|
case STATE_HELLO_RECEIVED:
|
|
return send_server_hello(this, type, writer);
|
|
case STATE_HELLO_SENT:
|
|
case STATE_CIPHERSPEC_CHANGED_OUT:
|
|
return send_encrypted_extensions(this, type, writer);
|
|
case STATE_ENCRYPTED_EXTENSIONS_SENT:
|
|
if (this->peer)
|
|
{
|
|
return send_certificate_request(this, type, writer);
|
|
}
|
|
/* otherwise fall through to next state */
|
|
case STATE_CERTREQ_SENT:
|
|
return send_certificate(this, type, writer);
|
|
case STATE_CERT_SENT:
|
|
return send_certificate_verify(this, type, writer);
|
|
case STATE_CERT_VERIFY_SENT:
|
|
return send_finished(this, type, writer);
|
|
case STATE_FINISHED_SENT:
|
|
if (!this->crypto->derive_app_keys(this->crypto))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
/* inbound key switches after process client finished message */
|
|
this->crypto->change_cipher(this->crypto, FALSE);
|
|
this->state = STATE_FINISHED_SENT_KEY_SWITCHED;
|
|
return INVALID_STATE;
|
|
case STATE_KEY_UPDATE_REQUESTED:
|
|
return send_key_update(this, type, writer);
|
|
case STATE_KEY_UPDATE_SENT:
|
|
if (!this->crypto->update_app_keys(this->crypto, FALSE))
|
|
{
|
|
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
|
return NEED_MORE;
|
|
}
|
|
this->crypto->change_cipher(this->crypto, FALSE);
|
|
this->state = STATE_FINISHED_RECEIVED;
|
|
default:
|
|
return INVALID_STATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
METHOD(tls_handshake_t, cipherspec_changed, bool,
|
|
private_tls_server_t *this, bool inbound)
|
|
{
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
if (inbound)
|
|
{
|
|
if (this->resume)
|
|
{
|
|
return this->state == STATE_FINISHED_SENT;
|
|
}
|
|
if (this->peer)
|
|
{
|
|
return this->state == STATE_CERT_VERIFY_RECEIVED;
|
|
}
|
|
return this->state == STATE_KEY_EXCHANGE_RECEIVED;
|
|
}
|
|
else
|
|
{
|
|
if (this->resume)
|
|
{
|
|
return this->state == STATE_HELLO_SENT;
|
|
}
|
|
return this->state == STATE_FINISHED_RECEIVED;
|
|
}
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (inbound)
|
|
{ /* accept ChangeCipherSpec after ServerFinish or HelloRetryRequest */
|
|
return this->state == STATE_FINISHED_SENT ||
|
|
this->state == STATE_FINISHED_SENT_KEY_SWITCHED ||
|
|
retrying(this);
|
|
}
|
|
else
|
|
{
|
|
return this->state == STATE_HELLO_SENT;
|
|
}
|
|
}
|
|
}
|
|
|
|
METHOD(tls_handshake_t, change_cipherspec, void,
|
|
private_tls_server_t *this, bool inbound)
|
|
{
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
this->crypto->change_cipher(this->crypto, inbound);
|
|
}
|
|
|
|
if (retrying(this))
|
|
{ /* client might send a ChangeCipherSpec after a HelloRetryRequest and
|
|
* before a new ClientHello which should not cause any state changes */
|
|
return;
|
|
}
|
|
|
|
if (inbound)
|
|
{
|
|
this->state = STATE_CIPHERSPEC_CHANGED_IN;
|
|
}
|
|
else
|
|
{
|
|
this->state = STATE_CIPHERSPEC_CHANGED_OUT;
|
|
}
|
|
}
|
|
|
|
METHOD(tls_handshake_t, finished, bool,
|
|
private_tls_server_t *this)
|
|
{
|
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
|
{
|
|
if (this->resume)
|
|
{
|
|
return this->state == STATE_FINISHED_RECEIVED;
|
|
}
|
|
return this->state == STATE_FINISHED_SENT;
|
|
}
|
|
else
|
|
{
|
|
return this->state == STATE_FINISHED_RECEIVED;
|
|
}
|
|
}
|
|
|
|
METHOD(tls_handshake_t, get_peer_id, identification_t*,
|
|
private_tls_server_t *this)
|
|
{
|
|
return this->peer;
|
|
}
|
|
|
|
METHOD(tls_handshake_t, get_server_id, identification_t*,
|
|
private_tls_server_t *this)
|
|
{
|
|
return this->server;
|
|
}
|
|
|
|
METHOD(tls_handshake_t, get_auth, auth_cfg_t*,
|
|
private_tls_server_t *this)
|
|
{
|
|
return this->peer_auth;
|
|
}
|
|
|
|
METHOD(tls_handshake_t, destroy, void,
|
|
private_tls_server_t *this)
|
|
{
|
|
DESTROY_IF(this->private);
|
|
DESTROY_IF(this->dh);
|
|
DESTROY_IF(this->peer);
|
|
this->server->destroy(this->server);
|
|
this->peer_auth->destroy(this->peer_auth);
|
|
this->server_auth->destroy(this->server_auth);
|
|
free(this->hashsig.ptr);
|
|
free(this->curves.ptr);
|
|
free(this->session.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,
|
|
.get_peer_id = _get_peer_id,
|
|
.get_server_id = _get_server_id,
|
|
.get_auth = _get_auth,
|
|
.destroy = _destroy,
|
|
},
|
|
},
|
|
.tls = tls,
|
|
.crypto = crypto,
|
|
.alert = alert,
|
|
.server = server->clone(server),
|
|
.peer = peer ? peer->clone(peer) : NULL,
|
|
.state = STATE_INIT,
|
|
.peer_auth = auth_cfg_create(),
|
|
.server_auth = auth_cfg_create(),
|
|
.send_certreq_authorities = lib->settings->get_bool(lib->settings,
|
|
"%s.tls.send_certreq_authorities",
|
|
TRUE, lib->ns),
|
|
);
|
|
|
|
return &this->public;
|
|
}
|