From d2b0798317aa56bd827461738cbfebbfa2100a7d Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Wed, 29 Jan 2025 12:26:34 +0100 Subject: [PATCH 1/5] Added rmw_security_common Signed-off-by: Alejandro Hernandez Cordero --- rmw_security_common/CMakeLists.txt | 77 +++ .../include/rmw_security_common/security.hpp | 99 ++++ .../rmw_security_common/visibility_control.h | 58 +++ rmw_security_common/package.xml | 26 + rmw_security_common/src/security.cpp | 166 ++++++ rmw_security_common/test/test_security.cpp | 479 ++++++++++++++++++ 6 files changed, 905 insertions(+) create mode 100644 rmw_security_common/CMakeLists.txt create mode 100644 rmw_security_common/include/rmw_security_common/security.hpp create mode 100644 rmw_security_common/include/rmw_security_common/visibility_control.h create mode 100644 rmw_security_common/package.xml create mode 100644 rmw_security_common/src/security.cpp create mode 100644 rmw_security_common/test/test_security.cpp diff --git a/rmw_security_common/CMakeLists.txt b/rmw_security_common/CMakeLists.txt new file mode 100644 index 00000000..5972ca72 --- /dev/null +++ b/rmw_security_common/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.5) + +project(rmw_security_common) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rcutils REQUIRED) +find_package(rmw REQUIRED) + +ament_add_default_options() +ament_export_dependencies(rcutils) + +add_library(${PROJECT_NAME}_library + src/security.cpp) + +set_target_properties(${PROJECT_NAME}_library + PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) +target_link_libraries(${PROJECT_NAME}_library PUBLIC + rcutils::rcutils + rmw::rmw) +target_include_directories(${PROJECT_NAME}_library + PUBLIC + "$" + "$") + +# Causes the visibility macros to use dllexport rather than dllimport, +# which is appropriate when building the dll but not consuming it. +target_compile_definitions(${PROJECT_NAME}_library + PRIVATE "RMW_SECURITY_COMMON_BUILDING_LIBRARY") + +install( + TARGETS ${PROJECT_NAME}_library EXPORT ${PROJECT_NAME}_library + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +# Export old-style CMake variables +ament_export_include_directories("include/${PROJECT_NAME}") +ament_export_libraries(${PROJECT_NAME}_library) + +# Export modern CMake targets +ament_export_targets(${PROJECT_NAME}_library) + +install( + DIRECTORY include/ + DESTINATION include/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + find_package(ament_cmake_gmock REQUIRED) + ament_lint_auto_find_test_dependencies() + + ament_add_gmock(test_security test/test_security.cpp) + if(TARGET test_security) + target_link_libraries(test_security + ${PROJECT_NAME}_library + rcutils::rcutils) + endif() +endif() + +ament_package() diff --git a/rmw_security_common/include/rmw_security_common/security.hpp b/rmw_security_common/include/rmw_security_common/security.hpp new file mode 100644 index 00000000..24ca52b8 --- /dev/null +++ b/rmw_security_common/include/rmw_security_common/security.hpp @@ -0,0 +1,99 @@ +// Copyright 2021 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RMW_SECURITY_COMMON__SECURITY_HPP_ +#define RMW_SECURITY_COMMON__SECURITY_HPP_ + +#include +#include + +#include "rcutils/types/string_map.h" +#include "rmw/types.h" +#include "rmw_security_common/visibility_control.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif + +/// 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] 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_SECURITY_COMMON_PUBLIC +rmw_ret_t get_security_files( + const char * prefix, + const char * secure_root, + rcutils_string_map_t * result); + // 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_SECURITY_COMMON_PUBLIC +rmw_ret_t get_security_files_support_pkcs( + bool supports_pkcs11, + const char * prefix, + const char * secure_root, + rcutils_string_map_t * result); + +#ifdef __cplusplus +} +#endif + +#endif // RMW_SECURITY_COMMON__SECURITY_HPP_ diff --git a/rmw_security_common/include/rmw_security_common/visibility_control.h b/rmw_security_common/include/rmw_security_common/visibility_control.h new file mode 100644 index 00000000..021c95b5 --- /dev/null +++ b/rmw_security_common/include/rmw_security_common/visibility_control.h @@ -0,0 +1,58 @@ +// Copyright 2019 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RMW_SECURITY_COMMON__VISIBILITY_CONTROL_H_ +#define RMW_SECURITY_COMMON__VISIBILITY_CONTROL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define RMW_SECURITY_COMMON_EXPORT __attribute__ ((dllexport)) + #define RMW_SECURITY_COMMON_IMPORT __attribute__ ((dllimport)) + #else + #define RMW_SECURITY_COMMON_EXPORT __declspec(dllexport) + #define RMW_SECURITY_COMMON_IMPORT __declspec(dllimport) + #endif + #ifdef RMW_SECURITY_COMMON_BUILDING_LIBRARY + #define RMW_SECURITY_COMMON_PUBLIC RMW_SECURITY_COMMON_EXPORT + #else + #define RMW_SECURITY_COMMON_PUBLIC RMW_SECURITY_COMMON_IMPORT + #endif + #define RMW_SECURITY_COMMON_PUBLIC_TYPE RMW_SECURITY_COMMON_PUBLIC + #define RMW_SECURITY_COMMON_LOCAL +#else + #define RMW_SECURITY_COMMON_EXPORT __attribute__ ((visibility("default"))) + #define RMW_SECURITY_COMMON_IMPORT + #if __GNUC__ >= 4 + #define RMW_SECURITY_COMMON_PUBLIC __attribute__ ((visibility("default"))) + #define RMW_SECURITY_COMMON_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define RMW_SECURITY_COMMON_PUBLIC + #define RMW_SECURITY_COMMON_LOCAL + #endif + #define RMW_SECURITY_COMMON_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // RMW_SECURITY_COMMON__VISIBILITY_CONTROL_H_ diff --git a/rmw_security_common/package.xml b/rmw_security_common/package.xml new file mode 100644 index 00000000..aa1be415 --- /dev/null +++ b/rmw_security_common/package.xml @@ -0,0 +1,26 @@ + + + + rmw_security_common + 7.5.1 + Define a common rmw secutiry utils + + Alejandro Hernandez Cordero + + Apache License 2.0 + + Alejandro Hernandez Cordero + + ament_cmake + + rcutils + rmw + + ament_cmake_gmock + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/rmw_security_common/src/security.cpp b/rmw_security_common/src/security.cpp new file mode 100644 index 00000000..a47364e7 --- /dev/null +++ b/rmw_security_common/src/security.cpp @@ -0,0 +1,166 @@ +// Copyright 2021 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include + +#include "rcutils/error_handling.h" +#include "rmw/types.h" + +#include "rmw_security_common/security.hpp" + +// 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; +} + +rmw_ret_t get_security_files( + const char * prefix, + const char * secure_root, + rcutils_string_map_t * result) +{ + return get_security_files_support_pkcs(false, prefix, secure_root, result); +} + +rmw_ret_t get_security_files_support_pkcs( + bool supports_pkcs11, + const char * prefix, + const char * secure_root, + rcutils_string_map_t * result) +{ + // check arguments that could be null + RCUTILS_CHECK_ARGUMENT_FOR_NULL(prefix, RMW_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(secure_root, RMW_RET_INVALID_ARGUMENT); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(result, RMW_RET_INVALID_ARGUMENT); + + std::string prefix_std(prefix); + std::string secure_root_std(secure_root); + + 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"}, + }; + + std::unordered_map result_std; + + 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_std); + full_path /= proc.first; + if (proc.second(supports_pkcs11, prefix_std, full_path, attribute_value)) { + processed = true; + break; + } + } + if (!processed) { + result_std.clear(); + return RMW_RET_ERROR; + } + result_std[el.first] = attribute_value; + } + + for (const std::pair & el : optional_files) { + std::filesystem::path full_path(secure_root_std); + full_path /= el.second; + if (std::filesystem::is_regular_file(full_path)) { + result_std[el.first] = prefix_std + full_path.generic_string(); + } + } + + rcutils_ret_t ret; + ret = rcutils_string_map_reserve(result, result_std.size()); + + for (const auto & [key, value] : result_std) { + ret = rcutils_string_map_set(result, key.c_str(), value.c_str()); + } + + return RMW_RET_OK; +} diff --git a/rmw_security_common/test/test_security.cpp b/rmw_security_common/test/test_security.cpp new file mode 100644 index 00000000..cc6e7624 --- /dev/null +++ b/rmw_security_common/test/test_security.cpp @@ -0,0 +1,479 @@ +// Copyright 2021 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +#include "rmw_security_common/security.hpp" + +#include "rcutils/allocator.h" + +// 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); + 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); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +TEST_P(test_security, files_exist_with_prefix) +{ + 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); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +TEST_P(test_security, file_missing) +{ + 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" + }; + write_test_content(required_files); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_ERROR, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + size_t capacity = 0; + ret = rcutils_string_map_get_capacity(&security_files, &capacity); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(capacity, 0UL); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +TEST_P(test_security, optional_file_exist) +{ + 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", "crl.pem", + }; + write_test_content(required_files); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + + EXPECT_EQ( + rcutils_string_map_get(&security_files, "CRL"), + std::filesystem::path("./test_folder/crl.pem").generic_string()); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +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); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +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); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + if (GetParam()) { + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + "pkcs11://identity_ca.cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + "pkcs11://cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + "pkcs11://key.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + "pkcs11://permissions_ca.cert.p11"); + } else { + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + } + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +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); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + if (GetParam()) { + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + "pkcs11://identity_ca.cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + "pkcs11://cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + "pkcs11://key.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + "pkcs11://permissions_ca.cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + } else { + ASSERT_EQ(RMW_RET_ERROR, + get_security_files_support_pkcs( + GetParam(), "", dir.generic_string().c_str(), &security_files)); + + size_t capacity = 0; + ret = rcutils_string_map_get_capacity(&security_files, &capacity); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(capacity, 0UL); + } + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +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); + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + rcutils_string_map_t security_files = rcutils_get_zero_initialized_string_map(); + rcutils_ret_t ret = rcutils_string_map_init(&security_files, 0, allocator); + ASSERT_EQ(RMW_RET_OK, ret); + + ASSERT_EQ(RMW_RET_OK, + get_security_files_support_pkcs( + GetParam(), "file://", dir.generic_string().c_str(), &security_files)); + + if (GetParam()) { + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + "pkcs11://identity_ca.cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + "pkcs11://cert.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + "pkcs11://key.p11"); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + "pkcs11://permissions_ca.cert.p11"); + } else { + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "IDENTITY_CA")), + "file://" + std::filesystem::path("./test_folder/identity_ca.cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "CERTIFICATE")), + "file://" + std::filesystem::path("./test_folder/cert.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PRIVATE_KEY")), + "file://" + std::filesystem::path("./test_folder/key.pem").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS_CA")), + "file://" + std::filesystem::path("./test_folder/permissions_ca.cert.pem").generic_string()); + } + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "GOVERNANCE")), + "file://" + std::filesystem::path("./test_folder/governance.p7s").generic_string()); + EXPECT_EQ( + std::string(rcutils_string_map_get(&security_files, "PERMISSIONS")), + "file://" + std::filesystem::path("./test_folder/permissions.p7s").generic_string()); + + ret = rcutils_string_map_fini(&security_files); + ASSERT_EQ(RMW_RET_OK, ret); +} + +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"; + }); From 53e560900c2348e60bbeb644fb60ab0983064088 Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Wed, 29 Jan 2025 12:41:23 +0100 Subject: [PATCH 2/5] Removed warnings Signed-off-by: Alejandro Hernandez Cordero --- rmw_security_common/src/security.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/rmw_security_common/src/security.cpp b/rmw_security_common/src/security.cpp index a47364e7..85ebcf16 100644 --- a/rmw_security_common/src/security.cpp +++ b/rmw_security_common/src/security.cpp @@ -20,7 +20,7 @@ #include #include -#include "rcutils/error_handling.h" +#include "rmw/error_handling.h" #include "rmw/types.h" #include "rmw_security_common/security.hpp" @@ -155,11 +155,20 @@ rmw_ret_t get_security_files_support_pkcs( } } - rcutils_ret_t ret; + rmw_ret_t ret; ret = rcutils_string_map_reserve(result, result_std.size()); + if (ret != RMW_RET_OK) { + RMW_SET_ERROR_MSG("failed to reserve memory for the string map"); + return RMW_RET_ERROR; + } + for (const auto & [key, value] : result_std) { ret = rcutils_string_map_set(result, key.c_str(), value.c_str()); + RMW_SET_ERROR_MSG("failed to insert value in the string map"); + if (ret != RMW_RET_OK) { + return RMW_RET_ERROR; + } } return RMW_RET_OK; From f59454c24f2b9cc7d0c13a1d5145303e30c7856d Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Thu, 30 Jan 2025 11:43:33 +0100 Subject: [PATCH 3/5] feedback Signed-off-by: Alejandro Hernandez Cordero --- .../include/rmw_security_common/security.hpp | 10 +++++++--- .../include/rmw_security_common/visibility_control.h | 2 +- rmw_security_common/src/security.cpp | 4 ++-- rmw_security_common/test/test_security.cpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/rmw_security_common/include/rmw_security_common/security.hpp b/rmw_security_common/include/rmw_security_common/security.hpp index 24ca52b8..6c94ab1d 100644 --- a/rmw_security_common/include/rmw_security_common/security.hpp +++ b/rmw_security_common/include/rmw_security_common/security.hpp @@ -1,4 +1,4 @@ -// Copyright 2021 Open Source Robotics Foundation, Inc. +// Copyright 2025 Open Source Robotics Foundation, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -50,7 +50,9 @@ extern "C" * \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. + * \return `RMW_RET_OK` if successful, or + * \return `RMW_RET_INVALID_ARGUMENT` if any argument are invalid, or + * \return `RMW_RET_ERROR` an unexpected error occurs. */ RMW_SECURITY_COMMON_PUBLIC rmw_ret_t get_security_files( @@ -83,7 +85,9 @@ rmw_ret_t get_security_files( * \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. + * \return `RMW_RET_OK` if successful, or + * \return `RMW_RET_INVALID_ARGUMENT` if any argument are invalid, or + * \return `RMW_RET_ERROR` an unexpected error occurs. */ RMW_SECURITY_COMMON_PUBLIC rmw_ret_t get_security_files_support_pkcs( diff --git a/rmw_security_common/include/rmw_security_common/visibility_control.h b/rmw_security_common/include/rmw_security_common/visibility_control.h index 021c95b5..f4c2f263 100644 --- a/rmw_security_common/include/rmw_security_common/visibility_control.h +++ b/rmw_security_common/include/rmw_security_common/visibility_control.h @@ -1,4 +1,4 @@ -// Copyright 2019 Open Source Robotics Foundation, Inc. +// Copyright 2025 Open Source Robotics Foundation, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/rmw_security_common/src/security.cpp b/rmw_security_common/src/security.cpp index 85ebcf16..b0e2e40b 100644 --- a/rmw_security_common/src/security.cpp +++ b/rmw_security_common/src/security.cpp @@ -1,4 +1,4 @@ -// Copyright 2021 Open Source Robotics Foundation, Inc. +// Copyright 2025 Open Source Robotics Foundation, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -165,8 +165,8 @@ rmw_ret_t get_security_files_support_pkcs( for (const auto & [key, value] : result_std) { ret = rcutils_string_map_set(result, key.c_str(), value.c_str()); - RMW_SET_ERROR_MSG("failed to insert value in the string map"); if (ret != RMW_RET_OK) { + RMW_SET_ERROR_MSG("failed to insert value in the string map"); return RMW_RET_ERROR; } } diff --git a/rmw_security_common/test/test_security.cpp b/rmw_security_common/test/test_security.cpp index cc6e7624..16126bed 100644 --- a/rmw_security_common/test/test_security.cpp +++ b/rmw_security_common/test/test_security.cpp @@ -1,4 +1,4 @@ -// Copyright 2021 Open Source Robotics Foundation, Inc. +// Copyright 2025 Open Source Robotics Foundation, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 54c05da8a1285a81541e6ccdfc397a0c45717546 Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Thu, 30 Jan 2025 18:13:19 +0100 Subject: [PATCH 4/5] feedback Signed-off-by: Alejandro Hernandez Cordero --- rmw_security_common/include/rmw_security_common/security.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/rmw_security_common/include/rmw_security_common/security.hpp b/rmw_security_common/include/rmw_security_common/security.hpp index 6c94ab1d..afc2985a 100644 --- a/rmw_security_common/include/rmw_security_common/security.hpp +++ b/rmw_security_common/include/rmw_security_common/security.hpp @@ -59,8 +59,6 @@ rmw_ret_t get_security_files( const char * prefix, const char * secure_root, rcutils_string_map_t * result); - // const std::string & prefix, const std::string & secure_root, - // std::unordered_map & result); /// Get the set of security files in a security enclave. /** From 4f4fb2fcf23c589012d46e64316e7b89b5bd82a1 Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Thu, 13 Feb 2025 21:13:05 +0100 Subject: [PATCH 5/5] Increase CMake version Signed-off-by: Alejandro Hernandez Cordero --- rmw_security_common/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmw_security_common/CMakeLists.txt b/rmw_security_common/CMakeLists.txt index 5972ca72..28c049e1 100644 --- a/rmw_security_common/CMakeLists.txt +++ b/rmw_security_common/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.14) project(rmw_security_common)