4.15. Security SDK

4.15.1. Introduction

RTI Security Plugins introduce a robust set of security capabilities, including authentication, encryption, access control and logging. Secure multicast support enables efficient and scalable distribution of data to many subscribers. Performance is also optimized by fine-grained control over the level of security applied to each data flow, such as whether encryption or just data integrity is required.

The RTI Connext DDS Micro Security SDK includes a set of builtin plugins that implement the Service Plugin Interface defined by the DDS Security specification.

RTI Security Plugins is a separate package, available from the RTI Support Portal, https://support.rti.com/.

It is also possible to implement new custom plugins by using the Security Plugins SDK bundle (for more information, please contact support@rti.com). See the RTI Security Plugins Release Notes and RTI Security Plugins Getting Started Guide on the RTI Documentation page.

4.15.2. Installation

Please refer to the main Installation section for how to install the RTI Connext DDS Micro Security SDK.

4.15.3. Examples

For descriptions and examples of the security configuration in this release, please consult the HelloWorld_dpde_secure examples under the example/[unix, windows]/[C, CPP] directory. RTI Connext DDS Micro Security SDK supports both the C and C++ programming languages.

To use the RTI Connext DDS Micro Security SDK, you will need to create private keys, identity certificates, governance and permission files, as well as signed versions for use in secure authenticated, authorized, and/or encrypted communications.

4.15.4. Enabling RTI Security Plugins

To enable the RTI Security Plugins, the name of a “plugin suite” (i.e. the collection of security plugins defined by DDS Security) must be specified in a DomainParticipant’s QoS. Plugin factories for this suite must also be registered with the RT_Registry before the DomainParticipant is created.

When using RTI Connext DDS Micro’s C API, this can be achieved with the following code:

RTI_BOOL result = RTI_FALSE;
struct DDS_DomainParticipantQos dp_qos = DDS_DomainParticipantQos_INITIALIZER;
struct SECCORE_SecurePluginFactoryProperty sec_plugin_prop = SECCORE_SecurePluginFactoryProperty_INITIALIZER;
DDS_DomainParticipantFactory *factory =  DDS_DomainParticipantFactory_get_instance();
RT_Registry_T *registry =  DDS_DomainParticipantFactory_get_registry(factory);

/* Register factories for built-in security plugins, using default
 * properties and default name */
if (!SECCORE_SecurePluginFactory_register(
        registry,SECCORE_DEFAULT_SUITE_NAME,&sec_plugin_prop))
{
    printf("failed to register security plugins\n");
    goto done;
}

/* In order to enable security, the name used to register the suite of
 * plugins must be set in DomainParticipantQos */
if (!RT_ComponentFactoryId_set_name(
        &dp_qos->trust.suite, SECCORE_DEFAULT_SUITE_NAME))
{
    printf("failed to set component id\n");
    goto done;
}

result = RTI_TRUE;

done:

return result;

For users of RTI Connext DDS Micro’s C++ API, the suite can be registered using the following code:

RTI_BOOL result = RTI_FALSE;
DDS_DomainParticipantQos dp_qos;
SECCORE_SecurePluginFactoryProperty sec_plugin_prop;
DDSDomainParticipantFactory *factory = DDSDomainParticipantFactory::get_instance();
RTRegistry_T *registry = factory->get_registry();

/* Register factories for built-in security plugins, using default
 * properties and default name */
if (!SECCORE_SecurePluginFactory::register_suite(
        registry,SECCORE_DEFAULT_SUITE_NAME,sec_plugin_prop))
{
    printf("failed to register security plugins\n");
    goto done;
}

/* In order to enable security, the name used to register the suite of
 * plugins must be set in DomainParticipantQos */
if (!dp_qos.trust.suite.set_name(SECCORE_DEFAULT_SUITE_NAME))
{
    printf("failed to set component id\n");
    goto done;
}

result = RTI_TRUE;

done:

return result;

Additional properties can be controlled using (key,value) pairs in a DomainParticipant’s DDS_PropertyQosPolicy.

The configuration keys (and their corresponding valid values) supported by each security plugin are listed in the tables below:

In RTI Connext DDS Micro, you must set the security-related participant properties before you create a participant. You cannot create a participant without security and then call DomainParticipant::set_qos() with security properties, even if the participant has not yet been enabled.

Table 4.1 DDS Security Prefix Property
Property Name Property Value Description
DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY

Optional.

The prefix string for security properties. For example: com.rti.serv.secure. If set, you will use this string as the prefix, followed by a “dot”, to some of the property names.

Note: This is different than the Connext DDS Pro prefix property which also allows you to load the plugin. This property only allows you to use the prefix string.

Default : NULL

Table 4.2 DDS Security Properties for Configuring Authentication
Property Name Property Value Description
DDS_SECURITY_IDENTITY_CA_PROPERTY

Required.

The Identity Certificate Authority for signing authentication certificate files. See Section 4.15.4.1.

DDS_SECURITY_PRIVATE_KEY_PROPERTY

Required.

The private key associated with the first certificate that appears in the identity_certificate. See Section 4.15.4.2.

DDS_SECURITY_IDENTITY_CERTIFICATE_PROPERTY

Required.

An Identity Certificate, required for secure communication. See Section 4.15.4.3.

DDS_SECURITY_PASSWORD_PROPERTY

Only required if private_key is encrypted.

The password used to decrypt the private_key. See Section 4.15.4.4.

RTI_SECURITY_SHARED_SECRET_ALGORITHM_PROPERTY

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The algorithm used to establish a shared secret during authentication. See Section 4.15.4.5.

RTI_SECURITY_CRL_FILE_PROPERTY

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The Certificate Revocation List (CRL) that keeps track of untrusted X.509 certificates. See Section 4.15.4.6.

Table 4.3 DDS Security Properties for Configuring Access Control
Property Name Property Value Description
DDS_SECURITY_PERMISSIONS_CA_PROPERTY

Required.

Permissions Certificate Authority for signing access control governance and permissions XML files, and verifying the signatures of those files.

See Section 4.15.4.7.

DDS_SECURITY_GOVERNANCE_PROPERTY

Required.

Signed document that specifies the level of security required per domain and per topic.

See Section 4.15.4.8.

Table 4.4 RTI Properties for Configuring Cryptography
Property Name Property Value Description

RTI_SECURITY_ENCRYPTION_ALGORITHM_PROPERTY

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The algorithm used for encrypting and decrypting data and metadata. The options are aes-128-gcm, aes-192-gcm, and aes-256-gcm. (“gcm” is Galois/Counter Mode (GCM) authenticated encryption). The number indicates the number of bits in the key. Participants are not required to set this property to the same value in order to communicate with each other.

In the Domain Governance document, a “protection kind” set to ENCRYPT will use GCM, and a “protection kind” set to SIGN will use the GMAC variant of this algorithm.

Default: aes-128-gcm

RTI_SECURITY_MAX_RECEIVER_SPECIFIC_MACS_PROPERTY

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The maximum number of receiver-specific Message Authentication Codes (MACs) that are appended to an encoded result.

For example, if this value is 32 and the Participant is configured to protect both RTPS messages and submessages with origin authentication, there could be 32 receiver-specific MACs in the result of encode_datawriter_submessage, and there could be another 32 receiver-specific MACs in the result of encode_rtps_message.

If there are more than 32 receivers, the receivers will be assigned one of the 32 possible MACs in a round-robin fashion. Note that in the case of encode_datawriter_submessage, all the readers belonging to the same participant will always be assigned the same receiver-specific MAC.

Setting this value to 0 will completely disable receiver-specific MACs.

Default: 0.

Range: [0, 3275], excluding 1

RTI_SECURITY_MAX_BLOCKS_PER_SESSION_PROPERTY

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The number of message blocks that can be encrypted with the same key material. Whenever the number of blocks exceeds this value, new key material is computed. The block size depends on the encryption algorithm.

You can specify this value in decimal, octal, or hexadecimal. This value is an unsigned 64-bit integer.

Default: 0xffffffffffffffff

Table 4.5 RTI Properties for Configuring Logging
Property Name Property Value Description

DDS_PARTICIPANT_TRUST_PLUGINS_LOGGING_DISTRIBUTE_PROPERTY_NAME

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

This boolean value controls whether security-related log messages should be distributed over a DDS DataWriter. If true, then the Logging Plugin will create a Publisher and DataWriter within the same DomainParticipant that is setting this property.

There is no option to use a separate DomainParticipant or to share a DataWriter among multiple DomainParticipants.

To subscribe to the log messages, run rtiddsgen on resource/idl/builtin_logging_type.idl. Create a DataReader of type DDSSecurity::BuiltinLoggingType and topic DDS:Security:LogTopic. The DataReader must be allowed to subscribe to this topic according to its DomainParticipant’s permissions file.

Default: FALSE

DDS_PARTICIPANT_TRUST_PLUGINS_LOGGING_LOG_LEVEL_PROPERTY_NAME

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The logging verbosity level. All log messages at and below the log_level setting will be logged.

Possible values:

  • 0: emergency
  • 1: alert
  • 2: critical
  • 3: error (default)
  • 4: warning
  • 5: notice
  • 6: informational
  • 7: debug

DDS_PARTICIPANT_TRUST_PLUGINS_LOGGING_LOG_FILE_PROPERTY_NAME

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

The file that log messages are printed to.

Default: NULL

DDS_TRUST_PLUGINS_LOGGING_MAX_REMOTE_READERS_PROPERTY_NAME

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

Maximum remote readers the logging writer can write to.

Default: 16

DDS_TRUST_PLUGINS_LOGGING_MAX_ROUTES_PER_READER_PROPERTY_NAME

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

Logging writer resource-limit, which limits the number of routes that can be saved per Reader.

Default: 4

DDS_TRUST_PLUGINS_LOGGING_WRITER_DEPTH_PROPERTY_NAME

NOTE: Prefix with value of DDS_PARTICIPANT_TRUST_PREFIX_PROPERTY if set.

Optional.

History depth (in samples) of the logging DataWriter.

Integer.

Default: 64

4.15.4.1. DDS_SECURITY_IDENTITY_CA_PROPERTY

DDS_SECURITY_IDENTITY_CA_PROPERTY is a required property. It appears in Table 4.2, DDS Security Properties for Configuring Authentication.

This property’s value specifies an Identity Certificate Authority document that will be used for signing authentication certificate files. Participants that want to securely communicate with each other must use the same Identity Certificate Authority.

You will use OpenSSL and an openssl.cnf file to create a file in PEM format named cacert*.pem.

An example openssl.cnf file is here: rti_workspace/version/examples/dds_security/cert. You will need to modify this file to match your certificate folder structure and Identity Certificate Authority desired configuration.

Example OpenSSL commands are shown below.

RSA:

% openssl genrsa -out cakey.pem 2048
% openssl req -new -key cakey.pem -out ca.csr -config openssl.cnf
% openssl x509 -req -days 3650 -in ca.csr -signkey cakey.pem \
  -out cacert.pem

DSA:

% openssl dsaparam 2048 > dsaparam
% openssl gendsa -out cakeydsa.pem dsaparam
% openssl req -new -key cakeydsa.pem -out dsaca.csr \
  -config openssldsa.cnf
% openssl x509 -req -days 3650 -in dsaca.csr -signkey cakeydsa.pem \
  -out cacertdsa.pem

ECDSA:

% openssl ecparam -name prime256v1 > ecdsaparam
% openssl req -nodes -x509 -days 3650 -newkey ec:ecdsaparam \
  -keyout cakeyECdsa.pem -out cacertECdsa.pem -config opensslECdsa.cnf

Note

When running the above commands for ECDSA, you may run into these OpenSSL warnings:

WARNING: can't open config file: [default openssl
built-inpath]/openssl.cnf

unable to write 'random state'

To resolve the first warning, set the environmental variable OPENSSL_CONF with the path to the openssl.cnf file you are using. To resolve the second warning, set the environmental variable RANDFILE with the path to a writable file.

You may specify the value as either a file name or the document contents. The document should be in PEM format.

  • If setting the property value to a file name: The property value must have the prefix file: (no space after the colon), followed by the fully qualified path and name of the cacert*.pem file that appears after the -out parameter in the above commands.

  • If setting the property value to the contents of the document: The property value must have the prefix data:, (no space after the comma), followed by the contents inside the document. For example:

    "data:,-----BEGIN CERTIFICATE-----\nabcdef\n-----END CERTIFICATE-----"
    

    Note that the two \n characters are required.

4.15.4.2. DDS_SECURITY_PRIVATE_KEY_PROPERTY

DDS_SECURITY_PRIVATE_KEY_PROPERTY is a required property. It appears in Table 4.2, DDS Security Properties for Configuring Authentication.

Its value is a private key associated with the first certificate that appears in the identity_certificate. The default value is NULL.

After generating the ca_file, cacert*.pem (described in Section 4.15.4.1), use OpenSSL to generate a peer1key*.pem file using commands such as the following.

  • RSA:

    % openssl genrsa -out peer1key.pem 2048
    
  • DSA:

    % openssl dsaparam 2048 > dsaparam
    % openssl gendsa -out peer1keydsa.pem dsaparam
    
  • ECDSA:

    % openssl ecparam -name prime256v1 > ecdsaparam1
    % openssl req -nodes -new -newkey ec:ecdsaparam1 -config example1ECdsa.cnf \
      -keyout peer1keyECdsa.pem -out peer1reqECdsa.pem
    

    Notice that for ECDSA, OpenSSL also generates peer1reqECdsa.pem. That file will be used later to generate the certificate file for DDS_SECURITY_IDENTITY_CERTIFICATE_PROPERTY.

The document should be in PEM format. You may specify either the file name or the document contents.

  • If setting the property value to a file name: The property value must have the prefix file: (no space after the colon), followed by the fully qualified path and name of the file peer1key*.pem that appears after the -out parameters in the above commands.

  • If setting the property value to the contents of the document: The property value must have the prefix data:, (no space after the comma), followed by the contents inside the document. For example:

    "data:,-----BEGIN PRIVATE KEY-----\nabcdef\n-----END PRIVATE KEY-----"
    

    Note that the two \n characters are required.

4.15.4.3. DDS_SECURITY_IDENTITY_CERTIFICATE_PROPERTY

DDS_SECURITY_IDENTITY_CERTIFICATE_PROPERTY is a required property. It appears in Table 4.2, DDS Security Properties for Configuring Authentication.

Its value is an Identity Certificate, required for secure communication.

Before generating the certificate file for this property, first generate the ca_file (see Section 4.15.4.1) and the private_key_file (see Section 4.15.4.2).

Next, create a serial file whose contents are 01 and a blank index.txt file. The names of these files will depend on the contents of the openssl*.cnf file.

Then use OpenSSL to generate the certificate file using commands such as those seen below.

Example .cnf files are here: rti_workspace/version/examples/dds_security/cert.

Note: You will need to modify this file to match your certificate folder structure and Identity Certificate desired configuration:

  • RSA:

    % openssl req -config example1.cnf -new -key peer1key.pem \
      -out user.csr
    % openssl ca -config openssl.cnf -days 365 -in user.csr \
      -out peer1.pem
    
  • DSA:

    % openssl req -config example1dsa.cnf -new -key peer1keydsa.pem \
      -out dsauser.csr
    % openssl ca -config openssldsa.cnf -days 365 \
      -in dsauser.csr -out peer1dsa.pem
    

ECDSA:

Generate peer1reqECdsa.pem using the instructions for private_key_file (see Section 4.15.4.2).

% openssl ca -batch -create_serial -config opensslECdsa.cnf \
  -days 365 -in peer1reqECdsa.pem -out peer1ECdsa.pem

Notes:

  • openssl((EC)dsa).cnf must have the same countryName, stateOrProvinceName, and localityName as the example .cnf files.
  • Example .cnf files for different participants must have different commonNames.

The document should be in PEM format. You may specify either the file name or the document contents.

  • If setting the property value to a file name: The property value must have the prefix file: (no space after the colon), followed by the fully qualified path and name of the file peer1*.pem that appears after the -out parameters in the above commands.

  • If setting the property value to the contents of the document: The property value must have the prefix data:, (no space after the comma), followed by the contents inside the document. For example:

    "data:,-----BEGIN CERTIFICATE-----\nabcdef\n-----END CERTIFICATE-----"
    

    Note that the two “n” characters are required.

You may put a chain of certificates in the Identity Certificate by concatenating individual certificates and specifying the concatenated result as a single file or string. See Section 4.1, Identity Certificate Chaining, in the Security Plugins User’s Manual for 6.0.1 (available here if you have Internet access).

Default: NULL

4.15.4.4. DDS_SECURITY_PASSWORD_PROPERTY

This property is only required if private_key is encrypted.

Its value specifies the password used to decrypt the private_key (see Section 4.15.4.2).

This property appears in Table 4.2, DDS Security Properties for Configuring Authentication.

The value is interpreted as the Base64 encoding of the symmetric key that will be used to decrypt the private_key.

For example, suppose the private_key was encrypted using this command:

% openssl req -new -newkey ec:ecdsaparam2 \
  -config example2ECdsa.cnf -keyout peer2keyECdsa.pem \
  -passout pass:MyPassword -out peer2reqECdsa.pem

Then you can obtain the Base64 encoding of MyPassword with commands such as:

% echo MyPassword > foo
% openssl base64 -e -in foo
TXlQYXNzd29yZAo=

For the above example, the value of the password property should be “TXlQYXNzd29yZAo=”. If the private_key was not encrypted, the password must be NULL.

Default: NULL

4.15.4.5. RTI_SECURITY_SHARED_SECRET_ALGORITHM_PROPERTY

This is an optional property.

Its value sets the algorithm used to establish a shared secret during authentication. The options are dh and ecdh for (Elliptic Curve) Diffie-Hellman.

This property appears in Table 4.2, DDS Security Properties for Configuring Authentication.

If two participants discover each other and they specify different values for this algorithm, the algorithm that will be used is the one that belongs to the participant with the lower-valued participant_key.

Note: ecdh does not work with static OpenSSL libraries when using Certicom® Security Builder® engine.

Default: ecdh

4.15.4.6. RTI_SECURITY_CRL_FILE_PROPERTY

This is an optional property.

This Certificate Revocation List (CRL) keeps track of untrusted X.509 certificates.

This property appears in Table 4.2, DDS Security Properties for Configuring Authentication.

Use OpenSSL to generate this file using commands such as those seen below.

An example opensslECdsa.cnf file is here: rti_workspace/version/examples/dds_security/cert.

You will need to modify this file to match your certificate folder structure and Certificate Revocation List desired configuration.

% touch indexECdsa.txt
% echo 01 > crlnumberECdsa
% openssl ca -config opensslECdsa.cnf -batch \
  -revoke peerRevokedECdsa.pem
% openssl ca -config opensslECdsa.cnf -batch -gencrl \
  -out democaECdsa.crl

In this example:

  • crlnumberECdsa is the database of revoked certificates. This file should match the crlnumber value in opensslECdsa.cnf.
  • peerRevokedECdsa.pem is the certificate_file of a revoked DomainParticipant.
  • democaECdsa.crl should be the value of the crl_file property.
  • If crl_file is set to NULL, no CRL is checked and all valid certificates will be considered trusted.
  • If crl_file is set to an invalid CRL file, DomainParticipant creation will fail.
  • If crl_file is set to a valid CRL file, the CRL will be checked upon DomainParticipant creation and upon discovering other DomainParticipants. Creating a DomainParticipant with a revoked certificate will fail. If ParticipantA uses a certificate that does not appear in ParticipantA’s CRL, but does appear in ParticipantB’s CRL, then ParticipantB will reject and ignore ParticipantA. Changes in the CRL will not be enforced until the DomainParticipant using the CRL is deleted and recreated.

Default: NULL

4.15.4.7. DDS_SECURITY_PERMISSIONS_CA_PROPERTY

DDS_SECURITY_PERMISSIONS_CA_PROPERTY is a required property. It appears in Table 4.3, DDS Security Properties for Configuring Access Control.

This Permissions Certificate Authority is used for signing access control governance and permissions XML files, and verifying the signatures of those files.

The Permissions Certificate Authority file may or may not be the same as the Identity Certificate Authority file, but both files are generated in the same way. See Section 4.15.4.1 for the steps to generate this file.

Two participants that want to securely communicate with each other must use the same Permissions Certificate Authority.

The document should be in PEM format. You may specify either the file name or the document contents.

  • If specifying the file name, the property value must have the prefix file: (no space after the colon), followed by the fully qualified path and name of the file.

  • If specifying the contents of the document, the property value must have the prefix data:, (no space after the comma), followed by the contents inside the document. For example:

    ``data:,-----BEGIN CERTIFICATE-----\nabcdef\n-----END CERTIFICATE-----``
    

    Note that the two “n” characters are required.

Default: NULL

4.15.4.8. DDS_SECURITY_GOVERNANCE_PROPERTY

DDS_SECURITY_GOVERNANCE_PROPERTY is a required property. It appears in Table 4.3, DDS Security Properties for Configuring Access Control.

The Governance property configures the signed document that specifies the level of security required per domain and per topic. To sign an XML document with a Permissions Certificate Authority, run the following OpenSSL command (enter this all on one line):

openssl smime -sign -in Governance.xml -text
-out signed_Governance.p7s -signer cacert.pem
-inkey cakey.pem

Then set this property’s value to signed_Governance.p7s.

You may specify either the file name or the document contents.

  • If specifying the file name, the property value must have the prefix file: (no space after the colon), followed by the fully qualified path and name of the file.

  • If specifying the contents of the document, the property value must have the prefix data:, (no space after the comma), followed by the contents inside the document. For example:

    "data:,MIME-Version: 1.0\nContent-Type:...boundary=\”---7236\”\n\n"
    

Note that for signed XML files, all whitespace characters (‘ ‘, ‘r’, ‘n’) are significant, and all quotes must be escaped with a backslash. The safest way to get the correct property value is to call the fread() function on the file and use the resulting buffer as the property value.

Default: NULL

4.15.4.9. DDS_SECURITY_PERMISSIONS_PROPERTY

DDS_SECURITY_PERMISSIONS_PROPERTY is a required property. It appears in Table 4.3, DDS Security Properties for Configuring Access Control.

This property configures the signed document that specifies the access control permissions per domain and per topic.

The <subject_name> element identifies the DomainParticipant to which the permissions apply. Each subject name can only appear in a single <permissions> section within the XML Permissions document. The contents of the <subject_name> element should be the X.509 subject name for the DomainParticipant, as given in the “Subject” field of its Identity Certificate.

A <permissions> section with a subject name that does not match the subject name given in the corresponding Identity Certificate will be ignored.

To sign an XML document with a Permissions Certificate Authority, run the following OpenSSL command (enter this all on one line):

openssl smime -sign -in PermissionsA.xml -text
-out signed_PermissionsA.p7s -signer cacert.pem
-inkey cakey.pem

Then set this property value to signed_PermissionsA.p7s.

The signed permissions document only supports validity dates between 1970010100 and 2038011903. Any dates before 1970010100 will result in an error, and any dates after 2038011903 will be treated as 2038011903. Currently, Connext DDS will not work if the system time is after January 19th, 2038.

You may specify either the file name or the document contents.

  • If specifying the file name, the property value must have the prefix file: (no space after the colon), followed by the fully qualified path and name of the file.

  • If specifying the contents of the document, the property value must have the prefix data:, (no space after the comma), followed by the contents inside the document. For example:

    "data:,MIME-Version: 1.0\nContent-Type:...boundary=\”---7236\”\n\n"
    

Note that for signed XML files, all whitespace characters (‘ ‘, ‘r’, ‘n’) are significant, and all quotes must be escaped with a backslash. The safest way to get the correct property value is to call the fread() function on the file and use the resulting buffer as the property value.

Default: NULL