Skip to main content

Transport Layer Security

Modbus Security is a specification to wrap Modbus TCP inside a TLS secure transport to enable authentication and privacy of the communication links. It uses an X.509v3 extension to provide an authorization mechanism on the server. The IANA reserved port for Modbus Security is 802.

In the presented certificate, the client must provide an x.509v3 extension with OID 1.3.6.1.4.1.50316.802.1 containing an ASN1:UTF8String. This arbitrary string represents the authorized role of the client and is used to authorize operations. For example, a client with the historian role might only be allowed to read a certain range of values, while a client with the operator role might be able to write coils and registers.

sequenceDiagram Client->Server: TLS handshake Client->>Server: Modbus request Server->>Authorization Handler: Check authorization alt authorized Authorization Handler->>Server: AUTHORIZED Server->>Point Database: Execute operation Point Database->>Server: Response Server->>Client: Response else not authorized Authorization Handler->>Server: NOT_AUTHORIZED Server->>Client: Illegal function code exception end

This 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.

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
  • 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
  • Server name validation through the Subject Alternative Name (SAN) extension or the Common Name (CN).
  • 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 (client only):
    • The client verifies that the certificate presented by the server 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.
  • 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.

The client always validates the server name when certificate_mode is TrustChain.

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.

tip

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.

tip

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 and NotAfter time fields are valid for the current time
note

Name validation is performed indirectly in self-signed mode, since the byte-for-byte comparison also compares the internal name fields.

AuthorizationHandler

When creating a Modbus Security server, you also need to provide an AuthorizationHandler. It is use to validate that the client has the authorization to perform the operation. There's a callback for each function code. You get the unit ID of the destination, information about what points are requested (either as an AddressRange or a 16-bit unsigned integer) as well as the Role ID that was extracted from the presented certificate.

If your handler returns AuthorizationResult::Authorized, then the request is passed to the appropriate handler and the operation is performed. If your handler returns AuthorizationResult::NotAuthorized, then the request is dropped and an Illegal function code exception is returned to the client.

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 client CSR:
openssl req -new -newkey rsa:4096 -keyout ./client_key.pem -out ./client_csr.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "1.3.6.1.4.1.50316.802.1=ASN1:UTF8String:operator" -nodes -days 365
  • Generate the client certificate:
openssl x509 -req -days 365 -in ./client_csr.pem -extfile <(printf "1.3.6.1.4.1.50316.802.1=ASN1:UTF8String:operator") -CA ./ca_cert.pem -CAkey ./ca_key.pem -set_serial 1 -out ./client_cert.pem -sha256
  • Generate the server CSR:
openssl req -new -newkey rsa:4096 -keyout ./server_key.pem -out ./server_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 server certificate:
openssl x509 -req -days 365 -in ./server_csr.pem -extfile <(printf "subjectAltName=DNS:test.com") -CA ./ca_cert.pem -CAkey ./ca_key.pem -set_serial 2 -out ./server_cert.pem -sha256

Self-signed certificate

  • Generate the client certificate:
openssl req -x509 -newkey rsa:4096 -keyout ./client_key.pem -out ./client_cert.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "1.3.6.1.4.1.50316.802.1=ASN1:UTF8String:operator" -nodes -days 365
  • Generate the server certificate:
openssl req -x509 -newkey rsa:4096 -keyout ./server_key.pem -out ./server/entity2_cert.pem -subj "/C=US/ST=Oregon/L=Bend/O=Test/CN=DO NOT USE" -addext "subjectAltName=DNS:test.com" -nodes -days 365