Skip to content

feat(sdk): return ErrAESGCMExhausted when IV collision risk exceeds 2^-32 (DSPX-4492) - #4203

Merged
dmihalcik-virtru merged 1 commit into
mainfrom
DSPX-4492-exhausted
Oct 9, 2026
Merged

dmihalcik-virtru merged 1 commit into
mainfrom
DSPX-4492-exhausted

Conversation

@dmihalcik-virtru

Copy link
Copy Markdown
Member

Proposed Changes

  • Adds the exported sentinel sdk.ErrAESGCMExhausted, aliased as experimental/tdf.ErrAESGCMExhausted. A TDF writer returns it, wrapped with %w, when encrypting another segment would push the AES-GCM IV collision probability under the DEK above 2^-32.
  • Adds the constant maxAESGCMSegmentsPerDEK = 2^32 - 1.
    • Payload segment IVs on main are random 96-bit values, so the collision probability is P ≈ k²/2^97. NIST SP 800-38D §8 requires P ≤ 2^-32, which means k ≤ ~2^32.5.
    • We use 2^32 − 1 because invocation 0 under the key is set aside for key-access metadata. It is also the limit of a 32-bit invocation counter, so the deterministic-IV follow-up can keep the same cap.
  • The check lives in chunkedWriter.WriteSegment, which every write path goes through: CreateTDF with a known length, CreateTDF with an unknown-length stream (no cap there before this change; see DSPX-4905), NewChunkedWriter, and the experimental tdf.Writer. It runs under the writer lock, before the segment is encrypted, and it counts attempts. A failed encryption has still drawn an IV, so a retry does not get it back.
  • IV generation is unchanged. On main it is still crypto/rand 96-bit IVs through ocrypto.AesGcm in every mode. Deterministic §8.2.1 IVs and FIPS mode will be stacked on top of this PR later (see feat(sdk): derive AES-GCM segment IVs from a message id and part number #4118 / feat(sdk): stateful per-key AES-GCM IV counter (alternative to #4118) #4121).

Crypto-risk callout: if two GCM IVs collide under one DEK, the XOR of the two plaintexts and the GHASH key are exposed, which allows forgery under that key. This PR adds a hard stop only. It does not change the wire format, and readers are unaffected. In practice the cap is out of reach: at the 2 MiB default segment size it is about 8 PiB, and at the 16 KiB minimum it is about 64 TiB.

Note: the existing declared-size check (errTooManySegments, math.MaxInt32 segments, archive int addressing) is stricter than this cap and stays as it is. That means known-length inputs still fail early with that error. The new cap is what bounds unknown-length streams and the chunked writers.

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation (godoc on the error, constant, and experimental writer)

Testing Instructions

cd sdk
go test -race ./...
go test -run 'TestChunkedAESGCM|TestChunkedDefaultMaxEncryptions|TestTDF/Test_CreateTDF_InputSizeBounds' -v .
golangci-lint run ./...   # no new issues; the remaining ones are pre-existing in untouched files

The tests lower the cap through unexported test seams (withChunkedMaxEncryptions, withMaxSegmentEncryptions), so they reach the error without encrypting 2^32 segments. They cover the following:

  • The segment past the cap is refused, and it is never encrypted.
  • The writer can still finalize what it wrote before the refusal.
  • Failed attempts count toward the cap.
  • The default cap is 2^32 − 1.
  • CreateTDF returns ErrAESGCMExhausted for both known-length and unknown-length input.
  • A payload exactly at the cap round-trips.

Part of DSPX-4492.

…^-32 (DSPX-4492)

TDF writers now refuse to encrypt more than 2^32-1 payload segments under one DEK. With random 96-bit IVs the collision probability is about k^2/2^97, and NIST SP 800-38D section 8 caps it at 2^-32. Attempts are counted, and the check runs before the segment is encrypted, on every CreateTDF path and the chunked/experimental writers.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 55 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 21caa996-59cb-4141-8144-506c71acf847
📥 Commits

Reviewing files that changed from the base of the PR and between 6d3a01a and c73a95d.

📒 Files selected for processing (9)
  • sdk/chunked_options.go
  • sdk/chunked_test.go
  • sdk/chunked_writer.go
  • sdk/experimental/tdf/doc.go
  • sdk/experimental/tdf/writer.go
  • sdk/tdf.go
  • sdk/tdf_config.go
  • sdk/tdf_test.go
  • sdk/tdferrors.go
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added comp:sdk A software development kit, including library, for client applications and inter-service communicati size/s labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 435.350242ms
Throughput 229.70 requests/second

TDF3 Benchmark Results

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 48.054102796s
Average Latency 479.574558ms
Throughput 104.05 requests/second

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • otdfctl
  • service
  • tests-bdd

See the workflow run for details.

@dmihalcik-virtru
dmihalcik-virtru marked this pull request as ready for review October 8, 2026 20:28
@dmihalcik-virtru
dmihalcik-virtru requested review from a team as code owners October 8, 2026 20:28
Comment thread sdk/chunked_test.go
@dmihalcik-virtru
dmihalcik-virtru added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 55a9b57 Oct 9, 2026
49 of 50 checks passed
@dmihalcik-virtru
dmihalcik-virtru deleted the DSPX-4492-exhausted branch October 9, 2026 15:16
dmihalcik-virtru added a commit that referenced this pull request Oct 14, 2026
…nter

Align the Go SDK with option 2b of the AES-GCM IV ADR (virtru-corp/adr#373):

- ocrypto: MaxSeals is 2^32-1 on both the deterministic and the random-IV
  (FIPS) path, the cap #4203 already enforced on random IVs. At the cap the
  random path sits at P ~ 2^-33, inside SP 800-38D's 2^-32.
- sdk: drop the derived default segment size; it is 2 MiB (max 4 MiB) again
  in every mode, and maxPayloadSegments is the archive's MaxInt32 again.
- sdk: drop the writer's own encryption counter. The DEK sealer's counter is
  the only budget, and ocrypto.ErrSealerExhausted is reported wrapped as
  ErrAESGCMExhausted. Tests reach the cap through an injected sealer instead
  of a lowered writer cap.
- adr: record the decision and the rejected 2^26 cap.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
dmihalcik-virtru added a commit that referenced this pull request Oct 15, 2026
…nter

Align the Go SDK with option 2b of the AES-GCM IV ADR (virtru-corp/adr#373):

- ocrypto: MaxSeals is 2^32-1 on both the deterministic and the random-IV
  (FIPS) path, the cap #4203 already enforced on random IVs. At the cap the
  random path sits at P ~ 2^-33, inside SP 800-38D's 2^-32.
- sdk: drop the derived default segment size; it is 2 MiB (max 4 MiB) again
  in every mode, and maxPayloadSegments is the archive's MaxInt32 again.
- sdk: drop the writer's own encryption counter. The DEK sealer's counter is
  the only budget, and ocrypto.ErrSealerExhausted is reported wrapped as
  ErrAESGCMExhausted. Tests reach the cap through an injected sealer instead
  of a lowered writer cap.
- adr: record the decision and the rejected 2^26 cap.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:sdk A software development kit, including library, for client applications and inter-service communicati size/s

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants