Skip to content

Add base64url codec + unified AEAD size metadata (closes #12)#15

Merged
moisesja merged 2 commits into
mainfrom
feat/base64url-and-aead-metadata
Jun 14, 2026
Merged

Add base64url codec + unified AEAD size metadata (closes #12)#15
moisesja merged 2 commits into
mainfrom
feat/base64url-and-aead-metadata

Conversation

@moisesja

Copy link
Copy Markdown
Owner

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:

string Encode(ReadOnlySpan<byte> data);  // RFC 4648 §5, no '=' padding
byte[] Decode(ReadOnlySpan<char> text);  // tolerates optional padding

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, 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 (they were private const):

Cipher Key Nonce/IV Tag
AesGcmCipher KeySizeBytes 32 NonceSizeBytes 12 TagSizeBytes 16
AesCbcHmacCipher KeySizeBytes 64 IvSizeBytes 16 TagSizeBytes 32
XChaCha20Poly1305Cipher KeySizeBytes 32 NonceSizeBytes 24 TagSizeBytes 16

A 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), and KeySizeBytes/NonceSizeBytes/IvSizeBytes/TagSizeBytes promoted to public on the three ciphers, each with XML docs.
  • PublicAPI.Unshipped.txt — records the new type, two methods, and nine constants.
  • TestsBase64UrlTests (RFC 7515 Appendix A.1 JOSE vector round-trip, no = padding, URL-safe -/_ alphabet, padding-tolerant decode, FormatException on invalid input, random round-trip 0–64 bytes) and AeadSizeMetadataTests (constant values + each constant matches the bytes its cipher accepts/produces; a key one byte short is rejected).
  • Encryption sample — sizes the key/nonce/IV from the new constants and adds a base64url section (also satisfies the FR-17 API-coverage check).
  • PRD FR-16b and the CHANGELOG.

Notes for review

  • Placement (G4). The issue notes base64url arguably belongs in a future JOSE-enveloping module rather than the primitives library. Since that module doesn't exist yet, it lands here (the issue's AC allows "here or the JOSE module"); easy to relocate later.
  • Decode exception. Decode surfaces FormatException on invalid input — idiomatic for a codec (matches Convert.FromBase64String / the BCL Base64Url). NFR-3's "no FormatException from a public method" applies to the crypto key-input contract, not a general-purpose encoder; happy to wrap to ArgumentException if preferred.

Acceptance criteria (issue #12)

  • (G4) base64url-no-pad encode/decode available from the foundation package, round-tripping the JOSE test vectors.
  • (G5) each AEAD cipher type exposes its key/nonce/tag sizes as public constants.

Verification

  • dotnet build -warnaserror clean; full suite 819 tests green; API coverage OK; Encryption sample runs (base64url section included) and exits 0.

🤖 Generated with Claude Code

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>
@moisesja moisesja added this to the 1.1.0 milestone Jun 14, 2026

Copy link
Copy Markdown
Owner Author

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:

  • Scope is minimal and correct. Base64Url is a thin pass-through to the BCL System.Buffers.Text.Base64Url (available on this .NET 10 target), and the AEAD change is purely a private const → public const promotion — no behavioral change to any cipher, no secret material exposed. Promoting constants is API-only and safe.
  • Test coverage is thorough. RFC 7515 A.1 JOSE vector round-trip, no-padding assertion, URL-safe -/_ alphabet at indices 62/63, padding-tolerant decode, FormatException on invalid input, and 0–64-byte property round-trips. The AEAD tests don't just pin the literals — they prove each constant matches the bytes the cipher actually accepts/produces, including the one-byte-short rejection. That's exactly the right way to keep the constants from drifting into documentation-only fiction.
  • Housekeeping is complete: PublicAPI.Unshipped.txt, CHANGELOG, PRD FR-16b, and the Encryption sample are all updated in lockstep. Sample now sources sizes from the constants instead of magic numbers — good dogfooding of the new surface.

Two design calls you already flagged — both reasonable:

  • Decode throwing FormatException. Agreed this is idiomatic for a codec and consistent with Convert.FromBase64String/the BCL Base64Url; NFR-3's no-FormatException contract is about crypto key inputs, not a general encoder. No change needed.
  • Placement of base64url in the foundation. Fine to land here given no JOSE module exists yet; the relocation cost later is trivial (one type, two methods).

Nothing blocking, one optional nit: Decode's padding tolerance leans on undocumented-by-you BCL behavior — the Decode_ToleratesTrailingPadding test pins it, so if a future BCL/runtime bump ever tightened that, the test would catch it. No action needed; just noting the guarantee lives in the test rather than the wrapper.

CI: the no-native job is already green; the three build-test matrix jobs (ubuntu/macos/windows) were still in progress at review time. Assuming they go green to match your local dotnet build -warnaserror + 819-test run, this is good to merge.


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>
@moisesja moisesja self-assigned this Jun 14, 2026
@moisesja moisesja merged commit ee40257 into main Jun 14, 2026
4 checks passed
@moisesja moisesja deleted the feat/base64url-and-aead-metadata branch June 14, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ergonomics: base64url-no-pad codec + unified AEAD size metadata on the cipher types

1 participant