Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TLS 1.3] Hybrid PQ/T key establishment #3609

Merged
merged 10 commits into from
Oct 5, 2023
49 changes: 49 additions & 0 deletions doc/api_ref/tls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,55 @@ The ``TLS::Protocol_Version`` class represents a specific version:
Returns the latest version of the DTLS protocol known to the
library (currently DTLS v1.2)


Post-quantum-secure key exchange
--------------------------------

.. versionadded:: :: 3.2

Botan allows TLS 1.3 handshakes using both pure post-quantum secure algorithms
reneme marked this conversation as resolved.
Show resolved Hide resolved
or a hybrid key exchange that combines a classical and a post-quantum secure
algorithm. For the latter it implements the recent IETF
`draft-ietf-tls-hybrid-design
<https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design>`_.

Note that post-quantum key exchanges in TLS 1.3 are not conclusively
standardized. Therefore, the key exchange group identifiers used by various TLS
1.3 implementations are not consistent. Applications that wish to enable hybrid
key exchanges must enable the hybrid algorithms in their TLS policy. Override
`TLS::Policy::key_exchange_groups()` and return a list of the desired exchange
groups. For text-based policy configurations use the identifiers in parenthesis.

Currently, Botan supports the following post-quantum secure key exchanges:

* used `in Open Quantum Safe <https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/oqs-kem-info.md>`_
(PQC algorithm without a classical algorithm)

* ``KYBER_512_R3`` ("Kyber-512-r3")
* ``KYBER_768_R3`` ("Kyber-768-r3")
* ``KYBER_1024_R3`` ("Kyber-1024-r3")

* used `in Open Quantum Safe <https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/oqs-kem-info.md>`_
(hybrid between Kyber and a classical ECDH algorithm)

* ``HYBRID_X25519_KYBER_512_R3_OQS`` ("x25519/Kyber-512-r3")
* ``HYBRID_X25519_KYBER_768_R3_OQS`` ("x25519/Kyber-768-r3")
* ``HYBRID_SECP256R1_KYBER_512_R3_OQS`` ("secp256r1/Kyber-512-r3")
* ``HYBRID_SECP384R1_KYBER_768_R3_OQS`` ("secp384r1/Kyber-768-r3")
* ``HYBRID_SECP521R1_KYBER_1024_R3_OQS`` ("secp521r1/Kyber-1024-r3")

* used `by Cloudflare <https://blog.cloudflare.com/post-quantum-for-all/>`_
(hybrid between Kyber and the classical X25519 algorithm)

* ``HYBRID_X25519_KYBER_512_R3_CLOUDFLARE`` ("x25519/Kyber-512-r3/cloudflare")
* ``HYBRID_X25519_KYBER_768_R3_CLOUDFLARE`` ("x25519/Kyber-768-r3/cloudflare")

Client Code Example
^^^^^^^^^^^^^^^^^^^^

.. literalinclude:: /../src/examples/tls_13_hybrid_key_exchange_client.cpp
:language: cpp

TLS Custom Key Exchange Mechanisms
----------------------------------------

Expand Down
10 changes: 10 additions & 0 deletions src/bogo_shim/bogo_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ std::string map_to_bogo_error(const std::string& e) {
{"Server resumed session and removed extended master secret", ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"},
{"Server resumed session but added extended master secret", ":RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION:"},
{"Server resumed session but with wrong version", ":OLD_SESSION_VERSION_NOT_RETURNED:"},
{"Server selected a group that is not compatible with the negotiated ciphersuite", ":WRONG_CURVE:"},
{"Server sent ECC curve prohibited by policy", ":WRONG_CURVE:"},
{"group was not advertised as supported", ":WRONG_CURVE:"},
{"group was already offered", ":WRONG_CURVE:"},
Expand Down Expand Up @@ -984,6 +985,15 @@ class Shim_Policy final : public Botan::TLS::Policy {
if(std::find(supported_groups.cbegin(), supported_groups.cend(), group) != supported_groups.end()) {
groups.push_back(group);
}

// Given that this is still a draft-standard, we didn't add the
// hybrid groups to the default policy, yet.
//
// TODO: once `TLS::Policy::key_exchange_groups()` contains it by
// default, remove this explicit check.
if(group == Botan::TLS::Group_Params::HYBRID_X25519_KYBER_768_R3_OQS) {
groups.push_back(group);
}
}

return groups;
Expand Down
5 changes: 4 additions & 1 deletion src/bogo_shim/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
"ClientHelloPadding": "No support for client hello padding extension",
"TLSUnique*": "Not supported",
"*CECPQ2*": "Not implemented",
"*Kyber*": "Not implemented",
"PQExperimentSignal*": "Not implemented",
"*P-224*": "P-224 not supported in TLS",
"*V2ClientHello*": "No support for SSLv2 client hellos",
Expand All @@ -135,6 +134,10 @@
"Renegotiate-Client-UnfinishedWrite": "BoringSSL specific API test",
"FailEarlyCallback": "BoringSSL specific API test",

"NotJustKyberKeyShare": "BoringSSL specific policy test (we may offer solo PQ/T groups)",
"KyberKeyShareIncludedSecond": "BoringSSL specific policy test (we may offer solo PQ/T groups)",
"KyberKeyShareIncludedThird": "BoringSSL specific policy test (we may offer solo PQ/T groups)",

"ShimTicketRewritable": "Botan has a different ticket format",
"Resume-Server-DeclineCrossVersion*": "Botan has a different ticket format",
"Resume-Server-DeclineBadCipher*": "Botan has a different ticket format",
Expand Down
95 changes: 95 additions & 0 deletions src/examples/tls_13_hybrid_key_exchange_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include <botan/auto_rng.h>
#include <botan/certstor.h>
#include <botan/tls.h>

/**
* @brief Callbacks invoked by TLS::Channel.
*
* Botan::TLS::Callbacks is an abstract class.
* For improved readability, only the functions that are mandatory
* to implement are listed here. See src/lib/tls/tls_callbacks.h.
*/
class Callbacks : public Botan::TLS::Callbacks {
public:
void tls_emit_data(std::span<const uint8_t> data) override {
BOTAN_UNUSED(data);
// send data to tls server, e.g., using BSD sockets or boost asio
}

void tls_record_received(uint64_t seq_no, std::span<const uint8_t> data) override {
BOTAN_UNUSED(seq_no, data);
// process full TLS record received by tls server, e.g.,
// by passing it to the application
}

void tls_alert(Botan::TLS::Alert alert) override {
BOTAN_UNUSED(alert);
// handle a tls alert received from the tls server
}
};

/**
* @brief Credentials storage for the tls client.
*
* It returns a list of trusted CA certificates from a local directory.
* TLS client authentication is disabled. See src/lib/tls/credentials_manager.h.
*/
class Client_Credentials : public Botan::Credentials_Manager {
public:
std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string& type,
const std::string& context) override {
BOTAN_UNUSED(type, context);
// return a list of certificates of CAs we trust for tls server certificates,
// e.g., all the certificates in the local directory "cas"
return {&m_cert_store};
}

private:
Botan::Certificate_Store_In_Memory m_cert_store{"cas"};
};

class Client_Policy : public Botan::TLS::Default_Policy {
public:
// This needs to be overridden to enable the hybrid PQ/T groups
// additional to the default (classical) key exchange groups
std::vector<Botan::TLS::Group_Params> key_exchange_groups() const override {
auto groups = Botan::TLS::Default_Policy::key_exchange_groups();
groups.push_back(Botan::TLS::Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE);
groups.push_back(Botan::TLS::Group_Params::HYBRID_X25519_KYBER_512_R3_OQS);
return groups;
}

// Define that the client should exclusively pre-offer hybrid groups
// in its initial Client Hello.
std::vector<Botan::TLS::Group_Params> key_exchange_groups_to_offer() const override {
return {Botan::TLS::Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE,
Botan::TLS::Group_Params::HYBRID_X25519_KYBER_512_R3_OQS};
}
};

int main() {
// prepare all the parameters
auto rng = std::make_shared<Botan::AutoSeeded_RNG>();
auto callbacks = std::make_shared<Callbacks>();
auto session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
auto creds = std::make_shared<Client_Credentials>();
auto policy = std::make_shared<Botan::TLS::Strict_Policy>();

// open the tls connection
Botan::TLS::Client client(callbacks,
session_mgr,
creds,
policy,
rng,
Botan::TLS::Server_Information("botan.randombit.net", 443),
Botan::TLS::Protocol_Version::TLS_V12);

while(!client.is_closed()) {
// read data received from the tls server, e.g., using BSD sockets or boost asio
// ...

// send data to the tls server using client.send()
}

return 0;
}
4 changes: 4 additions & 0 deletions src/lib/pubkey/dh/dh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const BigInt& DH_PublicKey::get_int_field(std::string_view field) const {
return m_public_key->get_int_field(algo_name(), field);
}

const DL_Group& DH_PublicKey::group() const {
return m_public_key->group();
}

AlgorithmIdentifier DH_PublicKey::algorithm_identifier() const {
return AlgorithmIdentifier(object_identifier(), m_public_key->group().DER_encode(DL_Group_Format::ANSI_X9_42));
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/dh/dh.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class BOTAN_PUBLIC_API(2, 0) DH_PublicKey : public virtual Public_Key {

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

const DL_Group& group() const;

private:
friend class DH_PrivateKey;

Expand Down
2 changes: 1 addition & 1 deletion src/lib/pubkey/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ secure_vector<uint8_t> PK_Decryptor_EME::do_decrypt(uint8_t& valid_mask, const u
PK_KEM_Encryptor::PK_KEM_Encryptor(const Public_Key& key, std::string_view param, std::string_view provider) {
m_op = key.create_kem_encryption_op(param, provider);
if(!m_op) {
throw Invalid_Argument(fmt("Key type {} does not support KEM encryption"));
throw Invalid_Argument(fmt("Key type {} does not support KEM encryption", key.algo_name()));
reneme marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
36 changes: 19 additions & 17 deletions src/lib/pubkey/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <span>
#include <string>
#include <string_view>
#include <utility>

namespace Botan {

Expand Down Expand Up @@ -546,33 +547,36 @@ class BOTAN_PUBLIC_API(2, 0) PK_Decryptor_EME final : public PK_Decryptor {
};

/**
* Result of a key encapsulation operation.
*/
* Result of a key encapsulation operation.
*/
class KEM_Encapsulation final {
public:
KEM_Encapsulation(std::vector<uint8_t> encapsulated_shared_key, secure_vector<uint8_t> shared_key) :
m_encapsulated_shared_key(std::move(encapsulated_shared_key)), m_shared_key(std::move(shared_key)) {}

/**
* @returns the encapsulated shared secret (encrypted with the public key)
*/
* @returns the encapsulated shared secret (encrypted with the public key)
*/
const std::vector<uint8_t>& encapsulated_shared_key() const { return m_encapsulated_shared_key; }

/**
* @returns the plaintext shared secret
*/
* @returns the plaintext shared secret
*/
const secure_vector<uint8_t>& shared_key() const { return m_shared_key; }

/**
* @returns the pair (encapsulated key, key) extracted from @p kem
*/
static std::pair<std::vector<uint8_t>, secure_vector<uint8_t>> destructure(KEM_Encapsulation&& kem) {
return std::make_pair(std::exchange(kem.m_encapsulated_shared_key, {}), std::exchange(kem.m_shared_key, {}));
}

private:
friend class PK_KEM_Encryptor;

KEM_Encapsulation(size_t encapsulated_size, size_t shared_key_size) :
m_encapsulated_shared_key(encapsulated_size), m_shared_key(shared_key_size) {}

std::vector<uint8_t>& encapsulated_shared_key() { return m_encapsulated_shared_key; }

secure_vector<uint8_t>& shared_key() { return m_shared_key; }

private:
std::vector<uint8_t> m_encapsulated_shared_key;
secure_vector<uint8_t> m_shared_key;
Expand Down Expand Up @@ -651,13 +655,11 @@ class BOTAN_PUBLIC_API(2, 0) PK_KEM_Encryptor final {
KEM_Encapsulation encrypt(RandomNumberGenerator& rng,
size_t desired_shared_key_len = 32,
std::span<const uint8_t> salt = {}) {
KEM_Encapsulation result(encapsulated_key_length(), shared_key_length(desired_shared_key_len));
encrypt(std::span{result.encapsulated_shared_key()},
std::span{result.shared_key()},
rng,
desired_shared_key_len,
salt);
return result;
std::vector<uint8_t> encapsulated_shared_key(encapsulated_key_length());
secure_vector<uint8_t> shared_key(shared_key_length(desired_shared_key_len));

encrypt(std::span{encapsulated_shared_key}, std::span{shared_key}, rng, desired_shared_key_len, salt);
return KEM_Encapsulation(std::move(encapsulated_shared_key), std::move(shared_key));
}

/**
Expand Down
35 changes: 23 additions & 12 deletions src/lib/tls/msg_client_hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,27 @@ std::vector<uint8_t> Hello_Request::serialize() const {
return std::vector<uint8_t>();
}

void Client_Hello_12::add_tls12_supported_groups_extensions(const Policy& policy) {
// RFC 7919 3.
// A client that offers a group MUST be able and willing to perform a DH
// key exchange using that group.
//
// We don't support hybrid key exchange in TLS 1.2
const std::vector<Group_Params> kex_groups = policy.key_exchange_groups();
std::vector<Group_Params> compatible_kex_groups;
std::copy_if(kex_groups.begin(), kex_groups.end(), std::back_inserter(compatible_kex_groups), [](const auto group) {
return !is_post_quantum(group);
});

auto supported_groups = std::make_unique<Supported_Groups>(std::move(compatible_kex_groups));

if(!supported_groups->ec_groups().empty()) {
m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
}

m_data->extensions().add(std::move(supported_groups));
}

Client_Hello_12::Client_Hello_12(std::unique_ptr<Client_Hello_Internal> data) : Client_Hello(std::move(data)) {
if(offered_suite(static_cast<uint16_t>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) {
if(Renegotiation_Extension* reneg = m_data->extensions().get<Renegotiation_Extension>()) {
Expand Down Expand Up @@ -478,11 +499,7 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
m_data->extensions().add(new Certificate_Status_Request({}, {}));
}

auto supported_groups = std::make_unique<Supported_Groups>(policy.key_exchange_groups());
if(!supported_groups->ec_groups().empty()) {
m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
}
m_data->extensions().add(supported_groups.release());
add_tls12_supported_groups_extensions(policy);

m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) {
Expand Down Expand Up @@ -561,13 +578,7 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
m_data->extensions().add(new Certificate_Status_Request({}, {}));
}

auto supported_groups = std::make_unique<Supported_Groups>(policy.key_exchange_groups());

if(!supported_groups->ec_groups().empty()) {
m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
}

m_data->extensions().add(supported_groups.release());
add_tls12_supported_groups_extensions(policy);

m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/tls/tls12/msg_client_kex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ Client_Key_Exchange::Client_Key_Exchange(Handshake_IO& io,
const Group_Params curve_id = static_cast<Group_Params>(reader.get_uint16_t());
const std::vector<uint8_t> peer_public_value = reader.get_range<uint8_t>(1, 1, 255);

if(!is_ecdh(curve_id) && !is_x25519(curve_id)) {
throw TLS_Exception(Alert::HandshakeFailure,
"Server selected a group that is not compatible with the negotiated ciphersuite");
}

if(policy.choose_key_exchange_group({curve_id}, {}) != curve_id) {
throw TLS_Exception(Alert::HandshakeFailure, "Server sent ECC curve prohibited by policy");
}
Expand Down
8 changes: 4 additions & 4 deletions src/lib/tls/tls13/tls_extensions_key_share.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ class Key_Share_Entry {
const Policy& policy,
Callbacks& cb,
RandomNumberGenerator& rng) {
auto [encapsulated_bytes, shared_secret] =
cb.tls_kem_encapsulate(m_group, client_share.m_key_exchange, rng, policy);
m_key_exchange = std::move(encapsulated_bytes);
return shared_secret;
auto [encapsulated_shared_key, shared_key] =
KEM_Encapsulation::destructure(cb.tls_kem_encapsulate(m_group, client_share.m_key_exchange, rng, policy));
m_key_exchange = std::move(encapsulated_shared_key);
return std::move(shared_key);
}

/**
Expand Down
Loading