## Proposed changes
Three spec alignment fixes for the 1.5.378 line.
- **Session AuthenticationToken** (`SessionManager`): CreateSession now
always assigns a random 32 byte opaque AuthenticationToken. Previously a
numeric, incrementing token was used for sessions created on a secured
endpoint. OPC 10000-6 7.4.1 requires a random token of at least 32 bytes
for HTTPS, and a single format keeps all transports consistent.
- **OpenSecureChannel response policy** (`UaSCBinaryClientChannel`,
`UaSCBinaryChannel.Asymmetric`): a client channel opened with a
SecurityPolicy other than None now requires the OpenSecureChannel
response to use the same SecurityPolicy and SecurityMode and to carry
the server certificate. Previously the response could switch the channel
to SecurityPolicy None (OPC 10000-4 5.6.2.1, OPC 10000-6 6.7.2.3).
Channels configured for None are unchanged.
- **Identity token decryption errors** (`Session`, `RsaUtils`): every
failure while decrypting a user identity token is now reported as
`Bad_IdentityTokenInvalid` without an inner result, as OPC 10000-4
7.40.2.1 specifies; the cause is logged on the server instead. An
invalid length prefix in RSA-decrypted data now fails the same way as
other decryption errors instead of `Bad_EndOfStream`.
## Behaviour changes
- AuthenticationTokens on secured endpoints are ByteString (opaque)
NodeIds instead of numeric NodeIds.
- ActivateSession with an undecryptable identity token returns
`Bad_IdentityTokenInvalid` in all cases, also when the client requests
inner diagnostics.
## Tests
- `ClientOpenSecureChannelDowngradeTests` (Core): secured client rejects
a None OpenSecureChannel response; None client still accepts it.
- `RsaUtilsDecryptTests` (Core): round trip; invalid length prefixes
fail with the same exception type as invalid padding.
- `SessionSecurityRegressionTests` (Server): opaque 32 byte tokens on a
shared secured channel id; uniform ActivateSession status for identity
token decryption failures.
Local results (net10.0): Core 7938 passed / 0 failed, Server 511 passed
/ 0 failed, Client 1063 passed / 3 failed. The three Client failures
(`TransferSubscriptionNativeAsync` x2, `ShortHaulStabilityTestAsync`)
pass when run in isolation on both this branch and unmodified master378.
The new tests also pass on net48 (Release).
## Types of changes
- [x] Bugfix (non-breaking change which fixes an issue)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary
**Backport of the #3944 fix to the `master378` (1.5.378) maintenance
branch.** See the 2.0 PR #3947 for the primary change; this PR applies
the equivalent fix to the 1.5.378 code.
The stack did not verify that **CA/issuer certificates** in a chain
assert the KeyUsage required for a CA. Per **OPC 10000-6 §6.2.4, Table
52**, a CA certificate must carry a KeyUsage extension asserting
`keyCertSign` and `cRLSign`, and **OPC 10000-4 §6.1.3, Table 100
("Certificate Usage")** requires validators to check issuer usage and
report `Bad_CertificateIssuerUseNotAllowed`.
The validator only checked the **leaf** certificate's KeyUsage and
delegated issuer/CA usage to .NET `X509Chain`, which (RFC 5280 §4.2.1.3)
does **not** reject a CA whose KeyUsage extension is *absent* — so a CA
with no KeyUsage was accepted while strict third-party stacks reject the
chain with `BadCertificateInvalid`.
## Changes
- `X509Utils.HasRequiredIssuerKeyUsage(X509Certificate2)` — returns
whether both `keyCertSign` and `cRLSign` are asserted (absent KeyUsage →
`None` → fails).
- `CertificateValidator.InternalValidateAsync` — after the leaf KeyUsage
check, verifies every issuer/CA certificate in the chain and appends a
**suppressible** `Bad_CertificateIssuerUseNotAllowed` when the required
bits are missing.
- **Two coupled defects fixed in `CheckChainStatus`**:
1. A `FormatException` from a named-placeholder format string
(`{Status}: {Information}` → positional `{0}: {1}`) that crashed
validation on any `NotValidForUsage` chain status.
2. An inverted `isIssuer` status-code ternary (issuer↔leaf codes
swapped, contrary to Part 4 Table 100).
- `Docs/Certificates.md` — new "CA (issuer) KeyUsage validation"
subsection.
Every new check/helper carries an in-code citation to the governing spec
sections.
## Testing
- New tests in `CertificateValidatorTest.cs` (matching the branch's
existing style: `X509Certificate2`, classic asserts, throwing contract,
event-based approver): a deterministic helper unit test plus reject /
suppress / accept integration tests.
- Full `CertificateValidator` fixture green on **net8.0** and **net48**,
no regressions.
## Compatibility
- This is long-standing behaviour on both `master` and `master378` — not
a 2.0 regression. No public API change; the new error is suppressible.
Relates to #3944 · 2.0 PR: #3947
# Description
Fixes#3896 on the `1.5.x` maintenance line. A server configured with
`SecurityConfiguration.SendCertificateChain = true`, whose application
instance certificate is issued by a CA placed in the **issuer store**
(`TrustedIssuerCertificates`), sent only the **leaf** certificate
instead of the full chain. Clients that rely on receiving the chain then
failed to connect.
## Root cause
Introduced in `1.5.375` by commit `f001eabcc` ("Add support for ECC
profiles", #2398), which moved chain loading into
`CertificateTypesProvider.LoadCertificateChainAsync`:
```csharp
if (await m_certificateValidator.GetIssuersAsync(certificate, issuers).ConfigureAwait(false))
{
for (int i = 0; i < issuers.Count; i++)
{
certificateChain.Add(issuers[i].Certificate);
}
}
```
`GetIssuersAsync` returns `isTrusted` — `true` only when the issuer is
found in the **trusted** store — but populates the `issuers` out-list
regardless (trusted → issuer → untrusted). When the CA lives in the
**issuer store** (the spec-recommended setup), the call returns `false`,
so the already-resolved issuers were discarded and the chain stayed
leaf-only.
This is a regression from `1.5.374`, where the issuers were appended
**unconditionally**.
## Fix
Append the resolved issuers unconditionally; the boolean trust result is
intentionally ignored — a server must send its chain even when the
issuing CA is in the issuer store rather than the trusted store.
```csharp
_ = await m_certificateValidator.GetIssuersAsync(certificate, issuers).ConfigureAwait(false);
for (int i = 0; i < issuers.Count; i++)
{
certificateChain.Add(issuers[i].Certificate);
}
```
The companion fix for the 2.0 line (`master`) is in a separate PR.
## Tests
New `CertificateTypesProviderTests`:
- `LoadCertificateChainResolvesIssuerFromIssuerStoreAsync`: the CA is
placed in the issuer store only — **not** the trusted store — and the
resolved chain is asserted to be `leaf + CA` (and the raw blob `leaf ||
CA` byte-for-byte). Confirmed this test **fails** with the original gate
and **passes** with the fix.
- `LoadCertificateChainFallsBackToLeafWhenIssuerMissingAsync`: when the
CA is in neither store, the chain is leaf-only without throwing.
Verified locally on **net48** and **net10.0** (new tests + existing
`VerifyIssuerChain*` validator tests pass). Build is clean.
## Related Issues
Fixes#3896.
## Checklist
- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation (no public API change;
behavioural fix only).
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. (Ran the
certificate suites on net48 + net10.0; full-solution run pending CI.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
Co-authored-by: agent <agent@local>
This pull request modernizes the OPC UA .NET codebase and tests, removes .NET Standard 2.0 support from certificate/security code, and updates test code to use C# 12 and .NET 8+ features for clarity and maintainability.
- Certificate and BouncyCastle interop files (e.g., CertificateBuilder.cs, X509Extensions.cs, X509SignatureFactory.cs) are refactored to drop NETSTANDARD2_0 support and use modern .NET APIs and conditional compilation.
- Test code across the codebase is updated to use default, strongly-typed Parse methods, collection expressions, and improved async/await patterns.
- Exception handling and assertions in tests are modernized, and test method naming is made consistent with Async suffixes.
- The project and build files are updated to clarify Roslynator analyzer usage and remove obsolete netstandard tags.
- General code and test cleanup includes improved null safety, type safety, and removal of deprecated or unused code.
* Normalize distinguished name field abbreviations for platform compatibility of attribute State/ProvinceName
* Fix warning ca1845
* netstandard2.1 doesn’t support string.Concat with spans.
This change adds a **new Opc.Ua.Types** nuget that contains core types that do not rely on generated code. Why the name? Because almost all code that was in the Types subfolder is part of it, and because we do not want to change the name of the Opc.Ua.Core nuget/assembly because code relies on it. Any other name like base or bootstrap felt out of place due to the current structure already defining the name "types". Key design is to depend on only a small set of libraries with limited transitive dependencies, that includes hashcode, logging.abstraction, and for non standard20 builds System.Text.Json (needed for localizedText mul parsing). Also, key is to support .netstd 2.0 to support source generators and to allow removal of EEC_SUPPORT defines by removing std2.0 from core and all libraries.
The changes are mostly restructuring, and thus small but contain some key changes:
- **Status code** struct now comes with a Symbol field, which is used to carry symbols in case the code is not generated. Currently (until generated) the status code struct must be manually created with a symbol or the ToSymbolicId extension must be called in the core project. Future will generate Status code constants just like NodeId that contain the symbol which also removes the reflection-based lookup. Several tests needed to be adjusted as they checked the status code symbol strings instead of the code.
- **Core identifiers** are copied as they were needed for code to compile, that includes attributes, a subset of status codes and node identifiers. These will never change so no need to generate them.
- The types folder now contains **encoders**, except for json which is still in core. Once the json encoder is ported to system.text.json it will also be included and Newtonsoft.Json dropped as dependency (-> #3329).
- Since types relies on **IOperationContext** and **ISystemContext**, and these export IUserIdentity which relies on crypto, these were split and extended in core with ISessionOperationContext and ISessionSystemContext, which mean changes to ServerSystemContext and introduction of SessionOperationContext and SessionSystemContext classes that extend OperationContext and SystemContext. Some runtime checks in the server are therefore needed to get at the session and auth info, however, those are anyway optional fields and are not on the critical paths, and thus a type check is in order (and was done in the past already, but against OperationContext class).
- **Manifest resources** are now split between Core (nodeset zip, some generated resoures) and Types (built in bsd/xsds). This needs to be revisited later if the bsd/xsd need to be regenerated during type generation. Note that the embedded resource names changed as a result. Since these are only used internally and do not constitute public API this feels reasonable vs duplication of resources in 2 assemblies.
- Reflection however now needs to load not just the built in **encodeables** from Opc.Ua.Types assembly, but also the generated encodeables from Opc.Ua.Core assembly. For this a new helper Utils method was added to get the Core assembly when linked against Opc.Ua.Core otherwise null.
- Whatever utils methods needed to be split out into the new Opc.Ua.Types assembly, they are now in **CoreUtils** static class in Opc.Ua.Types, and Utils.xxxx forwards to them to ensure minimal changes. Later we would want to obsolete them in favor of CoreUtils, although it might make sense to split them again into domain specific utilities, such as networking, files, etc
- Remove ConnectAsync from lock. Useable means m_channel is set, not useable means it is null.
- Add comments on lock behavior during connection.
- Remove callback log from validator
- Change TCS creation to specify run continuations async
- Fix encoder factory reflecting every time it is created.
Refactored client-side logic to support batching of service requests, ensuring compliance with operation limits and improving scalability. Added telemetry and activity tracing using `ActivityTraceFlags` for better observability, including metrics collection and logging. Enhanced error handling with descriptive exception messages and improved certificate validation.
Introduced a comprehensive suite of unit tests for `SessionClientBatchTests.cs` and `ClientBaseTests.cs`, covering batching, error scenarios, and edge cases. Added helper classes for testing logging, metrics, and activity tracing. Updated tests to align with modern C# practices, including nullable reference types and collection initializers.
Improved code consistency, readability, and maintainability by standardizing method signatures, simplifying redundant patterns, and removing legacy constructs. Updated XML documentation and added conditional compilation for .NET 8.0+ compatibility.
* Added RejectCertificateUriMismatch property and explicit ApplicationUri validation after certificates are loaded
* Perform duplicate detection on thumbprint only
* Ported handle multiple ApplictionUri, removed assign ApplictionUri from certificate
* Missing letter
* Fixed LoadPrivateKeyAsync when no applicationUri is passed.
* Add tests for validating certificates with multiple AplicationUris on ApplicationInstance level and server side
* Renamed into CheckOrCreateCertificateAsync, logged Certificate, improved tests
* Added retry logic for transient connect errors in CreateSessionApplicationUriValidationTests
* Added TODO log when telemetry instance available
* Removed unused ValidateServerCertificateApplicationUri(X509Certificate2 serverCertificate)
* Refactor for robustness and async improvements
Refactored code to enhance robustness and maintainability by enabling nullable reference types and using `async`/`await` for asynchronous operations. Updated exception handling to use `BadNotConnected()` for more accurate error states. Removed unused code and improved test files to align with these changes. Introduced `OpenInternalAsync` for better channel management and handled on-demand connection creation in `SendRequestAsync`.
* Obsolete APM
* Remove WCF remainders, channel clean up and initial cut at Channel factory
* Clean up copyright notice formatting
* Make connect async in Socket and abstract bindings for easier testing
* Fix unit tests
These changes enhance robustness, maintainability, and correctness of channel and task management.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roman Ettlinger <romanett98@gmail.com>
* Replace fuzzy search with SimpleName search if comparing DistinguishedName fails.
* Added mandatory Organization Name attribute to SetSuitableDefaults().
Added Test for the special case.
* Added missing O attribute to CompareDistinguishedName call
* Update test
* CertificateIdentifier.Find will pick the certificate with the longest duration
* Find only valid certificates.
* Prioritizes CA-signed certificates over self-signed certificates
* Update Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Select longest remaining validity if valid, or least expired if all expired
* Renamings
* Improved comments, improved algo clarity and taken into account more possible cases of prioritization depending on cert validity
* Comment corrected
---------
Co-authored-by: romanett <romanett98@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Allow setting ReturnDiagnostics on the session factory to ensure the diagnostic settings are part of "Create" also. Set them to SymbolicIdAndText by default.
- Better logging in unit tests, now also logs to progress including exception traces, also prefixes SERVER and TEST on console to attach the logs to the server or client side.
- GDS client stability improvements to ensure client reliably reconnects between calls when connection is lost.
- Do not dispose the application certificate when it is updated from GDS client to avoid existing sessions to crash with CryptographicException. This needs more improvements later on because it is rather hacky today.
- Remove the certificate cache in CertificateFactory (as per discussion and agreement) which allowed re-enabling the older UserIdentity API
- Logging improvements here and there to analyze issues in unit tests better
* Refactor string usage for secrets (#394)
Strings are immutable and the lifetime of secrets can not be controlled.
Use byte[] or char[] instead of strings for secrets, which can be erased
after use.
* Clear pw
---------
Co-authored-by: Martin Regen <7962757+mregen@users.noreply.github.com>
* Implement search of certificates in PEM files on net 6 in directory store
* Implement using Custom Certificate Store
* Cleanup
* Implement Support for PEM Public Keys in Directory Store
* Throw exception if test file is not found
* Skip Tests on Mac OS & Net < 8.0
* Add delay for slow CI Agents
* Add more delay
* Make test file with explicit message
* Address review Feedback
* fix test
* Throw ex to show possible error in test
* next try
* Only throw on full load
* Manually set LastWriteTimeUtc
* make private key encrypted, use two separate tests instead of one
---------
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
* Make UserIdentity Token internally use byte Array instead of password.
* Add missing null check
* fix uninitialized array
* use Encoding.UTF8 instead of new Utf8Encoding()
* zero out array before setting the password
* Add missing constants for JSON encoding.
* Update generated files to 1.05.04.
* Update NuGets
* Fix compile errors due to abstract types.
* Update nodesets.
* Update JSON Encoder/Decoder.
* Update with JSON encoder.
* Change prefix char.
* Change prefix to uppercase.
* Fix JsonEncoder
* Fix null pointer.
* Update encoders to suppress artifacts.
* JSON encoding fixes.
* Fix JSON RawEncoding
* Update JSON encoders.
* Revert changes for Matrix in Variant.
* Update NodeSet.
* Resolve name conflict with new Opc.Ua types.
* Fix failing Tests
* Fix unit tests after change to Verbose/Compact encoding.
* Add final 1.05.04 generated classes and unit test fixes.
* Fix more unit tests.
* Update NodeSet to released version.
* Remove unintended changes to UANodeSet.xsd
* Update Schema/UANodeSet.xsd
* Merge breaking changes to JsonEncoder.cs from develop/main374
* Allow JObject in ExtensionObjects.
* fix version json
* Update generated files to UA-1.05.04-2025-01-08
* Some cleanup of the new APIs and Union implementation. (#2927)
* Some cleanup of the new APIs and Union implementation.
* fix build
* Handle status code Uncertain according to the specification (#2898)
* #2896: Write output arguments for good and uncertain status code
When a method state's call method is invoked the output arguments should be written in case the status code is good or uncertain. This behavior would be conform with the current specification.
* #2896: The service result corresponds the method call result
The result of the Call method in the CustomNodeManager2 class represents the status of the CallMethodResult. It does not correspond to the ServiceResult of the CallResponse, thus returning Good as a general response is incorrect behavior.
* Add SetHiResClockDisabled & fix Rejected Store Creation in ApplicationConfigurationBuilder (#2909)
Add the method SetHiResClockDisabled to IApplicationConfigurationBuilder
Fix the type of RejectedStore Created by the Builder to: CertificateStoreIdentifier
* revert based on feedback
* move some logic from class code to encoder/decoder
* Fix race condition on Nuget build (#2930)
- nodeset xmlzip is created in obj folders if multiple targets are built in parallel
* update common comments
* Update NodeSet files.
* Fix ReadSwitchField Implementation.
* Make UserIdentity.DisplayName writeable.
* Fix merge failures.
* Fix XmlElement encoding and bad NamespaceIndex fallback logic.
* Fix 2D array raw encoding.
* Add support for DesignToolOnly.
---------
Co-authored-by: Randy Armstrong <randy@sparhawksoftware.com>
Co-authored-by: Martin Regen <mregen@microsoft.com>
Co-authored-by: Martin Regen <7962757+mregen@users.noreply.github.com>
* enable nrts for CertificatIdentifier&CertificateStoreIdentifier&CertificateTrustList
* make RSA Utils and Certificate Stores nullable
* Make Certificate validator nullable
* fix nullability of CertificateTypeParameter
* Generate ECC Certificates in Client Tests
* Add more tests
* reenable check of certificate
* fix tests
* Dont use random in test
* fix unsupported certificate types
* fix Hash algorithm for nistP384 curve on linux as the friendly name is ECDSA_P384 instead of nistP384
* use friendly name by default and add special case only for linux while taking care of nullability
* disable special casing for finding correct certificates
* fix tests on net 462
* update Certificate Identifier
* update ServerConfiguration Node to support multiple Application Certificates. Trust List Support for User Certificates & Https Certificates
* GDS ready for ECC Step1
* fix test
* fix build
* ensure hash algorithm is set
* update configurations to new format, fix minor ecc bugs
* implicitly set certificate type of certificate identifier
extend CertificateFactory to allow Revocation with ECC Issuer Certificate
* Revert "update configurations to new format, fix minor ecc bugs"
This reverts commit 9ee17f3aff.
* Allow GDS Certificate Group to have multiple CA Certificates with different Certificate Types
* fix minor bugs
* adress review feedback
* fix some issues
* fix creation of CA signed ECC Certificates
* Update configurations to new ApplicationCertificateFormat and add extra CA Certificates for ECC Certificates
* remove brainpool certs from configuration
* Generate ECC Certificates in Client Tests
* Revert "Generate ECC Certificates in Client Tests"
This reverts commit 90b395f4ff.
* fix crl tests on platforms not supporting ECC
* fix crl test
* Update READMEs for ECC
* Add ECDSA support for reading private key from PEM
* Padded x, y EC point coordinates and the d private key with leading zeros to match keySize
* CreateCertificateWithPEMPrivateKey creates either ECDSA or RSA certificate depending on certificate type
* implicitly set certificate type of certificate identifier
extend CertificateFactory to allow Revocation with ECC Issuer Certificate
* Add Crl tests for ECC
* fix X509 PFXUtils VerifyECDsaKeyPair