mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 22:55:08 +08:00
7fe7794a19
> Follow-up to #4703 (now merged to master; this PR has been retargeted
to `master`).
Part of #4510. #4703 enabled the CBC ECC policies on net48 through a
BouncyCastle raw-ECDH polyfill. The AEAD policies were still unavailable
there because .NET Framework has no `AesGcm` or `ChaCha20Poly1305`. This
PR adds those ciphers the same way, so on net48 all of these are now
offered:
`ECC_{nistP256,nistP384,brainpoolP256r1,brainpoolP384r1}_AesGcm` /
`_ChaChaPoly`, `RSA_DH_AesGcm`, `RSA_DH_ChaChaPoly`.
## Changes
- **Polyfill**
`src/Opc.Ua.Security.Certificates/Org.BouncyCastle/AeadCiphers.cs`
(internal, `#if NETFRAMEWORK`, next to `EcdhRawAgreement`): `AesGcm` and
`ChaCha20Poly1305` types with the .NET 8 BCL surface. They wrap
BouncyCastle's `GcmBlockCipher(AesEngine)` and
`Org.BouncyCastle.Crypto.Modes.ChaCha20Poly1305`.
- A tag mismatch throws `CryptographicException`, never BC's
`InvalidCipherTextException`, so
`PlatformSymmetricCryptoProvider.DecryptAuthenticated` still returns
`false` and the channel still raises a protocol error.
- GCM releases plaintext blocks before it checks the tag, so plaintext
stays in a scratch buffer and is copied out only after the tag verifies.
On failure the destination is cleared, as the BCL does. Scratch buffers
and the key copy are zeroed.
- **Opc.Ua.Core**: aliases the polyfill types on `NETFRAMEWORK` and
removes the `NET8_0_OR_GREATER` gates in `SecurityPolicies`
(`SupportsAesGcmPolicy` / `SupportsChaCha20Poly1305Policy`),
`CryptoUtils` (`SymmetricEncryptAndSign` / `SymmetricDecryptAndVerify`,
AEAD helpers) and `PlatformSymmetricCryptoProvider`. The channel, the
provider facet and the encrypted ECC user tokens (`EncryptedSecret`,
through `CryptoUtils`) now run the same code on every target. .NET 8+
keeps the BCL ciphers.
- **FIPS** (requested by the #4703 session, after a2ecf13b1): under
`CryptoCompliancePolicy.FipsOnly`, `CryptoCompliance.IsPolicyPermitted`
now also withholds every AEAD policy on .NET Framework. That adds
`RSA_DH_AesGcm`, which is FIPS-approved and not ECC. The BouncyCastle
cipher runs outside any validated module and the user-token path
bypasses the provider registry, so the auditor cannot see it. Tests and
`docs/CryptoProvider.md` are updated to match.
- **Docs**: EccProfiles (Known Limitations), migration `certificates.md`
(and the bundled skill copy), ProductionChecklist, Profiles,
WhatsNewIn2.0, CryptoProvider. They state that on net48 these primitives
run in managed BouncyCastle, which is not a validated (FIPS) module and
not hardware-accelerated or hardened against cache-timing attacks like
CNG AES.
## Tests
- New `AeadPolyfillTests` (Core.Tests):
- Known answers: NIST GCM test cases 2/4 (AES-128) and 14/16 (AES-256),
plus RFC 8439 §2.8.2 for ChaCha20-Poly1305. They run through
`PlatformSymmetricCryptoProvider` on every TFM and through the polyfill
directly.
- Tampering with the tag, the ciphertext or the AAD gives `false` from
the provider, and `CryptographicException` with a cleared plaintext from
the polyfill.
- Argument validation.
- **BCL cross-check:** on .NET 8+, Core.Tests also compiles
`AeadCiphers.cs` (`AEAD_POLYFILL_CROSSCHECK`, test-only
`BouncyCastle.Cryptography` reference). Polyfill and BCL must produce
identical ciphertext and tag, and each must decrypt the other's output:
3 algorithms × 8 message lengths (0 to 8192) × 3 AAD lengths.
- `CryptoProviderRegistryTests`: `RSA_DH_AesGcm` is permitted under
FipsOnly only off .NET Framework and withheld on it.
- Tests that `Assert.Ignore` when AEAD is unavailable now run on net48,
e.g. `EccSecretRejectsDifferentNegotiatedPolicyAsync` and the
`RSA_DH_AesGcm` server session tests.
### Local results
| Run | Result |
|---|---|
| Core.Tests net48, full (before the FIPS commit) | 6049 passed, 0
failed, 88 skipped |
| Core.Tests crypto/AEAD/ECC/EncryptedSecret/Nonce subset, net48 /
net10.0 | 278 / 343 passed, 0 skipped |
| Server.Tests session-security/ECC subset, net48 | 128 passed, 0
skipped |
| `EccInteropTests` net48 vs **open62541 1.5.8 (OpenSSL)** peer | all 16
AEAD cases pass, 4 of them two-renewal token-renewal runs
(`ECC_nistP256_AesGcm`/`_ChaChaPoly` × Sign/SignAndEncrypt ×
Anonymous/UserName, both directions). Remaining 8 failures are the
documented open62541 CBC Sign-only HKDF differences in
`expected-differences.json` |
| `EccInteropTests` net48 vs 1.5.378 LegacyPeer | unchanged 26 passed /
28 skipped |
Note on the acceptance criterion: the 1.5.378 LegacyPeer does not
implement the AEAD policies, so its
`_AesGcm`/`_ChaChaPoly`/`*AeadTokenRenewalAsync` cases skip on every TFM
("The peer (UA-.NETStandard) does not declare …"). Before this PR they
skipped on the 2.0 side; now they get past it and stop at the peer
check. The open62541 peer is the real cross-stack check of the net48
AEAD path against a non-BouncyCastle implementation.
## PubSub DTLS on net48
The DTLS 1.3 transport (`src/Opc.Ua.PubSub.Udp/Dtls`) was compiled out
on .NET Framework, so net48 registered no DTLS profiles. It now reuses
the same polyfills (`Opc.Ua.Security.Certificates` gains
`InternalsVisibleTo Opc.Ua.PubSub.Udp`):
- The `NET8_0_OR_GREATER` gates in the handshake, ECDHE, record
protection and profile probe are removed.
- Two .NET 8 APIs get net48 fallbacks: the AES-ECB record-number mask
uses `CreateEncryptor`, and the handshake random uses
`RandomNumberGenerator.Create`.
- The HKDF probe now checks the managed `DtlsHkdf` that the key schedule
actually uses.
- The public `IsAeadSupported` / `IsRawSharedSecretSupported` probes are
kept and now return `true` everywhere.
- The DTLS tests now also run on net48. The net48 "registers no
profiles" test now asserts that the NIST AEAD profiles are registered.
The socket transport-peer tests and the benchmarks stay .NET 8-only
because they use .NET 8 socket APIs.
- `docs/PubSub.md` DTLS profile table: the net48 column is updated.
Results: PubSub.Udp.Tests net48, full suite: **229 passed, 0 skipped**.
It includes complete NIST AEAD, brainpool and integrity-only handshakes
and ChaCha20 records. The DTLS subset is 76 on net48 vs 81 on net10.0;
the 5 still-gated socket-peer tests are the difference.
Note: DTLS profile selection does not consult `CryptoCompliancePolicy`
on any target. That is pre-existing and unchanged here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
6.9 KiB
Markdown
125 lines
6.9 KiB
Markdown
# Production readiness checklist
|
|
|
|
The samples and [Getting started](GettingStarted.md) use shortcuts that are
|
|
suitable only on a development computer: temporary certificate stores,
|
|
automatic certificate acceptance, anonymous users, and short reconnect limits.
|
|
Review this checklist before you deploy a client or server. Each item links to
|
|
the guide that explains the setting. The checklist does not replace a security
|
|
review of your deployment.
|
|
|
|
## Contents
|
|
|
|
- [Application identity and certificates](#application-identity-and-certificates)
|
|
- [Endpoints and message security](#endpoints-and-message-security)
|
|
- [Users and permissions](#users-and-permissions)
|
|
- [Secrets and private keys](#secrets-and-private-keys)
|
|
- [Resource limits](#resource-limits)
|
|
- [Diagnostics and auditing](#diagnostics-and-auditing)
|
|
- [Clients](#clients)
|
|
- [Deployment and operation](#deployment-and-operation)
|
|
|
|
## Application identity and certificates
|
|
|
|
- [ ] Give each installation a unique `ApplicationUri` that matches its
|
|
application certificate. See [Concepts: applications](Concepts.md#applications).
|
|
- [ ] Set `PkiRoot` to a persistent, access-controlled directory. The hosted
|
|
client and server default to a folder in the system temporary directory. See
|
|
[Application identity and certificate defaults](DependencyInjection.md#application-identity-and-certificate-defaults).
|
|
- [ ] Keep `AutoAcceptUntrustedCertificates` disabled, and provision the
|
|
trusted and issuer certificates, or CA-issued certificates, before peers
|
|
connect. See [Certificates](Certificates.md).
|
|
- [ ] Keep SHA-1 certificates rejected and an RSA key size of at least 2048
|
|
bits; both are the defaults. See
|
|
[Server security and resource controls](DependencyInjection.md#server-security-and-resource-controls).
|
|
- [ ] Plan certificate renewal and trust-list updates, for example with
|
|
certificate-expiration alarms, push management, or a Global Discovery Server.
|
|
See [Certificate manager](CertificateManager.md) and [GDS](GDS.md).
|
|
|
|
## Endpoints and message security
|
|
|
|
- [ ] Offer `SignAndEncrypt` endpoints and keep `IncludeUnsecurePolicyNone`
|
|
set to `false`; both are the hosted-server defaults. See
|
|
[Server security and resource controls](DependencyInjection.md#server-security-and-resource-controls).
|
|
- [ ] Enable `IncludeEccPolicies` only when every client supports the ECC
|
|
policies. On .NET Framework the ECDH agreement and the AES-GCM and
|
|
ChaCha20-Poly1305 ciphers run in BouncyCastle rather than a validated module.
|
|
See [ECC profiles: known limitations](EccProfiles.md#known-limitations).
|
|
- [ ] Configure clients to select a `SignAndEncrypt` endpoint. See
|
|
[Concepts: endpoints, secure channels, and sessions](Concepts.md#endpoints-secure-channels-and-sessions).
|
|
|
|
## Users and permissions
|
|
|
|
- [ ] Decide whether anonymous access is acceptable. A hosted server without
|
|
`UserTokenPolicies` advertises an `Anonymous` policy. See
|
|
[User token policies](DependencyInjection.md#user-token-policies).
|
|
- [ ] Register an authenticator for each user-token policy that the server
|
|
advertises, for example with `AddDefaultIdentityAuthenticators`. A policy
|
|
without an authenticator does not authenticate users. See
|
|
[Identity providers](IdentityProviders.md).
|
|
- [ ] Map identities to roles and restrict node permissions. See
|
|
[Role-based user management](RoleBasedUserManagement.md).
|
|
- [ ] Set session and channel ceilings for the expected load, and keep the
|
|
failed-authentication lockout (`MaxFailedAuthenticationAttempts`, default 5).
|
|
See [Server security and resource controls](DependencyInjection.md#server-security-and-resource-controls).
|
|
|
|
## Secrets and private keys
|
|
|
|
- [ ] Keep passwords, tokens, and private keys out of source code,
|
|
configuration files in source control, and container images.
|
|
- [ ] Store credentials through an `ISecretStore` that is backed by a durable,
|
|
access-controlled secret service; a hosted server registers one with
|
|
`AddSecretStore`. See [Dependency injection: server feature](DependencyInjection.md#server-feature).
|
|
- [ ] Where policy requires it, keep private keys in a TPM, HSM, PKCS#11 token,
|
|
or key service. See [Crypto providers](CryptoProvider.md).
|
|
|
|
## Resource limits
|
|
|
|
- [ ] Review the transport quotas (`MaxMessageSize`, `MaxArrayLength`,
|
|
`MaxByteStringLength`) and the service `OperationLimits`. See
|
|
[Operation limits](DependencyInjection.md#operation-limits).
|
|
- [ ] Tune the admission rate limits, which are enabled by default, with
|
|
`ConfigureRateLimits`. See [Rate limiting](RateLimiting.md).
|
|
- [ ] Review the resource-isolation profile; `Balanced` is the default. See
|
|
[Server resource isolation](ResourceIsolation.md).
|
|
- [ ] Size the server for the expected sessions, subscriptions, and monitored
|
|
items. See [Server scalability](ServerScalability.md).
|
|
|
|
## Diagnostics and auditing
|
|
|
|
- [ ] Send logs, metrics, and traces to your monitoring system through the
|
|
host's logging and `ITelemetryContext`. See [Diagnostics](Diagnostics.md).
|
|
- [ ] Enable audit events when you must record security-relevant actions; they
|
|
are off by default. See [Enabling auditing](Diagnostics.md#enabling-auditing).
|
|
- [ ] Decide whether to keep the server diagnostics nodes (`DiagnosticsEnabled`,
|
|
default `true`), which add per-session and per-subscription work. See
|
|
[Enabling diagnostics](Diagnostics.md#enabling-diagnostics).
|
|
- [ ] Use packet capture and key logs only for troubleshooting. Key logs make
|
|
captured traffic decryptable. See [Diagnostics: security model](Diagnostics.md#security-model).
|
|
|
|
## Clients
|
|
|
|
- [ ] Keep the default reconnect policy for long-running clients. The Getting
|
|
started client limits retries only to report errors quickly. See
|
|
[Reconnect semantics](Sessions.md#reconnect-semantics-on-managedsession).
|
|
- [ ] Trust server certificates explicitly instead of accepting them
|
|
automatically. See [Certificates](Certificates.md).
|
|
- [ ] Store namespace URIs, not namespace indexes, and resolve indexes after
|
|
each connection. See [Concepts: node identity and namespaces](Concepts.md#node-identity-and-namespaces).
|
|
- [ ] Pass cancellation tokens and dispose sessions and subscriptions. See
|
|
[Concepts: how the SDK is organized](Concepts.md#how-the-sdk-is-organized).
|
|
|
|
## Deployment and operation
|
|
|
|
- [ ] Keep the clocks of clients and servers synchronized, for example with
|
|
NTP or PTP. Certificate validity, timestamps, and redundancy depend on it.
|
|
See [Kubernetes: time synchronization](Kubernetes.md#time-synchronization).
|
|
- [ ] In containers, mount the certificate stores on persistent storage and
|
|
supply secrets at run time. See
|
|
[Container reference server](ContainerReferenceServer.md) and
|
|
[Kubernetes: secrets, certificates, and trust](Kubernetes.md#secrets-certificates-and-trust).
|
|
- [ ] Use a Global Discovery Server or provisioning mode to manage certificates
|
|
across many installations. See [GDS](GDS.md) and
|
|
[Provisioning mode](ProvisioningMode.md).
|
|
- [ ] Add redundancy only when availability requirements need it. See
|
|
[High availability](HighAvailability.md).
|