Add configurable transport security profiles and bind address
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>
This commit is contained in:
@@ -37,6 +37,31 @@ Example:
|
||||
dotnet run -- write -u opc.tcp://localhost:4840 -n "ns=2;s=MyNode" -v 42 -U operator -P op123
|
||||
```
|
||||
|
||||
## Transport Security Options
|
||||
|
||||
All commands accept the `-S` / `--security` flag to select the transport security mode:
|
||||
|
||||
| Flag | Values | Description |
|
||||
|------|--------|-------------|
|
||||
| `-S` / `--security` | `none`, `sign`, `encrypt` | Transport security mode (default: `none`) |
|
||||
|
||||
When `sign` or `encrypt` is specified, the CLI tool:
|
||||
|
||||
1. Ensures a client application certificate exists (auto-created if missing)
|
||||
2. Discovers server endpoints and selects one matching the requested `MessageSecurityMode`
|
||||
3. Prefers `Basic256Sha256` when multiple matching endpoints exist
|
||||
4. Fails with a clear error if no matching endpoint is found
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Connect with encrypted transport
|
||||
dotnet run -- connect -u opc.tcp://localhost:4840/LmxOpcUa -S encrypt
|
||||
|
||||
# Browse with signed transport and credentials
|
||||
dotnet run -- browse -u opc.tcp://localhost:4840/LmxOpcUa -S sign -U admin -P secret -r -d 2
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### connect
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The service loads configuration from `appsettings.json` at startup using the Microsoft.Extensions.Configuration stack. `AppConfiguration` is the root holder class that aggregates five typed sections: `OpcUa`, `MxAccess`, `GalaxyRepository`, `Dashboard`, and `Historian`. Each section binds to a dedicated POCO class with sensible defaults, so the service runs with zero configuration on a standard deployment.
|
||||
The service loads configuration from `appsettings.json` at startup using the Microsoft.Extensions.Configuration stack. `AppConfiguration` is the root holder class that aggregates typed sections: `OpcUa`, `MxAccess`, `GalaxyRepository`, `Dashboard`, `Historian`, `Authentication`, and `Security`. Each section binds to a dedicated POCO class with sensible defaults, so the service runs with zero configuration on a standard deployment.
|
||||
|
||||
## Config Binding Pattern
|
||||
|
||||
@@ -22,6 +22,7 @@ configuration.GetSection("GalaxyRepository").Bind(_config.GalaxyRepository);
|
||||
configuration.GetSection("Dashboard").Bind(_config.Dashboard);
|
||||
configuration.GetSection("Historian").Bind(_config.Historian);
|
||||
configuration.GetSection("Authentication").Bind(_config.Authentication);
|
||||
configuration.GetSection("Security").Bind(_config.Security);
|
||||
```
|
||||
|
||||
This pattern uses `IConfiguration.GetSection().Bind()` rather than `IOptions<T>` because the service targets .NET Framework 4.8, where the full dependency injection container is not used.
|
||||
@@ -46,6 +47,7 @@ Controls the OPC UA server endpoint and session limits. Defined in `OpcUaConfigu
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `BindAddress` | `string` | `"0.0.0.0"` | IP address or hostname the server binds to. Use `0.0.0.0` for all interfaces, `localhost` for local-only, or a specific IP |
|
||||
| `Port` | `int` | `4840` | TCP port the OPC UA server listens on |
|
||||
| `EndpointPath` | `string` | `"/LmxOpcUa"` | Path appended to the host URI |
|
||||
| `ServerName` | `string` | `"LmxOpcUa"` | Server name presented to OPC UA clients |
|
||||
@@ -130,6 +132,30 @@ Example configuration:
|
||||
}
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
Controls OPC UA transport security profiles and certificate handling. Defined in `SecurityProfileConfiguration`. See [Security Guide](security.md) for detailed usage.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `Profiles` | `List<string>` | `["None"]` | Security profiles to expose. Valid: `None`, `Basic256Sha256-Sign`, `Basic256Sha256-SignAndEncrypt` |
|
||||
| `AutoAcceptClientCertificates` | `bool` | `true` | Auto-accept untrusted client certificates. Set to `false` in production |
|
||||
| `RejectSHA1Certificates` | `bool` | `true` | Reject client certificates signed with SHA-1 |
|
||||
| `MinimumCertificateKeySize` | `int` | `2048` | Minimum RSA key size for client certificates |
|
||||
| `PkiRootPath` | `string?` | `null` | Override for PKI root directory. Defaults to `%LOCALAPPDATA%\OPC Foundation\pki` |
|
||||
| `CertificateSubject` | `string?` | `null` | Override for server certificate subject. Defaults to `CN={ServerName}, O=ZB MOM, DC=localhost` |
|
||||
|
||||
Example — production deployment with encrypted transport:
|
||||
|
||||
```json
|
||||
"Security": {
|
||||
"Profiles": ["Basic256Sha256-SignAndEncrypt"],
|
||||
"AutoAcceptClientCertificates": false,
|
||||
"RejectSHA1Certificates": true,
|
||||
"MinimumCertificateKeySize": 2048
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
||||
Three boolean properties act as feature flags that control optional subsystems:
|
||||
@@ -146,6 +172,10 @@ Three boolean properties act as feature flags that control optional subsystems:
|
||||
- `OpcUa.GalaxyName` must not be empty
|
||||
- `MxAccess.ClientName` must not be empty
|
||||
- `GalaxyRepository.ConnectionString` must not be empty
|
||||
- `Security.MinimumCertificateKeySize` must be at least 2048
|
||||
- Unknown security profile names are logged as warnings
|
||||
- `AutoAcceptClientCertificates = true` emits a warning
|
||||
- Only-`None` profile configuration emits a warning
|
||||
|
||||
If validation fails, the service throws `InvalidOperationException` and does not start.
|
||||
|
||||
@@ -169,6 +199,7 @@ Integration tests use this constructor to inject substitute implementations of `
|
||||
```json
|
||||
{
|
||||
"OpcUa": {
|
||||
"BindAddress": "0.0.0.0",
|
||||
"Port": 4840,
|
||||
"EndpointPath": "/LmxOpcUa",
|
||||
"ServerName": "LmxOpcUa",
|
||||
@@ -210,6 +241,14 @@ Integration tests use this constructor to inject substitute implementations of `
|
||||
"AllowAnonymous": true,
|
||||
"AnonymousCanWrite": true,
|
||||
"Users": []
|
||||
},
|
||||
"Security": {
|
||||
"Profiles": ["None"],
|
||||
"AutoAcceptClientCertificates": true,
|
||||
"RejectSHA1Certificates": true,
|
||||
"MinimumCertificateKeySize": 2048,
|
||||
"PkiRootPath": null,
|
||||
"CertificateSubject": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ The OPC UA server component hosts the Galaxy-backed namespace on a configurable
|
||||
|
||||
| Property | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `BindAddress` | `0.0.0.0` | IP address or hostname the server binds to |
|
||||
| `Port` | `4840` | TCP port the server listens on |
|
||||
| `EndpointPath` | `/LmxOpcUa` | URI path appended to the base address |
|
||||
| `ServerName` | `LmxOpcUa` | Application name presented to clients |
|
||||
@@ -16,7 +17,7 @@ The OPC UA server component hosts the Galaxy-backed namespace on a configurable
|
||||
| `SessionTimeoutMinutes` | `30` | Idle session timeout |
|
||||
| `AlarmTrackingEnabled` | `false` | Enables `AlarmConditionState` nodes for alarm attributes |
|
||||
|
||||
The resulting endpoint URL is `opc.tcp://0.0.0.0:{Port}{EndpointPath}`, e.g., `opc.tcp://0.0.0.0:4840/LmxOpcUa`.
|
||||
The resulting endpoint URL is `opc.tcp://{BindAddress}:{Port}{EndpointPath}`, e.g., `opc.tcp://0.0.0.0:4840/LmxOpcUa`.
|
||||
|
||||
The namespace URI follows the pattern `urn:{GalaxyName}:LmxOpcUa` and serves as both the `ApplicationUri` and `ProductUri`.
|
||||
|
||||
@@ -31,19 +32,21 @@ The configuration covers:
|
||||
- **TransportQuotas** -- 4 MB max message/string/byte-string size, 120-second operation timeout, 1-hour security token lifetime
|
||||
- **TraceConfiguration** -- OPC Foundation SDK tracing is disabled (output path `null`, trace masks `0`); all logging goes through Serilog instead
|
||||
|
||||
## Security Policy
|
||||
## Security Profiles
|
||||
|
||||
The server runs with `MessageSecurityMode.None` and `SecurityPolicies.None`:
|
||||
The server supports configurable transport security profiles controlled by the `Security` section in `appsettings.json`. The default configuration exposes only `MessageSecurityMode.None` for backward compatibility.
|
||||
|
||||
```csharp
|
||||
SecurityPolicies = { new ServerSecurityPolicy
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.None,
|
||||
SecurityPolicyUri = SecurityPolicies.None
|
||||
} }
|
||||
```
|
||||
Supported Phase 1 profiles:
|
||||
|
||||
This is intentional for plant-floor deployments where the server sits on an isolated OT network. Galaxy-level security classification controls write access per attribute rather than at the transport layer.
|
||||
| Profile Name | SecurityPolicy URI | MessageSecurityMode |
|
||||
|---|---|---|
|
||||
| `None` | `SecurityPolicy#None` | `None` |
|
||||
| `Basic256Sha256-Sign` | `SecurityPolicy#Basic256Sha256` | `Sign` |
|
||||
| `Basic256Sha256-SignAndEncrypt` | `SecurityPolicy#Basic256Sha256` | `SignAndEncrypt` |
|
||||
|
||||
`SecurityProfileResolver` maps configured profile names to `ServerSecurityPolicy` instances at startup. Unknown names are skipped with a warning, and an empty or invalid list falls back to `None`.
|
||||
|
||||
For production deployments, configure `["Basic256Sha256-SignAndEncrypt"]` or `["None", "Basic256Sha256-SignAndEncrypt"]` and set `AutoAcceptClientCertificates` to `false`. See the [Security Guide](security.md) for hardening details.
|
||||
|
||||
### User token policies
|
||||
|
||||
@@ -62,7 +65,7 @@ On successful validation, the session identity is set to a `RoleBasedIdentity` t
|
||||
|
||||
## Certificate handling
|
||||
|
||||
On startup, `OpcUaServerHost.StartAsync` calls `CheckApplicationInstanceCertificate(false, 2048)` to locate or create a 2048-bit self-signed certificate. The certificate subject follows the format `CN={ServerName}, O=ZB MOM, DC=localhost`. Certificate stores use the directory-based store type under `%LOCALAPPDATA%\OPC Foundation\pki\`:
|
||||
On startup, `OpcUaServerHost.StartAsync` calls `CheckApplicationInstanceCertificate(false, minKeySize)` to locate or create a self-signed certificate meeting the configured minimum key size (default 2048). The certificate subject defaults to `CN={ServerName}, O=ZB MOM, DC=localhost` but can be overridden via `Security.CertificateSubject`. Certificate stores use the directory-based store type under the configured `Security.PkiRootPath` (default `%LOCALAPPDATA%\OPC Foundation\pki\`):
|
||||
|
||||
| Store | Path suffix |
|
||||
|-------|-------------|
|
||||
@@ -71,7 +74,7 @@ On startup, `OpcUaServerHost.StartAsync` calls `CheckApplicationInstanceCertific
|
||||
| Trusted peers | `pki/trusted` |
|
||||
| Rejected | `pki/rejected` |
|
||||
|
||||
`AutoAcceptUntrustedCertificates` is set to `true` so the server does not reject client certificates.
|
||||
`AutoAcceptUntrustedCertificates` is controlled by `Security.AutoAcceptClientCertificates` (default `true`). Set to `false` in production to enforce client certificate trust. When `RejectSHA1Certificates` is `true` (default), client certificates signed with SHA-1 are rejected. Certificate validation events are logged for visibility into accepted and rejected client connections.
|
||||
|
||||
## Server class hierarchy
|
||||
|
||||
@@ -101,4 +104,6 @@ On startup, `OpcUaServerHost.StartAsync` calls `CheckApplicationInstanceCertific
|
||||
|
||||
- `src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/OpcUaServerHost.cs` -- Application lifecycle and programmatic configuration
|
||||
- `src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxOpcUaServer.cs` -- StandardServer subclass and node manager creation
|
||||
- `src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/SecurityProfileResolver.cs` -- Profile-name to ServerSecurityPolicy mapping
|
||||
- `src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/OpcUaConfiguration.cs` -- Configuration POCO
|
||||
- `src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/SecurityProfileConfiguration.cs` -- Security configuration POCO
|
||||
|
||||
259
docs/security.md
Normal file
259
docs/security.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user