Adds Security section to appsettings.json with configurable OPC UA transport profiles (None, Basic256Sha256-Sign, Basic256Sha256-SignAndEncrypt), certificate policy settings, and a configurable BindAddress for the OPC UA endpoint. Defaults preserve backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
260 lines
11 KiB
Markdown
260 lines
11 KiB
Markdown
# Transport Security
|
|
|
|
## Overview
|
|
|
|
The LmxOpcUa server supports configurable transport security profiles that control how data is protected on the wire between OPC UA clients and the server.
|
|
|
|
There are two distinct layers of security in OPC UA:
|
|
|
|
- **Transport security** -- secures the communication channel itself using TLS-style certificate exchange, message signing, and encryption. This is what the `Security` configuration section controls.
|
|
- **UserName token encryption** -- protects user credentials (username/password) sent during session activation. The OPC UA stack encrypts UserName tokens using the server's application certificate regardless of the transport security mode. This means UserName authentication works on `None` endpoints too — the credentials themselves are always encrypted. However, a secure transport profile adds protection against message-level tampering and eavesdropping of data payloads.
|
|
|
|
## Supported Security Profiles
|
|
|
|
The server supports three transport security profiles in Phase 1:
|
|
|
|
| Profile Name | Security Policy | Message Security Mode | Description |
|
|
|-----------------------------------|---------------------|-----------------------|--------------------------------------------------|
|
|
| `None` | None | None | No signing or encryption. Suitable for development and isolated networks only. |
|
|
| `Basic256Sha256-Sign` | Basic256Sha256 | Sign | Messages are signed but not encrypted. Protects against tampering but data is visible on the wire. |
|
|
| `Basic256Sha256-SignAndEncrypt` | Basic256Sha256 | SignAndEncrypt | Messages are both signed and encrypted. Full protection against tampering and eavesdropping. |
|
|
|
|
Multiple profiles can be enabled simultaneously. The server exposes a separate endpoint for each configured profile, and clients select the one they prefer during connection.
|
|
|
|
If no valid profiles are configured (or all names are unrecognized), the server falls back to `None` with a warning in the log.
|
|
|
|
## Configuration
|
|
|
|
Transport security is configured in the `Security` section of `appsettings.json`:
|
|
|
|
```json
|
|
{
|
|
"Security": {
|
|
"Profiles": ["None"],
|
|
"AutoAcceptClientCertificates": true,
|
|
"RejectSHA1Certificates": true,
|
|
"MinimumCertificateKeySize": 2048,
|
|
"PkiRootPath": null,
|
|
"CertificateSubject": null
|
|
}
|
|
}
|
|
```
|
|
|
|
### Properties
|
|
|
|
| Property | Type | Default | Description |
|
|
|--------------------------------|------------|--------------------------------------------------|-------------|
|
|
| `Profiles` | `string[]` | `["None"]` | List of security profile names to expose as server endpoints. Valid values: `None`, `Basic256Sha256-Sign`, `Basic256Sha256-SignAndEncrypt`. Profile names are case-insensitive. Duplicates are ignored. |
|
|
| `AutoAcceptClientCertificates` | `bool` | `true` | When `true`, the server automatically trusts client certificates that are not already in the trusted store. Set to `false` in production for explicit trust management. |
|
|
| `RejectSHA1Certificates` | `bool` | `true` | When `true`, client certificates signed with SHA-1 are rejected. SHA-1 is considered cryptographically weak. |
|
|
| `MinimumCertificateKeySize` | `int` | `2048` | Minimum RSA key size (in bits) required for client certificates. Certificates with shorter keys are rejected. |
|
|
| `PkiRootPath` | `string?` | `null` (defaults to `%LOCALAPPDATA%\OPC Foundation\pki`) | Override for the PKI root directory where certificates are stored. When `null`, uses the OPC Foundation default location. |
|
|
| `CertificateSubject` | `string?` | `null` (defaults to `CN={ServerName}, O=ZB MOM, DC=localhost`) | Override for the server certificate subject name. When `null`, the subject is derived from the configured `ServerName`. |
|
|
|
|
### Example: Development (no security)
|
|
|
|
```json
|
|
{
|
|
"Security": {
|
|
"Profiles": ["None"],
|
|
"AutoAcceptClientCertificates": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Example: Production (encrypted only)
|
|
|
|
```json
|
|
{
|
|
"Security": {
|
|
"Profiles": ["Basic256Sha256-SignAndEncrypt"],
|
|
"AutoAcceptClientCertificates": false,
|
|
"RejectSHA1Certificates": true,
|
|
"MinimumCertificateKeySize": 2048
|
|
}
|
|
}
|
|
```
|
|
|
|
### Example: Mixed (sign and encrypt endpoints, no plaintext)
|
|
|
|
```json
|
|
{
|
|
"Security": {
|
|
"Profiles": ["Basic256Sha256-Sign", "Basic256Sha256-SignAndEncrypt"],
|
|
"AutoAcceptClientCertificates": false
|
|
}
|
|
}
|
|
```
|
|
|
|
## PKI Directory Layout
|
|
|
|
The server stores certificates in a directory-based PKI store. The default root is:
|
|
|
|
```
|
|
%LOCALAPPDATA%\OPC Foundation\pki\
|
|
```
|
|
|
|
This can be overridden with the `PkiRootPath` setting. The directory structure is:
|
|
|
|
```
|
|
pki/
|
|
own/ Server's own application certificate and private key
|
|
issuer/ CA certificates that issued trusted client certificates
|
|
trusted/ Explicitly trusted client (peer) certificates
|
|
rejected/ Certificates that were presented but not trusted
|
|
```
|
|
|
|
### Certificate Trust Flow
|
|
|
|
When a client connects using a secure profile (`Sign` or `SignAndEncrypt`), the following trust evaluation occurs:
|
|
|
|
1. The client presents its application certificate during the secure channel handshake.
|
|
2. The server checks whether the certificate exists in the `trusted/` store.
|
|
3. If found, the connection proceeds (subject to key size and SHA-1 checks).
|
|
4. If not found and `AutoAcceptClientCertificates` is `true`, the certificate is automatically copied to `trusted/` and the connection proceeds.
|
|
5. If not found and `AutoAcceptClientCertificates` is `false`, the certificate is copied to `rejected/` and the connection is refused.
|
|
6. Regardless of trust status, the certificate must meet the `MinimumCertificateKeySize` requirement and pass the SHA-1 check (if `RejectSHA1Certificates` is `true`).
|
|
|
|
On first startup with a secure profile, the server automatically generates a self-signed application certificate in the `own/` directory if one does not already exist.
|
|
|
|
## Production Hardening
|
|
|
|
The default settings prioritize ease of development. Before deploying to production, apply the following changes:
|
|
|
|
### 1. Disable automatic certificate acceptance
|
|
|
|
Set `AutoAcceptClientCertificates` to `false` so that only explicitly trusted client certificates are accepted:
|
|
|
|
```json
|
|
{
|
|
"Security": {
|
|
"AutoAcceptClientCertificates": false
|
|
}
|
|
}
|
|
```
|
|
|
|
After changing this setting, you must manually copy each client's application certificate (the `.der` file) into the `trusted/` directory.
|
|
|
|
### 2. Remove the None profile
|
|
|
|
Remove `None` from the `Profiles` list to prevent unencrypted connections:
|
|
|
|
```json
|
|
{
|
|
"Security": {
|
|
"Profiles": ["Basic256Sha256-SignAndEncrypt"]
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Configure named users
|
|
|
|
Disable anonymous access and define named users in the `Authentication` section. Use `AnonymousCanWrite` to control whether anonymous clients (if still allowed) can write:
|
|
|
|
```json
|
|
{
|
|
"Authentication": {
|
|
"AllowAnonymous": false,
|
|
"AnonymousCanWrite": false,
|
|
"Users": [
|
|
{ "Username": "operator", "Password": "secure-password" },
|
|
{ "Username": "viewer", "Password": "read-only-password" }
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
While UserName tokens are always encrypted by the OPC UA stack (using the server certificate), enabling a secure transport profile adds protection against message-level tampering and data eavesdropping.
|
|
|
|
### 4. Review the rejected certificate store
|
|
|
|
Periodically inspect the `rejected/` directory. Certificates that appear here were presented by clients but were not trusted. If you recognize a legitimate client certificate, move it to the `trusted/` directory to grant access.
|
|
|
|
## CLI Examples
|
|
|
|
The `tools/opcuacli-dotnet` CLI tool supports the `-S` (or `--security`) flag to select the transport security mode when connecting. Valid values are `none`, `sign`, and `encrypt`.
|
|
|
|
### Connect with no security
|
|
|
|
```bash
|
|
cd tools/opcuacli-dotnet
|
|
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S none
|
|
```
|
|
|
|
### Connect with signing
|
|
|
|
```bash
|
|
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S sign
|
|
```
|
|
|
|
### Connect with signing and encryption
|
|
|
|
```bash
|
|
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S encrypt
|
|
```
|
|
|
|
### Browse with encryption and authentication
|
|
|
|
```bash
|
|
dotnet run -- browse -u opc.tcp://localhost:4840/LmxOpcUa -S encrypt -U operator -P secure-password -r -d 3
|
|
```
|
|
|
|
### Read a node with signing
|
|
|
|
```bash
|
|
dotnet run -- read -u opc.tcp://localhost:4840/LmxOpcUa -S sign -n "ns=2;s=TestMachine_001/Speed"
|
|
```
|
|
|
|
The CLI tool auto-generates its own client certificate on first use (stored under `%LOCALAPPDATA%\OpcUaCli\pki\own\`). When connecting to a server with `AutoAcceptClientCertificates` set to `false`, you must copy the CLI tool's certificate into the server's `trusted/` directory before the connection will succeed.
|
|
|
|
## Troubleshooting
|
|
|
|
### Certificate trust failure
|
|
|
|
**Symptom:** The client receives a `BadSecurityChecksFailed` or `BadCertificateUntrusted` error when connecting.
|
|
|
|
**Cause:** The server does not trust the client's certificate (or vice versa), and `AutoAcceptClientCertificates` is `false`.
|
|
|
|
**Resolution:**
|
|
1. Check the server's `rejected/` directory for the client's certificate file.
|
|
2. Copy the `.der` file from `rejected/` to `trusted/`.
|
|
3. Retry the connection.
|
|
4. If the server's own certificate is not trusted by the client, copy the server's certificate from `pki/own/certs/` to the client's trusted store.
|
|
|
|
### Endpoint mismatch
|
|
|
|
**Symptom:** The client receives a `BadSecurityModeRejected` or `BadSecurityPolicyRejected` error, or reports "No endpoint found with security mode...".
|
|
|
|
**Cause:** The client is requesting a security mode that the server does not expose. For example, the client requests `SignAndEncrypt` but the server only has `None` configured.
|
|
|
|
**Resolution:**
|
|
1. Verify the server's configured `Profiles` in `appsettings.json`.
|
|
2. Ensure the profile matching the client's requested mode is listed (e.g., add `Basic256Sha256-SignAndEncrypt` for encrypted connections).
|
|
3. Restart the server after changing the configuration.
|
|
4. Use the CLI tool to verify available endpoints:
|
|
```bash
|
|
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S none
|
|
```
|
|
The output displays the security mode and policy of the connected endpoint.
|
|
|
|
### Server certificate not generated
|
|
|
|
**Symptom:** The server logs a warning about application certificate check failure on startup.
|
|
|
|
**Cause:** The `pki/own/` directory may not be writable, or the certificate generation failed.
|
|
|
|
**Resolution:**
|
|
1. Ensure the service account has write access to the PKI root directory.
|
|
2. Check that the `PkiRootPath` (if overridden) points to a valid, writable location.
|
|
3. Delete any corrupt certificate files in `pki/own/` and restart the server to trigger regeneration.
|
|
|
|
### SHA-1 certificate rejection
|
|
|
|
**Symptom:** A client with a valid certificate is rejected, and the server logs mention SHA-1.
|
|
|
|
**Cause:** The client's certificate was signed with SHA-1, and `RejectSHA1Certificates` is `true` (the default).
|
|
|
|
**Resolution:**
|
|
- Regenerate the client certificate using SHA-256 or stronger (recommended).
|
|
- Alternatively, set `RejectSHA1Certificates` to `false` in the server configuration (not recommended for production).
|