Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include "helpers.hpp"
#include "trusted_ca_keys.hpp"
#include <cstddef>
std::ostream& operator<<(std::ostream& out, const openssl::certificate_ptr& obj) {
const auto subject = openssl::certificate_subject(obj.get());
if (!subject.empty()) {
out << "subject:";
for (const auto& itt : subject) {
out << " " << itt.first << ":" << itt.second;
}
}
return out;
}
std::ostream& operator<<(std::ostream& out, const openssl::sha_1_digest_t& obj) {
const auto sav = out.flags();
for (const auto& c : obj) {
out << std::setw(2) << std::setfill('0') << std::hex << static_cast<std::uint32_t>(c);
}
out.flags(sav);
return out;
}
std::ostream& operator<<(std::ostream& out, const tls::trusted_ca_keys::trusted_ca_keys_t& obj) {
out << "trusted ca keys: pre-agreed: " << obj.pre_agreed << std::endl;
if (!obj.cert_sha1_hash.empty()) {
for (const auto& hash : obj.cert_sha1_hash) {
out << " certificate hash: " << hash << std::endl;
}
}
if (!obj.key_sha1_hash.empty()) {
for (const auto& hash : obj.key_sha1_hash) {
out << " subject key hash: " << hash << std::endl;
}
}
return out;
}
std::ostream& operator<<(std::ostream& out, const openssl::DER& obj) {
const auto sav = out.flags();
const auto* ptr = obj.get();
for (std::size_t i = 0; i < obj.size(); i++) {
out << std::setw(2) << std::setfill('0') << std::hex << static_cast<std::uint32_t>(*ptr++);
}
out.flags(sav);
return out;
}

View File

@@ -0,0 +1,174 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#ifndef EXTENSIONS_HELPERS_
#define EXTENSIONS_HELPERS_
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <string>
#ifdef UNIT_TEST
#include <iostream>
#endif
#include <everest/tls/openssl_util.hpp>
namespace tls {
using openssl::log_warning;
/**
* \brief update position and remaining by amount
* \param[inout] ptr the pointer to increment
* \param[inout] remaining the value to decrement
*/
constexpr void update_position(const std::uint8_t*& ptr, std::int32_t& remaining, std::size_t amount) {
ptr += amount;
remaining -= amount;
}
/**
* \brief update position and remaining by amount
* \param[inout] ptr the pointer to increment
* \param[inout] remaining the value to decrement
*/
constexpr void update_position(std::uint8_t*& ptr, std::int32_t& remaining, std::size_t amount) {
ptr += amount;
remaining -= amount;
}
/**
* \brief copy structure from data pointer
* \param[out] dest the destination structure
* \param[inout] ptr the pointer the start of the data, updated to point to the
* next byte (ptr += sizeof(dest))
* \param[inout] remaining updated with the remaining number of bytes
* (remaining -= sizeof(dest))
* \param[in] err_message to log on error
* \return true when structure populated from data
*/
template <typename T>
constexpr bool struct_copy(T& dest, const std::uint8_t*& ptr, std::int32_t& remaining, const std::string& err_message) {
bool bResult{false};
if (remaining < sizeof(T)) {
log_warning(err_message);
} else {
std::memcpy(&dest, ptr, sizeof(T));
update_position(ptr, remaining, sizeof(T));
bResult = true;
}
return bResult;
}
/**
* \brief copy structure to data pointer
* \param[out] ptr the destination pointer, updated to point to the
* next byte (ptr += sizeof(src))
* \param[inout] src the source structure
* \param[inout] remaining updated with the remaining number of bytes
* (remaining -= sizeof(src))
* \param[in] err_message to log on error
* \return true when ptr populated from structure
*/
template <typename T>
constexpr bool struct_copy(std::uint8_t*& ptr, const T& src, std::int32_t& remaining, const std::string& err_message) {
bool bResult{false};
if (remaining < sizeof(T)) {
log_warning(err_message);
} else {
std::memcpy(ptr, &src, sizeof(T));
update_position(ptr, remaining, sizeof(T));
bResult = true;
}
return bResult;
}
/**
* \brief copy DER to data pointer
* \param[out] ptr the destination pointer, updated to point to the
* next byte (ptr += src.size())
* \param[inout] src the DER source object
* \param[inout] remaining updated with the remaining number of bytes
* (remaining -= src.size())
* \param[in] err_message to log on error
* \return true when ptr populated from structure
*/
inline bool der_copy(std::uint8_t*& ptr, const openssl::DER& src, std::int32_t& remaining,
const std::string& err_message) {
bool bResult{false};
if (remaining < src.size()) {
log_warning(err_message + ' ' + std::to_string(remaining) + '/' + std::to_string(src.size()));
} else {
std::memcpy(ptr, src.get(), src.size());
update_position(ptr, remaining, src.size());
bResult = true;
}
return bResult;
}
/**
* \brief convert a big endian 3 byte (24 bit) unsigned value to uint32
* \param[in] ptr the pointer to the most significant byte
* \return the interpreted value
*/
constexpr std::uint32_t uint24(const std::uint8_t* ptr) {
return (static_cast<std::uint32_t>(ptr[0]) << 16U) | (static_cast<std::uint32_t>(ptr[1]) << 8U) |
static_cast<std::uint32_t>(ptr[2]);
}
/**
* \brief convert a uint32 to big endian 3 byte (24 bit) value
* \param[in] ptr the pointer to the most significant byte
* \param[in] value the 24 bit value
*/
constexpr void uint24(std::uint8_t* ptr, std::uint32_t value) {
ptr[0] = (value >> 16U) & 0xffU;
ptr[1] = (value >> 8U) & 0xffU;
ptr[2] = value & 0xffU;
}
/**
* \brief convert a big endian 2 byte (16 bit) unsigned value to uint16
* \param[in] ptr the pointer to the most significant byte
* \return the interpreted value
*/
constexpr std::uint16_t uint16(const std::uint8_t* ptr) {
return (static_cast<std::uint32_t>(ptr[0]) << 8U) | static_cast<std::uint32_t>(ptr[1]);
}
/**
* \brief convert a uint16 to big endian 2 byte (16 bit) value
* \param[in] ptr the pointer to the most significant byte
* \param[in] value the 16 bit value
*/
constexpr void uint16(std::uint8_t* ptr, std::uint32_t value) {
ptr[0] = (value >> 8U) & 0xffU;
ptr[1] = value & 0xffU;
}
template <typename T> std::string to_string(const T& digest) {
std::stringstream string_stream;
string_stream << std::hex;
for (const auto& c : digest) {
string_stream << std::setw(2) << std::setfill('0') << static_cast<int>(c);
}
return string_stream.str();
}
} // namespace tls
#ifdef UNIT_TEST
namespace tls::trusted_ca_keys {
struct trusted_ca_keys_t;
}
std::ostream& operator<<(std::ostream& out, const openssl::certificate_ptr& obj);
std::ostream& operator<<(std::ostream& out, const openssl::sha_1_digest_t& obj);
std::ostream& operator<<(std::ostream& out, const tls::trusted_ca_keys::trusted_ca_keys_t& obj);
std::ostream& operator<<(std::ostream& out, const openssl::DER& obj);
#endif
#endif // EXTENSIONS_HELPERS_

View File

@@ -0,0 +1,587 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include "extensions/status_request.hpp"
#include "helpers.hpp"
#include <cstdint>
#include <everest/tls/openssl_util.hpp>
#include <cassert>
#include <limits>
#include <openssl/ocsp.h>
#include <openssl/ssl.h>
#include <openssl/tls1.h>
#include <string>
#ifdef TLSEXT_STATUSTYPE_ocsp_multi
#define OPENSSL_PATCHED
#endif
using openssl::log_debug;
using openssl::log_error;
namespace {
/**
* \brief load OCSP response from file
* \param[in] filename is the file to read
* \return a pointer to the OCSP reponse which will need to be freed
* using OCSP_RESPONSE_free()
*/
OCSP_RESPONSE* load_ocsp(const char* filename) {
// update the cache
OCSP_RESPONSE* resp{nullptr};
if (filename != nullptr) {
BIO* bio_file = BIO_new_file(filename, "r");
if (bio_file == nullptr) {
log_error(std::string("BIO_new_file: ") + filename);
} else {
resp = d2i_OCSP_RESPONSE_bio(bio_file, nullptr);
BIO_free(bio_file);
}
if (resp == nullptr) {
log_error("d2i_OCSP_RESPONSE_bio");
}
}
return resp;
}
} // namespace
namespace tls {
using OCSP_RESPONSE_ptr = std::shared_ptr<OCSP_RESPONSE>;
struct ocsp_cache_ctx {
std::map<OcspCache::digest_t, OCSP_RESPONSE_ptr> cache;
};
// ----------------------------------------------------------------------------
// OcspCache
OcspCache::OcspCache() : m_context(std::make_unique<ocsp_cache_ctx>()) {
}
OcspCache::~OcspCache() = default;
bool OcspCache::load(const ocsp_entry_list_t& filenames) {
assert(m_context != nullptr);
bool bResult{true};
if (filenames.empty()) {
// clear the cache
std::lock_guard lock(mux);
m_context->cache.clear();
} else {
std::map<digest_t, OCSP_RESPONSE_ptr> updates;
for (const auto& entry : filenames) {
const auto& digest = std::get<digest_t>(entry);
const auto* filename = std::get<const char*>(entry);
OCSP_RESPONSE* resp{nullptr};
if (filename != nullptr) {
resp = load_ocsp(filename);
if (resp == nullptr) {
bResult = false;
}
}
if (resp != nullptr) {
updates[digest] = std::shared_ptr<OCSP_RESPONSE>(resp, &::OCSP_RESPONSE_free);
}
}
{
std::lock_guard lock(mux);
m_context->cache.swap(updates);
}
}
return bResult;
}
OcspCache::OcspResponse_t OcspCache::lookup(const digest_t& digest) {
assert(m_context != nullptr);
OcspResponse_t resp;
std::lock_guard lock(mux);
if (const auto itt = m_context->cache.find(digest); itt != m_context->cache.end()) {
resp = itt->second;
} else {
log_error("OcspCache::lookup: not in cache: " + to_string(digest));
}
return resp;
}
bool OcspCache::digest(digest_t& digest, const x509_st* cert) {
assert(cert != nullptr);
return openssl::certificate_sha_1(digest, cert);
}
namespace status_request {
// ----------------------------------------------------------------------------
// ServerStatusRequestV2
int ServerStatusRequestV2::s_index{-1};
ServerStatusRequestV2::ServerStatusRequestV2(OcspCache& cache) : m_cache(cache) {
if (s_index == -1) {
s_index = CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0, nullptr, nullptr, nullptr, nullptr);
}
}
bool ServerStatusRequestV2::init_ssl(SSL_CTX* ctx) {
bool bRes{true};
SSL_CTX_set_client_hello_cb(ctx, &client_hello_cb, nullptr);
if (SSL_CTX_set_tlsext_status_cb(ctx, &status_request_cb) != 1) {
log_error("SSL_CTX_set_tlsext_status_cb");
bRes = false;
}
if (SSL_CTX_set_tlsext_status_arg(ctx, this) != 1) {
log_error("SSL_CTX_set_tlsext_status_arg");
bRes = false;
}
// TLS 1.2 and below only - managed differently in TLS 1.3
constexpr int context_srv2 = SSL_EXT_TLS_ONLY | SSL_EXT_TLS1_2_AND_BELOW_ONLY | SSL_EXT_IGNORE_ON_RESUMPTION |
SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO;
if (SSL_CTX_add_custom_ext(ctx, TLSEXT_TYPE_status_request_v2, context_srv2, &status_request_v2_add,
&status_request_v2_free, this, &status_request_v2_cb, nullptr) != 1) {
log_error("SSL_CTX_add_custom_ext status_request_v2");
bRes = false;
}
return bRes;
}
bool ServerStatusRequestV2::set_ocsp_response(const digest_t& digest, SSL* ctx) {
bool bResult{false};
auto response = m_cache.lookup(digest);
if (response) {
unsigned char* der{nullptr};
auto len = i2d_OCSP_RESPONSE(response.get(), &der);
if (len > 0) {
bResult = SSL_set_tlsext_status_ocsp_resp(ctx, der, len) == 1;
if (bResult) {
SSL_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp);
} else {
log_error("SSL_set_tlsext_status_ocsp_resp");
OPENSSL_free(der);
}
}
}
return bResult;
}
int ServerStatusRequestV2::status_request_cb(SSL* ctx, void* object) {
// returns:
// - SSL_TLSEXT_ERR_OK response to client via SSL_set_tlsext_status_ocsp_resp
// - SSL_TLSEXT_ERR_NOACK no response to client
// - SSL_TLSEXT_ERR_ALERT_FATAL abort connection
bool bSet{false};
int result = SSL_TLSEXT_ERR_NOACK;
digest_t digest{};
if (ctx != nullptr) {
const auto* cert = SSL_get_certificate(ctx);
bSet = OcspCache::digest(digest, cert);
}
bool tls_1_3{false};
const auto* session = SSL_get0_session(ctx);
if (session != nullptr) {
tls_1_3 = SSL_SESSION_get_protocol_version(session) == TLS1_3_VERSION;
}
if (!tls_1_3) {
auto* flags_p = get_data(ctx);
if (flags_p != nullptr) {
/*
* if there is a status_request_v2 then don't provide a status_request response
* unless this is TLS 1.3 where status_request_v2 is deprecated (not to be used)
*/
if (flags_p->has_status_request_v2()) {
bSet = false;
result = SSL_TLSEXT_ERR_NOACK;
}
}
}
auto* ptr = reinterpret_cast<ServerStatusRequestV2*>(object);
if (bSet && (ptr != nullptr)) {
if (ptr->set_ocsp_response(digest, ctx)) {
result = SSL_TLSEXT_ERR_OK;
}
}
return result;
}
bool ServerStatusRequestV2::set_ocsp_v2_response(const digest_list_t& digests, SSL* ctx) {
/*
* There is no response in the extension. An additional handshake message is
* sent after the certificate (certificate status) that includes the
* actual response.
*/
/*
* s->ext.status_expected, set to 1 to include the certificate status message
* s->ext.status_type, ocsp(1), ocsp_multi(2)
* s->ext.ocsp.resp, set by SSL_set_tlsext_status_ocsp_resp
* s->ext.ocsp.resp_len, set by SSL_set_tlsext_status_ocsp_resp
*/
bool bResult{false};
#ifdef OPENSSL_PATCHED
if (ctx != nullptr) {
std::vector<std::pair<std::size_t, std::uint8_t*>> response_list;
std::size_t total_size{0};
for (const auto& digest : digests) {
auto response = m_cache.lookup(digest);
if (response) {
unsigned char* der{nullptr};
auto len = i2d_OCSP_RESPONSE(response.get(), &der);
if (len > 0) {
const std::size_t adjusted_len = len + 3;
total_size += adjusted_len;
// prefix the length of the DER encoded OCSP response
auto* der_len = static_cast<std::uint8_t*>(OPENSSL_malloc(adjusted_len));
if (der_len != nullptr) {
uint24(der_len, len);
std::memcpy(&der_len[3], der, len);
response_list.emplace_back(adjusted_len, der_len);
}
OPENSSL_free(der);
}
}
}
// don't include the extension when there are no OCSP responses
if (total_size > 0) {
std::size_t resp_len = total_size;
auto* resp = static_cast<std::uint8_t*>(OPENSSL_malloc(resp_len));
if (resp == nullptr) {
resp_len = 0;
} else {
std::size_t idx{0};
for (auto& entry : response_list) {
auto len = entry.first;
auto* der = entry.second;
std::memcpy(&resp[idx], der, len);
OPENSSL_free(der);
idx += len;
}
}
// SSL_set_tlsext_status_ocsp_resp sets the correct overall length
bResult = SSL_set_tlsext_status_ocsp_resp(ctx, resp, resp_len) == 1;
if (bResult) {
SSL_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp_multi);
SSL_set_tlsext_status_expected(ctx, 1);
} else {
log_error((std::string("SSL_set_tlsext_status_ocsp_resp")));
}
}
}
#endif // OPENSSL_PATCHED
return bResult;
}
int ServerStatusRequestV2::status_request_v2_add(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char** out, std::size_t* outlen, X509* cert,
std::size_t chainidx, int* alert, void* object) {
/*
* return values:
* - fatal, abort handshake and sent TLS Alert: result = -1 and *alert = alert value
* - do not include extension: result = 0
* - include extension: result = 1
*/
*out = nullptr;
*outlen = 0;
int result = 0;
#ifdef OPENSSL_PATCHED
digest_t digest{};
digest_list_t digest_chain;
if (ctx != nullptr) {
const auto* cert = SSL_get_certificate(ctx);
if (OcspCache::digest(digest, cert)) {
digest_chain.push_back(digest);
}
STACK_OF(X509) * chain{nullptr};
if (SSL_get0_chain_certs(ctx, &chain) != 1) {
log_error((std::string("SSL_get0_chain_certs")));
} else {
const auto num = sk_X509_num(chain);
for (std::size_t i = 0; i < num; i++) {
cert = sk_X509_value(chain, i);
if (OcspCache::digest(digest, cert)) {
digest_chain.push_back(digest);
}
}
}
}
auto* ptr = reinterpret_cast<ServerStatusRequestV2*>(object);
if (!digest_chain.empty() && (ptr != nullptr)) {
if (ptr->set_ocsp_v2_response(digest_chain, ctx)) {
result = 1;
}
}
#endif // OPENSSL_PATCHED
return result;
}
void ServerStatusRequestV2::status_request_v2_free(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char* out, void* object) {
OPENSSL_free(const_cast<unsigned char*>(out));
}
int ServerStatusRequestV2::status_request_v2_cb(Ssl* ctx, unsigned int ext_type, unsigned int context,
const unsigned char* data, std::size_t datalen, X509* cert,
std::size_t chainidx, int* alert, void* object) {
/*
* return values:
* - fatal, abort handshake and sent TLS Alert: result = 0 or negative and *alert = alert value
* - success: result = 1
*/
// TODO(james-ctc): check requested type std, or multi
return 1;
}
int ServerStatusRequestV2::client_hello_cb(SSL* ctx, int* alert, void* object) {
/*
* return values:
* - fatal, abort handshake and sent TLS Alert: result = 0 or negative and *alert = alert value
* - success: result = 1
*/
auto* flags_p = get_data(ctx);
if (flags_p != nullptr) {
int* extensions{nullptr};
std::size_t length{0};
if (SSL_client_hello_get1_extensions_present(ctx, &extensions, &length) == 1) {
std::string logstr("Extensions:");
for (std::size_t i = 0; i < length; i++) {
logstr += " " + std::to_string(extensions[i]);
if (extensions[i] == TLSEXT_TYPE_status_request) {
flags_p->status_request_received();
} else if (extensions[i] == TLSEXT_TYPE_status_request_v2) {
flags_p->status_request_v2_received();
}
}
log_debug(logstr);
OPENSSL_free(extensions);
}
}
return 1;
}
void ServerStatusRequestV2::set_data(SSL* ctx, StatusFlags* ptr) {
assert(ctx != nullptr);
SSL_set_ex_data(ctx, s_index, ptr);
}
StatusFlags* ServerStatusRequestV2::get_data(SSL* ctx) {
assert(ctx != nullptr);
return reinterpret_cast<StatusFlags*>(SSL_get_ex_data(ctx, s_index));
}
bool ClientStatusRequestV2::print_ocsp_response(FILE* stream, const unsigned char*& response, std::size_t length) {
OCSP_RESPONSE* ocsp{nullptr};
if (response != nullptr) {
ocsp = d2i_OCSP_RESPONSE(nullptr, &response, static_cast<std::int64_t>(length));
if (ocsp == nullptr) {
log_error("d2i_OCSP_RESPONSE: decode error");
} else {
BIO* bio_out = BIO_new_fp(stream, BIO_NOCLOSE);
OCSP_RESPONSE_print(bio_out, ocsp, 0);
OCSP_RESPONSE_free(ocsp);
BIO_free(bio_out);
}
}
return ocsp != nullptr;
}
int ClientStatusRequestV2::status_request_cb(SSL* ctx) {
/*
* This callback is called when status_request or status_request_v2 extensions
* were present in the Client Hello. It doesn't mean that the extension is in
* the Server Hello SSL_get_tlsext_status_ocsp_resp() returns -1 in that case
*/
/*
* The callback when used on the client side should return
* a negative value on error,
* 0 if the response is not acceptable (in which case the handshake will fail), or
* a positive value if it is acceptable.
*/
int result{1};
const unsigned char* response{nullptr};
const auto total_length = SSL_get_tlsext_status_ocsp_resp(ctx, &response);
// length == -1 on no response and response will be nullptr
if ((response != nullptr) && (total_length > 0) && (total_length <= std::numeric_limits<std::int32_t>::max())) {
// there is a response
const auto* tmp_p = &response[0];
if (response[0] == 0x30) {
// not a multi response
auto* resp = d2i_OCSP_RESPONSE(nullptr, &tmp_p, total_length);
if (resp == nullptr) {
log_error("d2i_OCSP_RESPONSE (single)");
result = 0;
}
OCSP_RESPONSE_free(resp);
} else {
// multiple responses
auto remaining = static_cast<std::int32_t>(total_length);
const unsigned char* ptr{response};
while (remaining >= 3) {
const auto len = uint24(ptr);
update_position(ptr, remaining, 3);
// d2i_OCSP_RESPONSE updates tmp_p
tmp_p = ptr;
auto* resp = d2i_OCSP_RESPONSE(nullptr, &tmp_p, len);
update_position(ptr, remaining, len);
if ((resp == nullptr) || (ptr != tmp_p)) {
log_error("d2i_OCSP_RESPONSE (multi)");
result = 0;
remaining = -1;
}
OCSP_RESPONSE_free(resp);
}
if (remaining != 0) {
log_error("OCSP_RESPONSE decode error (multi)");
result = 0;
}
}
}
return result;
}
int ClientStatusRequestV2::status_request_v2_multi_cb(SSL* ctx, void* object) {
/*
* This callback is called when status_request or status_request_v2 extensions
* were present in the Client Hello. It doesn't mean that the extension is in
* the Server Hello SSL_get_tlsext_status_ocsp_resp() returns -1 in that case
*/
/*
* The callback when used on the client side should return
* a negative value on error,
* 0 if the response is not acceptable (in which case the handshake will fail), or
* a positive value if it is acceptable.
*/
auto* client_ptr = reinterpret_cast<ClientStatusRequestV2*>(object);
int result{1};
if (client_ptr != nullptr) {
result = client_ptr->status_request_cb(ctx);
} else {
log_error("ClientStatusRequestV2::status_request_v2_multi_cb missing Client *");
}
return result;
}
int ClientStatusRequestV2::status_request_v2_add(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char** out, std::size_t* outlen, X509* cert,
std::size_t chainidx, int* alert, void* object) {
int result{0};
if (context == SSL_EXT_CLIENT_HELLO) {
/*
* struct {
* CertificateStatusType status_type;
* uint16 request_length; // Length of request field in bytes
* select (status_type) {
* case ocsp: OCSPStatusRequest;
* case ocsp_multi: OCSPStatusRequest;
* } request;
* } CertificateStatusRequestItemV2;
*
* enum { ocsp(1), ocsp_multi(2), (255) } CertificateStatusType;
*
* struct {
* ResponderID responder_id_list<0..2^16-1>;
* Extensions request_extensions;
* } OCSPStatusRequest;
*
* opaque ResponderID<1..2^16-1>;
* opaque Extensions<0..2^16-1>;
*
* struct {
* CertificateStatusRequestItemV2
* certificate_status_req_list<1..2^16-1>;
* } CertificateStatusRequestListV2;
*
* Minimal request:
* 0x0007 certificate_status_req_list length
* 0x02 CertificateStatusType - OCSP multi
* 0x0004 request_length (uint 16)
* 0x0000 responder_id_list length
* 0x0000 request_extensions length
*/
// don't use constexpr
static const std::uint8_t asn1[] = {0x00, 0x07, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00};
*out = &asn1[0];
*outlen = sizeof(asn1);
/*
* ensure client callback is called - SSL_set_tlsext_status_type() needs to have a value
* TLSEXT_STATUSTYPE_ocsp_multi for status_request_v2, or
* TLSEXT_STATUSTYPE_ocsp for status_request and status_request_v2
*/
if (SSL_get_tlsext_status_type(ctx) != TLSEXT_STATUSTYPE_ocsp) {
#ifdef OPENSSL_PATCHED
SSL_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp_multi);
#else
SSL_set_tlsext_status_type(ctx, 2);
#endif // OPENSSL_PATCHED
}
result = 1;
}
return result;
}
int ClientStatusRequestV2::status_request_v2_cb(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char* data, std::size_t datalen, X509* cert,
std::size_t chainidx, int* alert, void* object) {
#ifdef OPENSSL_PATCHED
SSL_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp_multi);
SSL_set_tlsext_status_expected(ctx, 1);
#endif // OPENSSL_PATCHED
return 1;
}
} // namespace status_request
} // namespace tls

View File

@@ -0,0 +1,276 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#ifndef EXTENSIONS_STATUS_REQUEST_
#define EXTENSIONS_STATUS_REQUEST_
#include <everest/tls/openssl_util.hpp>
#include <everest/tls/tls_types.hpp>
#include <cstddef>
#include <memory>
#include <mutex>
namespace tls {
struct ocsp_cache_ctx;
// ----------------------------------------------------------------------------
// Cache of OCSP responses for status_request and status_request_v2 extensions
/**
* \brief cache of OCSP responses
* \note responses can be updated at any time via load()
*/
class OcspCache {
public:
using digest_t = openssl::sha_1_digest_t;
using ocsp_entry_t = std::tuple<digest_t, const char*>;
using ocsp_entry_list_t = std::vector<ocsp_entry_t>;
using OcspResponse_t = std::shared_ptr<OcspResponse>;
private:
std::unique_ptr<ocsp_cache_ctx> m_context; //!< opaque cache data
std::mutex mux; //!< protects the cached OCSP responses
public:
OcspCache();
OcspCache(const OcspCache&) = delete;
OcspCache(OcspCache&&) = delete;
OcspCache& operator=(const OcspCache&) = delete;
OcspCache& operator=(OcspCache&&) = delete;
~OcspCache();
/**
* \brief populate the cache from a list of (digest, filename) pairs
* \param[in] filenames is a list of (digest, filename) pairs
* \return true when successfully loaded
* \note the OCSP response from the file is cached rather than the filename
*/
bool load(const ocsp_entry_list_t& filenames);
/**
* \brief return a pointer to the OCSP response for the given digest
* \param[in] digest is the lookup key
* \return the OCSP response or nullptr
*/
OcspResponse_t lookup(const digest_t& digest);
/**
* \brief calculate the digest for the specified certificate
* \param[out] digest is the calculated hash
* \param[in] cert is the certificate to hash
* \return true on success
*/
static bool digest(digest_t& digest, const x509_st* cert);
};
namespace status_request {
// ----------------------------------------------------------------------------
// TLS handshake extension status_request and status_request_v2 support
using digest_t = OcspCache::digest_t;
using digest_list_t = std::vector<digest_t>;
/// \brief status_request and status_request_v2 handler for TLS server
class ServerStatusRequestV2 {
private:
static int s_index; //!< index used for storing per connection data
OcspCache& m_cache; //!< reference to the OCSP cache
public:
using StatusFlags = tls::StatusFlags;
explicit ServerStatusRequestV2(OcspCache& cache);
ServerStatusRequestV2() = delete;
ServerStatusRequestV2(const ServerStatusRequestV2&) = delete;
ServerStatusRequestV2(ServerStatusRequestV2&&) = delete;
ServerStatusRequestV2& operator=(const ServerStatusRequestV2&) = delete;
ServerStatusRequestV2& operator=(ServerStatusRequestV2&&) = delete;
~ServerStatusRequestV2() = default;
/**
* \brief add the extension to the SSL context
* \param[inout] ctx the context to configure
* \return true on success
*/
bool init_ssl(SslContext* ctx);
/**
* \brief set the OCSP response for the SSL context
* \param[in] digest the certificate requested
* \param[in] ctx the connection context
* \return true on success
* \note for status_request extension
*/
bool set_ocsp_response(const digest_t& digest, Ssl* ctx);
/**
* \brief the OpenSSL callback for the status_request extension
* \param[in] ctx the connection context
* \param[in] object the instance of a ServerStatusRequestV2
* \return SSL_TLSEXT_ERR_OK on success and SSL_TLSEXT_ERR_NOACK on error
*/
static int status_request_cb(Ssl* ctx, void* object);
/**
* \brief set the OCSP response for the SSL context
* \param[in] digest the certificate requested
* \param[in] ctx the connection context
* \return true on success
* \note for status_request_v2 extension
*/
bool set_ocsp_v2_response(const digest_list_t& digests, Ssl* ctx);
/**
* \brief add status_request_v2 extension to server hello
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] out pointer to the extension data
* \param[in] outlen size of extension data
* \param[in] cert certificate
* \param[in] chainidx certificate chain index
* \param[in] alert the alert to send on error
* \param[in] object the instance of a ServerStatusRequestV2
* \return success = 1, do not include = 0, error == -1
*/
static int status_request_v2_add(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char** out,
std::size_t* outlen, Certificate* cert, std::size_t chainidx, int* alert,
void* object);
/**
* \brief free status_request_v2 extension added to server hello
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] out pointer to the extension data
* \param[in] object the instance of a ServerStatusRequestV2 - not used
*/
static void status_request_v2_free(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char* out,
void* object);
/**
* \brief the OpenSSL callback for the status_request_v2 extension
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] data pointer to the extension data
* \param[in] datalen size of extension data
* \param[in] cert certificate
* \param[in] chainidx certificate chain index
* \param[in] alert the alert to send on error
* \param[in] object not used
* \return success = 1, error = zero or negative
*/
static int status_request_v2_cb(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char* data,
std::size_t datalen, Certificate* cert, std::size_t chainidx, int* alert,
void* object);
/**
* \brief the OpenSSL callback for the client hello record
* \param[in] ctx the connection context
* \param[in] alert the alert to send on error
* \param[in] object not used
* \return success = 1, error = zero or negative
*
* This callback has early access to the extensions requested by the client.
* It is used to determine whether status_request and status_request_v2
* have been requested so that status_request_v2 can take priority.
*
* Calls StatusFlags::has_status_request() when a status_request extension
* is present.
*
* Calls StatusFlags::has_status_request_v2() when a status_request_v2
* extension is present.
*/
static int client_hello_cb(Ssl* ctx, int* alert, void* object);
/**
* \brief store pointer to connection data
* \param[in] ctx the connection context
* \param[in] ptr pointer to the data
*/
static void set_data(Ssl* ctx, StatusFlags* ptr);
/**
* \brief retrieve pointer to connection data
* \param[in] ctx the connection context
* \return pointer to the data or nullptr
*/
static StatusFlags* get_data(Ssl* ctx);
};
/// \brief status_request and status_request_v2 handler for TLS client
class ClientStatusRequestV2 {
public:
ClientStatusRequestV2() = default;
ClientStatusRequestV2(const ClientStatusRequestV2&) = delete;
ClientStatusRequestV2(ClientStatusRequestV2&&) = delete;
ClientStatusRequestV2& operator=(const ClientStatusRequestV2&) = delete;
ClientStatusRequestV2& operator=(ClientStatusRequestV2&&) = delete;
virtual ~ClientStatusRequestV2() = default;
/**
* \brief print an OCSP response to the specified stream
* \param[in] stream the output stream to use
* \param[in] response the OCSP response
* \param[in] length of the OCSP response
* \return true on success
*/
static bool print_ocsp_response(FILE* stream, const unsigned char*& response, std::size_t length);
/**
* \brief the OpenSSL callback for the status_request and status_request_v2 extensions
* \param[in] ctx the connection context
* \return SSL_TLSEXT_ERR_OK on success and SSL_TLSEXT_ERR_NOACK on error
*/
virtual int status_request_cb(Ssl* ctx);
/**
* \brief the OpenSSL callback for the status_request_v2 extension
* \param[in] ctx the connection context
* \param[in] object the instance of ClientStatusRequestV2
* \return SSL_TLSEXT_ERR_OK on success and SSL_TLSEXT_ERR_NOACK on error
*/
static int status_request_v2_multi_cb(Ssl* ctx, void* object);
/**
* \brief add status_request_v2 extension to client hello
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] out pointer to the extension data
* \param[in] outlen size of extension data
* \param[in] cert certificate
* \param[in] chainidx certificate chain index
* \param[in] alert the alert to send on error
* \param[in] object not used
* \return success = 1, do not include = 0, error == -1
*/
static int status_request_v2_add(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char** out,
std::size_t* outlen, Certificate* cert, std::size_t chainidx, int* alert,
void* object);
/**
* \brief the OpenSSL callback for the status_request_v2 extension
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] data pointer to the extension data
* \param[in] datalen size of extension data
* \param[in] cert certificate
* \param[in] chainidx certificate chain index
* \param[in] alert the alert to send on error
* \param[in] object not used
* \return success = 1, error = zero or negative
*/
static int status_request_v2_cb(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char* data,
std::size_t datalen, Certificate* cert, std::size_t chainidx, int* alert,
void* object);
};
} // namespace status_request
} // namespace tls
#endif // EXTENSIONS_STATUS_REQUEST_

View File

@@ -0,0 +1,444 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#include "extensions/trusted_ca_keys.hpp"
#include "helpers.hpp"
#include <boost/smart_ptr/shared_ptr.hpp>
#include <everest/tls/openssl_util.hpp>
#include <cassert>
#include <limits>
#include <openssl/ssl.h>
#include <string>
namespace {
using namespace tls::trusted_ca_keys;
using namespace openssl;
using hash_fn = bool (*)(sha_1_digest_t& digest, const x509_st* cert);
/**
* \brief check a list of certificates against trusted hashes
* \param[in] hashes is the list of trusted hashes
* \param[in] certs is the list of trust anchors
* \param[in] gen_hash is the function to generate the hash
* \return true when there is a match
* \note this is a generic function that can be used to check certificate hashes
* or certificate public key hashes
*/
bool match_hash(const digest_list& hashes, const certificate_list& certs, hash_fn gen_hash) {
bool result{false};
if (!hashes.empty()) {
// check for a match against all known trust anchors
// select the first one to match
for (const auto& ta : certs) {
sha_1_digest_t digest;
if (gen_hash(digest, ta.get())) {
// with the digest from the trust anchor check against
// the trusted CA keys hashes
for (const auto& hash : hashes) {
result = digest == hash;
if (result) {
break;
}
}
}
if (result) {
break;
}
}
}
return result;
}
/**
* \brief check trust anchors against certificate hashes
* \param[in] extension contains the list of trusted certificate hashes
* \param[in] chain contains the list of trust anchors
* \return true when there is a match
*/
inline bool match_cert_hash(const trusted_ca_keys_t& extension, const chain_t& chain) {
return match_hash(extension.cert_sha1_hash, chain.chain.trust_anchors, &certificate_sha_1);
}
/**
* \brief check trust anchors against certificate public key hashes
* \param[in] extension contains the list of trusted public key hashes
* \param[in] chain contains the list of trust anchors
* \return true when there is a match
*/
inline bool match_key_hash(const trusted_ca_keys_t& extension, const chain_t& chain) {
return match_hash(extension.key_sha1_hash, chain.chain.trust_anchors, &certificate_subject_public_key_sha_1);
}
/**
* \brief check trust anchors against certificate subject names
* \param[in] extension contains the list of trusted certificate subject names
* \param[in] chain contains the list of trust anchors
* \return true when there is a match
* \note compares the DER encoded certificate subject name
*/
bool match_name(const trusted_ca_keys_t& extension, const chain_t& chain) {
bool result{false};
if (!extension.x509_name.empty()) {
for (const auto& ta : chain.chain.trust_anchors) {
auto subject = certificate_subject_der(ta.get());
for (const auto& name : extension.x509_name) {
result = subject == name;
if (result) {
break;
}
}
if (result) {
break;
}
}
}
return result;
}
} // namespace
namespace tls::trusted_ca_keys {
// ----------------------------------------------------------------------------
// TrustedCaKeys
bool extract_TrustedAuthority(trusted_ca_keys_t& result, const std::uint8_t*& ptr, std::int32_t& remaining) {
bool bResult{false};
if ((remaining > 0) && (ptr != nullptr)) {
const auto identifier = static_cast<IdentifierType>(*ptr);
update_position(ptr, remaining, 1);
switch (identifier) {
case IdentifierType::pre_agreed:
result.pre_agreed = true;
bResult = true;
break;
case IdentifierType::key_sha1_hash: {
digest_t digest;
bResult = struct_copy(digest, ptr, remaining, "trusted_ca_keys extension: key_sha1_hash decode error");
if (bResult) {
result.key_sha1_hash.emplace_back(digest);
}
break;
}
case IdentifierType::x509_name: {
if (remaining >= 2) {
const auto name_len = uint16(ptr);
update_position(ptr, remaining, 2);
if (remaining >= name_len) {
DER name(ptr, name_len);
update_position(ptr, remaining, name_len);
result.x509_name.emplace_back(std::move(name));
bResult = true;
}
}
break;
}
case IdentifierType::cert_sha1_hash: {
digest_t digest;
bResult = struct_copy(digest, ptr, remaining, "trusted_ca_keys extension: cert_sha1_hash decode error");
if (bResult) {
result.cert_sha1_hash.emplace_back(digest);
}
break;
}
default:
log_warning("trusted_ca_keys extension: IdentifierType decode error: " +
std::to_string(static_cast<int>(identifier)));
break;
}
}
return bResult;
}
std::size_t TrustedAuthority_size(const trusted_ca_keys_t& data) {
// list length in bytes (2 bytes) + 1 if data.pre_agreed is true
std::size_t size = (data.pre_agreed) ? 3 : 2;
// IdentifierType (1 byte) + SHA1 digest size
constexpr std::size_t hash_size = sizeof(digest_t) + 1;
size += (data.key_sha1_hash.size() + data.cert_sha1_hash.size()) * hash_size;
if (!data.x509_name.empty()) {
for (const auto& i : data.x509_name) {
// IdentifierType (1 byte) + 2 bytes length + DER X509 name
size += i.size() + 3;
}
}
return size;
}
bool certificate_digest(digest_t& digest, const x509_st* cert) {
assert(cert != nullptr);
return openssl::certificate_sha_1(digest, cert);
}
bool public_key_digest(digest_t& digest, const x509_st* cert) {
assert(cert != nullptr);
return openssl::certificate_subject_public_key_sha_1(digest, cert);
}
/*
* Presentation Language
* https://datatracker.ietf.org/doc/html/rfc5246
*
* extension_data (see https://datatracker.ietf.org/doc/html/rfc6066)
* struct {
* TrustedAuthority trusted_authorities_list<0..2^16-1>;
* } TrustedAuthorities;
*
* struct {
* IdentifierType identifier_type;
* select (identifier_type) {
* case pre_agreed: struct {};
* case key_sha1_hash: SHA1Hash;
* case x509_name: DistinguishedName;
* case cert_sha1_hash: SHA1Hash;
* } identifier;
* } TrustedAuthority;
*
* enum {
* pre_agreed(0), key_sha1_hash(1), x509_name(2),
* cert_sha1_hash(3), (255)
* } IdentifierType;
*
* opaque DistinguishedName<1..2^16-1>;
*
* (note the extension is not DER encoded, only DistinguishedName is)
*
* === captured traces ===
*
* PEV1
* 0069 trusted_authorities_list length
* 03 identifier_type cert_sha1_hash d8367e861f5807f8141fea572d676dbf58bb5f7c SHA1Hash
* 03 identifier_type cert_sha1_hash b491ddd08fafe72d9f6f9bafc68eb04da84cc09a SHA1Hash
* 03 identifier_type cert_sha1_hash 30aaaab25b1cc8a09a7b32652c33cc5a973c13f3 SHA1Hash
* 03 identifier_type cert_sha1_hash 700bf78ad58e0819dac6fcaead5ed20f7bb0554f SHA1Hash
* 03 identifier_type cert_sha1_hash 8c821f41604ed4c3431cf6d19f2ae107cf1f50e1 SHA1Hash
*
* PEV2
* 002a trusted_authorities_list length
* 03 identifier_type cert_sha1_hash d8367e861f5807f8141fea572d676dbf58bb5f7c SHA1Hash
* 03 identifier_type cert_sha1_hash 8c821f41604ed4c3431cf6d19f2ae107cf1f50e1 SHA1Hash
*
* PEV3 (invalid missing the size of trusted_authorities_list)
* 01 identifier_type key_sha1_hash 4cd7290bf592d2c1ba90f56e08946d4c8e99dc38 SHA1Hash
* 01 identifier_type key_sha1_hash 00fae3900795c888a4d4d7bd9fdffa60418ac19f SHA1Hash
*/
trusted_authority convert(const trusted_ca_keys_t& keys) {
const auto size = TrustedAuthority_size(keys);
trusted_authority result{};
// empty trusted_ca_keys_t has a size of 2
if ((size > 2) && (size <= std::numeric_limits<std::uint16_t>::max())) {
result = trusted_authority(size);
auto* ptr = result.get();
auto remaining = static_cast<std::int32_t>(size);
uint16(ptr, size - 2);
update_position(ptr, remaining, 2);
if (keys.pre_agreed) {
*ptr = static_cast<std::uint8_t>(IdentifierType::pre_agreed);
update_position(ptr, remaining, 1);
}
for (const auto& i : keys.cert_sha1_hash) {
*ptr = static_cast<std::uint8_t>(IdentifierType::cert_sha1_hash);
update_position(ptr, remaining, 1);
if (!struct_copy(ptr, i, remaining, "trusted_ca_keys extension: cert_sha1_hash encode error")) {
break;
}
}
for (const auto& i : keys.key_sha1_hash) {
*ptr = static_cast<std::uint8_t>(IdentifierType::key_sha1_hash);
update_position(ptr, remaining, 1);
if (!struct_copy(ptr, i, remaining, "trusted_ca_keys extension: key_sha1_hash encode error")) {
break;
}
}
for (const auto& i : keys.x509_name) {
*ptr = static_cast<std::uint8_t>(IdentifierType::x509_name);
update_position(ptr, remaining, 1);
uint16(ptr, i.size());
update_position(ptr, remaining, 2);
if (!der_copy(ptr, i, remaining, "trusted_ca_keys extension: x509_name encode error")) {
break;
}
}
}
return result;
}
trusted_ca_keys_t convert(const std::uint8_t* extension_data, const std::size_t len) {
trusted_ca_keys_t result{};
bool bResult{false};
if ((extension_data != nullptr) && (len <= std::numeric_limits<std::uint16_t>::max()) && (len >= 2)) {
std::int32_t remaining = uint16(extension_data);
extension_data += 2;
if (remaining != (len - 2)) {
log_warning("trusted_ca_keys extension: TrustedAuthorities decode error");
} else {
bResult = true;
while (bResult && remaining > 0) {
bResult = extract_TrustedAuthority(result, extension_data, remaining);
}
}
}
// do not return partially parsed extension
return (bResult) ? std::move(result) : std::move(trusted_ca_keys_t());
}
bool match(const trusted_ca_keys_t& extension, const chain_t& chain) {
bool result = match_cert_hash(extension, chain);
result = result || match_key_hash(extension, chain);
result = result || match_name(extension, chain);
return result;
}
const chain_t* select(const trusted_ca_keys_t& extension, const chain_list& chains) {
const chain_t* result{nullptr};
for (const auto& chain : chains) {
if (match(extension, chain)) {
result = &chain;
break;
}
}
return result;
}
int ServerTrustedCaKeys::s_index{-1};
ServerTrustedCaKeys::ServerTrustedCaKeys() {
if (s_index == -1) {
s_index = CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0, nullptr, nullptr, nullptr, nullptr);
}
}
bool ServerTrustedCaKeys::init_ssl(SslContext* ctx) {
bool bRes{true};
// TLS 1.2 and below only - use certificate_authorities in TLS 1.3
constexpr int context_tck =
SSL_EXT_TLS_ONLY | SSL_EXT_TLS1_2_AND_BELOW_ONLY | SSL_EXT_IGNORE_ON_RESUMPTION | SSL_EXT_CLIENT_HELLO;
if (SSL_CTX_add_custom_ext(ctx, TLSEXT_TYPE_trusted_ca_keys, context_tck, nullptr, nullptr, nullptr,
&ServerTrustedCaKeys::trusted_ca_keys_cb, nullptr) != 1) {
log_error("SSL_CTX_add_custom_ext");
bRes = false;
}
// used to change the server certificate depending on trusted_ca_keys
SSL_CTX_set_cert_cb(ctx, &ServerTrustedCaKeys::handle_certificate_cb, this);
return bRes;
}
void ServerTrustedCaKeys::update(chain_list&& new_chains) {
std::lock_guard lock(m_mux);
m_chains = std::move(new_chains);
}
const chain_t* ServerTrustedCaKeys::select(const trusted_ca_keys_t& extension) {
return trusted_ca_keys::select(extension, m_chains);
}
const chain_t* ServerTrustedCaKeys::select_default() {
return (m_chains.empty()) ? nullptr : m_chains.data();
}
int ServerTrustedCaKeys::trusted_ca_keys_cb(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char* data, std::size_t datalen, Certificate* cert,
std::size_t chainidx, int* alert, void* object) {
/*
* return values:
* - fatal, abort handshake and sent TLS Alert: result = 0 or negative and *alert = alert value
* - success: result = 1
*/
auto* keys_p = get_data(ctx);
if (keys_p != nullptr) {
keys_p->flags.trusted_ca_keys_received();
keys_p->tck = convert(data, datalen);
}
return 1;
}
int ServerTrustedCaKeys::handle_certificate_cb(SSL* ssl, void* arg) {
/*
* return values:
* - fatal, abort handshake and sent TLS Alert: result = 0 or negative
* - success: result = 1
*/
int result{1};
auto* tck_p = reinterpret_cast<ServerTrustedCaKeys*>(arg);
auto* keys_p = get_data(ssl);
/*
* From OpenSSL man page
* An application will typically call SSL_use_certificate() and SSL_use_PrivateKey()
* to set the end entity certificate and private key. It can add intermediate and
* optionally the root CA certificates using SSL_add1_chain_cert().
* It might also call SSL_certs_clear().
*/
if ((tck_p != nullptr) && (keys_p != nullptr) && (keys_p->flags.has_trusted_ca_keys())) {
// prevent update() from changing pointers
std::lock_guard lock(tck_p->m_mux);
const auto* selected = tck_p->select(keys_p->tck);
if (selected != nullptr) {
if (!use_certificate_and_key(ssl, *selected)) {
// setting failed - try and use the default
selected = tck_p->select_default();
if (selected != nullptr) {
if (!use_certificate_and_key(ssl, *selected)) {
// there has been a problem setting the server
// certificate, key and chain
result = 0;
log_warning("terminating TLS handshake: trusted_ca_keys");
}
}
}
}
}
return result;
}
void ServerTrustedCaKeys::set_data(SSL* ctx, server_trusted_ca_keys_t* ptr) {
assert(ctx != nullptr);
SSL_set_ex_data(ctx, s_index, ptr);
}
server_trusted_ca_keys_t* ServerTrustedCaKeys::get_data(SSL* ctx) {
assert(ctx != nullptr);
return reinterpret_cast<server_trusted_ca_keys_t*>(SSL_get_ex_data(ctx, s_index));
}
int ClientTrustedCaKeys::trusted_ca_keys_add(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char** out, std::size_t* outlen, X509* cert,
std::size_t chainidx, int* alert, void* object) {
int result{0};
if ((context == SSL_EXT_CLIENT_HELLO) && (object != nullptr)) {
auto* config = reinterpret_cast<trusted_ca_keys_t*>(object);
auto der = convert(*config);
const auto len = der.size();
auto* ptr = der.release();
if (ptr != nullptr) {
*out = ptr;
*outlen = len;
result = 1;
}
}
return result;
}
void ClientTrustedCaKeys::trusted_ca_keys_free(SSL* ctx, unsigned int ext_type, unsigned int context,
const unsigned char* out, void* object) {
openssl::DER::free(const_cast<unsigned char*>(out));
}
} // namespace tls::trusted_ca_keys

View File

@@ -0,0 +1,262 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest
#ifndef EXTENSIONS_TRUSTED_CA_KEYS_
#define EXTENSIONS_TRUSTED_CA_KEYS_
#include <everest/tls/openssl_util.hpp>
#include <everest/tls/tls_types.hpp>
#include <mutex>
namespace tls::trusted_ca_keys {
using der_ptr = openssl::der_ptr;
using DER = openssl::DER;
using der_list = std::vector<DER>;
using digest_t = openssl::sha_1_digest_t;
using digest_list = std::vector<digest_t>;
// The trusted_ca_keys extension isn't DER encoded
// openssl::DER class can be reused though
using trusted_authority = openssl::DER;
using openssl::chain_list;
using openssl::chain_t;
/**
* \brief items that can be requested in the trusted_ca_keys extension
*/
struct trusted_ca_keys_t {
der_list x509_name; //!< a list of DER encoded certificate subject names
digest_list key_sha1_hash; //!< a list of certificate key hashes
digest_list cert_sha1_hash; //!< a list of certificate hashes
bool pre_agreed{false}; //!< use the pre-agreed certificate
};
enum class IdentifierType : std::uint8_t {
pre_agreed = 0,
key_sha1_hash = 1,
x509_name = 2,
cert_sha1_hash = 3,
};
/**
* \brief extract item from trusted_ca_keys extension
* \param[out] result updated structure of parsed options
* \param[inout] ptr a pointer to the first byte of the next item, updated to
* be the first byte of the next item
* \param[inout] remaining number of bytes left (updated with new value)
* \return true on success
*/
bool extract_trusted_authority(trusted_ca_keys_t& result, const std::uint8_t*& ptr, std::int32_t& remaining);
/**
* \brief calculate the size of array needed to hold the extension
* \param[in] data the trusted items to include
* \return the number of bytes needed
*/
std::size_t trusted_authority_size(const trusted_ca_keys_t& data);
/**
* \brief calculate the digest over the certificate
* \param[in] cert is the certificate
* \param[out] digest the calculated digest
* \return true on success
*/
bool certificate_digest(digest_t& digest, const x509_st* cert);
/**
* \brief calculate the digest over the certificate's public key
* \param[in] cert is the certificate
* \param[out] digest the calculated digest
* \return true on success
*/
bool public_key_digest(digest_t& digest, const x509_st* cert);
/**
* \brief convert a trusted_ca_keys_t to the encoded extension
* \param[in] keys the items to include in the extension
* \return the encoded extension
*/
trusted_authority convert(const trusted_ca_keys_t& keys);
/**
* \brief convert a encoded extension into trusted_ca_keys_t
* \param[in] extension_data a pointer to the extension data
* \param[in] len is the length of the extension data
* \return the populated trusted_ca_keys_t
* \note trusted_ca_keys_t will be empty if there is any problem decoding the
* extension. It will not contain partial results.
*/
trusted_ca_keys_t convert(const std::uint8_t* extension_data, std::size_t len);
/**
* \brief convert a encoded extension into trusted_ca_keys_t
* \param[in] extension_data the items to include in the extension
* \return the populated trusted_ca_keys_t
* \note trusted_ca_keys_t will be empty if there is any problem decoding the
* extension. It will not contain partial results.
*/
inline trusted_ca_keys_t convert(const trusted_authority& extension_data) {
return convert(extension_data.get(), extension_data.size());
}
/**
* \brief check if one of the trust anchors matches the trusted_ca_keys
* \param[in] extension the information to check against
* \param[in] chain the certificate chain to check against
* \return true when the chain matches the extension
*/
bool match(const trusted_ca_keys_t& extension, const chain_t& chain);
/**
* \brief select the certificate chain to use
* \param[in] extension the information to check against
* \param[in] chains the list of certificate chains to check against
* \return a pointer to the chain (in chains) or nullptr when none match
*/
const chain_t* select(const trusted_ca_keys_t& extension, const chain_list& chains);
/// \brief per connection data
struct server_trusted_ca_keys_t {
trusted_ca_keys_t& tck; //!< parsed values
tls::StatusFlags& flags; //!< flags to indicate which extensions were present
};
/// \brief trusted_ca_keys extension handler for a TLS server
class ServerTrustedCaKeys {
private:
static int s_index; //!< index used for storing per connection data
chain_list m_chains; //!< known certificate chains
std::mutex m_mux; //!< protects m_chains
public:
ServerTrustedCaKeys();
ServerTrustedCaKeys(const ServerTrustedCaKeys&) = delete;
ServerTrustedCaKeys(ServerTrustedCaKeys&&) = delete;
ServerTrustedCaKeys& operator=(const ServerTrustedCaKeys&) = delete;
ServerTrustedCaKeys& operator=(ServerTrustedCaKeys&&) = delete;
~ServerTrustedCaKeys() = default;
/**
* \brief add the extension to the SSL context
* \param[inout] ctx the context to configure
* \return true on success
*/
bool init_ssl(SslContext* ctx);
/**
* \brief update m_chains with the new certificate chains to support
* \param[in] new_chains the chains to support
* \note new_chains is moved to m_chains and hence will be empty after
* calling update()
*/
void update(chain_list&& new_chains);
/**
* \brief select chain to use based on parsed extension
* \param[in] extension is the parsed extension
* \return a pointer to the chain (in m_chains) or nullptr when none match
* \note pointer will be invalid if update() called before it is used
*/
const chain_t* select(const trusted_ca_keys_t& extension);
/**
* \brief select the default chain to use (first entry in m_chains)
* \return a pointer to the first chain (in m_chains) or nullptr on error
* \note pointer will be invalid if update() called before it is used
*/
const chain_t* select_default();
/**
* \brief the OpenSSL callback for the trusted_ca_keys extension
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] data pointer to the extension data
* \param[in] datalen size of extension data
* \param[in] cert certificate
* \param[in] chainidx certificate chain index
* \param[in] alert the alert to send on error
* \param[in] object the instance of a CertificateStatusRequestV2
* \return success = 1, error = zero or negative
*
* Parses the extension data to extract and populate trusted_ca_keys_t.
* Calls StatusFlags::has_trusted_ca_keys() when a trusted_ca_keys extension
* is present. Note that trusted_ca_keys_t may contain no information if
* there was a parsing error.
*/
static int trusted_ca_keys_cb(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char* data,
std::size_t datalen, Certificate* cert, std::size_t chainidx, int* alert,
void* object);
/**
* \brief the OpenSSL callback for the trusted_ca_keys extension
* \param[in] ctx the connection context
* \param[in] arg A ServerTrustedCaKeys object
* \return success = 1, error = zero or negative
*
* Calls select() and select_default() after acquiring the mutex to ensure
* that calls to update() don't invalidate the pointers.
*/
static int handle_certificate_cb(Ssl* ssl, void* arg);
/**
* \brief store pointer to connection data
* \param[in] ctx the connection context
* \param[in] ptr pointer to the data
*/
static void set_data(Ssl* ctx, server_trusted_ca_keys_t* ptr);
/**
* \brief retrieve pointer to connection data
* \param[in] ctx the connection context
* \return pointer to the data or nullptr
*/
static server_trusted_ca_keys_t* get_data(Ssl* ctx);
};
/// \brief trusted_ca_keys extension handler for a TLS client
class ClientTrustedCaKeys {
public:
ClientTrustedCaKeys() = default;
ClientTrustedCaKeys(const ClientTrustedCaKeys&) = delete;
ClientTrustedCaKeys(ClientTrustedCaKeys&&) = delete;
ClientTrustedCaKeys& operator=(const ClientTrustedCaKeys&) = delete;
ClientTrustedCaKeys& operator=(ClientTrustedCaKeys&&) = delete;
~ClientTrustedCaKeys() = default;
/**
* \brief add trusted_ca_keys extension to client hello
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] out pointer to the extension data
* \param[in] outlen size of extension data
* \param[in] cert certificate
* \param[in] chainidx certificate chain index
* \param[in] alert the alert to send on error
* \param[in] object pointer to trusted_ca_keys_t to populate the extension
* \return success = 1, do not include = 0, error == -1
*/
static int trusted_ca_keys_add(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char** out,
std::size_t* outlen, Certificate* cert, std::size_t chainidx, int* alert,
void* object);
/**
* \brief free trusted_ca_keys extension added to server hello
* \param[in] ctx the connection context
* \param[in] ext_type the TLS extension
* \param[in] context the extension context flags
* \param[in] out pointer to the extension data
* \param[in] object not used
*/
static void trusted_ca_keys_free(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char* out,
void* object);
};
} // namespace tls::trusted_ca_keys
#endif // EXTENSIONS_TRUSTED_CA_KEYS_