diff --git a/.gitignore b/.gitignore index 2c5ef39d..fc33e763 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ *build .vscode -pki +/pki test/sample_data diff --git a/include/iso15118/config.hpp b/include/iso15118/config.hpp index 7a77e343..0a5b59e4 100644 --- a/include/iso15118/config.hpp +++ b/include/iso15118/config.hpp @@ -2,6 +2,7 @@ // Copyright 2023 Pionix GmbH and Contributors to EVerest #pragma once +#include #include #include @@ -17,16 +18,21 @@ enum class CertificateBackend { EVEREST_LAYOUT, JOSEPPA_LAYOUT, }; + struct SSLConfig { - CertificateBackend backend; + CertificateBackend backend{CertificateBackend::EVEREST_LAYOUT}; // Used by the JOSEPPA_LAYOUT std::string config_string; // Used by the EVEREST_LAYOUT std::string path_certificate_chain; std::string path_certificate_key; - std::optional private_key_password; + std::optional private_key_password{}; + std::string path_certificate_v2g_root; + std::string path_certificate_mo_root; bool enable_ssl_logging{false}; bool enable_tls_key_logging{false}; + bool enforce_tls_1_3{false}; + std::filesystem::path tls_key_logging_path{}; }; } // namespace iso15118::config diff --git a/include/iso15118/detail/io/sha_hash.hpp b/include/iso15118/detail/io/sha_hash.hpp new file mode 100644 index 00000000..68438681 --- /dev/null +++ b/include/iso15118/detail/io/sha_hash.hpp @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include + +namespace iso15118::io { + +constexpr std::size_t sha_512_hash_size = 64; +using sha512_hash_t = std::array; + +} // namespace iso15118::io diff --git a/include/iso15118/io/connection_abstract.hpp b/include/iso15118/io/connection_abstract.hpp index f416e0a9..69f2e731 100644 --- a/include/iso15118/io/connection_abstract.hpp +++ b/include/iso15118/io/connection_abstract.hpp @@ -4,9 +4,12 @@ #include #include +#include #include "ipv6_endpoint.hpp" +#include + namespace iso15118::io { enum class ConnectionEvent { @@ -33,6 +36,8 @@ struct IConnection { virtual void close() = 0; + virtual std::optional get_vehicle_cert_hash() const = 0; + virtual ~IConnection() = default; }; } // namespace iso15118::io diff --git a/include/iso15118/io/connection_plain.hpp b/include/iso15118/io/connection_plain.hpp index cdf6483d..926802f5 100644 --- a/include/iso15118/io/connection_plain.hpp +++ b/include/iso15118/io/connection_plain.hpp @@ -21,6 +21,10 @@ class ConnectionPlain : public IConnection { void close() final; + std::optional get_vehicle_cert_hash() const final { + return std::nullopt; + } + ~ConnectionPlain(); private: diff --git a/include/iso15118/io/connection_ssl.hpp b/include/iso15118/io/connection_ssl.hpp index 87440194..43e0c7f4 100644 --- a/include/iso15118/io/connection_ssl.hpp +++ b/include/iso15118/io/connection_ssl.hpp @@ -4,8 +4,10 @@ #include "connection_abstract.hpp" #include +#include #include +#include #include namespace iso15118::io { @@ -24,6 +26,8 @@ class ConnectionSSL : public IConnection { void close() final; + std::optional get_vehicle_cert_hash() const final; + ~ConnectionSSL(); private: diff --git a/include/iso15118/io/sdp_server.hpp b/include/iso15118/io/sdp_server.hpp index db2655f8..3e3df1cb 100644 --- a/include/iso15118/io/sdp_server.hpp +++ b/include/iso15118/io/sdp_server.hpp @@ -53,8 +53,13 @@ class TlsKeyLoggingServer { return fd; } + auto get_port() const { + return port; + } + private: int fd{-1}; + uint16_t port{0}; sockaddr_in6 destination_address{}; }; diff --git a/include/iso15118/tbd_controller.hpp b/include/iso15118/tbd_controller.hpp index 9acfc6ff..a3dc58c6 100644 --- a/include/iso15118/tbd_controller.hpp +++ b/include/iso15118/tbd_controller.hpp @@ -20,7 +20,7 @@ namespace iso15118 { struct TbdConfig { - config::SSLConfig ssl{config::CertificateBackend::EVEREST_LAYOUT, {}, {}, {}, {}}; + config::SSLConfig ssl{config::CertificateBackend::EVEREST_LAYOUT, {}, {}, {}, {}, {}, {}}; std::string interface_name; config::TlsNegotiationStrategy tls_negotiation_strategy{config::TlsNegotiationStrategy::ACCEPT_CLIENT_OFFER}; bool enable_sdp_server{true}; diff --git a/src/iso15118/io/connection_ssl.cpp b/src/iso15118/io/connection_ssl.cpp index 576da923..7ac6e386 100644 --- a/src/iso15118/io/connection_ssl.cpp +++ b/src/iso15118/io/connection_ssl.cpp @@ -2,16 +2,22 @@ // Copyright 2024 Pionix GmbH and Contributors to EVerest #include +#include +#include #include #include #include #include +#include +#include #include +#include #include #include #include +#include #include #include @@ -36,11 +42,6 @@ template <> class default_delete { namespace iso15118::io { -static constexpr auto DEFAULT_SOCKET_BACKLOG = 4; -static constexpr auto TLS_PORT = 50000; - -static int ex_data_idx; - struct SSLContext { std::unique_ptr ssl_ctx; std::unique_ptr ssl; @@ -48,10 +49,163 @@ struct SSLContext { int accept_fd{-1}; std::string interface_name; bool enable_key_logging{false}; + std::filesystem::path tls_key_log_file_path{}; std::unique_ptr key_server; + bool enforce_tls_1_3{false}; + std::optional vehicle_cert_hash{std::nullopt}; }; -static int private_key_callback(char* buf, int size, [[maybe_unused]] int rwflag, void* userdata) { +namespace { + +constexpr auto DEFAULT_SOCKET_BACKLOG = 4; +constexpr auto TLS_PORT = 50000; + +int ssl_keylog_server_index{-1}; +int ssl_keylog_file_index{-1}; + +std::vector extract_supported_versions(const uint8_t* data, std::size_t remaining) { + uint8_t length_supported_versions = *(data++); + remaining -= 1; + + if (length_supported_versions != remaining) { + logf_error("length_supported_versions is not remaining"); + return {}; + } + + if (length_supported_versions % 2 != 0) { + logf_error("length_supported_versions is not diveded by 2"); + return {}; + } + + // First Byte: length + // Byte 2+3 -> First Version (03, 04) + // Byte 4+5 -> Second Version (03, 03) + // .... + + std::vector tls_versions{}; + for (auto i = 0; i < length_supported_versions; i += 2) { + const uint8_t first_byte = *(data++); + const uint8_t second_byte = *(data++); + tls_versions.push_back(first_byte << 8 | second_byte); + } + return tls_versions; +} + +std::string convert_ssl_tls_versions_to_string(uint16_t version) { + switch (version) { + case SSL3_VERSION: + return "SSL3"; + case TLS1_VERSION: + return "TLS1"; + case TLS1_1_VERSION: + return "TLS1_1"; + case TLS1_2_VERSION: + return "TLS1_2"; + case TLS1_3_VERSION: + return "TLS1_3"; + default: + return "Unknown"; + } +} + +int client_hello_cb(SSL* ssl, int* alert, void* object) { + + // To shut up the compiler... + (void)alert; + (void)object; + + int* extensions{nullptr}; + std::size_t length{0}; + if (SSL_client_hello_get1_extensions_present(ssl, &extensions, &length) == 1) { + for (std::size_t i = 0; i < length; i++) { + if (extensions[i] == TLSEXT_TYPE_supported_versions) { + const unsigned char* data; + std::size_t datalen{0}; + + // NOTE(SL): I found no get or callback function for the supported_versions extension, so I wrote + // my own parser + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_supported_versions, &data, &datalen)) { + const auto tls_versions = extract_supported_versions(data, datalen); + + for (auto tls_version : tls_versions) { + logf_debug("Client supported tls version: %s", + convert_ssl_tls_versions_to_string(tls_version).c_str()); + } + + const auto tls_1_3_found = + std::any_of(tls_versions.begin(), tls_versions.end(), + [](std::uint16_t version) { return version == TLS1_3_VERSION; }); + if (tls_1_3_found) { + logf_info("Client supports TLS1.3: Change verify mode to SSL_VERIFY_PEER and " + "SSL_VERIFY_FAIL_IF_NO_PEER_CERT"); + int mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_set_verify(ssl, mode, nullptr); + } + } + } else if (extensions[i] == TLSEXT_TYPE_certificate_authorities) { + logf_info("Extension certificate_authorities found!"); + // TODO(SL): Setting var for handle_certificate_cb + } + } + OPENSSL_free(extensions); + } + return SSL_CLIENT_HELLO_SUCCESS; +} + +int handle_certificate_cb(SSL* ssl, void* arg) { + + // To shut up the compiler... + (void)arg; + + // TODO(sl): Check only after names if the extension is there + const STACK_OF(X509_NAME)* names = SSL_get0_peer_CA_list(ssl); + + if (names == NULL || sk_X509_NAME_num(names) == 0) { + logf_error("No certificate CA names sent"); + } else { + logf_info("Found certificate CA names!"); + + for (auto i = 0; i < sk_X509_NAME_num(names); i++) { + char name[256]{}; + X509_NAME_oneline(sk_X509_NAME_value(names, i), name, sizeof(name)); + logf_info("Name: %s", name); + } + } + + // TODO(sl): Change leaf and chain certificate based on ca names + + return 1; +} + +void keylog_callback(const SSL* ssl, const char* line) { + auto key_logging_server = static_cast(SSL_get_ex_data(ssl, ssl_keylog_server_index)); + + std::string key_log_msg = "TLS Handshake keys on port "; + key_log_msg += std::to_string(key_logging_server->get_port()) + ": "; + key_log_msg += std::string(line); + + logf_info(key_log_msg.c_str()); + + if (key_logging_server->get_fd() != -1) { + const auto result = key_logging_server->send(line); + if (not cmp_equal(result, strlen(line))) { + const auto error_msg = adding_err_msg("key_logging_server send() failed"); + logf_error(error_msg.c_str()); + } + } + + const auto keylog_file_path = + static_cast(SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), ssl_keylog_file_index)); + + if (not keylog_file_path->empty()) { + std::ofstream ofs; + ofs.open(keylog_file_path->string(), std::ofstream::out | std::ofstream::app); + ofs << line << std::endl; + ofs.close(); + } +} + +int private_key_callback(char* buf, int size, [[maybe_unused]] int rwflag, void* userdata) { const auto* password = static_cast(userdata); const std::size_t max_pass_len = (size - 1); // we exclude the endline const std::size_t max_copy_chars = @@ -63,7 +217,7 @@ static int private_key_callback(char* buf, int size, [[maybe_unused]] int rwflag return max_copy_chars; } -static SSL_CTX* init_ssl(const config::SSLConfig& ssl_config) { +SSL_CTX* init_ssl(const config::SSLConfig& ssl_config) { // Note: openssl does not provide support for ECDH-ECDSA-AES128-SHA256 anymore static constexpr auto TLS1_2_CIPHERSUITES = "ECDHE-ECDSA-AES128-SHA256"; @@ -76,11 +230,15 @@ static SSL_CTX* init_ssl(const config::SSLConfig& ssl_config) { log_and_raise_openssl_error("Failed in SSL_CTX_new()"); } - if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) == 0) { + const int result_set_min_proto_version = (ssl_config.enforce_tls_1_3) + ? SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) + : SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + + if (result_set_min_proto_version == 0) { log_and_raise_openssl_error("Failed in SSL_CTX_set_min_proto_version()"); } - if (SSL_CTX_set_cipher_list(ctx, TLS1_2_CIPHERSUITES) == 0) { + if (not ssl_config.enforce_tls_1_3 and SSL_CTX_set_cipher_list(ctx, TLS1_2_CIPHERSUITES) == 0) { log_and_raise_openssl_error("Failed in SSL_CTX_set_cipher_list()"); } @@ -104,30 +262,39 @@ static SSL_CTX* init_ssl(const config::SSLConfig& ssl_config) { log_and_raise_openssl_error("Failed in SSL_CTX_use_PrivateKey_file()"); } - // TODO(sl): How switch verify mode to none if tls 1.2 is used? - SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); + // Loading root certificates to verify client (only for tls 1.3) + if (SSL_CTX_load_verify_file(ctx, ssl_config.path_certificate_v2g_root.c_str()) == 0) { + logf_error("Verify V2G root not found!"); + } - if (ssl_config.enable_tls_key_logging) { + if (SSL_CTX_load_verify_file(ctx, ssl_config.path_certificate_mo_root.c_str()) == 0) { + logf_error("Verify OEM root not found!"); + } - SSL_CTX_set_keylog_callback(ctx, [](const SSL* ssl, const char* line) { - logf_info("TLS handshake keys: %s", line); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); - auto& key_logging_server = *static_cast(SSL_get_ex_data(ssl, ex_data_idx)); + SSL_CTX_set_client_hello_cb(ctx, &client_hello_cb, nullptr); - if (key_logging_server.get_fd() == -1) { - logf_warning("Error - key_logging_server fd is not available"); - return; - } - const auto result = key_logging_server.send(line); - if (not cmp_equal(result, strlen(line))) { - const auto error_msg = adding_err_msg("key_logging_server send() failed"); - logf_error(error_msg.c_str()); - } - }); + // TODO(SL): Adding multi root support with certificate_authorities extension + // SSL_CTX_set_cert_cb(ctx, &handle_certificate_cb, nullptr); + + if (ssl_config.enable_tls_key_logging) { + ssl_keylog_file_index = SSL_CTX_get_ex_new_index(0, std::string("").data(), nullptr, nullptr, nullptr); + ssl_keylog_server_index = SSL_get_ex_new_index(0, std::string("").data(), nullptr, nullptr, nullptr); + + if (ssl_keylog_file_index == -1 or ssl_keylog_server_index == -1) { + auto error_msg = std::string("_get_ex_new_index failed: ssl_keylog_file_index: "); + error_msg += std::to_string(ssl_keylog_file_index); + error_msg += ", ssl_keylog_server_index: " + std::to_string(ssl_keylog_server_index); + logf_error(error_msg.c_str()); + } else { + SSL_CTX_set_keylog_callback(ctx, keylog_callback); + } } return ctx; } +} // namespace ConnectionSSL::ConnectionSSL(PollManager& poll_manager_, const std::string& interface_name_, const config::SSLConfig& ssl_config) : @@ -135,11 +302,16 @@ ConnectionSSL::ConnectionSSL(PollManager& poll_manager_, const std::string& inte ssl->interface_name = interface_name_; ssl->enable_key_logging = ssl_config.enable_tls_key_logging; + ssl->enforce_tls_1_3 = ssl_config.enforce_tls_1_3; // Openssl stuff missing! const auto ssl_ctx = init_ssl(ssl_config); ssl->ssl_ctx = std::unique_ptr(ssl_ctx); + if (ssl_keylog_file_index != -1) { + ssl->tls_key_log_file_path = ssl_config.tls_key_logging_path / "tls_session_keys.log"; + SSL_CTX_set_ex_data(ssl->ssl_ctx.get(), ssl_keylog_file_index, &ssl->tls_key_log_file_path); + } sockaddr_in6 address; if (not get_first_sockaddr_in6_for_interface(interface_name_, address)) { const auto msg = "Failed to get ipv6 socket address for interface " + interface_name_; @@ -202,6 +374,10 @@ Ipv6EndPoint ConnectionSSL::get_public_endpoint() const { return end_point; } +std::optional ConnectionSSL::get_vehicle_cert_hash() const { + return ssl->vehicle_cert_hash; +} + void ConnectionSSL::write(const uint8_t* buf, size_t len) { assert(handshake_complete); // TODO(sl): Adding states? @@ -273,12 +449,7 @@ void ConnectionSSL::handle_connect() { if (ssl->enable_key_logging) { const auto port = std::stoul(service); ssl->key_server = std::make_unique(ssl->interface_name, port); - - // NOTE(sl): SSL_get_ex_new_index does not work with "ex data" because of const - std::string tmp = "ex data"; - ex_data_idx = SSL_get_ex_new_index(0, tmp.data(), nullptr, nullptr, nullptr); - - SSL_set_ex_data(ssl_ptr, ex_data_idx, ssl->key_server.get()); + SSL_set_ex_data(ssl_ptr, ssl_keylog_server_index, ssl->key_server.get()); } poll_manager.register_fd(ssl->accept_fd, [this]() { this->handle_data(); }); @@ -306,6 +477,43 @@ void ConnectionSSL::handle_data() { } else { logf_info("Handshake complete!"); + const auto peer = SSL_get0_peer_certificate(ssl_ptr); + + if (SSL_get_verify_mode(ssl_ptr) > SSL_VERIFY_NONE and peer) { + + const auto verify_result = SSL_get_verify_result(ssl_ptr); + if (verify_result == X509_V_OK) { + logf_info("Verify certificate result is okay"); + + char name[256]{}; + X509_NAME_oneline(X509_get_subject_name(peer), name, sizeof(name)); + logf_debug("Peer subject name: %s", name); + memset(name, 0x00, sizeof(name)); + X509_NAME_oneline(X509_get_issuer_name(peer), name, sizeof(name)); + logf_debug("Peer issuer name: %s", name); + + unsigned int length = 0; + auto& vehicle_hash = ssl->vehicle_cert_hash.emplace(); + const auto result_digest = X509_digest(peer, EVP_sha512(), vehicle_hash.data(), &length); + + if (result_digest) { + std::stringstream ss; + ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0') + << static_cast(vehicle_hash[0]); + for (unsigned int i = 1; i < length; ++i) { + ss << ":" << std::uppercase << std::hex << std::setw(2) << std::setfill('0') + << (int)static_cast(vehicle_hash[i]); + } + logf_debug("sha512 fingerprint: %s", ss.str().c_str()); + // openssl command: openssl x509 -in *.pem -noout -fingerprint -sha512 + } else { + logf_error("X509_digest failed"); + } + } else { + logf_error("Verify certificate result is not okay"); + } + } + handshake_complete = true; if (ssl->enable_key_logging) { ssl->key_server.reset(); diff --git a/src/iso15118/io/sdp_server.cpp b/src/iso15118/io/sdp_server.cpp index e8387a5d..5d451d86 100644 --- a/src/iso15118/io/sdp_server.cpp +++ b/src/iso15118/io/sdp_server.cpp @@ -153,7 +153,7 @@ void SdpServer::send_response(const PeerRequestContext& request, const Ipv6EndPo sendto(fd, v2g_packet, sizeof(v2g_packet), 0, reinterpret_cast(&request.address), peer_addr_len); } -TlsKeyLoggingServer::TlsKeyLoggingServer(const std::string& interface_name, uint16_t port) { +TlsKeyLoggingServer::TlsKeyLoggingServer(const std::string& interface_name, uint16_t port_) : port(port_) { static constexpr auto LINK_LOCAL_MULTICAST = "ff02::1"; fd = socket(AF_INET6, SOCK_DGRAM, 0); diff --git a/test/iso15118/io/CMakeLists.txt b/test/iso15118/io/CMakeLists.txt index 3c037a19..a354791c 100644 --- a/test/iso15118/io/CMakeLists.txt +++ b/test/iso15118/io/CMakeLists.txt @@ -9,3 +9,20 @@ target_link_libraries(test_logging ) catch_discover_tests(test_logging) + +add_executable(connection_openssl_test) +add_custom_command( + TARGET connection_openssl_test + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/pki + COMMAND cd pki && cp pki.sh ${CMAKE_CURRENT_BINARY_DIR}/pki + COMMAND cp -r configs ${CMAKE_CURRENT_BINARY_DIR}/pki + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +target_sources(connection_openssl_test + PRIVATE + connection_openssl.cpp +) +target_link_libraries(connection_openssl_test + PRIVATE + iso15118::iso15118 +) diff --git a/test/iso15118/io/README.md b/test/iso15118/io/README.md new file mode 100644 index 00000000..72ca1874 --- /dev/null +++ b/test/iso15118/io/README.md @@ -0,0 +1,33 @@ +# Connection Test + +## 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 test executable + +### Standalone TLS server + +Tests the TLS Server in isolation. + +- `./connection_openssl -i ` +- gracefully terminates after 30 seconds +- requires client certificate +- Supports TCP, TLS1.2 and TLS1.3 + +### openssl s_client commands + +TLS 1.2 and 1.3: +```sh +openssl s_client -connect [ipv6%iface]:port -verify 2 -debug -msg -verifyCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem -verify_return_error -cert ./pki/certs/client/vehicle/VEHICLE_LEAF.pem -cert_chain ./pki/certs/ca/vehicle/VEHICLE_CERT_CHAIN.pem -certform PEM -key ./pki/certs/client/vehicle/VEHICLE_LEAF.key -keyform PEM -pass file:./pki/certs/client/vehicle/VEHICLE_LEAF_PASSWORD.txt -ciphersuites "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" -cipher "ECDHE-ECDSA-AES128-SHA256" -requestCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem +``` + +TLS 1.2 only: +```sh +openssl s_client -connect [ipv6%iface]:port -verify 2 -debug -msg -verifyCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem -verify_return_error -tls1_2 -cipher "ECDHE-ECDSA-AES128-SHA256" +``` + +TLS 1.3 only: +```sh +openssl s_client -connect [ipv6%iface]:port -verify 2 -debug -msg -verifyCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem -verify_return_error -tls1_3 -cert ./pki/certs/client/vehicle/VEHICLE_LEAF.pem -cert_chain ./pki/certs/ca/vehicle/VEHICLE_CERT_CHAIN.pem -certform PEM -key ./pki/certs/client/vehicle/VEHICLE_LEAF.key -keyform PEM -pass file:./pki/certs/client/vehicle/VEHICLE_LEAF_PASSWORD.txt -ciphersuites "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" -requestCAfile ./pki/certs/ca/v2g/V2G_ROOT_CA.pem +``` diff --git a/test/iso15118/io/connection_openssl.cpp b/test/iso15118/io/connection_openssl.cpp new file mode 100644 index 00000000..0fb5f3ec --- /dev/null +++ b/test/iso15118/io/connection_openssl.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +static constexpr auto DEFAULT_PW{"123456"}; +static constexpr auto POLL_MANAGER_TIMEOUT_MS = 50; +static constexpr auto STOP_TIME = 30; + +const char* short_opts = "hi:"; +std::string interface {}; + +void parse_options(int argc, char** argv) { + int c{0}; + + while ((c = getopt(argc, argv, short_opts)) != -1) { + switch (c) { + break; + case 'i': + interface = std::string(optarg); + break; + case 'h': + case '?': + std::cout << "Usage: " << argv[0] << " [-i]" << std::endl; + std::cout << " -i " << std::endl; + exit(1); + break; + default: + exit(2); + } + } + + if (interface.empty()) { + std::cerr << "Error: " << argv[0] << " requires -i " << std::endl; + exit(3); + } +} + +void handle_connection_event(iso15118::io::ConnectionEvent event) { + using namespace iso15118; + + using Event = io::ConnectionEvent; + switch (event) { + case Event::ACCEPTED: + std::cout << "Accepted connection" << std::endl; + return; + + case Event::NEW_DATA: + std::cout << "New Data" << std::endl; + return; + + case Event::OPEN: + std::cout << "Connection open" << std::endl; + return; + + case Event::CLOSED: + std::cout << "Connection is closed" << std::endl; + return; + } +} + +} // namespace + +int main(int argc, char** argv) { + using namespace iso15118; + + parse_options(argc, argv); + + io::set_logging_callback([](LogLevel level, std::string message) { + std::cout << "log(" << static_cast(level) << "): " << message << std::endl; + }); + + auto poll_manager = io::PollManager(); + + const auto interface_name = interface; + + const config::SSLConfig ssl{iso15118::config::CertificateBackend::EVEREST_LAYOUT, + {}, + "pki/certs/client/cso/CPO_CERT_CHAIN.pem", + "pki/certs/client/cso/SECC_LEAF.key", + DEFAULT_PW, + "pki/certs/ca/v2g/V2G_ROOT_CA.pem", + "pki/certs/ca/oem/OEM_ROOT_CA.pem", + false, // enable_ssl_logging + true, // enable_tls_key_logging + false, // enforce_tls_1_3 + "/tmp"}; // tls_key_log_file_path + + auto connection = io::ConnectionSSL(poll_manager, interface_name, ssl); + connection.set_event_callback([](io::ConnectionEvent event) { handle_connection_event(event); }); + + auto next_event = get_current_time_point(); + const auto start_time_point = next_event; + + while (true) { + const auto duration = + std::chrono::duration_cast>(get_current_time_point() - start_time_point); + if (duration.count() >= STOP_TIME) { + break; + } + + const auto poll_timeout_ms = get_timeout_ms_until(next_event, POLL_MANAGER_TIMEOUT_MS); + poll_manager.poll(poll_timeout_ms); + + next_event = offset_time_point_by_ms(get_current_time_point(), POLL_MANAGER_TIMEOUT_MS); + } + + connection.close(); + + return 0; +} diff --git a/test/iso15118/io/pki/.gitignore b/test/iso15118/io/pki/.gitignore new file mode 100644 index 00000000..dfd83dfb --- /dev/null +++ b/test/iso15118/io/pki/.gitignore @@ -0,0 +1,6 @@ +*.pem +*.der +*.key + +certs/ +csrs/ diff --git a/test/iso15118/io/pki/configs/contractLeafCert.cnf b/test/iso15118/io/pki/configs/contractLeafCert.cnf new file mode 100644 index 00000000..71f8fe93 --- /dev/null +++ b/test/iso15118/io/pki/configs/contractLeafCert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = UKSWI123456791A +organizationName = EVerest +countryName = DE +domainComponent = MO + +[ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature,nonRepudiation,keyEncipherment,keyAgreement +subjectKeyIdentifier = hash +authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer diff --git a/test/iso15118/io/pki/configs/cpoSubCA1Cert.cnf b/test/iso15118/io/pki/configs/cpoSubCA1Cert.cnf new file mode 100644 index 00000000..2fd5fa2b --- /dev/null +++ b/test/iso15118/io/pki/configs/cpoSubCA1Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = CPOSubCA1 +organizationName = EVerest +countryName = DE +domainComponent = V2G + +[ext] +basicConstraints = critical,CA:true,pathlen:1 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash +authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer diff --git a/test/iso15118/io/pki/configs/cpoSubCA2Cert.cnf b/test/iso15118/io/pki/configs/cpoSubCA2Cert.cnf new file mode 100644 index 00000000..9a84f657 --- /dev/null +++ b/test/iso15118/io/pki/configs/cpoSubCA2Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = CPOSubCA2 +organizationName = EVerest +countryName = DE +domainComponent = V2G + +[ext] +basicConstraints = critical,CA:true,pathlen:0 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash +authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer diff --git a/test/iso15118/io/pki/configs/cpsLeafCert.cnf b/test/iso15118/io/pki/configs/cpsLeafCert.cnf new file mode 100644 index 00000000..ec22764a --- /dev/null +++ b/test/iso15118/io/pki/configs/cpsLeafCert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = CPS Leaf +organizationName = EVerest +countryName = DE +domainComponent = CPS + +[ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/cpsSubCA1Cert.cnf b/test/iso15118/io/pki/configs/cpsSubCA1Cert.cnf new file mode 100644 index 00000000..1b3b2a75 --- /dev/null +++ b/test/iso15118/io/pki/configs/cpsSubCA1Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = ProvSubCA1 +organizationName = EVerest +countryName = DE +domainComponent = CPS + +[ext] +basicConstraints = critical,CA:true,pathlen:1 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/cpsSubCA2Cert.cnf b/test/iso15118/io/pki/configs/cpsSubCA2Cert.cnf new file mode 100644 index 00000000..7d73ae8f --- /dev/null +++ b/test/iso15118/io/pki/configs/cpsSubCA2Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = ProvSubCA2 +organizationName = EVerest +countryName = DE +domainComponent = CPS + +[ext] +basicConstraints = critical,CA:true,pathlen:0 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/moRootCACert.cnf b/test/iso15118/io/pki/configs/moRootCACert.cnf new file mode 100644 index 00000000..c4320d99 --- /dev/null +++ b/test/iso15118/io/pki/configs/moRootCACert.cnf @@ -0,0 +1,16 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = MORootCA +organizationName = EVerest +countryName = DE +domainComponent = MO + +[ext] +basicConstraints = critical,CA:true +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash +authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer + diff --git a/test/iso15118/io/pki/configs/moSubCA1Cert.cnf b/test/iso15118/io/pki/configs/moSubCA1Cert.cnf new file mode 100644 index 00000000..721d78aa --- /dev/null +++ b/test/iso15118/io/pki/configs/moSubCA1Cert.cnf @@ -0,0 +1,16 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = PKI-Ext_CRT_MO_SUB1_VALID +organizationName = EVerest +countryName = DE +domainComponent = MO + +[ext] +basicConstraints = critical,CA:true,pathlen:1 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash +authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer + diff --git a/test/iso15118/io/pki/configs/moSubCA2Cert.cnf b/test/iso15118/io/pki/configs/moSubCA2Cert.cnf new file mode 100644 index 00000000..8a484c94 --- /dev/null +++ b/test/iso15118/io/pki/configs/moSubCA2Cert.cnf @@ -0,0 +1,16 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = PKI-Ext_CRT_MO_SUB2_VALID +organizationName = EVerest +countryName = DE +domainComponent = MO + +[ext] +basicConstraints = critical,CA:true,pathlen:0 +keyUsage = critical,digitalSignature,nonRepudiation,keyCertSign,cRLSign +subjectKeyIdentifier = hash +authorityInfoAccess = OCSP;URI:https://www.example.com/, caIssuers;URI:https://www.example.com/Intermediate-CA.cer + diff --git a/test/iso15118/io/pki/configs/oemLeafCert.cnf b/test/iso15118/io/pki/configs/oemLeafCert.cnf new file mode 100644 index 00000000..6aac5a36 --- /dev/null +++ b/test/iso15118/io/pki/configs/oemLeafCert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = OEMProvCert +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature,keyAgreement +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/oemRootCACert.cnf b/test/iso15118/io/pki/configs/oemRootCACert.cnf new file mode 100644 index 00000000..d4c2cb9c --- /dev/null +++ b/test/iso15118/io/pki/configs/oemRootCACert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = OEMRootCA +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:true +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/oemSubCA1Cert.cnf b/test/iso15118/io/pki/configs/oemSubCA1Cert.cnf new file mode 100644 index 00000000..0ea711b2 --- /dev/null +++ b/test/iso15118/io/pki/configs/oemSubCA1Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = OEMSubCA1 +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:true,pathlen:1 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/oemSubCA2Cert.cnf b/test/iso15118/io/pki/configs/oemSubCA2Cert.cnf new file mode 100644 index 00000000..36db09e1 --- /dev/null +++ b/test/iso15118/io/pki/configs/oemSubCA2Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = OEMSubCA2 +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:true,pathlen:0 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/seccLeafCert.cnf b/test/iso15118/io/pki/configs/seccLeafCert.cnf new file mode 100644 index 00000000..74b290e1 --- /dev/null +++ b/test/iso15118/io/pki/configs/seccLeafCert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = SECCCert +organizationName = EVerest +countryName = DE +domainComponent = CPO + +[ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature,keyAgreement +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/v2gRootCACert.cnf b/test/iso15118/io/pki/configs/v2gRootCACert.cnf new file mode 100644 index 00000000..1c02c06b --- /dev/null +++ b/test/iso15118/io/pki/configs/v2gRootCACert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = V2GRootCA +organizationName = EVerest +countryName = DE +domainComponent = V2G + +[ext] +basicConstraints = critical,CA:true +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/vehicleLeafCert.cnf b/test/iso15118/io/pki/configs/vehicleLeafCert.cnf new file mode 100644 index 00000000..899b0ff5 --- /dev/null +++ b/test/iso15118/io/pki/configs/vehicleLeafCert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = WMIV1234567890ABCDEX +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature,keyAgreement +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/vehicleSubCA1Cert.cnf b/test/iso15118/io/pki/configs/vehicleSubCA1Cert.cnf new file mode 100644 index 00000000..9d492681 --- /dev/null +++ b/test/iso15118/io/pki/configs/vehicleSubCA1Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = VehicleSubCA1 +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:true,pathlen:1 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/configs/vehicleSubCA2Cert.cnf b/test/iso15118/io/pki/configs/vehicleSubCA2Cert.cnf new file mode 100644 index 00000000..9a5ba991 --- /dev/null +++ b/test/iso15118/io/pki/configs/vehicleSubCA2Cert.cnf @@ -0,0 +1,15 @@ +[req] +prompt = no +distinguished_name = ca_dn + +[ca_dn] +commonName = VehicleSubCA2 +organizationName = EVerest +countryName = DE +domainComponent = OEM + +[ext] +basicConstraints = critical,CA:true,pathlen:0 +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash + diff --git a/test/iso15118/io/pki/pki.sh b/test/iso15118/io/pki/pki.sh new file mode 100755 index 00000000..9bd32b69 --- /dev/null +++ b/test/iso15118/io/pki/pki.sh @@ -0,0 +1,128 @@ +#!/bin/sh +VALIDITY_OEM_LEAF_CERT=1460 +VALIDITY_OEM_SUBCA2_CERT=1460 +VALIDITY_OEM_SUBCA1_CERT=1460 +VALIDITY_OEM_ROOT_CERT=3650 +VALIDITY_SECC_LEAF_CERT=60 +VALIDITY_CPO_SUBCA1_CERT=1460 +VALIDITY_CPO_SUBCA2_CERT=365 +VALIDITY_V2G_ROOT_CERT=3650 +VALIDITY_VEHICLE_LEAF_CERT=1460 +VALIDITY_VEHICLE_SUBCA1_CERT=1460 +VALIDITY_VEHICLE_SUBCA2_CERT=3650 + +SYMMETRIC_CIPHER=-aes-128-cbc # TODO Check correct version for ISO 15118-20 +SYMMETRIC_CIPHER_PKCS12=-aes128 # TODO Check correct version for ISO 15118-20 +SHA=-sha256 # TODO Check correct version for ISO 15118-20 +EC_CURVE=prime256v1 # TODO Check correct version for ISO 15118-20 + +password=123456 + +echo "Password used is: '$password'" + +# 0) Create directories if not yet existing +CERT_PATH=certs +CSR_PATH=csrs + +CA_CSO_PATH=$CERT_PATH/ca/cso +CA_OEM_PATH=$CERT_PATH/ca/oem +CA_V2G_PATH=$CERT_PATH/ca/v2g +CA_VEHICLE_PATH=$CERT_PATH/ca/vehicle + +CLIENT_CSO_PATH=$CERT_PATH/client/cso +CLIENT_OEM_PATH=$CERT_PATH/client/oem +CLIENT_V2G_PATH=$CERT_PATH/client/v2g +CLIENT_VEHICLE_PATH=$CERT_PATH/client/vehicle + +mkdir -p $CERT_PATH +mkdir -p $CSR_PATH +mkdir -p $CA_CSO_PATH +mkdir -p $CA_OEM_PATH +mkdir -p $CA_V2G_PATH +mkdir -p $CA_VEHICLE_PATH +mkdir -p $CLIENT_CSO_PATH +mkdir -p $CLIENT_OEM_PATH +mkdir -p $CLIENT_V2G_PATH +mkdir -p $CLIENT_VEHICLE_PATH + +# 1) Create a self-signed V2G_ROOT_CA certificate +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_V2G_PATH/V2G_ROOT_CA.key +openssl req -new -key $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password -config configs/v2gRootCACert.cnf -out $CSR_PATH/V2G_ROOT_CA.csr +openssl x509 -req -in $CSR_PATH/V2G_ROOT_CA.csr -extfile configs/v2gRootCACert.cnf -extensions ext -signkey $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password $SHA -set_serial 12345 -out $CA_V2G_PATH/V2G_ROOT_CA.pem -days $VALIDITY_V2G_ROOT_CERT + +# 2) Create an intermediate CPO sub-CA 1 certificate which is directly signed +# by the V2G_ROOT_CA certificate +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_CSO_PATH/CPO_SUB_CA1.key +openssl req -new -key $CLIENT_CSO_PATH/CPO_SUB_CA1.key -passin pass:$password -config configs/cpoSubCA1Cert.cnf -out $CSR_PATH/CPO_SUB_CA1.csr +openssl x509 -req -in $CSR_PATH/CPO_SUB_CA1.csr -extfile configs/cpoSubCA1Cert.cnf -extensions ext -CA $CA_V2G_PATH/V2G_ROOT_CA.pem -CAkey $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password -set_serial 12346 -out $CA_CSO_PATH/CPO_SUB_CA1.pem -days $VALIDITY_CPO_SUBCA1_CERT + +# 3) Create a second intermediate CPO sub-CA certificate (sub-CA 2) just the way +# the previous intermedia certificate was created, which is directly signed +# by the CPO_SUB_CA1 +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_CSO_PATH/CPO_SUB_CA2.key +openssl req -new -key $CLIENT_CSO_PATH/CPO_SUB_CA2.key -passin pass:$password -config configs/cpoSubCA2Cert.cnf -out $CSR_PATH/CPO_SUB_CA2.csr +openssl x509 -req -in $CSR_PATH/CPO_SUB_CA2.csr -extfile configs/cpoSubCA2Cert.cnf -extensions ext -CA $CA_CSO_PATH/CPO_SUB_CA1.pem -CAkey $CLIENT_CSO_PATH/CPO_SUB_CA1.key -passin pass:$password -set_serial 12347 -days $VALIDITY_CPO_SUBCA2_CERT -out $CA_CSO_PATH/CPO_SUB_CA2.pem + +# 4) Create an SECC certificate, which is the leaf certificate belonging to +# the charging station that authenticates itself to the EVCC during a TLS +# handshake, signed by CPO_SUB_CA2 +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_CSO_PATH/SECC_LEAF.key +openssl req -new -key $CLIENT_CSO_PATH/SECC_LEAF.key -passin pass:$password -config configs/seccLeafCert.cnf -out $CSR_PATH/SECC_LEAF.csr +openssl x509 -req -in $CSR_PATH/SECC_LEAF.csr -extfile configs/seccLeafCert.cnf -extensions ext -CA $CA_CSO_PATH/CPO_SUB_CA2.pem -CAkey $CLIENT_CSO_PATH/CPO_SUB_CA2.key -passin pass:$password -set_serial 12348 -days $VALIDITY_SECC_LEAF_CERT -out $CLIENT_CSO_PATH/SECC_LEAF.pem +cat $CLIENT_CSO_PATH/SECC_LEAF.pem $CA_CSO_PATH/CPO_SUB_CA2.pem $CA_CSO_PATH/CPO_SUB_CA1.pem > $CLIENT_CSO_PATH/CPO_CERT_CHAIN.pem + +# 5) Create a self-signed OEM_ROOT_CA certificate (validity is up to the OEM) +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_ROOT_CA.key +openssl req -new -key $CLIENT_OEM_PATH/OEM_ROOT_CA.key -passin pass:$password -config configs/oemRootCACert.cnf -out $CSR_PATH/OEM_ROOT_CA.csr +openssl x509 -req -in $CSR_PATH/OEM_ROOT_CA.csr -extfile configs/oemRootCACert.cnf -extensions ext -signkey $CLIENT_OEM_PATH/OEM_ROOT_CA.key -passin pass:$password $SHA -set_serial 12349 -out $CA_OEM_PATH/OEM_ROOT_CA.pem -days $VALIDITY_OEM_ROOT_CERT + +# 6) Create an intermediate OEM sub-CA certificate, which is directly signed by +# the OEM_ROOT_CA certificate (validity is up to the OEM) +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_SUB_CA1.key +openssl req -new -key $CLIENT_OEM_PATH/OEM_SUB_CA1.key -passin pass:$password -config configs/oemSubCA1Cert.cnf -out $CSR_PATH/OEM_SUB_CA1.csr +openssl x509 -req -in $CSR_PATH/OEM_SUB_CA1.csr -extfile configs/oemSubCA1Cert.cnf -extensions ext -CA $CA_OEM_PATH/OEM_ROOT_CA.pem -CAkey $CLIENT_OEM_PATH/OEM_ROOT_CA.key -passin pass:$password -set_serial 12350 -days $VALIDITY_OEM_SUBCA1_CERT -out $CA_OEM_PATH/OEM_SUB_CA1.pem + +# 7) Create a second intermediate OEM sub-CA certificate, which is directly +# signed by the OEM_SUB_CA1 certificate (validity is up to the OEM) +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_SUB_CA2.key +openssl req -new -key $CLIENT_OEM_PATH/OEM_SUB_CA2.key -passin pass:$password -config configs/oemSubCA2Cert.cnf -out $CSR_PATH/OEM_SUB_CA2.csr +openssl x509 -req -in $CSR_PATH/OEM_SUB_CA2.csr -extfile configs/oemSubCA2Cert.cnf -extensions ext -CA $CA_OEM_PATH/OEM_SUB_CA1.pem -CAkey $CLIENT_OEM_PATH/OEM_SUB_CA1.key -passin pass:$password -set_serial 12351 -days $VALIDITY_OEM_SUBCA2_CERT -out $CA_OEM_PATH/OEM_SUB_CA2.pem + +# 8) Create an OEM provisioning certificate, which is the leaf certificate +# belonging to the OEM certificate chain (used for contract certificate +# installation) +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_OEM_PATH/OEM_LEAF.key +openssl req -new -key $CLIENT_OEM_PATH/OEM_LEAF.key -passin pass:$password -config configs/oemLeafCert.cnf -out $CSR_PATH/OEM_LEAF.csr +openssl x509 -req -in $CSR_PATH/OEM_LEAF.csr -extfile configs/oemLeafCert.cnf -extensions ext -CA $CA_OEM_PATH/OEM_SUB_CA2.pem -CAkey $CLIENT_OEM_PATH/OEM_SUB_CA2.key -passin pass:$password -set_serial 12352 -days $VALIDITY_OEM_LEAF_CERT -out $CLIENT_OEM_PATH/OEM_LEAF.pem +cat $CLIENT_OEM_PATH/OEM_LEAF.pem $CA_OEM_PATH/OEM_SUB_CA2.pem $CA_OEM_PATH/OEM_SUB_CA1.pem > $CA_OEM_PATH/OEM_CERT_CHAIN.pem + +# 16) Create an intermediate vehicle sub-CA 1 certificate which is directly signed +# by the V2GRootCA certificate +# --------------------------------------------------------------------------- +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA1.key +openssl req -new -key $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA1.key -passin pass:$password -config configs/vehicleSubCA1Cert.cnf -out $CSR_PATH/VEHICLE_SUB_CA1.csr +openssl x509 -req -in $CSR_PATH/VEHICLE_SUB_CA1.csr -extfile configs/vehicleSubCA1Cert.cnf -extensions ext -CA $CA_V2G_PATH/V2G_ROOT_CA.pem -CAkey $CLIENT_V2G_PATH/V2G_ROOT_CA.key -passin pass:$password -set_serial 12360 -out $CA_VEHICLE_PATH/VEHICLE_SUB_CA1.pem -days $VALIDITY_VEHICLE_SUBCA1_CERT + +# 17) Create a second intermediate vehicle sub-CA certificate (sub-CA 2) just the way +# the previous intermedia certificate was created, which is directly signed +# by the VEHICLE_SUB_CA1 +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA2.key +openssl req -new -key $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA2.key -passin pass:$password -config configs/vehicleSubCA2Cert.cnf -out $CSR_PATH/VEHICLE_SUB_CA2.csr +openssl x509 -req -in $CSR_PATH/VEHICLE_SUB_CA2.csr -extfile configs/vehicleSubCA2Cert.cnf -extensions ext -CA $CA_VEHICLE_PATH/VEHICLE_SUB_CA1.pem -CAkey $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA1.key -passin pass:$password -set_serial 12361 -days $VALIDITY_VEHICLE_SUBCA2_CERT -out $CA_VEHICLE_PATH/VEHICLE_SUB_CA2.pem + +# 18) Create an vehicle certificate, which is the leaf certificate belonging to +# the electric vehicle that authenticates itself to the SECC during a TLS +# handshake, signed by vehicleSubCA2 +openssl ecparam -genkey -name $EC_CURVE | openssl ec $SYMMETRIC_CIPHER -passout pass:$password -out $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.key +openssl req -new -key $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.key -passin pass:$password -config configs/vehicleLeafCert.cnf -out $CSR_PATH/VEHICLE_LEAF.csr +openssl x509 -req -in $CSR_PATH/VEHICLE_LEAF.csr -extfile configs/vehicleLeafCert.cnf -extensions ext -CA $CA_VEHICLE_PATH/VEHICLE_SUB_CA2.pem -CAkey $CLIENT_VEHICLE_PATH/VEHICLE_SUB_CA2.key -passin pass:$password -set_serial 12362 -days $VALIDITY_VEHICLE_LEAF_CERT -out $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.pem +cat $CLIENT_VEHICLE_PATH/VEHICLE_LEAF.pem $CA_VEHICLE_PATH/VEHICLE_SUB_CA2.pem $CA_VEHICLE_PATH/VEHICLE_SUB_CA1.pem > $CA_VEHICLE_PATH/VEHICLE_CERT_CHAIN.pem + +# 19) Place all passwords to generated private keys in separate text files. +# In this script, even though we use a single password for all certificates, +# certificates from a different source could have been generated with a different +# passphrase/passkey/password altogether. Leave them empty if no password is required. +echo $password > $CLIENT_CSO_PATH/SECC_LEAF_PASSWORD.txt +echo $password > $CLIENT_OEM_PATH/OEM_LEAF_PASSWORD.txt +echo $password > $CLIENT_V2G_PATH/V2G_ROOT_CA_PASSWORD.txt +echo $password > $CLIENT_VEHICLE_PATH/VEHICLE_LEAF_PASSWORD.txt