Automatic BouncyCastle AEAD fallback for Blazor WASM (#2)#15
Merged
Conversation
Route all ChaCha20-Poly1305 use through an internal IAeadCipher interface whose method shape mirrors System.Security.Cryptography.ChaCha20Poly1305 exactly, plus a BclAeadCipher pass-through and an AeadCipher factory that (for now) always returns the native backend. This is a pure refactor with zero behavioral change — the only edits to CryptoHelper/StreamEncryption/EncryptStream/DecryptStream are the cipher parameter/field type and the two construction sites. It sets up the seam so a managed (BouncyCastle) backend can be substituted on browser/ WASM, where the platform cipher is unavailable. Phase A of #2.
System.Security.Cryptography.ChaCha20Poly1305 is [UnsupportedOSPlatform("browser")]
— its constructor throws on mono-wasm, which is the only thing that made AgeSharp
unusable in Blazor WebAssembly (every other primitive is already BouncyCastle or
browser-safe).
- Add BouncyCastleAeadCipher (managed ChaCha20-Poly1305) behind the IAeadCipher
seam, marshaling BC's contiguous ct||tag to/from the split ct+tag spans and
translating InvalidCipherTextException to AuthenticationTagMismatchException (kept
local so it never intercepts BC's RSA-OAEP failures).
- AeadCipher.Create picks the native cipher where ChaCha20Poly1305.IsSupported and
the managed one otherwise — so the browser works with no configuration and
server/desktop keep the fast, zero-allocation path. The backend selection is fully
internal: no public configuration surface is added. A user-facing override is
deferred as a separate, deliberate decision.
- Tests: byte-exact cross-compatibility between the two backends across a size matrix
(proves the marshaling and the 128-bit MAC constant), tag/ciphertext tamper
detection, reuse, and both backends round-trip. A compile-time FORCE_PORTABLE_AEAD
switch (-p:ForcePortableAead=true) builds the library with the managed backend as
default so the whole suite + 143 CCTV vectors run through BouncyCastle, wired as a
test-portable CI job. A benchmark quantifies the tradeoff (~2x slower, ~336 B/chunk).
Phase B of #2. Fixes #2.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #15 +/- ##
==========================================
+ Coverage 91.87% 91.97% +0.10%
==========================================
Files 38 41 +3
Lines 2338 2381 +43
Branches 310 312 +2
==========================================
+ Hits 2148 2190 +42
Misses 133 133
- Partials 57 58 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The test-portable job runs the whole suite with the compile switch that flips the default AEAD backend to BouncyCastle — but because the two backends are wire-identical, every test passes on native too. So if the switch ever silently failed to engage, the job would run on native and still go green: a false 'portable' pass. Add DefaultBackend_MatchesBuildConfiguration, which asserts AeadCipher's default is BouncyCastleAeadCipher under FORCE_PORTABLE_AEAD and the native cipher otherwise. The same test asserting different types in the two builds makes the switch self-verifying: a broken switch now fails the job loudly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes AgeSharp work in Blazor WebAssembly.
System.Security.Cryptography.ChaCha20Poly1305is[UnsupportedOSPlatform("browser")]— its constructor throws onmono-wasm, and it's the only browser-incompatible primitive (X25519, scrypt, HKDF, ML-KEM, SSH RSA/Ed25519 are already BouncyCastle; SHA/HMAC/RNG are browser-safe). Fixes #2.Approach
No new public API. Backend selection is fully internal and automatic: the native platform cipher where
ChaCha20Poly1305.IsSupported, the managed BouncyCastle cipher otherwise. A browser just works; server/desktop keep the fast, zero-allocation path. (A user-facing override + broader options story is deferred as a separate, deliberate decision.)Review in two commits
1 —
IAeadCipherseam (no behavior change). An internal interface whose method shape mirrors the BCL cipher exactly, a pass-throughBclAeadCipher, and anAeadCipherfactory. The 4 crypto files change only the cipher parameter/field type and the two construction sites — every method body is untouched, so this commit is behaviorally identical (still 100% native). Safe checkpoint.2 — the BouncyCastle fallback. The actual feature.
The one file to scrutinize:
BouncyCastleAeadCipher128(bits), not 16 — a wrong value passes local round-trips but breaks interop. Pinned as a constant.ciphertext‖tagto/from our separate ct+tag spans via a pooled scratch buffer.InvalidCipherTextException→AuthenticationTagMismatchException, kept local so it never intercepts BouncyCastle's RSA-OAEP failures (SshRsaIdentity).Verification
AeadCipherTests): the two backends produce identicalct‖tagacross a size matrix{0,1,63,64,65,100,65536}and each decrypts the other's output — proving the marshaling and the MAC constant. Plus tamper detection and reuse.ageCLI interop through BouncyCastle via the newtest-portableCI job (-p:ForcePortableAead=true, a compile switch in the library — no runtime config, no mutable state). Managed code is OS-independent, so one Linux job suffices.ChaChaImplBenchmarks) quantifies the managed-backend tradeoff (~2× slower, ~336 B/chunk) — which is why native stays the default.Not covered
An actual in-browser runtime smoke test (no wasm workload in CI yet). The portable-path suite + CCTV + interop is the agreed proxy; a headless-browser lane is a follow-up.