.. include:: ../vars.rst

.. _chapter-pre-shared-key:

***************
|PSKProtection|
***************

This chapter describes |PSKProtection|, which expands and unifies |RTI_SP_PRODUCT|
entry-level protection. |PSKProtection| is a |CryptoPlugin| mechanism that
supports basic communication protection. It is based on a |PSK| (a pre-shared,
user-configurable key seed) distributed out-of-band to |DPs|.
|PSKProtection| does not require (but can be used in combination with)
authentication and does not provide (but can be used in combination with)
sophisticated security features such as granular security and topic permissions
enforcement.
|PSKProtection| offers metadata and data protection on the wire and
restricts communication only to |DPs| holding the correct |PSK|.

|PSKProtection| can be leveraged in two different ways:

  * As part of the |SP_BUILTIN|: |PSKProtection| works alongside existing
    |SP_BUILTIN| features and secures the communication happening before and
    during authentication (known as bootstrapping, see
    :ref:`p1_welcome/overview:Protecting Bootstrapping`).
    Note: While |RTPSbootmsgs| can only be protected through |PSKProtection|,
    non-bootstrapping messages can be protected either with a
    combination of |PSKProtection| with other security mechanisms from
    |SP_BUILTIN|, or using non-|PSKProtection| mechanisms exclusively.

  * As part of |LIGHT_SP_BUILTIN|: In this case, all traditional DDS Security
    mechanisms are disabled and the entire communication is protected with
    |PSKProtection|. |LIGHT_SP_BUILTIN| is described in detail in
    :ref:`p3_advanced/lightweight_security:The |LIGHT_SP_BUILTIN_HEADING|`.
    Note: since |PSKProtection| by itself does not support granular security or
    topic permissions, |LIGHT_SP_BUILTIN| can only be used to provide
    domain-Level protection from outsider adversaries (see
    :ref:`p1_welcome/overview:Domain-Level Protection From Outsider Adversaries`).

|PSKProtection| Motivation and Benefits
=======================================

|PSKProtection| expands the |CONNEXT| security offering and enables basic-level
protection wherever traditional DDS Security mechanisms are unavailable or
infeasible due to limited resources, paramount performance requirements, or other
reasons. The |PSK| secures all the traffic from the start-up of a |DDS_ENTITY|
and restricts the communication only to |ENTITIES| holding the correct pre-shared
key seed.

|PSKProtection| in |LIGHT_SP_BUILTIN_HEADING| vs. |PSKProtection| in |SP_BUILTIN_HEADING|
-----------------------------------------------------------------------------------------

|PSKProtection| uses the same mechanism in both |LIGHT_SP_BUILTIN| and the |SP_BUILTIN|, and
offers the same level of protection. The difference is in the |PSK| area of
responsibility.

|PSKProtection| within the |SP_BUILTIN| secures all communication that occurs before two
|DPs| authenticate each other. Once authentication completes successfully,
more sophisticated |SP_BUILTIN| features (such as enforcement of permissions at the
topic-level) can be used. Authentication itself is also protected with the |PSK|.
In this setting, |PSKProtection| complements more advanced mechanisms and
secures potentially sensitive information exchanged to bootstrap the DDS system
(such as topic names in the Permission Document, locators in SPDP messages,
or Participant User QoS propagated information), which would otherwise be
vulnerable to tampering and visible to unauthorized parties.
|PSKProtection| also provides an additional mitigation for potential vulnerabilities
in the participant discovery process.

|LIGHT_SP_BUILTIN| are provided as a separate library. It supports |PSKProtection| only
and the entire communication is protected with it. More sophisticated security
mechanisms are unavailable. |LIGHT_SP_BUILTIN| are recommended in systems that do not
require granular security, sophisticated authentication and permission
management, but are resource-limited or demand high performance and low latency.

|LIGHT_SP_BUILTIN| and |SP_BUILTIN| can interoperate if configuration allows - see the configuration
section below for details.

.. _section-configuring-pre-shared-key-protection:

Configuring |PSKProtection|
===========================

.. note::
  All |SP_BUILTIN|- and |LIGHT_SP_BUILTIN|-related properties must be prefixed with
  :property:`com.rti.serv.secure.`, assuming you used :property:`com.rti.serv.secure`
  as the alias to load the plugin. If not, change the prefix to match the string
  used with :property:`com.rti.serv.load_plugins`, followed by the :value:`.`
  character.

The following properties are important for successfully configuring the
|PSKProtection|:

* The required property :property:`dds.sec.crypto.rtps_psk_secret_passphrase`
  sets the value of the pre-shared key passphrase and enables |PSKProtection|.
  See :numref:`Properties for Configuring Cryptography Affecting Any Cryptography Plugin`
  for the formats for this property.

  Both the key passphrase and the unique key identifier must be consistent
  across all |DPs| in the system. The key passphrase, combined with public
  information, is used to produce the key that is used to encrypt and decrypt
  RTPS messages. You can change this property at runtime, and the unique key
  identifier is used to determine if the correct key revision is used by
  different |DPs|. You have to manually update the key for all |DPs| in the
  system. Until the key is updated across the system, decryption errors will
  occur if :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra` is not
  used.

* The :property:`dds.sec.crypto.rtps_psk_symmetric_cipher_algorithm` optional
  property determines the algorithm used to protect RTPS messages and the key
  bit-length. The available options are :value:`AES256+GCM` (default), :value:`AES128+GCM`
  and :value:`SM4+GCM` (exclusively in |LIGHT_SP_BUILTIN|).

* The |PSKProtection| can be configured to protect RTPS message integrity (GMAC variant)
  or both integrity and confidentiality (GCM variant). In the |SP_BUILTIN|, to specify the variant,
  set :xmltag:`rtps_psk_protection_kind` (see :ref:`p2_core/cryptography:rtps_psk_protection_kind (domain_rule)`)
  in the |GovernanceDoc| to either :xmlval:`ENCRYPT` (GCM) or :xmlval:`SIGN` (GMAC).
  In the |LIGHT_SP_BUILTIN|, configuration can be done analogously through the
  :property:`dds.sec.access.rtps_psk_protection_kind` property.

* The optional :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra`
  property configures an additional passphrase that can be used for decoding
  incoming RTPS messages only. It can be used, in addition to
  :property:`dds.sec.crypto.rtps_psk_secret_passphrase`, if there is a
  requirement for a seamless transition to a new passphrase. See section
  :ref:`p3_advanced/pre-shared_key:Updating the |PSK| passphrase without message loss`
  for more information.

For more information about these properties, see
:numref:`Properties for Configuring Cryptography Affecting Any Cryptography Plugin`.

|LIGHT_SP_BUILTIN_HEADING| and |SP_BUILTIN_HEADING| Interoperability
--------------------------------------------------------------------
|LIGHT_SP_BUILTIN| can interoperate with |SP_BUILTIN| if all of the following is true:

* The |SP_BUILTIN| |GovernanceDoc| is configured with
  :xmltag:`allow_unauthenticated_participants` = :xmlval:`TRUE`,
  :xmltag:`rtps_protection_kind` = :xmlval:`NONE` and
  :xmltag:`enable_key_revision` = :xmlval:`FALSE`. |LIGHT_SP_BUILTIN| do not support
  authentication and their RTPS messages are protected with |PSK|, which is
  incompatible with RTPS Origin Authentication Protection. See
  :ref:`p2_core/access_control:Participant-Level Permissions, Endpoint-Level Permissions, and Unsecure DomainParticipants`
  and :ref:`p2_core/cryptography:Origin Authentication Protection` for details.

* The |SP_BUILTIN| and the |LIGHT_SP_BUILTIN| are configured with the same value of the
  :property:`dds.sec.crypto.rtps_psk_symmetric_cipher_algorithm` property.

* The value of the :property:`dds.sec.access.rtps_psk_protection_kind` property
  in the |LIGHT_SP_BUILTIN| is the same as the value of the
  :xmltag:`rtps_psk_protection_kind` |GovRule| in the |SP_BUILTIN|'s |GovernanceDoc|.

.. note::
  When the |SP_BUILTIN| and the |LIGHT_SP_BUILTIN| interoperate, topics that have metadata or
  data protection kinds enabled (features specific to the |SP_BUILTIN|) are
  unavailable to the |LIGHT_SP_BUILTIN|.

.. _section-how-pskprotection-works:

How |PSKProtection| Works
=========================

|PSK| Material
--------------

|PSKProtection| relies on deriving unique symmetric keys for every |DP|. The
keys are based on a user-configurable, pre-shared secret passphrase (common for
all |DPs|) and publicly available data that uniquely identifies a |DP| in the
network. Thanks to this architecture, every |DP| with the right pre-shared
passphrase can recreate the unique key material (Master Sender Salt and Master
Sender Key) of any other |DP| in the network without the need for costly key
material exchange. The key material is used as an input parameter for a
cryptographic algorithm to encrypt or MAC RTPS messages.

The 256-bit long Master Sender Salt is derived according to this algorithm:

.. code-block:: none

  HMAC-<HASH>(
    HMAC-<HASH>(
      concatenate("PSK-SALT", sender's key identifier, target's RTPS header),
      pre-shared secret passphrase),
    concatenate("master salt derivation", 0x01))

The 256-bit long Master Sender Key is derived according to this algorithm:

.. code-block:: none

  HMAC-<HASH>(
    HMAC-<HASH>(
      concatenate("PSK-SKEY", sender's key identifier, target's RTPS header),
      pre-shared secret passphrase),
    concatenate("master sender key derivation", 0x01))

In both cases, the sender's key identifier consists of four bytes calculated
according to this algorithm:

.. code-block:: none

  First three bytes:
    HASH-<HASH>(concatenate("DomainId=", DomainId, ";DomainTag=", DomainTag))

  Last byte:
    Least-significant byte of the passphrase identifier (<passphrase_id>).

where:

* The domain identifier is given as a string containing the |DOMAIN|'s decimal
  representation.

* The domain tag is the string that the encoding |DP| propagates during
  |DP_disc|, and matches the value of its
  :property:`dds.domain_participant.domain_tag` property.

* The pre-shared secret identifier is an index that selects a single key from a
  configuration that specifies multiple pre-shared keys. In particular, the
  pre-shared secret id matches the `<passphrase_id>` value specified as part of
  the  :property:`dds.sec.crypto.rtps_psk_secret_passphrase` property.

* There is no NULL terminator at the end of the concatenated strings that we
  use as input for the hashing algorithm. There is also no NULL terminator at
  the end of the DomainId and DomainTag strings.

* <HASH> denotes the hashing algorithm used by the HMAC algorithm. The |RTI_SP_PRODUCT|
  use two different hashing algorithms: :value:`SHA256` (|SP_BUILTIN| and |LIGHT_SP_BUILTIN|)
  and :value:`SM3` (|LIGHT_SP_BUILTIN| only). The hashing algorithm is selected with
  the symmetric cipher algorithm through the
  :property:`dds.sec.crypto.rtps_psk_symmetric_cipher_algorithm` property.
  :value:`SHA256` is coupled with any AES algorithm, and :value:`SM3` is tied to
  :value:`SM4+GCM` algorithm. Both algorithms produce a 256-bit long digest.

.. note::

  The uniqueness of every |DP|'s key material is important to ensure a high level
  of security. No two |DPs| can have the same key. The algorithm's goal is to
  ensure reliable and fast protection against external threats, and that no
  external party can forge the key material without knowledge of the pre-shared
  key seed. It is not the intent of this mechanism to protect against insider
  spoofing. If insiders are not fully trustworthy, use a different mechanism
  from the |SP_BUILTIN| portfolio.

Key Management
--------------

A PSK-enabled |DP|, upon its creation, derives its own key material which is
stored and readily available for later use. Whenever it wants to communicate
with another |DP|, it applies the chosen protection (encryption or MAC)
to the outgoing message, using its pre-calculated key material. The recipient
of the PSK-protected message can quickly determine that the message is PSK-protected
by checking the submessage flags in the RTPS Crypto Header. If the message is
determined to be PSK-protected, the receiving |DP| checks if its internal storage
has the key corresponding to the RTPS Header received in the message. If so, it uses
that key to decode or verify the message. If the key is not found in the list, the
receiving |DP| derives the key using the algorithm and puts it in the list.

.. note::

  The remote |DPs|' keys are stored in a list of a fixed size (10 slots) during
  their initial discovery. Whenever all available key slots are used and a "new" remote
  |DP| is encountered, the oldest entry in the list is replaced with the newly derived
  one. This mechanism may have a considerable performance impact whenever we discover
  a number of nodes exceeding the number of slots in the list within a short time -
  the recipient would end up recalculating the keys frequently. After remote |DPs|
  discovery is complete, this limitation no longer exists since the keys are no longer
  stored in the list.

Passphrase Identifier
---------------------

The :property:`dds.sec.crypto.rtps_psk_secret_passphrase` property that the user
sets consists of a passphrase identifier and a passphrase. Both the passphrase
identifier and the passphrase are used to derive the key material. As seen in
:ref:`p3_advanced/pre-shared_key:|PSK| Material`, only the least-significant
byte of the passphrase identifier is used in the key derivation process (and the
``0xFF`` value is not allowed). We will refer to this byte as the passphrase key
identifier:

.. code-block:: none

  PassphraseKeyId = PassphraseId & 0xFF

The first three bytes are called the passphrase key revision:

.. code-block:: none

  PassphraseRevision = (PassphraseId 0xFFFFFF00) >> 8

They are used to increase the range of passphrases, and help preventing id
collisions. A |DP| will attempt to decode an RTPS message only if it has both
the key identifier and the key revision identical to the locally stored ones.

The passphrase key identifier and the passphrase key revision are propagated in
the transformation_id part of the |CryptoHeader| (see
:ref:`p2_core/cryptography:Cryptographic Information Added to RTPS Messages`).
In particular, the passphrase key identifier is sent as the last byte of the
transformation_key_id. The passphrase key revision is sent as the first three
bytes of the transformation_kind.

|PSKProtection| and the Cloud
-----------------------------

|PSKProtection| is the primary security mechanism for |CONNEXT| cloud deployments,
and can be employed to protect the discovery data and Binding Ping in
|RTI_CDS| and |RTI_RWT|. For a detailed description of these environments and
configuration information see
:ref:`p4_integrations/real-time_wan_transport:Support for RTI Real-Time WAN Transport`.

When |PSKProtection| is enabled, Participant Discovery messages' serialized
data is no longer protected using the TrustedState mechanism
(see :ref:`p2_core/authentication:Protecting Participant Discovery`).
Instead, the |SP_BUILTIN| protect the whole Participant Discovery message (including
replay attack protection information described in
:ref:`p4_integrations/real-time_wan_transport:Protection Against a Cloud Discovery Service Participant Announcement Replay Attack`).
TrustedState is disabled when |PSKProtection| is enabled for the following reasons:

* One of the main use cases of |PSKProtection| is to secure cloud deployments.

* |RTI_CDS|-based deployments are not compatible with enforcing TrustedState
  or protecting Participant Discovery messages' serialized data with the
  original Participant Private Key Signature (used to sign the TrustedState).

* |PSKProtection| provides better protection against attacks from DDS domain outsiders
  (i.e. malicious agents with no access to the |PSK| seed); it protects the whole
  RTPS message, as opposed to just the serialized data, and protects
  against confidentiality threats.

* While |PSKProtection| does not protect against domain insiders (i.e., malicious agents that
  have gained access to the |PSK| seed) flooding the network with malicious
  Participant Discovery messages, the eventual consistency of the Participant
  Discovery information is still enforced through the Authentication process
  and the Secure Reliable Participant Discovery topic.

Updating the |PSK| Passphrase Without Message Loss
--------------------------------------------------

The :property:`dds.sec.crypto.rtps_psk_secret_passphrase` mutable property sets
the value of the |PSK| passphrase and enables |PSKProtection|. This property
can be updated at runtime.

However, if you directly modify the
:property:`dds.sec.crypto.rtps_psk_secret_passphrase` property, you
should expect to lose some RTPS messages until all |DPs| in your system have the
updated passphrase. During the transition period, some |DPs| have the new
passphrase while others still have the old one. To address this issue, you can
use the :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra` property.

To update the |PSK| passphrase without message loss, follow these recommended
steps:

1. Set the :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra` property
   for all the |DPs| in your system. This property should include the values of
   the old and the new passphrases separated by a new line character.

2. After setting the :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra`
   property value for all |DPs|, update the
   :property:`dds.sec.crypto.rtps_psk_secret_passphrase` property (from the
   old passphrase to the new passphrase) for all |DPs|.

3. You should now remove the value of the
   :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra` property from all
   |DPs|.

As a precautionary measure, you can periodically use the above steps to update
the |PSK| passphrase. However, you can also update the
:property:`dds.sec.crypto.rtps_psk_secret_passphrase` directly, ignore
:property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra`, and accept the
loss of some RTPS messages. For example, it may be better to update the
passphrase as soon as possible if you suspect that it has been compromised.
Until all your |DPs| have transitioned to the new passphrase, you may see
warning messages saying that the pre-shared key used to encode the received
message has a different id than the one currently configured.

.. figure:: ../static/update_with_extra_passphrase.png
    :scale: 80%
    :alt:
    :name: FigureExtraPassphrase
    :align: center

    Updating the |PSK| passphrase without message loss

..
   Note to developers: You can find the original Google Drawings file for this
   picture at https://docs.google.com/drawings/d/1zpge4qyPIX9jH342aB62Ki8Nr1VHOQZ085xrk9yiYGk/edit

.. warning::
    The :property:`dds.sec.crypto.rtps_psk_secret_passphrase` and
    :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra` properties can
    change when you call ``DomainParticipant::set_qos()`` with new values, or
    due to a modification of the passphrase files when file tracking is enabled
    (file tracking is enabled by default, because the
    :property:`files_poll_interval` property has a default value different than
    :value:`0`).

    ``DomainParticipant::set_qos()`` updates the two passphrase properties at
    the same time. However, the change in the passphrase files is not guaranteed
    to be detected simultaneously. Therefore, the |RTI_SP_PRODUCT| don't allow
    updates of the primary and extra passphrases through the file tracker as
    illustrated in :numref:`updatePassphraseDisallowedFileTracker`.

    .. list-table:: If using the file tracker, do not update the pre-shared key passphrase as follows
        :name: updatePassphraseDisallowedFileTracker
        :widths: auto
        :width: 100%
        :header-rows: 1
        :class: longtable

        * - Passphrase
          - Time 0
          - Time 1
          - Time 2
          - Time 3

        * - Primary Passphrase
          - `<old>`
          - `<old>`
          - `<new>`
          - `<new>`

        * - Extra Passphrase
          -
          - `<new>`
          - `<old>`
          -

    This update pattern is not allowed because it attempts to configure the |DP|
    with the same primary and extra passphrase (both the old or both the new
    passphrase) in the transition between "Time 1" and "Time 2". Instead, set
    the value of the :property:`dds.sec.crypto.rtps_psk_secret_passphrase_extra`
    property with the old and the new passphrases, as described earlier in this
    section.
