tls-peer: Mutual authentication support for TLS 1.3
This commit is contained in:
committed by
Tobias Brunner
parent
2d933f318b
commit
9ef46cfaf9
@@ -665,6 +665,12 @@ START_TEST(test_tls13)
|
|||||||
}
|
}
|
||||||
END_TEST
|
END_TEST
|
||||||
|
|
||||||
|
START_TEST(test_tls13_mutual)
|
||||||
|
{
|
||||||
|
test_tls(TLS_1_3, 5670, TRUE, _i);
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
START_TEST(test_tls12)
|
START_TEST(test_tls12)
|
||||||
{
|
{
|
||||||
test_tls(TLS_1_2, 5671, FALSE, _i);
|
test_tls(TLS_1_2, 5671, FALSE, _i);
|
||||||
@@ -741,6 +747,11 @@ Suite *socket_suite_create()
|
|||||||
add_tls_test(test_tls13, TLS_1_3);
|
add_tls_test(test_tls13, TLS_1_3);
|
||||||
suite_add_tcase(s, tc);
|
suite_add_tcase(s, tc);
|
||||||
|
|
||||||
|
tc = tcase_create("TLS 1.3/mutl");
|
||||||
|
tcase_add_checked_fixture(tc, setup_creds, teardown_creds);
|
||||||
|
add_tls_test(test_tls13_mutual, TLS_1_3);
|
||||||
|
suite_add_tcase(s, tc);
|
||||||
|
|
||||||
tc = tcase_create("TLS 1.2/anon");
|
tc = tcase_create("TLS 1.2/anon");
|
||||||
tcase_add_checked_fixture(tc, setup_creds, teardown_creds);
|
tcase_add_checked_fixture(tc, setup_creds, teardown_creds);
|
||||||
add_tls_test(test_tls12, TLS_1_2);
|
add_tls_test(test_tls12, TLS_1_2);
|
||||||
|
|||||||
@@ -1775,7 +1775,14 @@ METHOD(tls_crypto_t, sign, bool,
|
|||||||
DBG1(DBG_TLS, "unable to create transcript hash");
|
DBG1(DBG_TLS, "unable to create transcript hash");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
data = chunk_cata("cm", tls13_sig_data_server, transcript_hash);
|
if (this->tls->is_server(this->tls))
|
||||||
|
{
|
||||||
|
data = chunk_cata("cm", tls13_sig_data_server, transcript_hash);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = chunk_cata("cm", tls13_sig_data_client, transcript_hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hashsig.len)
|
if (!hashsig.len)
|
||||||
@@ -1884,7 +1891,7 @@ METHOD(tls_crypto_t, verify, bool,
|
|||||||
tls_signature_scheme_names, scheme);
|
tls_signature_scheme_names, scheme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
if (this->tls->get_version_max(this->tls) == TLS_1_3)
|
if (this->tls->get_version_max(this->tls) >= TLS_1_3)
|
||||||
{
|
{
|
||||||
chunk_t transcript_hash;
|
chunk_t transcript_hash;
|
||||||
|
|
||||||
|
|||||||
+156
-54
@@ -49,6 +49,7 @@ typedef enum {
|
|||||||
STATE_FINISHED_SENT_KEY_SWITCHED,
|
STATE_FINISHED_SENT_KEY_SWITCHED,
|
||||||
STATE_KEY_UPDATE_REQUESTED,
|
STATE_KEY_UPDATE_REQUESTED,
|
||||||
STATE_KEY_UPDATE_SENT,
|
STATE_KEY_UPDATE_SENT,
|
||||||
|
STATE_CERT_VERIFY_SENT,
|
||||||
} peer_state_t;
|
} peer_state_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -846,55 +847,27 @@ static status_t process_key_exchange(private_tls_peer_t *this,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a Certificate Request message
|
* Read all available certificate authorities from the given reader
|
||||||
*/
|
*/
|
||||||
static status_t process_certreq(private_tls_peer_t *this, bio_reader_t *reader)
|
static bool read_certificate_authorities(private_tls_peer_t *this,
|
||||||
|
bio_reader_t *reader)
|
||||||
{
|
{
|
||||||
chunk_t types, hashsig, data;
|
chunk_t data;
|
||||||
bio_reader_t *authorities;
|
bio_reader_t *authorities;
|
||||||
identification_t *id;
|
identification_t *id;
|
||||||
certificate_t *cert;
|
certificate_t *cert;
|
||||||
|
|
||||||
if (!this->peer)
|
|
||||||
{
|
|
||||||
DBG1(DBG_TLS, "server requested a certificate, but client "
|
|
||||||
"authentication disabled");
|
|
||||||
}
|
|
||||||
this->crypto->append_handshake(this->crypto,
|
|
||||||
TLS_CERTIFICATE_REQUEST, reader->peek(reader));
|
|
||||||
|
|
||||||
if (!reader->read_data8(reader, &types))
|
|
||||||
{
|
|
||||||
DBG1(DBG_TLS, "certreq message header invalid");
|
|
||||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
||||||
return NEED_MORE;
|
|
||||||
}
|
|
||||||
this->cert_types = chunk_clone(types);
|
|
||||||
if (this->tls->get_version_max(this->tls) >= TLS_1_2)
|
|
||||||
{
|
|
||||||
if (!reader->read_data16(reader, &hashsig))
|
|
||||||
{
|
|
||||||
DBG1(DBG_TLS, "certreq message invalid");
|
|
||||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
||||||
return NEED_MORE;
|
|
||||||
}
|
|
||||||
this->hashsig = chunk_clone(hashsig);
|
|
||||||
}
|
|
||||||
if (!reader->read_data16(reader, &data))
|
if (!reader->read_data16(reader, &data))
|
||||||
{
|
{
|
||||||
DBG1(DBG_TLS, "certreq message invalid");
|
return FALSE;
|
||||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
||||||
return NEED_MORE;
|
|
||||||
}
|
}
|
||||||
authorities = bio_reader_create(data);
|
authorities = bio_reader_create(data);
|
||||||
while (authorities->remaining(authorities))
|
while (authorities->remaining(authorities))
|
||||||
{
|
{
|
||||||
if (!authorities->read_data16(authorities, &data))
|
if (!authorities->read_data16(authorities, &data))
|
||||||
{
|
{
|
||||||
DBG1(DBG_TLS, "certreq message invalid");
|
|
||||||
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
|
||||||
authorities->destroy(authorities);
|
authorities->destroy(authorities);
|
||||||
return NEED_MORE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
if (this->peer)
|
if (this->peer)
|
||||||
{
|
{
|
||||||
@@ -914,6 +887,105 @@ static status_t process_certreq(private_tls_peer_t *this, bio_reader_t *reader)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
authorities->destroy(authorities);
|
authorities->destroy(authorities);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a Certificate Request message
|
||||||
|
*/
|
||||||
|
static status_t process_certreq(private_tls_peer_t *this, bio_reader_t *reader)
|
||||||
|
{
|
||||||
|
chunk_t types, hashsig, context, ext;
|
||||||
|
bio_reader_t *extensions, *extension;
|
||||||
|
|
||||||
|
if (!this->peer)
|
||||||
|
{
|
||||||
|
DBG1(DBG_TLS, "server requested a certificate, but client "
|
||||||
|
"authentication disabled");
|
||||||
|
}
|
||||||
|
this->crypto->append_handshake(this->crypto,
|
||||||
|
TLS_CERTIFICATE_REQUEST, reader->peek(reader));
|
||||||
|
|
||||||
|
if (this->tls->get_version_max(this->tls) < TLS_1_3)
|
||||||
|
{
|
||||||
|
if (!reader->read_data8(reader, &types))
|
||||||
|
{
|
||||||
|
DBG1(DBG_TLS, "certreq message header invalid");
|
||||||
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||||
|
return NEED_MORE;
|
||||||
|
}
|
||||||
|
this->cert_types = chunk_clone(types);
|
||||||
|
if (this->tls->get_version_max(this->tls) >= TLS_1_2)
|
||||||
|
{
|
||||||
|
if (!reader->read_data16(reader, &hashsig))
|
||||||
|
{
|
||||||
|
DBG1(DBG_TLS, "certreq message invalid");
|
||||||
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||||
|
return NEED_MORE;
|
||||||
|
}
|
||||||
|
this->hashsig = chunk_clone(hashsig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!read_certificate_authorities(this, reader))
|
||||||
|
{
|
||||||
|
DBG1(DBG_TLS, "certreq message invalid");
|
||||||
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||||
|
return NEED_MORE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* certificate request context as described in RFC 8446, section 4.3.2 */
|
||||||
|
reader->read_data8(reader, &context);
|
||||||
|
|
||||||
|
reader->read_data16(reader, &ext);
|
||||||
|
extensions = bio_reader_create(ext);
|
||||||
|
while (extensions->remaining(extensions))
|
||||||
|
{
|
||||||
|
uint16_t extension_type;
|
||||||
|
chunk_t extension_data;
|
||||||
|
|
||||||
|
if (!extensions->read_uint16(extensions, &extension_type) ||
|
||||||
|
!extensions->read_data16(extensions, &extension_data))
|
||||||
|
{
|
||||||
|
DBG1(DBG_TLS, "invalid extension in CertificateRequest");
|
||||||
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||||
|
extensions->destroy(extensions);
|
||||||
|
return NEED_MORE;
|
||||||
|
}
|
||||||
|
extension = bio_reader_create(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);
|
||||||
|
extension->destroy(extension);
|
||||||
|
extensions->destroy(extensions);
|
||||||
|
return NEED_MORE;
|
||||||
|
}
|
||||||
|
chunk_free(&this->hashsig);
|
||||||
|
this->hashsig = chunk_clone(extension_data);
|
||||||
|
break;
|
||||||
|
case TLS_EXT_CERTIFICATE_AUTHORITIES:
|
||||||
|
if (!read_certificate_authorities(this, extension))
|
||||||
|
{
|
||||||
|
DBG1(DBG_TLS, "certificate request message invalid");
|
||||||
|
this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
|
||||||
|
extension->destroy(extension);
|
||||||
|
extensions->destroy(extensions);
|
||||||
|
return NEED_MORE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
extension->destroy(extension);
|
||||||
|
}
|
||||||
|
extensions->destroy(extensions);
|
||||||
|
}
|
||||||
this->state = STATE_CERTREQ_RECEIVED;
|
this->state = STATE_CERTREQ_RECEIVED;
|
||||||
return NEED_MORE;
|
return NEED_MORE;
|
||||||
}
|
}
|
||||||
@@ -1115,6 +1187,15 @@ METHOD(tls_handshake_t, process, status_t,
|
|||||||
expected = TLS_ENCRYPTED_EXTENSIONS;
|
expected = TLS_ENCRYPTED_EXTENSIONS;
|
||||||
break;
|
break;
|
||||||
case STATE_ENCRYPTED_EXTENSIONS_RECEIVED:
|
case STATE_ENCRYPTED_EXTENSIONS_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;
|
||||||
|
/* otherwise fall through to next state */
|
||||||
|
case STATE_CERTREQ_RECEIVED:
|
||||||
if (type == TLS_CERTIFICATE)
|
if (type == TLS_CERTIFICATE)
|
||||||
{
|
{
|
||||||
return process_certificate(this, reader);
|
return process_certificate(this, reader);
|
||||||
@@ -1426,37 +1507,45 @@ static status_t send_certificate(private_tls_peer_t *this,
|
|||||||
}
|
}
|
||||||
DESTROY_IF(enumerator);
|
DESTROY_IF(enumerator);
|
||||||
|
|
||||||
|
/* 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 */
|
/* generate certificate payload */
|
||||||
certs = bio_writer_create(256);
|
certs = bio_writer_create(256);
|
||||||
if (this->peer)
|
cert = this->peer_auth->get(this->peer_auth, AUTH_RULE_SUBJECT_CERT);
|
||||||
|
if (cert)
|
||||||
{
|
{
|
||||||
cert = this->peer_auth->get(this->peer_auth, AUTH_RULE_SUBJECT_CERT);
|
if (cert->get_encoding(cert, CERT_ASN1_DER, &data))
|
||||||
if (cert)
|
{
|
||||||
|
DBG1(DBG_TLS, "sending TLS client 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->peer_auth->create_enumerator(this->peer_auth);
|
||||||
|
while (enumerator->enumerate(enumerator, &rule, &cert))
|
||||||
|
{
|
||||||
|
if (rule == AUTH_RULE_IM_CERT)
|
||||||
{
|
{
|
||||||
if (cert->get_encoding(cert, CERT_ASN1_DER, &data))
|
if (cert->get_encoding(cert, CERT_ASN1_DER, &data))
|
||||||
{
|
{
|
||||||
DBG1(DBG_TLS, "sending TLS peer certificate '%Y'",
|
DBG1(DBG_TLS, "sending TLS intermediate certificate '%Y'",
|
||||||
cert->get_subject(cert));
|
cert->get_subject(cert));
|
||||||
certs->write_data24(certs, data);
|
certs->write_data24(certs, data);
|
||||||
free(data.ptr);
|
free(data.ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enumerator = this->peer_auth->create_enumerator(this->peer_auth);
|
|
||||||
while (enumerator->enumerate(enumerator, &rule, &cert))
|
|
||||||
{
|
|
||||||
if (rule == AUTH_RULE_IM_CERT)
|
|
||||||
{
|
|
||||||
if (cert->get_encoding(cert, CERT_ASN1_DER, &data))
|
|
||||||
{
|
|
||||||
DBG1(DBG_TLS, "sending TLS intermediate certificate '%Y'",
|
|
||||||
cert->get_subject(cert));
|
|
||||||
certs->write_data24(certs, data);
|
|
||||||
free(data.ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enumerator->destroy(enumerator);
|
|
||||||
}
|
}
|
||||||
|
enumerator->destroy(enumerator);
|
||||||
|
|
||||||
writer->write_data24(writer, certs->get_buf(certs));
|
writer->write_data24(writer, certs->get_buf(certs));
|
||||||
certs->destroy(certs);
|
certs->destroy(certs);
|
||||||
@@ -1596,7 +1685,8 @@ static status_t send_key_exchange(private_tls_peer_t *this,
|
|||||||
* Send certificate verify
|
* Send certificate verify
|
||||||
*/
|
*/
|
||||||
static status_t send_certificate_verify(private_tls_peer_t *this,
|
static status_t send_certificate_verify(private_tls_peer_t *this,
|
||||||
tls_handshake_type_t *type, bio_writer_t *writer)
|
tls_handshake_type_t *type,
|
||||||
|
bio_writer_t *writer)
|
||||||
{
|
{
|
||||||
if (!this->private ||
|
if (!this->private ||
|
||||||
!this->crypto->sign_handshake(this->crypto, this->private,
|
!this->crypto->sign_handshake(this->crypto, this->private,
|
||||||
@@ -1716,6 +1806,18 @@ METHOD(tls_handshake_t, build, status_t,
|
|||||||
return NEED_MORE;
|
return NEED_MORE;
|
||||||
}
|
}
|
||||||
this->crypto->change_cipher(this->crypto, TRUE);
|
this->crypto->change_cipher(this->crypto, TRUE);
|
||||||
|
if (this->peer)
|
||||||
|
{
|
||||||
|
return send_certificate(this, type, writer);
|
||||||
|
}
|
||||||
|
/* otherwise fall through to next state */
|
||||||
|
case STATE_CERT_SENT:
|
||||||
|
if (this->peer)
|
||||||
|
{
|
||||||
|
return send_certificate_verify(this, type, writer);
|
||||||
|
}
|
||||||
|
/* otherwise fall through to next state */
|
||||||
|
case STATE_VERIFY_SENT:
|
||||||
return send_finished(this, type, writer);
|
return send_finished(this, type, writer);
|
||||||
case STATE_FINISHED_SENT:
|
case STATE_FINISHED_SENT:
|
||||||
this->crypto->change_cipher(this->crypto, FALSE);
|
this->crypto->change_cipher(this->crypto, FALSE);
|
||||||
|
|||||||
Reference in New Issue
Block a user