Skip to content

Commit

Permalink
Merge pull request #3771 from Rohde-Schwarz/feature/rfc7250
Browse files Browse the repository at this point in the history
Feature: RawPublicKey authentication in TLS 1.3 (RFC 7250)
  • Loading branch information
reneme authored Nov 16, 2023
2 parents dc4ac41 + ba11760 commit 20279c6
Show file tree
Hide file tree
Showing 41 changed files with 1,381 additions and 159 deletions.
24 changes: 21 additions & 3 deletions src/cli/tls_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Callbacks : public Botan::TLS::Callbacks {

std::ostream& output();
bool flag_set(const std::string& flag_name) const;
std::string get_arg(const std::string& arg_name) const;
void send(std::span<const uint8_t> buffer);

int peer_closed() const { return m_peer_closed; }
Expand Down Expand Up @@ -75,6 +76,16 @@ class Callbacks : public Botan::TLS::Callbacks {
}
}

void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key,
Botan::Usage_Type /* usage */,
std::string_view /* hostname */,
const Botan::TLS::Policy& /* policy */) override {
const auto fingerprint = raw_public_key.fingerprint_public("SHA-256");
const auto trusted = (fingerprint == get_arg("trusted-pubkey-sha256"));
output() << "Raw Public Key (" << fingerprint
<< ") validation status: " << (trusted ? "trusted" : "NOT trusted") << "\n";
}

void tls_session_activated() override { output() << "Handshake complete\n"; }

void tls_session_established(const Botan::TLS::Session_Summary& session) override {
Expand Down Expand Up @@ -148,9 +159,10 @@ class TLS_Client final : public Command {
TLS_Client() :
Command(
"tls_client host --port=443 --print-certs --policy=default "
"--skip-system-cert-store --trusted-cas= --tls-version=default "
"--session-db= --session-db-pass= --next-protocols= --type=tcp "
"--client-cert= --client-cert-key= --psk= --psk-identity= --psk-prf=SHA-256 --debug") {
"--skip-system-cert-store --trusted-cas= --trusted-pubkey-sha256= "
"--tls-version=default --session-db= --session-db-pass= "
"--next-protocols= --type=tcp --client-cert= --client-cert-key= "
"--psk= --psk-identity= --psk-prf=SHA-256 --debug") {
init_sockets();
}

Expand All @@ -177,6 +189,7 @@ class TLS_Client final : public Command {
const std::string next_protos = get_arg("next-protocols");
const bool use_system_cert_store = flag_set("skip-system-cert-store") == false;
const std::string trusted_CAs = get_arg("trusted-cas");
const std::string trusted_pubkey_sha256 = get_arg("trusted-pubkey-sha256");
const auto tls_version = get_arg("tls-version");

if(!sessions_db.empty()) {
Expand Down Expand Up @@ -343,6 +356,7 @@ class TLS_Client final : public Command {

public:
using Command::flag_set;
using Command::get_arg;
using Command::output;

void send(std::span<const uint8_t> buf) const {
Expand Down Expand Up @@ -421,6 +435,10 @@ bool Callbacks::flag_set(const std::string& flag_name) const {
return m_client_command.flag_set(flag_name);
}

std::string Callbacks::get_arg(const std::string& arg_name) const {
return m_client_command.get_arg(arg_name);
}

void Callbacks::send(std::span<const uint8_t> buffer) {
m_client_command.send(buffer);
}
Expand Down
66 changes: 50 additions & 16 deletions src/cli/tls_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <botan/hex.h>
#include <botan/pkcs8.h>
#include <botan/tls_policy.h>
#include <botan/x509_key.h>
#include <botan/x509self.h>
#include <fstream>
#include <memory>
Expand Down Expand Up @@ -42,13 +43,23 @@ inline std::string maybe_hex_encode(std::string_view v) {

class Basic_Credentials_Manager : public Botan::Credentials_Manager {
protected:
void load_credentials(const std::string& crt, const std::string& key) {
Certificate_Info cert;

void load_credentials(const std::string& cred, const std::string& key) {
Botan::DataSource_Stream key_in(key);
cert.key = Botan::PKCS8::load_key(key_in);
auto privkey = Botan::PKCS8::load_key(key_in);

Botan::DataSource_Stream in(crt);
// first try to read @p cred as a public key
try {
auto pubkey = Botan::X509::load_key(cred);
m_raw_pubkey = {std::exchange(privkey, {}), std::move(pubkey)};
return;
} catch(const Botan::Decoding_Error&) {}

// ... then try again assuming that @p cred contains a certificate chain
BOTAN_ASSERT_NONNULL(privkey);
Certificate_Info cert;
cert.key = std::move(privkey);

Botan::DataSource_Stream in(cred);
while(!in.end_of_data()) {
try {
cert.certs.push_back(Botan::X509_Certificate(in));
Expand All @@ -59,13 +70,13 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {

// TODO: attempt to validate chain ourselves

m_creds.push_back(cert);
m_certs.push_back(cert);
}

public:
Basic_Credentials_Manager(bool use_system_store,
const std::string& ca_path,
std::optional<std::string> client_crt = std::nullopt,
std::optional<std::string> client_cred = std::nullopt,
std::optional<std::string> client_key = std::nullopt,
std::optional<Botan::secure_vector<uint8_t>> psk = std::nullopt,
std::optional<std::string> psk_identity = std::nullopt,
Expand All @@ -81,11 +92,11 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {
m_certstores.push_back(std::make_shared<Botan::Certificate_Store_In_Memory>(ca_path));
}

BOTAN_ARG_CHECK(client_crt.has_value() == client_key.has_value(),
BOTAN_ARG_CHECK(client_cred.has_value() == client_key.has_value(),
"either provide both client certificate and key or neither");

if(client_crt.has_value() && client_key.has_value()) {
load_credentials(client_crt.value(), client_key.value());
if(client_cred.has_value() && client_key.has_value()) {
load_credentials(client_cred.value(), client_key.value());
}

#if defined(BOTAN_HAS_CERTSTOR_SYSTEM)
Expand All @@ -97,7 +108,7 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {
#endif
}

Basic_Credentials_Manager(const std::string& server_crt,
Basic_Credentials_Manager(const std::string& server_cred,
const std::string& server_key,
std::optional<Botan::secure_vector<uint8_t>> psk = std::nullopt,
std::optional<std::string> psk_identity = std::nullopt,
Expand All @@ -109,7 +120,7 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {
// the Hash algorithm MUST be set when the PSK is established or
// default to SHA-256 if no such algorithm is defined.
m_psk_prf(psk_prf.value_or("SHA-256")) {
load_credentials(server_crt, server_key);
load_credentials(server_cred, server_key);
}

std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string& type,
Expand Down Expand Up @@ -138,14 +149,14 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {

if(type == "tls-client") {
for(const auto& dn : acceptable_cas) {
for(const auto& cred : m_creds) {
for(const auto& cred : m_certs) {
if(dn == cred.certs[0].issuer_dn()) {
return cred.certs;
}
}
}
} else if(type == "tls-server") {
for(const auto& i : m_creds) {
for(const auto& i : m_certs) {
if(std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) {
continue;
}
Expand All @@ -161,10 +172,18 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {
return {};
}

std::shared_ptr<Botan::Public_Key> find_raw_public_key(const std::vector<std::string>& algos,
const std::string& type,
const std::string& hostname) override {
BOTAN_UNUSED(type, hostname);
return (algos.empty() || value_exists(algos, m_raw_pubkey.public_key->algo_name())) ? m_raw_pubkey.public_key
: nullptr;
}

std::shared_ptr<Botan::Private_Key> private_key_for(const Botan::X509_Certificate& cert,
const std::string& /*type*/,
const std::string& /*context*/) override {
for(const auto& i : m_creds) {
for(const auto& i : m_certs) {
if(cert == i.certs[0]) {
return i.key;
}
Expand All @@ -173,6 +192,14 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {
return nullptr;
}

std::shared_ptr<Botan::Private_Key> private_key_for(const Botan::Public_Key& raw_public_key,
const std::string& /*type*/,
const std::string& /*context*/) override {
return (m_raw_pubkey.public_key->fingerprint_public() == raw_public_key.fingerprint_public())
? m_raw_pubkey.private_key
: nullptr;
}

std::string psk_identity(const std::string& type,
const std::string& context,
const std::string& identity_hint) override {
Expand Down Expand Up @@ -208,7 +235,14 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager {
std::shared_ptr<Botan::Private_Key> key;
};

std::vector<Certificate_Info> m_creds;
struct RawPublicKey_Info {
std::shared_ptr<Botan::Private_Key> private_key;
std::shared_ptr<Botan::Public_Key> public_key;
};

std::vector<Certificate_Info> m_certs;
RawPublicKey_Info m_raw_pubkey;

std::vector<std::shared_ptr<Botan::Certificate_Store>> m_certstores;
std::optional<Botan::secure_vector<uint8_t>> m_psk;
std::optional<std::string> m_psk_identity;
Expand Down
16 changes: 12 additions & 4 deletions src/cli/tls_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ class Callbacks : public Botan::TLS::Callbacks {
return "echo/0.1";
}

void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key,
Botan::Usage_Type /* usage */,
std::string_view /* hostname */,
const Botan::TLS::Policy& /* policy */) override {
const auto fingerprint = raw_public_key.fingerprint_public("SHA-256");
output() << "received Raw Public Key (" << fingerprint << ")\n";
}

private:
TLS_Server& m_server_command;
std::string m_line_buf;
Expand All @@ -105,11 +113,11 @@ class TLS_Server final : public Command {
#if defined(BOTAN_SO_SOCKETID)
TLS_Server() :
Command(
"tls_server cert key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --socket-id=0")
"tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --socket-id=0")
#else
TLS_Server() :
Command(
"tls_server cert key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0")
"tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0")
#endif
{
init_sockets();
Expand All @@ -127,7 +135,7 @@ class TLS_Server final : public Command {
std::string description() const override { return "Accept TLS/DTLS connections from TLS/DTLS clients"; }

void go() override {
const std::string server_crt = get_arg("cert");
const std::string server_cred = get_arg("cert-or-pubkey");
const std::string server_key = get_arg("key");
const uint16_t port = get_arg_u16("port");
const size_t max_clients = get_arg_sz("max-clients");
Expand Down Expand Up @@ -158,7 +166,7 @@ class TLS_Server final : public Command {
auto session_manager =
std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared()); // TODO sqlite3
auto creds =
std::make_shared<Basic_Credentials_Manager>(server_crt, server_key, std::move(psk), psk_identity, psk_prf);
std::make_shared<Basic_Credentials_Manager>(server_cred, server_key, std::move(psk), psk_identity, psk_prf);
auto callbacks = std::make_shared<Callbacks>(*this);

output() << "Listening for new connections on " << transport << " port " << port << std::endl;
Expand Down
14 changes: 13 additions & 1 deletion src/lib/tls/credentials_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ std::vector<X509_Certificate> Credentials_Manager::find_cert_chain(
return cert_chain(key_types, cert_signature_schemes, type, context);
}

std::shared_ptr<Public_Key> Credentials_Manager::find_raw_public_key(const std::vector<std::string>& /* key_types */,
const std::string& /* type */,
const std::string& /* context */) {
return nullptr;
}

std::vector<X509_Certificate> Credentials_Manager::cert_chain(const std::vector<std::string>& /*unused*/,
const std::vector<AlgorithmIdentifier>& /*unused*/,
const std::string& /*unused*/,
Expand All @@ -105,7 +111,13 @@ std::vector<X509_Certificate> Credentials_Manager::cert_chain_single_type(
std::shared_ptr<Private_Key> Credentials_Manager::private_key_for(const X509_Certificate& /*unused*/,
const std::string& /*unused*/,
const std::string& /*unused*/) {
return std::shared_ptr<Private_Key>();
return nullptr;
}

std::shared_ptr<Private_Key> Credentials_Manager::private_key_for(const Public_Key& /* raw_public_key */,
const std::string& /* type */,
const std::string& /* context */) {
return nullptr;
}

secure_vector<uint8_t> Credentials_Manager::session_ticket_key() {
Expand Down
29 changes: 29 additions & 0 deletions src/lib/tls/credentials_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ class BOTAN_PUBLIC_API(2, 0) Credentials_Manager {
const std::string& type,
const std::string& context);

/**
* Return a raw public key to be used for authentication or nullptr if no
* public key was found.
*
* It is assumed that the caller can get the private key of the leaf with
* private_key_for().
*
* @param key_types specifies the key types desired ("RSA", "DSA",
* "ECDSA", etc), or empty if there is no preference by
* the caller.
* @param type specifies the type of operation occurring
* @param context specifies a context relative to type.
*/
virtual std::shared_ptr<Public_Key> find_raw_public_key(const std::vector<std::string>& key_types,
const std::string& type,
const std::string& context);

/**
* Return a cert chain we can use, ordered from leaf to root,
* or else an empty vector.
Expand Down Expand Up @@ -134,6 +151,18 @@ class BOTAN_PUBLIC_API(2, 0) Credentials_Manager {
const std::string& type,
const std::string& context);

/**
* This function should either return nullptr or throw an exception if
* the key is unavailable.
*
* @return private key associated with this raw public key if we should
* use it with this context. @p raw_public_key was returned by
* find_raw_public_key()
*/
virtual std::shared_ptr<Private_Key> private_key_for(const Public_Key& raw_public_key,
const std::string& type,
const std::string& context);

/**
* Provides a secret value to encrypt session tickets for stateless
* session resumptions. The default implementation returns an empty
Expand Down
16 changes: 9 additions & 7 deletions src/lib/tls/msg_cert_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,14 @@ Certificate_Verify_13::Certificate_Verify_13(const Certificate_13& certificate_m
m_side(whoami) {
BOTAN_ASSERT_NOMSG(!certificate_msg.empty());

const auto private_key = creds_mgr.private_key_for(
certificate_msg.leaf(), m_side == Connection_Side::Client ? "tls-client" : "tls-server", std::string(hostname));
const auto op_type = (m_side == Connection_Side::Client) ? "tls-client" : "tls-server";
const auto context = std::string(hostname);

const auto private_key = (certificate_msg.has_certificate_chain())
? creds_mgr.private_key_for(certificate_msg.leaf(), op_type, context)
: creds_mgr.private_key_for(*certificate_msg.public_key(), op_type, context);
if(!private_key) {
throw TLS_Exception(Alert::InternalError, "Application did not provide a private key for its certificate");
throw TLS_Exception(Alert::InternalError, "Application did not provide a private key for its credential");
}

m_scheme = choose_signature_scheme(*private_key, policy.allowed_signature_schemes(), peer_allowed_schemes);
Expand All @@ -177,21 +180,20 @@ Certificate_Verify_13::Certificate_Verify_13(const std::vector<uint8_t>& buf, co
/*
* Verify a Certificate Verify message
*/
bool Certificate_Verify_13::verify(const X509_Certificate& cert,
bool Certificate_Verify_13::verify(const Public_Key& public_key,
Callbacks& callbacks,
const Transcript_Hash& transcript_hash) const {
BOTAN_ASSERT_NOMSG(m_scheme.is_available());

// RFC 8446 4.2.3
// The keys found in certificates MUST [...] be of appropriate type for
// the signature algorithms they are used with.
if(m_scheme.key_algorithm_identifier() != cert.subject_public_key_algo()) {
if(m_scheme.key_algorithm_identifier() != public_key.algorithm_identifier()) {
throw TLS_Exception(Alert::IllegalParameter, "Signature algorithm does not match certificate's public key");
}

const auto key = cert.subject_public_key();
const bool signature_valid = callbacks.tls_verify_message(
*key, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, transcript_hash), m_signature);
public_key, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, transcript_hash), m_signature);

#if defined(BOTAN_UNSAFE_FUZZER_MODE)
BOTAN_UNUSED(signature_valid);
Expand Down
7 changes: 7 additions & 0 deletions src/lib/tls/msg_client_hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,13 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols));
}

// RFC 7250 4.1
// In order to indicate the support of raw public keys, clients include
// the client_certificate_type and/or the server_certificate_type
// extensions in an extended client hello message.
m_data->extensions().add(new Client_Certificate_Type(policy.accepted_client_certificate_types()));
m_data->extensions().add(new Server_Certificate_Type(policy.accepted_server_certificate_types()));

if(policy.allow_tls12()) {
m_data->extensions().add(new Renegotiation_Extension());
m_data->extensions().add(new Session_Ticket_Extension());
Expand Down
Loading

0 comments on commit 20279c6

Please sign in to comment.