diff --git a/lib/evse_security/evse_security.cpp b/lib/evse_security/evse_security.cpp index 1420a90..6a790a6 100644 --- a/lib/evse_security/evse_security.cpp +++ b/lib/evse_security/evse_security.cpp @@ -1109,7 +1109,7 @@ GetCertificateFullInfoResult EvseSecurity::get_all_valid_certificates_info(LeafC // returns them for (const auto& chain : result.info) { // Ignore non-root items - if(!chain.certificate_root.has_value()) { + if (!chain.certificate_root.has_value()) { continue; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 43e4489..9443eb1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME}) install( PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/generate_test_certs.sh" + PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/generate_test_certs_multi.sh" DESTINATION "${CMAKE_BINARY_DIR}/tests" ) diff --git a/tests/generate_test_certs.sh b/tests/generate_test_certs.sh index 43214a2..00cd0e0 100755 --- a/tests/generate_test_certs.sh +++ b/tests/generate_test_certs.sh @@ -74,13 +74,8 @@ create_certificate SECC_LEAF "${CLIENT_CSO_PATH}" seccLeafCert.cnf 12348 "${CA_C # Invalid self-signed CSMS cert create_certificate INVALID_CSMS "${CLIENT_INVALID_PATH}" v2gRootCACert.cnf 12345 -# V2G alternate root CA -create_certificate V2G_ROOT_GRIDSYNC_CA "${CA_V2G_PATH}" v2gRootCACert_Alternate.cnf 12345 -# Alternate chargepoint leaf -create_certificate SECC_LEAF_GRIDSYNC "${CLIENT_CSMS_PATH}" seccLeafCert_Alternate.cnf 12348 "${CA_V2G_PATH}/V2G_ROOT_GRIDSYNC_CA.pem" "${CA_V2G_PATH}/V2G_ROOT_GRIDSYNC_CA.key" - # create cert chain bundles in the V2G root ca and chargepoint leaf dirs -cat "$CA_CSMS_PATH/CPO_SUB_CA2.pem" "$CA_CSMS_PATH/CPO_SUB_CA1.pem" "$CA_V2G_PATH/V2G_ROOT_CA.pem" "$CA_V2G_PATH/V2G_ROOT_GRIDSYNC_CA.pem" > "$CA_V2G_PATH/V2G_CA_BUNDLE.pem" +cat "$CA_CSMS_PATH/CPO_SUB_CA2.pem" "$CA_CSMS_PATH/CPO_SUB_CA1.pem" "$CA_V2G_PATH/V2G_ROOT_CA.pem" > "$CA_V2G_PATH/V2G_CA_BUNDLE.pem" cat "$CLIENT_CSO_PATH/SECC_LEAF.pem" "$CA_CSMS_PATH/CPO_SUB_CA2.pem" "$CA_CSMS_PATH/CPO_SUB_CA1.pem" > "$CLIENT_CSO_PATH/CPO_CERT_CHAIN.pem" cp "$CLIENT_CSO_PATH/SECC_LEAF.key" "$CLIENT_CSMS_PATH/CSMS_LEAF.key" diff --git a/tests/generate_test_certs_multi.sh b/tests/generate_test_certs_multi.sh new file mode 100755 index 0000000..43214a2 --- /dev/null +++ b/tests/generate_test_certs_multi.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +CERT_PATH="./certs" +CSR_PATH="./csr" + +EC_CURVE=prime256v1 +SYMMETRIC_CIPHER=-aes-128-cbc +password="123456" + +CA_CSMS_PATH="$CERT_PATH/ca/csms" +CA_CSO_PATH="$CERT_PATH/ca/cso" +CA_V2G_PATH="$CERT_PATH/ca/v2g" +CA_MO_PATH="$CERT_PATH/ca/mo" +CA_INVALID_PATH="$CERT_PATH/ca/invalid" + +CLIENT_CSMS_PATH="$CERT_PATH/client/csms" +CLIENT_CSO_PATH="$CERT_PATH/client/cso" +CLIENT_V2G_PATH="$CERT_PATH/client/v2g" +CLIENT_INVALID_PATH="$CERT_PATH/client/invalid" +VALIDITY=3650 + +TO_BE_INSTALLED_PATH="$CERT_PATH/to_be_installed" + +mkdir -p "$CERT_PATH" +mkdir -p "$CSR_PATH" +mkdir -p "$CA_CSMS_PATH" +mkdir -p "$CA_CSO_PATH" +mkdir -p "$CA_V2G_PATH" +mkdir -p "$CA_MO_PATH" +mkdir -p "$CLIENT_CSMS_PATH" +mkdir -p "$CLIENT_CSO_PATH" +mkdir -p "$CLIENT_V2G_PATH" +mkdir -p "$CLIENT_INVALID_PATH" +mkdir -p "$TO_BE_INSTALLED_PATH" + +function create_certificate() { + # Args: + # $1: name of the certificate (without the .pem extension) + # $2: directory to install the certificate and private key into + # $3: openssl config file for the certificate + # $4: serial number for the certificate + # $5: CA certificate file. If this is missing, we will create a self-signed certificate. + # $6: CA private key file. Likewise omit this to create a self-signed certificate. + + local name="$1" + local install_dir="$2" + local config="$3" + local serial_num="$4" + local signed_by_cert="$5" + local signed_by_key="$6" + + openssl ecparam -genkey -name "$EC_CURVE" | openssl ec "$SYMMETRIC_CIPHER" -passout pass:"$password" -out "${install_dir}/${name}.key" + + if [ -z $signed_by_cert ] + then + openssl req -new -key "${install_dir}/${name}.key" -passin pass:"$password" -config "configs/${config}" -out "${CSR_PATH}/${name}.csr" + openssl x509 -req -in "${CSR_PATH}/${name}.csr" -extfile "configs/${config}" -extensions ext -signkey "${install_dir}/${name}.key" -passin pass:"$password" $SHA -set_serial "${serial_num}" -out "${install_dir}/${name}.pem" -days "$VALIDITY" + else + openssl req -new -key "${install_dir}/${name}.key" -passin pass:"$password" -config "configs/${config}" -out "${CSR_PATH}/${name}.csr" + openssl x509 -req -in "${CSR_PATH}/${name}.csr" -extfile "configs/${config}" -extensions ext -CA "${signed_by_cert}" -CAkey "${signed_by_key}" -passin pass:"$password" -set_serial "${serial_num}" -out "${install_dir}/${name}.pem" -days "$VALIDITY" + fi +} + +# V2G root CA +create_certificate V2G_ROOT_CA "${CA_V2G_PATH}" v2gRootCACert.cnf 12345 +# Second V2G root CA +create_certificate V2G_ROOT_CA_NEW "${CA_V2G_PATH}" v2gRootCACert.cnf 12349 +# Sub-CA 1 +create_certificate CPO_SUB_CA1 "${CA_CSMS_PATH}" cpoSubCA1Cert.cnf 12346 "${CA_V2G_PATH}/V2G_ROOT_CA.pem" "${CA_V2G_PATH}/V2G_ROOT_CA.key" +# Sub-CA 2 +create_certificate CPO_SUB_CA2 "${CA_CSMS_PATH}" cpoSubCA2Cert.cnf 12347 "${CA_CSMS_PATH}/CPO_SUB_CA1.pem" "${CA_CSMS_PATH}/CPO_SUB_CA1.key" +# Chargepoint leaf +create_certificate SECC_LEAF "${CLIENT_CSO_PATH}" seccLeafCert.cnf 12348 "${CA_CSMS_PATH}/CPO_SUB_CA2.pem" "${CA_CSMS_PATH}/CPO_SUB_CA2.key" +# Invalid self-signed CSMS cert +create_certificate INVALID_CSMS "${CLIENT_INVALID_PATH}" v2gRootCACert.cnf 12345 + +# V2G alternate root CA +create_certificate V2G_ROOT_GRIDSYNC_CA "${CA_V2G_PATH}" v2gRootCACert_Alternate.cnf 12345 +# Alternate chargepoint leaf +create_certificate SECC_LEAF_GRIDSYNC "${CLIENT_CSMS_PATH}" seccLeafCert_Alternate.cnf 12348 "${CA_V2G_PATH}/V2G_ROOT_GRIDSYNC_CA.pem" "${CA_V2G_PATH}/V2G_ROOT_GRIDSYNC_CA.key" + +# create cert chain bundles in the V2G root ca and chargepoint leaf dirs +cat "$CA_CSMS_PATH/CPO_SUB_CA2.pem" "$CA_CSMS_PATH/CPO_SUB_CA1.pem" "$CA_V2G_PATH/V2G_ROOT_CA.pem" "$CA_V2G_PATH/V2G_ROOT_GRIDSYNC_CA.pem" > "$CA_V2G_PATH/V2G_CA_BUNDLE.pem" +cat "$CLIENT_CSO_PATH/SECC_LEAF.pem" "$CA_CSMS_PATH/CPO_SUB_CA2.pem" "$CA_CSMS_PATH/CPO_SUB_CA1.pem" > "$CLIENT_CSO_PATH/CPO_CERT_CHAIN.pem" + +cp "$CLIENT_CSO_PATH/SECC_LEAF.key" "$CLIENT_CSMS_PATH/CSMS_LEAF.key" + +# assume CSO and CSMS are same authority +cp -r $CA_CSMS_PATH/* $CA_CSO_PATH +cp "$CLIENT_CSO_PATH/SECC_LEAF.pem" "$CLIENT_CSMS_PATH/CSMS_LEAF.pem" + +# empty MO bundle +touch "$CA_MO_PATH/MO_CA_BUNDLE.pem" + +# Create certificates used for installation tests +create_certificate INSTALL_TEST_ROOT_CA1 "${TO_BE_INSTALLED_PATH}" install_test.cnf 21234 +create_certificate INSTALL_TEST_ROOT_CA2 "${TO_BE_INSTALLED_PATH}" install_test.cnf 21235 +create_certificate INSTALL_TEST_ROOT_CA3 "${TO_BE_INSTALLED_PATH}" install_test.cnf 21236 +create_certificate INSTALL_TEST_ROOT_CA3_SUBCA1 "${TO_BE_INSTALLED_PATH}" install_test_subca1.cnf 21237 "${TO_BE_INSTALLED_PATH}/INSTALL_TEST_ROOT_CA3.pem" "${TO_BE_INSTALLED_PATH}/INSTALL_TEST_ROOT_CA3.key" +create_certificate INSTALL_TEST_ROOT_CA3_SUBCA2 "${TO_BE_INSTALLED_PATH}" install_test_subca2.cnf 21238 "${TO_BE_INSTALLED_PATH}/INSTALL_TEST_ROOT_CA3_SUBCA1.pem" "${TO_BE_INSTALLED_PATH}/INSTALL_TEST_ROOT_CA3_SUBCA1.key" diff --git a/tests/tests.cpp b/tests/tests.cpp index 5f6fb87..d84f455 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -50,10 +50,6 @@ bool equal_certificate_strings(const std::string& cert1, const std::string& cert return true; } -void install_certs() { - std::system("./generate_test_certs.sh"); -} - namespace evse_security { class EvseSecurityTests : public ::testing::Test { @@ -86,6 +82,18 @@ class EvseSecurityTests : public ::testing::Test { fs::remove_all("certs"); fs::remove_all("csr"); } + + virtual void install_certs() { + std::system("./generate_test_certs.sh"); + } +}; + + +class EvseSecurityTestsMulti : public EvseSecurityTests { +protected: + void install_certs() override { + std::system("./generate_test_certs_multi.sh"); + } }; class EvseSecurityTestsExpired : public EvseSecurityTests { @@ -173,14 +181,6 @@ class EvseSecurityTestsExpired : public EvseSecurityTests { } }; -TEST_F(EvseSecurityTests, verify_multi_root_leaf_retrieval) { - auto result = this->evse_security->get_all_valid_certificates_info(LeafCertificateType::CSMS, EncodingFormat::PEM, false); - - ASSERT_EQ(result.status, GetCertificateInfoStatus::Accepted); - - std::cout << "Result size: " << result.info.size(); -} - TEST_F(EvseSecurityTests, verify_basics) { const char* bundle_path = "certs/ca/v2g/V2G_CA_BUNDLE.pem"; @@ -280,6 +280,27 @@ TEST_F(EvseSecurityTests, verify_certificate_counts) { ASSERT_EQ(this->evse_security->get_count_of_installed_certificates({CertificateType::MORootCertificate}), 0); } +TEST_F(EvseSecurityTestsMulti, verify_multi_root_leaf_retrieval) { + auto result = + this->evse_security->get_all_valid_certificates_info(LeafCertificateType::CSMS, EncodingFormat::PEM, false); + + ASSERT_EQ(result.status, GetCertificateInfoStatus::Accepted); + + // We have 2 leafs + ASSERT_EQ(result.info.size(), 2); + + ASSERT_EQ(fs::path("certs/client/csms/CSMS_LEAF.pem"), result.info[0].certificate_single.value()); + ASSERT_EQ(fs::path("certs/client/csms/SECC_LEAF_GRIDSYNC.pem"), result.info[1].certificate_single.value()); + + ASSERT_TRUE(result.info[0].certificate_root.has_value()); + ASSERT_TRUE(result.info[1].certificate_root.has_value()); + + ASSERT_TRUE(equal_certificate_strings(result.info[0].certificate_root.value(), + read_file_to_string("certs/ca/v2g/V2G_ROOT_CA.pem"))); + ASSERT_TRUE(equal_certificate_strings(result.info[1].certificate_root.value(), + read_file_to_string("certs/ca/v2g/V2G_ROOT_GRIDSYNC_CA.pem"))); +} + TEST_F(EvseSecurityTests, verify_normal_keygen) { KeyGenerationInfo info; KeyHandle_ptr key;