Skip to content

fix(core): Uses strict FIPS AES-GCM#3507

Open
strantalis wants to merge 4 commits into
mainfrom
fix/dspx-3326-fips-aes-gcm
Open

fix(core): Uses strict FIPS AES-GCM#3507
strantalis wants to merge 4 commits into
mainfrom
fix/dspx-3326-fips-aes-gcm

Conversation

@strantalis
Copy link
Copy Markdown
Member

@strantalis strantalis commented May 21, 2026

Summary

  • switch OpenTDF AES-GCM helpers to Go's strict-FIPS-compatible random-nonce AEAD
  • preserve standard nonce-prefixed AES-GCM encoding for TDF data and EC wrapping
  • reject caller-managed IV encryption and non-standard GCM tag sizes explicitly
  • update AES-GCM tests for standard round-trip, split nonce/ciphertext decrypt, and unsupported configurations

Jira

  • DSPX-3326

Testing

  • go test ./... from lib/ocrypto
  • go test ./internal/security/... from service
  • go test ./... from sdk
  • go test ./... from otdfctl
  • golangci-lint run from lib/ocrypto
  • GOFIPS140=v1.0.0 GODEBUG=fips140=only go test github.com/opentdf/platform/lib/ocrypto -run 'TestCreateAesGcm|TestNewAESGCM|TestDecrypt|TestAESProtectedKey'

Notes

  • The full strict-FIPS package commands now get past AES-GCM construction and expose separate existing strict-FIPS failures around RSA-OAEP SHA-1 usage and short HMAC test keys.

Summary by CodeRabbit

  • Refactor

    • AES‑GCM handling now uses randomized nonces internally and simplifies encrypt/decrypt flows.
    • APIs that allowed callers to supply IVs were removed; unsupported configurations now return an error.
  • Bug Fixes

    • Decryption now correctly interprets embedded nonces and validates IV/tag sizes to prevent misuse.
  • Tests

    • Test suite updated for in-place encryption, nonce/ciphertext length checks, and unsupported auth‑tag rejection.

Review Change Stack

Signed-off-by: strantalis <strantalis@virtru.com>
@strantalis strantalis requested a review from a team as a code owner May 21, 2026 15:53
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR migrates AES-GCM encryption/decryption to use Go's cipher.NewGCMWithRandomNonce (embedded nonces), removes caller-managed IV encryption APIs, updates asymmetric wrappers and protected-key validation, and adapts tests to the new sealed-output format.

Changes

AES-GCM random nonce migration

Layer / File(s) Summary
AES-GCM core API redesign
lib/ocrypto/aes_gcm.go
Adds ErrUnsupportedAESGCMConfiguration; rewrites Encrypt, EncryptInPlace, and Decrypt to use cipher.NewGCMWithRandomNonce with nil nonce parameters and embedded nonces; removes caller-IV APIs and updates minimum-length checks.
ProtectedKey AES-GCM validation and delegation
lib/ocrypto/protected_key.go
Validates IV length (GcmStandardNonceSize) and requires tagSize == aes.BlockSize, concatenates iv+body into a sealed buffer and delegates to k.aesGcm.Decrypt(sealed); returns ErrUnsupportedAESGCMConfiguration for unsupported tag sizes.
Asymmetric wrappers update
lib/ocrypto/asym_encryption.go, lib/ocrypto/asym_decryption.go
ECEncryptor.Encrypt and ECDecryptor.DecryptWithEphemeralKey now use cipher.NewGCMWithRandomNonce and gcm.Seal/Open(nil, nil, ...), removing manual nonce generation/splitting and adjusting ciphertext length checks.
Tests updated for embedded-nonce behavior
lib/ocrypto/aes_gcm_test.go, lib/ocrypto/protected_key_test.go
Add aes.BlockSize assertions, replace IV-driven tests with fixed-key roundtrip and EncryptInPlace validation, and add a test asserting DecryptAESGCM rejects unsupported auth tag sizes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through nonces, once prefixed and long,
Now Go hides the nonce so the seal stays strong.
Fewer bytes to juggle, fewer callers to mind,
A cleaner decrypt, a simpler grind.
The rabbit nods—safe crypto, neatly aligned.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title 'fix(core): Uses strict FIPS AES-GCM' accurately describes the main change: switching AES-GCM implementation to use Go's strict-FIPS-compatible random-nonce approach across multiple encryption/decryption helpers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dspx-3326-fips-aes-gcm

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 and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request updates the OpenTDF AES-GCM cryptographic helpers to align with strict FIPS 140-3 requirements by migrating to Go's internal random-nonce AEAD. The changes enforce safer defaults by restricting caller-managed IVs and non-standard tag sizes, while maintaining backward compatibility for standard TDF data and EC wrapping. These modifications ensure the platform remains compliant when running in restricted FIPS-only environments.

Highlights

  • Strict FIPS Compliance: Updated AES-GCM helpers to utilize Go's strict-FIPS-compatible cipher.NewGCMWithRandomNonce AEAD implementation.
  • Security Hardening: Explicitly rejected caller-managed IV encryption and non-standard GCM tag sizes to ensure compliance with strict FIPS requirements.
  • Test Suite Updates: Refactored AES-GCM tests to validate standard round-trip operations, split nonce/ciphertext decryption, and handle unsupported configurations.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


In FIPS we trust to keep the key, With random nonces, safe and free. No caller IVs, no custom tag, Just standard crypto, never lag.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request transitions the ocrypto package to use cipher.NewGCMWithRandomNonce for AES-GCM, enforcing internally generated nonces for FIPS compliance and deprecating caller-managed IV encryption. The review identifies a critical bug in EncryptInPlace where aliasing the destination buffer with the plaintext causes data corruption due to the prepended nonce. Further feedback recommends strengthening ciphertext length validation in Decrypt and optimizing DecryptWithIVAndTagSize by using the standard cipher.NewGCM to avoid unnecessary memory allocations during decryption.

Comment thread lib/ocrypto/aes_gcm.go Outdated
Comment thread lib/ocrypto/aes_gcm.go Outdated
Comment thread lib/ocrypto/aes_gcm.go Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 190.779849ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 98.970689ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 458.16896ms
Throughput 218.26 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 46.380160898s
Average Latency 461.181542ms
Throughput 107.80 requests/second

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ocrypto/aes_gcm_test.go`:
- Around line 91-119: The test TestCreateAESGcm_EncryptInPlace currently never
hits the in-place code path because append([]byte{}, plainText...) yields
cap==len; add an additional test case that constructs a pre-sized buffer (e.g.
buf := make([]byte, len(plainText),
len(plainText)+GcmStandardNonceSize+aes.BlockSize)), copy( buf, plainText ),
then call aesGcm.EncryptInPlace(buf) so gcm.Seal uses the existing backing array
and the in-place code path (and potential alias.InexactOverlap) is exercised;
keep the existing non-in-place case for coverage and assert nonce length,
ciphertext length, and successful DecryptWithIVAndTagSize on the in-place result
just like the current assertions.

In `@lib/ocrypto/aes_gcm.go`:
- Around line 87-98: The len(data) < GcmStandardNonceSize guard in
AesGcm.DecryptWithTagSize duplicates the same check performed in AesGcm.Decrypt
(and is therefore dead); either remove that check from DecryptWithTagSize, or
replace it with a stricter pre-check (len(data) < GcmStandardNonceSize +
aes.BlockSize) so clearly truncated inputs return ErrInvalidCiphertext instead
of allowing gcm.Open to produce a wrapped error. Update DecryptWithTagSize
accordingly and keep references to Decrypt, GcmStandardNonceSize, aes.BlockSize,
ErrInvalidCiphertext, and gcm.Open in your reasoning while editing.
- Around line 48-57: EncryptInPlace currently passes a slice backed by the
caller's plaintext (data[:0]) as the Seal destination which can create an
inexact-overlap panic when the plaintext has spare capacity; change
EncryptInPlace (method on AesGcm) to allocate a fresh destination buffer (e.g.
dst := make([]byte, 0, GcmStandardNonceSize+len(data)+gcm.Overhead())) and call
gcm.Seal(dst, nil, data, nil) instead of gcm.Seal(data[:0], ...), then extract
nonce and cipherText from the returned sealed slice as before
(sealed[:GcmStandardNonceSize], sealed[GcmStandardNonceSize:]) to avoid
overlapping input/output.

In `@lib/ocrypto/asym_decryption.go`:
- Around line 180-192: Move the cheap input-length precondition check so it runs
before constructing the AEAD: check len(data) <
GcmStandardNonceSize+aes.BlockSize at the top of the block (before calling
cipher.NewGCMWithRandomNonce(block)), return the same "ciphertext too short"
error if true, and only then call cipher.NewGCMWithRandomNonce(block) and use
gcm.Open; this preserves behavior but avoids constructing the AEAD for obviously
malformed inputs (see GcmStandardNonceSize, aes.BlockSize,
cipher.NewGCMWithRandomNonce, and gcm.Open).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ecf36cc8-ecde-4300-8b90-409af9d22b2b

📥 Commits

Reviewing files that changed from the base of the PR and between baa1403 and de9fd0b.

📒 Files selected for processing (4)
  • lib/ocrypto/aes_gcm.go
  • lib/ocrypto/aes_gcm_test.go
  • lib/ocrypto/asym_decryption.go
  • lib/ocrypto/asym_encryption.go

Comment thread lib/ocrypto/aes_gcm_test.go
Comment thread lib/ocrypto/aes_gcm.go
Comment thread lib/ocrypto/aes_gcm.go Outdated
Comment thread lib/ocrypto/asym_decryption.go
Signed-off-by: strantalis <strantalis@virtru.com>
@strantalis strantalis changed the title fix(ocrypto): use strict FIPS AES-GCM fix(core): use strict FIPS AES-GCM May 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 191.952689ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 98.161813ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 407.705207ms
Throughput 245.28 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.81109819s
Average Latency 445.711104ms
Throughput 111.58 requests/second

@strantalis strantalis marked this pull request as draft May 21, 2026 17:11
Signed-off-by: strantalis <strantalis@virtru.com>
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 167.628947ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 106.398779ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 412.09332ms
Throughput 242.66 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.546872441s
Average Latency 433.443852ms
Throughput 114.82 requests/second

@strantalis strantalis marked this pull request as ready for review May 21, 2026 21:07
Signed-off-by: strantalis <strantalis@virtru.com>
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 192.600796ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.836051ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 424.770691ms
Throughput 235.42 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 46.740063553s
Average Latency 465.347853ms
Throughput 106.97 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

@strantalis
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@dmihalcik-virtru dmihalcik-virtru changed the title fix(core): use strict FIPS AES-GCM fix(core): Uses strict FIPS AES-GCM May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants