Skip to content

test(ocrypto): add failing tests for EC P-384/P-521 ECDH bug#3071

Draft
pflynn-virtru wants to merge 4 commits intomainfrom
fix/3070-ec-p384-p521-ecdh-tests
Draft

test(ocrypto): add failing tests for EC P-384/P-521 ECDH bug#3071
pflynn-virtru wants to merge 4 commits intomainfrom
fix/3070-ec-p384-p521-ecdh-tests

Conversation

@pflynn-virtru
Copy link
Member

@pflynn-virtru pflynn-virtru commented Feb 12, 2026

Summary

TDD red-phase tests that reproduce #3070 — covers both the UncompressECPubKey P-256 hardcoding bug and the FormatAlg P-521 typo ("ec:secp512r1" instead of "ec:secp521r1").

ocrypto / security layer tests

Four tests across two layers, exercising three distinct code paths:

Test Layer Code path exercised
TestUncompressECPubKey_CurvePreservation lib/ocrypto Direct unit test of buggy function — asserts curve name, X/Y coordinates, ECDH usability
TestDecryptWithCompressedEphemeralKey lib/ocrypto DecryptWithEphemeralKey compressed-key fallback path
TestBasicManager_Decrypt_ECAllCurves service/security BasicManager.DecryptNewECDecryptorDecryptWithEphemeralKey with compressed ephemeral keys
TestBasicManager_DeriveKey_ECAllCurves service/security BasicManager.DeriveKeyUncompressECPubKey (separate call site, fails at x509.MarshalPKIXPublicKey)

All P-256 subtests pass. P-384 and P-521 subtests fail as expected with the production error signatures:

  • Decrypt path: ecdh failure: ecdsa: invalid public key
  • DeriveKey path: x509: invalid elliptic curve public key

FormatAlg round-trip tests

Two tests that prove FormatAlg (server) and getKasKeyAlg (SDK) share the same ocrypto.KeyType contract:

Test Layer What it proves
TestFormatAlg_RoundTrip service/pkg/db FormatAlg(enum) → reverse-map lookup → must equal original enum. EC-P521 fails (typo returns "ec:secp512r1")
TestFormatAlg_GetKasKeyAlg_RoundTrip sdk formatAlg(enum)getKasKeyAlg(result) → must equal original enum. All pass (SDK is correct)

Refs: #3070

Test plan

  • P-256 subtests pass in all four ocrypto/security tests
  • P-384 and P-521 subtests fail with expected errors
  • SDK FormatAlg round-trip passes (SDK formatAlg is correct)
  • Service FormatAlg round-trip EC-P521 fails (catches the typo)
  • Apply fixes and verify all subtests pass

🤖 Generated with Claude Code

@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 372.577427ms
Throughput 268.40 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.538010441s
Average Latency 403.879066ms
Throughput 123.34 requests/second

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @pflynn-virtru, 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 introduces a set of failing tests designed to expose a critical bug in the handling of Elliptic Curve (EC) public keys, specifically when uncompressing P-384 and P-521 curves. By adding these targeted tests, the PR establishes a clear, reproducible scenario for the bug, paving the way for a robust fix that ensures correct curve parameter usage during key operations.

Highlights

  • Bug Reproduction Tests: Two new tests have been added to specifically reproduce issue EC P-384/P-521 ECDH failure: hardcoded P-256 curve in UncompressECPubKey #3070, which concerns UncompressECPubKey incorrectly hardcoding elliptic.P256() instead of using the provided curve parameter.
  • Curve Preservation Test: TestUncompressECPubKey_CurvePreservation verifies that an EC public key's curve matches the input curve after a compress/uncompress round-trip, ensuring the correct curve is maintained.
  • End-to-End Decryption Test: TestDecryptWithCompressedEphemeralKey simulates an end-to-end encryption and decryption flow using compressed ephemeral keys, mirroring the rewrap code path where the bug manifests.
  • Expected Failures: The P-384 and P-521 subtests within these new tests are intentionally failing, demonstrating the bug in its 'red-phase' of Test-Driven Development (TDD) before the fix is applied.

🧠 New Feature in Public Preview: You can now enable Memory 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.

Changelog
  • lib/ocrypto/asym_encrypt_decrypt_test.go
  • lib/ocrypto/ec_key_pair_test.go
    • Added github.com/stretchr/testify/assert import.
    • Implemented TestUncompressECPubKey_CurvePreservation to verify that UncompressECPubKey correctly preserves the elliptic curve type for different ECC modes.
Activity
  • The pull request introduces new tests that are expected to fail for P-384 and P-521 curves, indicating a TDD red-phase for bug EC P-384/P-521 ECDH failure: hardcoded P-256 curve in UncompressECPubKey #3070.
  • The PR description includes a test plan outlining steps to verify both passing and failing subtests, and to confirm the fix.
  • The PR was generated with Claude Code, indicating AI assistance in its creation.
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 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 counter productive. 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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


A curve, a key, a hidden flaw, Tests now reveal what's out of law. P-384, P-521, they cry, For proper curve, or else they die.

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
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 adds two well-designed failing tests, TestUncompressECPubKey_CurvePreservation and TestDecryptWithCompressedEphemeralKey, to expose a bug in UncompressECPubKey related to handling different elliptic curves. The tests are clear, use a table-driven approach effectively, and are a great example of Test-Driven Development by establishing the "red" phase. The overall changes are excellent for identifying and documenting the bug. I have one minor suggestion to improve conciseness in one of the new tests.

Comment on lines +126 to +127
curve, err := GetECCurveFromECCMode(tc.mode)
require.NoError(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The curve object can be retrieved directly from the keyPair that was just created, which already holds the curve information. This avoids a redundant call to GetECCurveFromECCMode and makes the test setup slightly more concise.

Suggested change
curve, err := GetECCurveFromECCMode(tc.mode)
require.NoError(t, err)
curve := keyPair.PrivateKey.Curve

@pflynn-virtru pflynn-virtru force-pushed the fix/3070-ec-p384-p521-ecdh-tests branch from e4cd4dc to d518915 Compare February 12, 2026 15:35
@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 369.942374ms
Throughput 270.31 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.563860333s
Average Latency 393.471866ms
Throughput 126.38 requests/second

@pflynn-virtru pflynn-virtru force-pushed the fix/3070-ec-p384-p521-ecdh-tests branch from d518915 to 07c992e Compare February 12, 2026 16:04
@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 370.234333ms
Throughput 270.10 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.357914372s
Average Latency 390.897515ms
Throughput 127.04 requests/second

Add two TDD red-phase tests that reproduce issue #3070 where
UncompressECPubKey hardcodes elliptic.P256() instead of using
the curve parameter, breaking P-384 and P-521 decrypt operations.

- TestUncompressECPubKey_CurvePreservation: verifies the returned
  key's curve matches the input curve after compress/uncompress
- TestDecryptWithCompressedEphemeralKey: end-to-end encrypt/decrypt
  with compressed ephemeral keys simulating the rewrap code path

P-256 subtests pass; P-384 and P-521 subtests fail as expected.

Closes #3070

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paul Flynn <pflynn@virtru.com>
@pflynn-virtru pflynn-virtru force-pushed the fix/3070-ec-p384-p521-ecdh-tests branch from 07c992e to 6ed8791 Compare February 12, 2026 16:11
@github-actions
Copy link
Contributor

@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 381.417638ms
Throughput 262.18 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 38.908100093s
Average Latency 387.473233ms
Throughput 128.51 requests/second

Add two new test functions that reproduce issue #3070 in the
BasicManager layer (service/internal/security):

- TestBasicManager_Decrypt_ECAllCurves: Tests Decrypt() for P-256,
  P-384, and P-521 with **compressed** ephemeral keys. The existing
  EC decrypt test only covers P-256 and passes the ephemeral key in
  DER format (which bypasses UncompressECPubKey entirely).

- TestBasicManager_DeriveKey_ECAllCurves: Tests DeriveKey() for all
  three curves. DeriveKey calls UncompressECPubKey directly (not via
  DecryptWithEphemeralKey), exercising a separate code path affected
  by the hardcoded P-256 curve.

Both tests: P-256 passes, P-384/P-521 fail with the exact production
errors — "ecdsa: invalid public key" and "x509: invalid elliptic
curve public key" respectively.

Refs: #3070

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paul Flynn <pflynn@virtru.com>
@github-actions
Copy link
Contributor

X-Test Failure Report

opentdfplatformNZSLGY.dockerbuild
cukes-report
✅ js-v0.9.0

@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 393.537294ms
Throughput 254.11 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.382145814s
Average Latency 392.707391ms
Throughput 126.96 requests/second

- Drop sdk/ec_wrap_test.go: mirrored rewrap.go internals
- Drop TestECRewrapKeyGenerateAllCurves: doesn't exercise the bug
  (PEM-based ECDH never calls UncompressECPubKey)
- Simplify TestDecryptWithCompressedEphemeralKey: use FromPublicPEMWithSalt
  for encryption instead of manual ECDH+HKDF+AES-GCM; extract
  compressEphemeralDER helper
- Simplify TestBasicManager_DeriveKey_ECAllCurves: generate client key
  via NewECKeyPair instead of marshal→parse round-trip; drop ecdh.Curve
  from test table

Refs: #3070

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paul Flynn <pflynn@virtru.com>
@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 368.010319ms
Throughput 271.73 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 38.932174803s
Average Latency 387.958959ms
Throughput 128.43 requests/second

@pflynn-virtru pflynn-virtru force-pushed the fix/3070-ec-p384-p521-ecdh-tests branch from 70319e5 to 5e4daad Compare February 12, 2026 18:36
@github-actions
Copy link
Contributor

X-Test Failure Report

@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 363.517372ms
Throughput 275.09 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.301964401s
Average Latency 391.225184ms
Throughput 127.22 requests/second

@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 372.808908ms
Throughput 268.23 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.854983947s
Average Latency 396.626414ms
Throughput 125.45 requests/second

Add round-trip tests that prove FormatAlg and getKasKeyAlg share the
same ocrypto.KeyType contract:

- service/pkg/db: FormatAlg(enum) → reverse-map lookup → assert original
  enum. EC-P521 subtest fails (typo: "ec:secp512r1" vs "ec:secp521r1").
- sdk: formatAlg(enum) → getKasKeyAlg(result) → assert original enum.
  All pass (SDK formatAlg is correct).

Refs: #3070

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paul Flynn <pflynn@virtru.com>
@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 375.876889ms
Throughput 266.04 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.828186747s
Average Latency 396.975865ms
Throughput 125.54 requests/second

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.

1 participant