From d82d23acd0035d5e543b0b4623fd8cb2fa912a52 Mon Sep 17 00:00:00 2001 From: Miguel Company Date: Mon, 8 Apr 2024 19:26:01 +0200 Subject: [PATCH] Add pkcs11 support to get_security_files (#66) * Adding overload of get_security_files. * Implement changes to support .p11 files. * Security tests converted into TEST_P * Adding pkcs11 specific tests. Signed-off-by: Miguel Company --- .../include/rmw_dds_common/security.hpp | 32 ++ rmw_dds_common/src/security.cpp | 112 ++++++- rmw_dds_common/test/test_security.cpp | 292 ++++++++++++++++-- 3 files changed, 391 insertions(+), 45 deletions(-) diff --git a/rmw_dds_common/include/rmw_dds_common/security.hpp b/rmw_dds_common/include/rmw_dds_common/security.hpp index 3b9f0f5..fb3b14d 100644 --- a/rmw_dds_common/include/rmw_dds_common/security.hpp +++ b/rmw_dds_common/include/rmw_dds_common/security.hpp @@ -52,6 +52,38 @@ bool get_security_files( const std::string & prefix, const std::string & secure_root, std::unordered_map & result); +/// Get the set of security files in a security enclave. +/** + * This function will look through the passed in 'secure root' + * for a set of required filenames that must be in the enclave. + * If any of the required filenames are missing, the 'result' + * will be empty and the function will return false. + * If all of the required filenames are present, then this function + * will fill in the 'result' map with a key-value pair of + * friendy name -> filename. If the prefix is not empty, then + * the prefix will be applied to the filename. + * + * The friendly names that this function will currently fill in are: + * IDENTITY_CA + * CERTIFICATE + * PRIVATE_KEY + * PERMISSIONS_CA + * GOVERNANCE + * PERMISSIONS + * + * \param[in] supports_pkcs11 Whether the RMW has support for PKCS#11 URIs. + * \param[in] prefix An optional prefix to apply to the filenames when storing them. + * \param[in] secure_root The path to the security enclave to look at. + * \param[out] result The map where the friendly name -> filename pairs are stored. + * \return `true` if all required files exist in the security enclave, `false` otherwise. + */ +RMW_DDS_COMMON_PUBLIC +bool get_security_files( + bool supports_pkcs11, + const std::string & prefix, + const std::string & secure_root, + std::unordered_map & result); + } // namespace rmw_dds_common #endif // RMW_DDS_COMMON__SECURITY_HPP_ diff --git a/rmw_dds_common/src/security.cpp b/rmw_dds_common/src/security.cpp index 701ebba..c480d1a 100644 --- a/rmw_dds_common/src/security.cpp +++ b/rmw_dds_common/src/security.cpp @@ -13,41 +13,127 @@ // limitations under the License. #include +#include +#include #include #include #include +#include #include "rmw_dds_common/security.hpp" namespace rmw_dds_common { +// Processor for security attributes with FILE URI +static bool process_file_uri_security_file( + bool /*supports_pkcs11*/, + const std::string & prefix, + const std::filesystem::path & full_path, + std::string & result) +{ + if (!std::filesystem::is_regular_file(full_path)) { + return false; + } + result = prefix + full_path.generic_string(); + return true; +} + +// Processor for security attributes with PKCS#11 URI +static bool process_pkcs_uri_security_file( + bool supports_pkcs11, + const std::string & /*prefix*/, + const std::filesystem::path & full_path, + std::string & result) +{ + if (!supports_pkcs11) { + return false; + } + + const std::string p11_prefix("pkcs11:"); + + std::ifstream ifs(full_path); + if (!ifs.is_open()) { + return false; + } + + if (!(ifs >> result)) { + return false; + } + if (result.find(p11_prefix) != 0) { + return false; + } + + return true; +} + bool get_security_files( const std::string & prefix, const std::string & secure_root, std::unordered_map & result) { - const std::unordered_map required_files{ - {"IDENTITY_CA", "identity_ca.cert.pem"}, - {"CERTIFICATE", "cert.pem"}, - {"PRIVATE_KEY", "key.pem"}, - {"PERMISSIONS_CA", "permissions_ca.cert.pem"}, - {"GOVERNANCE", "governance.p7s"}, - {"PERMISSIONS", "permissions.p7s"}, + return get_security_files(false, prefix, secure_root, result); +} + +bool get_security_files( + bool supports_pkcs11, + const std::string & prefix, + const std::string & secure_root, + std::unordered_map & result) +{ + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + using std::placeholders::_4; + using security_file_processor = + std::function; + using processor_vector = + std::vector>; + + // Key: the security attribute + // Value: ordered sequence of pairs. Each pair contains one possible file name + // for the attribute and the corresponding processor method + // Pairs are ordered by priority: the first one matching is used. + const std::unordered_map required_files{ + {"IDENTITY_CA", { + {"identity_ca.cert.p11", std::bind(process_pkcs_uri_security_file, _1, _2, _3, _4)}, + {"identity_ca.cert.pem", std::bind(process_file_uri_security_file, _1, _2, _3, _4)}}}, + {"CERTIFICATE", { + {"cert.p11", std::bind(process_pkcs_uri_security_file, _1, _2, _3, _4)}, + {"cert.pem", std::bind(process_file_uri_security_file, _1, _2, _3, _4)}}}, + {"PRIVATE_KEY", { + {"key.p11", std::bind(process_pkcs_uri_security_file, _1, _2, _3, _4)}, + {"key.pem", std::bind(process_file_uri_security_file, _1, _2, _3, _4)}}}, + {"PERMISSIONS_CA", { + {"permissions_ca.cert.p11", std::bind(process_pkcs_uri_security_file, _1, _2, _3, _4)}, + {"permissions_ca.cert.pem", std::bind(process_file_uri_security_file, _1, _2, _3, _4)}}}, + {"GOVERNANCE", { + {"governance.p7s", std::bind(process_file_uri_security_file, _1, _2, _3, _4)}}}, + {"PERMISSIONS", { + {"permissions.p7s", std::bind(process_file_uri_security_file, _1, _2, _3, _4)}}}, }; const std::unordered_map optional_files{ {"CRL", "crl.pem"}, }; - for (const std::pair & el : required_files) { - std::filesystem::path full_path(secure_root); - full_path /= el.second; - if (!std::filesystem::is_regular_file(full_path)) { + for (const std::pair>> & el : required_files) + { + std::string attribute_value; + bool processed = false; + for (auto & proc : el.second) { + std::filesystem::path full_path(secure_root); + full_path /= proc.first; + if (proc.second(supports_pkcs11, prefix, full_path, attribute_value)) { + processed = true; + break; + } + } + if (!processed) { result.clear(); return false; } - - result[el.first] = prefix + full_path.generic_string(); + result[el.first] = attribute_value; } for (const std::pair & el : optional_files) { diff --git a/rmw_dds_common/test/test_security.cpp b/rmw_dds_common/test/test_security.cpp index e1fa36c..e6ac3a3 100644 --- a/rmw_dds_common/test/test_security.cpp +++ b/rmw_dds_common/test/test_security.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -22,7 +23,33 @@ #include "rmw_dds_common/security.hpp" -TEST(test_security, files_exist_no_prefix) +// Utility to write test content on required files +template +static void write_test_content(const std::array & required_files) +{ + for (const std::string & filename : required_files) { + std::filesystem::path full_path = std::filesystem::path("./test_folder") / filename; + std::ofstream output_buffer{full_path.generic_string()}; + output_buffer << "test"; + ASSERT_TRUE(std::filesystem::exists(full_path)); + } +} + +// Utility to write pkcs11 content on required files +template +static void write_test_pkcs11_content(const std::array & pkcs11_files) +{ + for (const std::string & filename : pkcs11_files) { + std::filesystem::path full_path = std::filesystem::path("./test_folder") / filename; + std::ofstream output_buffer{full_path.generic_string()}; + output_buffer << "pkcs11://" << filename; + ASSERT_TRUE(std::filesystem::exists(full_path)); + } +} + +class test_security : public ::testing::TestWithParam {}; + +TEST_P(test_security, files_exist_no_prefix) { std::filesystem::path dir = std::filesystem::path("./test_folder"); std::filesystem::remove_all(dir); @@ -34,15 +61,11 @@ TEST(test_security, files_exist_no_prefix) "identity_ca.cert.pem", "cert.pem", "key.pem", "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s" }; - for (const std::string & filename : required_files) { - std::filesystem::path full_path = dir / filename; - std::ofstream output_buffer{full_path.generic_string()}; - output_buffer << "test"; - ASSERT_TRUE(std::filesystem::exists(full_path)); - } + write_test_content(required_files); std::unordered_map security_files; - ASSERT_TRUE(rmw_dds_common::get_security_files("", dir.generic_string(), security_files)); + ASSERT_TRUE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); EXPECT_EQ( security_files["IDENTITY_CA"], @@ -64,7 +87,7 @@ TEST(test_security, files_exist_no_prefix) std::filesystem::path("./test_folder/permissions.p7s").generic_string()); } -TEST(test_security, files_exist_with_prefix) +TEST_P(test_security, files_exist_with_prefix) { std::filesystem::path dir = std::filesystem::path("./test_folder"); std::filesystem::remove_all(dir); @@ -76,15 +99,12 @@ TEST(test_security, files_exist_with_prefix) "identity_ca.cert.pem", "cert.pem", "key.pem", "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s" }; - for (const std::string & filename : required_files) { - std::filesystem::path full_path = dir / filename; - std::ofstream output_buffer{full_path.generic_string()}; - output_buffer << "test"; - ASSERT_TRUE(std::filesystem::exists(full_path)); - } + write_test_content(required_files); std::unordered_map security_files; - ASSERT_TRUE(rmw_dds_common::get_security_files("file://", dir.generic_string(), security_files)); + ASSERT_TRUE( + rmw_dds_common::get_security_files( + GetParam(), "file://", dir.generic_string(), security_files)); EXPECT_EQ( security_files["IDENTITY_CA"], @@ -106,7 +126,7 @@ TEST(test_security, files_exist_with_prefix) "file://" + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); } -TEST(test_security, file_missing) +TEST_P(test_security, file_missing) { std::filesystem::path dir = std::filesystem::path("./test_folder"); std::filesystem::remove_all(dir); @@ -118,19 +138,15 @@ TEST(test_security, file_missing) "identity_ca.cert.pem", "cert.pem", "key.pem", "permissions_ca.cert.pem", "governance.p7s" }; - for (const std::string & filename : required_files) { - std::filesystem::path full_path = dir / filename; - std::ofstream output_buffer{full_path.generic_string()}; - output_buffer << "test"; - ASSERT_TRUE(std::filesystem::exists(full_path)); - } + write_test_content(required_files); std::unordered_map security_files; - ASSERT_FALSE(rmw_dds_common::get_security_files("", dir.generic_string(), security_files)); + ASSERT_FALSE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); ASSERT_EQ(security_files.size(), 0UL); } -TEST(test_security, optional_file_exist) +TEST_P(test_security, optional_file_exist) { std::filesystem::path dir = std::filesystem::path("./test_folder"); std::filesystem::remove_all(dir); @@ -142,15 +158,11 @@ TEST(test_security, optional_file_exist) "identity_ca.cert.pem", "cert.pem", "key.pem", "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s", "crl.pem", }; - for (const std::string & filename : required_files) { - std::filesystem::path full_path = dir / filename; - std::ofstream output_buffer{full_path.generic_string()}; - output_buffer << "test"; - ASSERT_TRUE(std::filesystem::exists(full_path)); - } + write_test_content(required_files); std::unordered_map security_files; - ASSERT_TRUE(rmw_dds_common::get_security_files("", dir.generic_string(), security_files)); + ASSERT_TRUE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); EXPECT_EQ( security_files["IDENTITY_CA"], @@ -175,3 +187,219 @@ TEST(test_security, optional_file_exist) security_files["CRL"], std::filesystem::path("./test_folder/crl.pem").generic_string()); } + +TEST_P(test_security, wrong_pkcs11_file_ignored) +{ + std::filesystem::path dir = std::filesystem::path("./test_folder"); + std::filesystem::remove_all(dir); + EXPECT_TRUE(std::filesystem::create_directories(dir)); + EXPECT_TRUE(std::filesystem::exists(dir)); + EXPECT_TRUE(std::filesystem::is_directory(dir)); + + std::array required_files = { + "identity_ca.cert.pem", "cert.pem", "key.pem", + "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s", + "identity_ca.cert.p11", "cert.p11", "key.p11", + "permissions_ca.cert.p11" + }; + write_test_content(required_files); + + std::unordered_map security_files; + ASSERT_TRUE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); + + EXPECT_EQ( + security_files["IDENTITY_CA"], + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + security_files["CERTIFICATE"], + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + security_files["PRIVATE_KEY"], + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS_CA"], + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + EXPECT_EQ( + security_files["GOVERNANCE"], + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS"], + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); +} + +TEST_P(test_security, pkcs11_support_check) +{ + std::filesystem::path dir = std::filesystem::path("./test_folder"); + std::filesystem::remove_all(dir); + EXPECT_TRUE(std::filesystem::create_directories(dir)); + EXPECT_TRUE(std::filesystem::exists(dir)); + EXPECT_TRUE(std::filesystem::is_directory(dir)); + + std::array required_files = { + "identity_ca.cert.pem", "cert.pem", "key.pem", + "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s" + }; + write_test_content(required_files); + + std::array pkcs11_files = { + "identity_ca.cert.p11", "cert.p11", "key.p11", + "permissions_ca.cert.p11" + }; + write_test_pkcs11_content(pkcs11_files); + + std::unordered_map security_files; + ASSERT_TRUE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); + + if (GetParam()) { + EXPECT_EQ( + security_files["IDENTITY_CA"], + "pkcs11://identity_ca.cert.p11"); + EXPECT_EQ( + security_files["CERTIFICATE"], + "pkcs11://cert.p11"); + EXPECT_EQ( + security_files["PRIVATE_KEY"], + "pkcs11://key.p11"); + EXPECT_EQ( + security_files["PERMISSIONS_CA"], + "pkcs11://permissions_ca.cert.p11"); + } else { + EXPECT_EQ( + security_files["IDENTITY_CA"], + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + security_files["CERTIFICATE"], + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + security_files["PRIVATE_KEY"], + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS_CA"], + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + } + EXPECT_EQ( + security_files["GOVERNANCE"], + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS"], + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); +} + +TEST_P(test_security, only_pkcs11_present) +{ + std::filesystem::path dir = std::filesystem::path("./test_folder"); + std::filesystem::remove_all(dir); + EXPECT_TRUE(std::filesystem::create_directories(dir)); + EXPECT_TRUE(std::filesystem::exists(dir)); + EXPECT_TRUE(std::filesystem::is_directory(dir)); + + std::array required_files = { + "governance.p7s", "permissions.p7s" + }; + write_test_content(required_files); + + std::array pkcs11_files = { + "identity_ca.cert.p11", "cert.p11", "key.p11", + "permissions_ca.cert.p11" + }; + write_test_pkcs11_content(pkcs11_files); + + std::unordered_map security_files; + + if (GetParam()) { + ASSERT_TRUE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); + EXPECT_EQ( + security_files["IDENTITY_CA"], + "pkcs11://identity_ca.cert.p11"); + EXPECT_EQ( + security_files["CERTIFICATE"], + "pkcs11://cert.p11"); + EXPECT_EQ( + security_files["PRIVATE_KEY"], + "pkcs11://key.p11"); + EXPECT_EQ( + security_files["PERMISSIONS_CA"], + "pkcs11://permissions_ca.cert.p11"); + EXPECT_EQ( + security_files["GOVERNANCE"], + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS"], + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + } else { + ASSERT_FALSE( + rmw_dds_common::get_security_files(GetParam(), "", dir.generic_string(), security_files)); + ASSERT_EQ(security_files.size(), 0UL); + } +} + +TEST_P(test_security, pkcs11_prefix_ignored) +{ + std::filesystem::path dir = std::filesystem::path("./test_folder"); + std::filesystem::remove_all(dir); + EXPECT_TRUE(std::filesystem::create_directories(dir)); + EXPECT_TRUE(std::filesystem::exists(dir)); + EXPECT_TRUE(std::filesystem::is_directory(dir)); + + std::array required_files = { + "identity_ca.cert.pem", "cert.pem", "key.pem", + "permissions_ca.cert.pem", "governance.p7s", "permissions.p7s" + }; + write_test_content(required_files); + + std::array pkcs11_files = { + "identity_ca.cert.p11", "cert.p11", "key.p11", + "permissions_ca.cert.p11" + }; + write_test_pkcs11_content(pkcs11_files); + + std::unordered_map security_files; + ASSERT_TRUE( + rmw_dds_common::get_security_files( + GetParam(), "file://", dir.generic_string(), security_files)); + + if (GetParam()) { + EXPECT_EQ( + security_files["IDENTITY_CA"], + "pkcs11://identity_ca.cert.p11"); + EXPECT_EQ( + security_files["CERTIFICATE"], + "pkcs11://cert.p11"); + EXPECT_EQ( + security_files["PRIVATE_KEY"], + "pkcs11://key.p11"); + EXPECT_EQ( + security_files["PERMISSIONS_CA"], + "pkcs11://permissions_ca.cert.p11"); + } else { + EXPECT_EQ( + security_files["IDENTITY_CA"], + "file://" + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + security_files["CERTIFICATE"], + "file://" + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + security_files["PRIVATE_KEY"], + "file://" + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS_CA"], + "file://" + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + } + EXPECT_EQ( + security_files["GOVERNANCE"], + "file://" + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + security_files["PERMISSIONS"], + "file://" + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); +} + +INSTANTIATE_TEST_SUITE_P( + test_security, + test_security, + ::testing::Values(false, true), + [](const testing::TestParamInfo & info) { + return info.param ? "with_pkcs11_support" : "with_no_pkcs11_support"; + });