- Remove ConfigUserAuthenticationProvider and Users property — LDAP is the only auth mechanism - Fix historian quality mapping to use existing QualityMapper (OPC DA quality bytes, not custom mapping) - Add AppRoles constants, unify HasWritePermission/HasAlarmAckPermission into shared HasRole helper - Hoist write permission check out of per-item loop, eliminate redundant _ldapRolesEnabled field - Update docs (Configuration.md, Security.md, OpcUaServer.md, HistoricalDataAccess.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 KiB
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
Securityconfiguration 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
Noneendpoints 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:
{
"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)
{
"Security": {
"Profiles": ["None"],
"AutoAcceptClientCertificates": true
}
}
Example: Production (encrypted only)
{
"Security": {
"Profiles": ["Basic256Sha256-SignAndEncrypt"],
"AutoAcceptClientCertificates": false,
"RejectSHA1Certificates": true,
"MinimumCertificateKeySize": 2048
}
}
Example: Mixed (sign and encrypt endpoints, no plaintext)
{
"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:
- The client presents its application certificate during the secure channel handshake.
- The server checks whether the certificate exists in the
trusted/store. - If found, the connection proceeds (subject to key size and SHA-1 checks).
- If not found and
AutoAcceptClientCertificatesistrue, the certificate is automatically copied totrusted/and the connection proceeds. - If not found and
AutoAcceptClientCertificatesisfalse, the certificate is copied torejected/and the connection is refused. - Regardless of trust status, the certificate must meet the
MinimumCertificateKeySizerequirement and pass the SHA-1 check (ifRejectSHA1Certificatesistrue).
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:
{
"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:
{
"Security": {
"Profiles": ["Basic256Sha256-SignAndEncrypt"]
}
}
3. Configure LDAP authentication
Enable LDAP authentication to validate credentials against the GLAuth server. LDAP group membership controls what each user can do (read, write, alarm acknowledgment). See Configuration Guide for the full LDAP property reference.
{
"Authentication": {
"AllowAnonymous": false,
"AnonymousCanWrite": false,
"Ldap": {
"Enabled": true,
"Host": "localhost",
"Port": 3893,
"BaseDN": "dc=lmxopcua,dc=local",
"ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local",
"ServiceAccountPassword": "serviceaccount123"
}
}
}
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
cd tools/opcuacli-dotnet
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S none
Connect with signing
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S sign
Connect with signing and encryption
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S encrypt
Browse with encryption and authentication
dotnet run -- browse -u opc.tcp://localhost:4840/LmxOpcUa -S encrypt -U operator -P secure-password -r -d 3
Read a node with signing
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:
- Check the server's
rejected/directory for the client's certificate file. - Copy the
.derfile fromrejected/totrusted/. - Retry the connection.
- 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:
- Verify the server's configured
Profilesinappsettings.json. - Ensure the profile matching the client's requested mode is listed (e.g., add
Basic256Sha256-SignAndEncryptfor encrypted connections). - Restart the server after changing the configuration.
- Use the CLI tool to verify available endpoints:
The output displays the security mode and policy of the connected endpoint.
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S none
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:
- Ensure the service account has write access to the PKI root directory.
- Check that the
PkiRootPath(if overridden) points to a valid, writable location. - 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
RejectSHA1Certificatestofalsein the server configuration (not recommended for production).
LDAP Authentication
The server supports LDAP-based user authentication via GLAuth (or any standard LDAP server). When enabled, OPC UA UserName token credentials are validated by LDAP bind, and LDAP group membership controls what operations each user can perform.
Architecture
OPC UA Client → UserName Token → LmxOpcUa Server → LDAP Bind (validate credentials)
→ LDAP Search (resolve group membership)
→ Role assignment → Permission enforcement
LDAP Groups and OPC UA Permissions
| LDAP Group | OPC UA Permission |
|---|---|
| ReadOnly | Browse and read nodes |
| ReadWrite | Read and write tag values |
| AlarmAck | Acknowledge alarms |
Users can belong to multiple groups. A user with all three groups has full access.
GLAuth Setup
The project uses GLAuth v2.4.0 as the LDAP server, installed at C:\publish\glauth\. See C:\publish\glauth\auth.md for the complete user/group reference and service management commands.
Configuration
Enable LDAP in appsettings.json under Authentication.Ldap. See Configuration Guide for the full property reference.
Security Considerations
- LDAP credentials are transmitted in plaintext over the OPC UA channel unless transport security is enabled. Use
Basic256Sha256-SignAndEncryptfor production deployments. - The GLAuth LDAP server itself listens on plain LDAP (port 3893). Enable LDAPS in
glauth.cfgfor environments where LDAP traffic crosses network boundaries. - The service account password is stored in
appsettings.json. Protect this file with appropriate filesystem permissions.