Repository navigation
Enable AES-GCM and ChaCha20-Poly1305 policies on net48 via BouncyCastle (stacked on #4703) - #4705
Conversation
…yfill (#4510) OPC UA Part 6 feeds the raw ECDH shared secret into HKDF, which the .NET Framework BCL cannot produce (DeriveKeyMaterial always hashes it), so the ECC SecureChannel and user-token policies were unavailable on net48. - Add an internal DeriveRawSecretAgreement extension on ECDiffieHellman in Opc.Ua.Security.Certificates (net48 only) that computes the agreement with BouncyCastle's ECDHBasicAgreement. Keys, ECDSA and HKDF stay on CNG; the extension mirrors the .NET 8 signature so Nonce compiles unchanged. - Drop SupportsRawEccSecretAgreement; the CBC ECC policies (nistP256/384, brainpoolP256r1/384r1) are now offered on every target. The AES-GCM and ChaCha20-Poly1305 variants still need .NET 8. - Tests: symmetric agreement on all four curves, a net48 oracle against CNG's hashed agreement, curve-mismatch and off-curve rejection; tests that asserted the old limitation updated. - Docs: EccProfiles, migration guide, checklist, profiles, what's new; note the agreement step is not a validated module on net48. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The net48 raw ECDH agreement runs in BouncyCastle and bypasses the crypto provider registry, so the auditor could not see it and FipsOnly still permitted the NIST ECC policies. CryptoCompliance.IsPolicyPermitted now withholds every ECC policy on .NET Framework under FipsOnly. Docs and the bundled migration skill copy updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le (#4510) .NET Framework has no AesGcm or ChaCha20Poly1305, so the AEAD security policies (ECC_*_AesGcm, ECC_*_ChaChaPoly, RSA_DH_AesGcm, RSA_DH_ChaChaPoly) were unavailable on net48 even after the raw-ECDH polyfill. - Add internal AesGcm and ChaCha20Poly1305 types next to EcdhRawAgreement in Opc.Ua.Security.Certificates (net48 only). They mirror the .NET 8 BCL surface and run BouncyCastle's GcmBlockCipher over AesEngine and its ChaCha20Poly1305. A tag mismatch throws CryptographicException (not InvalidCipherTextException) and clears the plaintext; nothing unverified is released and scratch buffers and the key copy are zeroed. - Opc.Ua.Core aliases them on NETFRAMEWORK and drops the NET8_0_OR_GREATER gates in SecurityPolicies, CryptoUtils and PlatformSymmetricCryptoProvider, so the channel, the provider facet and the encrypted ECC user tokens use the same code on every target. .NET 8+ keeps the BCL ciphers. - Tests: NIST GCM (AES-128/256) and RFC 8439 2.8.2 known answers through the platform provider and the polyfill, tag/ciphertext/AAD tamper rejection. Core.Tests also compiles the polyfill on .NET 8+ to cross-check it against the BCL on random inputs. - Docs: EccProfiles, migration guide, checklist, profiles, what's new. PubSub DTLS record protection is still .NET 8 only (follow-up). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…147' into romanett/net48-aead-polyfill # Conflicts: # docs/EccProfiles.md # docs/migrate/2.0.x/certificates.md
The net48 AES-GCM and ChaCha20-Poly1305 now run in BouncyCastle, and the encrypted user tokens reach them without the provider registry, so the auditor cannot see them. Extend the FipsOnly check that already withholds the ECC policies on .NET Framework to every AEAD policy, which adds RSA_DH_AesGcm (FIPS-approved and not ECC). Tests and CryptoProvider.md updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ills The DTLS 1.3 transport was compiled out on .NET Framework (no raw ECDH agreement, no AesGcm/ChaCha20Poly1305), so net48 registered no DTLS profiles. Reuse the polyfills from Opc.Ua.Security.Certificates (now InternalsVisibleTo Opc.Ua.PubSub.Udp) and drop the NET8_0_OR_GREATER gates in the handshake, ECDHE key exchange, record protection and profile probe. - net48 fallbacks for the two remaining .NET 8 APIs: AES-ECB record-number mask via CreateEncryptor, RandomNumberGenerator.Create for the handshake random. The HKDF probe now exercises the managed DtlsHkdf the key schedule actually uses instead of the BCL HKDF. - The IsAeadSupported / IsRawSharedSecretSupported probes stay public and now return true on every target. - Tests: the DTLS handshake, keying, ECDHE, record protection and authenticator tests now also run on net48 (net6+ APIs replaced); the net48 "registers no profiles" test becomes one asserting the NIST AEAD profiles. The socket transport-peer tests and the benchmarks stay .NET 8. - docs/PubSub.md: net48 column of the DTLS profile table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UpdateIdentityAsync(provider, overrideUri) passes only the override as the enabled policy, but the selector still accepts token policies with an empty SecurityPolicyUri because they inherit the channel policy. When the override itself cannot be satisfied (an ECC policy whose curve the instance certificate does not match, or an ECC policy on an RSA instance certificate), the selection silently fell back to the inherited policy: the session logged the switch, fetched an EphemeralKey for the override, and then encrypted the token under the channel policy against it. With a different curve that failed with a raw CryptographicException/ArgumentException. net48 exposed it: ECC_nistP256_AesGcm is unavailable there, so FailedEccToEccOverrideKeepsSessionReconnectable overrode P-256 -> P-384. The override is now verified against the selected policy's effective SecurityPolicyUri and refused with BadIdentityTokenRejected (as documented) before any key is fetched or state committed. Adds a cross-curve test and lets UpdateIdentityAsyncSwitchesToEccUserTokenPolicy accept that refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…147' into romanett/net48-aead-polyfill
There was a problem hiding this comment.
🟡 Changes recommended
The newly ungated registry test assumes optional runtime primitives are universally available, causing valid platforms to fail.
3 open findings
What changed in this PR
Extends #4703 by enabling AEAD security policies and DTLS 1.3 on .NET Framework 4.8 through BouncyCastle polyfills.
Changes:
- Adds AES-GCM and ChaCha20-Poly1305 polyfills with known-answer and interoperability tests.
- Enables AEAD SecureChannel, encrypted-token, and DTLS paths on net48.
- Updates FIPS filtering and platform-support documentation.
| File | Description |
|---|---|
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsRecordProtectionTests.cs |
Enables record-protection tests on net48. |
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsProfileRegistryTests.cs |
Updates profile-registration expectations. |
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsKeyScheduleTests.cs |
Enables key-schedule tests on net48. |
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsHandshakeKeyingContextTests.cs |
Uses cross-target hashing and randomness. |
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsHandshakeContextTests.cs |
Enables handshake tests on net48. |
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsEcdheKeyExchangeTests.cs |
Enables ECDHE tests on net48. |
tests/Opc.Ua.PubSub.Udp.Tests/Dtls/DtlsCertificateAuthenticatorTests.cs |
Enables certificate-authentication tests. |
tests/Opc.Ua.Core.Tests/Security/Crypto/CryptoProviderRegistryTests.cs |
Verifies net48 FIPS exclusions. |
tests/Opc.Ua.Core.Tests/Security/Crypto/AeadPolyfillTests.cs |
Adds AEAD vectors, tampering, and cross-check tests. |
tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj |
Compiles the polyfill for BCL cross-checking. |
src/Opc.Ua.Security.Certificates/Org.BouncyCastle/AeadCiphers.cs |
Implements BouncyCastle AEAD polyfills. |
src/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj |
Grants PubSub access to internal polyfills. |
src/Opc.Ua.PubSub.Udp/Dtls/DtlsRecordProtection.cs |
Enables AEAD records and AES masking on net48. |
src/Opc.Ua.PubSub.Udp/Dtls/DtlsProfileRegistry.cs |
Probes managed DTLS primitives. |
src/Opc.Ua.PubSub.Udp/Dtls/DtlsHandshakeContext.cs |
Enables DTLS handshakes on net48. |
src/Opc.Ua.PubSub.Udp/Dtls/DtlsEcdheKeyExchange.cs |
Uses the raw-ECDH polyfill. |
src/Opc.Ua.Core/Security/SecurityPolicies.cs |
Exposes AEAD policies on net48. |
src/Opc.Ua.Core/Security/Crypto/PlatformSymmetricCryptoProvider.cs |
Routes AEAD operations through polyfills. |
src/Opc.Ua.Core/Security/Crypto/CryptoCompliance.cs |
Withholds managed AEAD under FIPS-only mode. |
src/Opc.Ua.Core/Security/Certificates/CryptoUtils.cs |
Enables AEAD channel and token processing. |
docs/WhatsNewIn2.0.md |
Announces cross-target AEAD availability. |
docs/PubSub.md |
Documents net48 DTLS support. |
docs/Profiles.md |
Updates ECC platform requirements. |
docs/ProductionChecklist.md |
Adds managed-crypto deployment guidance. |
docs/migrate/2.0.x/certificates.md |
Updates migration guidance. |
docs/EccProfiles.md |
Documents AEAD limitations and risks. |
docs/CryptoProvider.md |
Documents FIPS implications. |
.agents/skills/opcua-v20-migration/references/stack-migration/certificates.md |
Synchronizes bundled migration guidance. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
…, sealed fixture - DtlsProfileRegistryTests: expect each NIST AEAD profile exactly when the probed primitives allow it; only on .NET Framework, where the BouncyCastle ciphers, managed HKDF and CNG NIST curves always exist, assert they do. - DtlsPrimitiveSupport / Probe docs describe the BouncyCastle and managed HKDF fallbacks instead of "BCL APIs only". - Seal AeadPolyfillTests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@copilot resolve the merge conflicts in this pull request |
…-polyfill # Conflicts: # .agents/skills/opcua-v20-migration/references/stack-migration/certificates.md # docs/CryptoProvider.md # docs/EccProfiles.md # docs/ProductionChecklist.md # docs/Profiles.md # docs/WhatsNewIn2.0.md # docs/migrate/2.0.x/certificates.md # src/Opc.Ua.Core/Security/Crypto/CryptoCompliance.cs # tests/Opc.Ua.Core.Tests/Security/Crypto/CryptoProviderRegistryTests.cs
Merged |
Code coverage✅ Coverage gate passed.
Coverage is above the recorded baseline - consider ratcheting Thresholds live in |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4705 +/- ##
==========================================
- Coverage 82.82% 82.82% -0.01%
==========================================
Files 2264 2264
Lines 345410 345410
Branches 60182 60182
==========================================
- Hits 286081 286074 -7
- Misses 39259 39262 +3
- Partials 20070 20074 +4
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|


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
AesGcmorChaCha20Poly1305. 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
src/Opc.Ua.Security.Certificates/Org.BouncyCastle/AeadCiphers.cs(internal,#if NETFRAMEWORK, next toEcdhRawAgreement):AesGcmandChaCha20Poly1305types with the .NET 8 BCL surface. They wrap BouncyCastle'sGcmBlockCipher(AesEngine)andOrg.BouncyCastle.Crypto.Modes.ChaCha20Poly1305.CryptographicException, never BC'sInvalidCipherTextException, soPlatformSymmetricCryptoProvider.DecryptAuthenticatedstill returnsfalseand the channel still raises a protocol error.NETFRAMEWORKand removes theNET8_0_OR_GREATERgates inSecurityPolicies(SupportsAesGcmPolicy/SupportsChaCha20Poly1305Policy),CryptoUtils(SymmetricEncryptAndSign/SymmetricDecryptAndVerify, AEAD helpers) andPlatformSymmetricCryptoProvider. The channel, the provider facet and the encrypted ECC user tokens (EncryptedSecret, throughCryptoUtils) now run the same code on every target. .NET 8+ keeps the BCL ciphers.CryptoCompliancePolicy.FipsOnly,CryptoCompliance.IsPolicyPermittednow also withholds every AEAD policy on .NET Framework. That addsRSA_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 anddocs/CryptoProvider.mdare updated to match.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
AeadPolyfillTests(Core.Tests):PlatformSymmetricCryptoProvideron every TFM and through the polyfill directly.falsefrom the provider, andCryptographicExceptionwith a cleared plaintext from the polyfill.AeadCiphers.cs(AEAD_POLYFILL_CROSSCHECK, test-onlyBouncyCastle.Cryptographyreference). 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_AesGcmis permitted under FipsOnly only off .NET Framework and withheld on it.Assert.Ignorewhen AEAD is unavailable now run on net48, e.g.EccSecretRejectsDifferentNegotiatedPolicyAsyncand theRSA_DH_AesGcmserver session tests.Local results
EccInteropTestsnet48 vs open62541 1.5.8 (OpenSSL) peerECC_nistP256_AesGcm/_ChaChaPoly× Sign/SignAndEncrypt × Anonymous/UserName, both directions). Remaining 8 failures are the documented open62541 CBC Sign-only HKDF differences inexpected-differences.jsonEccInteropTestsnet48 vs 1.5.378 LegacyPeerNote on the acceptance criterion: the 1.5.378 LegacyPeer does not implement the AEAD policies, so its
_AesGcm/_ChaChaPoly/*AeadTokenRenewalAsynccases 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.CertificatesgainsInternalsVisibleTo Opc.Ua.PubSub.Udp):NET8_0_OR_GREATERgates in the handshake, ECDHE, record protection and profile probe are removed.CreateEncryptor, and the handshake random usesRandomNumberGenerator.Create.DtlsHkdfthat the key schedule actually uses.IsAeadSupported/IsRawSharedSecretSupportedprobes are kept and now returntrueeverywhere.docs/PubSub.mdDTLS 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
CryptoCompliancePolicyon any target. That is pre-existing and unchanged here.🤖 Generated with Claude Code