libtls: Implement TLS 1.3 handshake on client-side
The code is a minimal handshake with the HelloRetryRequest message implementation missing. Can be tested with an OpenSSL server running TLS 1.3. The server must be at least version 1.1.1 (September 2018). Co-authored-by: ryru <[email protected]>
This commit is contained in:
committed by
Tobias Brunner
co-authored by
ryru
parent
02d7405512
commit
7a2b02667c
+596
-142
@@ -37,6 +37,12 @@ typedef enum {
|
||||
STATE_FINISHED_SENT,
|
||||
STATE_CIPHERSPEC_CHANGED_IN,
|
||||
STATE_FINISHED_RECEIVED,
|
||||
/* new states in TLS 1.3 */
|
||||
STATE_HELLORETRYREQ_RECEIVED,
|
||||
STATE_ENCRYPTED_EXTENSIONS_RECEIVED,
|
||||
STATE_CERT_VERIFY_RECEIVED,
|
||||
STATE_FINISHED_SENT_KEY_SWITCHED,
|
||||
|
||||
} peer_state_t;
|
||||
|
||||
/**
|
||||
@@ -143,8 +149,11 @@ static status_t process_server_hello(private_tls_peer_t *this,
|
||||
{
|
||||
uint8_t compression;
|
||||
uint16_t version, cipher;
|
||||
chunk_t random, session, ext = chunk_empty;
|
||||
chunk_t random, session, ext = chunk_empty, ext_key_share = chunk_empty;
|
||||
tls_cipher_suite_t suite = 0;
|
||||
int offset = 0;
|
||||
uint16_t extension_type, extension_length;
|
||||
uint16_t key_type, key_length;
|
||||
|
||||
this->crypto->append_handshake(this->crypto,
|
||||
TLS_SERVER_HELLO, reader->peek(reader));
|
||||
@@ -163,6 +172,51 @@ static status_t process_server_hello(private_tls_peer_t *this,
|
||||
|
||||
memcpy(this->server_random, random.ptr, sizeof(this->server_random));
|
||||
|
||||
bio_reader_t *extension_reader = bio_reader_create(ext);
|
||||
|
||||
/* parse extension to decide which tls version of the state machine we
|
||||
* want to go
|
||||
*/
|
||||
while (offset < ext.len)
|
||||
{
|
||||
chunk_t extension_payload = chunk_empty;
|
||||
|
||||
extension_reader->read_uint16(extension_reader, &extension_type);
|
||||
extension_reader->read_uint16(extension_reader, &extension_length);
|
||||
offset += extension_length + 4;
|
||||
|
||||
if (!extension_reader->read_data(extension_reader,
|
||||
extension_length,
|
||||
&extension_payload))
|
||||
{
|
||||
DBG2(DBG_TLS, "unable to read extension payload data");
|
||||
}
|
||||
|
||||
bio_reader_t *ext_payload_reader = bio_reader_create(extension_payload);
|
||||
|
||||
switch (extension_type)
|
||||
{
|
||||
case TLS_EXT_SUPPORTED_VERSIONS:
|
||||
ext_payload_reader->read_uint16(ext_payload_reader, &version);
|
||||
break;
|
||||
|
||||
case TLS_EXT_KEY_SHARE:
|
||||
ext_payload_reader->read_uint16(ext_payload_reader, &key_type);
|
||||
ext_payload_reader->read_uint16(ext_payload_reader, &key_length);
|
||||
if (!ext_payload_reader->read_data(ext_payload_reader,
|
||||
key_length,
|
||||
&ext_key_share))
|
||||
{
|
||||
DBG2(DBG_TLS, "no valid key share found in extension");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
ext_payload_reader->destroy(ext_payload_reader);
|
||||
}
|
||||
extension_reader->destroy(extension_reader);
|
||||
|
||||
if (!this->tls->set_version(this->tls, version))
|
||||
{
|
||||
DBG1(DBG_TLS, "negotiated version %N not supported",
|
||||
@@ -171,17 +225,24 @@ static status_t process_server_hello(private_tls_peer_t *this,
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
if (chunk_equals(this->session, session))
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
suite = this->crypto->resume_session(this->crypto, session, this->server,
|
||||
chunk_from_thing(this->client_random),
|
||||
chunk_from_thing(this->server_random));
|
||||
if (suite)
|
||||
if (chunk_equals(this->session, session))
|
||||
{
|
||||
DBG1(DBG_TLS, "resumed %N using suite %N",
|
||||
tls_version_names, version, tls_cipher_suite_names, suite);
|
||||
this->resume = TRUE;
|
||||
suite = this->crypto->resume_session(this->crypto, session,
|
||||
this->server, chunk_from_thing
|
||||
(this->client_random),
|
||||
chunk_from_thing
|
||||
(this->server_random));
|
||||
if (suite)
|
||||
{
|
||||
DBG1(DBG_TLS, "resumed %N using suite %N",
|
||||
tls_version_names, version, tls_cipher_suite_names, suite);
|
||||
this->resume = TRUE;
|
||||
}
|
||||
}
|
||||
DESTROY_IF(this->dh);
|
||||
this->dh = NULL;
|
||||
}
|
||||
if (!suite)
|
||||
{
|
||||
@@ -198,10 +259,101 @@ static status_t process_server_hello(private_tls_peer_t *this,
|
||||
free(this->session.ptr);
|
||||
this->session = chunk_clone(session);
|
||||
}
|
||||
|
||||
if (this->tls->get_version_max(this->tls) == TLS_1_3)
|
||||
{
|
||||
chunk_t shared_secret;
|
||||
|
||||
if (key_type != CURVE_25519 &&
|
||||
!this->dh->set_other_public_value(this->dh, ext_key_share))
|
||||
{
|
||||
DBG2(DBG_TLS, "server key share unable to save");
|
||||
}
|
||||
if (!this->dh->get_shared_secret(this->dh, &shared_secret))
|
||||
{
|
||||
DBG2(DBG_TLS, "No shared secret key found");
|
||||
}
|
||||
|
||||
if (!this->crypto->derive_handshake_secret(this->crypto, shared_secret))
|
||||
{
|
||||
DBG2(DBG_TLS, "derive handshake traffic secret failed");
|
||||
}
|
||||
}
|
||||
|
||||
this->state = STATE_HELLO_RECEIVED;
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a server encrypted extensions message
|
||||
*/
|
||||
static status_t process_encrypted_extensions(private_tls_peer_t *this,
|
||||
bio_reader_t *reader)
|
||||
{
|
||||
uint16_t length;
|
||||
chunk_t ext = chunk_empty;
|
||||
int offset = 0;
|
||||
uint16_t extension_type, extension_length;
|
||||
|
||||
this->crypto->append_handshake(this->crypto,
|
||||
TLS_ENCRYPTED_EXTENSIONS, reader->peek(reader));
|
||||
|
||||
|
||||
if (!reader->read_uint16(reader, &length) ||
|
||||
(reader->remaining(reader) && !reader->read_data16(reader, &ext)))
|
||||
{
|
||||
DBG1(DBG_TLS, "received invalid EncryptedExtensions");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (ext.len == 0)
|
||||
{
|
||||
this->state = STATE_ENCRYPTED_EXTENSIONS_RECEIVED;
|
||||
return NEED_MORE;
|
||||
}
|
||||
else
|
||||
{
|
||||
bio_reader_t *extension_reader = bio_reader_create(ext);
|
||||
|
||||
while (offset < ext.len)
|
||||
{
|
||||
chunk_t extension_payload = chunk_empty;
|
||||
extension_reader->read_uint16(extension_reader, &extension_type);
|
||||
extension_reader->read_uint16(extension_reader, &extension_length);
|
||||
offset += extension_length + 4;
|
||||
|
||||
if (!extension_reader->read_data(extension_reader,
|
||||
extension_length,
|
||||
&extension_payload))
|
||||
{
|
||||
DBG2(DBG_TLS, "unable to read extension payload data");
|
||||
}
|
||||
switch (extension_type)
|
||||
{
|
||||
/* fall through because not supported so far */
|
||||
case TLS_EXT_SERVER_NAME:
|
||||
case TLS_EXT_MAX_FRAGMENT_LENGTH:
|
||||
case TLS_EXT_SUPPORTED_GROUPS:
|
||||
case TLS_EXT_USE_SRTP:
|
||||
case TLS_EXT_HEARTBEAT:
|
||||
case TLS_EXT_APPLICATION_LAYER_PROTOCOL_NEGOTIATION:
|
||||
case TLS_SERVER_CERTIFICATE_TYPE:
|
||||
this->state = STATE_ENCRYPTED_EXTENSIONS_RECEIVED;
|
||||
extension_reader->destroy(extension_reader);
|
||||
break;
|
||||
default:
|
||||
DBG1(DBG_TLS, "received forbidden EncryptedExtensions");
|
||||
this->alert->add(this->alert, TLS_FATAL,
|
||||
TLS_ILLEGAL_PARAMETER);
|
||||
extension_reader->destroy(extension_reader);
|
||||
return NEED_MORE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a server certificate is acceptable for the given server identity
|
||||
*/
|
||||
@@ -252,6 +404,20 @@ static status_t process_certificate(private_tls_peer_t *this,
|
||||
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 (data.len > 0)
|
||||
{
|
||||
DBG1(DBG_TLS, "certificate request context available,"
|
||||
"but CertificateRequest not received");
|
||||
}
|
||||
}
|
||||
if (!reader->read_data24(reader, &data))
|
||||
{
|
||||
DBG1(DBG_TLS, "certificate message header invalid");
|
||||
@@ -300,12 +466,69 @@ static status_t process_certificate(private_tls_peer_t *this,
|
||||
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, "reading extension field of certificate failed",
|
||||
&data);
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
certs->destroy(certs);
|
||||
this->state = STATE_CERT_RECEIVED;
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Certificate verify
|
||||
*/
|
||||
static status_t process_cert_verify(private_tls_peer_t *this,
|
||||
bio_reader_t *reader)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
public_key_t *public;
|
||||
auth_cfg_t *auth;
|
||||
bio_reader_t *sig;
|
||||
bool verified = FALSE;
|
||||
|
||||
enumerator = lib->credmgr->create_public_enumerator(lib->credmgr,
|
||||
KEY_ANY, this->server,
|
||||
this->server_auth, TRUE);
|
||||
while (enumerator->enumerate(enumerator, &public, &auth))
|
||||
{
|
||||
sig = bio_reader_create(reader->peek(reader));
|
||||
verified = this->crypto->verify_handshake(this->crypto, public, sig);
|
||||
sig->destroy(sig);
|
||||
if (verified)
|
||||
{
|
||||
this->server_auth->merge(this->server_auth, auth, FALSE);
|
||||
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->server);
|
||||
this->server->destroy(this->server);
|
||||
this->peer = NULL;
|
||||
this->state = STATE_KEY_EXCHANGE_RECEIVED;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->state = STATE_CERT_VERIFY_RECEIVED;
|
||||
}
|
||||
this->crypto->append_handshake(this->crypto,
|
||||
TLS_CERTIFICATE_VERIFY, reader->peek(reader));
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a trusted public key to encrypt/verify key exchange data
|
||||
*/
|
||||
@@ -407,10 +630,10 @@ static status_t process_modp_key_exchange(private_tls_peer_t *this,
|
||||
* Get the EC group for a TLS named curve
|
||||
*/
|
||||
static diffie_hellman_group_t curve_to_ec_group(private_tls_peer_t *this,
|
||||
tls_named_curve_t curve)
|
||||
tls_named_group_t curve)
|
||||
{
|
||||
diffie_hellman_group_t group;
|
||||
tls_named_curve_t current;
|
||||
tls_named_group_t current;
|
||||
enumerator_t *enumerator;
|
||||
|
||||
enumerator = this->crypto->create_ec_enumerator(this->crypto);
|
||||
@@ -464,7 +687,7 @@ static status_t process_ec_key_exchange(private_tls_peer_t *this,
|
||||
if (!group)
|
||||
{
|
||||
DBG1(DBG_TLS, "ECDH curve %N not supported",
|
||||
tls_named_curve_names, curve);
|
||||
tls_named_group_names, curve);
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_HANDSHAKE_FAILURE);
|
||||
return NEED_MORE;
|
||||
}
|
||||
@@ -569,7 +792,7 @@ static status_t process_certreq(private_tls_peer_t *this, bio_reader_t *reader)
|
||||
return NEED_MORE;
|
||||
}
|
||||
this->cert_types = chunk_clone(types);
|
||||
if (this->tls->get_version(this->tls) >= TLS_1_2)
|
||||
if (this->tls->get_version_max(this->tls) >= TLS_1_2)
|
||||
{
|
||||
if (!reader->read_data16(reader, &hashsig))
|
||||
{
|
||||
@@ -634,26 +857,54 @@ static status_t process_hello_done(private_tls_peer_t *this,
|
||||
*/
|
||||
static status_t process_finished(private_tls_peer_t *this, bio_reader_t *reader)
|
||||
{
|
||||
chunk_t received;
|
||||
chunk_t received, verify_data;
|
||||
char buf[12];
|
||||
uint32_t hash_length;
|
||||
|
||||
if (!reader->read_data(reader, sizeof(buf), &received))
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
DBG1(DBG_TLS, "received server finished too short");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||
return NEED_MORE;
|
||||
if (!reader->read_data(reader, sizeof(buf), &received))
|
||||
{
|
||||
DBG1(DBG_TLS, "received server finished too short");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (!this->crypto->calculate_finished(this->crypto, "server finished",
|
||||
buf))
|
||||
{
|
||||
DBG1(DBG_TLS, "calculating server finished failed");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (!chunk_equals_const(received, chunk_from_thing(buf)))
|
||||
{
|
||||
DBG1(DBG_TLS, "received server finished invalid");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECRYPT_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
}
|
||||
if (!this->crypto->calculate_finished(this->crypto, "server finished", buf))
|
||||
else
|
||||
{
|
||||
DBG1(DBG_TLS, "calculating server finished failed");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (!chunk_equals_const(received, chunk_from_thing(buf)))
|
||||
{
|
||||
DBG1(DBG_TLS, "received server finished invalid");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECRYPT_ERROR);
|
||||
return NEED_MORE;
|
||||
hash_length = reader->remaining(reader);
|
||||
if (!reader->read_data(reader, hash_length, &received))
|
||||
{
|
||||
DBG1(DBG_TLS, "received server finished too short");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (!this->crypto->calculate_finished_tls13(this->crypto, true,
|
||||
&verify_data))
|
||||
{
|
||||
DBG1(DBG_TLS, "calculating server finished failed");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (!chunk_equals(received, verify_data))
|
||||
{
|
||||
DBG1(DBG_TLS, "received server finished invalid");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECRYPT_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
}
|
||||
this->state = STATE_FINISHED_RECEIVED;
|
||||
this->crypto->append_handshake(this->crypto, TLS_FINISHED, received);
|
||||
@@ -661,61 +912,142 @@ static status_t process_finished(private_tls_peer_t *this, bio_reader_t *reader)
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process New Session Ticket message
|
||||
*/
|
||||
static status_t process_new_session_ticket(private_tls_peer_t *this,
|
||||
bio_reader_t *reader)
|
||||
{
|
||||
uint32_t ticket_lifetime, ticket_age_add;
|
||||
chunk_t ticket_nonce, ticket, extensions;
|
||||
|
||||
if (!reader->read_uint32(reader, &ticket_lifetime) ||
|
||||
!reader->read_uint32(reader, &ticket_age_add) ||
|
||||
!reader->read_data8(reader, &ticket_nonce) ||
|
||||
!reader->read_data16(reader, &ticket) ||
|
||||
!reader->read_data16(reader, &extensions))
|
||||
{
|
||||
DBG1(DBG_TLS, "received invalid NewSessionTicket");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
METHOD(tls_handshake_t, process, status_t,
|
||||
private_tls_peer_t *this, tls_handshake_type_t type, bio_reader_t *reader)
|
||||
{
|
||||
tls_handshake_type_t expected;
|
||||
|
||||
switch (this->state)
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
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_SERVER_KEY_EXCHANGE)
|
||||
{
|
||||
return process_key_exchange(this, reader);
|
||||
}
|
||||
/* fall through since TLS_SERVER_KEY_EXCHANGE is optional */
|
||||
case STATE_KEY_EXCHANGE_RECEIVED:
|
||||
if (type == TLS_CERTIFICATE_REQUEST)
|
||||
{
|
||||
return process_certreq(this, reader);
|
||||
}
|
||||
/* no cert request, server does not want to authenticate us */
|
||||
DESTROY_IF(this->peer);
|
||||
this->peer = NULL;
|
||||
/* fall through since TLS_CERTIFICATE_REQUEST is optional */
|
||||
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_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;
|
||||
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_SERVER_KEY_EXCHANGE)
|
||||
{
|
||||
return process_key_exchange(this, reader);
|
||||
}
|
||||
/* fall through since TLS_SERVER_KEY_EXCHANGE is optional */
|
||||
case STATE_KEY_EXCHANGE_RECEIVED:
|
||||
if (type == TLS_CERTIFICATE_REQUEST)
|
||||
{
|
||||
return process_certreq(this, reader);
|
||||
}
|
||||
/* no cert request, server does not want to authenticate us */
|
||||
DESTROY_IF(this->peer);
|
||||
this->peer = NULL;
|
||||
/* fall through since TLS_CERTIFICATE_REQUEST is optional */
|
||||
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_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_HELLO_SENT:
|
||||
if (type == TLS_SERVER_HELLO)
|
||||
{
|
||||
return process_server_hello(this, reader);
|
||||
}
|
||||
expected = TLS_SERVER_HELLO;
|
||||
break;
|
||||
case STATE_CIPHERSPEC_CHANGED_IN:
|
||||
case STATE_HELLO_RECEIVED:
|
||||
if (type == TLS_ENCRYPTED_EXTENSIONS)
|
||||
{
|
||||
return process_encrypted_extensions(this, reader);
|
||||
}
|
||||
expected = TLS_ENCRYPTED_EXTENSIONS;
|
||||
break;
|
||||
case STATE_ENCRYPTED_EXTENSIONS_RECEIVED:
|
||||
if (type == TLS_CERTIFICATE)
|
||||
{
|
||||
return process_certificate(this, reader);
|
||||
}
|
||||
expected = TLS_CERTIFICATE;
|
||||
break;
|
||||
case STATE_CERT_RECEIVED:
|
||||
if (type == TLS_CERTIFICATE_VERIFY)
|
||||
{
|
||||
return process_cert_verify(this, reader);
|
||||
}
|
||||
expected = TLS_CERTIFICATE_VERIFY;
|
||||
break;
|
||||
case STATE_CERT_VERIFY_RECEIVED:
|
||||
if (type == TLS_FINISHED)
|
||||
{
|
||||
return process_finished(this, reader);
|
||||
}
|
||||
expected = TLS_FINISHED;
|
||||
break;
|
||||
case STATE_FINISHED_RECEIVED:
|
||||
return NEED_MORE;
|
||||
case STATE_FINISHED_SENT_KEY_SWITCHED:
|
||||
if (type == TLS_NEW_SESSION_TICKET)
|
||||
{
|
||||
return process_new_session_ticket(this, reader);
|
||||
}
|
||||
expected = TLS_NEW_SESSION_TICKET;
|
||||
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);
|
||||
@@ -731,11 +1063,13 @@ static status_t send_client_hello(private_tls_peer_t *this,
|
||||
{
|
||||
tls_cipher_suite_t *suites;
|
||||
bio_writer_t *extensions, *curves = NULL;
|
||||
tls_version_t version;
|
||||
tls_named_curve_t curve;
|
||||
tls_version_t version_max, version_min;
|
||||
tls_named_group_t curve;
|
||||
enumerator_t *enumerator;
|
||||
int count, i;
|
||||
int count, i, v;
|
||||
rng_t *rng;
|
||||
chunk_t pub;
|
||||
uint8_t nof_tls_versions;
|
||||
|
||||
htoun32(&this->client_random, time(NULL));
|
||||
rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
|
||||
@@ -750,10 +1084,21 @@ static status_t send_client_hello(private_tls_peer_t *this,
|
||||
}
|
||||
rng->destroy(rng);
|
||||
|
||||
/* TLS version */
|
||||
version = this->tls->get_version(this->tls);
|
||||
this->hello_version = version;
|
||||
writer->write_uint16(writer, version);
|
||||
/* client key generation */
|
||||
this->dh = lib->crypto->create_dh(lib->crypto, CURVE_25519);
|
||||
|
||||
/* TLS version_max in handshake protocol */
|
||||
version_max = this->tls->get_version_max(this->tls);
|
||||
version_min = this->tls->get_version_min(this->tls);
|
||||
if (version_max < TLS_1_3)
|
||||
{
|
||||
this->hello_version = version_max;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->hello_version = TLS_1_2;
|
||||
}
|
||||
writer->write_uint16(writer, this->hello_version);
|
||||
writer->write_data(writer, chunk_from_thing(this->client_random));
|
||||
|
||||
/* session identifier */
|
||||
@@ -774,16 +1119,29 @@ static status_t send_client_hello(private_tls_peer_t *this,
|
||||
|
||||
extensions = bio_writer_create(32);
|
||||
|
||||
extensions->write_uint16(extensions, TLS_EXT_SIGNATURE_ALGORITHMS);
|
||||
this->crypto->get_signature_algorithms(this->crypto, extensions);
|
||||
if (this->server->get_type(this->server) == ID_FQDN)
|
||||
{
|
||||
bio_writer_t *names;
|
||||
|
||||
/* add supported Elliptic Curves, if any */
|
||||
DBG2(DBG_TLS, "sending extension: Server Name Indication for '%Y'",
|
||||
this->server);
|
||||
names = bio_writer_create(8);
|
||||
names->write_uint8(names, TLS_NAME_TYPE_HOST_NAME);
|
||||
names->write_data16(names, this->server->get_encoding(this->server));
|
||||
names->wrap16(names);
|
||||
extensions->write_uint16(extensions, TLS_EXT_SERVER_NAME);
|
||||
extensions->write_data16(extensions, names->get_buf(names));
|
||||
names->destroy(names);
|
||||
}
|
||||
|
||||
DBG2(DBG_TLS, "sending extension: %N",
|
||||
tls_extension_names, TLS_EXT_SUPPORTED_GROUPS);
|
||||
enumerator = this->crypto->create_ec_enumerator(this->crypto);
|
||||
while (enumerator->enumerate(enumerator, NULL, &curve))
|
||||
{
|
||||
if (!curves)
|
||||
{
|
||||
extensions->write_uint16(extensions, TLS_EXT_ELLIPTIC_CURVES);
|
||||
extensions->write_uint16(extensions, TLS_EXT_SUPPORTED_GROUPS);
|
||||
curves = bio_writer_create(16);
|
||||
}
|
||||
curves->write_uint16(curves, curve);
|
||||
@@ -791,6 +1149,10 @@ static status_t send_client_hello(private_tls_peer_t *this,
|
||||
enumerator->destroy(enumerator);
|
||||
if (curves)
|
||||
{
|
||||
if (version_max == TLS_1_3)
|
||||
{
|
||||
curves->write_uint16(curves, TLS_CURVE25519);
|
||||
}
|
||||
curves->wrap16(curves);
|
||||
extensions->write_data16(extensions, curves->get_buf(curves));
|
||||
curves->destroy(curves);
|
||||
@@ -801,21 +1163,39 @@ static status_t send_client_hello(private_tls_peer_t *this,
|
||||
extensions->write_uint8(extensions, 1);
|
||||
extensions->write_uint8(extensions, TLS_EC_POINT_UNCOMPRESSED);
|
||||
}
|
||||
if (this->server->get_type(this->server) == ID_FQDN)
|
||||
|
||||
DBG2(DBG_TLS, "sending extension: %N",
|
||||
tls_extension_names, TLS_EXT_SUPPORTED_VERSIONS);
|
||||
nof_tls_versions = (version_max - version_min + 1)*2;
|
||||
extensions->write_uint16(extensions, TLS_EXT_SUPPORTED_VERSIONS);
|
||||
extensions->write_uint16(extensions, nof_tls_versions+1);
|
||||
extensions->write_uint8(extensions, nof_tls_versions);
|
||||
for (v = version_max; v >= version_min; v--)
|
||||
{
|
||||
bio_writer_t *names;
|
||||
|
||||
DBG2(DBG_TLS, "sending Server Name Indication for '%Y'", this->server);
|
||||
|
||||
names = bio_writer_create(8);
|
||||
names->write_uint8(names, TLS_NAME_TYPE_HOST_NAME);
|
||||
names->write_data16(names, this->server->get_encoding(this->server));
|
||||
names->wrap16(names);
|
||||
extensions->write_uint16(extensions, TLS_EXT_SERVER_NAME);
|
||||
extensions->write_data16(extensions, names->get_buf(names));
|
||||
names->destroy(names);
|
||||
extensions->write_uint16(extensions, v);
|
||||
nof_tls_versions += 2;
|
||||
}
|
||||
|
||||
DBG2(DBG_TLS, "sending extension: %N",
|
||||
tls_extension_names, TLS_EXT_SIGNATURE_ALGORITHMS);
|
||||
extensions->write_uint16(extensions, TLS_EXT_SIGNATURE_ALGORITHMS);
|
||||
this->crypto->get_signature_algorithms(this->crypto, extensions);
|
||||
|
||||
DBG2(DBG_TLS, "sending extension: %N",
|
||||
tls_extension_names, TLS_EXT_KEY_SHARE);
|
||||
if (!this->dh->get_my_public_value(this->dh, &pub))
|
||||
{
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
extensions->write_uint16(extensions, TLS_EXT_KEY_SHARE);
|
||||
extensions->write_uint16(extensions, pub.len+6);
|
||||
extensions->write_uint16(extensions, pub.len+4);
|
||||
extensions->write_uint16(extensions, TLS_CURVE25519);
|
||||
extensions->write_uint16(extensions, pub.len);
|
||||
extensions->write_data(extensions, pub);
|
||||
free(pub.ptr);
|
||||
|
||||
writer->write_data16(writer, extensions->get_buf(extensions));
|
||||
extensions->destroy(extensions);
|
||||
|
||||
@@ -1071,16 +1451,33 @@ static status_t send_certificate_verify(private_tls_peer_t *this,
|
||||
static status_t send_finished(private_tls_peer_t *this,
|
||||
tls_handshake_type_t *type, bio_writer_t *writer)
|
||||
{
|
||||
char buf[12];
|
||||
chunk_t verify_data;
|
||||
|
||||
if (!this->crypto->calculate_finished(this->crypto, "client finished", buf))
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
DBG1(DBG_TLS, "calculating client finished data failed");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
char buf[12];
|
||||
|
||||
writer->write_data(writer, chunk_from_thing(buf));
|
||||
if (!this->crypto->calculate_finished(this->crypto, "client finished", buf))
|
||||
{
|
||||
DBG1(DBG_TLS, "calculating client finished data failed");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
writer->write_data(writer, chunk_from_thing(buf));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this->crypto->calculate_finished_tls13(this->crypto, false,
|
||||
&verify_data))
|
||||
{
|
||||
DBG1(DBG_TLS, "calculating client finished data failed");
|
||||
this->alert->add(this->alert, TLS_FATAL, TLS_INTERNAL_ERROR);
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
writer->write_data(writer, verify_data);
|
||||
}
|
||||
|
||||
*type = TLS_FINISHED;
|
||||
this->state = STATE_FINISHED_SENT;
|
||||
@@ -1091,63 +1488,112 @@ static status_t send_finished(private_tls_peer_t *this,
|
||||
METHOD(tls_handshake_t, build, status_t,
|
||||
private_tls_peer_t *this, tls_handshake_type_t *type, bio_writer_t *writer)
|
||||
{
|
||||
switch (this->state)
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
case STATE_INIT:
|
||||
return send_client_hello(this, type, writer);
|
||||
case STATE_HELLO_DONE:
|
||||
if (this->peer)
|
||||
{
|
||||
return send_certificate(this, type, writer);
|
||||
}
|
||||
/* otherwise fall through to next state */
|
||||
case STATE_CERT_SENT:
|
||||
return send_key_exchange(this, type, writer);
|
||||
case STATE_KEY_EXCHANGE_SENT:
|
||||
if (this->peer)
|
||||
{
|
||||
return send_certificate_verify(this, type, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (this->state)
|
||||
{
|
||||
case STATE_INIT:
|
||||
return send_client_hello(this, type, writer);
|
||||
case STATE_HELLO_DONE:
|
||||
if (this->peer)
|
||||
{
|
||||
return send_certificate(this, type, writer);
|
||||
}
|
||||
/* otherwise fall through to next state */
|
||||
case STATE_CERT_SENT:
|
||||
return send_key_exchange(this, type, writer);
|
||||
case STATE_KEY_EXCHANGE_SENT:
|
||||
if (this->peer)
|
||||
{
|
||||
return send_certificate_verify(this, type, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return INVALID_STATE;
|
||||
}
|
||||
case STATE_CIPHERSPEC_CHANGED_OUT:
|
||||
return send_finished(this, type, writer);
|
||||
default:
|
||||
return INVALID_STATE;
|
||||
}
|
||||
case STATE_CIPHERSPEC_CHANGED_OUT:
|
||||
return send_finished(this, type, writer);
|
||||
default:
|
||||
return INVALID_STATE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (this->state)
|
||||
{
|
||||
case STATE_INIT:
|
||||
return send_client_hello(this, type, writer);
|
||||
case STATE_HELLO_DONE:
|
||||
/* otherwise fall through to next state */
|
||||
case STATE_CIPHERSPEC_CHANGED_OUT:
|
||||
case STATE_FINISHED_RECEIVED:
|
||||
/* fall through since legacy TLS and TLS 1.3
|
||||
* expect the same message */
|
||||
return send_finished(this, type, writer);
|
||||
case STATE_FINISHED_SENT:
|
||||
this->crypto->derive_app_secret(this->crypto);
|
||||
this->crypto->change_cipher(this->crypto, TRUE);
|
||||
this->crypto->change_cipher(this->crypto, FALSE);
|
||||
this->state = STATE_FINISHED_SENT_KEY_SWITCHED;
|
||||
return SUCCESS;
|
||||
case STATE_FINISHED_SENT_KEY_SWITCHED:
|
||||
return SUCCESS;
|
||||
default:
|
||||
return INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
METHOD(tls_handshake_t, cipherspec_changed, bool,
|
||||
private_tls_peer_t *this, bool inbound)
|
||||
{
|
||||
if (inbound)
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
if (this->resume)
|
||||
if (inbound)
|
||||
{
|
||||
return this->state == STATE_HELLO_RECEIVED;
|
||||
if (this->resume)
|
||||
{
|
||||
return this->state == STATE_HELLO_RECEIVED;
|
||||
}
|
||||
return this->state == STATE_FINISHED_SENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->resume)
|
||||
{
|
||||
return this->state == STATE_FINISHED_RECEIVED;
|
||||
}
|
||||
if (this->peer)
|
||||
{
|
||||
return this->state == STATE_VERIFY_SENT;
|
||||
}
|
||||
return this->state == STATE_KEY_EXCHANGE_SENT;
|
||||
|
||||
}
|
||||
return this->state == STATE_FINISHED_SENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->resume)
|
||||
if (inbound)
|
||||
{
|
||||
return this->state == STATE_FINISHED_RECEIVED;
|
||||
return this->state == STATE_HELLO_RECEIVED;
|
||||
}
|
||||
if (this->peer)
|
||||
else
|
||||
{
|
||||
return this->state == STATE_VERIFY_SENT;
|
||||
return FALSE;
|
||||
}
|
||||
return this->state == STATE_KEY_EXCHANGE_SENT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
METHOD(tls_handshake_t, change_cipherspec, void,
|
||||
private_tls_peer_t *this, bool inbound)
|
||||
{
|
||||
this->crypto->change_cipher(this->crypto, inbound);
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
this->crypto->change_cipher(this->crypto, inbound);
|
||||
}
|
||||
|
||||
if (inbound)
|
||||
{
|
||||
this->state = STATE_CIPHERSPEC_CHANGED_IN;
|
||||
@@ -1161,11 +1607,19 @@ METHOD(tls_handshake_t, change_cipherspec, void,
|
||||
METHOD(tls_handshake_t, finished, bool,
|
||||
private_tls_peer_t *this)
|
||||
{
|
||||
if (this->resume)
|
||||
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||
{
|
||||
if (this->resume)
|
||||
{
|
||||
return this->state == STATE_FINISHED_SENT;
|
||||
}
|
||||
|
||||
return this->state == STATE_FINISHED_RECEIVED;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->state == STATE_FINISHED_SENT_KEY_SWITCHED;
|
||||
}
|
||||
return this->state == STATE_FINISHED_RECEIVED;
|
||||
}
|
||||
|
||||
METHOD(tls_handshake_t, get_peer_id, identification_t*,
|
||||
|
||||
Reference in New Issue
Block a user