diff --git a/Cargo.toml b/Cargo.toml index 1f43650d..8799b213 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,11 @@ security = [ "dep:openssl", "dep:cryptoki" ] +# If feature "security_in_fastdds_compatibility_mode" is enabled, +# the DDS Security implementation is adjusted so that RustDDS interoperates with FastDDS. +# This means doing some things against the spec. For more info, see the security readme. +security_in_fastdds_compatibility_mode = ["security"] + # If feature "build_openssl" is enabled (along with feature "security"), # a local copy of OpenSSL will be built. # Otherwise, we try to use the system installation of OpenSSL. diff --git a/SECURITY.md b/SECURITY.md index f567db18..e9d351ff 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,6 +14,10 @@ Please see the [DDS Security Specification](https://www.omg.org/spec/DDS-SECURIT In order to use the security functionality, enable the Cargo feature `security` in RustDDS. By default, it is not enabled, because it adds a large body of code and some processing overhead. +**Note**: if RustDDS needs to interoperate with DomainParticipants based on [FastDDS](https://github.com/eProsima/Fast-DDS), enable the feature `security_in_fastdds_compatibility_mode` instead. For more info, see the section [Security in FastDDS-compatible mode](#security-in-fastdds-compatible-mode) below. + + + Security needs to be configured in order to be used. There are several mandatory configuration files that need to be supplied to RustDDS. These configuration files and their format and semantics are not unique to RustDDS, but specified in the OMG DDS Security specification. The security configuration files should also be interoperable between compliant DDS implementations. Configuring security for DomainParticipants needs two Certificate Authority roles, or CAs. A CA is someone who has the ability to issue and sign the various configuration files. The two CAs are the Identity Certificate Authority and the Permissions Certificate Authority. @@ -73,3 +77,37 @@ The following security configuration files are needed: Configuration files can be created using any method, but the OpenSSL tool is recommended. Please see the examples and scripts in the directory [examples/security_configuration_files](examples/security_configuration_files). + + +# Security in FastDDS-compatible mode + +Currently, our default DDS Security implementation is not compatible with the implementation in FastDDS. Because of this, RustDDS compiled with the default security implementation cannot establish a secure connection with a DomainParticipant based on FastDDS. The problem here is that, at least from our perspective, the FastDDS implementation slightly deviates from the Security specification at some points. + +To remedy this, we have created the feature `security_in_fastdds_compatibility_mode`. When RustDDS is compiled with this feature enabled, the security functionality is adjusted to obtain interoperability with FastDDS. The section below describes these adjustments. + +**Note**: Because FastDDS is a widely used DDS implementation that likely interoperates with some other DDS implementations, it might be that the feature `security_in_fastdds_compatibility_mode` is required to obtain interoperability also with some other DDS implementation. + +## Adjustments to Security implementation in the compatibility mode + +### 1. In PermissionsToken and IdentityToken, use the algorithm identifiers expected by FastDDS +- The DDS Security specification (section 9.3.2.1 DDS:Auth:PKI-DH IdentityToken) states that In PermissionsToken and IdentityToken, the algorithm identifier (a string) is either `RSA-2048` or `EC-prime256v1` +- FastDDS uses the identifiers ``RSASSA-PSS-SHA256`` and ``ECDSA-SHA256`` which are similar, but used elsewhere in the spec. +- Because of this, FastDDS does not recognize the algorithm identifiers sent by RustDDS, and refuses to establish a connection + +**Compatiblity fix** ✅: Use the identifiers that FastDDS expects also in RustDDS + +**Proper fix**: Use the correct identifiers in FastDDS. TODO: submit an issue about it to FastDDS. + +### 2. Use the same string representation of certificate Subject name as FastDDS +- To represent a certificate’s Subject Name as a string, FastDDS uses the OpenSSL function [X509_NAME_oneline](https://www.openssl.org/docs/manmaster/man3/X509_NAME_oneline.html). In the OpenSSL documentation, this function is said to produce a non-standard output, and “its use is strongly discouraged in new applications and it could be deprecated in a future release”. +- RustDDS uses the [string formatting from the x509_cert crate](https://docs.rs/x509-cert/latest/x509_cert/name/struct.RdnSequence.html#impl-Display-for-RdnSequence) , which produces a representation according to the [RFC 4514](https://datatracker.ietf.org/doc/html/rfc4514), which seems to be the standard nowadays. A function equivalent to the X509_NAME_oneline used by FastDDS is not available in the openssl or certificate crates that RustDDS is using. Such a function seems to be available in the [boring_sys](https://docs.rs/boring-sys/latest/boring_sys/fn.X509_NAME_oneline.html) crate, but using this would require bringing in a complex new crate just for this quite small functionality & tinkering with unsafe code. +- The problem arises when FastDDS compares the CA subject name string (standard) that RustDDS has sent inside a PermissionsToken to its own string (non-standard) that FastDDS has computed from the permissions CA certificate. Since these two strings don’t match, FastDDS deduces that it does not know the CA that RustDDS is using and refuses to establish a connection. + - Note: ideally the Subject Name comparison method would be more robust than a simple string comparison +- Example formats: + - RustDDS (RFC 4514): ``CN=permissions_ca_common_name,O=Example Organization`` + - FastDDS (X509_NAME_oneline): ``/O=Example Organization/CN=permissions_ca_common_name`` + + +**Compatiblity fix** ✅: In RustDDS, we use a function for converting the subject name string from the standard format to the X509_NAME_oneline format expected by FastDDS. The conversion isn’t perfect (according to OpenSSL documentation, X509_NAME_oneline “has various quirks and inconsistencies”), but it should work in simple cases. + +**Proper fix**: Does FastDDS even need to compare the CA Subject Name we send in PermissionsToken to its own version of the Subject Name? The spec says that the subject name field in the PermissionsToken is optional, and does not require checking it in any way. What the spec *does* require is that the signed permissions document sent by RustDDS is verified against the local permissions CA cerificate. Doesn't this already guarantee that we're using the same permissions CA? diff --git a/src/security/authentication/authentication_builtin/types.rs b/src/security/authentication/authentication_builtin/types.rs index 6581026b..e9570ea1 100644 --- a/src/security/authentication/authentication_builtin/types.rs +++ b/src/security/authentication/authentication_builtin/types.rs @@ -26,12 +26,16 @@ const CA_ALGO_PROPERTY_NAME: &str = "dds.ca.algo"; // Algorithm identifiers used in IdentityToken and PermissionsToken // Correct identifiers from the spec: -// const RSA_2048_ALGO_NAME: &str = "RSA-2048"; -// const EC_PRIME_ALGO_NAME: &str = "EC-prime256v1"; +#[cfg(not(feature = "security_in_fastdds_compatibility_mode"))] +const RSA_2048_ALGO_NAME: &str = "RSA-2048"; +#[cfg(not(feature = "security_in_fastdds_compatibility_mode"))] +const EC_PRIME_ALGO_NAME: &str = "EC-prime256v1"; // Identifiers that FastDDS expects (the same as the signature algorithm // identifiers in the spec): +#[cfg(feature = "security_in_fastdds_compatibility_mode")] const RSA_2048_ALGO_NAME: &str = "RSASSA-PSS-SHA256"; +#[cfg(feature = "security_in_fastdds_compatibility_mode")] const EC_PRIME_ALGO_NAME: &str = "ECDSA-SHA256"; pub(in crate::security) const RSA_2048_KEY_LENGTH: usize = 256; diff --git a/src/security/certificate.rs b/src/security/certificate.rs index 52f1a25a..155ed8fd 100644 --- a/src/security/certificate.rs +++ b/src/security/certificate.rs @@ -35,9 +35,10 @@ use crate::security::{ }; //use crate::security_error; +#[cfg(feature = "security_in_fastdds_compatibility_mode")] fn rfc4514_to_openssl_oneline(dn_rfc4514: &str) -> String { // This function converts a RFC 4514 string representation of a Distinguished - // Name to the format produced by the deprecated OpenSSL function + // Name to the format produced by the legacy OpenSSL function // x509_name_oneline, which FastDDS uses. The conversion is needed for // interoperability. // The fuction @@ -240,7 +241,14 @@ impl DistinguishedName { pub fn serialize(&self) -> String { let rfc4514_string = self.0.to_string(); - rfc4514_to_openssl_oneline(&rfc4514_string) + #[cfg(not(feature = "security_in_fastdds_compatibility_mode"))] + { + rfc4514_string + } + #[cfg(feature = "security_in_fastdds_compatibility_mode")] + { + rfc4514_to_openssl_oneline(&rfc4514_string) + } } // TODO is this a too strict equivalence?