Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ endif()
# Compilation
###############################################################################

option(SECURITY "Whether to build with security features and test-suite" OFF)

# Create the target and its dependencies, note that variable PROJECT_NAME is created by the closest project()
# called in the current directory scope or above.
# We must populate this variables as project sources grow
Expand Down Expand Up @@ -358,6 +360,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
ASIO_STANDALONE
$<$<AND:$<BOOL:${WIN32}>,$<STREQUAL:"${CMAKE_SYSTEM_NAME}","WindowsStore">>:_WIN32_WINNT=0x0603>
$<$<AND:$<BOOL:${WIN32}>,$<NOT:$<STREQUAL:"${CMAKE_SYSTEM_NAME}","WindowsStore">>>:_WIN32_WINNT=0x0601>
$<$<BOOL:${SECURITY}>:SECURITY>
)

#if (WIN32)
Expand Down
17 changes: 17 additions & 0 deletions include/DiscoveryServerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,22 @@ class DiscoveryServerManager
// Snapshops container
snapshots_list snapshots;

// Participant PropertiesPolicy
PropertyPolicy properties_;

// Topic description profiles
std::map<std::string, TopicDescriptionItem> topic_description_profiles_map;

volatile bool no_callbacks; // ongoing participant destruction
bool auto_shutdown; // close when event processing is finished?
bool enable_prefix_validation; // allow multiple servers share the same prefix? (only for testing purposes)
bool correctly_created_; // store false if the DiscoveryServerManager has not been successfully created
bool security_enabled_; // security enabled

#ifdef SECURITY
// Ceritficate path
std::string auth_cert_path_;
#endif // SECURITY

void loadProfiles(
tinyxml2::XMLElement* profiles);
Expand Down Expand Up @@ -169,6 +178,13 @@ class DiscoveryServerManager
void saveSnapshots(
const std::string& file) const;

/**
* @brief: This method loads a set of properties into attributes
* @param [in] props_n: element containig the XML properties
*/
bool load_properties(
tinyxml2::XMLElement* props_n);

// File where to save snapshots
std::string snapshots_output_file;
// validation required
Expand All @@ -185,6 +201,7 @@ class DiscoveryServerManager

DiscoveryServerManager(
const std::string& xml_file_path,
const std::string& security_props_file_path,
const bool shared_memory_off);

~DiscoveryServerManager();
Expand Down
107 changes: 107 additions & 0 deletions src/DiscoveryServerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#include "LateJoiner.h"
#include "log/DSLog.h"

#ifdef SECURITY
#include "security_helpers.hpp"
#endif // ifdef SECURITY

using namespace eprosima::fastdds;
using namespace eprosima::discovery_server;

Expand All @@ -49,6 +53,9 @@ const char* TYPES = "types";
const char* PUBLISHER = "publisher";
const char* SUBSCRIBER = "subscriber";
const char* TOPIC = "topic";
const char* PROPERTIES = "properties";
const char* PROPERTY = "property";
const char* VALUE = "value";
} // namespace DSxmlparser
} // namespace fastdds
} // namespace eprosima
Expand All @@ -60,11 +67,13 @@ const std::chrono::seconds DiscoveryServerManager::last_snapshot_delay_ = std::c

DiscoveryServerManager::DiscoveryServerManager(
const std::string& xml_file_path,
const std::string& security_props_file_path,
const bool shared_memory_off)
: no_callbacks(false)
, auto_shutdown(true)
, enable_prefix_validation(true)
, correctly_created_(false)
, security_enabled_(false)
, last_PDP_callback_(Snapshot::_steady_clock)
, last_EDP_callback_(Snapshot::_steady_clock)
, shared_memory_off_(shared_memory_off)
Expand Down Expand Up @@ -101,6 +110,51 @@ DiscoveryServerManager::DiscoveryServerManager(
// try load the enable_prefix_validation attribute
enable_prefix_validation = root->BoolAttribute(s_sPrefixValidation.c_str(), enable_prefix_validation);

#ifdef SECURITY
//try load security properties if any
if (!security_props_file_path.empty())
{
tinyxml2::XMLDocument props_doc;

if (tinyxml2::XMLError::XML_SUCCESS == props_doc.LoadFile(security_props_file_path.c_str()))
{
tinyxml2::XMLElement* root = props_doc.FirstChildElement(eprosima::fastdds::DSxmlparser::PROPERTIES);
if (root != nullptr)
{
if (!load_properties(root))
{
LOG_ERROR("Error loading PropertiesPolicy from properties file");
return;
}
else
{
security_enabled_ = true;
auto auth_property = PropertyPolicyHelper::find_property(properties_, "dds.sec.auth.builtin.PKI-DH.identity_certificate");
if (auth_property == nullptr)
{
LOG_ERROR("No certificate path found in properties file");
return;
}
else
{
auth_cert_path_ = *auth_property;
}
}
}
else
{
LOG_ERROR("Error retrieving properties element from properties file");
return;
}
}
else
{
LOG_ERROR("Could not load properties file");
return;
}
}
#endif // ifdef SECURITY

for (auto child = doc.FirstChildElement(s_sDS.c_str());
child != nullptr; child = child->NextSiblingElement(s_sDS.c_str()))
{
Expand Down Expand Up @@ -969,6 +1023,23 @@ void DiscoveryServerManager::loadServer(
(void)b;
assert(b.discoveryProtocol == eprosima::fastdds::rtps::DiscoveryProtocol::SERVER || b.discoveryProtocol == eprosima::fastdds::rtps::DiscoveryProtocol::BACKUP);

// Extend Participant properties if applies
if (!properties_.properties().empty())
{
dpQOS.properties(properties_);
}

#ifdef SECURITY
// mangle GUID if security is enabled
if (security_enabled_)
{
GUID_t original_guid = GUID_t(guid.guidPrefix, c_EntityId_RTPSParticipant);
// If security is enabled, mangle GUID
mangle_guid(auth_cert_path_, original_guid,
guid);
}
#endif // SECURITY

// Create the participant or the associated events
DelayedParticipantCreation event(creation_time, std::move(dpQOS), &DiscoveryServerManager::addServer);
if (creation_time == getTime())
Expand Down Expand Up @@ -1176,6 +1247,12 @@ void DiscoveryServerManager::loadClient(
dpQOS.transport().user_transports.push_back(udp_transport);
}

// Extend Participant properties if applies
if (!properties_.properties().empty())
{
dpQOS.properties(properties_);
}

GUID_t guid(dpQOS.wire_protocol().prefix, c_EntityId_RTPSParticipant);
DelayedParticipantDestruction* destruction_event = nullptr;
DelayedParticipantCreation* creation_event = nullptr;
Expand Down Expand Up @@ -1281,6 +1358,12 @@ void DiscoveryServerManager::loadSimple(
dpQOS.name() = name;
}

// Extend Participant properties if applies
if (!properties_.properties().empty())
{
dpQOS.properties(properties_);
}

GUID_t guid(dpQOS.wire_protocol().prefix, c_EntityId_RTPSParticipant);
DelayedParticipantDestruction* destruction_event = nullptr;
DelayedParticipantCreation* creation_event = nullptr;
Expand Down Expand Up @@ -2172,3 +2255,27 @@ void DiscoveryServerManager::saveSnapshots(
LOG("Snapshot file saved " << file << ".");
}
}

bool DiscoveryServerManager::load_properties(tinyxml2::XMLElement* props_n)
{
bool ret = true;
tinyxml2::XMLElement* prop = props_n->FirstChildElement(eprosima::fastdds::DSxmlparser::PROPERTY);

for (;prop != nullptr; prop = prop->NextSiblingElement(eprosima::fastdds::DSxmlparser::PROPERTY))
{
tinyxml2::XMLElement* name = prop->FirstChildElement(eprosima::fastdds::DSxmlparser::NAME);
tinyxml2::XMLElement* value = prop->FirstChildElement(eprosima::fastdds::DSxmlparser::VALUE);

if (nullptr != name && nullptr != value)
{
properties_.properties().push_back({name->GetText(), value->GetText()});
}
else
{
LOG_ERROR("Missing name/value for property " << prop->GetText());
ret = false;
}
}

return ret;
}
4 changes: 4 additions & 0 deletions src/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum optionIndex
UNKNOWN,
HELP,
CONFIG_FILE,
PROPERTIES_FILE,
OUTPUT_FILE,
SHM
};
Expand All @@ -46,6 +47,9 @@ const option::Descriptor usage[] = {
{ CONFIG_FILE, 0, "c", "config-file", Arg::check_inp,
" -c \t--config-file Mandatory configuration file path\n"},

{ PROPERTIES_FILE, 0, "p", "props-file", Arg::check_inp,
" -p \t--props-file Optional participant properties configuration file path\n"},

{ OUTPUT_FILE, 0, "o", "output-file", Arg::check_inp,
" -o \t--output-file File to write result snapshots. If not specified"
" snapshots will be written in the file specified in the snapshot\n"},
Expand Down
18 changes: 17 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,24 @@ int main(
// Load Default XML files
DomainParticipantFactory::get_instance()->load_profiles();

// Load properties file path from arg
pOp = options[PROPERTIES_FILE];

std::string path_to_properties;

if ( nullptr != pOp )
{
if ( pOp->count() != 1)
{
cout << "Only one properties file can be specified." << endl;
return 1;
}

path_to_properties = pOp->arg;
}

// Create DiscoveryServerManager
DiscoveryServerManager manager(path_to_config, options[SHM]);
DiscoveryServerManager manager(path_to_config, path_to_properties, options[SHM]);
if (!manager.correctly_created())
{
return_code = 1;
Expand Down
113 changes: 113 additions & 0 deletions src/security_helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// 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.

#ifdef SECURITY

#include <openssl/opensslv.h>

#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/obj_mac.h>

#include <fastdds/rtps/common/Guid.hpp>

using namespace eprosima::fastdds::rtps;

inline bool mangle_guid(
const std::string& cert_path,
const GUID_t& candidate_participant_key,
GUID_t& adjusted_participant_key)
{
BIO* bio = BIO_new(BIO_s_file());
X509* cert;

if (bio != nullptr)
{
if (BIO_read_filename(bio, cert_path.substr(7).c_str()) > 0)
{
cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
}
else
{
std::cerr << "OpenSSL library cannot read file "<< cert_path.substr(7) << std::endl;
}

BIO_free(bio);
}
else
{
std::cerr << "OpenSSL library cannot allocate file" << std::endl;
}

X509_NAME* cert_sn = X509_get_subject_name(cert);

unsigned char md[SHA256_DIGEST_LENGTH];
unsigned int length = 0;

if (!X509_NAME_digest(cert_sn, EVP_sha256(), md, &length) || length != SHA256_DIGEST_LENGTH)
{
std::cerr << "OpenSSL library cannot hash sha256" << std::endl;
return false;
}

adjusted_participant_key.guidPrefix.value[0] = 0x80 | (md[0] >> 1);
adjusted_participant_key.guidPrefix.value[1] = (md[0] << 7) | (md[1] >> 1);
adjusted_participant_key.guidPrefix.value[2] = (md[1] << 7) | (md[2] >> 1);
adjusted_participant_key.guidPrefix.value[3] = (md[2] << 7) | (md[3] >> 1);
adjusted_participant_key.guidPrefix.value[4] = (md[3] << 7) | (md[4] >> 1);
adjusted_participant_key.guidPrefix.value[5] = (md[4] << 7) | (md[5] >> 1);

unsigned char key[16] = {
candidate_participant_key.guidPrefix.value[0],
candidate_participant_key.guidPrefix.value[1],
candidate_participant_key.guidPrefix.value[2],
candidate_participant_key.guidPrefix.value[3],
candidate_participant_key.guidPrefix.value[4],
candidate_participant_key.guidPrefix.value[5],
candidate_participant_key.guidPrefix.value[6],
candidate_participant_key.guidPrefix.value[7],
candidate_participant_key.guidPrefix.value[8],
candidate_participant_key.guidPrefix.value[9],
candidate_participant_key.guidPrefix.value[10],
candidate_participant_key.guidPrefix.value[11],
candidate_participant_key.entityId.value[0],
candidate_participant_key.entityId.value[1],
candidate_participant_key.entityId.value[2],
candidate_participant_key.entityId.value[3]
};

if (!EVP_Digest(&key, 16, md, NULL, EVP_sha256(), NULL))
{
std::cerr << "OpenSSL library cannot hash sha256" << std::endl;
return false;
}

adjusted_participant_key.guidPrefix.value[6] = md[0];
adjusted_participant_key.guidPrefix.value[7] = md[1];
adjusted_participant_key.guidPrefix.value[8] = md[2];
adjusted_participant_key.guidPrefix.value[9] = md[3];
adjusted_participant_key.guidPrefix.value[10] = md[4];
adjusted_participant_key.guidPrefix.value[11] = md[5];

adjusted_participant_key.entityId.value[0] = candidate_participant_key.entityId.value[0];
adjusted_participant_key.entityId.value[1] = candidate_participant_key.entityId.value[1];
adjusted_participant_key.entityId.value[2] = candidate_participant_key.entityId.value[2];
adjusted_participant_key.entityId.value[3] = candidate_participant_key.entityId.value[3];

EVP_cleanup();

return true;
}

#endif //SECURITY
Loading