Skip to content

Commit

Permalink
KEMTLS as spoken by thomwigger/kemtls-experiment
Browse files Browse the repository at this point in the history
Co-Authored-By: Hannes Rantzsch <[email protected]>
  • Loading branch information
reneme and Hannes Rantzsch committed Apr 7, 2022
1 parent 0ab571f commit 32b2ad1
Show file tree
Hide file tree
Showing 20 changed files with 380 additions and 23 deletions.
15 changes: 15 additions & 0 deletions src/lib/pubkey/pk_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
#include <botan/sm2.h>
#endif

#if defined(BOTAN_HAS_KYBER)
#include <botan/kyber.h>
#endif

namespace Botan {

std::unique_ptr<Public_Key>
Expand Down Expand Up @@ -152,6 +156,17 @@ load_public_key(const AlgorithmIdentifier& alg_id,
return std::make_unique<XMSS_PublicKey>(key_bits);
#endif

#if defined(BOTAN_HAS_KYBER)
// TODO: those OIDs are taken from thomwiggers/mk-cert for interoperability
// https://github.com/thomwiggers/mk-cert/blob/dd845f3825d8dc5fd69e0e234f6a62266ee4dd4a/algorithms.py#L84
if(alg_name == "1.3.6.1.4.1.44363.46.52")
return std::make_unique<Kyber_PublicKey>(key_bits, KyberMode::Kyber512, KyberKeyEncoding::Raw);
if(alg_name == "1.3.6.1.4.1.44363.46.53")
return std::make_unique<Kyber_PublicKey>(key_bits, KyberMode::Kyber768, KyberKeyEncoding::Raw);
if(alg_name == "1.3.6.1.4.1.44363.46.54")
return std::make_unique<Kyber_PublicKey>(key_bits, KyberMode::Kyber1024, KyberKeyEncoding::Raw);
#endif

throw Decoding_Error("Unknown or unavailable public key algorithm " + alg_name);
}

Expand Down
32 changes: 23 additions & 9 deletions src/lib/tls/msg_certificate_13.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <botan/internal/loadstor.h>
#include <botan/data_src.h>

#if defined(BOTAN_HAS_KEMTLS) && defined(BOTAN_HAS_KYBER)
#include <botan/kyber.h>
#endif

namespace Botan::TLS {

namespace {
Expand Down Expand Up @@ -90,6 +94,24 @@ void Certificate_13::verify(Callbacks& callbacks,
callbacks.tls_verify_cert_chain(certs, ocsp_responses, trusted_CAs, usage, hostname, policy);
}

std::unique_ptr<Public_Key> Certificate_13::public_key() const
{
BOTAN_STATE_CHECK(!m_entries.empty());
auto key = m_entries.front().certificate.load_subject_public_key();

#if defined(BOTAN_HAS_KEMTLS) && defined(BOTAN_HAS_KYBER)
// TODO: Is there a better way to detect a kyber public key?
// Maybe key->algorithm_identifier()
if(key->algo_name().find("Kyber") == 0)
{
dynamic_cast<Kyber_PublicKey&>(*key)
.set_binary_encoding(KyberKeyEncoding::Raw);
}
#endif

return key;
}

/**
* Deserialize a Certificate message
*/
Expand Down Expand Up @@ -179,15 +201,7 @@ Certificate_13::Certificate_13(const std::vector<uint8_t>& buf,
else
{
/* validation of provided certificate public key */
auto key = m_entries.front().certificate.load_subject_public_key();

policy.check_peer_key_acceptable(*key);

if(!policy.allowed_signature_method(key->algo_name()))
{
throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
"Rejecting " + key->algo_name() + " signature");
}
policy.check_peer_key_acceptable(*public_key());
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/lib/tls/msg_server_hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,11 @@ Protocol_Version Server_Hello_13::selected_version() const
return versions.front();
}

bool Server_Hello_13::requests_kemtls() const
{
return extensions().has<Key_Share>() && extensions().get<Key_Share>()->requests_kemtls();
}

Hello_Retry_Request::Hello_Retry_Request(std::unique_ptr<Server_Hello_Internal> data)
: Server_Hello_13(std::move(data), Server_Hello_13::as_hello_retry_request) {}

Expand Down
3 changes: 3 additions & 0 deletions src/lib/tls/tls12/tls_handshake_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ const char* handshake_type_to_string(Handshake_Type type)
case KEY_UPDATE:
return "key_update";

case KEM_ENCAPSULATION:
return "kem_encapsulation";

case HANDSHAKE_NONE:
return "invalid";
}
Expand Down
14 changes: 14 additions & 0 deletions src/lib/tls/tls13/kemtls/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<defines>
KEMTLS -> 20220407
</defines>

<header:public>
</header:public>

<header:internal>
</header:internal>

<requires>
tls13
pubkey
</requires>
25 changes: 25 additions & 0 deletions src/lib/tls/tls13/kemtls/msg_kem_encapsulation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* KEMTLS KEM_Encapsulation Message
* (C) 2022 Jack Lloyd
* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/tls_messages.h>
#include <botan/pubkey.h>

namespace Botan::TLS {

KEM_Encapsulation::KEM_Encapsulation(const Public_Key& peer_key, RandomNumberGenerator& rng)
{
// TODO: shared secret length is always 32 in thomwiggers/rustls, but is it?
PK_KEM_Encryptor(peer_key, rng).encrypt(m_ciphertext, m_secret, 32, rng);
}

std::vector<uint8_t> KEM_Encapsulation::serialize() const
{
return unlock(m_ciphertext);
}

}
70 changes: 68 additions & 2 deletions src/lib/tls/tls13/tls_cipher_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,70 @@ void Cipher_State::advance_with_client_finished(const Transcript_Hash& transcrip
m_state = State::Completed;
}

#if defined(BOTAN_HAS_KEMTLS)
void Cipher_State::advance_with_client_kem_encapsulation(
secure_vector<uint8_t> shared_secret,
const Transcript_Hash& transcript_hash)
{
BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic);

const auto kem_secret = hkdf_extract(std::move(shared_secret));

const auto client_handshake_traffic_secret = derive_secret(kem_secret, "c ahs traffic", transcript_hash);
const auto server_handshake_traffic_secret = derive_secret(kem_secret, "s ahs traffic", transcript_hash);

m_salt = derive_secret(kem_secret, "derived", empty_hash()); // dAHS
const auto master_secret = hkdf_extract(secure_vector<uint8_t>(m_hash->output_length(), 0x00));

// Note: these labels differ from the research paper to comply with thomwiggers rustls implementation.
// https://thomwiggers.nl/publication/kemtls/kemtls.pdf
auto client_finished_key = hkdf_expand_label(master_secret, "client", {}, m_hash->output_length());
auto server_finished_key = hkdf_expand_label(master_secret, "server", {}, m_hash->output_length());

if(m_connection_side == Connection_Side::SERVER)
{
derive_read_traffic_key(client_handshake_traffic_secret, false);
derive_write_traffic_key(server_handshake_traffic_secret, false);
m_finished_key = std::move(server_finished_key);
m_peer_finished_key = std::move(client_finished_key);
}
else
{
derive_read_traffic_key(server_handshake_traffic_secret, false);
derive_write_traffic_key(client_handshake_traffic_secret, false);
m_finished_key = std::move(client_finished_key);
m_peer_finished_key = std::move(server_finished_key);
}

m_state = State::AuthenticatedHandshakeTraffic;
}

void Cipher_State::advance_with_client_finished_kemtls(
const Transcript_Hash& transcript_hash /* CH..CF */)
{
BOTAN_ASSERT_NOMSG(m_state == State::AuthenticatedHandshakeTraffic);

const auto master_secret = hkdf_extract(secure_vector<uint8_t>(m_hash->output_length(), 0x00));
auto client_application_traffic_secret = derive_secret(master_secret, "c ap traffic", transcript_hash);
derive_write_traffic_key(client_application_traffic_secret);
}

void Cipher_State::advance_with_server_finished_kemtls(
const Transcript_Hash& transcript_hash /* CH..SF */)
{
BOTAN_ASSERT_NOMSG(m_state == State::AuthenticatedHandshakeTraffic);

const auto master_secret = hkdf_extract(secure_vector<uint8_t>(m_hash->output_length(), 0x00));
auto server_application_traffic_secret = derive_secret(master_secret, "s ap traffic", transcript_hash);
derive_read_traffic_key(server_application_traffic_secret);

// This was the final state change; the salt is no longer needed.
zap(m_salt);

m_state = State::Completed;
}
#endif

std::vector<uint8_t> Cipher_State::current_nonce(const uint64_t seq_no, const secure_vector<uint8_t>& iv) const
{
// RFC 8446 5.3
Expand Down Expand Up @@ -224,7 +288,8 @@ size_t Cipher_State::minimum_decryption_input_length() const

std::vector<uint8_t> Cipher_State::finished_mac(const Transcript_Hash& transcript_hash) const
{
BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic);
BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic ||
m_state == State::AuthenticatedHandshakeTraffic);

auto hmac = HMAC(m_hash->new_object());
hmac.set_key(m_finished_key);
Expand All @@ -235,7 +300,8 @@ std::vector<uint8_t> Cipher_State::finished_mac(const Transcript_Hash& transcrip
bool Cipher_State::verify_peer_finished_mac(const Transcript_Hash& transcript_hash,
const std::vector<uint8_t>& peer_mac) const
{
BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic);
BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic ||
m_state == State::AuthenticatedHandshakeTraffic);

auto hmac = HMAC(m_hash->new_object());
hmac.set_key(m_peer_finished_key);
Expand Down
11 changes: 11 additions & 0 deletions src/lib/tls/tls13/tls_cipher_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ class BOTAN_TEST_API Cipher_State
*/
void advance_with_client_finished(const Transcript_Hash& transcript_hash);

#if defined(BOTAN_HAS_KEMTLS)
void advance_with_client_kem_encapsulation(secure_vector<uint8_t> shared_secret,
const Transcript_Hash& transcript_hash);

void advance_with_client_finished_kemtls(const Transcript_Hash& transcript_hash);

void advance_with_server_finished_kemtls(const Transcript_Hash& transcript_hash);
#endif

/**
* Encrypt a TLS record fragment (RFC 8446 5.2 -- TLSInnerPlaintext) using the
* currently available traffic secret keys and the current sequence number.
Expand Down Expand Up @@ -167,6 +176,7 @@ class BOTAN_TEST_API Cipher_State
bool can_encrypt_application_traffic() const
{
return m_state != State::Uninitialized && m_state != State::HandshakeTraffic
&& m_state != State::AuthenticatedHandshakeTraffic
&& !m_write_key.empty() && !m_write_iv.empty();
}

Expand Down Expand Up @@ -248,6 +258,7 @@ class BOTAN_TEST_API Cipher_State
Uninitialized,
EarlyTraffic,
HandshakeTraffic,
AuthenticatedHandshakeTraffic, // KEMTLS only
ApplicationTraffic,
Completed
};
Expand Down
56 changes: 47 additions & 9 deletions src/lib/tls/tls13/tls_client_impl_13.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,36 @@ void Client_Impl_13::handle(const Certificate_13& certificate_msg)
m_info.hostname(),
m_handshake_state.client_hello().extensions().has<Certificate_Status_Request>());

m_transitions.set_expected_next(CERTIFICATE_VERIFY);
const auto pubkey = certificate_msg.public_key();

#if defined(BOTAN_HAS_KEMTLS)
if(m_handshake_state.server_hello().requests_kemtls())
{
send_handshake_message(
m_handshake_state.sent(KEM_Encapsulation(*pubkey, rng())));

m_cipher_state->advance_with_client_kem_encapsulation(
m_handshake_state.client_kem_encapsulation().secret(),
m_transcript_hash.current());

send_handshake_message(m_handshake_state.sent(Finished_13(m_cipher_state.get(),
m_transcript_hash.current())));

m_cipher_state->advance_with_client_finished_kemtls(m_transcript_hash.current());

m_transitions.set_expected_next(FINISHED);
}
else
#endif
{
if(!policy().allowed_signature_method(pubkey->algo_name()))
{
throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
"Rejecting " + pubkey->algo_name() + " signature");
}

m_transitions.set_expected_next(CERTIFICATE_VERIFY);
}
}

void Client_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg)
Expand All @@ -395,14 +424,23 @@ void Client_Impl_13::handle(const Finished_13& finished_msg)
m_transcript_hash.previous()))
{ throw TLS_Exception(Alert::DECRYPT_ERROR, "Finished message didn't verify"); }

// send client finished handshake message (still using handshake traffic secrets)
send_handshake_message(m_handshake_state.sent(Finished_13(m_cipher_state.get(),
m_transcript_hash.current())));

// derives the application traffic secrets and _replaces_ the handshake traffic secrets
// Note: this MUST happen AFTER the client finished message was sent!
m_cipher_state->advance_with_server_finished(m_transcript_hash.previous());
m_cipher_state->advance_with_client_finished(m_transcript_hash.current());
#if defined(BOTAN_HAS_KEMTLS)
if(m_handshake_state.server_hello().requests_kemtls())
{
m_cipher_state->advance_with_server_finished_kemtls(m_transcript_hash.current());
}
else
#endif
{
// send client finished handshake message (still using handshake traffic secrets)
send_handshake_message(m_handshake_state.sent(Finished_13(m_cipher_state.get(),
m_transcript_hash.current())));

// derives the application traffic secrets and _replaces_ the handshake traffic secrets
// Note: this MUST happen AFTER the client finished message was sent!
m_cipher_state->advance_with_server_finished(m_transcript_hash.previous());
m_cipher_state->advance_with_client_finished(m_transcript_hash.current());
}

// TODO: save session and invoke tls_session_established callback

Expand Down
8 changes: 8 additions & 0 deletions src/lib/tls/tls13/tls_handshake_state_13.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,12 @@ Finished_13& Handshake_State_13_Base::store(Finished_13 finished, const bool fro
return target.value();
}

#if defined(BOTAN_HAS_KEMTLS)
KEM_Encapsulation& Handshake_State_13_Base::store(KEM_Encapsulation kem_ct, const bool)
{
m_client_kem_encapsulation = std::move(kem_ct);
return m_client_kem_encapsulation.value();
}
#endif

}
12 changes: 12 additions & 0 deletions src/lib/tls/tls13/tls_handshake_state_13.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class BOTAN_TEST_API Handshake_State_13_Base
const Finished_13& client_finished() const { return get(m_client_finished); }
const Finished_13& server_finished() const { return get(m_server_finished); }

#if defined(BOTAN_HAS_KEMTLS)
const KEM_Encapsulation& client_kem_encapsulation() const { return get(m_client_kem_encapsulation); }
#endif

protected:
Handshake_State_13_Base(Connection_Side whoami) : m_side(whoami) {}

Expand All @@ -53,6 +57,10 @@ class BOTAN_TEST_API Handshake_State_13_Base
Certificate_Verify_13& store(Certificate_Verify_13 certificate_verify, const bool from_peer);
Finished_13& store(Finished_13 finished, const bool from_peer);

#if defined(BOTAN_HAS_KEMTLS)
KEM_Encapsulation& store(KEM_Encapsulation kem_ct, const bool from_peer);
#endif

private:
template<typename MessageT>
const MessageT& get(const std::optional<MessageT>& opt) const
Expand Down Expand Up @@ -81,6 +89,10 @@ class BOTAN_TEST_API Handshake_State_13_Base
std::optional<Certificate_Verify_13> m_server_verify;
std::optional<Finished_13> m_server_finished;
std::optional<Finished_13> m_client_finished;

#if defined(BOTAN_HAS_KEMTLS)
std::optional<KEM_Encapsulation> m_client_kem_encapsulation;
#endif
};
}

Expand Down
Loading

0 comments on commit 32b2ad1

Please sign in to comment.