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:
1
tools/EVerest-main/lib/everest/tls/.gitignore
vendored
Normal file
1
tools/EVerest-main/lib/everest/tls/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!*.patch
|
||||
31
tools/EVerest-main/lib/everest/tls/BUILD.bazel
Normal file
31
tools/EVerest-main/lib/everest/tls/BUILD.bazel
Normal file
@@ -0,0 +1,31 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
|
||||
cc_library(
|
||||
name = "tls",
|
||||
srcs = [
|
||||
"extensions/helpers.cpp",
|
||||
"extensions/status_request.cpp",
|
||||
"extensions/trusted_ca_keys.cpp",
|
||||
"src/openssl_conv.cpp",
|
||||
"src/openssl_util.cpp",
|
||||
"src/tls.cpp",
|
||||
],
|
||||
hdrs = [
|
||||
"extensions/helpers.hpp",
|
||||
"extensions/status_request.hpp",
|
||||
"extensions/trusted_ca_keys.hpp",
|
||||
"include/everest/tls/openssl_conv.hpp",
|
||||
"include/everest/tls/openssl_util.hpp",
|
||||
"include/everest/tls/tls.hpp",
|
||||
"include/everest/tls/tls_types.hpp"
|
||||
],
|
||||
copts = ["-std=c++17"],
|
||||
includes = [".", "include"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//lib/everest/util",
|
||||
"//lib/everest/evse_security:libevse-security",
|
||||
"//third-party/bazel/openssl:crypto",
|
||||
"//third-party/bazel/openssl:ssl",
|
||||
],
|
||||
)
|
||||
61
tools/EVerest-main/lib/everest/tls/CMakeLists.txt
Normal file
61
tools/EVerest-main/lib/everest/tls/CMakeLists.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
add_library(tls STATIC)
|
||||
add_library(everest::tls ALIAS tls)
|
||||
ev_register_library_target(tls)
|
||||
|
||||
find_package(OpenSSL 3)
|
||||
|
||||
target_sources(tls
|
||||
PRIVATE
|
||||
extensions/status_request.cpp
|
||||
extensions/trusted_ca_keys.cpp
|
||||
src/openssl_conv.cpp
|
||||
src/openssl_util.cpp
|
||||
src/tls.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(tls PRIVATE
|
||||
-DLIBEVSE_CRYPTO_SUPPLIER_OPENSSL
|
||||
)
|
||||
|
||||
target_include_directories(tls
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
|
||||
# FIXME (aw): check whether all of this needs to be publicly exposed
|
||||
target_link_libraries(tls
|
||||
PUBLIC
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
everest::evse_security
|
||||
everest::util
|
||||
)
|
||||
|
||||
set_target_properties(tls
|
||||
PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
if(EVEREST_CORE_BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(DISABLE_EDM)
|
||||
install(
|
||||
TARGETS
|
||||
tls
|
||||
EXPORT everest-tls-targets
|
||||
LIBRARY
|
||||
)
|
||||
|
||||
evc_setup_package(
|
||||
NAME everest-tls
|
||||
EXPORT everest-tls-targets
|
||||
NAMESPACE everest
|
||||
ADDITIONAL_CONTENT
|
||||
"find_dependency(everest-util)"
|
||||
"find_dependency(everest-evse_security)"
|
||||
"find_dependency(OpenSSL)"
|
||||
)
|
||||
endif()
|
||||
51
tools/EVerest-main/lib/everest/tls/extensions/helpers.cpp
Normal file
51
tools/EVerest-main/lib/everest/tls/extensions/helpers.cpp
Normal 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;
|
||||
}
|
||||
174
tools/EVerest-main/lib/everest/tls/extensions/helpers.hpp
Normal file
174
tools/EVerest-main/lib/everest/tls/extensions/helpers.hpp
Normal 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_
|
||||
587
tools/EVerest-main/lib/everest/tls/extensions/status_request.cpp
Normal file
587
tools/EVerest-main/lib/everest/tls/extensions/status_request.cpp
Normal 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
|
||||
276
tools/EVerest-main/lib/everest/tls/extensions/status_request.hpp
Normal file
276
tools/EVerest-main/lib/everest/tls/extensions/status_request.hpp
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef OPENSSL_CONV_HPP_
|
||||
#define OPENSSL_CONV_HPP_
|
||||
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
#include <evse_security/certificate/x509_wrapper.hpp>
|
||||
#include <evse_security/crypto/interface/crypto_types.hpp>
|
||||
|
||||
/**
|
||||
* \file Convert OpenSSL X509 certificates for use with EVSE Security
|
||||
*
|
||||
* The approach is to increase the reference count on the X509 certificate
|
||||
* rather than passing ownership. This is done so that certificates that are
|
||||
* already part of a structure (e.g. client certificate and chain from a SSL
|
||||
* connection) can be used easily. Otherwise the caller would need to remember
|
||||
* to make a copy before using these functions.
|
||||
*
|
||||
* example using an existing certificate:
|
||||
* \code
|
||||
* auto cert = to_X509Wrapper(SSL_get0_peer_certificate(ssl));
|
||||
* std::cout << cert.get_common_name() << std::endl;
|
||||
* \endcode
|
||||
*
|
||||
* example using a new certificate:
|
||||
* \code
|
||||
* auto* fp = fopen("certificate.pem", "r");
|
||||
* auto* x509 = PEM_read_X509(fp, nullptr, nullptr, nullptr);
|
||||
* fclose(fp);
|
||||
* auto cert = to_X509Wrapper(x509);
|
||||
* X509_free(x509);
|
||||
* std::cout << cert.get_common_name() << std::endl;
|
||||
* \endcode
|
||||
*
|
||||
* The following would lead to a memory leak because 'get1' returns a reference
|
||||
* to the certificate that needs to be freed.
|
||||
* \code
|
||||
* auto cert = to_X509Wrapper(SSL_get1_peer_certificate(ssl));
|
||||
* std::cout << cert.get_common_name() << std::endl;
|
||||
* \endcode
|
||||
*
|
||||
* The solution is to keep the result so it can be freed.
|
||||
* \code
|
||||
* auto* x509 = SSL_get1_peer_certificate(ssl);
|
||||
* auto cert = to_X509Wrapper(x509);
|
||||
* X509_free(x509);
|
||||
* std::cout << cert.get_common_name() << std::endl;
|
||||
* \endcode
|
||||
*/
|
||||
|
||||
namespace openssl::conversions {
|
||||
|
||||
/**
|
||||
* \brief create an EVSE Security certificate handle from a OpenSSL X509 pointer
|
||||
* \param[in] cert a pointer to an OpenSSL X509 structure
|
||||
* \return a EVSE Security managed pointer
|
||||
* \note the reference count on cert is incremented. It may still need to be
|
||||
* freed.
|
||||
*/
|
||||
evse_security::X509Handle_ptr to_X509Handle_ptr(x509_st* cert);
|
||||
/**
|
||||
* \brief create an EVSE Security certificate wrapper from a OpenSSL X509 pointer
|
||||
* \param[in] cert a pointer to an OpenSSL X509 structure
|
||||
* \return a EVSE Security managed wrapper
|
||||
* \note the reference count on cert is incremented. It may still need to be
|
||||
* freed.
|
||||
*/
|
||||
evse_security::X509Wrapper to_X509Wrapper(x509_st* cert);
|
||||
|
||||
} // namespace openssl::conversions
|
||||
|
||||
#endif // OPENSSL_CONV_HPP_
|
||||
@@ -0,0 +1,549 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef OPENSSL_UTIL_HPP_
|
||||
#define OPENSSL_UTIL_HPP_
|
||||
|
||||
#include <everest/tls/tls_types.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct evp_pkey_st;
|
||||
struct ssl_st;
|
||||
struct x509_st;
|
||||
|
||||
namespace openssl {
|
||||
|
||||
/// X509 certificate verification result
|
||||
enum class verify_result_t : std::uint8_t {
|
||||
Verified,
|
||||
CertChainError,
|
||||
CertificateExpired,
|
||||
CertificateRevoked,
|
||||
NoCertificateAvailable, // no root certificate available at all
|
||||
CertificateNotAllowed, // correct root certificate not available
|
||||
OtherError,
|
||||
};
|
||||
|
||||
constexpr std::size_t signature_size = 64;
|
||||
constexpr std::size_t signature_n_size = 32;
|
||||
constexpr std::size_t signature_der_size = 128;
|
||||
constexpr std::size_t sha_1_digest_size = 20;
|
||||
constexpr std::size_t sha_256_digest_size = 32;
|
||||
constexpr std::size_t sha_384_digest_size = 48;
|
||||
constexpr std::size_t sha_512_digest_size = 64;
|
||||
|
||||
enum class digest_alg_t : std::uint8_t {
|
||||
sha1,
|
||||
sha256,
|
||||
sha384,
|
||||
sha512,
|
||||
};
|
||||
|
||||
using sha_1_digest_t = std::array<std::uint8_t, sha_1_digest_size>;
|
||||
using sha_256_digest_t = std::array<std::uint8_t, sha_256_digest_size>;
|
||||
using sha_384_digest_t = std::array<std::uint8_t, sha_384_digest_size>;
|
||||
using sha_512_digest_t = std::array<std::uint8_t, sha_512_digest_size>;
|
||||
using bn_t = std::array<std::uint8_t, signature_n_size>;
|
||||
using bn_const_t = std::array<const std::uint8_t, signature_n_size>;
|
||||
|
||||
using certificate_ptr = std::unique_ptr<x509_st, void (*)(x509_st*)>;
|
||||
using certificate_list = std::vector<certificate_ptr>;
|
||||
using der_underlying_t = std::uint8_t;
|
||||
using der_ptr = std::unique_ptr<der_underlying_t, void (*)(der_underlying_t*)>;
|
||||
using pkey_ptr = std::unique_ptr<evp_pkey_st, void (*)(evp_pkey_st*)>;
|
||||
|
||||
/**
|
||||
* \brief represents DER encoded ASN1 data
|
||||
*
|
||||
* This class simplifies managing DER data ensuring that it is managed and
|
||||
* supporting equality tests.
|
||||
*/
|
||||
class DER {
|
||||
private:
|
||||
der_ptr ptr{nullptr, nullptr}; //!< pointer to the data
|
||||
std::size_t len{0}; //!< length of the data
|
||||
|
||||
public:
|
||||
DER() = default;
|
||||
/**
|
||||
* \brief create space for DER data of specified size
|
||||
* \param[in] size to allocate (note data is zeroed)
|
||||
*/
|
||||
explicit DER(std::size_t size);
|
||||
/**
|
||||
* \brief create a copy of the supplied DER data
|
||||
* \param[in] crc pointer to the DER data
|
||||
* \param[in] size of the DER data
|
||||
*/
|
||||
DER(const der_underlying_t* src, std::size_t size);
|
||||
/**
|
||||
* \brief move a unique pointer and size to this object
|
||||
* \param[in] der a unique pointer to DER data
|
||||
* \param[in] size the size of the DER data
|
||||
*/
|
||||
DER(der_ptr&& der, std::size_t size) : ptr(std::move(der)), len(size) {
|
||||
}
|
||||
|
||||
DER(const DER& obj);
|
||||
DER& operator=(const DER& obj);
|
||||
|
||||
DER(DER&& obj) noexcept;
|
||||
DER& operator=(DER&& obj) noexcept;
|
||||
|
||||
bool operator==(const DER& rhs) const;
|
||||
inline bool operator!=(const DER& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
bool operator==(const der_underlying_t* rhs) const;
|
||||
inline bool operator!=(const der_underlying_t* rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
explicit operator bool() const;
|
||||
|
||||
[[nodiscard]] inline der_underlying_t* get() {
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief release the pointer, must be freed using DER::free()
|
||||
* \return the pointer to DER data (or nullptr)
|
||||
*/
|
||||
[[nodiscard]] inline der_underlying_t* release() {
|
||||
len = 0;
|
||||
return ptr.release();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline const der_underlying_t* get() const {
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::size_t size() const {
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief create unmanaged copy which must be freed using DER::free()
|
||||
* \param[in] obj copy the memory contents from this object
|
||||
* \return a pointer to newly allocated heap memory
|
||||
*/
|
||||
static der_underlying_t* dup(const DER& obj);
|
||||
|
||||
/**
|
||||
* \brief free memory allocated by DER::dup()
|
||||
* \param[in] ptr pointer to the allocated memory (can be nullptr)
|
||||
*/
|
||||
static void free(der_underlying_t* ptr);
|
||||
};
|
||||
|
||||
/// contains filenames for the leaf, intermediate CAs and roots
|
||||
struct chain_filenames_t {
|
||||
const char* leaf;
|
||||
const char* chain;
|
||||
const char* root;
|
||||
};
|
||||
|
||||
using chain_filenames_list_t = std::vector<chain_filenames_t>;
|
||||
|
||||
/// contains the X509 certificates for leaf, intermediate CAs and roots
|
||||
struct chain_info_t {
|
||||
certificate_ptr leaf;
|
||||
certificate_list chain;
|
||||
certificate_list trust_anchors;
|
||||
};
|
||||
|
||||
using chain_info_list_t = std::vector<chain_info_t>;
|
||||
|
||||
/// X509 certificate chain and the private key for the leaf certificate
|
||||
struct chain_t {
|
||||
chain_info_t chain{{nullptr, nullptr}, {}, {}};
|
||||
pkey_ptr private_key{nullptr, nullptr};
|
||||
};
|
||||
|
||||
using chain_list = std::vector<chain_t>;
|
||||
|
||||
/**
|
||||
* \brief sign using ECDSA on curve secp256r1/prime256v1/P-256 of a SHA 256 digest
|
||||
* \param[in] pkey the private key
|
||||
* \param[out] r the R component of the signature as a BIGNUM
|
||||
* \param[out] s the S component of the signature as a BIGNUM
|
||||
* \param[out] digest the SHA256 digest to sign
|
||||
* \return true when successful
|
||||
*/
|
||||
bool sign(evp_pkey_st* pkey, bn_t& r, bn_t& s, const sha_256_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief sign using ECDSA on curve secp256r1/prime256v1/P-256 of a SHA 256 digest
|
||||
* \param[in] pkey the private key
|
||||
* \param[out] sig the buffer where the DER encoded signature will be placed
|
||||
* \param[inout] siglen the size of the signature buffer, updated to be the size of the signature
|
||||
* \param[in] tbs a pointer to the SHA256 digest
|
||||
* \param[in] tbslen the size of the SHA256 digest
|
||||
* \return true when successful
|
||||
*/
|
||||
bool sign(evp_pkey_st* pkey, unsigned char* sig, std::size_t& siglen, const unsigned char* tbs, std::size_t tbslen);
|
||||
|
||||
/**
|
||||
* \brief verify a signature against a SHA 256 digest using ECDSA on curve secp256r1/prime256v1/P-256
|
||||
* \param[in] pkey the public key
|
||||
* \param[in] r the R component of the signature as a BIGNUM
|
||||
* \param[in] s the S component of the signature as a BIGNUM
|
||||
* \param[in] digest the SHA256 digest to sign
|
||||
* \return true when successful
|
||||
*/
|
||||
bool verify(evp_pkey_st* pkey, const bn_t& r, const bn_t& s, const sha_256_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief verify a signature against a SHA 256 digest using ECDSA on curve secp256r1/prime256v1/P-256
|
||||
* \param[in] pkey the public key
|
||||
* \param[in] r the R component of the signature as a BIGNUM (0-padded 32 bytes)
|
||||
* \param[in] s the S component of the signature as a BIGNUM (0-padded 32 bytes)
|
||||
* \param[in] digest the SHA256 digest to sign
|
||||
* \return true when successful
|
||||
*/
|
||||
bool verify(evp_pkey_st* pkey, const std::uint8_t* r, const std::uint8_t* s, const sha_256_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief verify a signature against a SHA 256 digest using ECDSA on curve secp256r1/prime256v1/P-256
|
||||
* \param[in] pkey the public key
|
||||
* \param[in] sig the DER encoded signature
|
||||
* \param[in] siglen the size of the DER encoded signature
|
||||
* \param[in] tbs a pointer to the SHA256 digest
|
||||
* \param[in] tbslen the size of the SHA256 digest
|
||||
* \return true when successful
|
||||
*/
|
||||
bool verify(evp_pkey_st* pkey, const unsigned char* sig, std::size_t siglen, const unsigned char* tbs,
|
||||
std::size_t tbslen);
|
||||
|
||||
/**
|
||||
* \brief calculate the SHA1 digest over an array of bytes
|
||||
* \param[in] data the start of the data
|
||||
* \param[in] len the length of the data
|
||||
* \param[out] the SHA1 digest
|
||||
* \return true on success
|
||||
*/
|
||||
bool sha_1(const void* data, std::size_t len, sha_1_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief calculate the SHA256 digest over an array of bytes
|
||||
* \param[in] data the start of the data
|
||||
* \param[in] len the length of the data
|
||||
* \param[out] the SHA256 digest
|
||||
* \return true on success
|
||||
*/
|
||||
bool sha_256(const void* data, std::size_t len, sha_256_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief calculate the SHA384 digest over an array of bytes
|
||||
* \param[in] data the start of the data
|
||||
* \param[in] len the length of the data
|
||||
* \param[out] the SHA384 digest
|
||||
* \return true on success
|
||||
*/
|
||||
bool sha_384(const void* data, std::size_t len, sha_384_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief calculate the SHA512 digest over an array of bytes
|
||||
* \param[in] data the start of the data
|
||||
* \param[in] len the length of the data
|
||||
* \param[out] the SHA512 digest
|
||||
* \return true on success
|
||||
*/
|
||||
bool sha_512(const void* data, std::size_t len, sha_512_digest_t& digest);
|
||||
|
||||
/**
|
||||
* \brief decode a base64 string into it's binary form
|
||||
* \param[in] text the base64 string (does not need to be \0 terminated)
|
||||
* \param[in] len the length of the string (excluding any terminating \0)
|
||||
* \return binary array or empty on error
|
||||
* \note tolerates stray '\t', '\n', '\v', '\f', '\r', ' ' (matching
|
||||
* EVP_Decode*'s B64_WS set); other non-alphabet bytes cause the
|
||||
* underlying BIO_f_base64 to fail and an empty vector is returned.
|
||||
* An embedded '\0' terminates the scan: anything past the first
|
||||
* '\0' is ignored.
|
||||
*/
|
||||
std::vector<std::uint8_t> base64_decode(const char* text, std::size_t len);
|
||||
|
||||
/**
|
||||
* \brief decode a base64 string into it's binary form
|
||||
* \param[in] text the base64 string (does not need to be \0 terminated)
|
||||
* \param[in] len the length of the string (excluding any terminating \0)
|
||||
* \param[out] out_data where to place the decoded data
|
||||
* \param[inout] out_len the size of out_data, updated to be the length of the decoded data
|
||||
* \return true on success
|
||||
*/
|
||||
bool base64_decode(const char* text, std::size_t len, std::uint8_t* out_data, std::size_t& out_len);
|
||||
|
||||
/**
|
||||
* \brief encode data into a base64 text string
|
||||
* \param[in] data the data to encode
|
||||
* \param[in] len the length of the data
|
||||
* \param[in] newLine when true add a \n to break the result into multiple lines
|
||||
* \return base64 string or empty on error
|
||||
*/
|
||||
std::string base64_encode(const std::uint8_t* data, std::size_t len, bool newLine);
|
||||
|
||||
/**
|
||||
* \brief encode data into a base64 text string
|
||||
* \param[in] data the data to encode
|
||||
* \param[in] len the length of the data
|
||||
* \return base64 string or empty on error
|
||||
* \note the return string doesn't include line breaks
|
||||
*/
|
||||
inline std::string base64_encode(const std::uint8_t* data, std::size_t len) {
|
||||
return base64_encode(data, len, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief zero a structure
|
||||
* \param mem the structure to zero
|
||||
*/
|
||||
template <typename T> constexpr void zero(T& mem) {
|
||||
std::memset(mem.data(), 0, mem.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief load a private key from file
|
||||
* \param mem the structure to zero
|
||||
*/
|
||||
pkey_ptr load_private_key(const char* filename, const char* password);
|
||||
|
||||
/**
|
||||
* \brief convert R, S BIGNUM to DER signature
|
||||
* \param[in] r the BIGNUM R component of the signature
|
||||
* \param[in] s the BIGNUM S component of the signature
|
||||
* \return The DER signature and its length
|
||||
*/
|
||||
DER bn_to_signature(const bn_t& r, const bn_t& s);
|
||||
|
||||
/**
|
||||
* \brief convert R, S BIGNUM to DER signature
|
||||
* \param[in] r the BIGNUM R component of the signature (0-padded 32 bytes)
|
||||
* \param[in] s the BIGNUM S component of the signature (0-padded 32 bytes)
|
||||
* \return The DER signature and its length
|
||||
*/
|
||||
DER bn_to_signature(const std::uint8_t* r, const std::uint8_t* s);
|
||||
|
||||
/**
|
||||
* \brief convert DER signature into BIGNUM R and S components
|
||||
* \param[out] r the BIGNUM R component of the signature
|
||||
* \param[out] s the BIGNUM S component of the signature
|
||||
* \param[in] sig_p a pointer to the DER encoded signature
|
||||
* \param[in] len the length of the DER encoded signature
|
||||
* \return true when successful
|
||||
*/
|
||||
bool signature_to_bn(openssl::bn_t& r, openssl::bn_t& s, const std::uint8_t* sig_p, std::size_t len);
|
||||
|
||||
/**
|
||||
* \brief load any PEM encoded certificates from a string
|
||||
* \param[in] pem_string
|
||||
* \return a list of 0 or more certificates
|
||||
* \note PEM string only supports certificates and not other PEM types
|
||||
*/
|
||||
certificate_list load_certificates_pem(const char* pem_string);
|
||||
|
||||
/**
|
||||
* \brief load any PEM encoded certificates from a file
|
||||
* \param[in] filename
|
||||
* \return a list of 0 or more certificates
|
||||
*/
|
||||
certificate_list load_certificates(const char* filename);
|
||||
|
||||
/**
|
||||
* \brief load any PEM encoded certificates from list of files
|
||||
* \param[in] filenames
|
||||
* \return a list of 0 or more certificates
|
||||
*/
|
||||
certificate_list load_certificates(const std::vector<const char*>& filenames);
|
||||
|
||||
/**
|
||||
* \brief load a PKI chain from leaf to root
|
||||
* \param[in] leaf_file is the server certificate
|
||||
* \param[in] chain_file is the file of intermediate certificates
|
||||
* \param[in] root_file is the self signed trust anchor
|
||||
* \return the certificate chain. chain_info_t::leaf will be nullptr when a chain
|
||||
* cannot be built
|
||||
* \note when leaf_file is null pointer the server certificate is the first certificate in the chain_file
|
||||
*/
|
||||
chain_info_t load_certificates(const char* leaf_file, const char* chain_file, const char* root_file);
|
||||
|
||||
/**
|
||||
* \brief load a PKI chain from leaf to root
|
||||
* \param[in] chain is the structure containing the three filenames
|
||||
* \return the certificate chain. chain_info_t::leaf will be nullptr when a chain
|
||||
* cannot be built
|
||||
* \note when leaf_file is null pointer the server certificate is the first certificate in the chain_file
|
||||
*/
|
||||
static inline chain_info_t load_certificates(const chain_filenames_t& chain) {
|
||||
return load_certificates(chain.leaf, chain.chain, chain.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief load a PKI chains from leaf to root from a list of chain filenames
|
||||
* \param[in] chains is a list of structures containing the three filenames
|
||||
* \return a list of valid certificate chains (can be an empty list)
|
||||
*/
|
||||
chain_info_list_t load_certificates(const chain_filenames_list_t& chains);
|
||||
|
||||
/**
|
||||
* \brief check that a private key is associated with a certificate
|
||||
* \param[in] cert is the certificate
|
||||
* \param[in] pkey is the private key
|
||||
* \return true when the key matches the certificate
|
||||
*/
|
||||
bool verify_certificate_key(const x509_st* cert, const evp_pkey_st* pkey);
|
||||
|
||||
/**
|
||||
* \brief verify a certificate chain from leaf to trust anchor(s)
|
||||
* \param[in] chain the structure containing the certificates
|
||||
* \return true when there is a valid chain
|
||||
*/
|
||||
bool verify_chain(const chain_info_t& chain);
|
||||
|
||||
/**
|
||||
* \brief verify a certificate chain from leaf to trust anchor(s) and that
|
||||
* the private key is associated with the leaf certificate
|
||||
* \param[in] chain the structure containing the certificates and private key
|
||||
* \return true when there is a valid chain and the key matches
|
||||
*/
|
||||
bool verify_chain(const chain_t& chain);
|
||||
|
||||
/**
|
||||
* \brief apply the certificates and private key to the SSL session
|
||||
* \param[in] chain the structure containing the certificates and private key
|
||||
* \return true when successfully applied
|
||||
*/
|
||||
bool use_certificate_and_key(ssl_st* ssl, const chain_t& chain);
|
||||
|
||||
/**
|
||||
* \brief convert a certificate to a PEM string
|
||||
* \param[in] cert the certificate
|
||||
* \return the PEM string or empty on error
|
||||
*/
|
||||
std::string certificate_to_pem(const x509_st* cert);
|
||||
|
||||
/**
|
||||
* \brief convert a PEM string to a certificate
|
||||
* \param[in] pem the PEM string
|
||||
* \return the certificate or empty unique_ptr on error
|
||||
*/
|
||||
certificate_ptr pem_to_certificate(const std::string& pem);
|
||||
|
||||
/**
|
||||
* \brief parse a DER (ASN.1) encoded certificate
|
||||
* \param[in] der a pointer to the DER encoded certificate
|
||||
* \param[in] len the length of the DER encoded certificate
|
||||
* \return the certificate or empty unique_ptr on error
|
||||
*/
|
||||
certificate_ptr der_to_certificate(const std::uint8_t* der, std::size_t len);
|
||||
|
||||
/**
|
||||
* \brief encode a certificate to DER (ASN.1)
|
||||
* \param[in] cert the certificate
|
||||
* \return the DER encoded certificate or nullptr on error
|
||||
*/
|
||||
DER certificate_to_der(const x509_st* cert);
|
||||
|
||||
/**
|
||||
* \brief verify a certificate against a certificate chain and trust anchors
|
||||
* \param[in] cert the certificate to verify - when nullptr the certificate must
|
||||
* be the first certificate in the untrusted list
|
||||
* \param[in] trust_anchors a list of trust anchors. Must not contain any
|
||||
* intermediate CAs
|
||||
* \param[in] untrusted intermediate CAs needed to form a chain from the leaf
|
||||
* certificate to one of the supplied trust anchors
|
||||
*/
|
||||
verify_result_t verify_certificate(const x509_st* cert, const certificate_list& trust_anchors,
|
||||
const certificate_list& untrusted);
|
||||
|
||||
/**
|
||||
* \brief extract the certificate subject as a dictionary of name/value pairs
|
||||
* \param cert the certificate
|
||||
* \return dictionary of the (short name, value) pairs
|
||||
* \note short name examples "CN" for CommonName "OU" for OrganizationalUnit
|
||||
* "C" for Country ...
|
||||
*/
|
||||
std::map<std::string, std::string> certificate_subject(const x509_st* cert);
|
||||
|
||||
/**
|
||||
* \brief extract the certificate subject as DER encoded data
|
||||
* \param cert the certificate
|
||||
* \return the DER encoded subject or nullptr on error
|
||||
*/
|
||||
DER certificate_subject_der(const x509_st* cert);
|
||||
|
||||
/**
|
||||
* \brief extract the subject public key from the certificate
|
||||
* \param[in] cert the certificate
|
||||
* \return a unique_ptr holding the key or empty on error
|
||||
*/
|
||||
pkey_ptr certificate_public_key(x509_st* cert);
|
||||
|
||||
/**
|
||||
* \brief calculate SHA1 hash over the DER certificate
|
||||
* \param[out] digest the SHA1 digest of the certificate
|
||||
* \param[in] cert the certificate
|
||||
* \return true on success
|
||||
* \note this is the hash of the whole certificate including signature
|
||||
*/
|
||||
bool certificate_sha_1(openssl::sha_1_digest_t& digest, const x509_st* cert);
|
||||
|
||||
/**
|
||||
* \brief calculate SHA1 hash over the DER certificate's subject public key
|
||||
* \param[out] digest the SHA1 digest of the public key
|
||||
* \param[in] cert the certificate
|
||||
* \return true on success
|
||||
*/
|
||||
bool certificate_subject_public_key_sha_1(openssl::sha_1_digest_t& digest, const x509_st* cert);
|
||||
|
||||
enum class log_level_t : std::uint8_t {
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief log an OpenSSL event
|
||||
* \param[in] level the event level
|
||||
* \param[in] str string to display
|
||||
* \note any OpenSSL error is displayed after the string
|
||||
*/
|
||||
void log(log_level_t level, const std::string& str);
|
||||
|
||||
static inline void log_error(const std::string& str) {
|
||||
log(log_level_t::error, str);
|
||||
}
|
||||
|
||||
static inline void log_warning(const std::string& str) {
|
||||
log(log_level_t::warning, str);
|
||||
}
|
||||
|
||||
static inline void log_debug(const std::string& str) {
|
||||
log(log_level_t::debug, str);
|
||||
}
|
||||
|
||||
static inline void log_info(const std::string& str) {
|
||||
log(log_level_t::info, str);
|
||||
}
|
||||
|
||||
using log_handler_t = void (*)(log_level_t level, const std::string& err);
|
||||
|
||||
/**
|
||||
* \brief set log handler function
|
||||
* \param[in] handler a pointer to the function
|
||||
* \return the pointer to the previous handler or nullptr
|
||||
* where there is no previous handler
|
||||
*/
|
||||
log_handler_t set_log_handler(log_handler_t handler);
|
||||
|
||||
} // namespace openssl
|
||||
|
||||
#endif // OPENSSL_UTIL_HPP_
|
||||
691
tools/EVerest-main/lib/everest/tls/include/everest/tls/tls.hpp
Normal file
691
tools/EVerest-main/lib/everest/tls/include/everest/tls/tls.hpp
Normal file
@@ -0,0 +1,691 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef TLS_HPP_
|
||||
#define TLS_HPP_
|
||||
|
||||
#include "extensions/status_request.hpp"
|
||||
#include "extensions/trusted_ca_keys.hpp"
|
||||
#include <everest/tls/tls_types.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <netinet/in.h>
|
||||
#include <openssl/types.h>
|
||||
#include <optional>
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace tls {
|
||||
|
||||
constexpr int INVALID_SOCKET{-1};
|
||||
constexpr std::uint32_t c_shutdown_timeout_ms = 5000; // 5 seconds
|
||||
#ifdef UNIT_TEST
|
||||
constexpr std::uint32_t c_serve_timeout_ms = 1000; // 1 second
|
||||
#else
|
||||
constexpr std::uint32_t c_serve_timeout_ms = 60000; // 60 seconds
|
||||
#endif
|
||||
|
||||
struct connection_ctx;
|
||||
struct server_ctx;
|
||||
struct client_ctx;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ConfigItem - store configuration item allowing nullptr
|
||||
|
||||
/**
|
||||
* \brief class to hold configuration strings, behaves like const char *
|
||||
* but keeps a copy
|
||||
*
|
||||
* unlike std::string this class allows nullptr as a valid setting and it
|
||||
* doesn't have scope issues since it holds a copy.
|
||||
*/
|
||||
class ConfigItem {
|
||||
private:
|
||||
std::optional<std::string> value{};
|
||||
|
||||
public:
|
||||
ConfigItem() = default;
|
||||
ConfigItem(const char* ptr) {
|
||||
if (ptr != nullptr) {
|
||||
value = ptr;
|
||||
}
|
||||
}
|
||||
inline operator const char*() const {
|
||||
return (value) ? value.value().c_str() : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class TlsKeyLoggingServer {
|
||||
public:
|
||||
TlsKeyLoggingServer(const std::string& interface_name, uint16_t port_);
|
||||
~TlsKeyLoggingServer();
|
||||
|
||||
ssize_t send(const char* line);
|
||||
|
||||
auto get_fd() const {
|
||||
return fd;
|
||||
}
|
||||
|
||||
auto get_port() const {
|
||||
return port;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd{-1};
|
||||
uint16_t port{0};
|
||||
sockaddr_in6 destination_address{};
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Connection represents a TLS connection
|
||||
|
||||
/**
|
||||
* \brief class representing a TLS connection
|
||||
* \note small timeout values (under 200ms) can cause connections to fail
|
||||
* even on the loopback interface.
|
||||
*
|
||||
* Non-blocking I/O is configured on the underlying socket. Its use is controlled
|
||||
* by the 'timeout_ms' parameter:
|
||||
*
|
||||
* timeout_ms is -1 means wait forever. The method will block until there is an
|
||||
* event on the socket.
|
||||
|
||||
* timeout_ms is 0 means don't wait. The method will return want_read or
|
||||
* want_write when it is unable to complete the operation. The method should be
|
||||
* called with the same arguments once read/write is available on the socket -
|
||||
* via select() or poll(). The underlying socket is available via socket().
|
||||
* This supports waiting for events on many sockets.
|
||||
* A helper method wait_for() is provided where waiting on only one socket is
|
||||
* needed.
|
||||
*
|
||||
* timeout_ms > 0 means wait no more than timeout_ms.
|
||||
*/
|
||||
class Connection {
|
||||
public:
|
||||
/**
|
||||
* \brief connection state
|
||||
*/
|
||||
enum class state_t : std::uint8_t {
|
||||
idle, //!< no connection in progress
|
||||
connected, //!< active connection
|
||||
closed, //!< connection has closed
|
||||
fault, //!< connection has faulted
|
||||
};
|
||||
|
||||
enum class result_t : std::uint8_t {
|
||||
success, //!< operation completed successfully
|
||||
closed, //!< connection closed (possibly due to error)
|
||||
timeout, //!< operation timed out
|
||||
want_read, //!< non-blocking - operation waiting for read available on socket
|
||||
want_write, //!< non-blocking - operation waiting for write available on socket
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<connection_ctx> m_context; //!< opaque connection data
|
||||
state_t m_state{state_t::idle}; //!< connection state
|
||||
std::string m_ip; //!< peer IP address
|
||||
std::string m_service; //!< peer port
|
||||
std::int32_t m_timeout_ms; //!< default operation timeout
|
||||
|
||||
// prevent standalone construction
|
||||
Connection(SslContext* ctx, int soc, const char* ip_in, const char* service_in, std::int32_t timeout_ms);
|
||||
|
||||
public:
|
||||
Connection() = delete;
|
||||
Connection(const Connection&) = delete;
|
||||
Connection(Connection&&) = delete;
|
||||
Connection& operator=(const Connection&) = delete;
|
||||
Connection& operator=(Connection&&) = delete;
|
||||
~Connection();
|
||||
|
||||
/**
|
||||
* \brief change the default operation timeout
|
||||
* \param[in] timeout_ms new timeout value
|
||||
*/
|
||||
void timeout(std::int32_t timeout_ms) {
|
||||
m_timeout_ms = timeout_ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief get the default operation timeout
|
||||
* \return timeout value
|
||||
*/
|
||||
[[nodiscard]] std::int32_t timeout() const {
|
||||
return m_timeout_ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief read bytes from the TLS connection
|
||||
* \param[out] buf pointer to output buffer
|
||||
* \param[in] num size of output buffer
|
||||
* \param[in] timeout_ms time to wait in milliseconds, -1 is wait forever, 0 is don't wait
|
||||
* \param[out] readBytes number of received bytes. May be less than num
|
||||
* when there has been a timeout
|
||||
* \return see result_t. On error the connection will have
|
||||
* been closed
|
||||
*/
|
||||
[[nodiscard]] result_t read(std::byte* buf, std::size_t num, std::size_t& readbytes, int timeout_ms);
|
||||
[[nodiscard]] inline result_t read(std::byte* buf, std::size_t num, std::size_t& readbytes) {
|
||||
return read(buf, num, readbytes, m_timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief write bytes to the TLS connection
|
||||
* \param[in] buf pointer to input buffer
|
||||
* \param[in] num size of input buffer
|
||||
* \param[in] timeout_ms time to wait in milliseconds, -1 is wait forever, 0 is don't wait
|
||||
* \param[out] writeBytes number of sent bytes. May be less than num
|
||||
* when there has been a timeout
|
||||
* \return see result_t. On error the connection will have
|
||||
* been closed
|
||||
*/
|
||||
[[nodiscard]] result_t write(const std::byte* buf, std::size_t num, std::size_t& writebytes, int timeout_ms);
|
||||
[[nodiscard]] inline result_t write(const std::byte* buf, std::size_t num, std::size_t& writeBytes) {
|
||||
return write(buf, num, writeBytes, m_timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief close the TLS connection
|
||||
* \param[in] timeout_ms time to wait in milliseconds
|
||||
* \return see result_t
|
||||
* \note result_t::closed is more likely than result_t::success since the
|
||||
* connection is closed.
|
||||
*/
|
||||
result_t shutdown(int timeout_ms);
|
||||
inline result_t shutdown() {
|
||||
return shutdown(c_shutdown_timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief wait for activity on the socket
|
||||
* \param[in] action is result_t::want_read or result_t::want_write
|
||||
* \param[in] timeout_ms time to wait in milliseconds, -1 is wait forever, 0 is don't wait
|
||||
* \return see result_t
|
||||
*/
|
||||
result_t wait_for(result_t action, int timeout_ms);
|
||||
inline result_t wait_for(result_t action) {
|
||||
return wait_for(action, m_timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* IP address of the connection's peer
|
||||
*/
|
||||
[[nodiscard]] const std::string& ip_address() const {
|
||||
return m_ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service (port number) of the connection's peer
|
||||
*/
|
||||
[[nodiscard]] const std::string& service() const {
|
||||
return m_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief return the current state
|
||||
* \return the current state
|
||||
*/
|
||||
[[nodiscard]] state_t state() const {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief obtain the underlying socket for use with poll() or select()
|
||||
* \returns the underlying socket or INVALID_SOCKET on error
|
||||
*/
|
||||
[[nodiscard]] int socket() const;
|
||||
|
||||
/**
|
||||
* \brief obtain the peer certificate
|
||||
* \returns the certificate or nullptr
|
||||
* \note the certificate must not be freed
|
||||
*/
|
||||
[[nodiscard]] const Certificate* peer_certificate() const;
|
||||
|
||||
/**
|
||||
* \brief obtain the underlying SSL context
|
||||
* \returns the underlying SSL context pointer
|
||||
*/
|
||||
[[nodiscard]] [[deprecated(
|
||||
"Temporarily used with IsoMux module. Will be removed together with IsoMux module in the future.")]] SSL*
|
||||
ssl_context() const;
|
||||
|
||||
/**
|
||||
* \brief set the read timeout in ms
|
||||
*/
|
||||
[[deprecated(
|
||||
"Temporarily used with IsoMux module. Will be removed together with IsoMux module in the future.")]] void
|
||||
set_read_timeout(int ms) {
|
||||
m_timeout_ms = ms;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief class representing a TLS connection (server side)
|
||||
*/
|
||||
class ServerConnection : public Connection {
|
||||
private:
|
||||
using ServerStatusRequestV2 = status_request::ServerStatusRequestV2;
|
||||
using ServerTrustedCaKeys = trusted_ca_keys::ServerTrustedCaKeys;
|
||||
using trusted_ca_keys_t = trusted_ca_keys::trusted_ca_keys_t;
|
||||
using server_trusted_ca_keys_t = trusted_ca_keys::server_trusted_ca_keys_t;
|
||||
|
||||
static std::uint32_t m_count; //!< number of active connections
|
||||
static std::mutex m_cv_mutex; //!< used for wait_all_closed()
|
||||
static std::condition_variable m_cv; //!< used for wait_all_closed()
|
||||
trusted_ca_keys_t m_trusted_ca_keys; //!< trusted CA keys configuration
|
||||
StatusFlags m_flags; //!< extension flags
|
||||
server_trusted_ca_keys_t m_tck_data; //!< extension per connection data
|
||||
|
||||
std::unique_ptr<TlsKeyLoggingServer> m_keylog_server{nullptr};
|
||||
|
||||
public:
|
||||
ServerConnection(SslContext* ctx, int soc, const char* ip_in, const char* service_in, std::int32_t timeout_ms,
|
||||
const ConfigItem& tls_key_interface);
|
||||
ServerConnection() = delete;
|
||||
ServerConnection(const ServerConnection&) = delete;
|
||||
ServerConnection(ServerConnection&&) = delete;
|
||||
ServerConnection& operator=(const ServerConnection&) = delete;
|
||||
ServerConnection& operator=(ServerConnection&&) = delete;
|
||||
~ServerConnection();
|
||||
|
||||
/**
|
||||
* \brief accept the incoming connection and run the TLS handshake
|
||||
* \param[in] timeout_ms time to wait in milliseconds, -1 is wait forever, 0 is don't wait
|
||||
* \return see result_t. On error the connection will have
|
||||
* been closed
|
||||
*/
|
||||
[[nodiscard]] result_t accept(int timeout_ms);
|
||||
[[nodiscard]] inline result_t accept() {
|
||||
return accept(m_timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief wait for all connections to be closed
|
||||
*/
|
||||
static void wait_all_closed();
|
||||
|
||||
/**
|
||||
* \brief return number of active connections (indicative only)
|
||||
* \return number of active connections
|
||||
*/
|
||||
static std::uint32_t active_connections() {
|
||||
return m_count;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief class representing a TLS connection (client side)
|
||||
*/
|
||||
class ClientConnection : public Connection {
|
||||
public:
|
||||
ClientConnection(SslContext* ctx, int soc, const char* ip_in, const char* service_in, std::int32_t timeout_ms);
|
||||
ClientConnection() = delete;
|
||||
ClientConnection(const ClientConnection&) = delete;
|
||||
ClientConnection(ClientConnection&&) = delete;
|
||||
ClientConnection& operator=(const ClientConnection&) = delete;
|
||||
ClientConnection& operator=(ClientConnection&&) = delete;
|
||||
~ClientConnection();
|
||||
|
||||
/**
|
||||
* \brief run the TLS handshake
|
||||
* \param[in] timeout_ms time to wait in milliseconds, -1 is wait forever, 0 is don't wait
|
||||
* \return see result_t. On error the connection will have
|
||||
* been closed
|
||||
*/
|
||||
[[nodiscard]] result_t connect(int timeout_ms);
|
||||
[[nodiscard]] inline result_t connect() {
|
||||
return connect(m_timeout_ms);
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TLS server
|
||||
|
||||
/**
|
||||
* \brief represents a TLS server
|
||||
*
|
||||
* The TLS server does not make use of any threads. This is to support multiple
|
||||
* use cases giving developers the option on how best to support multiple
|
||||
* incoming connections.
|
||||
*
|
||||
* Example code in tests has some examples on how this can be done.
|
||||
*
|
||||
* One option is to have a server thread calling Server.serve() with the
|
||||
* supplied handler creating a new connection thread when a connection arrives.
|
||||
*
|
||||
* For unit tests the connection handler can perform the test directly which
|
||||
* has the advantage of preventing new connections from being accepted.
|
||||
*
|
||||
* Another option is for the connection handler to add the new connection to a
|
||||
* list which is serviced by an event handler - i.e. one thread could manage
|
||||
* all connections.
|
||||
*
|
||||
* Listens on either IPv4 or IPv6. OpenSSL recommends that two listen sockets
|
||||
* are used to support both IPv4 and IPv6. (not implemented)
|
||||
*/
|
||||
class Server {
|
||||
public:
|
||||
/**
|
||||
* \brief server state
|
||||
*/
|
||||
enum class state_t : std::uint8_t {
|
||||
init_needed, //!< not initialised yet - call init()
|
||||
init_socket, //!< TCP listen socket initialised (but not SSL) - call update()
|
||||
init_complete, //!< initialised but not running - call serve()
|
||||
running, //!< waiting for connections - fully initialised
|
||||
stopped, //!< stopped - reinitialisation will be needed
|
||||
};
|
||||
|
||||
struct certificate_config_t {
|
||||
//!< server certificate is the first certificate in the file followed by any intermediate CAs
|
||||
ConfigItem certificate_chain_file{nullptr};
|
||||
ConfigItem trust_anchor_file{nullptr}; //!< one or more trust anchor PEM certificates
|
||||
ConfigItem trust_anchor_pem{nullptr}; //!< one or more trust anchor PEM certificates
|
||||
ConfigItem private_key_file{nullptr}; //!< key associated with the server certificate
|
||||
ConfigItem private_key_password{nullptr}; //!< optional password to read private key
|
||||
std::vector<ConfigItem> ocsp_response_files; //!< list of OCSP files in certificate chain order
|
||||
};
|
||||
|
||||
struct config_t {
|
||||
ConfigItem cipher_list{nullptr}; //!< nullptr means use default
|
||||
ConfigItem ciphersuites{nullptr}; //!< nullptr means use default, "" disables TSL 1.3
|
||||
|
||||
std::vector<certificate_config_t> chains; //!< server certificate chains - must be at least one
|
||||
//!< one or more trust anchor PEM certificates for client certificate verification
|
||||
ConfigItem verify_locations_file{nullptr};
|
||||
ConfigItem verify_locations_path{nullptr}; //!< for client certificate
|
||||
std::int32_t io_timeout_ms{-1}; //!< socket timeout in milliseconds (recommend > 1 sec)
|
||||
bool verify_client{true}; //!< client certificate required
|
||||
|
||||
// config not used on update()
|
||||
ConfigItem host{nullptr}; //!< see BIO_lookup_ex()
|
||||
ConfigItem service{nullptr}; //!< TLS port number as a string
|
||||
int socket{INVALID_SOCKET}; //!< use this specific socket - bypasses socket setup in init_socket() when set
|
||||
bool ipv6_only{true}; //!< listen on IPv6 only, when false listen on IPv4 only
|
||||
|
||||
bool tls_key_logging{false}; //!< tls key logging is active when true
|
||||
std::string tls_key_logging_path; //!< tls key logging file path
|
||||
};
|
||||
|
||||
using ConnectionPtr = std::unique_ptr<ServerConnection>;
|
||||
using ConnectionHandler = std::function<void(ConnectionPtr&& ctx)>;
|
||||
using OptionalConfig = std::optional<std::unique_ptr<config_t>>;
|
||||
using ConfigurationCallback = std::function<OptionalConfig()>;
|
||||
|
||||
private:
|
||||
using ServerStatusRequestV2 = status_request::ServerStatusRequestV2;
|
||||
using trusted_ca_keys_t = trusted_ca_keys::trusted_ca_keys_t;
|
||||
using ServerTrustedCaKeys = trusted_ca_keys::ServerTrustedCaKeys;
|
||||
|
||||
std::unique_ptr<server_ctx> m_context; //!< opaque object data
|
||||
int m_socket{INVALID_SOCKET}; //!< server socket value
|
||||
volatile bool m_running{false}; //!< server is listening for connections
|
||||
std::int32_t m_timeout_ms{-1}; //!< default operation timeout passed to new connections
|
||||
std::atomic_bool m_exit{false}; //!< stop listening for connections
|
||||
std::atomic<state_t> m_state{state_t::init_needed}; //!< server state
|
||||
std::mutex m_mutex; //!< prevent multiple initialisation or serve requests
|
||||
std::mutex m_update_mutex; //!< ensure safe updating of SSL configuration
|
||||
std::mutex m_cv_mutex; //!< used by wait_running() and wait_stopped()
|
||||
std::condition_variable m_cv; //!< used by wait_running() and wait_stopped()
|
||||
OcspCache m_cache; //!< cached OCSP responses
|
||||
ServerStatusRequestV2 m_status_request_v2; //!< status request extension handler
|
||||
ServerTrustedCaKeys m_server_trusted_ca_keys; //!< trusted ca keys extension handler
|
||||
pthread_t m_server_thread{}; //!< serve() POSIX threads ID
|
||||
static int s_sig_int; //!< signal to use to wakeup serve()
|
||||
ConfigurationCallback m_init_callback{nullptr}; //!< callback to retrieve SSL configuration
|
||||
|
||||
ConfigItem m_tls_key_interface{nullptr};
|
||||
std::filesystem::path tls_key_log_file_path{};
|
||||
|
||||
/**
|
||||
* \brief initialise the server socket
|
||||
* \param[in] cfg server configuration
|
||||
* \return true on success
|
||||
*/
|
||||
bool init_socket(const config_t& cfg);
|
||||
|
||||
/**
|
||||
* \brief initialise TLS configuration
|
||||
* \param[in] cfg server configuration
|
||||
* \return true on success
|
||||
*/
|
||||
bool init_ssl(const config_t& cfg);
|
||||
|
||||
/**
|
||||
* \brief unconfigure SSL
|
||||
*/
|
||||
void deinit_ssl();
|
||||
|
||||
/**
|
||||
* \brief initialise server certificate chains
|
||||
* \param[in] chain_files server certificate chains
|
||||
* \return true on success
|
||||
*/
|
||||
bool init_certificates(const std::vector<certificate_config_t>& chain_files);
|
||||
|
||||
/**
|
||||
* \brief unconfigure SSL certificates
|
||||
*/
|
||||
void deinit_certificates();
|
||||
|
||||
/**
|
||||
* \brief waits for incoming connections
|
||||
* \param[in] handler - called with the new connection socket
|
||||
*/
|
||||
void wait_for_connection(const ConnectionHandler& handler);
|
||||
|
||||
public:
|
||||
Server();
|
||||
Server(const Server&) = delete;
|
||||
Server(Server&&) = delete;
|
||||
Server& operator=(const Server&) = delete;
|
||||
Server& operator=(Server&&) = delete;
|
||||
~Server();
|
||||
|
||||
/**
|
||||
* \brief configure a signal handler to wakeup serve()
|
||||
* \note stop() sends interrupt_signal to m_server_thread to interrupt poll()
|
||||
*/
|
||||
static void configure_signal_handler(int interrupt_signal);
|
||||
|
||||
/**
|
||||
* \brief initialise the server socket and TLS configuration
|
||||
* \param[in] cfg server configuration
|
||||
* \param[in] init_ssl function to collect certificates and keys, can be nullptr
|
||||
* \return need_init - initialisation failed
|
||||
* socket_init - server socket created and ready for serve()
|
||||
* init_complete - SSL certificates and keys loaded
|
||||
*
|
||||
* It is possible to initialise the server and start listening for
|
||||
* connections before certificates and keys are available.
|
||||
* when init() returns socket_init the server will call init_ssl() with a
|
||||
* reference to the object so that update() can be called with updated
|
||||
* OCSP and SSL configuration.
|
||||
*
|
||||
* init_ssl() should return true when SSL has been configured so that the
|
||||
* incoming connection is accepted.
|
||||
*/
|
||||
state_t init(const config_t& cfg, const ConfigurationCallback& init_ssl);
|
||||
|
||||
/**
|
||||
* \brief update configuration
|
||||
* \param[in] cfg server configuration
|
||||
* \return true on success
|
||||
* \note used to update OCSP caches and SSL certificates and keys.
|
||||
* Does not change the listen socket settings
|
||||
*/
|
||||
bool update(const config_t& cfg);
|
||||
|
||||
/**
|
||||
* \brief wait for incomming connections
|
||||
* \param[in] handler called when there is a new connection
|
||||
* \return stopped after it has been running, or init_* values when listening
|
||||
* can not start
|
||||
* \note this is a blocking call that will not return until stop() has been
|
||||
* called (unless it couldn't start listening)
|
||||
* \note changing socket configuration requires stopping the server and
|
||||
* calling init()
|
||||
* \note after server() returns stopped init() will need to be called
|
||||
* before further connections can be managed
|
||||
*/
|
||||
state_t serve(const ConnectionHandler& handler);
|
||||
|
||||
/**
|
||||
* \brief stop accepting new connections and wait until SSL configuration
|
||||
* is loaded via update()
|
||||
* \returns true when the suspend was successful
|
||||
*/
|
||||
bool suspend();
|
||||
|
||||
/**
|
||||
* \brief stop listening for new connections
|
||||
* \note returns immediately
|
||||
*
|
||||
* may raise a signal on the m_server_thread to terminate poll() so that
|
||||
* serve() stops quickly. (see configure_signal_handler())
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* \brief wait for server to start listening for connections
|
||||
* \note blocks until server is running
|
||||
*/
|
||||
void wait_running();
|
||||
|
||||
/**
|
||||
* \brief wait for server to stop
|
||||
* \note blocks until server has stopped
|
||||
*/
|
||||
void wait_stopped();
|
||||
|
||||
/**
|
||||
* \brief return the current server state (indicative only)
|
||||
* \return current server state
|
||||
*/
|
||||
[[nodiscard]] state_t state() const {
|
||||
return m_state;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TLS client
|
||||
|
||||
/**
|
||||
* \brief represents a TLS client
|
||||
* \note mainly intended for unit and system tests
|
||||
*/
|
||||
class Client {
|
||||
public:
|
||||
using trusted_ca_keys_t = trusted_ca_keys::trusted_ca_keys_t;
|
||||
using ClientStatusRequestV2 = status_request::ClientStatusRequestV2;
|
||||
using ClientTrustedCaKeys = trusted_ca_keys::ClientTrustedCaKeys;
|
||||
|
||||
/**
|
||||
* \brief configure different extension handler callbacks.
|
||||
* \note all callbacks must be specified, nullptr is not allowed.
|
||||
* \note mainly used for unit tests to send invalid extensions to the server.
|
||||
*/
|
||||
struct override_t {
|
||||
int (*tlsext_status_cb)(Ssl* ctx, void* object){nullptr};
|
||||
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){nullptr};
|
||||
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){nullptr};
|
||||
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){nullptr};
|
||||
void (*trusted_ca_keys_free)(Ssl* ctx, unsigned int ext_type, unsigned int context, const unsigned char* out,
|
||||
void* object){nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief client state
|
||||
*/
|
||||
enum class state_t : std::uint8_t {
|
||||
need_init, //!< not initialised yet
|
||||
init, //!< initialised but not connected
|
||||
connected, //!< connected to server
|
||||
stopped, //!< stopped
|
||||
};
|
||||
|
||||
struct config_t {
|
||||
const char* cipher_list{nullptr}; //!< nullptr means use default
|
||||
const char* ciphersuites{nullptr}; //!< nullptr means use default, "" disables TSL 1.3
|
||||
const char* certificate_chain_file{nullptr}; //!< client certificate and intermediate CAs
|
||||
const char* private_key_file{nullptr}; //!< private key for client certificate
|
||||
const char* private_key_password{nullptr}; //!< optional password for private key
|
||||
const char* verify_locations_file{nullptr}; //!< PEM trust anchors for server certificate
|
||||
const char* verify_locations_path{nullptr}; //!< for server certificate
|
||||
trusted_ca_keys_t trusted_ca_keys_data; //!< trusted CA keys configuration data
|
||||
std::int32_t io_timeout_ms{-1}; //!< default socket timeout in milliseconds (recommend > 1 sec)
|
||||
bool verify_server{true}; //!< verify the server certificate
|
||||
bool status_request{false}; //!< include a status request extension in the client hello
|
||||
bool status_request_v2{false}; //!< include a status request v2 extension in the client hello
|
||||
bool trusted_ca_keys{false}; //!< include a trusted ca keys extension in the client hello
|
||||
};
|
||||
|
||||
using ConnectionPtr = std::unique_ptr<ClientConnection>;
|
||||
|
||||
private:
|
||||
std::unique_ptr<client_ctx> m_context; //!< opaque object data
|
||||
std::int32_t m_timeout_ms{-1}; //!< default operation timeout
|
||||
trusted_ca_keys_t m_trusted_ca_keys; //!< trusted CA keys configuration data
|
||||
std::unique_ptr<ClientStatusRequestV2> m_status_request_v2; //!< status request extension handler
|
||||
|
||||
public:
|
||||
Client();
|
||||
explicit Client(std::unique_ptr<ClientStatusRequestV2>&& handler);
|
||||
Client(const Client&) = delete;
|
||||
Client(Client&&) = delete;
|
||||
Client& operator=(const Client&) = delete;
|
||||
Client& operator=(Client&&) = delete;
|
||||
virtual ~Client();
|
||||
|
||||
/**
|
||||
* \brief initialise the client socket and TLS configuration
|
||||
* \param[in] cfg server configuration
|
||||
* \return true on success
|
||||
*/
|
||||
bool init(const config_t& cfg);
|
||||
|
||||
/**
|
||||
* \brief initialise the client socket and TLS configuration
|
||||
* \param[in] cfg server configuration
|
||||
* \param[in] override override SSL callbacks
|
||||
* \return true on success
|
||||
*/
|
||||
bool init(const config_t& cfg, const override_t& override);
|
||||
|
||||
/**
|
||||
* \brief connect to server
|
||||
* \param[in] host host to connect to
|
||||
* \param[in] service port to connect to
|
||||
* \param[in] ipv6_only false - also support IPv4
|
||||
* \param[in] timeout_ms how long to wait for a connection in milliseconds
|
||||
* \return a connection pointer (nullptr on error)
|
||||
*/
|
||||
[[nodiscard]] ConnectionPtr connect(const char* host, const char* service, bool ipv6_only, int timeout_ms);
|
||||
[[nodiscard]] inline ConnectionPtr connect(const char* host, const char* service, bool ipv6_only) {
|
||||
return connect(host, service, ipv6_only, m_timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief the default SSL callbacks
|
||||
*/
|
||||
static override_t default_overrides();
|
||||
};
|
||||
|
||||
} // namespace tls
|
||||
|
||||
#endif // TLS_HPP_
|
||||
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EXTENSIONS_TLS_TYPES_
|
||||
#define EXTENSIONS_TLS_TYPES_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <everest/util/enum/EnumFlags.hpp>
|
||||
|
||||
struct ocsp_response_st;
|
||||
struct ssl_ctx_st;
|
||||
struct ssl_st;
|
||||
struct x509_st;
|
||||
struct evp_pkey_st;
|
||||
|
||||
namespace tls {
|
||||
|
||||
/// \brief flags used to keep track of TLS extensions in the client hello
|
||||
class StatusFlags {
|
||||
private:
|
||||
enum class flags_t : std::uint8_t {
|
||||
status_request,
|
||||
status_request_v2,
|
||||
trusted_ca_keys,
|
||||
last = trusted_ca_keys,
|
||||
};
|
||||
|
||||
everest::lib::util::AtomicEnumFlags<flags_t> flags;
|
||||
|
||||
public:
|
||||
void status_request_received() {
|
||||
flags.set(flags_t::status_request);
|
||||
}
|
||||
void status_request_v2_received() {
|
||||
flags.set(flags_t::status_request_v2);
|
||||
}
|
||||
void trusted_ca_keys_received() {
|
||||
flags.set(flags_t::trusted_ca_keys);
|
||||
}
|
||||
[[nodiscard]] bool has_status_request() const {
|
||||
return flags.is_set(flags_t::status_request);
|
||||
}
|
||||
[[nodiscard]] bool has_status_request_v2() const {
|
||||
return flags.is_set(flags_t::status_request_v2);
|
||||
}
|
||||
[[nodiscard]] bool has_trusted_ca_keys() const {
|
||||
return flags.is_set(flags_t::trusted_ca_keys);
|
||||
}
|
||||
};
|
||||
|
||||
// opaque types
|
||||
|
||||
using Certificate = struct ::x509_st;
|
||||
using OcspResponse = struct ::ocsp_response_st;
|
||||
using PKey = struct ::evp_pkey_st;
|
||||
using Ssl = struct ::ssl_st;
|
||||
using SslContext = struct ::ssl_ctx_st;
|
||||
|
||||
// see https://datatracker.ietf.org/doc/html/rfc6961
|
||||
constexpr int TLSEXT_TYPE_status_request_v2 = 17;
|
||||
|
||||
} // namespace tls
|
||||
|
||||
#endif // EXTENSIONS_TLS_TYPES_
|
||||
@@ -0,0 +1,139 @@
|
||||
From 92125584f2fe87023cbfe96bba06358111ed8c13 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <james.chapman@pionix.de>
|
||||
Date: Fri, 21 Jun 2024 10:29:44 +0100
|
||||
Subject: [PATCH 1/1] feat: updates to support status_request_v2
|
||||
|
||||
Signed-off-by: James Chapman <james.chapman@pionix.de>
|
||||
---
|
||||
include/openssl/ssl.h.in | 2 ++
|
||||
include/openssl/tls1.h | 7 +++++++
|
||||
ssl/s3_lib.c | 8 ++++++++
|
||||
ssl/statem/extensions_clnt.c | 3 ++-
|
||||
ssl/statem/extensions_srvr.c | 4 ++++
|
||||
ssl/statem/statem_clnt.c | 3 ++-
|
||||
6 files changed, 25 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in
|
||||
index 105b4a4a3c..b29f65fbfa 100644
|
||||
--- a/include/openssl/ssl.h.in
|
||||
+++ b/include/openssl/ssl.h.in
|
||||
@@ -1251,6 +1251,8 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
||||
# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_IDS 69
|
||||
# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP 70
|
||||
# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP 71
|
||||
+# define SSL_CTRL_GET_TLSEXT_STATUS_EXPECTED 270
|
||||
+# define SSL_CTRL_SET_TLSEXT_STATUS_EXPECTED 271
|
||||
# ifndef OPENSSL_NO_DEPRECATED_3_0
|
||||
# define SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB 72
|
||||
# endif
|
||||
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
|
||||
index d6e9331fa1..f0a8413703 100644
|
||||
--- a/include/openssl/tls1.h
|
||||
+++ b/include/openssl/tls1.h
|
||||
@@ -160,6 +160,7 @@ extern "C" {
|
||||
# define TLSEXT_NAMETYPE_host_name 0
|
||||
/* status request value from RFC3546 */
|
||||
# define TLSEXT_STATUSTYPE_ocsp 1
|
||||
+# define TLSEXT_STATUSTYPE_ocsp_multi 2
|
||||
|
||||
/* ECPointFormat values from RFC4492 */
|
||||
# define TLSEXT_ECPOINTFORMAT_first 0
|
||||
@@ -291,6 +292,12 @@ __owur int SSL_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain)
|
||||
# define SSL_set_tlsext_status_ocsp_resp(ssl, arg, arglen) \
|
||||
SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP,arglen,arg)
|
||||
|
||||
+# define SSL_get_tlsext_status_expected(ssl) \
|
||||
+ SSL_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_EXPECTED,0,NULL)
|
||||
+
|
||||
+# define SSL_set_tlsext_status_expected(ssl, arg) \
|
||||
+ SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_EXPECTED,arg,NULL)
|
||||
+
|
||||
# define SSL_CTX_set_tlsext_servername_callback(ctx, cb) \
|
||||
SSL_CTX_callback_ctrl(ctx,SSL_CTRL_SET_TLSEXT_SERVERNAME_CB,\
|
||||
(void (*)(void))cb)
|
||||
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
|
||||
index 78d4f04056..ede3a56f2f 100644
|
||||
--- a/ssl/s3_lib.c
|
||||
+++ b/ssl/s3_lib.c
|
||||
@@ -3556,6 +3556,14 @@ long ssl3_ctrl(SSL *s, int cmd, long larg, void *parg)
|
||||
ret = 1;
|
||||
break;
|
||||
|
||||
+ case SSL_CTRL_GET_TLSEXT_STATUS_EXPECTED:
|
||||
+ return (long)s->ext.status_expected;
|
||||
+
|
||||
+ case SSL_CTRL_SET_TLSEXT_STATUS_EXPECTED:
|
||||
+ s->ext.status_expected = larg;
|
||||
+ ret = 1;
|
||||
+ break;
|
||||
+
|
||||
case SSL_CTRL_CHAIN:
|
||||
if (larg)
|
||||
return ssl_cert_set1_chain(s, NULL, (STACK_OF(X509) *)parg);
|
||||
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c
|
||||
index 842be0722b..b9d5493e72 100644
|
||||
--- a/ssl/statem/extensions_clnt.c
|
||||
+++ b/ssl/statem/extensions_clnt.c
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <openssl/ocsp.h>
|
||||
+#include <openssl/tls1.h>
|
||||
#include "../ssl_local.h"
|
||||
#include "internal/cryptlib.h"
|
||||
#include "statem_local.h"
|
||||
@@ -1397,7 +1398,7 @@ int tls_parse_stoc_status_request(SSL *s, PACKET *pkt, unsigned int context,
|
||||
* MUST only be sent if we've requested a status
|
||||
* request message. In TLS <= 1.2 it must also be empty.
|
||||
*/
|
||||
- if (s->ext.status_type != TLSEXT_STATUSTYPE_ocsp) {
|
||||
+ if ((s->ext.status_type != TLSEXT_STATUSTYPE_ocsp) && (s->ext.status_type != TLSEXT_STATUSTYPE_ocsp_multi)) {
|
||||
SSLfatal(s, SSL_AD_UNSUPPORTED_EXTENSION, SSL_R_BAD_EXTENSION);
|
||||
return 0;
|
||||
}
|
||||
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
|
||||
index 16765a5a5b..7fb67937bf 100644
|
||||
--- a/ssl/statem/extensions_srvr.c
|
||||
+++ b/ssl/statem/extensions_srvr.c
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <openssl/ocsp.h>
|
||||
+#include <openssl/tls1.h>
|
||||
#include "../ssl_local.h"
|
||||
#include "statem_local.h"
|
||||
#include "internal/cryptlib.h"
|
||||
@@ -1421,6 +1422,9 @@ EXT_RETURN tls_construct_stoc_status_request(SSL *s, WPACKET *pkt,
|
||||
if (!s->ext.status_expected)
|
||||
return EXT_RETURN_NOT_SENT;
|
||||
|
||||
+ if (s->ext.status_type == TLSEXT_STATUSTYPE_ocsp_multi)
|
||||
+ return EXT_RETURN_NOT_SENT;
|
||||
+
|
||||
if (SSL_IS_TLS13(s) && chainidx != 0)
|
||||
return EXT_RETURN_NOT_SENT;
|
||||
|
||||
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
|
||||
index 3cd1ee2d3d..29a07bd413 100644
|
||||
--- a/ssl/statem/statem_clnt.c
|
||||
+++ b/ssl/statem/statem_clnt.c
|
||||
@@ -9,6 +9,7 @@
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
+#include <openssl/tls1.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
@@ -2636,7 +2637,7 @@ int tls_process_cert_status_body(SSL *s, PACKET *pkt)
|
||||
unsigned int type;
|
||||
|
||||
if (!PACKET_get_1(pkt, &type)
|
||||
- || type != TLSEXT_STATUSTYPE_ocsp) {
|
||||
+ || (type != TLSEXT_STATUSTYPE_ocsp) && (type != TLSEXT_STATUSTYPE_ocsp_multi)) {
|
||||
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_UNSUPPORTED_STATUS_TYPE);
|
||||
return 0;
|
||||
}
|
||||
--
|
||||
2.34.1
|
||||
|
||||
97
tools/EVerest-main/lib/everest/tls/openssl-patch.md
Normal file
97
tools/EVerest-main/lib/everest/tls/openssl-patch.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# OpenSSL 3.0.8 patch
|
||||
|
||||
The file `openssl-3.0.8-feat-updates-to-support-status_request_v2.patch` is a
|
||||
patch to OpenSSL 3.0.8 to support the `status_request_v2` TLS extension defined
|
||||
in [RFC 6961](https://datatracker.ietf.org/doc/html/rfc6961).
|
||||
|
||||
## Apply the patch
|
||||
|
||||
Assuming `openssl-3.0.8-feat-updates-to-support-status_request_v2.patch` is in
|
||||
the current directory:
|
||||
|
||||
```sh
|
||||
$ git clone --branch openssl-3.0.8 https://github.com/openssl/openssl.git
|
||||
$ cd openssl
|
||||
$ patch -p1 < ../openssl-3.0.8-feat-updates-to-support-status_request_v2.patch
|
||||
$ ./Configure
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
The patch can also be added to `SRC_URI` in a yocto bbappend file
|
||||
`openssl_3.0.8.bbappend`:
|
||||
|
||||
```bitbake
|
||||
SRC_URI:append = " file://openssl-3.0.8-feat-updates-to-support-status_request_v2.patch"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
The patch is designed to be a minimal change so that `status_request_v2` can be
|
||||
supported with the emphasis on TLS server support. TLS client support exists to
|
||||
facilitate testing.
|
||||
|
||||
`status_request_v2` is deprecated for TLS 1.3 and must not be used. The code
|
||||
ignores `status_request_v2` extensions when TLS 1.3 has been negotiated.
|
||||
|
||||
When a client requests `status_request` and `status_request_v2` then
|
||||
`status_request_v2` is used and `status_request` ignored.
|
||||
|
||||
## Implementation
|
||||
|
||||
`status_request_v2` is implemented in `tls.cpp` and relies on OCSP responses
|
||||
being available in separate files that are associated with the server
|
||||
certificate and chain.
|
||||
|
||||
The patch defines `TLSEXT_STATUSTYPE_ocsp_multi` which is used in `tls.cpp` to
|
||||
detect a patched version of OpenSSL.
|
||||
|
||||
### OpenSSL
|
||||
|
||||
OpenSSL contains a framework for adding handlers for TLS extensions that are not
|
||||
natively handled. `status_request` is supported and the same mechanism is used
|
||||
to to build the `status_request_v2` response.
|
||||
|
||||
Unfortunately both `status_request` and `status_request_v2` add an additional
|
||||
TLS handshake record `Certificate Status` containing the OCSP responses rather
|
||||
than including them as part of the extension. The OpenSSL extension framework
|
||||
doesn't provide a mechanism to add a `Certificate Status` record.
|
||||
|
||||
The solution is to reuse the support for `status_request` and provide the
|
||||
`status_request_v2` data for the `Certificate Status` record in application
|
||||
code.
|
||||
|
||||
The patch adds the additional status type `TLSEXT_STATUSTYPE_ocsp_multi` for use
|
||||
with `SSL_set_tlsext_status_type()` and updates checks on `ext.status_type` so
|
||||
that it isn't rejected.
|
||||
|
||||
Additional functions `SSL_get_tlsext_status_expected()` and
|
||||
`SSL_set_tlsext_status_expected()` are added so that application code can
|
||||
indicate to OpenSSL that the `Certificate Status` record needs to be added.
|
||||
|
||||
`SSL_set_tlsext_status_ocsp_resp()` is used by both `status_request` and
|
||||
`status_request_v2` to populate the response.
|
||||
|
||||
An early `Client Hello` handler is used to detect `status_request` and
|
||||
`status_request_v2` extensions so that the `status_request` handler can ignore
|
||||
the request (unless TLS 1.3 had been negotiated).
|
||||
|
||||
### OcspCache
|
||||
|
||||
Contains a digest method that produces a digest of a certificate. This digest
|
||||
is paired with the OCSP response filename which provides the association used
|
||||
in the OCSP cache.
|
||||
|
||||
When responding to a `status_request_v2` the server iterates through the server
|
||||
certificates and builds the response including the cached OCSP response for each
|
||||
certificate where available.
|
||||
|
||||
## Testing
|
||||
|
||||
The primary testing has been performed using `Wireshark` to ensure that the
|
||||
`Server Hello` and `Certificate Status` records are correctly formed.
|
||||
|
||||
There is a googletest test suite `patched_test` that checks operation via the
|
||||
OpenSSL APIs but it isn't able to check the handshake records directly.
|
||||
|
||||
There are a test TLS server and client that can be used to check operation.
|
||||
28
tools/EVerest-main/lib/everest/tls/src/openssl_conv.cpp
Normal file
28
tools/EVerest-main/lib/everest/tls/src/openssl_conv.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/tls/openssl_conv.hpp>
|
||||
|
||||
#include <evse_security/crypto/openssl/openssl_types.hpp>
|
||||
#include <memory>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
using evse_security::X509Handle_ptr;
|
||||
using evse_security::X509HandleOpenSSL;
|
||||
using evse_security::X509Wrapper;
|
||||
|
||||
namespace openssl::conversions {
|
||||
|
||||
X509Handle_ptr to_X509Handle_ptr(x509_st* cert) {
|
||||
X509Handle_ptr ptr;
|
||||
if (X509_up_ref(cert) == 1) {
|
||||
ptr = std::make_unique<X509HandleOpenSSL>(cert);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
X509Wrapper to_X509Wrapper(x509_st* cert) {
|
||||
return X509Wrapper(to_X509Handle_ptr(cert));
|
||||
}
|
||||
|
||||
} // namespace openssl::conversions
|
||||
855
tools/EVerest-main/lib/everest/tls/src/openssl_util.cpp
Normal file
855
tools/EVerest-main/lib/everest/tls/src/openssl_util.cpp
Normal file
@@ -0,0 +1,855 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
#include <evse_security/crypto/openssl/openssl_provider.hpp>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/store.h>
|
||||
#include <openssl/types.h>
|
||||
#include <openssl/ui.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
namespace {
|
||||
|
||||
openssl::log_handler_t s_log_handler{nullptr}; //!< logs are passed to this function
|
||||
|
||||
/**
|
||||
* \brief add OpenSSL error information to the string
|
||||
* \param[in] str is the OpenSSL \n\0 terminated error string
|
||||
* \param[in] len is the length of the string including the \n
|
||||
* \param[in] u is user data - a std::string to append to
|
||||
* \return 0 on success
|
||||
*/
|
||||
int add_error_str(const char* str, std::size_t len, void* u) {
|
||||
assert(u != nullptr);
|
||||
auto* list = reinterpret_cast<std::string*>(u);
|
||||
*list += '\n' + std::string(str, len - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief present a password for private key files
|
||||
* \param[in] buf is a pointer for where to place the password
|
||||
* \param[in] size is the size of buf i.e. max password size
|
||||
* \param[in] rwflag to indicate whether a file os being read or written
|
||||
* \param[in] u is user data which must be set to the required password
|
||||
* \return the length of the password written to buf or -1 on error
|
||||
* \note this callback is used to prevent attempts to prompt for a password
|
||||
* from a terminal
|
||||
*/
|
||||
int password_cb(char* buf, int size, int rwflag, void* u) {
|
||||
int result{-1};
|
||||
|
||||
if ((u != nullptr) && (buf != nullptr) && (size > 0)) {
|
||||
std::strncpy(buf, static_cast<const char*>(u), size);
|
||||
buf[size - 1] = '\0';
|
||||
result = static_cast<int>(std::strlen(buf));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace openssl {
|
||||
|
||||
void log(log_level_t level, const std::string& str) {
|
||||
std::string messages = {str};
|
||||
ERR_print_errors_cb(&add_error_str, &messages);
|
||||
if (s_log_handler == nullptr) {
|
||||
std::cerr << messages << std::endl;
|
||||
} else {
|
||||
s_log_handler(level, messages);
|
||||
}
|
||||
}
|
||||
|
||||
log_handler_t set_log_handler(log_handler_t handler) {
|
||||
const auto tmp = s_log_handler;
|
||||
s_log_handler = handler;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace openssl
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* \brief calculate a SHA digest
|
||||
* \param[in] data is the data to hash
|
||||
* \param[in] len is the length of the data to hash
|
||||
* \param[out] digest is the calculated digest
|
||||
* \param[in] HASH is the type of digest e.g. SHA1, SHA256 ...
|
||||
* \return true on success
|
||||
*/
|
||||
template <typename DIGEST> bool sha_impl(const void* data, std::size_t len, DIGEST& digest, const EVP_MD* HASH) {
|
||||
std::array<std::uint8_t, EVP_MAX_MD_SIZE> buffer{};
|
||||
unsigned int digestlen{0};
|
||||
const auto res = EVP_Digest(data, len, buffer.data(), &digestlen, HASH, nullptr);
|
||||
if (res == 1) {
|
||||
if (digestlen == digest.size()) {
|
||||
std::memcpy(digest.data(), buffer.data(), digest.size());
|
||||
} else {
|
||||
openssl::log_error("EVP_Digest - size");
|
||||
}
|
||||
} else {
|
||||
openssl::log_error("EVP_Digest");
|
||||
}
|
||||
return res == 1;
|
||||
}
|
||||
|
||||
template <typename DIGEST> bool sha(const void* data, std::size_t len, DIGEST& digest);
|
||||
|
||||
template <> bool sha(const void* data, std::size_t len, openssl::sha_1_digest_t& digest) {
|
||||
return sha_impl(data, len, digest, EVP_sha1());
|
||||
}
|
||||
template <> bool sha(const void* data, std::size_t len, openssl::sha_256_digest_t& digest) {
|
||||
return sha_impl(data, len, digest, EVP_sha256());
|
||||
}
|
||||
template <> bool sha(const void* data, std::size_t len, openssl::sha_384_digest_t& digest) {
|
||||
return sha_impl<openssl::sha_384_digest_t>(data, len, digest, EVP_sha384());
|
||||
}
|
||||
template <> bool sha(const void* data, std::size_t len, openssl::sha_512_digest_t& digest) {
|
||||
return sha_impl(data, len, digest, EVP_sha512());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace openssl {
|
||||
using evse_security::OpenSSLProvider;
|
||||
|
||||
DER::DER(std::size_t size) : DER(nullptr, size) {
|
||||
}
|
||||
|
||||
DER::DER(const der_underlying_t* src, std::size_t size) {
|
||||
auto* tmp = static_cast<std::uint8_t*>(OPENSSL_malloc(size));
|
||||
if (tmp != nullptr) {
|
||||
ptr = der_ptr{tmp, &DER::free};
|
||||
len = size;
|
||||
if (src != nullptr) {
|
||||
std::memcpy(ptr.get(), src, size);
|
||||
} else {
|
||||
std::memset(ptr.get(), 0, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DER::DER(const DER& obj) : DER(obj.ptr.get(), obj.len) {
|
||||
}
|
||||
|
||||
DER& DER::operator=(const DER& obj) {
|
||||
if (&obj != this) {
|
||||
*this = DER(obj);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
DER::DER(DER&& obj) noexcept : ptr(std::move(obj.ptr)), len(obj.len) {
|
||||
obj.len = 0;
|
||||
}
|
||||
|
||||
DER& DER::operator=(DER&& obj) noexcept {
|
||||
if (this != &obj) {
|
||||
ptr = std::move(obj.ptr);
|
||||
len = obj.len;
|
||||
obj.len = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool DER::operator==(const DER& rhs) const {
|
||||
if (&rhs == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result{false};
|
||||
const auto* lhs_p = ptr.get();
|
||||
const auto* rhs_p = rhs.ptr.get();
|
||||
|
||||
if ((lhs_p != nullptr) && (rhs_p != nullptr)) {
|
||||
result = len == rhs.len;
|
||||
result = result && (std::memcmp(lhs_p, rhs_p, len) == 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DER::operator==(const der_underlying_t* rhs) const {
|
||||
return ptr.get() == rhs;
|
||||
}
|
||||
|
||||
DER::operator bool() const {
|
||||
return (ptr.get() != nullptr) && (len > 0);
|
||||
}
|
||||
|
||||
der_underlying_t* DER::dup(const DER& obj) {
|
||||
auto* ptr = static_cast<std::uint8_t*>(OPENSSL_malloc(obj.len));
|
||||
if ((ptr != nullptr) && (obj.ptr != nullptr)) {
|
||||
std::memcpy(ptr, obj.ptr.get(), obj.len);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void DER::free(der_underlying_t* ptr) {
|
||||
OPENSSL_free(ptr);
|
||||
}
|
||||
|
||||
bool sign(EVP_PKEY* pkey, bn_t& r, bn_t& s, const sha_256_digest_t& digest) {
|
||||
bool bRes{false};
|
||||
std::array<std::uint8_t, signature_der_size> signature{};
|
||||
auto len = signature.size();
|
||||
bRes = sign(pkey, signature.data(), len, digest.data(), sha_256_digest_size);
|
||||
if (bRes) {
|
||||
bRes = signature_to_bn(r, s, signature.data(), len);
|
||||
}
|
||||
return bRes;
|
||||
}
|
||||
|
||||
bool sign(EVP_PKEY* pkey, unsigned char* sig, std::size_t& siglen, const unsigned char* tbs, std::size_t tbslen) {
|
||||
bool bRes{true};
|
||||
|
||||
auto* ctx = EVP_PKEY_CTX_new(pkey, nullptr);
|
||||
if (ctx == nullptr) {
|
||||
log_error("EVP_PKEY_CTX_new");
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes && (EVP_PKEY_sign_init(ctx) != 1)) {
|
||||
log_error("EVP_PKEY_sign_init");
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes && (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) != 1)) {
|
||||
log_error("EVP_PKEY_CTX_set_signature_md");
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes) {
|
||||
// calculate signature size
|
||||
std::size_t length{0};
|
||||
if (EVP_PKEY_sign(ctx, nullptr, &length, tbs, tbslen) != 1) {
|
||||
log_error("EVP_PKEY_sign - length");
|
||||
bRes = false;
|
||||
} else if (siglen < length) {
|
||||
log_error("EVP_PKEY_sign - length too small: " + std::to_string(length));
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes) {
|
||||
const auto res = EVP_PKEY_sign(ctx, sig, &siglen, tbs, tbslen);
|
||||
if (res != 1) {
|
||||
log_error("EVP_PKEY_sign" + std::to_string(res));
|
||||
bRes = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
return bRes;
|
||||
}
|
||||
|
||||
bool verify(EVP_PKEY* pkey, const bn_t& r, const bn_t& s, const sha_256_digest_t& digest) {
|
||||
return verify(pkey, r.data(), s.data(), digest);
|
||||
}
|
||||
|
||||
bool verify(EVP_PKEY* pkey, const std::uint8_t* r, const std::uint8_t* s, const sha_256_digest_t& digest) {
|
||||
bool bRes{false};
|
||||
auto signature = bn_to_signature(r, s);
|
||||
if (signature) {
|
||||
bRes = verify(pkey, signature.get(), signature.size(), digest.data(), sha_256_digest_size);
|
||||
}
|
||||
return bRes;
|
||||
}
|
||||
|
||||
bool verify(EVP_PKEY* pkey, const unsigned char* sig, std::size_t siglen, const unsigned char* tbs,
|
||||
std::size_t tbslen) {
|
||||
bool bRes{true};
|
||||
|
||||
auto* ctx = EVP_PKEY_CTX_new(pkey, nullptr);
|
||||
if (ctx == nullptr) {
|
||||
log_error("EVP_PKEY_CTX_new");
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes && (EVP_PKEY_verify_init(ctx) != 1)) {
|
||||
log_error("EVP_PKEY_verify_init");
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes && (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) != 1)) {
|
||||
log_error("EVP_PKEY_CTX_set_signature_md");
|
||||
bRes = false;
|
||||
}
|
||||
if (bRes) {
|
||||
const auto res = EVP_PKEY_verify(ctx, sig, siglen, tbs, tbslen);
|
||||
if (res != 1) {
|
||||
log_error("EVP_PKEY_verify: " + std::to_string(res));
|
||||
bRes = false;
|
||||
}
|
||||
}
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
return bRes;
|
||||
}
|
||||
|
||||
bool sha_1(const void* data, std::size_t len, sha_1_digest_t& digest) {
|
||||
return sha(data, len, digest);
|
||||
}
|
||||
|
||||
bool sha_256(const void* data, std::size_t len, sha_256_digest_t& digest) {
|
||||
return sha(data, len, digest);
|
||||
}
|
||||
|
||||
bool sha_384(const void* data, std::size_t len, sha_384_digest_t& digest) {
|
||||
return sha(data, len, digest);
|
||||
}
|
||||
|
||||
bool sha_512(const void* data, std::size_t len, sha_512_digest_t& digest) {
|
||||
return sha(data, len, digest);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> base64_decode(const char* text, std::size_t len) {
|
||||
// Strip whitespace; pass everything else to BIO_f_base64. Byte set matches
|
||||
// EVP_Decode*'s B64_WS table so this stays drop-in equivalent to the
|
||||
// evse_security path (consolidation target). NUL terminates the scan: an
|
||||
// embedded NUL almost certainly indicates a length-parameter error and
|
||||
// continuing would feed garbage to the decoder.
|
||||
auto input = std::make_unique<std::uint8_t[]>(len);
|
||||
std::size_t input_len{0};
|
||||
|
||||
for (std::size_t i = 0; i < len; i++) {
|
||||
const auto item = text[i];
|
||||
if (item == '\0') {
|
||||
break;
|
||||
}
|
||||
switch (item) {
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\v':
|
||||
case '\f':
|
||||
case '\r':
|
||||
case ' ':
|
||||
continue;
|
||||
default:
|
||||
input.get()[input_len++] = static_cast<std::uint8_t>(item);
|
||||
}
|
||||
}
|
||||
|
||||
auto* b64 = BIO_new(BIO_f_base64());
|
||||
auto* mem = BIO_new_mem_buf(input.get(), static_cast<int>(input_len));
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO_push(b64, mem);
|
||||
|
||||
std::size_t output_len{0};
|
||||
int read_len{0};
|
||||
std::array<char, 256> buffer{};
|
||||
std::vector<std::uint8_t> result(len);
|
||||
|
||||
while ((read_len = BIO_read(b64, buffer.data(), buffer.size())) > 0) {
|
||||
if ((output_len + read_len) <= result.size()) {
|
||||
std::memcpy(&result[output_len], buffer.data(), read_len);
|
||||
output_len += static_cast<std::size_t>(read_len);
|
||||
} else {
|
||||
// decoded data is larger than the input - can't happen!
|
||||
output_len = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.resize(output_len);
|
||||
BIO_free_all(b64);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool base64_decode(const char* text, std::size_t len, std::uint8_t* out_data, std::size_t& out_len) {
|
||||
assert(out_data != nullptr);
|
||||
|
||||
bool bResult = false;
|
||||
auto res = base64_decode(text, len);
|
||||
if ((res.size() > 0) && (res.size() <= out_len)) {
|
||||
std::memcpy(out_data, res.data(), res.size());
|
||||
out_len = res.size();
|
||||
bResult = true;
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
std::string base64_encode(const std::uint8_t* data, std::size_t len, bool newLine) {
|
||||
auto* b64 = BIO_new(BIO_f_base64());
|
||||
auto* mem = BIO_new(BIO_s_mem());
|
||||
BIO_push(b64, mem);
|
||||
if (!newLine) {
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
}
|
||||
BIO_write(b64, data, static_cast<int>(len));
|
||||
BIO_flush(b64);
|
||||
|
||||
char* ptr{nullptr};
|
||||
const auto size = BIO_get_mem_data(mem, &ptr);
|
||||
|
||||
std::string result(ptr, size);
|
||||
BIO_free_all(b64);
|
||||
return result;
|
||||
}
|
||||
|
||||
pkey_ptr load_private_key(const char* filename, const char* password) {
|
||||
/*
|
||||
* should read the file into memory to check the key type so the correct
|
||||
* provider can be selected. For simplicity reuse existing function
|
||||
* that causes key file to be opened an additional time
|
||||
*/
|
||||
|
||||
{
|
||||
OpenSSLProvider provider; // ensure providers are loaded
|
||||
// minimise holding the mutex
|
||||
}
|
||||
|
||||
pkey_ptr private_key{nullptr, nullptr};
|
||||
auto* bio = BIO_new_file(filename, "r");
|
||||
if (bio != nullptr) {
|
||||
// password is passed to password_cb() as parameter u which is never
|
||||
// written to, hence const_cast is okay
|
||||
auto* pkey = PEM_read_bio_PrivateKey(bio, nullptr, &password_cb, const_cast<char*>(password));
|
||||
if (pkey != nullptr) {
|
||||
private_key = pkey_ptr{pkey, &EVP_PKEY_free};
|
||||
}
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
return private_key;
|
||||
}
|
||||
|
||||
DER bn_to_signature(const bn_t& r, const bn_t& s) {
|
||||
return bn_to_signature(r.data(), s.data());
|
||||
};
|
||||
|
||||
DER bn_to_signature(const std::uint8_t* r, const std::uint8_t* s) {
|
||||
std::uint8_t* sig_p{nullptr};
|
||||
std::size_t signature_len{0};
|
||||
BIGNUM* rbn{nullptr};
|
||||
BIGNUM* sbn{nullptr};
|
||||
|
||||
auto* signature = ECDSA_SIG_new();
|
||||
if (signature == nullptr) {
|
||||
log_error("ECDSA_SIG_new");
|
||||
} else {
|
||||
rbn = BN_bin2bn(r, signature_n_size, nullptr);
|
||||
sbn = BN_bin2bn(s, signature_n_size, nullptr);
|
||||
}
|
||||
|
||||
if (rbn != nullptr && sbn != nullptr) {
|
||||
if (ECDSA_SIG_set0(signature, rbn, sbn) == 1) {
|
||||
/* Set these to NULL since they are now owned by obj */
|
||||
rbn = sbn = nullptr;
|
||||
signature_len = i2d_ECDSA_SIG(signature, &sig_p);
|
||||
if (signature_len == 0) {
|
||||
log_error("i2d_ECDSA_SIG");
|
||||
}
|
||||
} else {
|
||||
log_error("ECDSA_SIG_set0");
|
||||
}
|
||||
}
|
||||
|
||||
BN_free(rbn);
|
||||
BN_free(sbn);
|
||||
ECDSA_SIG_free(signature);
|
||||
// move sig_p to DER
|
||||
return {der_ptr{sig_p, &DER::free}, signature_len};
|
||||
};
|
||||
|
||||
bool signature_to_bn(bn_t& r, bn_t& s, const std::uint8_t* sig_p, std::size_t len) {
|
||||
bool bRes{false};
|
||||
|
||||
auto* signature = d2i_ECDSA_SIG(nullptr, &sig_p, static_cast<long>(len));
|
||||
if (signature == nullptr) {
|
||||
log_error("d2i_ECDSA_SIG");
|
||||
} else {
|
||||
const auto* rbn = ECDSA_SIG_get0_r(signature);
|
||||
const auto* sbn = ECDSA_SIG_get0_s(signature);
|
||||
|
||||
bRes = BN_bn2binpad(rbn, r.data(), static_cast<int>(r.size())) != -1;
|
||||
bRes = bRes && BN_bn2binpad(sbn, s.data(), static_cast<int>(s.size())) != -1;
|
||||
if (!bRes) {
|
||||
log_error("BN_bn2binpad");
|
||||
}
|
||||
}
|
||||
|
||||
ECDSA_SIG_free(signature);
|
||||
return bRes;
|
||||
};
|
||||
|
||||
certificate_list load_certificates_pem(const char* pem_string) {
|
||||
certificate_list result{};
|
||||
if (pem_string != nullptr) {
|
||||
const auto len = std::strlen(pem_string);
|
||||
auto* mem = BIO_new_mem_buf(pem_string, static_cast<int>(len));
|
||||
X509* cert = nullptr;
|
||||
|
||||
while (!BIO_eof(mem)) {
|
||||
if (PEM_read_bio_X509(mem, &cert, nullptr, nullptr) == nullptr) {
|
||||
log_error("PEM_read_bio_X509");
|
||||
break;
|
||||
} else {
|
||||
result.emplace_back(certificate_ptr{cert, &X509_free});
|
||||
cert = nullptr;
|
||||
}
|
||||
}
|
||||
BIO_free(mem);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
certificate_list load_certificates(const char* filename) {
|
||||
certificate_list result{};
|
||||
if (filename != nullptr) {
|
||||
auto* store = OSSL_STORE_open(filename, UI_null(), nullptr, nullptr, nullptr);
|
||||
if (store != nullptr) {
|
||||
while (OSSL_STORE_eof(store) != 1) {
|
||||
auto* info = OSSL_STORE_load(store);
|
||||
|
||||
if (info != nullptr) {
|
||||
if (OSSL_STORE_error(store) == 1) {
|
||||
log_error("OSSL_STORE_load");
|
||||
} else {
|
||||
const auto type = OSSL_STORE_INFO_get_type(info);
|
||||
|
||||
if (type == OSSL_STORE_INFO_CERT) {
|
||||
// get a copy of the certificate
|
||||
auto cert = OSSL_STORE_INFO_get1_CERT(info);
|
||||
result.push_back({cert, &X509_free});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OSSL_STORE_INFO_free(info);
|
||||
}
|
||||
}
|
||||
|
||||
OSSL_STORE_close(store);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
certificate_list load_certificates(const std::vector<const char*>& filenames) {
|
||||
certificate_list result{};
|
||||
for (const auto* i : filenames) {
|
||||
auto tmp = load_certificates(i);
|
||||
std::move(tmp.begin(), tmp.end(), std::back_inserter(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
chain_info_t load_certificates(const char* leaf_file, const char* chain_file, const char* root_file) {
|
||||
certificate_ptr leaf_cert{nullptr, nullptr};
|
||||
auto leaf = load_certificates(leaf_file);
|
||||
auto chain = load_certificates(chain_file);
|
||||
auto root = load_certificates(root_file);
|
||||
|
||||
if (leaf.empty()) {
|
||||
if (!chain.empty()) {
|
||||
leaf_cert.swap(chain[0]);
|
||||
chain.erase(chain.begin());
|
||||
}
|
||||
} else {
|
||||
leaf_cert.swap(leaf[0]);
|
||||
}
|
||||
|
||||
if (leaf_cert && !root.empty()) {
|
||||
if (verify_certificate(leaf_cert.get(), root, chain) == verify_result_t::Verified) {
|
||||
return {std::move(leaf_cert), std::move(chain), std::move(root)};
|
||||
}
|
||||
}
|
||||
|
||||
return {{nullptr, nullptr}, {}, {}};
|
||||
}
|
||||
|
||||
chain_info_list_t load_certificates(const chain_filenames_list_t& chains) {
|
||||
chain_info_list_t result;
|
||||
result.reserve(chains.size());
|
||||
for (const auto& chain : chains) {
|
||||
result.emplace_back(load_certificates(chain));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool verify_certificate_key(const X509* cert, const EVP_PKEY* pkey) {
|
||||
return X509_check_private_key(cert, pkey) == 1;
|
||||
}
|
||||
|
||||
bool verify_chain(const chain_info_t& chain) {
|
||||
return verify_certificate(chain.leaf.get(), chain.trust_anchors, chain.chain) == verify_result_t::Verified;
|
||||
}
|
||||
|
||||
bool verify_chain(const chain_t& chain) {
|
||||
bool result = verify_chain(chain.chain);
|
||||
result = result && verify_certificate_key(chain.chain.leaf.get(), chain.private_key.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
bool use_certificate_and_key(SSL* ssl, const chain_t& chain) {
|
||||
assert(ssl != nullptr);
|
||||
|
||||
bool result{false};
|
||||
SSL_certs_clear(ssl);
|
||||
auto* untrusted = sk_X509_new_null();
|
||||
if (untrusted == nullptr) {
|
||||
log_error("sk_X509_new_null");
|
||||
} else {
|
||||
for (const auto& cert : chain.chain.chain) {
|
||||
if (X509_add_cert(untrusted, cert.get(),
|
||||
X509_ADD_FLAG_UP_REF | X509_ADD_FLAG_NO_DUP | X509_ADD_FLAG_NO_SS) != 1) {
|
||||
log_error("X509_add_cert");
|
||||
}
|
||||
}
|
||||
result = SSL_use_cert_and_key(ssl, chain.chain.leaf.get(), chain.private_key.get(), untrusted, 1) == 1;
|
||||
if (!result) {
|
||||
log_error("SSL_use_cert_and_key");
|
||||
}
|
||||
sk_X509_pop_free(untrusted, X509_free);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string certificate_to_pem(const X509* cert) {
|
||||
assert(cert != nullptr);
|
||||
|
||||
auto* mem = BIO_new(BIO_s_mem());
|
||||
std::string result;
|
||||
if (PEM_write_bio_X509(mem, cert) != 1) {
|
||||
log_error("PEM_write_bio_X509");
|
||||
} else {
|
||||
BIO_flush(mem);
|
||||
|
||||
char* ptr{nullptr};
|
||||
const auto size = BIO_get_mem_data(mem, &ptr);
|
||||
|
||||
result = std::string(ptr, size);
|
||||
}
|
||||
BIO_free(mem);
|
||||
return result;
|
||||
}
|
||||
|
||||
certificate_ptr pem_to_certificate(const std::string& pem) {
|
||||
certificate_ptr result{nullptr, nullptr};
|
||||
auto* mem = BIO_new_mem_buf(pem.c_str(), static_cast<int>(pem.size()));
|
||||
X509* cert = nullptr;
|
||||
|
||||
if (PEM_read_bio_X509(mem, &cert, nullptr, nullptr) == nullptr) {
|
||||
log_error("PEM_read_bio_X509");
|
||||
} else {
|
||||
result = certificate_ptr{cert, &X509_free};
|
||||
}
|
||||
BIO_free(mem);
|
||||
return result;
|
||||
}
|
||||
|
||||
certificate_ptr der_to_certificate(const std::uint8_t* der, std::size_t len) {
|
||||
certificate_ptr result{nullptr, nullptr};
|
||||
const auto* ptr = der;
|
||||
auto* cert = d2i_X509(nullptr, &ptr, static_cast<std::int64_t>(len));
|
||||
if (cert == nullptr) {
|
||||
log_error("d2i_X509");
|
||||
} else {
|
||||
result = certificate_ptr{cert, &X509_free};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DER certificate_to_der(const x509_st* cert) {
|
||||
assert(cert != nullptr);
|
||||
|
||||
unsigned char* data{nullptr};
|
||||
|
||||
// DO NOT FREE - internal pointers to certificate
|
||||
int len = i2d_X509(cert, &data);
|
||||
|
||||
// move data to DER
|
||||
return {der_ptr{data, &DER::free}, static_cast<std::size_t>(len)};
|
||||
}
|
||||
|
||||
verify_result_t verify_certificate(const X509* cert, const certificate_list& trust_anchors,
|
||||
const certificate_list& untrusted) {
|
||||
verify_result_t result = verify_result_t::Verified;
|
||||
auto* store_ctx = X509_STORE_CTX_new();
|
||||
auto* ta_store = X509_STORE_new();
|
||||
auto* chain = sk_X509_new_null();
|
||||
X509* target{nullptr};
|
||||
|
||||
if (trust_anchors.empty()) {
|
||||
log_error("No trust anchors provided");
|
||||
return verify_result_t::NoCertificateAvailable;
|
||||
}
|
||||
if (store_ctx == nullptr) {
|
||||
log_error("X509_STORE_CTX_new");
|
||||
result = verify_result_t::OtherError;
|
||||
}
|
||||
|
||||
if (ta_store == nullptr) {
|
||||
log_error("X509_STORE_new");
|
||||
result = verify_result_t::OtherError;
|
||||
}
|
||||
|
||||
if (chain == nullptr) {
|
||||
log_error("sk_X509_new_null");
|
||||
result = verify_result_t::OtherError;
|
||||
}
|
||||
|
||||
if (cert != nullptr) {
|
||||
target = X509_dup(cert);
|
||||
if (target == nullptr) {
|
||||
log_error("X509_dup");
|
||||
result = verify_result_t::OtherError;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == verify_result_t::Verified) {
|
||||
result = verify_result_t::OtherError;
|
||||
|
||||
for (const auto& i : trust_anchors) {
|
||||
if (X509_STORE_add_cert(ta_store, i.get()) != 1) {
|
||||
log_error("X509_STORE_add_cert");
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& j : untrusted) {
|
||||
if (X509_add_cert(chain, j.get(), X509_ADD_FLAG_UP_REF | X509_ADD_FLAG_NO_DUP | X509_ADD_FLAG_NO_SS) != 1) {
|
||||
log_error("X509_add_cert");
|
||||
}
|
||||
}
|
||||
|
||||
if (X509_STORE_CTX_init(store_ctx, ta_store, target, chain) != 1) {
|
||||
log_error("X509_STORE_CTX_init");
|
||||
} else {
|
||||
if (X509_STORE_CTX_verify(store_ctx) != 1) {
|
||||
const auto err = X509_STORE_CTX_get_error(store_ctx);
|
||||
if (err != X509_V_OK) {
|
||||
log_error("X509_STORE_CTX_verify (" + std::to_string(X509_STORE_CTX_get_error_depth(store_ctx)) +
|
||||
") " + X509_verify_cert_error_string(err));
|
||||
}
|
||||
|
||||
switch (err) {
|
||||
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
|
||||
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
|
||||
case X509_V_ERR_CERT_UNTRUSTED:
|
||||
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
|
||||
case X509_V_ERR_UNSPECIFIED:
|
||||
result = verify_result_t::CertChainError;
|
||||
break;
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
result = verify_result_t::CertificateExpired;
|
||||
break;
|
||||
case X509_V_ERR_CERT_REVOKED:
|
||||
result = verify_result_t::CertificateRevoked;
|
||||
break;
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
result = verify_result_t::CertificateNotAllowed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
result = verify_result_t::Verified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
X509_STORE_CTX_free(store_ctx);
|
||||
X509_STORE_free(ta_store);
|
||||
sk_X509_pop_free(chain, X509_free);
|
||||
X509_free(target);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> certificate_subject(const X509* cert) {
|
||||
assert(cert != nullptr);
|
||||
std::map<std::string, std::string> result;
|
||||
|
||||
// DO NOT FREE - internal pointers to certificate
|
||||
const auto* subject = X509_get_subject_name(cert);
|
||||
if (subject != nullptr) {
|
||||
for (int i = 0; i < X509_NAME_entry_count(subject); i++) {
|
||||
const auto* name_entry = X509_NAME_get_entry(subject, i);
|
||||
if (name_entry != nullptr) {
|
||||
const auto* object = X509_NAME_ENTRY_get_object(name_entry);
|
||||
const auto* data = X509_NAME_ENTRY_get_data(name_entry);
|
||||
if ((object != nullptr) && (data != nullptr)) {
|
||||
std::string name(OBJ_nid2sn(OBJ_obj2nid(object)));
|
||||
std::string value(reinterpret_cast<const char*>(ASN1_STRING_get0_data(data)),
|
||||
ASN1_STRING_length(data));
|
||||
result[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DER certificate_subject_der(const X509* cert) {
|
||||
assert(cert != nullptr);
|
||||
|
||||
int len{0};
|
||||
unsigned char* data{nullptr};
|
||||
|
||||
// DO NOT FREE - internal pointers to certificate
|
||||
const auto* subject = X509_get_subject_name(cert);
|
||||
if (subject != nullptr) {
|
||||
len = i2d_X509_NAME(subject, &data);
|
||||
}
|
||||
|
||||
// move data to DER
|
||||
return {der_ptr{data, &DER::free}, static_cast<std::size_t>(len)};
|
||||
}
|
||||
|
||||
pkey_ptr certificate_public_key(X509* cert) {
|
||||
pkey_ptr result{nullptr, nullptr};
|
||||
auto* pkey = X509_get_pubkey(cert);
|
||||
if (pkey == nullptr) {
|
||||
log_error("X509_get_pubkey");
|
||||
} else {
|
||||
result = pkey_ptr(pkey, &EVP_PKEY_free);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool certificate_sha_1(openssl::sha_1_digest_t& digest, const X509* cert) {
|
||||
assert(cert != nullptr);
|
||||
|
||||
bool bResult{false};
|
||||
auto der = certificate_to_der(cert);
|
||||
if (der) {
|
||||
bResult = openssl::sha_1(der.get(), der.size(), digest);
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool certificate_subject_public_key_sha_1(openssl::sha_1_digest_t& digest, const X509* cert) {
|
||||
assert(cert != nullptr);
|
||||
|
||||
bool bResult{false};
|
||||
const auto* pubkey = X509_get_X509_PUBKEY(cert);
|
||||
if (pubkey != nullptr) {
|
||||
unsigned char* data{nullptr};
|
||||
const auto len = i2d_X509_PUBKEY(pubkey, &data);
|
||||
if (len > 0) {
|
||||
bResult = openssl::sha_1(data, len, digest);
|
||||
}
|
||||
OPENSSL_free(data);
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
} // namespace openssl
|
||||
1557
tools/EVerest-main/lib/everest/tls/src/tls.cpp
Normal file
1557
tools/EVerest-main/lib/everest/tls/src/tls.cpp
Normal file
File diff suppressed because it is too large
Load Diff
161
tools/EVerest-main/lib/everest/tls/tests/CMakeLists.txt
Normal file
161
tools/EVerest-main/lib/everest/tls/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,161 @@
|
||||
find_package(OpenSSL 3)
|
||||
|
||||
set(TLS_TEST_FILES
|
||||
alt_openssl-pki.conf
|
||||
iso_pkey.asn1
|
||||
openssl-pki.conf
|
||||
ocsp_response.der
|
||||
pki.sh
|
||||
pki-tpm.sh
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${TLS_TEST_FILES}
|
||||
COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/pki
|
||||
COMMAND cd pki && cp ${TLS_TEST_FILES} ${CMAKE_CURRENT_BINARY_DIR}/
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_custom_target(tls_test_files_target
|
||||
DEPENDS ${TLS_TEST_FILES}
|
||||
)
|
||||
|
||||
set(TLS_GTEST_NAME tls_test)
|
||||
add_executable(${TLS_GTEST_NAME})
|
||||
add_dependencies(${TLS_GTEST_NAME} tls_test_files_target)
|
||||
|
||||
target_include_directories(${TLS_GTEST_NAME} PRIVATE
|
||||
..
|
||||
../include
|
||||
)
|
||||
|
||||
target_compile_definitions(${TLS_GTEST_NAME} PRIVATE
|
||||
-DUNIT_TEST
|
||||
-DLIBEVSE_CRYPTO_SUPPLIER_OPENSSL
|
||||
)
|
||||
|
||||
target_sources(${TLS_GTEST_NAME} PRIVATE
|
||||
gtest_main.cpp
|
||||
crypto_test.cpp
|
||||
openssl_util_test.cpp
|
||||
tls_test.cpp
|
||||
tls_connection_test.cpp
|
||||
../extensions/helpers.cpp
|
||||
../extensions/status_request.cpp
|
||||
../extensions/trusted_ca_keys.cpp
|
||||
../src/openssl_conv.cpp
|
||||
../src/openssl_util.cpp
|
||||
../src/tls.cpp
|
||||
)
|
||||
|
||||
if(USING_TPM2)
|
||||
target_sources(${TLS_GTEST_NAME} PRIVATE
|
||||
tls_connection_test_tpm.cpp
|
||||
)
|
||||
target_compile_definitions(${TLS_GTEST_NAME} PRIVATE
|
||||
USING_TPM2
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TLS_GTEST_NAME}
|
||||
PRIVATE
|
||||
GTest::gtest
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
everest::evse_security
|
||||
everest::util
|
||||
)
|
||||
|
||||
set(TLS_MAIN_NAME tls_server)
|
||||
add_executable(${TLS_MAIN_NAME})
|
||||
add_dependencies(${TLS_MAIN_NAME} tls_test_files_target)
|
||||
|
||||
target_include_directories(${TLS_MAIN_NAME} PRIVATE
|
||||
..
|
||||
../include
|
||||
)
|
||||
|
||||
target_compile_definitions(${TLS_MAIN_NAME} PRIVATE
|
||||
-DUNIT_TEST
|
||||
)
|
||||
|
||||
target_sources(${TLS_MAIN_NAME} PRIVATE
|
||||
tls_main.cpp
|
||||
../extensions/helpers.cpp
|
||||
../extensions/status_request.cpp
|
||||
../extensions/trusted_ca_keys.cpp
|
||||
../src/openssl_util.cpp
|
||||
../src/tls.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${TLS_MAIN_NAME}
|
||||
PRIVATE
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
everest::evse_security
|
||||
everest::util
|
||||
)
|
||||
|
||||
set(TLS_CLIENT_NAME tls_client)
|
||||
add_executable(${TLS_CLIENT_NAME})
|
||||
add_dependencies(${TLS_CLIENT_NAME} tls_test_files_target)
|
||||
|
||||
target_include_directories(${TLS_CLIENT_NAME} PRIVATE
|
||||
..
|
||||
../include
|
||||
)
|
||||
|
||||
target_compile_definitions(${TLS_CLIENT_NAME} PRIVATE
|
||||
-DUNIT_TEST
|
||||
)
|
||||
|
||||
target_sources(${TLS_CLIENT_NAME} PRIVATE
|
||||
tls_client_main.cpp
|
||||
../extensions/helpers.cpp
|
||||
../extensions/status_request.cpp
|
||||
../extensions/trusted_ca_keys.cpp
|
||||
../src/openssl_util.cpp
|
||||
../src/tls.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${TLS_CLIENT_NAME}
|
||||
PRIVATE
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
everest::evse_security
|
||||
everest::util
|
||||
)
|
||||
|
||||
set(TLS_PATCH_NAME patched_test)
|
||||
add_executable(${TLS_PATCH_NAME})
|
||||
add_dependencies(${TLS_PATCH_NAME} tls_test_files_target)
|
||||
|
||||
target_include_directories(${TLS_PATCH_NAME} PRIVATE
|
||||
..
|
||||
../include
|
||||
)
|
||||
|
||||
target_compile_definitions(${TLS_PATCH_NAME} PRIVATE
|
||||
-DUNIT_TEST
|
||||
)
|
||||
|
||||
target_sources(${TLS_PATCH_NAME} PRIVATE
|
||||
patched_test.cpp
|
||||
../extensions/helpers.cpp
|
||||
../extensions/status_request.cpp
|
||||
../extensions/trusted_ca_keys.cpp
|
||||
../src/openssl_util.cpp
|
||||
../src/tls.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${TLS_PATCH_NAME}
|
||||
PRIVATE
|
||||
GTest::gtest_main
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
everest::evse_security
|
||||
everest::util
|
||||
)
|
||||
|
||||
add_test(${TLS_GTEST_NAME} ${TLS_GTEST_NAME})
|
||||
ev_register_test_target(${TLS_GTEST_NAME})
|
||||
42
tools/EVerest-main/lib/everest/tls/tests/README.md
Normal file
42
tools/EVerest-main/lib/everest/tls/tests/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Tests
|
||||
|
||||
Building tests:
|
||||
|
||||
```sh
|
||||
$ cd EVerest
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake -GNinja -DEVEREST_CORE_BUILD_TESTING=ON ..
|
||||
$ ninja install
|
||||
```
|
||||
|
||||
`touch release.json` may be needed if it hasn't been created
|
||||
(then re-run `ninja install`).
|
||||
|
||||
## Unit tests
|
||||
|
||||
- `./tls_test` and `./patched_test`
|
||||
- automatically runs `pki.sh`
|
||||
- run from the directory containing the executable
|
||||
|
||||
## Standalone server
|
||||
|
||||
- Run `pki.sh` to build the test certificates and keys
|
||||
- use openssl_s_client to make test connections
|
||||
- run from the directory containing the executable
|
||||
|
||||
### Standalone TLS server
|
||||
|
||||
Tests the Server class in isolation.
|
||||
|
||||
- `./tls_server`
|
||||
- connects to IPv4 and IPv6
|
||||
- only one connection at a time
|
||||
- gracefully terminates after 30 seconds
|
||||
- `valgrind` can be used to check memory allocations (should be none)
|
||||
- requires client certificate and supports `status_request` extension
|
||||
- s_client echos back what is typed
|
||||
|
||||
```sh
|
||||
openssl s_client -connect localhost:8444 -verify 2 -CAfile server_root_cert.pem -cert client_cert.pem -cert_chain client_chain.pem -key client_priv.pem -verify_return_error -verify_hostname evse.pionix.de -status
|
||||
```
|
||||
41
tools/EVerest-main/lib/everest/tls/tests/crypto_test.cpp
Normal file
41
tools/EVerest-main/lib/everest/tls/tests/crypto_test.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <everest/tls/openssl_conv.hpp>
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
#include <evse_security/certificate/x509_hierarchy.hpp>
|
||||
#include <evse_security/certificate/x509_wrapper.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
using evse_security::HashAlgorithm;
|
||||
using evse_security::X509Wrapper;
|
||||
using openssl::load_certificates;
|
||||
using openssl::conversions::to_X509Wrapper;
|
||||
|
||||
TEST(evseSecurity, certificateHash) {
|
||||
auto chain = load_certificates("client_chain.pem");
|
||||
ASSERT_GT(chain.size(), 0);
|
||||
|
||||
std::vector<X509Wrapper> certs;
|
||||
|
||||
for (const auto& cert : chain) {
|
||||
certs.push_back(to_X509Wrapper(cert.get()));
|
||||
}
|
||||
|
||||
for (std::uint8_t i = 0; i < certs.size() - 1; i++) {
|
||||
SCOPED_TRACE("i=" + std::to_string(i));
|
||||
const auto& cert = certs[i];
|
||||
const auto& issuer = certs[i + 1];
|
||||
const auto resA = cert.get_certificate_hash_data(issuer);
|
||||
EXPECT_EQ(resA.hash_algorithm, HashAlgorithm::SHA256);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
60
tools/EVerest-main/lib/everest/tls/tests/gtest_main.cpp
Normal file
60
tools/EVerest-main/lib/everest/tls/tests/gtest_main.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
void log_handler(openssl::log_level_t level, const std::string& str) {
|
||||
switch (level) {
|
||||
case openssl::log_level_t::debug:
|
||||
// std::cout << "DEBUG: " << str << std::endl;
|
||||
break;
|
||||
case openssl::log_level_t::info:
|
||||
std::cout << "INFO: " << str << std::endl;
|
||||
break;
|
||||
case openssl::log_level_t::warning:
|
||||
std::cout << "WARN: " << str << std::endl;
|
||||
break;
|
||||
case openssl::log_level_t::error:
|
||||
std::cerr << "ERROR: " << str << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unknown: " << str << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// create test certificates and keys
|
||||
openssl::set_log_handler(log_handler);
|
||||
if (std::system("./pki.sh") != 0) {
|
||||
std::cerr << "Problem creating test certificates and keys" << std::endl;
|
||||
char buf[PATH_MAX];
|
||||
if (getcwd(&buf[0], sizeof(buf)) != nullptr) {
|
||||
std::cerr << "./pki.sh not found in " << buf << std::endl;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#ifdef USING_TPM2
|
||||
if (std::system("./pki-tpm.sh") != 0) {
|
||||
std::cerr << "Problem creating TPM test certificates and keys" << std::endl;
|
||||
char buf[PATH_MAX];
|
||||
if (getcwd(&buf[0], sizeof(buf)) != nullptr) {
|
||||
std::cerr << "./pki-tpm.sh not found in " << buf << std::endl;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
817
tools/EVerest-main/lib/everest/tls/tests/openssl_util_test.cpp
Normal file
817
tools/EVerest-main/lib/everest/tls/tests/openssl_util_test.cpp
Normal file
@@ -0,0 +1,817 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <vector>
|
||||
|
||||
#include <evse_security/crypto/openssl/openssl_provider.hpp>
|
||||
|
||||
bool operator==(const ::openssl::certificate_ptr& lhs, const ::openssl::certificate_ptr& rhs) {
|
||||
using ::openssl::certificate_to_pem;
|
||||
if (lhs && rhs) {
|
||||
const auto res_lhs = certificate_to_pem(lhs.get());
|
||||
const auto res_rhs = certificate_to_pem(rhs.get());
|
||||
return res_lhs == res_rhs;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T> constexpr void setCharacters(T& dest, const std::string& s) {
|
||||
dest.charactersLen = s.size();
|
||||
std::memcpy(&dest.characters[0], s.c_str(), s.size());
|
||||
}
|
||||
|
||||
template <typename T> constexpr void setBytes(T& dest, const std::uint8_t* b, std::size_t len) {
|
||||
dest.bytesLen = len;
|
||||
std::memcpy(&dest.bytes[0], b, len);
|
||||
}
|
||||
|
||||
struct test_vectors_t {
|
||||
const char* input;
|
||||
const std::uint8_t digest[32];
|
||||
};
|
||||
|
||||
constexpr std::uint8_t sign_test[] = {0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
|
||||
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
|
||||
|
||||
constexpr test_vectors_t sha_256_test[] = {
|
||||
{"", {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
|
||||
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}},
|
||||
{"abc", {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
|
||||
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}}};
|
||||
|
||||
// EXI AuthorizationReq: checked okay (hash computes correctly)
|
||||
constexpr std::uint8_t iso_exi_a[] = {0x80, 0x04, 0x01, 0x52, 0x51, 0x0c, 0x40, 0x82, 0x9b, 0x7b, 0x6b, 0x29, 0x02,
|
||||
0x93, 0x0b, 0x73, 0x23, 0x7b, 0x69, 0x02, 0x23, 0x0b, 0xa3, 0x09, 0xe8};
|
||||
|
||||
// checked okay
|
||||
constexpr std::uint8_t iso_exi_a_hash[] = {0xd1, 0xb5, 0xe0, 0x3d, 0x00, 0x65, 0xbe, 0xe5, 0x6b, 0x31, 0x79,
|
||||
0x84, 0x45, 0x30, 0x51, 0xeb, 0x54, 0xca, 0x18, 0xfc, 0x0e, 0x09,
|
||||
0x16, 0x17, 0x4f, 0x8b, 0x3c, 0x77, 0xa9, 0x8f, 0x4a, 0xa9};
|
||||
|
||||
// EXI AuthorizationReq signature block: checked okay (hash computes correctly)
|
||||
constexpr std::uint8_t iso_exi_b[] = {
|
||||
0x80, 0x81, 0x12, 0xb4, 0x3a, 0x3a, 0x38, 0x1d, 0x17, 0x97, 0xbb, 0xbb, 0xbb, 0x97, 0x3b, 0x99, 0x97, 0x37, 0xb9,
|
||||
0x33, 0x97, 0xaa, 0x29, 0x17, 0xb1, 0xb0, 0xb7, 0x37, 0xb7, 0x34, 0xb1, 0xb0, 0xb6, 0x16, 0xb2, 0xbc, 0x34, 0x97,
|
||||
0xa1, 0xab, 0x43, 0xa3, 0xa3, 0x81, 0xd1, 0x79, 0x7b, 0xbb, 0xbb, 0xb9, 0x73, 0xb9, 0x99, 0x73, 0x7b, 0x93, 0x39,
|
||||
0x79, 0x91, 0x81, 0x81, 0x89, 0x79, 0x81, 0xa1, 0x7b, 0xc3, 0x6b, 0x63, 0x23, 0x9b, 0x4b, 0x39, 0x6b, 0x6b, 0x7b,
|
||||
0x93, 0x29, 0x1b, 0x2b, 0x1b, 0x23, 0x9b, 0x09, 0x6b, 0x9b, 0x43, 0x09, 0x91, 0xa9, 0xb2, 0x20, 0x62, 0x34, 0x94,
|
||||
0x43, 0x10, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72,
|
||||
0x67, 0x2f, 0x54, 0x52, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x65, 0x78, 0x69, 0x2f,
|
||||
0x48, 0x52, 0xd0, 0xe8, 0xe8, 0xe0, 0x74, 0x5e, 0x5e, 0xee, 0xee, 0xee, 0x5c, 0xee, 0x66, 0x5c, 0xde, 0xe4, 0xce,
|
||||
0x5e, 0x64, 0x60, 0x60, 0x62, 0x5e, 0x60, 0x68, 0x5e, 0xf0, 0xda, 0xd8, 0xca, 0xdc, 0xc6, 0x46, 0xe6, 0xd0, 0xc2,
|
||||
0x64, 0x6a, 0x6c, 0x84, 0x1a, 0x36, 0xbc, 0x07, 0xa0, 0x0c, 0xb7, 0xdc, 0xad, 0x66, 0x2f, 0x30, 0x88, 0xa6, 0x0a,
|
||||
0x3d, 0x6a, 0x99, 0x43, 0x1f, 0x81, 0xc1, 0x22, 0xc2, 0xe9, 0xf1, 0x67, 0x8e, 0xf5, 0x31, 0xe9, 0x55, 0x23, 0x70};
|
||||
|
||||
// checked okay
|
||||
constexpr std::uint8_t iso_exi_b_hash[] = {0xa4, 0xe9, 0x03, 0xe1, 0x82, 0x43, 0x04, 0x1b, 0x55, 0x4e, 0x11,
|
||||
0x64, 0x7e, 0x10, 0x1e, 0xd2, 0x5f, 0xc9, 0xf2, 0x15, 0x2a, 0xf4,
|
||||
0x67, 0x40, 0x14, 0xfe, 0x2a, 0xde, 0xac, 0x1e, 0x1c, 0xf7};
|
||||
|
||||
// checked okay (verifies iso_exi_b_hash with iso_priv.pem)
|
||||
constexpr std::uint8_t iso_exi_sig[] = {0x4c, 0x8f, 0x20, 0xc1, 0x40, 0x0b, 0xa6, 0x76, 0x06, 0xaa, 0x48, 0x11, 0x57,
|
||||
0x2a, 0x2f, 0x1a, 0xd3, 0xc1, 0x50, 0x89, 0xd9, 0x54, 0x20, 0x36, 0x34, 0x30,
|
||||
0xbb, 0x26, 0xb4, 0x9d, 0xb1, 0x04, 0xf0, 0x8d, 0xfa, 0x8b, 0xf8, 0x05, 0x5e,
|
||||
0x63, 0xa4, 0xb7, 0x5a, 0x8d, 0x31, 0x69, 0x20, 0x6f, 0xa8, 0xd5, 0x43, 0x08,
|
||||
0xba, 0x58, 0xf0, 0x56, 0x6b, 0x96, 0xba, 0xf6, 0x92, 0xce, 0x59, 0x50};
|
||||
|
||||
const char iso_exi_a_hash_b64[] = "0bXgPQBlvuVrMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=";
|
||||
const char iso_exi_a_hash_b64_nl[] = "0bXgPQBlvuVrMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=\n";
|
||||
|
||||
const char iso_exi_a_hash_b64_crlf[] = "0bXgPQBlvuVrMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=\r\n";
|
||||
const char iso_exi_a_hash_b64_cr[] = "0bXgPQBlvuVrMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=\r";
|
||||
const char iso_exi_a_hash_b64_spaces[] = "0bXgPQBlvuVr MXmERTBR61TK GPwOCRYXT4s8d6mPSqk=";
|
||||
const char iso_exi_a_hash_b64_tabs[] = "0bXgPQBlvuVr\tMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=";
|
||||
|
||||
const char iso_exi_sig_b64[] =
|
||||
"TI8gwUALpnYGqkgRVyovGtPBUInZVCA2NDC7JrSdsQTwjfqL+AVeY6S3Wo0xaSBvqNVDCLpY8FZrlrr2ks5ZUA==";
|
||||
const char iso_exi_sig_b64_nl[] =
|
||||
"TI8gwUALpnYGqkgRVyovGtPBUInZVCA2NDC7JrSdsQTwjfqL+AVeY6S3Wo0xaSBv\nqNVDCLpY8FZrlrr2ks5ZUA==\n";
|
||||
|
||||
const char test_cert_pem[] = "-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICBDCCAaqgAwIBAgIUQnMkyWtvc/a5OG8dZr9ziA5uQqYwCgYIKoZIzj0EAwIw\n"
|
||||
"TjELMAkGA1UEBhMCR0IxDzANBgNVBAcMBkxvbmRvbjEPMA0GA1UECgwGUGlvbml4\n"
|
||||
"MR0wGwYDVQQDDBRDUyBSb290IFRydXN0IEFuY2hvcjAeFw0yNDA5MTkxMzQwMDBa\n"
|
||||
"Fw0yNDEwMjExMzQwMDBaME4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQHDAZMb25kb24x\n"
|
||||
"DzANBgNVBAoMBlBpb25peDEdMBsGA1UEAwwUQ1MgUm9vdCBUcnVzdCBBbmNob3Iw\n"
|
||||
"WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARLkawitst5NtPoYGpDCp8/GBTDrNRJ\n"
|
||||
"pCzS3KHT2lZJDOwzegRn+Zhs0csqXIQgbkCqdSozg+d83QNKcpmJk4FYo2YwZDAO\n"
|
||||
"BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFB6Ytfi9uSF7NSYGXmyZEcKsWHwJMB8G\n"
|
||||
"A1UdIwQYMBaAFB6Ytfi9uSF7NSYGXmyZEcKsWHwJMBIGA1UdEwEB/wQIMAYBAf8C\n"
|
||||
"AQIwCgYIKoZIzj0EAwIDSAAwRQIge4+uxc2EFYD7AkHR+9d/NbULUnKFIBRLqYE+\n"
|
||||
"Ib4h2CMCIQCtFWyvxwOUNidUTZGqyZXFmDyutJiNM0mi1iuFk8/8Mw==\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
const char test_cert_hash[] = "082f891b26de97c8bdedb159f8d59113cfb55dc0";
|
||||
const char test_cert_key_hash[] = "3b094e5f2594a3ae4511a9ff4285acd91fcd11c0";
|
||||
|
||||
inline const auto to_hex_string(const openssl::sha_1_digest_t& b) {
|
||||
std::stringstream string_stream;
|
||||
string_stream << std::hex;
|
||||
|
||||
for (int idx = 0; idx < sizeof(b); ++idx)
|
||||
string_stream << std::setw(2) << std::setfill('0') << (int)b[idx];
|
||||
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
TEST(util, removeHyphen) {
|
||||
const std::string expected{"UKSWI123456791A"};
|
||||
std::string cert_emaid{"UKSWI123456791A"};
|
||||
|
||||
EXPECT_EQ(cert_emaid, expected);
|
||||
cert_emaid.erase(std::remove(cert_emaid.begin(), cert_emaid.end(), '-'), cert_emaid.end());
|
||||
EXPECT_EQ(cert_emaid, expected);
|
||||
|
||||
cert_emaid = std::string{"-UKSWI-123456791-A-"};
|
||||
cert_emaid.erase(std::remove(cert_emaid.begin(), cert_emaid.end(), '-'), cert_emaid.end());
|
||||
EXPECT_EQ(cert_emaid, expected);
|
||||
}
|
||||
|
||||
TEST(certificate_sha_1, hash) {
|
||||
auto cert = openssl::pem_to_certificate(test_cert_pem);
|
||||
EXPECT_TRUE(cert);
|
||||
openssl::sha_1_digest_t digest;
|
||||
auto res = openssl::certificate_sha_1(digest, cert.get());
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(to_hex_string(digest), test_cert_hash);
|
||||
}
|
||||
|
||||
TEST(certificate_subject_public_key_sha_1, hash) {
|
||||
auto cert = openssl::pem_to_certificate(test_cert_pem);
|
||||
EXPECT_TRUE(cert);
|
||||
openssl::sha_1_digest_t digest;
|
||||
auto res = openssl::certificate_subject_public_key_sha_1(digest, cert.get());
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(to_hex_string(digest), test_cert_key_hash);
|
||||
}
|
||||
|
||||
TEST(DER, equal) {
|
||||
const std::uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
openssl::DER a;
|
||||
openssl::DER b(sizeof(data));
|
||||
openssl::DER c1(&data[0], sizeof(data));
|
||||
openssl::DER c2(&data[0], sizeof(data));
|
||||
openssl::DER d(&data[0], sizeof(data) - 1);
|
||||
|
||||
EXPECT_FALSE(a);
|
||||
EXPECT_TRUE(b);
|
||||
EXPECT_TRUE(c1);
|
||||
EXPECT_TRUE(c2);
|
||||
EXPECT_TRUE(d);
|
||||
|
||||
EXPECT_EQ(a, nullptr);
|
||||
EXPECT_NE(b, nullptr);
|
||||
EXPECT_NE(c1, nullptr);
|
||||
EXPECT_NE(c2, nullptr);
|
||||
EXPECT_NE(d, nullptr);
|
||||
|
||||
EXPECT_NE(c1.get(), c2.get());
|
||||
EXPECT_EQ(c1.size(), c2.size());
|
||||
EXPECT_EQ(c1, c2);
|
||||
EXPECT_EQ(c1, c1);
|
||||
|
||||
EXPECT_NE(c1, a);
|
||||
EXPECT_NE(c1, b);
|
||||
EXPECT_NE(c1, d);
|
||||
EXPECT_NE(a, c1);
|
||||
EXPECT_NE(b, c1);
|
||||
EXPECT_NE(d, c1);
|
||||
}
|
||||
|
||||
TEST(DER, construct) {
|
||||
const std::uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
const openssl::DER a(&data[0], sizeof(data));
|
||||
|
||||
auto b = a;
|
||||
EXPECT_NE(a.get(), b.get());
|
||||
EXPECT_EQ(a, b);
|
||||
|
||||
auto c{a};
|
||||
EXPECT_NE(a.get(), c.get());
|
||||
EXPECT_EQ(a, c);
|
||||
EXPECT_EQ(b, c);
|
||||
|
||||
auto d = std::move(b);
|
||||
EXPECT_EQ(a, d);
|
||||
EXPECT_EQ(b.size(), 0);
|
||||
EXPECT_EQ(b, nullptr);
|
||||
EXPECT_FALSE(b);
|
||||
|
||||
auto e(std::move(c));
|
||||
EXPECT_EQ(a, e);
|
||||
EXPECT_EQ(c.size(), 0);
|
||||
EXPECT_EQ(c, nullptr);
|
||||
EXPECT_FALSE(c);
|
||||
|
||||
const std::uint8_t alt[] = {9, 8, 7, 6, 5, 4};
|
||||
openssl::DER x(&alt[0], sizeof(alt));
|
||||
x = e;
|
||||
EXPECT_EQ(x, a);
|
||||
EXPECT_NE(x, a.get());
|
||||
EXPECT_NE(x, e.get());
|
||||
}
|
||||
|
||||
TEST(DER, release) {
|
||||
const std::uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
openssl::DER a(&data[0], sizeof(data));
|
||||
EXPECT_EQ(a.size(), sizeof(data));
|
||||
EXPECT_NE(a.get(), &data[0]);
|
||||
const auto* tmp_p = a.get();
|
||||
|
||||
auto* ptr = a.release();
|
||||
EXPECT_EQ(a.size(), 0);
|
||||
EXPECT_EQ(a.get(), nullptr);
|
||||
EXPECT_EQ(ptr, tmp_p);
|
||||
openssl::DER::free(ptr);
|
||||
}
|
||||
|
||||
TEST(openssl, sizes) {
|
||||
EXPECT_EQ(sizeof(openssl::sha_1_digest_t), openssl::sha_1_digest_size);
|
||||
EXPECT_EQ(sizeof(openssl::sha_256_digest_t), openssl::sha_256_digest_size);
|
||||
EXPECT_EQ(sizeof(openssl::sha_384_digest_t), openssl::sha_384_digest_size);
|
||||
EXPECT_EQ(sizeof(openssl::sha_512_digest_t), openssl::sha_512_digest_size);
|
||||
}
|
||||
|
||||
TEST(openssl, base64Encode) {
|
||||
auto res = openssl::base64_encode(&iso_exi_a_hash[0], sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(res, iso_exi_a_hash_b64);
|
||||
res = openssl::base64_encode(&iso_exi_sig[0], sizeof(iso_exi_sig));
|
||||
EXPECT_EQ(res, iso_exi_sig_b64);
|
||||
}
|
||||
|
||||
TEST(openssl, base64EncodeNl) {
|
||||
auto res = openssl::base64_encode(&iso_exi_a_hash[0], sizeof(iso_exi_a_hash), true);
|
||||
EXPECT_EQ(res, iso_exi_a_hash_b64_nl);
|
||||
res = openssl::base64_encode(&iso_exi_sig[0], sizeof(iso_exi_sig), true);
|
||||
EXPECT_EQ(res, iso_exi_sig_b64_nl);
|
||||
}
|
||||
|
||||
TEST(openssl, base64Decode) {
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64[0], sizeof(iso_exi_a_hash_b64) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
res = openssl::base64_decode(&iso_exi_sig_b64[0], sizeof(iso_exi_sig_b64) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_sig));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_sig[0], res.size()), 0);
|
||||
|
||||
std::array<std::uint8_t, 512> buffer{};
|
||||
std::size_t buffer_len = buffer.size();
|
||||
|
||||
EXPECT_TRUE(
|
||||
openssl::base64_decode(&iso_exi_a_hash_b64[0], sizeof(iso_exi_a_hash_b64) - 1, buffer.data(), buffer_len));
|
||||
ASSERT_EQ(buffer_len, sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(buffer.data(), &iso_exi_a_hash[0], buffer_len), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeNl) {
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64_nl[0], sizeof(iso_exi_a_hash_b64_nl) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
res = openssl::base64_decode(&iso_exi_sig_b64_nl[0], sizeof(iso_exi_sig_b64_nl) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_sig));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_sig[0], res.size()), 0);
|
||||
|
||||
std::array<std::uint8_t, 512> buffer{};
|
||||
std::size_t buffer_len = buffer.size();
|
||||
|
||||
EXPECT_TRUE(openssl::base64_decode(&iso_exi_a_hash_b64_nl[0], sizeof(iso_exi_a_hash_b64_nl) - 1, buffer.data(),
|
||||
buffer_len));
|
||||
ASSERT_EQ(buffer_len, sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(buffer.data(), &iso_exi_a_hash[0], buffer_len), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeCrlf) {
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64_crlf[0], sizeof(iso_exi_a_hash_b64_crlf) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeCrOnly) {
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64_cr[0], sizeof(iso_exi_a_hash_b64_cr) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeInternalSpaces) {
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64_spaces[0], sizeof(iso_exi_a_hash_b64_spaces) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeTabs) {
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64_tabs[0], sizeof(iso_exi_a_hash_b64_tabs) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeTrailingNulIsTolerated) {
|
||||
// sizeof(literal) includes the trailing NUL; impl must skip it.
|
||||
auto res = openssl::base64_decode(&iso_exi_a_hash_b64[0], sizeof(iso_exi_a_hash_b64));
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeVerticalTabAndFormFeed) {
|
||||
const char vt_ff[] = "0bXgPQBlvuVr\vMXmERTBR61TKGPwOC\fRYXT4s8d6mPSqk=";
|
||||
auto res = openssl::base64_decode(&vt_ff[0], sizeof(vt_ff) - 1);
|
||||
ASSERT_EQ(res.size(), sizeof(iso_exi_a_hash));
|
||||
EXPECT_EQ(std::memcmp(res.data(), &iso_exi_a_hash[0], res.size()), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeInvalidByteRejected) {
|
||||
// Non-whitespace non-alphabet bytes still produce empty output.
|
||||
auto res = openssl::base64_decode("@@@@", 4);
|
||||
EXPECT_TRUE(res.empty());
|
||||
}
|
||||
|
||||
TEST(openssl, base64DecodeEmptyInputReturnsEmpty) {
|
||||
auto res = openssl::base64_decode("", 0);
|
||||
EXPECT_TRUE(res.empty());
|
||||
|
||||
std::array<std::uint8_t, 16> buffer{};
|
||||
std::size_t buffer_len = buffer.size();
|
||||
EXPECT_FALSE(openssl::base64_decode("", 0, buffer.data(), buffer_len));
|
||||
}
|
||||
|
||||
TEST(openssl, base64EncodeEmptyInputReturnsEmpty) {
|
||||
const std::uint8_t empty_buf[1] = {0};
|
||||
auto res = openssl::base64_encode(empty_buf, 0);
|
||||
EXPECT_TRUE(res.empty());
|
||||
}
|
||||
|
||||
TEST(openssl, sha256) {
|
||||
openssl::sha_256_digest_t digest;
|
||||
EXPECT_TRUE(openssl::sha_256(sha_256_test[0].input, 0, digest));
|
||||
EXPECT_EQ(std::memcmp(digest.data(), &sha_256_test[0].digest[0], 32), 0);
|
||||
EXPECT_TRUE(openssl::sha_256(sha_256_test[1].input, 3, digest));
|
||||
EXPECT_EQ(std::memcmp(digest.data(), &sha_256_test[1].digest[0], 32), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, sha256Exi) {
|
||||
openssl::sha_256_digest_t digest;
|
||||
EXPECT_TRUE(openssl::sha_256(&iso_exi_a[0], sizeof(iso_exi_a), digest));
|
||||
EXPECT_EQ(std::memcmp(digest.data(), &iso_exi_a_hash[0], 32), 0);
|
||||
|
||||
EXPECT_TRUE(openssl::sha_256(&iso_exi_b[0], sizeof(iso_exi_b), digest));
|
||||
EXPECT_EQ(std::memcmp(digest.data(), &iso_exi_b_hash[0], 32), 0);
|
||||
}
|
||||
|
||||
TEST(openssl, signVerify) {
|
||||
auto pkey = openssl::load_private_key("server_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
|
||||
std::array<std::uint8_t, 256> sig_der{};
|
||||
std::size_t sig_der_len{sig_der.size()};
|
||||
openssl::sha_256_digest_t digest;
|
||||
EXPECT_TRUE(openssl::sha_256(&sign_test[0], openssl::sha_256_digest_size, digest));
|
||||
|
||||
EXPECT_TRUE(openssl::sign(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
// std::cout << "signature size: " << sig_der_len << std::endl;
|
||||
EXPECT_TRUE(openssl::verify(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
}
|
||||
|
||||
#ifdef USING_TPM2
|
||||
TEST(opensslTpm, signVerify) {
|
||||
auto pkey = openssl::load_private_key("tpm_pki/server_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
|
||||
// looks like sign/verify may not be supported
|
||||
// TODO(james-ctc) investigation needed
|
||||
// perhaps the public key needs to be extracted
|
||||
|
||||
#if 0
|
||||
if (EVP_PKEY_can_sign(pkey.get()) == 1) {
|
||||
std::array<std::uint8_t, 256> sig_der{};
|
||||
std::size_t sig_der_len{sig_der.size()};
|
||||
openssl::sha_256_digest_t digest;
|
||||
evse_security::OpenSSLProvider provider;
|
||||
provider.set_global_mode(evse_security::OpenSSLProvider::mode_t::custom_provider);
|
||||
EXPECT_TRUE(openssl::sha_256(&sign_test[0], openssl::sha_256_digest_size, digest));
|
||||
|
||||
EXPECT_TRUE(openssl::sign(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
// std::cout << "signature size: " << sig_der_len << std::endl;
|
||||
|
||||
EXPECT_TRUE(openssl::verify(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
} else {
|
||||
std::cout << "sign/verify not supported" << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(openssl, signVerifyBn) {
|
||||
auto pkey = openssl::load_private_key("server_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
|
||||
openssl::bn_t r;
|
||||
openssl::bn_t s;
|
||||
|
||||
openssl::sha_256_digest_t digest;
|
||||
EXPECT_TRUE(openssl::sha_256(&sign_test[0], openssl::sha_256_digest_size, digest));
|
||||
|
||||
EXPECT_TRUE(openssl::sign(pkey.get(), r, s, digest));
|
||||
// std::cout << "signature size: " << sig_der_len << std::endl;
|
||||
EXPECT_TRUE(openssl::verify(pkey.get(), r, s, digest));
|
||||
}
|
||||
|
||||
TEST(openssl, signVerifyMix) {
|
||||
auto pkey = openssl::load_private_key("server_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
|
||||
std::array<std::uint8_t, 80> sig_der;
|
||||
std::size_t sig_der_len{sig_der.size()};
|
||||
openssl::sha_256_digest_t digest;
|
||||
EXPECT_TRUE(openssl::sha_256(&sign_test[0], openssl::sha_256_digest_size, digest));
|
||||
|
||||
EXPECT_TRUE(openssl::sign(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
|
||||
openssl::bn_t r;
|
||||
openssl::bn_t s;
|
||||
EXPECT_TRUE(openssl::signature_to_bn(r, s, sig_der.data(), sig_der_len));
|
||||
EXPECT_TRUE(openssl::verify(pkey.get(), r, s, digest));
|
||||
}
|
||||
|
||||
TEST(openssl, signVerifyFail) {
|
||||
auto pkey = openssl::load_private_key("server_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
auto pkey_inv = openssl::load_private_key("client_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
|
||||
std::array<std::uint8_t, 256> sig_der;
|
||||
std::size_t sig_der_len{sig_der.size()};
|
||||
openssl::sha_256_digest_t digest;
|
||||
EXPECT_TRUE(openssl::sha_256(&sign_test[0], openssl::sha_256_digest_size, digest));
|
||||
|
||||
EXPECT_TRUE(openssl::sign(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
// std::cout << "signature size: " << sig_der_len << std::endl;
|
||||
EXPECT_FALSE(openssl::verify(pkey_inv.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
|
||||
}
|
||||
|
||||
TEST(openssl, verifyIso) {
|
||||
auto pkey = openssl::load_private_key("iso_priv.pem", nullptr);
|
||||
ASSERT_TRUE(pkey);
|
||||
|
||||
auto sig = openssl::bn_to_signature(&iso_exi_sig[0], &iso_exi_sig[32]);
|
||||
EXPECT_TRUE(openssl::verify(pkey.get(), sig.get(), sig.size(), &iso_exi_b_hash[0], sizeof(iso_exi_b_hash)));
|
||||
}
|
||||
|
||||
TEST(certificateLoad, single) {
|
||||
auto certs = ::openssl::load_certificates("server_cert.pem");
|
||||
EXPECT_EQ(certs.size(), 1);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, chain) {
|
||||
auto certs = ::openssl::load_certificates("server_chain.pem");
|
||||
EXPECT_EQ(certs.size(), 2);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, key) {
|
||||
auto certs = ::openssl::load_certificates("server_priv.pem");
|
||||
EXPECT_EQ(certs.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, missing) {
|
||||
auto certs = ::openssl::load_certificates("server_priv.pem-not-found");
|
||||
EXPECT_EQ(certs.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, nullptr) {
|
||||
auto certs = ::openssl::load_certificates(nullptr);
|
||||
EXPECT_EQ(certs.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, empty) {
|
||||
auto certs = ::openssl::load_certificates("");
|
||||
EXPECT_EQ(certs.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, multiFile) {
|
||||
std::vector<const char*> files{"server_chain.pem", "alt_server_chain.pem"};
|
||||
|
||||
auto certs = ::openssl::load_certificates(files);
|
||||
ASSERT_EQ(certs.size(), 4);
|
||||
|
||||
auto server = ::openssl::load_certificates("server_cert.pem");
|
||||
auto ca = ::openssl::load_certificates("server_ca_cert.pem");
|
||||
auto alt_server = ::openssl::load_certificates("alt_server_cert.pem");
|
||||
auto alt_ca = ::openssl::load_certificates("alt_server_ca_cert.pem");
|
||||
|
||||
EXPECT_EQ(certs[0], server[0]);
|
||||
EXPECT_EQ(certs[1], ca[0]);
|
||||
EXPECT_EQ(certs[2], alt_server[0]);
|
||||
EXPECT_EQ(certs[3], alt_ca[0]);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, multiFileEmpty) {
|
||||
std::vector<const char*> files{};
|
||||
|
||||
auto certs = ::openssl::load_certificates(files);
|
||||
ASSERT_EQ(certs.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, multiFileMissing) {
|
||||
std::vector<const char*> files{"server_chain.pem", "alt_server_chain.pem-not-found"};
|
||||
|
||||
auto certs = ::openssl::load_certificates(files);
|
||||
ASSERT_EQ(certs.size(), 2);
|
||||
|
||||
auto server = ::openssl::load_certificates("server_cert.pem");
|
||||
auto ca = ::openssl::load_certificates("server_ca_cert.pem");
|
||||
|
||||
EXPECT_EQ(certs[0], server[0]);
|
||||
EXPECT_EQ(certs[1], ca[0]);
|
||||
}
|
||||
|
||||
TEST(certificateLoad, multiFileNullptr) {
|
||||
std::vector<const char*> files{nullptr, "alt_server_chain.pem"};
|
||||
|
||||
auto certs = ::openssl::load_certificates(files);
|
||||
ASSERT_EQ(certs.size(), 2);
|
||||
|
||||
auto alt_server = ::openssl::load_certificates("alt_server_cert.pem");
|
||||
auto alt_ca = ::openssl::load_certificates("alt_server_ca_cert.pem");
|
||||
|
||||
EXPECT_EQ(certs[0], alt_server[0]);
|
||||
EXPECT_EQ(certs[1], alt_ca[0]);
|
||||
}
|
||||
|
||||
TEST(certificateLoadPki, none) {
|
||||
auto certs = ::openssl::load_certificates(nullptr, nullptr, nullptr);
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
|
||||
certs = ::openssl::load_certificates("server_cert.pem", nullptr, nullptr);
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
|
||||
certs = ::openssl::load_certificates(nullptr, "server_chain.pem", nullptr);
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
|
||||
certs = ::openssl::load_certificates(nullptr, nullptr, "server_root_cert.pem");
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificateLoadPki, full) {
|
||||
auto certs = ::openssl::load_certificates("server_cert.pem", "server_ca_cert.pem", "server_root_cert.pem");
|
||||
|
||||
auto server = ::openssl::load_certificates("server_cert.pem");
|
||||
ASSERT_EQ(server.size(), 1);
|
||||
auto chain = ::openssl::load_certificates("server_ca_cert.pem");
|
||||
ASSERT_EQ(chain.size(), 1);
|
||||
auto trust_anchors = ::openssl::load_certificates("server_root_cert.pem");
|
||||
ASSERT_EQ(trust_anchors.size(), 1);
|
||||
|
||||
ASSERT_NE(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.leaf, server[0]);
|
||||
ASSERT_EQ(certs.chain.size(), 1);
|
||||
ASSERT_EQ(certs.trust_anchors.size(), 1);
|
||||
|
||||
EXPECT_EQ(certs.chain, chain);
|
||||
EXPECT_EQ(certs.trust_anchors, trust_anchors);
|
||||
}
|
||||
|
||||
TEST(certificateLoadPki, noLeaf) {
|
||||
// should work since leaf is 1st certificate in server_chain.pem
|
||||
auto certs = ::openssl::load_certificates(nullptr, "server_chain.pem", "server_root_cert.pem");
|
||||
|
||||
auto server = ::openssl::load_certificates("server_cert.pem");
|
||||
ASSERT_EQ(server.size(), 1);
|
||||
auto chain = ::openssl::load_certificates("server_ca_cert.pem");
|
||||
ASSERT_EQ(chain.size(), 1);
|
||||
auto trust_anchors = ::openssl::load_certificates("server_root_cert.pem");
|
||||
ASSERT_EQ(trust_anchors.size(), 1);
|
||||
|
||||
EXPECT_EQ(certs.leaf, server[0]);
|
||||
ASSERT_EQ(certs.chain.size(), 1);
|
||||
ASSERT_EQ(certs.trust_anchors.size(), 1);
|
||||
|
||||
EXPECT_EQ(certs.chain, chain);
|
||||
EXPECT_EQ(certs.trust_anchors, trust_anchors);
|
||||
}
|
||||
|
||||
TEST(certificateLoadPki, invalid) {
|
||||
auto certs = ::openssl::load_certificates("client_cert.pem", "server_ca_cert.pem", "server_root_cert.pem");
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
|
||||
certs = ::openssl::load_certificates("server_cert.pem", "client_ca_cert.pem", "server_root_cert.pem");
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
|
||||
certs = ::openssl::load_certificates("server_cert.pem", "server_ca_cert.pem", "client_root_cert.pem");
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
|
||||
certs = ::openssl::load_certificates(nullptr, "server_chain.pem", "client_root_cert.pem");
|
||||
EXPECT_EQ(certs.leaf, nullptr);
|
||||
EXPECT_EQ(certs.chain.size(), 0);
|
||||
EXPECT_EQ(certs.trust_anchors.size(), 0);
|
||||
}
|
||||
|
||||
TEST(certificate, toPem) {
|
||||
auto certs = ::openssl::load_certificates("client_ca_cert.pem");
|
||||
ASSERT_EQ(certs.size(), 1);
|
||||
auto pem = ::openssl::certificate_to_pem(certs[0].get());
|
||||
EXPECT_FALSE(pem.empty());
|
||||
// std::cout << pem << std::endl;
|
||||
}
|
||||
|
||||
TEST(certificate, loadPemSingle) {
|
||||
auto certs = ::openssl::load_certificates("client_ca_cert.pem");
|
||||
ASSERT_EQ(certs.size(), 1);
|
||||
auto pem = ::openssl::certificate_to_pem(certs[0].get());
|
||||
EXPECT_FALSE(pem.empty());
|
||||
|
||||
auto pem_certs = ::openssl::load_certificates_pem(pem.c_str());
|
||||
ASSERT_EQ(pem_certs.size(), 1);
|
||||
EXPECT_EQ(certs[0], pem_certs[0]);
|
||||
}
|
||||
|
||||
TEST(certificate, loadPemMulti) {
|
||||
auto certs = ::openssl::load_certificates("client_chain.pem");
|
||||
ASSERT_GT(certs.size(), 1);
|
||||
std::string pem;
|
||||
for (const auto& cert : certs) {
|
||||
pem += ::openssl::certificate_to_pem(cert.get());
|
||||
}
|
||||
EXPECT_FALSE(pem.empty());
|
||||
// std::cout << pem << std::endl << "Output" << std::endl;
|
||||
|
||||
auto pem_certs = ::openssl::load_certificates_pem(pem.c_str());
|
||||
ASSERT_EQ(pem_certs.size(), certs.size());
|
||||
for (auto i = 0; i < certs.size(); i++) {
|
||||
SCOPED_TRACE(std::to_string(i));
|
||||
// std::cout << ::openssl::certificate_to_pem(pem_certs[i].get()) << std::endl;
|
||||
EXPECT_EQ(certs[i], pem_certs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(certificate, verify) {
|
||||
auto client = ::openssl::load_certificates("client_cert.pem");
|
||||
auto chain = ::openssl::load_certificates("client_chain.pem");
|
||||
auto root = ::openssl::load_certificates("client_root_cert.pem");
|
||||
|
||||
ASSERT_EQ(client.size(), 1);
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
EXPECT_EQ(::openssl::verify_certificate(client[0].get(), root, chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, verifyCross) {
|
||||
auto client = ::openssl::load_certificates("server_cert.pem");
|
||||
auto chain = ::openssl::load_certificates("cross_ca_cert.pem");
|
||||
auto root = ::openssl::load_certificates("client_root_cert.pem");
|
||||
|
||||
ASSERT_EQ(client.size(), 1);
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
EXPECT_EQ(::openssl::verify_certificate(client[0].get(), root, chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, verifyRemoveClientFromChain) {
|
||||
auto client = ::openssl::load_certificates("client_cert.pem");
|
||||
auto chain = ::openssl::load_certificates("client_chain.pem");
|
||||
auto root = ::openssl::load_certificates("client_root_cert.pem");
|
||||
|
||||
ASSERT_EQ(client.size(), 1);
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
// client certificate is 1st in the list
|
||||
openssl::certificate_list new_chain;
|
||||
for (auto itt = std::next(chain.begin()); itt != chain.end(); itt++) {
|
||||
new_chain.push_back(std::move(*itt));
|
||||
}
|
||||
|
||||
EXPECT_EQ(::openssl::verify_certificate(client[0].get(), root, new_chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, verifyNoClient) {
|
||||
// client certificate is in the chain
|
||||
auto chain = ::openssl::load_certificates("client_chain.pem");
|
||||
auto root = ::openssl::load_certificates("client_root_cert.pem");
|
||||
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
EXPECT_EQ(::openssl::verify_certificate(nullptr, root, chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, verifyFailWrongClient) {
|
||||
auto client = ::openssl::load_certificates("server_cert.pem");
|
||||
auto chain = ::openssl::load_certificates("client_chain.pem");
|
||||
auto root = ::openssl::load_certificates("client_root_cert.pem");
|
||||
|
||||
ASSERT_EQ(client.size(), 1);
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
EXPECT_NE(::openssl::verify_certificate(client[0].get(), root, chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, verifyFailWrongRoot) {
|
||||
auto client = ::openssl::load_certificates("client_cert.pem");
|
||||
auto chain = ::openssl::load_certificates("client_chain.pem");
|
||||
auto root = ::openssl::load_certificates("server_root_cert.pem");
|
||||
|
||||
ASSERT_EQ(client.size(), 1);
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
EXPECT_NE(::openssl::verify_certificate(client[0].get(), root, chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, verifyFailWrongChain) {
|
||||
auto client = ::openssl::load_certificates("client_cert.pem");
|
||||
auto chain = ::openssl::load_certificates("server_chain.pem");
|
||||
auto root = ::openssl::load_certificates("client_root_cert.pem");
|
||||
|
||||
ASSERT_EQ(client.size(), 1);
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
EXPECT_EQ(root.size(), 1);
|
||||
|
||||
EXPECT_NE(::openssl::verify_certificate(client[0].get(), root, chain), openssl::verify_result_t::Verified);
|
||||
}
|
||||
|
||||
TEST(certificate, subjectName) {
|
||||
auto chain = ::openssl::load_certificates("client_chain.pem");
|
||||
EXPECT_GT(chain.size(), 0);
|
||||
|
||||
for (const auto& cert : chain) {
|
||||
auto subject = ::openssl::certificate_subject(cert.get());
|
||||
EXPECT_GT(subject.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(certificateChainInfo, valid) {
|
||||
auto chain = openssl::load_certificates("client_cert.pem", "client_ca_cert.pem", "client_root_cert.pem");
|
||||
EXPECT_TRUE(openssl::verify_chain(chain));
|
||||
}
|
||||
|
||||
TEST(certificateChainInfo, invalid) {
|
||||
auto chain = openssl::load_certificates("server_cert.pem", "client_ca_cert.pem", "client_root_cert.pem");
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
chain = openssl::load_certificates("client_cert.pem", "server_ca_cert.pem", "client_root_cert.pem");
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
chain = openssl::load_certificates("client_cert.pem", "client_ca_cert.pem", "server_root_cert.pem");
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
}
|
||||
|
||||
TEST(certificateChain, valid) {
|
||||
auto pkey = openssl::load_private_key("client_priv.pem", nullptr);
|
||||
auto chain_info = openssl::load_certificates("client_cert.pem", "client_ca_cert.pem", "client_root_cert.pem");
|
||||
openssl::chain_t chain = {std::move(chain_info), std::move(pkey)};
|
||||
EXPECT_TRUE(openssl::verify_chain(chain));
|
||||
}
|
||||
|
||||
TEST(certificateChain, invalid) {
|
||||
auto pkey = openssl::load_private_key("server_priv.pem", nullptr);
|
||||
auto chain_info = openssl::load_certificates("client_cert.pem", "client_ca_cert.pem", "client_root_cert.pem");
|
||||
openssl::chain_t chain = {std::move(chain_info), std::move(pkey)};
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
chain_info = openssl::load_certificates("server_cert.pem", "client_ca_cert.pem", "client_root_cert.pem");
|
||||
chain.chain = std::move(chain_info);
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
chain_info = openssl::load_certificates("client_cert.pem", "server_ca_cert.pem", "client_root_cert.pem");
|
||||
chain.chain = std::move(chain_info);
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
chain_info = openssl::load_certificates("client_cert.pem", "client_ca_cert.pem", "server_root_cert.pem");
|
||||
chain.chain = std::move(chain_info);
|
||||
EXPECT_FALSE(openssl::verify_chain(chain));
|
||||
}
|
||||
|
||||
TEST(certificate, apply) {
|
||||
auto pkey = openssl::load_private_key("client_priv.pem", nullptr);
|
||||
auto chain_info = openssl::load_certificates("client_cert.pem", "client_ca_cert.pem", "client_root_cert.pem");
|
||||
openssl::chain_t chain = {std::move(chain_info), std::move(pkey)};
|
||||
auto* ctx = SSL_CTX_new(TLS_server_method());
|
||||
auto* ssl = SSL_new(ctx);
|
||||
EXPECT_TRUE(openssl::use_certificate_and_key(ssl, chain));
|
||||
SSL_free(ssl);
|
||||
SSL_CTX_free(ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
62
tools/EVerest-main/lib/everest/tls/tests/patched_test.cpp
Normal file
62
tools/EVerest-main/lib/everest/tls/tests/patched_test.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* \file testing patched version of OpenSSL
|
||||
*
|
||||
* These tests will only pass on a patched version of OpenSSL.
|
||||
* (they should compile and run fine with some test failures)
|
||||
*
|
||||
* It is recommended to also run tests alongside Wireshark
|
||||
* e.g. `./patched_test --gtest_filter=TlsTest.TLS12`
|
||||
* to check that the Server Hello record is correctly formed:
|
||||
* - no status_request or status_request_v2 then no Certificate Status record
|
||||
* - status_request or status_request_v2 then there is a Certificate Status record
|
||||
* - never both status_request and status_request_v2
|
||||
*/
|
||||
|
||||
#include "tls_connection_test.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// The tests - only pass on a patched OpenSSL
|
||||
|
||||
TEST_F(TlsTest, TLS12) {
|
||||
// test using TLS 1.2
|
||||
start();
|
||||
connect();
|
||||
// no status requested
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = true;
|
||||
connect();
|
||||
// status_request only
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = false;
|
||||
client_config.status_request_v2 = true;
|
||||
connect();
|
||||
// status_request_v2 only
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = true;
|
||||
connect();
|
||||
// status_request and status_request_v2
|
||||
// status_request_v2 is preferred over status_request
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
1
tools/EVerest-main/lib/everest/tls/tests/pki/.gitignore
vendored
Normal file
1
tools/EVerest-main/lib/everest/tls/tests/pki/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.pem
|
||||
@@ -0,0 +1,143 @@
|
||||
openssl_conf = openssl_init
|
||||
|
||||
[openssl_init]
|
||||
providers = provider_section
|
||||
|
||||
[provider_section]
|
||||
default = default_section
|
||||
tpm2 = tpm2_section
|
||||
base = base_section
|
||||
|
||||
[default_section]
|
||||
activate = 1
|
||||
|
||||
[tpm2_section]
|
||||
activate = 1
|
||||
|
||||
[base_section]
|
||||
activate = 1
|
||||
|
||||
# server section
|
||||
# ==============
|
||||
[req_server_root]
|
||||
distinguished_name = req_dn_server_root
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server_root
|
||||
|
||||
[req_server_ca]
|
||||
distinguished_name = req_dn_server_ca
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server_ca
|
||||
|
||||
[req_server]
|
||||
distinguished_name = req_dn_server
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server
|
||||
|
||||
[req_dn_server_root]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = Alternate Root Trust Anchor
|
||||
|
||||
[req_dn_server_ca]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = Alternate Intermediate CA
|
||||
|
||||
[req_dn_server]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = 11111111
|
||||
|
||||
[req_dn_client]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = 98765432
|
||||
|
||||
[v3_server_root]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true, pathlen:2
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_server_ca]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_server]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always
|
||||
keyUsage = digitalSignature, keyEncipherment, keyAgreement
|
||||
extendedKeyUsage = serverAuth, clientAuth
|
||||
subjectAltName = IP:192.168.245.1, DNS:evse.pionix.de
|
||||
|
||||
# client section
|
||||
# ==============
|
||||
[req_client]
|
||||
distinguished_name = req_dn_client
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_client
|
||||
|
||||
[req_client_root]
|
||||
distinguished_name = req_dn_client_root
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_client_root
|
||||
|
||||
[req_client_ca]
|
||||
distinguished_name = req_dn_client_ca
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_client_ca
|
||||
|
||||
[req_server]
|
||||
distinguished_name = req_dn_server
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server
|
||||
|
||||
[req_dn_client_root]
|
||||
C = DE
|
||||
O = Pionix
|
||||
L = Frankfurt
|
||||
CN = Alternate Root Trust Anchor
|
||||
|
||||
[req_dn_client_ca]
|
||||
C = DE
|
||||
O = Pionix
|
||||
L = Frankfurt
|
||||
CN = Alternate Intermediate CA
|
||||
|
||||
[req_dn_client]
|
||||
C = DE
|
||||
O = Pionix
|
||||
L = Frankfurt
|
||||
CN = 66666666
|
||||
|
||||
[v3_client_root]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true, pathlen:2
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_client_ca]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_client]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always
|
||||
keyUsage = digitalSignature, keyEncipherment, keyAgreement
|
||||
extendedKeyUsage = clientAuth
|
||||
11
tools/EVerest-main/lib/everest/tls/tests/pki/iso_pkey.asn1
Normal file
11
tools/EVerest-main/lib/everest/tls/tests/pki/iso_pkey.asn1
Normal file
@@ -0,0 +1,11 @@
|
||||
asn1=SEQ:pkcs8c
|
||||
[pkcs8c]
|
||||
ver=INT:0
|
||||
algid=SEQ:algid
|
||||
data=OCTWRAP,SEQ:sec1
|
||||
[algid]
|
||||
alg=OID:id-ecPublicKey
|
||||
parm=OID:prime256v1
|
||||
[sec1]
|
||||
ver=INT:1
|
||||
privkey=FORMAT:HEX,OCT:b9134963f51c4414738435057f97bbf1010cabcb8dbde9c5d48138396aa94b9d
|
||||
BIN
tools/EVerest-main/lib/everest/tls/tests/pki/ocsp_response.der
Normal file
BIN
tools/EVerest-main/lib/everest/tls/tests/pki/ocsp_response.der
Normal file
Binary file not shown.
143
tools/EVerest-main/lib/everest/tls/tests/pki/openssl-pki.conf
Normal file
143
tools/EVerest-main/lib/everest/tls/tests/pki/openssl-pki.conf
Normal file
@@ -0,0 +1,143 @@
|
||||
openssl_conf = openssl_init
|
||||
|
||||
[openssl_init]
|
||||
providers = provider_section
|
||||
|
||||
[provider_section]
|
||||
default = default_section
|
||||
tpm2 = tpm2_section
|
||||
base = base_section
|
||||
|
||||
[default_section]
|
||||
activate = 1
|
||||
|
||||
[tpm2_section]
|
||||
activate = 1
|
||||
|
||||
[base_section]
|
||||
activate = 1
|
||||
|
||||
# server section
|
||||
# ==============
|
||||
[req_server_root]
|
||||
distinguished_name = req_dn_server_root
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server_root
|
||||
|
||||
[req_server_ca]
|
||||
distinguished_name = req_dn_server_ca
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server_ca
|
||||
|
||||
[req_server]
|
||||
distinguished_name = req_dn_server
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server
|
||||
|
||||
[req_dn_server_root]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = Root Trust Anchor
|
||||
|
||||
[req_dn_server_ca]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = Intermediate CA
|
||||
|
||||
[req_dn_server]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = 00000000
|
||||
|
||||
[req_dn_client]
|
||||
C = GB
|
||||
O = Pionix
|
||||
L = London
|
||||
CN = 12345678
|
||||
|
||||
[v3_server_root]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true, pathlen:2
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_server_ca]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_server]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always
|
||||
keyUsage = digitalSignature, keyEncipherment, keyAgreement
|
||||
extendedKeyUsage = serverAuth, clientAuth
|
||||
subjectAltName = IP:192.168.245.1, DNS:evse.pionix.de
|
||||
|
||||
# client section
|
||||
# ==============
|
||||
[req_client]
|
||||
distinguished_name = req_dn_client
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_client
|
||||
|
||||
[req_client_root]
|
||||
distinguished_name = req_dn_client_root
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_client_root
|
||||
|
||||
[req_client_ca]
|
||||
distinguished_name = req_dn_client_ca
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_client_ca
|
||||
|
||||
[req_server]
|
||||
distinguished_name = req_dn_server
|
||||
utf8 = yes
|
||||
prompt = no
|
||||
req_extensions = v3_server
|
||||
|
||||
[req_dn_client_root]
|
||||
C = DE
|
||||
O = Pionix
|
||||
L = Frankfurt
|
||||
CN = Root Trust Anchor
|
||||
|
||||
[req_dn_client_ca]
|
||||
C = DE
|
||||
O = Pionix
|
||||
L = Frankfurt
|
||||
CN = Intermediate CA
|
||||
|
||||
[req_dn_client]
|
||||
C = DE
|
||||
O = Pionix
|
||||
L = Frankfurt
|
||||
CN = 12345678
|
||||
|
||||
[v3_client_root]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true, pathlen:2
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_client_ca]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer:always
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[v3_client]
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always
|
||||
keyUsage = digitalSignature, keyEncipherment, keyAgreement
|
||||
extendedKeyUsage = clientAuth
|
||||
57
tools/EVerest-main/lib/everest/tls/tests/pki/pki-tpm.sh
Executable file
57
tools/EVerest-main/lib/everest/tls/tests/pki/pki-tpm.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
|
||||
base=.
|
||||
cfg=./openssl-pki.conf
|
||||
dir=tpm_pki
|
||||
|
||||
[ ! -f "$cfg" ] && echo "missing openssl-pki.conf" && exit 1
|
||||
|
||||
generate() {
|
||||
local base=$1
|
||||
local dir=$2
|
||||
mkdir -p ${base}/${dir}
|
||||
|
||||
local root_priv=${base}/${dir}/server_root_priv.pem
|
||||
local ca_priv=${base}/${dir}/server_ca_priv.pem
|
||||
local server_priv=${base}/${dir}/server_priv.pem
|
||||
|
||||
local root_cert=${base}/${dir}/server_root_cert.pem
|
||||
local ca_cert=${base}/${dir}/server_ca_cert.pem
|
||||
local server_cert=${base}/${dir}/server_cert.pem
|
||||
local cert_path=${base}/${dir}/server_chain.pem
|
||||
|
||||
local tpmA="-provider"
|
||||
local tpmB="tpm2"
|
||||
local propA="-propquery"
|
||||
local propB="?provider=tpm2"
|
||||
|
||||
# generate keys
|
||||
for i in ${root_priv} ${ca_priv} ${server_priv}
|
||||
do
|
||||
openssl genpkey -config ${cfg} ${tpmA} ${tpmB} ${propA} ${propB} -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out $i
|
||||
done
|
||||
|
||||
export OPENSSL_CONF=${cfg}
|
||||
# generate root cert
|
||||
echo "Generate root"
|
||||
openssl req ${tpmA} ${tpmB} -provider default ${propA} ${propB} \
|
||||
-config ${cfg} -x509 -section req_server_root -extensions v3_server_root \
|
||||
-key ${root_priv} -out ${root_cert}
|
||||
# generate ca cert
|
||||
echo "Generate ca"
|
||||
openssl req ${tpmA} ${tpmB} -provider default ${propA} ${propB} \
|
||||
-config ${cfg} -x509 -section req_server_ca -extensions v3_server_ca \
|
||||
-key ${ca_priv} -CA ${root_cert} \
|
||||
-CAkey ${root_priv} -out ${ca_cert}
|
||||
# generate server cert
|
||||
echo "Generate server"
|
||||
openssl req ${tpmA} ${tpmB} -provider default ${propA} ${propB} \
|
||||
-config ${cfg} -x509 -section req_server -extensions v3_server \
|
||||
-key ${server_priv} -CA ${ca_cert} \
|
||||
-CAkey ${ca_priv} -out ${server_cert}
|
||||
|
||||
# create bundle
|
||||
cat ${server_cert} ${ca_cert} > ${cert_path}
|
||||
}
|
||||
|
||||
generate $base $dir
|
||||
80
tools/EVerest-main/lib/everest/tls/tests/pki/pki.sh
Executable file
80
tools/EVerest-main/lib/everest/tls/tests/pki/pki.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
|
||||
generate() {
|
||||
local base="$1"
|
||||
# generate keys
|
||||
for i in "${base}${server_root_priv}" "${base}${server_ca_priv}" "${base}${server_priv}" \
|
||||
"${base}${client_root_priv}" "${base}${client_ca_priv}" "${base}${client_priv}"
|
||||
do
|
||||
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "$i"
|
||||
chmod 644 "$i"
|
||||
done
|
||||
|
||||
export OPENSSL_CONF="${base}${cfg}"
|
||||
|
||||
echo "Generate ${base}server_root"
|
||||
openssl req \
|
||||
-config "${base}${cfg}" -x509 -section req_server_root -extensions v3_server_root \
|
||||
-key "${base}${server_root_priv}" -out "${base}${server_root_cert}"
|
||||
echo "Generate ${base}server_ca"
|
||||
openssl req \
|
||||
-config "${base}${cfg}" -x509 -section req_server_ca -extensions v3_server_ca \
|
||||
-key "${base}${server_ca_priv}" -CA "${base}${server_root_cert}" \
|
||||
-CAkey "${base}${server_root_priv}" -out "${base}${server_ca_cert}"
|
||||
echo "Generate ${base}server"
|
||||
openssl req \
|
||||
-config "${base}${cfg}" -x509 -section req_server -extensions v3_server \
|
||||
-key "${base}${server_priv}" -CA "${base}${server_ca_cert}" \
|
||||
-CAkey "${base}${server_ca_priv}" -out "${base}${server_cert}"
|
||||
cat "${base}${server_cert}" "${base}${server_ca_cert}" > "${base}${server_chain}"
|
||||
|
||||
echo "Generate ${base}client_root"
|
||||
openssl req \
|
||||
-config "${base}${cfg}" -x509 -section req_client_root -extensions v3_client_root \
|
||||
-key "${base}${client_root_priv}" -out "${base}${client_root_cert}"
|
||||
echo "Generate ${base}client_ca"
|
||||
openssl req \
|
||||
-config "${base}${cfg}" -x509 -section req_client_ca -extensions v3_client_ca \
|
||||
-key "${base}${client_ca_priv}" -CA "${base}${client_root_cert}" \
|
||||
-CAkey "${base}${client_root_priv}" -out "${base}${client_ca_cert}"
|
||||
echo "Generate ${base}client"
|
||||
openssl req \
|
||||
-config "${base}${cfg}" -x509 -section req_client -extensions v3_client \
|
||||
-key "${base}${client_priv}" -CA "${base}${client_ca_cert}" \
|
||||
-CAkey "${base}${client_ca_priv}" -out "${base}${client_cert}"
|
||||
|
||||
cat "${base}${client_cert}" "${base}${client_ca_cert}" > "${base}${client_chain}"
|
||||
}
|
||||
|
||||
cfg=openssl-pki.conf
|
||||
|
||||
server_root_priv=server_root_priv.pem
|
||||
server_ca_priv=server_ca_priv.pem
|
||||
server_priv=server_priv.pem
|
||||
|
||||
server_root_cert=server_root_cert.pem
|
||||
server_ca_cert=server_ca_cert.pem
|
||||
server_cert=server_cert.pem
|
||||
server_chain=server_chain.pem
|
||||
|
||||
client_root_priv=client_root_priv.pem
|
||||
client_ca_priv=client_ca_priv.pem
|
||||
client_priv=client_priv.pem
|
||||
|
||||
client_root_cert=client_root_cert.pem
|
||||
client_ca_cert=client_ca_cert.pem
|
||||
client_cert=client_cert.pem
|
||||
client_chain=client_chain.pem
|
||||
|
||||
generate
|
||||
generate alt_
|
||||
|
||||
# cross signed intermediate certificate
|
||||
echo "Generate cross_ca"
|
||||
openssl req \
|
||||
-config "${cfg}" -x509 -section req_server_ca -extensions v3_server_ca \
|
||||
-key "${base}${server_ca_priv}" -CA "${base}${client_root_cert}" \
|
||||
-CAkey "${base}${client_root_priv}" -out cross_ca_cert.pem
|
||||
|
||||
# convert iso key to PEM
|
||||
openssl asn1parse -genconf iso_pkey.asn1 -noout -out -| openssl pkey -inform der -out iso_priv.pem
|
||||
135
tools/EVerest-main/lib/everest/tls/tests/tls_client_main.cpp
Normal file
135
tools/EVerest-main/lib/everest/tls/tests/tls_client_main.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
#include <everest/tls/tls.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
const char* short_opts = "h123r:";
|
||||
bool use_tls1_3{false};
|
||||
bool use_status_request{false};
|
||||
bool use_status_request_v2{false};
|
||||
const char* trust_anchor{nullptr};
|
||||
|
||||
void parse_options(int argc, char** argv) {
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, short_opts)) != -1) {
|
||||
switch (c) {
|
||||
break;
|
||||
case '1':
|
||||
use_status_request = true;
|
||||
break;
|
||||
case '2':
|
||||
use_status_request_v2 = true;
|
||||
break;
|
||||
case '3':
|
||||
use_tls1_3 = true;
|
||||
break;
|
||||
case 'r':
|
||||
trust_anchor = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
std::cout << "Usage: " << argv[0] << " [-1|-2|-3] [-r server_root_cert.pem]" << std::endl;
|
||||
std::cout << " -1 request status_request" << std::endl;
|
||||
std::cout << " -2 request status_request_v2" << std::endl;
|
||||
std::cout << " -3 use TLS 1.3 (TLS 1.2 otherwise)" << std::endl;
|
||||
std::cout << " -r root certificate / trust anchor" << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
parse_options(argc, argv);
|
||||
|
||||
// used test client for extra output
|
||||
tls::Client client;
|
||||
tls::Client::config_t config;
|
||||
|
||||
if (use_tls1_3) {
|
||||
config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
std::cout << "use_tls1_3 true" << std::endl;
|
||||
} else {
|
||||
config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
config.ciphersuites = ""; // No TLS1.3
|
||||
std::cout << "use_tls1_3 false" << std::endl;
|
||||
}
|
||||
|
||||
config.certificate_chain_file = "client_chain.pem";
|
||||
config.private_key_file = "client_priv.pem";
|
||||
config.verify_locations_file = "server_root_cert.pem";
|
||||
config.io_timeout_ms = 500;
|
||||
config.verify_server = false;
|
||||
|
||||
if (trust_anchor != nullptr) {
|
||||
openssl::sha_1_digest_t digest;
|
||||
auto certs = openssl::load_certificates(trust_anchor);
|
||||
for (const auto& ta : certs) {
|
||||
if (openssl::certificate_sha_1(digest, ta.get())) {
|
||||
config.trusted_ca_keys_data.cert_sha1_hash.push_back(digest);
|
||||
}
|
||||
}
|
||||
config.verify_locations_file = trust_anchor;
|
||||
config.trusted_ca_keys = true;
|
||||
config.trusted_ca_keys_data.pre_agreed = true;
|
||||
}
|
||||
|
||||
if (use_status_request) {
|
||||
config.status_request = true;
|
||||
std::cout << "use_status_request true" << std::endl;
|
||||
} else {
|
||||
config.status_request = false;
|
||||
std::cout << "use_status_request false" << std::endl;
|
||||
}
|
||||
|
||||
if (use_status_request_v2) {
|
||||
config.status_request_v2 = true;
|
||||
std::cout << "use_status_request_v2 true" << std::endl;
|
||||
} else {
|
||||
config.status_request_v2 = false;
|
||||
std::cout << "use_status_request_v2 false" << std::endl;
|
||||
}
|
||||
|
||||
client.init(config);
|
||||
|
||||
// localhost works in some cases but not in the CI pipeline ip6-localhost is an option
|
||||
auto connection = client.connect("localhost", "8444", false, 1000);
|
||||
if (connection) {
|
||||
if (connection->connect() == tls::Connection::result_t::success) {
|
||||
const auto* cert = connection->peer_certificate();
|
||||
if (cert != nullptr) {
|
||||
const auto subject = openssl::certificate_subject(cert);
|
||||
if (!subject.empty()) {
|
||||
std::cout << "subject:";
|
||||
for (const auto& itt : subject) {
|
||||
std::cout << " " << itt.first << ":" << itt.second;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
std::array<std::byte, 1024> buffer{};
|
||||
std::size_t readbytes = 0;
|
||||
std::cout << "about to read" << std::endl;
|
||||
const auto res = connection->read(buffer.data(), buffer.size(), readbytes);
|
||||
std::cout << (int)res << std::endl;
|
||||
std::this_thread::sleep_for(1s);
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
908
tools/EVerest-main/lib/everest/tls/tests/tls_connection_test.cpp
Normal file
908
tools/EVerest-main/lib/everest/tls/tests/tls_connection_test.cpp
Normal file
@@ -0,0 +1,908 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "tls_connection_test.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <poll.h>
|
||||
#include <thread>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
using result_t = tls::Connection::result_t;
|
||||
using tls::status_request::ClientStatusRequestV2;
|
||||
|
||||
constexpr auto server_root_CN = "00000000";
|
||||
constexpr auto alt_server_root_CN = "11111111";
|
||||
constexpr auto WAIT_FOR_SERVER_START_TIMEOUT = 50ms;
|
||||
|
||||
void do_poll(std::array<pollfd, 2>& fds, int server_soc, int client_soc) {
|
||||
const std::int16_t events = POLLOUT | POLLIN;
|
||||
fds[0].fd = server_soc;
|
||||
fds[0].events = events;
|
||||
fds[0].revents = 0;
|
||||
fds[1].fd = client_soc;
|
||||
fds[1].events = events;
|
||||
fds[1].revents = 0;
|
||||
auto poll_res = poll(fds.data(), fds.size(), -1);
|
||||
ASSERT_NE(poll_res, -1);
|
||||
}
|
||||
|
||||
tls::Server::OptionalConfig ssl_init() {
|
||||
std::cout << "ssl_init" << std::endl;
|
||||
auto server_config = std::make_unique<tls::Server::config_t>();
|
||||
server_config->cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
server_config->ciphersuites = "";
|
||||
auto& ref = server_config->chains.emplace_back();
|
||||
ref.certificate_chain_file = "server_chain.pem";
|
||||
ref.private_key_file = "server_priv.pem";
|
||||
ref.trust_anchor_file = "server_root_cert.pem";
|
||||
ref.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
|
||||
server_config->host = "127.0.0.1";
|
||||
server_config->service = "8444";
|
||||
server_config->ipv6_only = false;
|
||||
server_config->verify_client = false;
|
||||
server_config->io_timeout_ms = 500;
|
||||
return {{std::move(server_config)}};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// The tests
|
||||
|
||||
TEST_F(TlsTest, StartStop) {
|
||||
// test shouldn't hang
|
||||
start();
|
||||
|
||||
// check TearDown on stopped server is okay
|
||||
server.stop();
|
||||
server.wait_stopped();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, StartConnectDisconnect) {
|
||||
// test shouldn't hang
|
||||
start();
|
||||
connect();
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, NonBlocking) {
|
||||
client_config.io_timeout_ms = 0;
|
||||
server_config.io_timeout_ms = 0;
|
||||
std::timed_mutex mux;
|
||||
mux.lock();
|
||||
|
||||
tls::Server::ConnectionPtr server_connection;
|
||||
tls::Client::ConnectionPtr client_connection;
|
||||
|
||||
auto server_handler_fn = [&server_connection, &mux](tls::Server::ConnectionPtr&& connection) {
|
||||
server_connection = std::move(connection);
|
||||
mux.unlock();
|
||||
};
|
||||
start(server_handler_fn);
|
||||
|
||||
auto client_handler_fn = [&client_connection](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
client_connection = std::move(connection);
|
||||
}
|
||||
};
|
||||
connect(client_handler_fn);
|
||||
|
||||
// FIXME (aw): this is not a proper solution. It would be necessary
|
||||
// to get an exception or result on whether `start()` function has
|
||||
// been successful
|
||||
if (not mux.try_lock_for(WAIT_FOR_SERVER_START_TIMEOUT)) {
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
// check there is a TCP connection
|
||||
ASSERT_TRUE(server_connection);
|
||||
ASSERT_TRUE(client_connection);
|
||||
|
||||
int server_soc = server_connection->socket();
|
||||
int client_soc = client_connection->socket();
|
||||
std::array<pollfd, 2> fds;
|
||||
|
||||
EXPECT_EQ(server_connection->accept(0), result_t::want_read);
|
||||
EXPECT_EQ(client_connection->connect(0), result_t::want_read);
|
||||
|
||||
bool s_complete{false};
|
||||
bool c_complete{false};
|
||||
std::uint32_t s_count{0};
|
||||
std::uint32_t c_count{0};
|
||||
|
||||
while (!s_complete && !c_complete) {
|
||||
do_poll(fds, server_soc, client_soc);
|
||||
if (((fds[0].revents & POLLIN) != 0) || ((fds[0].revents & POLLOUT) != 0)) {
|
||||
s_complete = server_connection->accept(0) == result_t::success;
|
||||
s_count++;
|
||||
}
|
||||
if (((fds[1].revents & POLLIN) != 0) || ((fds[1].revents & POLLOUT) != 0)) {
|
||||
c_complete = client_connection->connect(0) == result_t::success;
|
||||
c_count++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(fds[0].revents & POLLHUP, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLHUP, 0);
|
||||
ASSERT_EQ(fds[0].revents & POLLERR, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLERR, 0);
|
||||
}
|
||||
|
||||
// std::cout << "counts: " << s_count << " " << c_count << std::endl;
|
||||
EXPECT_GT(s_count, 0);
|
||||
EXPECT_GT(c_count, 0);
|
||||
|
||||
const std::byte data{0xf3};
|
||||
|
||||
std::byte s_buf{0};
|
||||
std::size_t s_readbytes{0};
|
||||
std::size_t s_writebytes{0};
|
||||
std::byte c_buf{0};
|
||||
std::size_t c_readbytes{0};
|
||||
std::size_t c_writebytes{0};
|
||||
|
||||
EXPECT_EQ(server_connection->read(&s_buf, sizeof(s_buf), s_readbytes, 0), result_t::want_read);
|
||||
EXPECT_EQ(client_connection->read(&c_buf, sizeof(c_buf), c_readbytes, 0), result_t::want_read);
|
||||
|
||||
EXPECT_EQ(server_connection->write(&data, sizeof(data), s_writebytes, 0), result_t::success);
|
||||
EXPECT_EQ(client_connection->write(&data, sizeof(data), c_writebytes, 0), result_t::success);
|
||||
|
||||
s_complete = false;
|
||||
c_complete = false;
|
||||
s_count = 0;
|
||||
c_count = 0;
|
||||
|
||||
while (!s_complete && !c_complete) {
|
||||
do_poll(fds, server_soc, client_soc);
|
||||
if ((fds[0].revents & POLLIN) != 0) {
|
||||
s_complete = server_connection->read(&s_buf, sizeof(s_buf), s_readbytes, 0) == result_t::success;
|
||||
s_count++;
|
||||
}
|
||||
if ((fds[1].revents & POLLIN) != 0) {
|
||||
c_complete = client_connection->read(&c_buf, sizeof(c_buf), c_readbytes, 0) == result_t::success;
|
||||
c_count++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(fds[0].revents & POLLHUP, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLHUP, 0);
|
||||
ASSERT_EQ(fds[0].revents & POLLERR, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLERR, 0);
|
||||
}
|
||||
|
||||
EXPECT_EQ(s_readbytes, 1);
|
||||
EXPECT_EQ(s_buf, data);
|
||||
EXPECT_EQ(c_readbytes, 1);
|
||||
EXPECT_EQ(c_buf, data);
|
||||
|
||||
// std::cout << "counts: " << s_count << " " << c_count << std::endl;
|
||||
EXPECT_GT(s_count, 0);
|
||||
EXPECT_GT(c_count, 0);
|
||||
|
||||
s_complete = false;
|
||||
c_complete = false;
|
||||
s_count = 0;
|
||||
c_count = 0;
|
||||
|
||||
EXPECT_EQ(server_connection->read(&s_buf, sizeof(s_buf), s_readbytes, 0), result_t::want_read);
|
||||
EXPECT_EQ(client_connection->shutdown(0), result_t::closed); // closed
|
||||
while (!s_complete && !c_complete) {
|
||||
do_poll(fds, server_soc, client_soc);
|
||||
if (((fds[0].revents & POLLIN) != 0) || ((fds[0].revents & POLLOUT) != 0)) {
|
||||
s_complete = server_connection->read(&s_buf, sizeof(s_buf), s_readbytes, 0) == result_t::closed;
|
||||
s_count++;
|
||||
}
|
||||
if (((fds[1].revents & POLLIN) != 0) || ((fds[1].revents & POLLOUT) != 0)) {
|
||||
c_complete = client_connection->shutdown(0) == result_t::success;
|
||||
c_count++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(fds[0].revents & POLLERR, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLERR, 0);
|
||||
}
|
||||
|
||||
// std::cout << "counts: " << s_count << " " << c_count << std::endl;
|
||||
EXPECT_GT(s_count, 0);
|
||||
EXPECT_GT(c_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, NonBlockingClientClose) {
|
||||
std::timed_mutex mux;
|
||||
mux.lock();
|
||||
|
||||
tls::Server::ConnectionPtr server_connection;
|
||||
tls::Client::ConnectionPtr client_connection;
|
||||
|
||||
auto server_handler_fn = [&server_connection, &mux](tls::Server::ConnectionPtr&& connection) {
|
||||
if (connection->accept() == result_t::success) {
|
||||
server_connection = std::move(connection);
|
||||
mux.unlock();
|
||||
}
|
||||
};
|
||||
start(server_handler_fn);
|
||||
|
||||
auto client_handler_fn = [&client_connection](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
client_connection = std::move(connection);
|
||||
}
|
||||
}
|
||||
};
|
||||
connect(client_handler_fn);
|
||||
|
||||
if (not mux.try_lock_for(WAIT_FOR_SERVER_START_TIMEOUT)) {
|
||||
GTEST_SKIP();
|
||||
}
|
||||
// check there is a TCP connection
|
||||
ASSERT_TRUE(server_connection);
|
||||
ASSERT_TRUE(client_connection);
|
||||
|
||||
int server_soc = server_connection->socket();
|
||||
int client_soc = client_connection->socket();
|
||||
std::array<pollfd, 2> fds;
|
||||
|
||||
bool s_complete{false};
|
||||
bool c_complete{false};
|
||||
std::uint32_t s_count{0};
|
||||
std::uint32_t c_count{0};
|
||||
|
||||
std::byte buf{0};
|
||||
std::size_t readbytes{0};
|
||||
|
||||
EXPECT_EQ(server_connection->read(&buf, sizeof(buf), readbytes, 0), result_t::want_read);
|
||||
EXPECT_EQ(client_connection->shutdown(0), result_t::closed); // closed
|
||||
while (!s_complete && !c_complete) {
|
||||
do_poll(fds, server_soc, client_soc);
|
||||
if (((fds[0].revents & POLLIN) != 0) || ((fds[0].revents & POLLOUT) != 0)) {
|
||||
s_complete = server_connection->read(&buf, sizeof(buf), readbytes, 0) == result_t::closed;
|
||||
s_count++;
|
||||
}
|
||||
if (((fds[1].revents & POLLIN) != 0) || ((fds[1].revents & POLLOUT) != 0)) {
|
||||
c_complete = client_connection->shutdown(0) == result_t::closed;
|
||||
c_count++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(fds[0].revents & POLLERR, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLERR, 0);
|
||||
}
|
||||
|
||||
// std::cout << "counts: " << s_count << " " << c_count << std::endl;
|
||||
EXPECT_GT(s_count, 0);
|
||||
EXPECT_GT(c_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, NonBlockingServerClose) {
|
||||
std::timed_mutex mux;
|
||||
mux.lock();
|
||||
|
||||
tls::Server::ConnectionPtr server_connection;
|
||||
tls::Client::ConnectionPtr client_connection;
|
||||
|
||||
auto server_handler_fn = [&server_connection, &mux](tls::Server::ConnectionPtr&& connection) {
|
||||
if (connection->accept() == result_t::success) {
|
||||
server_connection = std::move(connection);
|
||||
mux.unlock();
|
||||
}
|
||||
};
|
||||
start(server_handler_fn);
|
||||
|
||||
auto client_handler_fn = [&client_connection](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
client_connection = std::move(connection);
|
||||
}
|
||||
}
|
||||
};
|
||||
connect(client_handler_fn);
|
||||
|
||||
if (not mux.try_lock_for(WAIT_FOR_SERVER_START_TIMEOUT)) {
|
||||
GTEST_SKIP();
|
||||
}
|
||||
// check there is a TCP connection
|
||||
ASSERT_TRUE(server_connection);
|
||||
ASSERT_TRUE(client_connection);
|
||||
|
||||
int server_soc = server_connection->socket();
|
||||
int client_soc = client_connection->socket();
|
||||
std::array<pollfd, 2> fds;
|
||||
|
||||
bool s_complete{false};
|
||||
bool c_complete{false};
|
||||
std::uint32_t s_count{0};
|
||||
std::uint32_t c_count{0};
|
||||
|
||||
std::byte buf{0};
|
||||
std::size_t readbytes{0};
|
||||
|
||||
EXPECT_EQ(server_connection->shutdown(0), result_t::closed); // closed
|
||||
EXPECT_EQ(client_connection->read(&buf, sizeof(buf), readbytes, 0), result_t::want_read);
|
||||
while (!s_complete && !c_complete) {
|
||||
do_poll(fds, server_soc, client_soc);
|
||||
if (((fds[0].revents & POLLIN) != 0) || ((fds[0].revents & POLLOUT) != 0)) {
|
||||
s_complete = server_connection->shutdown(0) == result_t::closed;
|
||||
s_count++;
|
||||
}
|
||||
if (((fds[1].revents & POLLIN) != 0) || ((fds[1].revents & POLLOUT) != 0)) {
|
||||
c_complete = client_connection->read(&buf, sizeof(buf), readbytes, 0) == result_t::closed;
|
||||
c_count++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(fds[0].revents & POLLERR, 0);
|
||||
ASSERT_EQ(fds[1].revents & POLLERR, 0);
|
||||
}
|
||||
|
||||
// std::cout << "counts: " << s_count << " " << c_count << std::endl;
|
||||
EXPECT_GT(s_count, 0);
|
||||
EXPECT_GT(c_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, ClientReadTimeout) {
|
||||
// test shouldn't hang
|
||||
client_config.io_timeout_ms = 50;
|
||||
|
||||
auto client_handler_fn = [this](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
std::byte buffer{};
|
||||
std::size_t readbytes{0};
|
||||
auto res = connection->read(&buffer, sizeof(buffer), readbytes);
|
||||
EXPECT_EQ(readbytes, 0);
|
||||
EXPECT_EQ(res, result_t::timeout);
|
||||
if (res != result_t::closed) {
|
||||
connection->shutdown();
|
||||
}
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, ClientWriteTimeout) {
|
||||
// test shouldn't hang
|
||||
client_config.io_timeout_ms = 50;
|
||||
|
||||
bool did_timeout{false};
|
||||
std::size_t count{0};
|
||||
std::mutex mux;
|
||||
mux.lock();
|
||||
|
||||
constexpr std::size_t max_bytes = 1024 * 1024 * 1024;
|
||||
|
||||
auto server_handler_fn = [&mux](tls::Server::ConnectionPtr&& con) {
|
||||
if (con->accept() == result_t::success) {
|
||||
mux.lock();
|
||||
con->shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
auto client_handler_fn = [this, &mux, &did_timeout, &count](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
std::array<std::byte, 1024> buffer{};
|
||||
std::size_t writebytes{0};
|
||||
|
||||
bool exit{false};
|
||||
|
||||
while (!exit) {
|
||||
switch (connection->write(buffer.data(), buffer.size(), writebytes)) {
|
||||
case result_t::success:
|
||||
count += writebytes;
|
||||
exit = count > max_bytes;
|
||||
break;
|
||||
case result_t::timeout:
|
||||
// std::cout << "timeout: " << count << " bytes" << std::endl;
|
||||
did_timeout = true;
|
||||
exit = true;
|
||||
break;
|
||||
case result_t::closed:
|
||||
default:
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mux.unlock();
|
||||
std::size_t readbytes = 0;
|
||||
auto res = connection->read(buffer.data(), buffer.size(), readbytes);
|
||||
if (res != result_t::closed) {
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start(server_handler_fn);
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(did_timeout);
|
||||
EXPECT_LE(count, max_bytes);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, ServerReadTimeout) {
|
||||
// test shouldn't hang
|
||||
bool did_timeout{false};
|
||||
std::mutex mux;
|
||||
mux.lock();
|
||||
|
||||
auto server_handler_fn = [&mux, &did_timeout](tls::Server::ConnectionPtr&& con) {
|
||||
if (con->accept() == result_t::success) {
|
||||
std::array<std::byte, 1024> buffer{};
|
||||
std::size_t readbytes = 0;
|
||||
auto res = con->read(buffer.data(), buffer.size(), readbytes);
|
||||
did_timeout = res == result_t::timeout;
|
||||
mux.unlock();
|
||||
con->shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
auto client_handler_fn = [this, &mux](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
mux.lock();
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start(server_handler_fn);
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(did_timeout);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, ServerWriteTimeout) {
|
||||
// test shouldn't hang
|
||||
bool did_timeout{false};
|
||||
std::size_t count{0};
|
||||
std::mutex mux;
|
||||
mux.lock();
|
||||
|
||||
constexpr std::size_t max_bytes = 1024 * 1024 * 1024;
|
||||
|
||||
auto server_handler_fn = [&mux, &did_timeout, &count](tls::Server::ConnectionPtr&& con) {
|
||||
if (con->accept() == result_t::success) {
|
||||
std::array<std::byte, 1024> buffer{};
|
||||
std::size_t writebytes{0};
|
||||
|
||||
bool exit{false};
|
||||
|
||||
while (!exit) {
|
||||
switch (con->write(buffer.data(), buffer.size(), writebytes)) {
|
||||
case result_t::success:
|
||||
count += writebytes;
|
||||
exit = count > max_bytes;
|
||||
break;
|
||||
case result_t::timeout:
|
||||
// std::cout << "timeout: " << count << " bytes" << std::endl;
|
||||
did_timeout = true;
|
||||
exit = true;
|
||||
break;
|
||||
case result_t::closed:
|
||||
default:
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mux.unlock();
|
||||
std::size_t readbytes = 0;
|
||||
auto res = con->read(buffer.data(), buffer.size(), readbytes);
|
||||
if (res != result_t::closed) {
|
||||
con->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto client_handler_fn = [this, &mux](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
}
|
||||
mux.lock();
|
||||
connection->shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
start(server_handler_fn);
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(did_timeout);
|
||||
EXPECT_LE(count, max_bytes);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, delayedConfig) {
|
||||
// partial config
|
||||
server_config.chains.clear();
|
||||
|
||||
start(ssl_init);
|
||||
connect();
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, partialConfig) {
|
||||
// partial config - no support for trusted_ca_keys
|
||||
for (auto& i : server_config.chains) {
|
||||
i.trust_anchor_file = nullptr;
|
||||
}
|
||||
|
||||
start();
|
||||
connect();
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TLS13) {
|
||||
// test using TLS 1.3
|
||||
// there shouldn't be status_request_v2 responses
|
||||
// TLS 1.3 still supports status_request however it is handled differently
|
||||
// (which is handled within the OpenSSL API)
|
||||
server_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
start();
|
||||
connect();
|
||||
// no status requested
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = true;
|
||||
connect();
|
||||
// status_request only
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = false;
|
||||
client_config.status_request_v2 = true;
|
||||
connect();
|
||||
// status_request_v2 only - ignored by server
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = true;
|
||||
connect();
|
||||
// status_request and status_request_v2
|
||||
// status_request_v2 is ignored by server and status_request used
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, NoOcspFiles) {
|
||||
// test using TLS 1.2
|
||||
for (auto& chain : server_config.chains) {
|
||||
chain.ocsp_response_files.clear();
|
||||
}
|
||||
|
||||
start();
|
||||
connect();
|
||||
// no status requested
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = true;
|
||||
connect();
|
||||
// status_request only
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = false;
|
||||
client_config.status_request_v2 = true;
|
||||
connect();
|
||||
// status_request_v2 only
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
|
||||
client_config.status_request = true;
|
||||
connect();
|
||||
// status_request and status_request_v2
|
||||
// status_request_v2 is preferred over status_request
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_set(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, CertVerify) {
|
||||
client_config.verify_locations_file = "alt_server_root_cert.pem";
|
||||
start();
|
||||
connect();
|
||||
EXPECT_FALSE(is_set(flags_t::connected));
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TCKeysNone) {
|
||||
// trusted_ca_keys - none match - default certificate should be used
|
||||
std::map<std::string, std::string> subject;
|
||||
|
||||
client_config.trusted_ca_keys = true;
|
||||
client_config.trusted_ca_keys_data.pre_agreed = true;
|
||||
add_ta_cert_hash("client_root_cert.pem");
|
||||
add_ta_key_hash("client_root_cert.pem");
|
||||
add_ta_name("client_root_cert.pem");
|
||||
|
||||
auto client_handler_fn = [this, &subject](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
subject = openssl::certificate_subject(connection->peer_certificate());
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], server_root_CN);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TCKeysCert) {
|
||||
// trusted_ca_keys - cert hash matches
|
||||
std::map<std::string, std::string> subject;
|
||||
|
||||
client_config.trusted_ca_keys = true;
|
||||
client_config.verify_locations_file = "alt_server_root_cert.pem";
|
||||
add_ta_cert_hash("alt_server_root_cert.pem");
|
||||
|
||||
auto client_handler_fn = [this, &subject](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
subject = openssl::certificate_subject(connection->peer_certificate());
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
|
||||
client_config.trusted_ca_keys_data.x509_name.clear();
|
||||
add_ta_cert_hash("client_root_cert.pem");
|
||||
add_ta_cert_hash("alt_server_root_cert.pem");
|
||||
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TCKeysKey) {
|
||||
// trusted_ca_keys - key hash matches
|
||||
std::map<std::string, std::string> subject;
|
||||
|
||||
client_config.trusted_ca_keys = true;
|
||||
client_config.verify_locations_file = "alt_server_root_cert.pem";
|
||||
add_ta_key_hash("alt_server_root_cert.pem");
|
||||
|
||||
auto client_handler_fn = [this, &subject](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
subject = openssl::certificate_subject(connection->peer_certificate());
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
|
||||
client_config.trusted_ca_keys_data.x509_name.clear();
|
||||
add_ta_key_hash("client_root_cert.pem");
|
||||
add_ta_key_hash("alt_server_root_cert.pem");
|
||||
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TCKeysKeyPem) {
|
||||
// same as TCKeysKey but using a PEM string trust anchor rather than file
|
||||
std::map<std::string, std::string> subject;
|
||||
|
||||
client_config.trusted_ca_keys = true;
|
||||
client_config.verify_locations_file = "alt_server_root_cert.pem";
|
||||
add_ta_key_hash("alt_server_root_cert.pem");
|
||||
|
||||
auto client_handler_fn = [this, &subject](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
subject = openssl::certificate_subject(connection->peer_certificate());
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// convert file to PEM in config
|
||||
for (auto& cfg : server_config.chains) {
|
||||
const auto certs = ::openssl::load_certificates(cfg.trust_anchor_file);
|
||||
std::string pem;
|
||||
for (const auto& cert : certs) {
|
||||
pem += ::openssl::certificate_to_pem(cert.get());
|
||||
}
|
||||
// std::cout << cfg.trust_anchor_file << ": " << certs.size() << std::endl;
|
||||
ASSERT_FALSE(pem.empty());
|
||||
cfg.trust_anchor_file = nullptr;
|
||||
cfg.trust_anchor_pem = pem.c_str();
|
||||
}
|
||||
|
||||
start();
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
|
||||
client_config.trusted_ca_keys_data.x509_name.clear();
|
||||
add_ta_key_hash("client_root_cert.pem");
|
||||
add_ta_key_hash("alt_server_root_cert.pem");
|
||||
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TCKeysName) {
|
||||
// trusted_ca_keys - subject name matches
|
||||
std::map<std::string, std::string> subject;
|
||||
|
||||
client_config.trusted_ca_keys = true;
|
||||
client_config.verify_locations_file = "alt_server_root_cert.pem";
|
||||
add_ta_name("alt_server_root_cert.pem");
|
||||
|
||||
auto client_handler_fn = [this, &subject](tls::Client::ConnectionPtr& connection) {
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
this->set(ClientTest::flags_t::connected);
|
||||
subject = openssl::certificate_subject(connection->peer_certificate());
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
|
||||
client_config.trusted_ca_keys_data.x509_name.clear();
|
||||
add_ta_name("client_root_cert.pem");
|
||||
add_ta_name("alt_server_root_cert.pem");
|
||||
|
||||
connect(client_handler_fn);
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], alt_server_root_CN);
|
||||
}
|
||||
|
||||
// based on an example seen in a WireShark log
|
||||
// (invalid missing the size of trusted_authorities_list)
|
||||
// 01 identifier_type key_sha1_hash 4cd7290bf592d2c1ba90f56e08946d4c8e99dc38 SHA1Hash
|
||||
// 01 identifier_type key_sha1_hash 00fae3900795c888a4d4d7bd9fdffa60418ac19f SHA1Hash
|
||||
int trusted_ca_keys_add_bad(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) {
|
||||
// std::cout << "trusted_ca_keys_add_bad" << std::endl;
|
||||
int result{0};
|
||||
if ((context == SSL_EXT_CLIENT_HELLO) && (object != nullptr)) {
|
||||
constexpr std::uint8_t value[] = {
|
||||
0x01, 0x4c, 0xd7, 0x29, 0x0b, 0xf5, 0x92, 0xd2, 0xc1, 0xba, 0x90, 0xf5, 0x6e, 0x08,
|
||||
0x94, 0x6d, 0x4c, 0x8e, 0x99, 0xdc, 0x38, 0x01, 0x00, 0xfa, 0xe3, 0x90, 0x07, 0x95,
|
||||
0xc8, 0x88, 0xa4, 0xd4, 0xd7, 0xbd, 0x9f, 0xdf, 0xfa, 0x60, 0x41, 0x8a, 0xc1, 0x9f,
|
||||
};
|
||||
openssl::DER der(&value[0], sizeof(value));
|
||||
const auto len = der.size();
|
||||
auto* ptr = openssl::DER::dup(der);
|
||||
if (ptr != nullptr) {
|
||||
*out = ptr;
|
||||
*outlen = len;
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, TCKeysInvalid) {
|
||||
// trusted_ca_keys - incorrectly formatted extension, connect using defaults
|
||||
std::map<std::string, std::string> subject;
|
||||
|
||||
client_config.trusted_ca_keys = true;
|
||||
client_config.verify_locations_file = "server_root_cert.pem";
|
||||
|
||||
auto override = tls::Client::default_overrides();
|
||||
override.trusted_ca_keys_add = &trusted_ca_keys_add_bad;
|
||||
|
||||
start();
|
||||
client.init(client_config, override);
|
||||
client.reset();
|
||||
// localhost works in some cases but not in the CI pipeline for IPv6
|
||||
// use ip6-localhost
|
||||
auto connection = client.connect("127.0.0.1", "8444", false, 1000);
|
||||
if (connection) {
|
||||
if (connection->connect() == result_t::success) {
|
||||
set(ClientTest::flags_t::connected);
|
||||
subject = openssl::certificate_subject(connection->peer_certificate());
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(subject["CN"], server_root_CN);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, Suspend) {
|
||||
using state_t = tls::Server::state_t;
|
||||
start();
|
||||
EXPECT_EQ(server.state(), state_t::running);
|
||||
connect();
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(server.suspend());
|
||||
EXPECT_EQ(server.state(), state_t::init_socket);
|
||||
connect();
|
||||
EXPECT_FALSE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(server.state(), state_t::init_socket);
|
||||
EXPECT_TRUE(server.update(server_config));
|
||||
EXPECT_EQ(server.state(), state_t::init_complete);
|
||||
connect();
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(server.state(), state_t::running);
|
||||
}
|
||||
|
||||
TEST_F(TlsTest, SuspendToRunning) {
|
||||
using state_t = tls::Server::state_t;
|
||||
start();
|
||||
EXPECT_EQ(server.state(), state_t::running);
|
||||
connect();
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(server.suspend());
|
||||
EXPECT_EQ(server.state(), state_t::init_socket);
|
||||
connect();
|
||||
EXPECT_FALSE(is_set(flags_t::connected));
|
||||
EXPECT_EQ(server.state(), state_t::init_socket);
|
||||
EXPECT_TRUE(server.update(server_config));
|
||||
EXPECT_EQ(server.state(), state_t::init_complete);
|
||||
// should switch to running after a timeout
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(tls::c_serve_timeout_ms + 100));
|
||||
EXPECT_EQ(server.state(), state_t::running);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
323
tools/EVerest-main/lib/everest/tls/tests/tls_connection_test.hpp
Normal file
323
tools/EVerest-main/lib/everest/tls/tests/tls_connection_test.hpp
Normal file
@@ -0,0 +1,323 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef TLS_CONNECTION_TEST_HPP_
|
||||
#define TLS_CONNECTION_TEST_HPP_
|
||||
|
||||
#include "extensions/helpers.hpp"
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <everest/tls/tls.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <openssl/ssl.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <everest/util/enum/EnumFlags.hpp>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
using tls::status_request::ClientStatusRequestV2;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// set up code
|
||||
|
||||
struct ClientStatusRequestV2Test : public ClientStatusRequestV2 {
|
||||
enum class flags_t : std::uint8_t {
|
||||
status_request_cb,
|
||||
status_request,
|
||||
status_request_v2,
|
||||
connected,
|
||||
last = connected,
|
||||
};
|
||||
|
||||
everest::lib::util::AtomicEnumFlags<flags_t>& flags;
|
||||
|
||||
ClientStatusRequestV2Test() = delete;
|
||||
explicit ClientStatusRequestV2Test(everest::lib::util::AtomicEnumFlags<flags_t>& flag_ref) : flags(flag_ref) {
|
||||
}
|
||||
|
||||
int status_request_cb(tls::Ssl* ctx) override {
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
int result{1};
|
||||
const unsigned char* response{nullptr};
|
||||
const auto total_length = SSL_get_tlsext_status_ocsp_resp(ctx, &response);
|
||||
flags.set(flags_t::status_request_cb);
|
||||
if ((response != nullptr) && (total_length > 0) && (total_length <= std::numeric_limits<std::int32_t>::max())) {
|
||||
switch (response[0]) {
|
||||
case 0x30: // a status_request response
|
||||
flags.set(flags_t::status_request);
|
||||
if (!print_ocsp_response(stdout, response, total_length)) {
|
||||
result = 0;
|
||||
}
|
||||
break;
|
||||
case 0x00: // a status_request_v2 response
|
||||
{
|
||||
flags.set(flags_t::status_request_v2);
|
||||
|
||||
// multiple responses
|
||||
auto remaining = static_cast<std::int32_t>(total_length);
|
||||
const unsigned char* ptr{response};
|
||||
|
||||
while (remaining >= 3) {
|
||||
const auto len = tls::uint24(ptr);
|
||||
tls::update_position(ptr, remaining, 3);
|
||||
// print_ocsp_response updates tmp_p
|
||||
auto* tmp_p = ptr;
|
||||
const auto res = print_ocsp_response(stdout, tmp_p, len);
|
||||
tls::update_position(ptr, remaining, len);
|
||||
if (!res || (ptr != tmp_p)) {
|
||||
result = 0;
|
||||
remaining = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining != 0) {
|
||||
result = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClientTest : public tls::Client {
|
||||
using flags_t = ClientStatusRequestV2Test::flags_t;
|
||||
everest::lib::util::AtomicEnumFlags<flags_t> flags;
|
||||
|
||||
ClientTest() : tls::Client(std::unique_ptr<ClientStatusRequestV2>(new ClientStatusRequestV2Test(flags))) {
|
||||
}
|
||||
|
||||
void reset() {
|
||||
flags.reset();
|
||||
}
|
||||
};
|
||||
|
||||
void handler(tls::Server::ConnectionPtr&& con) {
|
||||
if (con->accept() == tls::Connection::result_t::success) {
|
||||
std::uint32_t count{0};
|
||||
std::array<std::byte, 1024> buffer{};
|
||||
bool bExit = false;
|
||||
while (!bExit) {
|
||||
std::size_t readbytes = 0;
|
||||
std::size_t writebytes = 0;
|
||||
|
||||
switch (con->read(buffer.data(), buffer.size(), readbytes)) {
|
||||
case tls::Connection::result_t::success:
|
||||
switch (con->write(buffer.data(), readbytes, writebytes)) {
|
||||
case tls::Connection::result_t::success:
|
||||
break;
|
||||
case tls::Connection::result_t::timeout:
|
||||
case tls::Connection::result_t::closed:
|
||||
default:
|
||||
bExit = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case tls::Connection::result_t::timeout:
|
||||
count++;
|
||||
if (count > 10) {
|
||||
bExit = true;
|
||||
}
|
||||
break;
|
||||
case tls::Connection::result_t::closed:
|
||||
default:
|
||||
bExit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
con->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void run_server(tls::Server& server) {
|
||||
server.serve(&handler);
|
||||
}
|
||||
|
||||
class TlsTest : public testing::Test {
|
||||
protected:
|
||||
using flags_t = ClientTest::flags_t;
|
||||
|
||||
tls::Server server;
|
||||
tls::Server::config_t server_config;
|
||||
std::thread server_thread;
|
||||
ClientTest client;
|
||||
tls::Client::config_t client_config;
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
struct sigaction action;
|
||||
std::memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &action, nullptr);
|
||||
tls::Server::configure_signal_handler(SIGUSR1);
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
server_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
// server_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
server_config.ciphersuites = "";
|
||||
auto& ref0 = server_config.chains.emplace_back();
|
||||
ref0.certificate_chain_file = "server_chain.pem";
|
||||
ref0.private_key_file = "server_priv.pem";
|
||||
ref0.trust_anchor_file = "server_root_cert.pem";
|
||||
ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
|
||||
auto& ref1 = server_config.chains.emplace_back();
|
||||
ref1.certificate_chain_file = "alt_server_chain.pem";
|
||||
ref1.private_key_file = "alt_server_priv.pem";
|
||||
ref1.trust_anchor_file = "alt_server_root_cert.pem";
|
||||
ref1.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
|
||||
// server_config.verify_locations_file = "client_root_cert.pem";
|
||||
server_config.host = "127.0.0.1";
|
||||
server_config.service = "8444";
|
||||
server_config.ipv6_only = false;
|
||||
server_config.verify_client = false;
|
||||
server_config.io_timeout_ms = 1000; // no lower than 200ms, valgrind need much higher
|
||||
|
||||
client_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
// client_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
// client_config.certificate_chain_file = "client_chain.pem";
|
||||
// client_config.private_key_file = "client_priv.pem";
|
||||
client_config.verify_locations_file = "server_root_cert.pem";
|
||||
client_config.io_timeout_ms = 1000;
|
||||
client_config.verify_server = true;
|
||||
client_config.status_request = false;
|
||||
client_config.status_request_v2 = false;
|
||||
client.reset();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
server.stop();
|
||||
server.wait_stopped();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void start(const tls::Server::ConfigurationCallback& init_ssl = nullptr) {
|
||||
using state_t = tls::Server::state_t;
|
||||
const auto res = server.init(server_config, init_ssl);
|
||||
if ((res == state_t::init_complete) || (res == state_t::init_socket)) {
|
||||
server_thread = std::thread(&run_server, std::ref(server));
|
||||
server.wait_running();
|
||||
}
|
||||
}
|
||||
|
||||
void start(const std::function<void(tls::Server::ConnectionPtr&& con)>& handler) {
|
||||
using state_t = tls::Server::state_t;
|
||||
const auto res = server.init(server_config, nullptr);
|
||||
if ((res == state_t::init_complete) || (res == state_t::init_socket)) {
|
||||
server_thread = std::thread([this, handler]() { this->server.serve(handler); });
|
||||
server.wait_running();
|
||||
}
|
||||
}
|
||||
|
||||
void connect(const std::function<void(tls::Client::ConnectionPtr& con)>& handler = nullptr) {
|
||||
client.init(client_config);
|
||||
client.reset();
|
||||
// localhost works in some cases but not in the CI pipeline for IPv6
|
||||
// use ip6-localhost
|
||||
auto connection = client.connect("127.0.0.1", "8444", false, 1000);
|
||||
if (handler == nullptr) {
|
||||
if (connection) {
|
||||
if (connection->connect() == tls::Connection::result_t::success) {
|
||||
set(ClientTest::flags_t::connected);
|
||||
connection->shutdown();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handler(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void set(flags_t flag) {
|
||||
client.flags.set(flag);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_set(flags_t flag) const {
|
||||
return client.flags.is_set(flag);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_reset(flags_t flag) const {
|
||||
return client.flags.is_reset(flag);
|
||||
}
|
||||
|
||||
void add_ta_cert_hash(const char* filename) {
|
||||
openssl::sha_1_digest_t digest;
|
||||
auto certs = openssl::load_certificates(filename);
|
||||
for (const auto& ta : certs) {
|
||||
if (openssl::certificate_sha_1(digest, ta.get())) {
|
||||
client_config.trusted_ca_keys_data.cert_sha1_hash.push_back(digest);
|
||||
}
|
||||
}
|
||||
}
|
||||
void add_ta_key_hash(const char* filename) {
|
||||
openssl::sha_1_digest_t digest;
|
||||
auto certs = openssl::load_certificates(filename);
|
||||
for (const auto& ta : certs) {
|
||||
if (openssl::certificate_subject_public_key_sha_1(digest, ta.get())) {
|
||||
client_config.trusted_ca_keys_data.key_sha1_hash.push_back(digest);
|
||||
}
|
||||
}
|
||||
}
|
||||
void add_ta_name(const char* filename) {
|
||||
auto certs = openssl::load_certificates(filename);
|
||||
for (const auto& ta : certs) {
|
||||
auto res = openssl::certificate_subject_der(ta.get());
|
||||
if (res) {
|
||||
client_config.trusted_ca_keys_data.x509_name.emplace_back(std::move(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TlsTestTpm : public TlsTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
server_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
// server_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
server_config.ciphersuites = "";
|
||||
auto& ref0 = server_config.chains.emplace_back();
|
||||
ref0.certificate_chain_file = "tpm_pki/server_chain.pem";
|
||||
ref0.private_key_file = "tpm_pki/server_priv.pem";
|
||||
ref0.trust_anchor_file = "tpm_pki/server_root_cert.pem";
|
||||
ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
|
||||
// auto& ref1 = server_config.chains.emplace_back();
|
||||
// ref1.certificate_chain_file = "alt_server_chain.pem";
|
||||
// ref1.private_key_file = "alt_server_priv.pem";
|
||||
// ref1.trust_anchor_file = "alt_server_root_cert.pem";
|
||||
// ref1.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
|
||||
server_config.host = "127.0.0.1";
|
||||
server_config.service = "8444";
|
||||
server_config.ipv6_only = false;
|
||||
server_config.verify_client = false;
|
||||
server_config.io_timeout_ms = 1000; // no lower than 200ms, valgrind need much higher
|
||||
|
||||
client_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
// client_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
// client_config.certificate_chain_file = "client_chain.pem";
|
||||
// client_config.private_key_file = "client_priv.pem";
|
||||
client_config.verify_locations_file = "tpm_pki/server_root_cert.pem";
|
||||
client_config.io_timeout_ms = 1000;
|
||||
client_config.verify_server = true;
|
||||
client_config.status_request = false;
|
||||
client_config.status_request_v2 = false;
|
||||
client.reset();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif // TLS_CONNECTION_TEST_HPP_
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "tls_connection_test.hpp"
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <poll.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
using result_t = tls::Connection::result_t;
|
||||
using tls::status_request::ClientStatusRequestV2;
|
||||
|
||||
constexpr auto server_root_CN = "00000000";
|
||||
constexpr auto alt_server_root_CN = "11111111";
|
||||
constexpr auto WAIT_FOR_SERVER_START_TIMEOUT = 50ms;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// The tests
|
||||
|
||||
TEST_F(TlsTestTpm, StartConnectDisconnect) {
|
||||
start();
|
||||
connect();
|
||||
// no status requested
|
||||
EXPECT_TRUE(is_set(flags_t::connected));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request));
|
||||
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
159
tools/EVerest-main/lib/everest/tls/tests/tls_main.cpp
Normal file
159
tools/EVerest-main/lib/everest/tls/tests/tls_main.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
* testing options
|
||||
* openssl s_client -connect localhost:8444 -verify 2 -CAfile server_root_cert.pem -cert client_cert.pem -cert_chain
|
||||
* client_chain.pem -key client_priv.pem -verify_return_error -verify_hostname evse.pionix.de -status
|
||||
*/
|
||||
|
||||
#include <everest/tls/tls.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
const char* short_opts = "ch36t";
|
||||
bool disable_tls1_3{false};
|
||||
bool enable_tpm{false};
|
||||
bool ipv6_only{false};
|
||||
bool verify_client{false};
|
||||
|
||||
void parse_options(int argc, char** argv) {
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, short_opts)) != -1) {
|
||||
switch (c) {
|
||||
break;
|
||||
case 'c':
|
||||
verify_client = true;
|
||||
break;
|
||||
case '3':
|
||||
disable_tls1_3 = true;
|
||||
break;
|
||||
case '6':
|
||||
ipv6_only = true;
|
||||
break;
|
||||
case 't':
|
||||
enable_tpm = true;
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
std::cout << "Usage: " << argv[0] << " [-c|-3|-6]" << std::endl;
|
||||
std::cout << " -c verify client" << std::endl;
|
||||
std::cout << " -3 disable TLS 1.3" << std::endl;
|
||||
std::cout << " -6 IPv6 only" << std::endl;
|
||||
std::cout << " -t enable TPM" << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_connection(tls::Server::ConnectionPtr&& con) {
|
||||
std::cout << "Connection" << std::endl;
|
||||
if (con->accept() == tls::Connection::result_t::success) {
|
||||
std::uint32_t count{0};
|
||||
std::array<std::byte, 1024> buffer{};
|
||||
bool bExit = false;
|
||||
while (!bExit) {
|
||||
std::size_t readbytes = 0;
|
||||
std::size_t writebytes = 0;
|
||||
|
||||
switch (con->read(buffer.data(), buffer.size(), readbytes)) {
|
||||
case tls::Connection::result_t::success:
|
||||
switch (con->write(buffer.data(), readbytes, writebytes)) {
|
||||
case tls::Connection::result_t::success:
|
||||
break;
|
||||
case tls::Connection::result_t::timeout:
|
||||
case tls::Connection::result_t::closed:
|
||||
default:
|
||||
bExit = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case tls::Connection::result_t::timeout:
|
||||
count++;
|
||||
if (count > 10) {
|
||||
bExit = true;
|
||||
}
|
||||
break;
|
||||
case tls::Connection::result_t::closed:
|
||||
default:
|
||||
bExit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
con->shutdown();
|
||||
}
|
||||
std::cout << "Connection closed" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
parse_options(argc, argv);
|
||||
|
||||
tls::Server::configure_signal_handler(SIGUSR1);
|
||||
tls::Server server;
|
||||
tls::Server::config_t config;
|
||||
|
||||
// 15118 required suites, ECDH-ECDSA-AES128-SHA256 not supported by OpenSSL
|
||||
// config.cipher_list = "ECDHE-ECDSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256";
|
||||
|
||||
config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
if (disable_tls1_3) {
|
||||
config.ciphersuites = "";
|
||||
} else {
|
||||
config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
|
||||
}
|
||||
|
||||
auto& ref0 = config.chains.emplace_back();
|
||||
if (enable_tpm) {
|
||||
ref0.certificate_chain_file = "tpm_pki/server_chain.pem";
|
||||
ref0.private_key_file = "tpm_pki/server_priv.pem";
|
||||
ref0.trust_anchor_file = "tpm_pki/server_root_cert.pem";
|
||||
} else {
|
||||
ref0.certificate_chain_file = "server_chain.pem";
|
||||
ref0.private_key_file = "server_priv.pem";
|
||||
ref0.trust_anchor_file = "server_root_cert.pem";
|
||||
}
|
||||
ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
|
||||
auto& ref1 = config.chains.emplace_back();
|
||||
ref1.certificate_chain_file = "alt_server_chain.pem";
|
||||
ref1.private_key_file = "alt_server_priv.pem";
|
||||
ref1.trust_anchor_file = "alt_server_root_cert.pem";
|
||||
config.verify_locations_file = "client_root_cert.pem";
|
||||
|
||||
config.service = "8444";
|
||||
config.ipv6_only = ipv6_only;
|
||||
config.verify_client = verify_client;
|
||||
config.io_timeout_ms = 10000;
|
||||
|
||||
std::thread stop([&server]() {
|
||||
std::this_thread::sleep_for(30s);
|
||||
std::cout << "stopping ..." << std::endl;
|
||||
server.stop();
|
||||
});
|
||||
|
||||
server.init(config, nullptr);
|
||||
server.wait_stopped();
|
||||
|
||||
server.serve(&handle_connection);
|
||||
server.wait_stopped();
|
||||
|
||||
stop.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
452
tools/EVerest-main/lib/everest/tls/tests/tls_test.cpp
Normal file
452
tools/EVerest-main/lib/everest/tls/tests/tls_test.cpp
Normal file
@@ -0,0 +1,452 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2024 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "extensions/trusted_ca_keys.hpp"
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
#include <everest/tls/tls.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <iterator>
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
std::string to_string(const std::uint8_t* const ptr, const std::size_t len) {
|
||||
std::stringstream string_stream;
|
||||
string_stream << std::hex;
|
||||
|
||||
for (int idx = 0; idx < len; ++idx)
|
||||
string_stream << std::setw(2) << std::setfill('0') << (int)ptr[idx];
|
||||
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
std::string to_string(const tls::OcspCache::digest_t& digest) {
|
||||
return to_string(reinterpret_cast<const std::uint8_t*>(&digest), sizeof(digest));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(strdup, usage) {
|
||||
// auto* r1 = strdup(nullptr); need to ensure non-nullptr
|
||||
auto* r2 = strdup("");
|
||||
auto* r3 = strdup("hello");
|
||||
// free(r1);
|
||||
free(r2);
|
||||
free(r3);
|
||||
free(nullptr);
|
||||
}
|
||||
|
||||
TEST(string, use) {
|
||||
// was hoping to use std::string for config, but ...
|
||||
std::string empty;
|
||||
std::string space{""};
|
||||
std::string value{"something"};
|
||||
|
||||
EXPECT_TRUE(empty.empty());
|
||||
// EXPECT_FALSE(space.empty()); was hoping it would be true
|
||||
EXPECT_FALSE(value.empty());
|
||||
|
||||
// EXPECT_EQ(empty.c_str(), nullptr); was hoping it would be nullptr
|
||||
EXPECT_NE(space.c_str(), nullptr);
|
||||
EXPECT_NE(value.c_str(), nullptr);
|
||||
}
|
||||
|
||||
TEST(ConfigItem, test) {
|
||||
// tests reduced with new ConfigStore implementation
|
||||
tls::ConfigItem i1;
|
||||
tls::ConfigItem i2{nullptr};
|
||||
tls::ConfigItem i3{"Hello"};
|
||||
tls::ConfigItem i4 = nullptr;
|
||||
tls::ConfigItem i5(nullptr);
|
||||
tls::ConfigItem i6("Hello");
|
||||
|
||||
EXPECT_EQ(i1, nullptr);
|
||||
EXPECT_EQ(i4, nullptr);
|
||||
EXPECT_EQ(i5, nullptr);
|
||||
|
||||
EXPECT_EQ(i2, i5);
|
||||
EXPECT_STREQ(i3, i6);
|
||||
|
||||
EXPECT_EQ(i1, i2);
|
||||
EXPECT_NE(i1, i3);
|
||||
EXPECT_EQ(i1, i5);
|
||||
EXPECT_NE(i1, i6);
|
||||
|
||||
tls::ConfigItem j2{""};
|
||||
|
||||
auto j1(std::move(i3));
|
||||
j2 = std::move(i6);
|
||||
EXPECT_STREQ(i6, i3);
|
||||
EXPECT_STREQ(j1, j2);
|
||||
EXPECT_STREQ(j1, "Hello");
|
||||
EXPECT_NE(j1, i6);
|
||||
|
||||
EXPECT_NE(j1, nullptr);
|
||||
EXPECT_NE(j2, nullptr);
|
||||
|
||||
// EXPECT_EQ(i3, nullptr);
|
||||
// EXPECT_EQ(i6, nullptr);
|
||||
// EXPECT_EQ(i6, i3);
|
||||
|
||||
std::vector<tls::ConfigItem> j3 = {"one", "two", nullptr};
|
||||
EXPECT_STREQ(j3[0], "one");
|
||||
EXPECT_STREQ(j3[1], "two");
|
||||
EXPECT_EQ(j3[2], nullptr);
|
||||
|
||||
const char* p = j1;
|
||||
EXPECT_STREQ(p, "Hello");
|
||||
j1 = "Goodbye";
|
||||
EXPECT_STRNE(j1, "Hello");
|
||||
j1 = j2;
|
||||
EXPECT_STREQ(j1, j2);
|
||||
}
|
||||
|
||||
TEST(ConfigItem, testB) {
|
||||
tls::ConfigItem i1;
|
||||
tls::ConfigItem i2{nullptr};
|
||||
tls::ConfigItem i3{""};
|
||||
tls::ConfigItem i4{"Hello"};
|
||||
|
||||
EXPECT_EQ(i1, nullptr);
|
||||
EXPECT_EQ(i2, nullptr);
|
||||
EXPECT_STREQ(i3, "");
|
||||
EXPECT_STREQ(i4, "Hello");
|
||||
EXPECT_STREQ("Hello", i4);
|
||||
}
|
||||
|
||||
using namespace tls::trusted_ca_keys;
|
||||
using namespace openssl;
|
||||
|
||||
TEST(OcspCache, initEmpty) {
|
||||
tls::OcspCache cache;
|
||||
tls::OcspCache::digest_t digest{};
|
||||
auto res = cache.lookup(digest);
|
||||
EXPECT_EQ(res.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST(OcspCache, init) {
|
||||
tls::OcspCache cache;
|
||||
|
||||
auto chain = openssl::load_certificates("client_chain.pem");
|
||||
std::vector<tls::OcspCache::ocsp_entry_t> entries;
|
||||
|
||||
tls::OcspCache::digest_t digest{};
|
||||
for (const auto& cert : chain) {
|
||||
ASSERT_TRUE(tls::OcspCache::digest(digest, cert.get()));
|
||||
// std::cout << "digest: " << to_string(digest) << std::endl;
|
||||
entries.emplace_back(digest, "ocsp_response.der");
|
||||
}
|
||||
|
||||
EXPECT_TRUE(cache.load(entries));
|
||||
// std::cout << "digest: " << to_string(digest) << std::endl;
|
||||
auto res = cache.lookup(digest);
|
||||
EXPECT_NE(res.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, parseAudi) {
|
||||
|
||||
/*
|
||||
* Audi
|
||||
* 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
|
||||
*/
|
||||
|
||||
using trusted_authority = tls::trusted_ca_keys::trusted_authority;
|
||||
|
||||
std::uint8_t extension[] = {
|
||||
0x00, 0x69, 0x03, 0xd8, 0x36, 0x7e, 0x86, 0x1f, 0x58, 0x07, 0xf8, 0x14, 0x1f, 0xea, 0x57, 0x2d, 0x67, 0x6d,
|
||||
0xbf, 0x58, 0xbb, 0x5f, 0x7c, 0x03, 0xb4, 0x91, 0xdd, 0xd0, 0x8f, 0xaf, 0xe7, 0x2d, 0x9f, 0x6f, 0x9b, 0xaf,
|
||||
0xc6, 0x8e, 0xb0, 0x4d, 0xa8, 0x4c, 0xc0, 0x9a, 0x03, 0x30, 0xaa, 0xaa, 0xb2, 0x5b, 0x1c, 0xc8, 0xa0, 0x9a,
|
||||
0x7b, 0x32, 0x65, 0x2c, 0x33, 0xcc, 0x5a, 0x97, 0x3c, 0x13, 0xf3, 0x03, 0x70, 0x0b, 0xf7, 0x8a, 0xd5, 0x8e,
|
||||
0x08, 0x19, 0xda, 0xc6, 0xfc, 0xae, 0xad, 0x5e, 0xd2, 0x0f, 0x7b, 0xb0, 0x55, 0x4f, 0x03, 0x8c, 0x82, 0x1f,
|
||||
0x41, 0x60, 0x4e, 0xd4, 0xc3, 0x43, 0x1c, 0xf6, 0xd1, 0x9f, 0x2a, 0xe1, 0x07, 0xcf, 0x1f, 0x50, 0xe1,
|
||||
};
|
||||
|
||||
trusted_authority ext{&extension[0], sizeof(extension)};
|
||||
auto res = tls::trusted_ca_keys::convert(ext);
|
||||
EXPECT_EQ(res.cert_sha1_hash.size(), 5);
|
||||
EXPECT_EQ(res.key_sha1_hash.size(), 0);
|
||||
EXPECT_EQ(res.x509_name.size(), 0);
|
||||
EXPECT_FALSE(res.pre_agreed);
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, parseBuzz) {
|
||||
|
||||
/*
|
||||
* Buzz
|
||||
* 002a trusted_authorities_list length
|
||||
* 03 identifier_type cert_sha1_hash d8367e861f5807f8141fea572d676dbf58bb5f7c SHA1Hash
|
||||
* 03 identifier_type cert_sha1_hash 8c821f41604ed4c3431cf6d19f2ae107cf1f50e1 SHA1Hash
|
||||
*/
|
||||
|
||||
using trusted_authority = tls::trusted_ca_keys::trusted_authority;
|
||||
|
||||
std::uint8_t extension[] = {
|
||||
0x00, 0x2a, 0x03, 0xd8, 0x36, 0x7e, 0x86, 0x1f, 0x58, 0x07, 0xf8, 0x14, 0x1f, 0xea, 0x57,
|
||||
0x2d, 0x67, 0x6d, 0xbf, 0x58, 0xbb, 0x5f, 0x7c, 0x03, 0x8c, 0x82, 0x1f, 0x41, 0x60, 0x4e,
|
||||
0xd4, 0xc3, 0x43, 0x1c, 0xf6, 0xd1, 0x9f, 0x2a, 0xe1, 0x07, 0xcf, 0x1f, 0x50, 0xe1,
|
||||
};
|
||||
|
||||
trusted_authority ext{&extension[0], sizeof(extension)};
|
||||
auto res = tls::trusted_ca_keys::convert(ext);
|
||||
EXPECT_EQ(res.cert_sha1_hash.size(), 2);
|
||||
EXPECT_EQ(res.key_sha1_hash.size(), 0);
|
||||
EXPECT_EQ(res.x509_name.size(), 0);
|
||||
EXPECT_FALSE(res.pre_agreed);
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, parseIoniq6) {
|
||||
|
||||
/*
|
||||
* Ioniq 6 (invalid missing the size of trusted_authorities_list)
|
||||
* 01 identifier_type key_sha1_hash 4cd7290bf592d2c1ba90f56e08946d4c8e99dc38 SHA1Hash
|
||||
* 01 identifier_type key_sha1_hash 00fae3900795c888a4d4d7bd9fdffa60418ac19f SHA1Hash
|
||||
*/
|
||||
|
||||
using trusted_authority = tls::trusted_ca_keys::trusted_authority;
|
||||
|
||||
std::uint8_t extension[] = {
|
||||
0x00, 0x2a, 0x01, 0x4c, 0xd7, 0x29, 0x0b, 0xf5, 0x92, 0xd2, 0xc1, 0xba, 0x90, 0xf5, 0x6e,
|
||||
0x08, 0x94, 0x6d, 0x4c, 0x8e, 0x99, 0xdc, 0x38, 0x01, 0x00, 0xfa, 0xe3, 0x90, 0x07, 0x95,
|
||||
0xc8, 0x88, 0xa4, 0xd4, 0xd7, 0xbd, 0x9f, 0xdf, 0xfa, 0x60, 0x41, 0x8a, 0xc1, 0x9f,
|
||||
};
|
||||
|
||||
trusted_authority ext{&extension[2], sizeof(extension) - 2};
|
||||
auto res = tls::trusted_ca_keys::convert(ext);
|
||||
EXPECT_EQ(res.cert_sha1_hash.size(), 0);
|
||||
EXPECT_EQ(res.key_sha1_hash.size(), 0);
|
||||
EXPECT_EQ(res.x509_name.size(), 0);
|
||||
EXPECT_FALSE(res.pre_agreed);
|
||||
|
||||
ext = trusted_authority{&extension[0], sizeof(extension)};
|
||||
res = tls::trusted_ca_keys::convert(ext);
|
||||
EXPECT_EQ(res.cert_sha1_hash.size(), 0);
|
||||
EXPECT_EQ(res.key_sha1_hash.size(), 2);
|
||||
EXPECT_EQ(res.x509_name.size(), 0);
|
||||
EXPECT_FALSE(res.pre_agreed);
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, generateBuzz) {
|
||||
|
||||
/*
|
||||
* Buzz
|
||||
* 002a trusted_authorities_list length
|
||||
* 03 identifier_type cert_sha1_hash d8367e861f5807f8141fea572d676dbf58bb5f7c SHA1Hash
|
||||
* 03 identifier_type cert_sha1_hash 8c821f41604ed4c3431cf6d19f2ae107cf1f50e1 SHA1Hash
|
||||
*/
|
||||
|
||||
using trusted_ca_keys_t = tls::trusted_ca_keys::trusted_ca_keys_t;
|
||||
|
||||
openssl::sha_1_digest_t hash1 = {
|
||||
0xd8, 0x36, 0x7e, 0x86, 0x1f, 0x58, 0x07, 0xf8, 0x14, 0x1f,
|
||||
0xea, 0x57, 0x2d, 0x67, 0x6d, 0xbf, 0x58, 0xbb, 0x5f, 0x7c,
|
||||
};
|
||||
openssl::sha_1_digest_t hash2 = {
|
||||
0x8c, 0x82, 0x1f, 0x41, 0x60, 0x4e, 0xd4, 0xc3, 0x43, 0x1c,
|
||||
0xf6, 0xd1, 0x9f, 0x2a, 0xe1, 0x07, 0xcf, 0x1f, 0x50, 0xe1,
|
||||
};
|
||||
|
||||
std::uint8_t extension[] = {
|
||||
0x00, 0x2a, 0x03, 0xd8, 0x36, 0x7e, 0x86, 0x1f, 0x58, 0x07, 0xf8, 0x14, 0x1f, 0xea, 0x57,
|
||||
0x2d, 0x67, 0x6d, 0xbf, 0x58, 0xbb, 0x5f, 0x7c, 0x03, 0x8c, 0x82, 0x1f, 0x41, 0x60, 0x4e,
|
||||
0xd4, 0xc3, 0x43, 0x1c, 0xf6, 0xd1, 0x9f, 0x2a, 0xe1, 0x07, 0xcf, 0x1f, 0x50, 0xe1,
|
||||
};
|
||||
|
||||
trusted_ca_keys_t tck;
|
||||
tck.cert_sha1_hash.push_back(hash1);
|
||||
tck.cert_sha1_hash.push_back(hash2);
|
||||
|
||||
auto res = tls::trusted_ca_keys::convert(tck);
|
||||
|
||||
EXPECT_EQ(res.size(), sizeof(extension));
|
||||
EXPECT_EQ(std::memcmp(res.get(), &extension, sizeof(extension)), 0);
|
||||
// std::cout << "A: " << to_string(std::get<TrustedAuthority_ptr>(res).get(), sizeof(extension)) << std::endl;
|
||||
// std::cout << "B: " << to_string(&extension[0], sizeof(extension)) << std::endl;
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, CertChain) {
|
||||
// match server certificate to trust anchors
|
||||
auto server_root = openssl::load_certificates("server_root_cert.pem");
|
||||
auto server_ca = openssl::load_certificates("server_ca_cert.pem");
|
||||
auto server = openssl::load_certificates("server_cert.pem");
|
||||
auto alt_server_root = openssl::load_certificates("alt_server_root_cert.pem");
|
||||
auto alt_server_ca = openssl::load_certificates("alt_server_ca_cert.pem");
|
||||
auto alt_server = openssl::load_certificates("alt_server_cert.pem");
|
||||
|
||||
openssl::certificate_list chain;
|
||||
std::move(server_ca.begin(), server_ca.end(), std::back_inserter(chain));
|
||||
std::move(alt_server_ca.begin(), alt_server_ca.end(), std::back_inserter(chain));
|
||||
|
||||
ASSERT_EQ(server_root.size(), 1);
|
||||
ASSERT_EQ(alt_server_root.size(), 1);
|
||||
|
||||
EXPECT_EQ(openssl::verify_certificate(server[0].get(), server_root, chain), openssl::verify_result_t::Verified);
|
||||
EXPECT_EQ(openssl::verify_certificate(alt_server[0].get(), alt_server_root, chain),
|
||||
openssl::verify_result_t::Verified);
|
||||
|
||||
EXPECT_EQ(openssl::verify_certificate(server[0].get(), alt_server_root, chain),
|
||||
openssl::verify_result_t::CertificateNotAllowed);
|
||||
EXPECT_EQ(openssl::verify_certificate(alt_server[0].get(), server_root, chain),
|
||||
openssl::verify_result_t::CertificateNotAllowed);
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, matchNone) {
|
||||
|
||||
trusted_ca_keys_t keys;
|
||||
chain_t chain;
|
||||
|
||||
EXPECT_FALSE(match(keys, chain));
|
||||
|
||||
auto root = load_certificates("server_root_cert.pem");
|
||||
const auto* root_cert = root[0].get();
|
||||
|
||||
sha_1_digest_t digest;
|
||||
|
||||
keys.x509_name.emplace_back(certificate_subject_der(root_cert));
|
||||
EXPECT_TRUE(certificate_sha_1(digest, root_cert));
|
||||
keys.cert_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(certificate_subject_public_key_sha_1(digest, root_cert));
|
||||
keys.key_sha1_hash.push_back(digest);
|
||||
|
||||
EXPECT_FALSE(match(keys, chain));
|
||||
|
||||
auto alt_root = load_certificates("client_root_cert.pem");
|
||||
chain.chain.trust_anchors = std::move(alt_root);
|
||||
EXPECT_FALSE(match(keys, chain));
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, matchName) {
|
||||
|
||||
trusted_ca_keys_t keys;
|
||||
chain_t chain;
|
||||
|
||||
auto root = load_certificates("server_root_cert.pem");
|
||||
chain.chain.trust_anchors = std::move(root);
|
||||
|
||||
const auto* root_cert = chain.chain.trust_anchors[0].get();
|
||||
|
||||
keys.x509_name.emplace_back(certificate_subject_der(root_cert));
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
|
||||
sha_1_digest_t digest;
|
||||
|
||||
EXPECT_TRUE(certificate_sha_1(digest, root_cert));
|
||||
keys.cert_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
EXPECT_TRUE(certificate_subject_public_key_sha_1(digest, root_cert));
|
||||
keys.key_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, matchCertHash) {
|
||||
|
||||
trusted_ca_keys_t keys;
|
||||
chain_t chain;
|
||||
|
||||
auto root = load_certificates("server_root_cert.pem");
|
||||
chain.chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
root = load_certificates("client_root_cert.pem");
|
||||
chain.chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
ASSERT_EQ(chain.chain.trust_anchors.size(), 2);
|
||||
|
||||
const auto* server_root_cert = chain.chain.trust_anchors[0].get();
|
||||
const auto* client_root_cert = chain.chain.trust_anchors[1].get();
|
||||
|
||||
sha_1_digest_t digest;
|
||||
EXPECT_TRUE(certificate_sha_1(digest, client_root_cert));
|
||||
keys.cert_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
|
||||
EXPECT_TRUE(certificate_sha_1(digest, server_root_cert));
|
||||
keys.cert_sha1_hash.clear();
|
||||
keys.cert_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, matchKeyHash) {
|
||||
|
||||
trusted_ca_keys_t keys;
|
||||
chain_t chain;
|
||||
|
||||
auto root = load_certificates("server_root_cert.pem");
|
||||
chain.chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
root = load_certificates("client_root_cert.pem");
|
||||
chain.chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
ASSERT_EQ(chain.chain.trust_anchors.size(), 2);
|
||||
|
||||
const auto* server_root_cert = chain.chain.trust_anchors[0].get();
|
||||
const auto* client_root_cert = chain.chain.trust_anchors[1].get();
|
||||
|
||||
sha_1_digest_t digest;
|
||||
EXPECT_TRUE(certificate_subject_public_key_sha_1(digest, client_root_cert));
|
||||
keys.key_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
|
||||
EXPECT_TRUE(certificate_subject_public_key_sha_1(digest, server_root_cert));
|
||||
keys.key_sha1_hash.clear();
|
||||
keys.key_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(match(keys, chain));
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, selectNone) {
|
||||
|
||||
trusted_ca_keys_t keys;
|
||||
chain_list chains;
|
||||
EXPECT_EQ(select(keys, chains), nullptr);
|
||||
|
||||
chains.emplace_back();
|
||||
auto root = load_certificates("server_root_cert.pem");
|
||||
chains[0].chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
EXPECT_EQ(select(keys, chains), nullptr);
|
||||
|
||||
chains.emplace_back();
|
||||
root = load_certificates("alt_server_root_cert.pem");
|
||||
chains[1].chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
EXPECT_EQ(select(keys, chains), nullptr);
|
||||
|
||||
sha_1_digest_t digest;
|
||||
root = load_certificates("client_root_cert.pem");
|
||||
auto* client_root_cert = root[0].get();
|
||||
EXPECT_TRUE(certificate_sha_1(digest, client_root_cert));
|
||||
keys.cert_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(certificate_subject_public_key_sha_1(digest, client_root_cert));
|
||||
keys.key_sha1_hash.push_back(digest);
|
||||
}
|
||||
|
||||
TEST(TrustedCaKeys, select) {
|
||||
|
||||
trusted_ca_keys_t keys;
|
||||
chain_list chains;
|
||||
EXPECT_EQ(select(keys, chains), nullptr);
|
||||
|
||||
chains.emplace_back();
|
||||
auto root = load_certificates("server_root_cert.pem");
|
||||
chains[0].chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
EXPECT_EQ(select(keys, chains), nullptr);
|
||||
|
||||
chains.emplace_back();
|
||||
root = load_certificates("alt_server_root_cert.pem");
|
||||
chains[1].chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
EXPECT_EQ(select(keys, chains), nullptr);
|
||||
|
||||
sha_1_digest_t digest;
|
||||
root = load_certificates("client_root_cert.pem");
|
||||
auto* client_root_cert = root[0].get();
|
||||
EXPECT_TRUE(certificate_sha_1(digest, client_root_cert));
|
||||
keys.cert_sha1_hash.push_back(digest);
|
||||
EXPECT_TRUE(certificate_subject_public_key_sha_1(digest, client_root_cert));
|
||||
keys.key_sha1_hash.push_back(digest);
|
||||
|
||||
chains.emplace_back();
|
||||
root = load_certificates("client_root_cert.pem");
|
||||
chains[2].chain.trust_anchors.emplace_back(std::move(root[0]));
|
||||
auto result = select(keys, chains);
|
||||
EXPECT_NE(result, nullptr);
|
||||
EXPECT_EQ(result, &chains[2]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
106
tools/EVerest-main/lib/everest/tls/tests/trusted_ca_keys_decode.py
Executable file
106
tools/EVerest-main/lib/everest/tls/tests/trusted_ca_keys_decode.py
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
trusted ca keys hex string
|
||||
"0069011d484406bca8888997a7462416445e7db117114c017f204de30f1cd42c9e6dae91b6a8ac9b8d481ba601597be7013ad6fc397b78b01d90cea1b7f909f145011d484406bca8888997a7462416445e7db117114c0100fae3900795c888a4d4d7bd9fdffa60418ac19f"
|
||||
|
||||
length 0069
|
||||
"01 1d484406bca8888997a7462416445e7db117114c"
|
||||
"01 7f204de30f1cd42c9e6dae91b6a8ac9b8d481ba6"
|
||||
"01 597be7013ad6fc397b78b01d90cea1b7f909f145"
|
||||
"01 1d484406bca8888997a7462416445e7db117114c"
|
||||
"01 00fae3900795c888a4d4d7bd9fdffa60418ac19f"
|
||||
|
||||
key hash from certificate
|
||||
openssl x509 -in cert.pem -pubkey -noout | openssl enc -base64 -d | openssl dgst -sha1
|
||||
"""
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
def certificate_key_hash(filename):
|
||||
with open(filename, "rb") as fp:
|
||||
cert = x509.load_pem_x509_certificate(fp.read())
|
||||
pub = cert.public_key()
|
||||
pub_der = pub.public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
dgst = hashes.Hash(hashes.SHA1())
|
||||
dgst.update(pub_der)
|
||||
sha1 = dgst.finalize()
|
||||
print(sha1.hex())
|
||||
|
||||
|
||||
def certificate_hash(filename):
|
||||
# note this is the hash of the whole certificate including signature
|
||||
with open(filename, "rb") as fp:
|
||||
cert = x509.load_pem_x509_certificate(fp.read())
|
||||
pub_der = cert.public_bytes(encoding=serialization.Encoding.DER)
|
||||
dgst = hashes.Hash(hashes.SHA1())
|
||||
dgst.update(pub_der)
|
||||
sha1 = dgst.finalize()
|
||||
print(sha1.hex())
|
||||
|
||||
|
||||
def trusted_ca_keys_decode(data):
|
||||
data_len = int.from_bytes(data[:2], "big", signed=False)
|
||||
data = data[2:]
|
||||
assert len(data) == data_len
|
||||
while data:
|
||||
entry_type = data[0]
|
||||
data = data[1:]
|
||||
if entry_type == 0:
|
||||
print("pre_agreed")
|
||||
elif entry_type == 1:
|
||||
sha1 = data[:20]
|
||||
data = data[20:]
|
||||
print("key_sha1_hash: %s" % sha1.hex())
|
||||
elif entry_type == 2:
|
||||
print("x509_name (not decoded yet)")
|
||||
elif entry_type == 3:
|
||||
sha1 = data[:20]
|
||||
data = data[20:]
|
||||
print("cert_sha1_hash: %s" % sha1.hex())
|
||||
|
||||
|
||||
def trusted_ca_keys_decode_file(filename):
|
||||
with open(filename, "rb") as fp:
|
||||
trusted_ca_keys_decode(fp.read())
|
||||
|
||||
|
||||
def trusted_ca_keys_decode_hex(hexstr):
|
||||
trusted_ca_keys_decode(bytes.fromhex(hexstr))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--key",
|
||||
action="store",
|
||||
help="filename: Print sha1 hash of certificate public key",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cert", action="store", help="filename: Print sha1 hash of certificate"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--file", action="store", help="filename: Parse trusted ca keys"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hex", action="store", help="parse trusted ca keys hex string"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.key:
|
||||
certificate_key_hash(args.key)
|
||||
if args.cert:
|
||||
certificate_hash(args.cert)
|
||||
if args.file:
|
||||
trusted_ca_keys_decode_file(args.file)
|
||||
if args.hex:
|
||||
trusted_ca_keys_decode_hex(args.hex)
|
||||
Reference in New Issue
Block a user