# Certificates The application creates its required OPC UA certificates in a directory or OS-level certificate store on first startup. They remain in use until an administrator deletes them from the store. The stack also supports application certificates issued by a certificate authority (CA), plus remote certificate-store and trust-list management through a *Global Discovery Server* using *Server Push*. Private keys do not have to live in a file or OS certificate store. A TPM, smart card, hardware security module (HSM), or remote key service can hold a key without exposing it to process memory. See [CryptoProvider](CryptoProvider.md) for pluggable cryptography and hardware-held keys, or use the `OPCFoundation.NetStandard.Opc.Ua.Security.Pkcs11` package for PKCS#11 tokens. ## Contents - [Certificate stores](#certificate-stores) - [X509Store on Windows](#x509store-on-windows) - [Certificate and CertificateCollection Types](#certificate-and-certificatecollection-types) - [Opening a certificate store](#opening-a-certificate-store) - [Windows .NET applications](#windows-net-applications) - [Windows UWP applications](#windows-uwp-applications) - [.NET Core applications on Windows, Linux, iOS etc](#net-core-applications-on-windows-linux-ios-etc) - [Certificate Validation](#certificate-validation) - [Validation Workflow](#validation-workflow) - [Chain Building Process](#chain-building-process) - [Overview](#overview) - [Chain Building Algorithm Diagram](#chain-building-algorithm-diagram) - [Detailed Algorithm Steps](#detailed-algorithm-steps) - [Step 1: Initialization](#step-1-initialization) - [Step 2: Iterative Chain Building Loop](#step-2-iterative-chain-building-loop) - [Step 3: Issuer Matching Algorithm](#step-3-issuer-matching-algorithm) - [Step 4: Certificate Revocation List (CRL) Checking](#step-4-certificate-revocation-list-crl-checking) - [X509Chain Validation](#x509chain-validation) - [Chain Validation Results](#chain-validation-results) - [Key Behaviors](#key-behaviors) - [Certificate List Configuration](#certificate-list-configuration) - [Configuration Sources](#configuration-sources) - [Configuration File Structure](#configuration-file-structure) - [Certificate Store Types](#certificate-store-types) - [Certificate List Population](#certificate-list-population) - [1. Initialization via ApplicationConfiguration](#1-initialization-via-applicationconfiguration) - [2. Internal Update Process](#2-internal-update-process) - [3. Certificate Search Behavior](#3-certificate-search-behavior) - [Runtime Certificate Management](#runtime-certificate-management) - [Certificate Store Management](#certificate-store-management) - [Dual-Mode Operation](#dual-mode-operation) - [Configuration Best Practices](#configuration-best-practices) - [Configuration Settings](#configuration-settings) - [AutoAcceptUntrustedCertificates](#autoacceptuntrustedcertificates) - [RejectSHA1SignedCertificates](#rejectsha1signedcertificates) - [RejectUnknownRevocationStatus](#rejectunknownrevocationstatus) - [MinimumCertificateKeySize](#minimumcertificatekeysize) - [UseValidatedCertificates](#usevalidatedcertificates) - [MaxRejectedCertificates](#maxrejectedcertificates) - [Suppressible Validation Errors](#suppressible-validation-errors) - [CA (issuer) KeyUsage validation](#ca-issuer-keyusage-validation) - [Inspecting Certificate Validation Results](#inspecting-certificate-validation-results) - [Configuring a Custom Certificate Validator](#configuring-a-custom-certificate-validator) - [Best Practices](#best-practices) ## Certificate stores File-based sample applications follow the certificate-store layout recommended in the [specification](https://reference.opcfoundation.org/v104/GDS/docs/F.1/). The root folder, called `pki`, contains a `certs` folder for certificates, a `private` folder for private keys, and a `crl` folder for certificate revocation lists (CRLs). The UA .NET Standard stack supports the following certificate stores: - The **Application** store `/own`which contains private keys used by the application. - The **Issuer** store `/issuer`which contains certificates which are needed for validation, for example to complete the validation of a certificate chain. A certificate in the *Issuer* store is *not* trusted! - The **Trusted** store `/trusted`which contains certificates which are trusted by the application. The certificates in this store can either be self signed, leaf, root CA or sub CA certificates. The most common use case is to add a self signed application certificate to the *Trusted* store to establish trust with that application. If the application certificate is the leaf of a chain, the trust can be established by adding the root CA, a sub CA or the leaf certificate itself to the *Trusted* store. Each of the options enables a different set of trusted certificates. A trusted Root CA or Sub CA certificate acts as the chain's trust anchor. The application trusts a leaf certificate when its chain contains that anchor. The specification still requires validation of the entire chain. For this validation, the *Issuer* store must contain every certificate in the chain except the leaf. If only the leaf certificate is in the *Trusted* store and the rest of the chain is stored in the *Issuer* store, then only the leaf certificate is trusted. As an example, to trust an application certificate that is issued by a Root CA, only the Root CA certificate is required in the *Trusted* store to establish trust to all application certificates issued by the CA. This option can greatly simplify the management of OPC UA Clients and Servers because only one certificate needs to be distributed across all systems. - The **Rejected** store `/rejected` which contains certificates which have been rejected. This store is provided as a convenience for the administrator of an application to allow to copy an untrusted certificate from the *Rejected* to the *Trusted* store to establish trust with that application. - The **Issuer User** store `/issuerUser` which contains user certificates which are used to validate user certificates. - The **Trusted User** store `/trustedUser` which contains user certificates which are trusted by an application. To establish trust, the same rules apply as explained for the *Trusted* and the *Issuer* store. - The **Issuer Https** store `/issuerHttps` which contains https certificates which are used to validate https connection certificates. - The **Trusted Https** store `/trustedHttps` which contains https certificates which are trusted by an application. To establish trust, the same rules apply as explained for the *Trusted* and the *Issuer* store. ## X509Store on Windows On the **Windows OS** the X509Store supports the storage and retrieval of CRLs. This enables the usage of the X509Store instead of the Directory Store for stores requiring the use of crls, e.g. the issuer or the directory Store. ## Certificate and CertificateCollection Types The stack uses the `Certificate` wrapper type (in `Opc.Ua.Security.Certificates`) instead of `X509Certificate2` directly. `Certificate` wraps `X509Certificate2` with reference counting for safe shared ownership — the inner `X509Certificate2` is disposed only when the last reference is released. Use `Certificate.AddRef()` before sharing and `Dispose()` to release. `CertificateCollection` implements `IList` and `IDisposable`. Disposing a collection decrements the reference count on each member. Store methods like `EnumerateAsync()` call `AddRef()` on cached certificates, so disposing the returned collection is safe. For interop with .NET APIs that require `X509Certificate2`, use `certificate.AsX509Certificate2()` which creates a copy that the caller must dispose. See [CertificateManager.md](CertificateManager.md#certificate-wrapper-and-reference-counting) for details. ## Opening a certificate store A `CertificateStoreIdentifier` is only a *description* of a store (`StoreType` / `StorePath`) — the analogue of a `CertificateIdentifier` resolving to a `Certificate`. Every call to `OpenStore` creates and opens a new `ICertificateStore` instance that the caller owns and must dispose: ```csharp var identifier = new CertificateStoreIdentifier(storePath); using ICertificateStore store = identifier.OpenStore(telemetry); CertificateCollection certificates = await store.EnumerateAsync(ct); ``` A component that accesses a store repeatedly should open one instance and keep it for the component's lifetime. Dispose the store at shutdown instead of reopening it for each operation. The store refreshes its parsed-certificate cache when the backing data changes. ## Windows .NET applications By default the self signed certificates are stored in a **X509Store** called **CurrentUser\\UA_MachineDefault**. The certificates can be viewed or deleted with the Windows Certificate Management Console (certmgr.msc). The *trusted*, *issuer* and *rejected* stores remain in a folder called **OPC Foundation\pki** with a root folder which is specified by the `SpecialFolder` variable **%CommonApplicationData%**. On Windows 7/8/8.1/10 this is usually the invisible folder **C:\ProgramData**. ## Windows UWP applications By default the self signed certificates are stored in a **X509Store** called **CurrentUser\\UA_MachineDefault**. The certificates can be viewed or deleted with the Windows Certificate Management Console (certmgr.msc). The *trusted*, *issuer* and *rejected* stores remain in a folder called **OPC Foundation\pki** in the **LocalState** folder of the installed universal windows package. Deleting the application state also deletes the certificate stores. ## .NET Core applications on Windows, Linux, iOS etc The self signed certificates are stored in a folder called **OPC Foundation/pki/own** with a root folder which is specified by the `SpecialFolder` variable **%LocalApplicationData%** or in a **X509Store** called **CurrentUser\\My**, depending on the configuration. For best cross platform support the personal store **CurrentUser\\My** was chosen to support all platforms with the same configuration. Some platforms, like macOS, do not support arbitrary certificate stores. The *trusted*, *issuer* and *rejected* stores remain in a shared folder called **OPC Foundation\pki** with a root folder specified by the `SpecialFolder` variable **%LocalApplicationData%**. Depending on the target platform, this folder maps to a hidden locations under the user home directory. ## Certificate Validation The OPC UA .NET Standard Stack validates certificates according to the OPC UA specification. `CertificateManager` provides centralized certificate management with trust-list-scoped validation, lifecycle monitoring, and pluggable store backends (see [CertificateManager.md](CertificateManager.md)). The legacy validator and adapter are removed. This section explains trust, validation workflow, and configuration; the current API reference lives in [CertificateManager](CertificateManager.md#icertificatevalidatorex). ### Validation Workflow The certificate validation process follows these steps: 1. **Structural Check**: Any certificate in the chain whose **subject or issuer is an empty distinguished name** is rejected immediately with the non-suppressible `Bad_CertificateInvalid`. An empty name is an empty `RDNSequence`: it identifies nothing, and two unrelated issuers become indistinguishable, so such a certificate can never take part in a trust decision. RFC 5280 §4.1.2.4 requires a non-empty issuer, and §4.1.2.6 only permits an empty subject for an end entity carrying a critical `subjectAltName` — which a CA may never do. This check runs before the trust-list lookup and the chain build so the outcome is identical on every target framework, rather than depending on whether the platform's X.509 parser happens to accept the certificate. 2. **Validation Cache**: `UseValidatedCertificates` can skip expensive chain-policy rebuilding for a previously accepted certificate. The issuer walk and revocation checks still run before a cached result is used, so a newly revoked certificate is not accepted solely because it was previously cached. 3. **Trust Check**: The validator checks if the certificate is explicitly trusted by searching in: - The **trusted certificate list** (`TrustedPeerCertificates.TrustedCertificates`) - An in-memory collection of explicitly trusted certificates - The **trusted certificate store** (`TrustedPeerCertificates.StorePath`) - A file system directory or X509Store containing trusted certificates - The **application's own certificate collection** (`ApplicationCertificates`) - The certificates used by the application itself See [Certificate List Configuration](#certificate-list-configuration) for details on how these lists are populated. 4. **Issuer Chain Validation**: For certificates issued by a CA, the validator builds and validates the complete certificate chain. See [Chain Building Process](#chain-building-process) for detailed technical documentation. 5. **Certificate Properties Validation**: The validator checks: - Certificate expiration dates (NotBefore/NotAfter) - Key usage flags (DigitalSignature for ECDSA, DataEncipherment for RSA) - Minimum key size requirements - Signature algorithm strength (e.g., rejecting SHA-1 if configured) - Certificate signature validity 6. **Domain Validation**: If an endpoint is provided, the validator checks that the certificate contains the endpoint's domain name in its Subject Alternative Names. 7. **Application URI Validation**: Verifies that the certificate contains the expected Application URI in the Subject Alternative Name extension. 8. **Error Handling**: If validation errors occur, they are classified as either: - **Suppressible errors**: Can be accepted via `CertificateValidationOptions.AcceptError` or the manager's global `AcceptError` callback - **Non-suppressible errors**: Always cause validation to fail 9. **Rejected Certificate Storage**: Failed certificates are saved to the rejected certificate store for administrator review. Certificates with an empty distinguished name are also skipped when a PEM file is read, so they never reach a trust list in the first place. `PEMReader.ImportPublicKeysFromPEM` drops only the offending entry and returns the remaining certificates, so one malformed certificate cannot empty an otherwise usable trust list. `DistinguishedNameUtils.HasEmptyDistinguishedName` exposes the same check to applications. ### Chain Building Process This section explains the chain-building and validation algorithm used by `CertificateManager`. The implementation is in the internal [`CertificateValidationCore`](../src/Opc.Ua.Core/Security/Certificates/CertificateManager/CertificateValidationCore.cs); applications use `ICertificateValidatorEx`, not that internal class. #### Overview The chain building process constructs a complete certificate chain from a leaf certificate up to a self-signed root CA certificate. The algorithm searches through multiple certificate stores in a specific priority order and performs revocation checking at each step. #### Chain Building Algorithm Diagram ``` ┌─────────────────────────────────────────────────────────────────┐ │ Start Chain Building │ │ Input: Certificate Chain │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ ┌────────────────────────────┐ │ Current = certificates[0] │ │ (Leaf Certificate) │ └────────────┬───────────────┘ │ ▼ ┌────────────────────────────┐ │ Create untrusted list │ │ from certificates[1..n] │ └────────────┬───────────────┘ │ ┌────────────▼───────────────┐ │ Loop: Find Issuer Chain │ └────────────┬───────────────┘ │ ┌────────────▼───────────────────────────┐ │ Is Current certificate self-signed? │ └─┬─────────────────────────────────┬────┘ │ YES │ NO │ │ ▼ ▼ ┌────────────────┐ ┌─────────────────────────────┐ │ Chain Complete │ │ Search for Issuer (Step 1) │ │ Return Result │ │ Location: Trusted Store │ └────────────────┘ └──────────┬──────────────────┘ │ ┌───────────▼──────────────┐ │ Issuer Found in Trusted? │ └─┬─────────────────────┬──┘ │ YES │ NO │ │ ▼ ▼ ┌────────────────────┐ ┌──────────────────────────┐ │ Mark as Trusted │ │ Search for Issuer (Step 2)│ │ Check Revocation │ │ Location: Issuer Store │ └──────┬─────────────┘ └────────┬─────────────────┘ │ │ │ ┌───────────▼──────────────┐ │ │ Issuer Found in Issuer? │ │ └─┬─────────────────────┬──┘ │ │ YES │ NO │ │ │ │ ▼ ▼ │ ┌──────────────────┐ ┌──────────────────────────┐ │ │ Check Revocation │ │ Search for Issuer (Step 3)│ │ └────────┬─────────┘ │ Location: Untrusted Certs │ │ │ └────────┬─────────────────┘ │ │ │ │ │ ┌───────────▼──────────────┐ │ │ │ Issuer Found in Chain? │ │ │ └─┬─────────────────────┬──┘ │ │ │ YES │ NO │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────────┐ ┌──────────────┐ │ │ │ Check Revocation │ │ Chain Broken │ │ │ └────────┬─────────┘ │ Return Result│ │ │ │ └──────────────┘ │ │ │ └─────────────┴───────────┘ │ ┌─────────────▼──────────────┐ │ Check for Circular Chain │ │ (Duplicate Thumbprint) │ └─┬────────────────────────┬─┘ │ DUPLICATE │ UNIQUE │ │ ▼ ▼ ┌────────────────┐ ┌──────────────────────┐ │ Chain Complete │ │ Add Issuer to List │ │ Return Result │ │ Current = Issuer Cert│ └────────────────┘ └──────────┬───────────┘ │ │ ┌───────────────────┘ │ Loop Back to Top └──────────────────────┐ │ ▼ (Continue Building Chain) ``` #### Detailed Algorithm Steps ##### Step 1: Initialization ``` INPUT: - certificates: CertificateCollection (certificate chain from peer) - issuers: List (output list, initially empty) - validationErrors: Dictionary (output) INITIALIZE: - isTrusted ← false - current ← certificates[0] // Leaf certificate - untrustedCollection ← certificates[1..n] // Additional certs from peer ``` ##### Step 2: Iterative Chain Building Loop ``` WHILE (issuer is found) DO: // Exit condition: Self-signed certificate reached IF (IsSelfSigned(current)) THEN BREAK // Chain is complete END IF // Step 2.1: Search in Trusted Certificate Store issuer ← FindIssuer( certificate: current, location: TrustedCertificateList + TrustedCertificateStore, checkRevocation: true ) IF (issuer != null) THEN isTrusted ← true // Chain ends in trusted store revocationStatus ← CheckCRL(issuer, current) validationErrors[current] ← revocationStatus GOTO Step_2.4 END IF // Step 2.2: Search in Issuer Certificate Store issuer ← FindIssuer( certificate: current, location: IssuerCertificateList + IssuerCertificateStore, checkRevocation: true ) IF (issuer != null) THEN revocationStatus ← CheckCRL(issuer, current) validationErrors[current] ← revocationStatus GOTO Step_2.4 END IF // Step 2.3: Search in Untrusted Certificates (from peer) issuer ← FindIssuer( certificate: current, location: untrustedCollection, checkRevocation: true ) IF (issuer == null) THEN BREAK // Chain building failed - no issuer found END IF // Step 2.4: Circular Chain Detection FOR EACH existingIssuer IN issuers DO IF (existingIssuer.Thumbprint == issuer.Thumbprint) THEN BREAK LOOP // Circular chain detected END IF END FOR // Step 2.5: Add Issuer to Chain issuers.Add(issuer) current ← LoadCertificate(issuer) END WHILE RETURN isTrusted ``` ##### Step 3: Issuer Matching Algorithm The `FindIssuer()` function uses the following matching criteria: ``` FUNCTION FindIssuer(certificate, location, checkRevocation): // Extract issuer information from certificate subjectName ← certificate.IssuerName authorityKeyId ← ExtractAuthorityKeyIdentifier(certificate) serialNumber ← ExtractSerialNumber(certificate) // Search all certificates in location FOR EACH candidate IN location DO // Basic matching criteria IF (candidate.SubjectName != subjectName) THEN CONTINUE // Subject name must match issuer name END IF // Check if issuer is allowed (not end-entity cert) IF (NOT IsIssuerAllowed(candidate)) THEN CONTINUE // Must have CA capabilities END IF // Optional: Match by serial number IF (serialNumber != null AND candidate.SerialNumber != serialNumber) THEN CONTINUE END IF // Optional: Match by Authority Key Identifier IF (authorityKeyId != null) THEN subjectKeyId ← candidate.SubjectKeyIdentifier IF (subjectKeyId != authorityKeyId) THEN CONTINUE END IF END IF // Candidate matches - perform revocation check IF (checkRevocation) THEN revocationStatus ← CheckCRL(candidate, certificate) IF (revocationStatus == Revoked) THEN RETURN (candidate, RevocationError) END IF END IF RETURN (candidate, revocationStatus) END FOR RETURN (null, null) // No matching issuer found END FUNCTION ``` ##### Step 4: Certificate Revocation List (CRL) Checking ``` FUNCTION CheckCRL(issuer, certificate): // Only check if store supports CRL operations IF (store.SupportsCRL()) THEN crlStatus ← store.IsRevoked(issuer, certificate) CASE crlStatus OF StatusCodes.Good: RETURN null // Not revoked StatusCodes.BadCertificateRevoked: IF (IsCertificateAuthority(certificate)) THEN RETURN BadCertificateIssuerRevoked ELSE RETURN BadCertificateRevoked END IF StatusCodes.BadCertificateRevocationUnknown: IF (IsCertificateAuthority(certificate)) THEN statusCode ← BadCertificateIssuerRevocationUnknown ELSE statusCode ← BadCertificateRevocationUnknown END IF // Check if error should be suppressed IF (RejectUnknownRevocationStatus AND NOT HasValidationOption(SuppressRevocationStatusUnknown)) THEN RETURN statusCode END IF RETURN null // Suppressed StatusCodes.BadNotSupported: RETURN null // CRL not supported by store END CASE END IF RETURN null END FUNCTION ``` #### X509Chain Validation After building the issuer chain, the validator uses .NET's `X509Chain` to verify cryptographic signatures and certificate properties: ``` // Configure chain policy policy ← new X509ChainPolicy() policy.RevocationMode ← NoCheck // Already checked via CRL policy.RevocationFlag ← EntireChain policy.VerificationFlags ← ConfigureFromValidationOptions(issuers) // Add all found issuers to extra store FOR EACH issuer IN issuers DO policy.ExtraStore.Add(issuer.Certificate) END FOR // Build and validate chain chain ← new X509Chain() chain.ChainPolicy ← policy chain.Build(certificate) // Process chain status FOR EACH chainStatus IN chain.ChainStatus DO ProcessChainStatus(chainStatus) END FOR // Verify chain matches issuers IF (chain.ChainElements.Count != issuers.Count + 1) THEN chainIncomplete ← true END IF // Validate each chain element FOR EACH element IN chain.ChainElements DO ValidateChainElement(element) END FOR ``` #### Chain Validation Results The chain building process returns: 1. **isTrusted**: `true` if any issuer was found in the trusted store, `false` otherwise 2. **issuers**: List of all issuer certificates in the chain (leaf to root, excluding the leaf itself) 3. **validationErrors**: Dictionary mapping certificates to their revocation status errors These results are then used by the main validation logic to determine if the certificate should be accepted or rejected. #### Key Behaviors 1. **Search Priority**: Trusted store → Issuer store → Untrusted collection (from peer) 2. **Trust Anchoring**: If any issuer is found in the trusted store, the entire chain is considered to have a trusted anchor 3. **CRL Checking**: Performed during chain building if the store supports it 4. **Circular Chain Detection**: Prevents infinite loops by checking for duplicate thumbprints 5. **Partial Chains**: If no issuer is found, the chain is marked incomplete but processing continues 6. **Self-Signed Detection**: Chain building stops when a self-signed certificate is encountered ### Certificate List Configuration This section describes how certificate lists and stores are configured and populated in the OPC UA .NET Standard Stack. #### Configuration Sources Certificate lists are populated from two primary sources: 1. **Configuration File** (XML): The `ApplicationConfiguration` file defines certificate store locations and optional explicit certificate lists 2. **Runtime API**: Applications can programmatically add certificates to trust lists using the `SecurityConfiguration` API #### Configuration File Structure The application configuration file (`*.Config.xml`) defines certificate stores in its `SecurityConfiguration` section: ```xml Directory %LocalApplicationData%/OPC Foundation/pki/own CN=MyApplication, O=MyOrganization RsaSha256 Directory %LocalApplicationData%/OPC Foundation/pki/issuer 1234567890ABCDEF... Directory %LocalApplicationData%/OPC Foundation/pki/trusted FEDCBA0987654321... Directory %LocalApplicationData%/OPC Foundation/pki/rejected ``` #### Certificate Store Types Three store types are supported out of the box. Custom store types can be registered using the `ICertificateStoreProvider` interface (see [CertificateManager.md](CertificateManager.md#pluggable-store-backends)). 1. **Directory**: File system-based certificate store - Certificates stored as `.der` or `.crt` files in `certs/` subdirectory - Private keys stored as `.pfx` or `.pem` files in `private/` subdirectory - CRLs stored in `crl/` subdirectory - Example: `%LocalApplicationData%/OPC Foundation/pki/trusted` 2. **X509Store**: Windows certificate store (on Windows platforms) - Uses Windows Certificate Store API - Example: `CurrentUser\My` or `LocalMachine\Root` - Supports CRL operations on Windows only 3. **InMemory**: In-memory certificate store for testing - Prefix: `InMemory:` - No persistence — certificates are lost when the store is disposed On Windows, private-key directories grant inheritable access to the application account, SYSTEM, and Administrators, preserving access to existing keys during upgrades. The .NET 8+ Unix implementation uses owner-only directory/file modes (`0700`/`0600`). A permission-update failure aborts the write unless the path already existed and its current permissions are verified to restrict key access to the trusted Windows identities or the Unix owner. That exception is logged; unverifiable or overly broad permissions are never silently accepted. #### Certificate List Population `CertificateManager` sources its well-known trust lists from the `SecurityConfiguration` in `ApplicationConfiguration`. Hosts may also register named trust lists for other purposes. ##### 1. Initialization via ApplicationConfiguration ```csharp // Direct-construction alternative for an already loaded configuration. using CertificateManager manager = CertificateManagerFactory.Create( config.SecurityConfiguration, telemetry); await manager .LoadApplicationCertificatesAsync(config.SecurityConfiguration, config.ApplicationUri) .ConfigureAwait(false); ``` For a hosted application, use `config.CertificateManager` after application certificate initialization instead of creating another manager. See [initialization ordering](migrate/2.0.x/certificates.md#removed-certificate-apis). ##### 2. Internal Update Process The factory registers the configured trusted and issuer stores. Validation uses the selected trust list's certificate lists and stores; application code does not populate private validator fields or call a legacy `UpdateAsync` method. For changing a live trust list, use [trust-list transactions](CertificateManager.md#working-with-trust-lists). The manager coordinates change notifications and validation-cache invalidation. ##### 3. Certificate Search Behavior During validation, certificates are searched in the following order: **For Trusted Certificates:** 1. Search the configured explicit trusted-certificate list, if populated. 2. Search the trusted store (file system or X509Store). 3. Check the application's own certificates. **For Issuer Certificates:** 1. Search the configured explicit issuer-certificate list, if populated. 2. Search the issuer store (file system or X509Store). #### Runtime Certificate Management Applications can dynamically add certificates to trust lists: ```csharp // Add a trusted peer certificate programmatically byte[] certificateData = File.ReadAllBytes("peer-cert.der"); config.SecurityConfiguration.AddTrustedPeer(certificateData); // Or use the CertificateManager transactional update API: await using ITrustListTransaction tx = await manager .BeginUpdateAsync(TrustListIdentifier.Peers) .ConfigureAwait(false); using Certificate trusted = Certificate.FromRawData(certificateData); await tx.AddTrustedCertificateAsync(trusted).ConfigureAwait(false); await tx.CommitAsync().ConfigureAwait(false); ``` #### Certificate Store Management Certificates in file system stores are managed as follows: 1. **Adding Certificates**: Copy certificate files to the `certs/` subdirectory of the store path - Format: `[Thumbprint].der` or `[Thumbprint].crt` - Example: `%LocalApplicationData%/OPC Foundation/pki/trusted/certs/1234567890ABCDEF.der` 2. **Adding CRLs**: Copy CRL files to the `crl/` subdirectory - Format: `[IssuerThumbprint].crl` - Example: `%LocalApplicationData%/OPC Foundation/pki/issuer/crl/FEDCBA0987654321.crl` 3. **Rejected Certificates**: Automatically added by the validator when validation fails - Stored in the rejected certificate store - Can be manually moved to trusted store to establish trust #### Dual-Mode Operation The validator supports both explicit lists and certificate stores: - **Explicit List Only**: Specify certificates in `` without a store path - **Store Only**: Specify store path without explicit certificates (most common) - **Combined Mode**: Use both explicit list and store for maximum flexibility - Explicit list is searched first for performance - Store is searched if not found in list #### Configuration Best Practices 1. **Store Path**: Use environment variables for platform independence: - `%LocalApplicationData%` - Per-user application data - `%CommonApplicationData%` - Machine-wide application data - Relative paths - Relative to application directory 2. **Explicit Lists**: Use for: - Small, fixed set of trusted certificates - Performance optimization (faster than store enumeration) - Pre-deployment certificate distribution 3. **Certificate Stores**: Use for: - Dynamic trust management - Administrator-managed certificate stores - Integration with OS certificate infrastructure 4. **Separation**: Keep different certificate types in separate stores: - Application certificates: `pki/own` - Trusted peers: `pki/trusted` - Trusted CAs: `pki/issuer` - Rejected: `pki/rejected` ### Configuration Settings The certificate validation behavior is controlled by several configuration settings in the `SecurityConfiguration` class: #### AutoAcceptUntrustedCertificates - **Type**: `bool` - **Default**: `false` - **Description**: When `true`, automatically accepts certificates that have the `BadCertificateUntrusted` status. This is useful for development environments but should not be used in production. - **Example**: ```csharp configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates = true; ``` #### RejectSHA1SignedCertificates - **Type**: `bool` - **Default**: `true` (when default hash size >= 256) - **Description**: When `true`, rejects certificates signed with SHA-1 algorithms as they are considered cryptographically weak. - **Example**: ```csharp configuration.SecurityConfiguration.RejectSHA1SignedCertificates = true; ``` #### RejectUnknownRevocationStatus - **Type**: `bool` - **Default**: `false` - **Description**: When `true`, rejects certificates when the revocation status cannot be determined (e.g., CRL is not available). - **Example**: ```csharp configuration.SecurityConfiguration.RejectUnknownRevocationStatus = true; ``` #### MinimumCertificateKeySize - **Type**: `ushort` - **Default**: `2048` (CertificateFactory.DefaultKeySize) - **Description**: The minimum RSA key size in bits that will be accepted. Common values are 2048, 3072, or 4096. - **Example**: ```csharp configuration.SecurityConfiguration.MinimumCertificateKeySize = 2048; ``` #### UseValidatedCertificates - **Type**: `bool` - **Default**: `false` - **Description**: When `true`, skips validation for certificates that have already been successfully validated in the current session. This improves performance by caching validation results. - **Example**: ```csharp configuration.SecurityConfiguration.UseValidatedCertificates = true; ``` #### MaxRejectedCertificates - **Type**: `int` - **Default**: `5` - **Description**: Limits the number of rejected certificates kept in history. A value of 0 means all rejected certificates are kept. A negative value means no history is kept. - **Example**: ```csharp configuration.SecurityConfiguration.MaxRejectedCertificates = 10; ``` ### Suppressible Validation Errors The following validation errors can be suppressed by handling the `CertificateValidation` event and setting `e.Accept = true`: - **BadCertificateUntrusted**: The certificate is not trusted (not in the trusted store or chain). - **BadCertificateHostNameInvalid**: The domain name in the endpoint URL does not match any domain in the certificate. - **BadCertificateIssuerRevocationUnknown**: The revocation status of the issuer cannot be determined. - **BadCertificateIssuerTimeInvalid**: The issuer certificate has expired or is not yet valid. - **BadCertificateIssuerUseNotAllowed**: An issuer/CA certificate in the chain is not valid for use as a CA — for example it does not assert the `keyCertSign` and `cRLSign` KeyUsage bits required for a CA (see [CA (issuer) KeyUsage validation](#ca-issuer-keyusage-validation)). - **BadCertificateRevocationUnknown**: The revocation status of the certificate cannot be determined. - **BadCertificateTimeInvalid**: The certificate has expired or is not yet valid. - **BadCertificatePolicyCheckFailed**: The certificate does not meet policy requirements (e.g., key size, signature algorithm). - **BadCertificateUseNotAllowed**: The certificate is not valid for the intended use (missing key usage flags). All other validation errors are **non-suppressible** and will always cause the validation to fail. This includes **BadCertificateChainIncomplete** (an issuer certificate is missing from the trusted and issuer stores): OPC 10000-4 Table 100 does not allow chain-building errors to be suppressed, so install the issuing CA instead of accepting the error. #### CA (issuer) KeyUsage validation OPC UA requires every CA (issuer) certificate in a chain to carry a KeyUsage extension with both `keyCertSign` and `cRLSign` set (OPC 10000-6 §6.2.4, Table 52 — Issuer Certificate). During chain validation, the stack checks each issuer certificate. If a CA certificate lacks either bit or has no KeyUsage extension, the stack reports the suppressible `BadCertificateIssuerUseNotAllowed` error (OPC 10000-4 §6.1.3, Table 100, "Certificate Usage"). Strict third-party OPC UA stacks also reject such CA certificates, typically with `BadCertificateInvalid`. This stack's `CertificateBuilder` includes the required bits in every CA certificate it creates. To interoperate with a legacy CA that omits them, suppress the error as described above. ### Inspecting Certificate Validation Results The new `ICertificateValidatorEx` (composed in `ICertificateManager`) returns a structured `CertificateValidationResult` describing the outcome of each validation. Callers can examine the result to decide whether to accept errors: ```csharp CertificateValidationResult result = await manager .ValidateAsync(certificate, TrustListIdentifier.Peers) .ConfigureAwait(false); if (!result.IsValid) { Console.WriteLine($"Validation failed: {result.StatusCode}"); foreach (ServiceResult error in result.Errors) { Console.WriteLine($" • {error}"); } // Accept specific suppressible errors (e.g. untrusted certificates // in development) by inspecting the StatusCode. bool autoAccept = false; if (result.IsSuppressible && result.StatusCode == StatusCodes.BadCertificateUntrusted && autoAccept) { // Application-specific accept logic. } else { throw new ServiceResultException(result.StatusCode); } } ``` Per-call validation behavior (e.g. auto-accepting untrusted certificates, or relaxing revocation checks) can also be overridden via `CertificateValidationOptions`: ```csharp var options = new CertificateValidationOptions { AutoAcceptUntrustedCertificates = true, RejectUnknownRevocationStatus = false }; CertificateValidationResult result = await manager .ValidateAsync(certificate, TrustListIdentifier.Peers, options) .ConfigureAwait(false); ``` Subscribe to `manager.CertificateChanges` (an `IObservable`) for lifecycle notifications such as `ApplicationCertificateUpdated`, `TrustListUpdated`, `CrlUpdated`, `CertificateRejected`, and `CertificateExpiring`. See [CertificateManager.md](CertificateManager.md) for the full reference. The legacy `CertificateValidator.CertificateValidation` event and adapter are removed. See [certificate migration](migrate/2.0.x/certificates.md#migrating-the-certificatevalidatorcertificatevalidation-event) for the old-to-new mapping. ### Configuring a Custom Certificate Validator Use `ApplicationConfiguration.CertificateManager` for the application's normal validation path. It combines trust-list management, certificate validation, and certificate lifecycle notifications. Keep those checks in place when adding application-specific requirements; an always-successful validator bypasses trust, validity, and revocation checks. The current validation contract is `ICertificateValidatorEx`. Its `CertificateValidationOptions.AcceptError` callback can accept individual suppressible errors; accepting untrusted certificates is appropriate only for controlled development environments. For the interface, initialization order, callback precedence, and examples, see [CertificateManager validation](CertificateManager.md#icertificatevalidatorex). There is no `CertificateValidatorAdapter` or legacy `ApplicationConfiguration.CertificateValidator` property in the current API. ### Best Practices 1. **Production vs Development**: Never use `AutoAcceptUntrustedCertificates = true` in production environments. 2. **Certificate Store Management**: Regularly review rejected certificates in the rejected store and move trusted certificates to the appropriate trust store. 3. **Revocation Checking**: Enable `RejectUnknownRevocationStatus` for high-security environments where CRL checking is critical. 4. **Minimum Key Size**: Use at least 2048 bits for RSA keys. Consider 3072 or 4096 bits for long-term security. 5. **SHA-1 Deprecation**: Keep `RejectSHA1SignedCertificates = true` to ensure only certificates with strong signature algorithms are accepted. 6. **Validation Callback**: Always log certificate validation events for security auditing purposes. 7. **Custom Validators**: When implementing a custom validator, ensure it complies with OPC UA security requirements and thoroughly test edge cases.