Add base64url codec + unified AEAD size metadata (closes #12)#15
Conversation
Two shared-primitive ergonomics gaps every JOSE/DIDComm consumer of NetCrypto otherwise re-implements locally. G4 — base64url codec. New public static class Base64Url: Encode(ReadOnlySpan<byte>) -> string // RFC 4648 §5, no '=' padding Decode(ReadOnlySpan<char>) -> byte[] // tolerates optional padding A thin wrapper over the BCL System.Buffers.Text.Base64Url, giving the foundation a single source of truth for the JOSE/JWK byte boundary (headers, signatures, JWE iv/ciphertext/tag/encrypted_key, JWK x/y/d, apu/apv). G5 — unified AEAD size metadata. Each content-encryption cipher now exposes its key/nonce/tag sizes as public const int (previously private): AesGcmCipher KeySizeBytes 32, NonceSizeBytes 12, TagSizeBytes 16 AesCbcHmacCipher KeySizeBytes 64, IvSizeBytes 16, TagSizeBytes 32 XChaCha20Poly1305Cipher KeySizeBytes 32, NonceSizeBytes 24, TagSizeBytes 16 so a JOSE builder can size the CEK and IV/nonce from the source of truth instead of a hard-coded table. The IV/nonce name follows each cipher's own parameter. - PublicAPI.Unshipped.txt: record the new type, methods, and constants. - Tests: Base64Url (RFC 7515 A.1 JOSE vector, no-padding, url-safe alphabet, padding-tolerant decode, FormatException on invalid, round-trip 0..64 bytes); AeadSizeMetadata (constant values + they match what each cipher accepts/produces). - Encryption sample: source key/nonce/iv sizes from the constants and add a Base64url section (also satisfies the FR-17 API-coverage check). - PRD FR-16b and the CHANGELOG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review — looks good to merge ✅Reviewed the full diff. This is a tight, additive, low-risk change (closes #12) and I have no blocking concerns. What's solid:
Two design calls you already flagged — both reasonable:
Nothing blocking, one optional nit: CI: the Generated by Claude Code |
…inding)
An adversarial pass found that Decode delegated straight to the BCL
System.Buffers.Text.Base64Url, which silently STRIPS ASCII whitespace before
decoding — so "QU JD", "\nQUJD\n", and "QUJD" all decoded to the same bytes, and
Decode(" ") returned an empty array. For a canonical JOSE/JWK primitive whose
whole point is to avoid charset divergence, mapping several wire forms to one byte
string is wrong, and it contradicted the documented "throws FormatException on
invalid input" contract.
Decode now validates the input alphabet up front and rejects any character outside
[A-Za-z0-9-_=] (including whitespace) with FormatException; optional trailing
padding is still tolerated (the BCL still validates '=' placement). Added
regression tests for whitespace (incl. all-whitespace) and the standard-base64
'+'/'/' characters; updated the XML doc, PRD FR-16b, and the CHANGELOG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Closes #12. Two small ergonomics gaps every JOSE/DIDComm consumer of NetCrypto otherwise re-implements locally. Neither is a correctness gap — both are "every consumer re-implements the same trivial thing" duplication.
G4 — base64url codec
New
public static class Base64Url:A thin wrapper over the BCL
System.Buffers.Text.Base64Url(.NET 9+), giving the foundation a single source of truth for the JOSE/JWK byte boundary (protected headers, signatures, JWEiv/ciphertext/tag/encrypted_key, JWKx/y/d,apu/apv).G5 — unified AEAD size metadata
Each content-encryption cipher now exposes its key/nonce/tag sizes as
public const int(they wereprivate const):AesGcmCipherKeySizeBytes32NonceSizeBytes12TagSizeBytes16AesCbcHmacCipherKeySizeBytes64IvSizeBytes16TagSizeBytes32XChaCha20Poly1305CipherKeySizeBytes32NonceSizeBytes24TagSizeBytes16A JOSE builder must allocate the CEK and IV/nonce before calling
Encrypt, so it needs(keyLen, nonceLen, tagLen)per cipher; exposing them lets consumers validate against the source of truth and avoid drift. The IV/nonce name follows each cipher's own parameter (CBC has an IV; GCM/XChaCha have a nonce).Changes
Base64Url.cs(new), andKeySizeBytes/NonceSizeBytes/IvSizeBytes/TagSizeBytespromoted to public on the three ciphers, each with XML docs.PublicAPI.Unshipped.txt— records the new type, two methods, and nine constants.Base64UrlTests(RFC 7515 Appendix A.1 JOSE vector round-trip, no=padding, URL-safe-/_alphabet, padding-tolerant decode,FormatExceptionon invalid input, random round-trip 0–64 bytes) andAeadSizeMetadataTests(constant values + each constant matches the bytes its cipher accepts/produces; a key one byte short is rejected).Encryptionsample — sizes the key/nonce/IV from the new constants and adds a base64url section (also satisfies the FR-17 API-coverage check).Notes for review
DecodesurfacesFormatExceptionon invalid input — idiomatic for a codec (matchesConvert.FromBase64String/ the BCLBase64Url). NFR-3's "noFormatExceptionfrom a public method" applies to the crypto key-input contract, not a general-purpose encoder; happy to wrap toArgumentExceptionif preferred.Acceptance criteria (issue #12)
Verification
dotnet build -warnaserrorclean; full suite 819 tests green; API coverage OK;Encryptionsample runs (base64url section included) and exits 0.🤖 Generated with Claude Code