diff --git a/examples/connext_secure/CMakeLists.txt b/examples/connext_secure/CMakeLists.txt index 4df2dc5a4..3ceb72855 100644 --- a/examples/connext_secure/CMakeLists.txt +++ b/examples/connext_secure/CMakeLists.txt @@ -24,6 +24,7 @@ if(NOT DEFINED CONNEXTDDS_CONNEXT_SECURE_EXAMPLES) set(CONNEXTDDS_CONNEXT_SECURE_EXAMPLES "cds" "certificate_revocation_list" + "dynamic_permissions" "lightweight" "whitelist" ) diff --git a/examples/connext_secure/dynamic_permissions/README.md b/examples/connext_secure/dynamic_permissions/README.md new file mode 100644 index 000000000..4d9cc53d6 --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/README.md @@ -0,0 +1,93 @@ +# Example Code: Dynamic Permissions + +## Concept + +This example showcases how the Security Plugins enforce Permissions Document +expiration, and how the Permissions Document can be renewed to resume +communication. + +## Building the Example + +Use the following commands to build the example and get the executables that +you can run: + +```sh +cd c++11/ +mkdir build && cd build +cmake .. +cmake --build . +``` + +You can optionally pass the +``-DCONNEXTDDS_DIR=``, +``-DOPENSSL_ROOT_DIR=``, +``-DCONNEXTDDS_ARCH=``, +``-DCMAKE_BUILD_TYPE=``, and +``-DBUILD_SHARED_LIBS=`` variables to the cmake configuration step. + +After building the example, you will have a publisher Permissions Document that +expires in 1 minute. If you need to re-create it, please remove this file from +your build directory and re-run the ``createExpiringPermissions`` target. + +```sh +rm security/ecdsa01/xml/Permissions2_expiring.xml && \ + cmake --build . --target createExpiringPermissions +``` + +## Running the example + +Demo is based on a standard rtiddsgen publisher and subscriber example code. + +Run a publisher and a subscriber in separate terminal windows. + +```sh +./dynamic_permissions_publisher +``` + +```sh +./dynamic_permissions_subscriber +``` + +Verify that they communicate and that the subscriber is receiving data. + +```sh +# Publisher +Writing ::DynamicPermissions, count 0 +Writing ::DynamicPermissions, count 1 +# [...] + +# Subscriber +::DynamicPermissions subscriber sleeping up to 1 sec... +[value: 0] +::DynamicPermissions subscriber sleeping up to 1 sec... +[value: 1] +::DynamicPermissions subscriber sleeping up to 1 sec... +# [...] +``` + +Once the Permissions Document of the publisher DomainParticipant expires, you +will see the following error messages: + +```sh +# Publisher +ERROR [0x831AB06E,0x43876C36,0xFD825600:0x000001C1|ADVANCE NOTIFY INVALID LOCAL PERMISSIONS|CHECK STATUS|LC:Security] RTI_Security_PermissionsGrant_isValidTime:{"DDS:Security:LogTopicV2":{"f":"10","s":"3","t":{"s":"1748517658","n":"108000"},"h":"RTISP-10036","i":"0.0.0.0","a":"RTI Secure DDS Application","p":"85264","k":"50331706","x":[{"DDS":[{"domain_id":"0"},{"guid":"831AB06E.43876C36.FD825600.000001C1"},{"plugin_class":"DDS:Access:Permissions"},{"plugin_method":"RTI_Security_PermissionsGrant_isValidTime"}]}],"m":"now is after not_after of permissions file"}} +ERROR [0x831AB06E,0x43876C36,0xFD825600:0x000001C1|ADVANCE NOTIFY INVALID LOCAL PERMISSIONS|CHECK STATUS|LC:Security] RTI_Security_AccessControl_validate_status:{"DDS:Security:LogTopicV2":{"f":"10","s":"3","t":{"s":"1748517658","n":"192000"},"h":"RTISP-10036","i":"0.0.0.0","a":"RTI Secure DDS Application","p":"85264","k":"50331706","x":[{"DDS":[{"domain_id":"0"},{"guid":"831AB06E.43876C36.FD825600.000001C1"},{"plugin_class":"DDS:Access:Permissions"},{"plugin_method":"RTI_Security_AccessControl_validate_status"}]}],"m":"permissions' validity period is invalid."}} +ERROR [0x831AB06E,0x43876C36,0xFD825600:0x000001C1|ADVANCE NOTIFY INVALID LOCAL PERMISSIONS|CHECK STATUS|LC:Security] PRESParticipant_onSecurityLocalCredentialValidateEvent:FAILED TO VALIDATE | Local permissions credentials. +ERROR [0x831AB06E,0x43876C36,0xFD825600:0x000001C1|ADVANCE NOTIFY INVALID LOCAL PERMISSIONS|LC:Security] PRESParticipant_onSecurityLocalCredentialEventListener:FAILED TO VALIDATE | Local credentials. + +# Subscriber +ERROR [PARSE MESSAGE|0xDED844B7,0x87B9550F,0xB66DD964:0x000201C4{Entity=DR,MessageKind=DATA}|RECEIVE FROM 0x831AB06E,0x43876C36,0xFD825600:0x000201C3|:0x000001C1{Domain=0}|RECEIVE SAMPLE|PROCESS HANDSHAKE|GET SECURITY STATE|LC:Security] RTI_Security_PermissionsGrant_isValidTime:{"DDS:Security:LogTopicV2":{"f":"10","s":"3","t":{"s":"1748517682","n":"984966998"},"h":"RTISP-10036","i":"0.0.0.0","a":"RTI Secure DDS Application","p":"85248","k":"50331706","x":[{"DDS":[{"domain_id":"0"},{"guid":"DED844B7.87B9550F.B66DD964.000001C1"},{"plugin_class":"DDS:Access:Permissions"},{"plugin_method":"RTI_Security_PermissionsGrant_isValidTime"}]}],"m":"now is after not_after of permissions file"}} +ERROR [PARSE MESSAGE|0xDED844B7,0x87B9550F,0xB66DD964:0x000201C4{Entity=DR,MessageKind=DATA}|RECEIVE FROM 0x831AB06E,0x43876C36,0xFD825600:0x000201C3|:0x000001C1{Domain=0}|RECEIVE SAMPLE|PROCESS HANDSHAKE|GET SECURITY STATE|LC:Security] RTI_Security_AccessControl_validatePermissionsDocument:{"DDS:Security:LogTopicV2":{"f":"10","s":"3","t":{"s":"1748517682","n":"985028998"},"h":"RTISP-10036","i":"0.0.0.0","a":"RTI Secure DDS Application","p":"85248","k":"50331706","x":[{"DDS":[{"domain_id":"0"},{"guid":"DED844B7.87B9550F.B66DD964.000001C1"},{"plugin_class":"DDS:Access:Permissions"},{"plugin_method":"RTI_Security_AccessControl_validatePermissionsDocument"}]}],"m":"grant has invalid time"}} +ERROR [PARSE MESSAGE|0xDED844B7,0x87B9550F,0xB66DD964:0x000201C4{Entity=DR,MessageKind=DATA}|RECEIVE FROM 0x831AB06E,0x43876C36,0xFD825600:0x000201C3|:0x000001C1{Domain=0}|RECEIVE SAMPLE|PROCESS HANDSHAKE|GET SECURITY STATE|LC:Security] RTI_Security_AccessControl_validate_remote_permissions:{"DDS:Security:LogTopicV2":{"f":"10","s":"1","t":{"s":"1748517682","n":"985044998"},"h":"RTISP-10036","i":"0.0.0.0","a":"RTI Secure DDS Application","p":"85248","k":"50331706","x":[{"DDS":[{"domain_id":"0"},{"guid":"DED844B7.87B9550F.B66DD964.000001C1"},{"plugin_class":"DDS:Access:Permissions"},{"plugin_method":"RTI_Security_AccessControl_validate_remote_permissions"}]}],"m":"failed to validate remote permissions"}} +ERROR [PARSE MESSAGE|0xDED844B7,0x87B9550F,0xB66DD964:0x000201C4{Entity=DR,MessageKind=DATA}|RECEIVE FROM 0x831AB06E,0x43876C36,0xFD825600:0x000201C3|:0x000001C1{Domain=0}|RECEIVE SAMPLE|PROCESS HANDSHAKE|GET SECURITY STATE|LC:Security] DDS_DomainParticipantTrustPlugins_forwardGetAuthenticatedRemoteParticipantSecurityState:FAILED TO VALIDATE | Remote permissions. +ERROR [PARSE MESSAGE|0xDED844B7,0x87B9550F,0xB66DD964:0x000201C4{Entity=DR,MessageKind=DATA}|RECEIVE FROM 0x831AB06E,0x43876C36,0xFD825600:0x000201C3|:0x000001C1{Domain=0}|RECEIVE SAMPLE|PROCESS HANDSHAKE|LC:Security] PRESParticipant_authorizeRemoteParticipant:{"DDS:Security:LogTopicV2":{"f":"10","s":"3","t":{"s":"1748517682","n":"985078998"},"h":"RTISP-10036","i":"0.0.0.0","a":"RTI Secure DDS Application","p":"85248","k":"50331706","x":[{"DDS":[{"domain_id":"0"},{"guid":"DED844B7.87B9550F.B66DD964.000001C1"},{"plugin_class":"RTI:Auth"},{"plugin_method":"PRESParticipant_authorizeRemoteParticipant"}]}],"m":"unauthorized remote participant 831ab06e.43876c36.fd825600 denied by local participant ded844b7.87b9550f.b66dd964"}} +ERROR [PARSE MESSAGE|0xDED844B7,0x87B9550F,0xB66DD964:0x000201C4{Entity=DR,MessageKind=DATA}|RECEIVE FROM 0x831AB06E,0x43876C36,0xFD825600:0x000201C3|:0x000001C1{Domain=0}|RECEIVE SAMPLE|PROCESS HANDSHAKE|LC:Security] PRESParticipant_processHandshake:FAILED TO VALIDATE | Failed to authorize remote DP (GUID: 0x831AB06E,0x43876C36,0xFD825600:0x000001C1). +``` + +Communication will stop. + +## Renewing the Permissions Document + +This example updates the publisher DomainParticipant's Permissions Document +after 70 samples. At that point, communication with the subscriber will +resume. diff --git a/examples/connext_secure/dynamic_permissions/c++11/CMakeLists.txt b/examples/connext_secure/dynamic_permissions/c++11/CMakeLists.txt new file mode 100644 index 000000000..c4fac74da --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/CMakeLists.txt @@ -0,0 +1,67 @@ +# +# (c) 2025 Copyright, Real-Time Innovations, Inc. All rights reserved. +# +# RTI grants Licensee a license to use, modify, compile, and create derivative +# works of the Software. Licensee has the right to distribute object form +# only for use with RTI products. The Software is provided "as is", with no +# warranty of any type, including any warranty for fitness for any purpose. +# RTI is under no obligation to maintain or support the Software. RTI shall +# not be liable for any incidental or consequential damages arising out of the +# use or inability to use the software. +# +cmake_minimum_required(VERSION 3.11) +project(rtiexamples-dynamic-permissions) +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/../../../../resources/cmake/Modules" +) +include(ConnextDdsConfigureCmakeUtils) +connextdds_configure_cmake_utils() + +find_package(RTIConnextDDS + "7.0.0" + REQUIRED + COMPONENTS + security_plugins +) + +if(NOT TARGET RTIConnextDDS::security_plugins) + message(WARNING "RTIConnextDDS::security_plugins component is missing. Skipping example") + return() +endif() + +# Include ConnextDdsAddExample.cmake from resources/cmake +include(ConnextDdsAddExample) + +connextdds_add_example( + IDL "dynamic_permissions" + LANG "C++11" +) + +include (ConnextDdsGenerateSecurityArtifacts) +connextdds_generate_security_artifacts() + +# Do a copy of the original subscriber's Permissions Document, but with the +# validity modified so that it expires in 1 minute. +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/Permissions2_expiring.xml" + COMMAND ${CMAKE_COMMAND} + -DINPUT_FILE="${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/Permissions2.xml" + -DOUTPUT_FILE="${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/Permissions2_expiring.xml" + -P ${CMAKE_SOURCE_DIR}/modify_permissions.cmake + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/Permissions2.xml" +) + +# Sign the modified Permissions Document +connextdds_openssl_smime_sign( + INPUT "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/Permissions2_expiring.xml" + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/signed/signed_Permissions2_expiring.p7s" + SIGNER_CERTIFICATE "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/certs/ca_cert.pem" + PRIVATE_KEY_FILE "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/certs/ca_key.pem" +) + +# Create a Permissions Document that is about to expire +add_custom_target(createExpiringPermissions + ALL + DEPENDS + dynamic_permissions_securityArtifacts + "${CMAKE_CURRENT_BINARY_DIR}/security/ecdsa01/xml/signed/signed_Permissions2_expiring.p7s") diff --git a/examples/connext_secure/dynamic_permissions/c++11/USER_QOS_PROFILES.xml b/examples/connext_secure/dynamic_permissions/c++11/USER_QOS_PROFILES.xml new file mode 100644 index 000000000..2b31c22dd --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/USER_QOS_PROFILES.xml @@ -0,0 +1,68 @@ + + + + + + + + + + dds.sec.auth.identity_ca + file:security/ecdsa01/certs/ca_cert.pem + + + dds.sec.auth.identity_certificate + file:security/ecdsa01/certs/peer1_cert.pem + + + dds.sec.auth.private_key + file:security/ecdsa01/certs/peer1_key.pem + + + dds.sec.access.permissions_ca + file:security/ecdsa01/certs/ca_cert.pem + + + dds.sec.access.governance + file:security/ecdsa01/xml/signed/signed_Governance.p7s + + + dds.sec.access.permissions + file:security/ecdsa01/xml/signed/signed_Permissions1.p7s + + + + + + + + + + + dds.sec.auth.identity_certificate + file:security/ecdsa01/certs/peer2_cert.pem + + + dds.sec.auth.private_key + file:security/ecdsa01/certs/peer2_key.pem + + + dds.sec.access.permissions + file:security/ecdsa01/xml/signed/signed_Permissions2_expiring.p7s + + + + + + + diff --git a/examples/connext_secure/dynamic_permissions/c++11/application.h b/examples/connext_secure/dynamic_permissions/c++11/application.h new file mode 100644 index 000000000..b4353c51e --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/application.h @@ -0,0 +1,132 @@ +/* + * (c) Copyright, Real-Time Innovations, 2025. All rights reserved. + * RTI grants Licensee a license to use, modify, compile, and create derivative + * works of the software solely for use with RTI Connext DDS. Licensee may + * redistribute copies of the software provided that all such copies are subject + * to this license. The software is provided "as is", with no warranty of any + * type, including any warranty for fitness for any purpose. RTI is under no + * obligation to maintain or support the software. RTI shall not be liable for + * any incidental or consequential damages arising out of the use or inability + * to use the software. + */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include +#include + +namespace application { + +// Catch control-C and tell application to shut down +bool shutdown_requested = false; + +inline void stop_handler(int) +{ + shutdown_requested = true; + std::cout << "preparing to shut down..." << std::endl; +} + +inline void setup_signal_handlers() +{ + signal(SIGINT, stop_handler); + signal(SIGTERM, stop_handler); +} + +enum ParseReturn { PARSE_RETURN_OK, PARSE_RETURN_FAILURE, PARSE_RETURN_EXIT }; + +struct ApplicationArguments { + ParseReturn parse_result; + unsigned int domain_id; + unsigned int sample_count; + NDDS_Config_LogVerbosity verbosity; +}; + +inline void set_verbosity(ApplicationArguments &arguments, int verbosity) +{ + switch (verbosity) { + case 0: + arguments.verbosity = NDDS_CONFIG_LOG_VERBOSITY_SILENT; + break; + case 1: + arguments.verbosity = NDDS_CONFIG_LOG_VERBOSITY_ERROR; + break; + case 2: + arguments.verbosity = NDDS_CONFIG_LOG_VERBOSITY_WARNING; + break; + case 3: + arguments.verbosity = NDDS_CONFIG_LOG_VERBOSITY_STATUS_ALL; + break; + default: + arguments.verbosity = NDDS_CONFIG_LOG_VERBOSITY_ERROR; + break; + } +} + +// Parses application arguments for example. Returns whether to exit. +inline void parse_arguments( + ApplicationArguments &arguments, + int argc, + char *argv[]) +{ + int arg_processing = 1; + bool show_usage = false; + arguments.domain_id = 0; + arguments.sample_count = INT_MAX; + arguments.verbosity = NDDS_CONFIG_LOG_VERBOSITY_ERROR; + arguments.parse_result = PARSE_RETURN_OK; + + while (arg_processing < argc) { + if ((argc > arg_processing + 1) + && (strcmp(argv[arg_processing], "-d") == 0 + || strcmp(argv[arg_processing], "--domain") == 0)) { + arguments.domain_id = atoi(argv[arg_processing + 1]); + arg_processing += 2; + } else if ( + (argc > arg_processing + 1) + && (strcmp(argv[arg_processing], "-s") == 0 + || strcmp(argv[arg_processing], "--sample-count") == 0)) { + arguments.sample_count = atoi(argv[arg_processing + 1]); + arg_processing += 2; + } else if ( + (argc > arg_processing + 1) + && (strcmp(argv[arg_processing], "-v") == 0 + || strcmp(argv[arg_processing], "--verbosity") == 0)) { + set_verbosity(arguments, atoi(argv[arg_processing + 1])); + arg_processing += 2; + } else if ( + strcmp(argv[arg_processing], "-h") == 0 + || strcmp(argv[arg_processing], "--help") == 0) { + std::cout << "Example application." << std::endl; + show_usage = true; + arguments.parse_result = PARSE_RETURN_EXIT; + break; + } else { + std::cout << "Bad parameter." << std::endl; + show_usage = true; + arguments.parse_result = PARSE_RETURN_FAILURE; + break; + } + } + if (show_usage) { + std::cout << "Usage:\n" + " -d, --domain Domain ID this " + "application will\n" + " subscribe in. \n" + " Default: 0\n" + " -s, --sample_count Number of samples to " + "receive before\n" + " cleanly shutting down. \n" + " Default: infinite\n" + " -v, --verbosity How much debugging output " + "to show.\n" + " Range: 0-3 \n" + " Default: 1" + << std::endl; + } +} + +} // namespace application + +#endif // APPLICATION_H diff --git a/examples/connext_secure/dynamic_permissions/c++11/application.hpp b/examples/connext_secure/dynamic_permissions/c++11/application.hpp new file mode 100644 index 000000000..3c8820e8b --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/application.hpp @@ -0,0 +1,141 @@ +/* +* (c) Copyright, Real-Time Innovations, 2025. All rights reserved. +* RTI grants Licensee a license to use, modify, compile, and create derivative +* works of the software solely for use with RTI Connext DDS. Licensee may +* redistribute copies of the software provided that all such copies are subject +* to this license. The software is provided "as is", with no warranty of any +* type, including any warranty for fitness for any purpose. RTI is under no +* obligation to maintain or support the software. RTI shall not be liable for +* any incidental or consequential damages arising out of the use or inability +* to use the software. +*/ + +#ifndef APPLICATION_HPP +#define APPLICATION_HPP + +#include +#include +#include + +namespace application { + + // Catch control-C and tell application to shut down + bool shutdown_requested = false; + + inline void stop_handler(int) + { + shutdown_requested = true; + std::cout << "preparing to shut down..." << std::endl; + } + + inline void setup_signal_handlers() + { + signal(SIGINT, stop_handler); + signal(SIGTERM, stop_handler); + } + + enum class ParseReturn { + ok, + failure, + exit + }; + + struct ApplicationArguments { + ParseReturn parse_result; + unsigned int domain_id; + unsigned int sample_count; + rti::config::Verbosity verbosity; + + ApplicationArguments( + ParseReturn parse_result_param, + unsigned int domain_id_param, + unsigned int sample_count_param, + rti::config::Verbosity verbosity_param) + : parse_result(parse_result_param), + domain_id(domain_id_param), + sample_count(sample_count_param), + verbosity(verbosity_param) {} + }; + + inline void set_verbosity( + rti::config::Verbosity& verbosity, + int verbosity_value) + { + switch (verbosity_value) { + case 0: + verbosity = rti::config::Verbosity::SILENT; + break; + case 1: + verbosity = rti::config::Verbosity::EXCEPTION; + break; + case 2: + verbosity = rti::config::Verbosity::WARNING; + break; + case 3: + verbosity = rti::config::Verbosity::STATUS_ALL; + break; + default: + verbosity = rti::config::Verbosity::EXCEPTION; + break; + } + } + + // Parses application arguments for example. + inline ApplicationArguments parse_arguments(int argc, char *argv[]) + { + int arg_processing = 1; + bool show_usage = false; + ParseReturn parse_result = ParseReturn::ok; + unsigned int domain_id = 0; + unsigned int sample_count = (std::numeric_limits::max)(); + rti::config::Verbosity verbosity(rti::config::Verbosity::EXCEPTION); + + while (arg_processing < argc) { + if ((argc > arg_processing + 1) + && (strcmp(argv[arg_processing], "-d") == 0 + || strcmp(argv[arg_processing], "--domain") == 0)) { + domain_id = atoi(argv[arg_processing + 1]); + arg_processing += 2; + } else if ((argc > arg_processing + 1) + && (strcmp(argv[arg_processing], "-s") == 0 + || strcmp(argv[arg_processing], "--sample-count") == 0)) { + sample_count = atoi(argv[arg_processing + 1]); + arg_processing += 2; + } else if ((argc > arg_processing + 1) + && (strcmp(argv[arg_processing], "-v") == 0 + || strcmp(argv[arg_processing], "--verbosity") == 0)) { + set_verbosity(verbosity, atoi(argv[arg_processing + 1])); + arg_processing += 2; + } else if (strcmp(argv[arg_processing], "-h") == 0 + || strcmp(argv[arg_processing], "--help") == 0) { + std::cout << "Example application." << std::endl; + show_usage = true; + parse_result = ParseReturn::exit; + break; + } else { + std::cout << "Bad parameter." << std::endl; + show_usage = true; + parse_result = ParseReturn::failure; + break; + } + } + if (show_usage) { + std::cout << "Usage:\n"\ + " -d, --domain Domain ID this application will\n" \ + " subscribe in. \n" + " Default: 0\n"\ + " -s, --sample_count Number of samples to receive before\n"\ + " cleanly shutting down. \n" + " Default: infinite\n" + " -v, --verbosity How much debugging output to show.\n"\ + " Range: 0-3 \n" + " Default: 1" + << std::endl; + } + + return ApplicationArguments(parse_result, domain_id, sample_count, verbosity); + } + +} // namespace application + +#endif // APPLICATION_HPP diff --git a/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions.idl b/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions.idl new file mode 100644 index 000000000..1b39e1cd6 --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions.idl @@ -0,0 +1,14 @@ +/* + * (c) 2025 Copyright, Real-Time Innovations, Inc. All rights reserved. + * + * RTI grants Licensee a license to use, modify, compile, and create derivative + * works of the Software. Licensee has the right to distribute object form + * only for use with RTI products. The Software is provided "as is", with no + * warranty of any type, including any warranty for fitness for any purpose. + * RTI is under no obligation to maintain or support the Software. RTI shall + * not be liable for any incidental or consequential damages arising out of the + * use or inability to use the software. + */ +struct DynamicPermissions { + int32 value; +}; diff --git a/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions_publisher.cxx b/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions_publisher.cxx new file mode 100644 index 000000000..aaf8b441e --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions_publisher.cxx @@ -0,0 +1,104 @@ +/* + * (c) Copyright, Real-Time Innovations, 2025. All rights reserved. + * RTI grants Licensee a license to use, modify, compile, and create derivative + * works of the software solely for use with RTI Connext DDS. Licensee may + * redistribute copies of the software provided that all such copies are subject + * to this license. The software is provided "as is", with no warranty of any + * type, including any warranty for fitness for any purpose. RTI is under no + * obligation to maintain or support the software. RTI shall not be liable for + * any incidental or consequential damages arising out of the use or inability + * to use the software. + */ + +#include + +#include +#include // for sleep() +#include // for logging + +#include "application.hpp" // for command line parsing and ctrl-c +#include "dynamic_permissions.hpp" + +void run_publisher_application( + unsigned int domain_id, + unsigned int sample_count) +{ + // Start communicating in a domain, usually one participant per application + dds::domain::DomainParticipant participant( + domain_id, + dds::core::QosProvider::Default().participant_qos( + "dynamic_permissions_Library::publisher")); + + // Create a Topic with a name and a datatype + dds::topic::Topic<::DynamicPermissions> topic( + participant, + "Example DynamicPermissions"); + + // Create a Publisher + dds::pub::Publisher publisher(participant); + + // Create a DataWriter with default QoS + dds::pub::DataWriter<::DynamicPermissions> writer(publisher, topic); + + ::DynamicPermissions data; + // Main loop, write data + for (unsigned int samples_written = 0; + !application::shutdown_requested && samples_written < sample_count; + samples_written++) { + // Modify the data to be written here + data.value(static_cast(samples_written)); + std::cout << "Writing ::DynamicPermissions, count " << samples_written + << std::endl; + + writer.write(data); + + // Send once every second + rti::util::sleep(dds::core::Duration(1)); + + // The Permissions Document expires after 1 minute (~60 samples). + // Let's update it after 70 samples. At this point, the publisher and + // subscriber lost communication. This will be fixed by updating the + // Permissions Document. + if (samples_written == 70) { + std::cout << "Updating Permissions Document" << std::endl; + dds::domain::qos::DomainParticipantQos updated_qos = + participant.qos(); + updated_qos->property.set(rti::core::policy::Property::Entry( + "dds.sec.access.permissions", + "security/ecdsa01/xml/signed/signed_Permissions2.p7s")); + participant << updated_qos; + } + } +} + +int main(int argc, char *argv[]) +{ + using namespace application; + + // Parse arguments and handle control-C + auto arguments = parse_arguments(argc, argv); + if (arguments.parse_result == ParseReturn::exit) { + return EXIT_SUCCESS; + } else if (arguments.parse_result == ParseReturn::failure) { + return EXIT_FAILURE; + } + setup_signal_handlers(); + + // Sets Connext verbosity to help debugging + rti::config::Logger::instance().verbosity(arguments.verbosity); + + try { + run_publisher_application(arguments.domain_id, arguments.sample_count); + } catch (const std::exception &ex) { + // This will catch DDS exceptions + std::cerr << "Exception in run_publisher_application(): " << ex.what() + << std::endl; + return EXIT_FAILURE; + } + + // Releases the memory used by the participant factory. Optional at + // application exit + dds::domain::DomainParticipant::finalize_participant_factory(); + + return EXIT_SUCCESS; +} diff --git a/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions_subscriber.cxx b/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions_subscriber.cxx new file mode 100644 index 000000000..42edf4d60 --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/dynamic_permissions_subscriber.cxx @@ -0,0 +1,113 @@ +/* + * (c) Copyright, Real-Time Innovations, 2025. All rights reserved. + * RTI grants Licensee a license to use, modify, compile, and create derivative + * works of the software solely for use with RTI Connext DDS. Licensee may + * redistribute copies of the software provided that all such copies are subject + * to this license. The software is provided "as is", with no warranty of any + * type, including any warranty for fitness for any purpose. RTI is under no + * obligation to maintain or support the software. RTI shall not be liable for + * any incidental or consequential damages arising out of the use or inability + * to use the software. + */ + +#include +#include + +#include +#include +#include + +#include "dynamic_permissions.hpp" +#include "application.hpp" // for command line parsing and ctrl-c + +int process_data(dds::sub::DataReader<::DynamicPermissions> reader) +{ + // Take all samples + int count = 0; + dds::sub::LoanedSamples<::DynamicPermissions> samples = reader.take(); + for (auto sample : samples) { + if (sample.info().valid()) { + count++; + std::cout << sample.data() << std::endl; + } else { + std::cout << "Instance state changed to " + << sample.info().state().instance_state() << std::endl; + } + } + + return count; +} // The LoanedSamples destructor returns the loan + +void run_subscriber_application( + unsigned int domain_id, + unsigned int sample_count) +{ + // Start communicating in a domain, usually one participant per application + dds::domain::DomainParticipant participant( + domain_id, + dds::core::QosProvider::Default().participant_qos( + "dynamic_permissions_Library::subscriber")); + + // Create a Topic with a name and a datatype + dds::topic::Topic<::DynamicPermissions> topic( + participant, + "Example DynamicPermissions"); + + // Create a Subscriber and DataReader with default Qos + dds::sub::Subscriber subscriber(participant); + dds::sub::DataReader<::DynamicPermissions> reader(subscriber, topic); + + // Create a ReadCondition for any data received on this reader and set a + // handler to process the data + unsigned int samples_read = 0; + dds::sub::cond::ReadCondition read_condition( + reader, + dds::sub::status::DataState::any(), + [reader, &samples_read]() { + samples_read += process_data(reader); + }); + + // WaitSet will be woken when the attached condition is triggered + dds::core::cond::WaitSet waitset; + waitset += read_condition; + + while (!application::shutdown_requested && samples_read < sample_count) { + std::cout << "::DynamicPermissions subscriber sleeping up to 1 sec..." + << std::endl; + + // Run the handlers of the active conditions. Wait for up to 1 second. + waitset.dispatch(dds::core::Duration(1)); + } +} + +int main(int argc, char *argv[]) +{ + using namespace application; + + // Parse arguments and handle control-C + auto arguments = parse_arguments(argc, argv); + if (arguments.parse_result == ParseReturn::exit) { + return EXIT_SUCCESS; + } else if (arguments.parse_result == ParseReturn::failure) { + return EXIT_FAILURE; + } + setup_signal_handlers(); + + // Sets Connext verbosity to help debugging + rti::config::Logger::instance().verbosity(arguments.verbosity); + + try { + run_subscriber_application(arguments.domain_id, arguments.sample_count); + } catch (const std::exception &ex) { + // This will catch DDS exceptions + std::cerr << "Exception in run_subscriber_application(): " << ex.what() + << std::endl; + return EXIT_FAILURE; + } + + // Releases the memory used by the participant factory. Optional at + // application exit + dds::domain::DomainParticipant::finalize_participant_factory(); + + return EXIT_SUCCESS; +} diff --git a/examples/connext_secure/dynamic_permissions/c++11/modify_permissions.cmake b/examples/connext_secure/dynamic_permissions/c++11/modify_permissions.cmake new file mode 100644 index 000000000..ddd239f75 --- /dev/null +++ b/examples/connext_secure/dynamic_permissions/c++11/modify_permissions.cmake @@ -0,0 +1,46 @@ +file(READ ${INPUT_FILE} CONTENTS) + +# Find the positions of the start and end tags +string(FIND "${CONTENTS}" "" START_INDEX) +string(FIND "${CONTENTS}" "" END_INDEX) + +if(START_INDEX EQUAL -1 OR END_INDEX EQUAL -1) + message(FATAL_ERROR "Tags not found in the input file") +endif() + +# Compute the new contents +string(LENGTH "" NOT_AFTER_LENGTH) +math(EXPR START_INDEX "${START_INDEX} + ${NOT_AFTER_LENGTH}") +string(SUBSTRING "${CONTENTS}" 0 ${START_INDEX} BEFORE_START) + +string(LENGTH "${CONTENTS}" TOTAL_LENGTH) +math(EXPR TRAILING_LENGTH "${TOTAL_LENGTH} - ${END_INDEX}") +string(SUBSTRING "${CONTENTS}" ${END_INDEX} ${TRAILING_LENGTH} AFTER_END) + +# Replace with the current date + 1 minute +string(TIMESTAMP current_epoch "%s" UTC) +MATH(EXPR expiring_epoch "(${current_epoch} + 60)") + +# SOURCE_DATE_EPOCH allows the date and time to be set externally by an exported +# environment variable. If the SOURCE_DATE_EPOCH environment variable is set, +# the string(TIMESTAMP [...]) cmake command will return its value instead of the +# current time. +# Backup. +if (DEFINED ENV{SOURCE_DATE_EPOCH}) + set(_old_source_date_epoch ENV{SOURCE_DATE_EPOCH}) +endif() +# +# Set new value. +set(ENV{SOURCE_DATE_EPOCH} ${expiring_epoch}) +# +# Get the timestamp that we want. +string(TIMESTAMP expiring_date "%Y-%m-%dT%H:%M:%S" UTC) +# +# Revert old value. +if (DEFINED _old_source_date_epoch) + set(ENV{SOURCE_DATE_EPOCH} ${_old_source_date_epoch}) +else() + unset(ENV{SOURCE_DATE_EPOCH}) +endif() + +file(WRITE ${OUTPUT_FILE} "${BEFORE_START}${expiring_date}${AFTER_END}") diff --git a/resources/cmake/Modules/ConnextDdsGenerateSecurityArtifacts.cmake b/resources/cmake/Modules/ConnextDdsGenerateSecurityArtifacts.cmake index 65fe6693b..168178f27 100644 --- a/resources/cmake/Modules/ConnextDdsGenerateSecurityArtifacts.cmake +++ b/resources/cmake/Modules/ConnextDdsGenerateSecurityArtifacts.cmake @@ -90,7 +90,7 @@ function(connextdds_generate_security_artifacts) ) set(xmls_name Governance Permissions1 Permissions2) foreach(xml ${xmls_name}) - list(APPEND artifacts_input_files "${xml}.xml") + list(APPEND artifacts_input_files "${openssl_working_dir}/xml/${xml}.xml") endforeach() add_custom_command( @@ -143,12 +143,12 @@ function(connextdds_generate_security_artifacts) OUTPUT_KEY_FILE "${ca_key_file}" OUTPUT_CERT_FILE "${ca_cert_file}" CRL_NUMBER_FILE "${openssl_temporary_dir}/crlNumber" - TEXT DIGEST SHA256 DAYS ${expiration_days} ECPARAM_NAME prime256v1 ECPARAM_OUTPUT_FILE "${openssl_temporary_dir}/ecdsaparam" CONFIG_FILE "${ca_config_file}" + CA_EXTENSION v3_ca WORKING_DIRECTORY "${openssl_working_dir}" ) @@ -157,7 +157,6 @@ function(connextdds_generate_security_artifacts) OUTPUT_CERT_FILE "${peer1_cert_file}" OUTPUT_CERT_REQUEST_FILE "${openssl_temporary_dir}/peer1_req_cert.pem" OUTPUT_KEY_FILE "${peer1_key_file}" - TEXT ECPARAM_NAME "prime256v1" ECPARAM_OUTPUT_FILE "${openssl_temporary_dir}/ecdsaparam1" CONFIG_FILE "${peer1_config_file}" @@ -173,7 +172,6 @@ function(connextdds_generate_security_artifacts) OUTPUT_CERT_FILE "${peer2_cert_file}" OUTPUT_CERT_REQUEST_FILE "${openssl_temporary_dir}/peer2_req_cert.pem" OUTPUT_KEY_FILE "${peer2_key_file}" - TEXT ECPARAM_NAME "prime256v1" ECPARAM_OUTPUT_FILE "${openssl_temporary_dir}/ecdsaparam1" CONFIG_FILE "${peer2_config_file}" diff --git a/resources/security/xml/Governance.xml b/resources/security/xml/Governance.xml index 21e8ebf12..c24796be4 100644 --- a/resources/security/xml/Governance.xml +++ b/resources/security/xml/Governance.xml @@ -1,6 +1,6 @@ + xsi:noNamespaceSchemaLocation="http://community.rti.com/schema/7.6.0/dds_security_governance.xsd"> @@ -10,6 +10,7 @@ false true + true NONE NONE ENCRYPT