Transport Layer Security
Masters and outstations can use TLS for encryption and authentication. The library uses rustls instead of OpenSSL:
- More secure and reliable: rustls is written in memory-safe Rust, which rules out the buffer overflows and use-after-free bugs behind many of OpenSSL's most serious vulnerabilities, such as Heartbleed, and the crashes they cause. It only implements TLS 1.2 and 1.3 with modern cipher suites, so there are no legacy protocols or weak options to leave enabled by mistake.
- The future: security agencies such as CISA and the NSA urge moving critical software to memory-safe languages, and rustls is backed by the Internet Security Research Group, the organization behind Let's Encrypt, as part of its effort to do that for the internet's infrastructure. With the AWS-LC cryptography provider, it already supports post-quantum key exchange.
Secure authentication?
DNP3 Secure Authentication v5 has known design flaws and little adoption, and its successors, SAv6 and AMP, reinvent what TLS already provides. We explain why in The Case Against DNP3 SAv6 and AMP. We recommend TLS: it's proven, audited, and already used with DNP3 in production.
Supported features
- TLS v1.2 and v1.3
- The cipher suites, key exchange groups, and signature schemes are the rustls defaults for the cryptography provider: ring or aws-lc-rs in Rust, selected by the
tlsortls-aws-lcfeature, and aws-lc-rs in the prebuilt bindings on every platform. - Client and server name validation through the Subject Alternative Name (SAN) extension or the Common Name.
- Self-signed certificates (with a special configuration).
Configuration
Configure TLS with TlsClientConfig or TlsServerConfig: the trusted peer certificate(s), your certificate and private
key, the expected peer name, the minimum TLS version, and the certificate mode described below. The API documentation
describes each field.
In the bindings, pass an empty string, not null, as the password of a private key that isn't encrypted.
Name validation
In AuthorityBased mode, the name is matched against the certificate's Subject Alternative Name (SAN) entries, ignoring
case, and wildcards are allowed. An IP address is matched against the SAN's IP entries. If the certificate has no SAN,
its Common Name must match exactly.
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
Private keys are PEM files in PKCS#1 or PKCS#8 format. An encrypted key must be PKCS#8
(BEGIN ENCRYPTED PRIVATE KEY) using scrypt or PBKDF2 with AES-CBC. Legacy SHA-1 and DES/3DES encryption isn't
supported. The OpenSSL pkcs8 command can convert a key.
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.
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 NotBeforeandNotAftertime fields are valid for the current time
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_cert.pem \
-subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" \
-addext "subjectAltName=DNS:test.com" -nodes -days 365