Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

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

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

View File

@@ -0,0 +1,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})

View 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
```

View 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

View 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();
}

View 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

View 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

View File

@@ -0,0 +1 @@
*.pem

View 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 = 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

View 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_

View File

@@ -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

View 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;
}

View 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

View 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)