Transport Layer Security
Masters and outstations may optionally use TLS which protects DNP3 communication by adding encryption and authentication. The DNP3 library uses rustls, a modern TLS library written in safe Rust. It does not depend on OpenSSL or other system libraries, but will interoperate seamlessly with other implementations.
Rustls has been reviewed by a third party and is production ready. It outperforms OpenSSL in almost every aspect and has the backing of the ISRG.
Secure Authentication?
DNP3 SAv5 (and v2) has a number of design flaws and is not widely adopted. The multi-user support in SAv5 was deprecated because it did not meet its stated design goals. SAv6 aims to reduce the complexity of SAv5, but it has not been standardized yet and there are no full implementations.
We recommend using TLS to secure DNP3 and caution the industry to avoid home-grown solutions such as secure authentication.
Supported Features
- TLS v1.2 and v1.3
- Supported cipher suites (in descending order of preference):
- TLS v1.3:
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256
- TLS v1.2:
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS v1.3:
- Supported key exchange algorithms (in descending order of preference):
x25519
secp384r1
secp256r1
- Supported signature algorithms (in descending order of preference):
ecdsa_secp384r1_sha384
ecdsa_secp256r1_sha256
ed25519
(v1.3 only)rsa_pss_sha512
(v1.3 only)rsa_pss_sha384
(v1.3 only)rsa_pss_sha256
(v1.3 only)rsa_pkcs1_sha512
rsa_pkcs1_sha384
rsa_pkcs1_sha256
- Client and server name validation through the Subject Alternative Name (SAN) extension or the Common Name.
- Self-signed certificates (with a special configuration).
Configuration
TLS configuration is configured using the TlsClientConfig
or TlsServerConfig
structures. They are very similar and contain the following fields:
name
:- The client and the server both verify that the certificate presented by the peer is valid for this name. Check out the next section for the gory details on name validation.
- The client will advertise this name through a Server Name Indication (SNI) extension in the
Client Hello
.
peer_cert_path
:- Path to the unencrypted PEM file containing the trusted root certificate(s) or the peer self-signed certificate.
local_cert_path
:- Path to the unencrypted PEM file containing the certificate(s) to present to the peer.
private_key_path
:- Path to the PEM file containing the encrypted or plaintext private key corresponding to the public key in the presented certificate.
password
:- Password used to decrypt the private key file. This field should be left empty if the file is not encrypted. See the next section for more details.
min_tls_version
:- Minimum TLS version to support. Setting this to
Tls1_3
will force the usage of TLSv1.3.
- Minimum TLS version to support. Setting this to
certificate_mode
:- Mode used to verify the peer certificate.
Name Validation
This section describes how name validation is performed when certificate_mode
is AuthorityBased
. When the mode
is SelfSigned
, name validation is performed indirectly since a byte-for-byte comparison occurs of the entire certificate.
A valid name has the same requirements as a DNS name. This is defined in
RFC 1034 Section 3.5,
updated by RFC1123 Section 2.1,
with the additional exception that underscores _
are allowed. A brief (but incomplete)
definition of a valid name includes:
- One or more labels separated by a single period
- Labels are made of alphanumeric characters
[A-Za-z0-9]
, hyphens-
, and underscores_
- A label cannot start or end with an hyphen
-
- A label cannot be all numeric
- A label cannot be empty
- Maximum of 63 characters per label
- Maximum of 253 characters total
If the SAN extension is present, the name is validated against it. The SAN may contain multiple names and each name can contain a wildcard *
character.
The comparison is case-insensitive.
If the SAN is absent, then the Common Name
from the certificate's Subject
is extracted and compared. The Common Name cannot contain a wildcard character
and the comparison is case-sensitive. It is effectively compared byte-for-byte with the expected name.
New certificates should always include the SAN extension. Performing name verification using the Common Name
is still secure, but it is deprecated for new use cases.
Private Key Encryption
Unencrypted private keys may be stored in PKCS#1 or PKCS#8 formats:
----BEGIN RSA PRIVATE KEY-----
-----BEGIN PRIVATE KEY-----
Encrypted private keys must be stored in PKCS#8v2 format:
-----BEGIN ENCRYPTED PRIVATE KEY-----
Keys encrypted in PKCS#1 format are not supported.
The following algorithms are supported when keys are encrypted in PKCS#8v2 format:
- Key derivation functions:
- scrypt (RFC 7914) recommended
- PBKDF2 (RFC 8018) using one of the following HMAC:
- HMAC-SHA224
- HMAC-SHA256
- HMAC-SHA384
- HMAC-SHA512
- Symmetric encryption:
- AES-128-CBC
- AES-192-CBC
- AES-256-CBC recommended
SHA-1 based key derivation and DES/3DES symmetric encryption are not supported because they are insecure.
The OpenSSL pkcs8 command can be used to encrypt keys or convert private key formats.
Encrypting private keys for use with TLS rarely addresses a meaningful threat model. Encrypting a private key and storing the password in a configuration file is equivalent to locking your doors and leaving the key under the doormat.
Certificate Mode
The certificate_mode
parameter determines how the presented peer certificate
is validated.
Authority-based
The default AuthorityBased
mode uses the default rustls
implementation to check the presented chain of certificates
and verify that the root certificate of the chain is one of those provided in the peer_cert_path
file.
Most, if not all applications, in the power industry, will have a single root certificate.
This mode validates the chain and the end-entity certificate in accordance with RFC 5280.
Self-Signed
The SelfSigned
mode validates that:
- Only a single certificate is presented
- It is a byte-for-byte match of the one specified in
peer_cert_path
NotBefore
andNotAfter
time fields are valid for the current time
Name validation is performed indirectly in self-signed mode, since the byte-for-byte comparison also compares the internal name fields.
Generating Certificates
The following OpenSSL commands are provided for testing purposes only. Real systems will use some kind of specialized CA software for certificate procurement.
Full CA chain
- Generate the root CA certificate:
openssl req -x509 -newkey rsa:4096 -keyout ./ca_key.pem -out ./ca_cert.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -nodes -days 3650
- Generate the master CSR:
openssl req -new -newkey rsa:4096 -keyout ./master_key.pem -out ./master_csr.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "subjectAltName=DNS:test.com" -nodes -days 365
- Generate the master certificate:
openssl x509 -req -days 365 -in ./master_csr.pem -extfile <(printf "subjectAltName=DNS:test.com") -CA ./ca_cert.pem -CAkey ./ca_key.pem -set_serial 1 -out ./master_cert.pem -sha256
- Generate the outstation CSR:
openssl req -new -newkey rsa:4096 -keyout ./outstation_key.pem -out ./outstation_csr.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "subjectAltName=DNS:test.com" -nodes -days 365
- Generate the outstation certificate:
openssl x509 -req -days 365 -in ./outstation_csr.pem -extfile <(printf "subjectAltName=DNS:test.com") -CA ./ca_cert.pem -CAkey ./ca_key.pem -set_serial 2 -out ./outstation_cert.pem -sha256
Self-signed certificate
- Generate the master certificate:
openssl req -x509 -newkey rsa:4096 -keyout ./master_key.pem -out ./master_cert.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "subjectAltName=DNS:test.com" -nodes -days 365
- Generate the outstation certificate:
openssl req -x509 -newkey rsa:4096 -keyout ./outstation_key.pem -out ./outstation/entity2_cert.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "subjectAltName=DNS:test.com" -nodes -days 365