# 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 seven transport security profiles: | 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. | | `Aes128_Sha256_RsaOaep-Sign` | Aes128_Sha256_RsaOaep | Sign | Modern profile with AES-128 encryption and SHA-256 signing. | | `Aes128_Sha256_RsaOaep-SignAndEncrypt` | Aes128_Sha256_RsaOaep | SignAndEncrypt | Modern profile with AES-128 encryption. Recommended for production. | | `Aes256_Sha256_RsaPss-Sign` | Aes256_Sha256_RsaPss | Sign | Strongest profile with AES-256 and RSA-PSS signatures. | | `Aes256_Sha256_RsaPss-SignAndEncrypt` | Aes256_Sha256_RsaPss | SignAndEncrypt | Strongest profile. Recommended for high-security deployments. | 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`, `Aes128_Sha256_RsaOaep-Sign`, `Aes128_Sha256_RsaOaep-SignAndEncrypt`, `Aes256_Sha256_RsaPss-Sign`, `Aes256_Sha256_RsaPss-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": ["Aes256_Sha256_RsaPss-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](Configuration.md) for the full LDAP property reference. ```json { "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. ## X.509 Certificate Authentication The server supports X.509 certificate-based user authentication in addition to Anonymous and UserName tokens. When any non-None security profile is configured, the server advertises `UserTokenType.Certificate` in its endpoint descriptions. Clients can authenticate by presenting an X.509 certificate. The server extracts the Common Name (CN) from the certificate subject and assigns the `AuthenticatedUser` and `ReadOnly` roles. The authentication is logged with the certificate's CN, subject, and thumbprint. X.509 authentication is available automatically when transport security is enabled -- no additional configuration is required. ## Audit Logging The server generates audit log entries for security-relevant operations. All audit entries use the `AUDIT:` prefix and are written to the Serilog rolling file sink for compliance review. Audited events: - **Authentication success**: Logs username, assigned roles, and session ID - **Authentication failure**: Logs username and session ID - **X.509 authentication**: Logs certificate CN, subject, and thumbprint - **Certificate validation**: Logs certificate subject, thumbprint, and expiry for all validation events (accepted or rejected) - **Write access denial**: Logged by the role-based access control system when a user lacks the required role Example audit log entries: ``` AUDIT: Authentication SUCCESS for user admin with roles [ReadOnly, WriteOperate, AlarmAck] session abc123 AUDIT: Authentication FAILED for user baduser from session def456 X509 certificate authenticated: CN=ClientApp, Subject=CN=ClientApp,O=Acme, Thumbprint=AB12CD34 ``` ## CLI Examples The Client CLI supports the `-S` (or `--security`) flag to select the transport security mode when connecting. Valid values are `none`, `sign`, `encrypt`, and `signandencrypt`. ### Connect with no security ```bash dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S none ``` ### Connect with signing ```bash dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S sign ``` ### Connect with signing and encryption ```bash dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- 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). --- ## 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. LDAP group membership is resolved once during authentication and mapped to custom OPC UA role `NodeId`s in the `urn:zbmom:lmxopcua:roles` namespace. These role NodeIds are stored on the session's `RoleBasedIdentity.GrantedRoleIds` and checked directly during write and alarm-ack operations. ### Architecture ``` OPC UA Client → UserName Token → LmxOpcUa Server → LDAP Bind (validate credentials) → LDAP Search (resolve group membership) → Map groups to OPC UA role NodeIds → Store on RoleBasedIdentity.GrantedRoleIds → Permission checks via GrantedRoleIds.Contains() ``` ### LDAP Groups and OPC UA Permissions All authenticated LDAP users can browse and read nodes regardless of group membership. Groups grant additional permissions: | LDAP Group | Permission | |---|---| | ReadOnly | No additional permissions (read-only access) | | WriteOperate | Write FreeAccess and Operate attributes | | WriteTune | Write Tune attributes | | WriteConfigure | Write Configure attributes | | AlarmAck | Acknowledge alarms | Users can belong to multiple groups. The `admin` user in the default GLAuth configuration belongs to all groups. ### Effective Permission Matrix The effective permission for a write operation depends on two factors: the user's session role (from LDAP group membership or anonymous access) and the Galaxy attribute's security classification. The security classification controls the node's `AccessLevel` — attributes classified as `SecuredWrite`, `VerifiedWrite`, or `ViewOnly` are exposed as read-only nodes regardless of the user's role. For writable classifications, the required write role depends on the classification. | | FreeAccess | Operate | SecuredWrite | VerifiedWrite | Tune | Configure | ViewOnly | |---|---|---|---|---|---|---|---| | **Anonymous (`AnonymousCanWrite=true`)** | Write | Write | Read | Read | Write | Write | Read | | **Anonymous (`AnonymousCanWrite=false`)** | Read | Read | Read | Read | Read | Read | Read | | **ReadOnly** | Read | Read | Read | Read | Read | Read | Read | | **WriteOperate** | Write | Write | Read | Read | Read | Read | Read | | **WriteTune** | Read | Read | Read | Read | Write | Read | Read | | **WriteConfigure** | Read | Read | Read | Read | Read | Write | Read | | **AlarmAck** (only) | Read | Read | Read | Read | Read | Read | Read | | **Admin** (all groups) | Write | Write | Read | Read | Write | Write | Read | All roles can browse and read all nodes. The "Read" entries above mean the node is either read-only by classification or the user lacks the required write role. "Write" means the write is permitted by both the node's classification and the user's role. Alarm acknowledgment is an independent permission controlled by the `AlarmAck` role and is not affected by security classification. ### GLAuth Setup The project uses [GLAuth](https://github.com/glauth/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](Configuration.md) 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-SignAndEncrypt` for production deployments. - The GLAuth LDAP server itself listens on plain LDAP (port 3893). Enable LDAPS in `glauth.cfg` for environments where LDAP traffic crosses network boundaries. - The service account password is stored in `appsettings.json`. Protect this file with appropriate filesystem permissions.