Skip to content

feat(core): Deprecate ECKeyPair in favor of asym_*#3293

Open
dmihalcik-virtru wants to merge 6 commits intomainfrom
DSPX-1089-deprecate-eckp
Open

feat(core): Deprecate ECKeyPair in favor of asym_*#3293
dmihalcik-virtru wants to merge 6 commits intomainfrom
DSPX-1089-deprecate-eckp

Conversation

@dmihalcik-virtru
Copy link
Copy Markdown
Member

@dmihalcik-virtru dmihalcik-virtru commented Apr 10, 2026

Proposed Changes

  • Deprecates ECKeyPair, which is largely redundant with AsymDecrypt and AsymEncrypt
  • This focuses interfaces on use, not on implemenation details
  • Further, you often will only have a public part, and only use that, so the metaphor is just confusing.

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

Summary by CodeRabbit

Release Notes

  • New Features

    • Added flexible private key decryptor factory function with automatic key type detection
    • Enhanced EC encryption with improved ephemeral key serialization and ECIES-style wrapping
    • Extended key pair interfaces with public key derivation and key type identification methods
  • Deprecations

    • Marked legacy key pair and EC key operation APIs as deprecated; users should transition to the new PrivateKeyDecryptor interface
  • Bug Fixes

    • Improved error handling for ephemeral key generation in EC encryption
    • Enhanced key type detection with better error reporting for unsupported key types
  • Tests

    • Added comprehensive unit test coverage for new key constructor and decryption methods

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the cryptographic key handling APIs across the ocrypto package and dependent SDK modules. It replaces the KeyPair abstraction with separate PrivateKeyDecryptor and PublicKeyEncryptor interfaces, introduces factory functions for constructing key decryptors, adds shared PEM encoding utilities, and updates EC ephemeral key handling to export public keys via a dedicated method.

Changes

Cohort / File(s) Summary
Gitignore
.gitignore
Added trailing newline to .claude/ ignore rule.
OCrypto Interface & Decryption
lib/ocrypto/asym_decryption.go
Extended PrivateKeyDecryptor interface with PrivateKeyInPemFormat(), Public(), and KeyType() methods. Added NewPrivateKeyDecryptor factory and NewECPrivateKey constructor. Implemented interface methods for AsymDecryption and ECDecryptor. Refactored ephemeral key parsing into parseEphemeralPublicKey helper. Updated convCurve to return error instead of silently returning nil.
OCrypto Decryption Tests
lib/ocrypto/asym_decryption_test.go
Added comprehensive test suite validating key construction, type reporting, PEM serialization round-trips, and nil-key error handling for RSA and EC decryptors.
OCrypto Encryption & Ephemeral Keys
lib/ocrypto/asym_encryption.go
Enhanced error handling in newECIES for ephemeral key generation. Modified ECEncryptor.PublicKeyInPemFormat() to export recipient public key instead of ephemeral key. Added EphemeralPublicKeyInPemFormat() method to export ephemeral key separately.
OCrypto EC Utilities
lib/ocrypto/ec_key_pair.go
Added shared PEM formatting helpers (privateKeyInPemFormat, publicKeyInPemFormat, curveFromECCMode, keyTypeFromECDHCurve). Refactored ECKeyPair to delegate PEM operations to helpers. Added Decrypt(), Public(), KeyType(), and DeriveSharedKey() methods. Updated GetKeyType() to return dynamic key type. Marked KeyPair and related functions as deprecated.
OCrypto RSA Key Pair
lib/ocrypto/rsa_key_pair.go
Refactored PrivateKeyInPemFormat() and PublicKeyInPemFormat() to delegate to shared helpers. Added Decrypt(), Public(), and KeyType() methods. Updated GetKeyType() to return dynamic key type instead of fixed RSA2048Key.
OCrypto Tests
lib/ocrypto/ec_key_pair_test.go, lib/ocrypto/ec_decrypt_compressed_test.go
Updated EC key pair tests to use NewECPrivateKey and Public() instead of NewECKeyPair. Simplified test assertions using new assertion helpers. Removed manual ECDH derivation logic in rewrap test.
SDK Codegen
sdk/codegen/runner/generate.go
Changed generateInterfaceType to use fmt.Fprintf instead of builder.WriteString(fmt.Sprintf(...)) for writing formatted strings.
SDK TDF Core
sdk/tdf.go, sdk/tdf_config.go
Changed Reader.kasSessionKey and TDFReaderConfig.kasSessionKey type from ocrypto.KeyPair to ocrypto.PrivateKeyDecryptor. Updated WithSessionKeyType to use NewPrivateKeyDecryptor factory. Refactored generateWrapKeyWithEC to use Encrypt() API instead of manual ECDH/HKDF/AES-GCM.
SDK TDF Tests
sdk/tdf_test.go, sdk/kas_client_test.go
Updated EC test flows to use new ocrypto APIs. Changed ephemeral key export to use EphemeralPublicKeyInPemFormat(). Removed manual key derivation logic. Updated processECResponse test calls to accept refactored parameters.
SDK KAS Client
sdk/kas_client.go
Changed sessionKey type from ocrypto.KeyPair to ocrypto.PrivateKeyDecryptor. Refactored EC handling to use ECDecryptor.DecryptWithEphemeralKey() instead of manual AES-GCM. Updated RSA handling to accept decryptor interface directly.
SDK Key Access
sdk/experimental/tdf/key_access.go, sdk/experimental/tdf/key_access_test.go
Updated wrapKeyWithPublicKey to parse KAS public key once and pass parsed object to wrappers. Changed EC wrapping to use Encrypt() API and EphemeralPublicKeyInPemFormat(). Updated test helper to construct EC private key via NewECPrivateKey and derive public key via Public().
Service Security Managers
service/internal/security/basic_manager.go, service/internal/security/basic_manager_test.go, service/internal/security/in_process_provider.go
Changed EC decryption to type-assert decryptor to ECDecryptor interface. Marked DeriveKey methods as deprecated and replaced with stub returning "unsupported operation" error. Updated test helper to return separate private decryptor and public encryptor. Removed test case for DeriveKey.
Service KAS
service/kas/access/rewrap.go
Changed EC session public key serialization to use EphemeralPublicKeyInPemFormat() instead of PublicKeyInPemFormat().
Service Trust
service/trust/key_manager.go
Added documentation clarifications and marked DeriveKey method as deprecated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop, hop! The keys have rearranged,
From pairs that held both sides entwined,
To separate decryptors neatly changed,
Where ephemeral secrets are redesigned.
Encryption sparkles, PEM helpers shine—
A cryptographic garden, rebuilt fine!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(core): Deprecate ECKeyPair in favor of asym_*' clearly and specifically describes the main change—deprecating ECKeyPair in favor of asymmetric encryption/decryption APIs, which aligns with the substantial refactoring across multiple files.

✏️ 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 DSPX-1089-deprecate-eckp

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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.

@github-actions github-actions bot added comp:sdk A software development kit, including library, for client applications and inter-service communicati comp:kas Key Access Server comp:lib:ocrypto size/m labels Apr 10, 2026
@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 refactors the cryptographic key management system by deprecating the ECKeyPair struct and introducing a more abstract PrivateKeyDecryptor interface. This change streamlines how private keys are handled across different algorithms (RSA and EC), focusing on a consistent API for key operations rather than specific implementation types. It enhances flexibility, especially for scenarios involving public-only key usage, and centralizes key generation and PEM encoding logic, leading to a cleaner and more maintainable codebase.

Highlights

  • Deprecation of ECKeyPair: The ECKeyPair struct has been deprecated in favor of new, more flexible interfaces, aligning with a focus on usage rather than implementation details.
  • Introduction of PrivateKeyDecryptor Interface: A new PrivateKeyDecryptor interface has been introduced, providing a unified way to handle private key operations such as PEM formatting, public key derivation, key type identification, and shared key derivation for both RSA and EC keys.
  • Refactored Key Management: Key generation, PEM encoding, and shared key derivation logic have been refactored to utilize the new PrivateKeyDecryptor and PublicKeyEncryptor interfaces, centralizing common cryptographic operations and improving consistency.
  • SDK and Service Integration: The SDK and various service components have been updated to consume the new PrivateKeyDecryptor interface, ensuring that all key-related operations leverage the standardized API.

🧠 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.

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.


Old key pairs now must fade, New interfaces take their place, Decryption's path is made.

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 introduces the PrivateKeyDecryptor interface to unify RSA and EC private key operations, such as decryption and shared secret derivation, across the ocrypto package and SDK. It refactors RsaKeyPair, ECKeyPair, and KASClient to implement this interface while deprecating legacy functions. Feedback suggests that several nil checks in the KeyType methods are redundant and should be removed to simplify the code and avoid hiding initialization issues.

Comment on lines +161 to +163
if asymDecryption.PrivateKey == nil {
return KeyType("rsa:[unknown]")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The check for asymDecryption.PrivateKey == nil is redundant here because PrivateKey is a field of the struct and the method is called on an instance. If the instance is initialized, this field should be checked at the point of creation or usage, not inside every getter method. This adds unnecessary complexity.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah, that would make sense.. unfortunately FromRSA and the corresponding SDK WithSession...RSA options don't return error so it would be hard to wire that in at the moment

Comment on lines +230 to +232
if e.sk == nil {
return KeyType("ec:[unknown]")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the RSA implementation, checking for e.sk == nil inside the KeyType() method is defensive programming that might hide initialization issues. It is better to ensure the struct is properly initialized upon creation.

Comment on lines +250 to +252
if keyPair.PrivateKey == nil {
return KeyType("ec:[unknown]")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Checking for keyPair.PrivateKey == nil in KeyType() is redundant if the struct is properly initialized. Consider removing this check to simplify the method.

Comment on lines +64 to +66
if keyPair.privateKey == nil {
return KeyType("rsa:[unknown]")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The check for keyPair.privateKey == nil is redundant. If the RsaKeyPair struct is properly initialized, this field should be valid. Removing this check simplifies the code.

@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 180.391028ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 423.614297ms
Throughput 236.06 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.275696322s
Average Latency 410.605051ms
Throughput 121.14 requests/second

@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 195.997865ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 399.973892ms
Throughput 250.02 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.794970893s
Average Latency 395.202366ms
Throughput 125.64 requests/second

dmihalcik-virtru and others added 2 commits April 14, 2026 12:11
- Remove DeriveSharedKey from PrivateKeyDecryptor interface, eliminating
  false mutual-exclusion between RSA and EC implementations
- Replace SDK encrypt-side DeriveSharedKey with FromPublicPEMWithSalt+Encrypt
  in generateWrapKeyWithEC (tdf.go)
- Replace SDK decrypt-side DeriveSharedKey with DecryptWithEphemeralKey
  in handleECKeyResponse/processECResponse (kas_client.go)
- Service-side and experimental XOR callers type-assert to ECDecryptor
  and call DeriveSharedKey on the concrete type directly
- Fix silent failure chain in newECIES, EphemeralKey, and Metadata
- Fix convCurve to return error instead of nil (prevents nil-panic)
- Extract parseEphemeralPublicKey helper to fix nestif lint violation
- Fix ECKeyPair.Decrypt/Public/DeriveSharedKey to use NewECDecryptor
  so TDF salt is propagated correctly through the shim
- Add asym_decryption_test.go with coverage for new interface and methods
- Update ec_key_pair_test.go and ec_decrypt_compressed_test.go to use
  non-deprecated APIs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@dmihalcik-virtru dmihalcik-virtru force-pushed the DSPX-1089-deprecate-eckp branch from 66d1f7d to 08055b2 Compare April 14, 2026 16:12
@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 196.407073ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 404.879425ms
Throughput 246.99 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.142066379s
Average Latency 399.563177ms
Throughput 124.56 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

@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 202.612075ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 404.347861ms
Throughput 247.31 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.825852469s
Average Latency 416.426574ms
Throughput 119.54 requests/second

@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 149.451429ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 390.987148ms
Throughput 255.76 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 38.804898061s
Average Latency 386.655783ms
Throughput 128.85 requests/second

@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 196.97842ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 435.17699ms
Throughput 229.79 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.674189801s
Average Latency 434.825368ms
Throughput 114.48 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

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

See the workflow run for details.

@dmihalcik-virtru dmihalcik-virtru marked this pull request as ready for review April 14, 2026 19:08
@dmihalcik-virtru dmihalcik-virtru requested review from a team as code owners April 14, 2026 19:08
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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/tdf.go (1)

676-686: ⚠️ Potential issue | 🟠 Major

Derive the wrap scheme from the parsed public key, not kasInfo.Algorithm.

Line 677 still trusts metadata to choose the EC/RSA path. If kasInfo.Algorithm is empty or stale while kasInfo.PublicKey is actually EC, this falls into the RSA branch, generateWrapKeyWithRSA will still encrypt with the EC encryptor returned by FromPublicPEM, and the manifest is emitted as wrapped without an EphemeralPublicKey. That key-access object is not rewrappable later.

Please branch on the parsed key/encryptor type instead, or at least fail fast when the PEM type and declared algorithm disagree.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/tdf.go` around lines 676 - 686, The branch decision should be based on
the parsed public key/encryptor type rather than trusting kasInfo.Algorithm;
parse kasInfo.PublicKey (via the same flow that returns an encryptor from
FromPublicPEM) and inspect its key type (EC vs RSA) before choosing
generateWrapKeyWithEC or generateWrapKeyWithRSA, or else return an error if the
parsed key type conflicts with kasInfo.Algorithm; update the logic around
ktype/kasInfo.Algorithm, generateWrapKeyWithEC, generateWrapKeyWithRSA,
FromPublicPEM, and the keyAccess fields (KeyType, WrappedKey,
EphemeralPublicKey) so the manifest correctly records an EphemeralPublicKey for
EC keys and prevents creating non-rewrappable entries when the PEM and declared
algorithm disagree.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/ocrypto/asym_decryption_test.go`:
- Around line 136-153: The TestAsymDecryptionKeyType loop should be converted to
subtests so failures show which RSA size failed: for each bits in {RSA2048Size,
RSA4096Size} call t.Run with a descriptive name (e.g. "RSA2048"/"RSA4096" or
fmt.Sprintf("%d", bits)), capture bits in the closure, and move t.Parallel()
into the subtest body; inside the subtest keep the existing logic
(NewRSAKeyPair, PrivateKeyInPemFormat, FromPrivatePEM, type assertion to
AsymDecryption and the KeyType checks against RSA2048Key/RSA4096Key).

In `@lib/ocrypto/asym_encryption.go`:
- Around line 288-293: Define a small interface (e.g.,
EphemeralPublicKeyExporter) that declares EphemeralPublicKeyInPemFormat()
(string, error) and have ECEncryptor implicitly implement it (the method already
exists), then update the public API to return or accept that interface where
callers need to export the ephemeral PEM instead of requiring a concrete
ocrypto.ECEncryptor; ensure references to PublicKeyEncryptor remain unchanged
unless the API should explicitly include the new interface, and update call
sites to use the new EphemeralPublicKeyExporter interface for PEM export without
downcasting to ECEncryptor.

In `@lib/ocrypto/ec_key_pair.go`:
- Around line 480-488: The two exported helpers (ECPrivateKeyInPemFormat,
ECPublicKeyInPemFormat) pass value-type ecdsa.PrivateKey/ecdsa.PublicKey to
privateKeyInPemFormat which only handles pointer cases, so zero-value structs
bypass nil checks; update privateKeyInPemFormat to add switch cases for the
value types (case ecdsa.PrivateKey: and case ecdsa.PublicKey:) and perform the
same validity checks as for pointers (e.g., for ecdsa.PrivateKey verify key.D !=
nil and for ecdsa.PublicKey verify key.X != nil && key.Y != nil), returning the
same error ("failed to generate PEM formatted private key"/appropriate public
key error) when those fields are nil to prevent falling through to
x509.MarshalPKCS8PrivateKey with a zero-value.

In `@sdk/experimental/tdf/key_access.go`:
- Around line 186-201: The EC wrapper currently returns the non-canonical string
"eccWrapped" from wrapKeyWithEC; update the return to use the canonical key type
"ec-wrapped" so it matches the rest of the codebase (see wrapKeyWithEC in
key_access.go and the canonical uses in sdk/tdf.go and
lib/ocrypto/asym_encryption.go), keeping the other return values and error
handling unchanged.
- Around line 165-182: The code currently chooses the wrapping branch using
pubKeyInfo.Algorithm (ktype) even after parsing the PEM; instead, base the
decision on the parsed kasPublicKey's runtime type: after
ocrypto.FromPublicPEM(...) check if kasPublicKey implements ocrypto.ECEncryptor
(or use a type switch) and call wrapKeyWithEC(ktype, epk, symKey) when it does,
otherwise call wrapKeyWithRSA(kasPublicKey, symKey); update error paths to
report a clear mismatch if the parsed key cannot perform the expected operations
and remove reliance on pubKeyInfo.Algorithm for branching.

In `@sdk/kas_client.go`:
- Around line 196-208: In handleECKeyResponse, explicitly validate that
response.GetSessionPublicKey() is not empty before calling pem.Decode so you can
return a clearer error; check the returned string from
kas.RewrapResponse.GetSessionPublicKey(), if it's empty return a descriptive
error like "empty KAS session public key" and only then call pem.Decode,
preserving the existing session key type assertion (k.sessionKey as
ocrypto.ECDecryptor) and the subsequent call to k.processECResponse with
block.Bytes.

---

Outside diff comments:
In `@sdk/tdf.go`:
- Around line 676-686: The branch decision should be based on the parsed public
key/encryptor type rather than trusting kasInfo.Algorithm; parse
kasInfo.PublicKey (via the same flow that returns an encryptor from
FromPublicPEM) and inspect its key type (EC vs RSA) before choosing
generateWrapKeyWithEC or generateWrapKeyWithRSA, or else return an error if the
parsed key type conflicts with kasInfo.Algorithm; update the logic around
ktype/kasInfo.Algorithm, generateWrapKeyWithEC, generateWrapKeyWithRSA,
FromPublicPEM, and the keyAccess fields (KeyType, WrappedKey,
EphemeralPublicKey) so the manifest correctly records an EphemeralPublicKey for
EC keys and prevents creating non-rewrappable entries when the PEM and declared
algorithm disagree.
🪄 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: 0a7022c7-f77c-4a72-8811-b1965976ce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7fae2d7 and 0a1cba2.

📒 Files selected for processing (21)
  • .gitignore
  • lib/ocrypto/asym_decryption.go
  • lib/ocrypto/asym_decryption_test.go
  • lib/ocrypto/asym_encryption.go
  • lib/ocrypto/ec_decrypt_compressed_test.go
  • lib/ocrypto/ec_key_pair.go
  • lib/ocrypto/ec_key_pair_test.go
  • lib/ocrypto/rsa_key_pair.go
  • sdk/codegen/runner/generate.go
  • sdk/experimental/tdf/key_access.go
  • sdk/experimental/tdf/key_access_test.go
  • sdk/kas_client.go
  • sdk/kas_client_test.go
  • sdk/tdf.go
  • sdk/tdf_config.go
  • sdk/tdf_test.go
  • service/internal/security/basic_manager.go
  • service/internal/security/basic_manager_test.go
  • service/internal/security/in_process_provider.go
  • service/kas/access/rewrap.go
  • service/trust/key_manager.go

Comment on lines +136 to +153
func TestAsymDecryptionKeyType(t *testing.T) {
t.Parallel()

for _, bits := range []int{RSA2048Size, RSA4096Size} {
kp, err := NewRSAKeyPair(bits)
require.NoError(t, err)
privPEM, err := kp.PrivateKeyInPemFormat()
require.NoError(t, err)
d, err := FromPrivatePEM(privPEM)
require.NoError(t, err)
ad, ok := d.(AsymDecryption)
require.True(t, ok)
if bits == RSA2048Size {
require.Equal(t, RSA2048Key, ad.KeyType())
} else {
require.Equal(t, RSA4096Key, ad.KeyType())
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using subtests for each RSA key size to improve test diagnostics.

The loop iterates over multiple RSA key sizes but doesn't wrap each iteration in t.Run(). This is inconsistent with the other tests in this file and makes it harder to identify which key size failed if there's an assertion error.

♻️ Proposed fix to add subtests
 func TestAsymDecryptionKeyType(t *testing.T) {
 	t.Parallel()

-	for _, bits := range []int{RSA2048Size, RSA4096Size} {
+	sizes := []struct {
+		bits int
+		kt   KeyType
+	}{
+		{RSA2048Size, RSA2048Key},
+		{RSA4096Size, RSA4096Key},
+	}
+
+	for _, tc := range sizes {
+		t.Run(string(tc.kt), func(t *testing.T) {
+			t.Parallel()
+			kp, err := NewRSAKeyPair(tc.bits)
+			require.NoError(t, err)
+			privPEM, err := kp.PrivateKeyInPemFormat()
+			require.NoError(t, err)
+			d, err := FromPrivatePEM(privPEM)
+			require.NoError(t, err)
+			ad, ok := d.(AsymDecryption)
+			require.True(t, ok)
+			require.Equal(t, tc.kt, ad.KeyType())
+		})
+	}
-		kp, err := NewRSAKeyPair(bits)
-		require.NoError(t, err)
-		privPEM, err := kp.PrivateKeyInPemFormat()
-		require.NoError(t, err)
-		d, err := FromPrivatePEM(privPEM)
-		require.NoError(t, err)
-		ad, ok := d.(AsymDecryption)
-		require.True(t, ok)
-		if bits == RSA2048Size {
-			require.Equal(t, RSA2048Key, ad.KeyType())
-		} else {
-			require.Equal(t, RSA4096Key, ad.KeyType())
-		}
-	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestAsymDecryptionKeyType(t *testing.T) {
t.Parallel()
for _, bits := range []int{RSA2048Size, RSA4096Size} {
kp, err := NewRSAKeyPair(bits)
require.NoError(t, err)
privPEM, err := kp.PrivateKeyInPemFormat()
require.NoError(t, err)
d, err := FromPrivatePEM(privPEM)
require.NoError(t, err)
ad, ok := d.(AsymDecryption)
require.True(t, ok)
if bits == RSA2048Size {
require.Equal(t, RSA2048Key, ad.KeyType())
} else {
require.Equal(t, RSA4096Key, ad.KeyType())
}
}
func TestAsymDecryptionKeyType(t *testing.T) {
t.Parallel()
sizes := []struct {
bits int
kt KeyType
}{
{RSA2048Size, RSA2048Key},
{RSA4096Size, RSA4096Key},
}
for _, tc := range sizes {
t.Run(string(tc.kt), func(t *testing.T) {
t.Parallel()
kp, err := NewRSAKeyPair(tc.bits)
require.NoError(t, err)
privPEM, err := kp.PrivateKeyInPemFormat()
require.NoError(t, err)
d, err := FromPrivatePEM(privPEM)
require.NoError(t, err)
ad, ok := d.(AsymDecryption)
require.True(t, ok)
require.Equal(t, tc.kt, ad.KeyType())
})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ocrypto/asym_decryption_test.go` around lines 136 - 153, The
TestAsymDecryptionKeyType loop should be converted to subtests so failures show
which RSA size failed: for each bits in {RSA2048Size, RSA4096Size} call t.Run
with a descriptive name (e.g. "RSA2048"/"RSA4096" or fmt.Sprintf("%d", bits)),
capture bits in the closure, and move t.Parallel() into the subtest body; inside
the subtest keep the existing logic (NewRSAKeyPair, PrivateKeyInPemFormat,
FromPrivatePEM, type assertion to AsymDecryption and the KeyType checks against
RSA2048Key/RSA4096Key).

Comment on lines +288 to +293
func (e ECEncryptor) EphemeralPublicKeyInPemFormat() (string, error) {
if e.ek == nil {
return "", errors.New("failed to generate PEM formatted public key")
}

return publicKeyInPemFormat(e.ek.PublicKey())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Expose this through an interface, not only the concrete ECEncryptor.

Callers now have to downcast PublicKeyEncryptor back to ocrypto.ECEncryptor in order to export the ephemeral PEM. That leaks the concrete implementation back into higher layers and makes the API fragile to future representation changes. A small EC-specific interface for this method would keep the new asym_* surface properly abstracted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ocrypto/asym_encryption.go` around lines 288 - 293, Define a small
interface (e.g., EphemeralPublicKeyExporter) that declares
EphemeralPublicKeyInPemFormat() (string, error) and have ECEncryptor implicitly
implement it (the method already exists), then update the public API to return
or accept that interface where callers need to export the ephemeral PEM instead
of requiring a concrete ocrypto.ECEncryptor; ensure references to
PublicKeyEncryptor remain unchanged unless the API should explicitly include the
new interface, and update call sites to use the new EphemeralPublicKeyExporter
interface for PEM export without downcasting to ECEncryptor.

Comment on lines 480 to 488
// ECPrivateKeyInPemFormat Returns private key in pem format.
func ECPrivateKeyInPemFormat(privateKey ecdsa.PrivateKey) (string, error) {
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return "", fmt.Errorf("x509.MarshalPKCS8PrivateKey failed: %w", err)
}

privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyBytes,
},
)
return string(privateKeyPem), nil
return privateKeyInPemFormat(privateKey)
}

// ECPublicKeyInPemFormat Returns public key in pem format.
func ECPublicKeyInPemFormat(publicKey ecdsa.PublicKey) (string, error) {
pkb, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", fmt.Errorf("x509.MarshalPKIXPublicKey failed: %w", err)
}

publicKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "PUBLIC KEY",
Bytes: pkb,
},
)

return string(publicKeyPem), nil
return publicKeyInPemFormat(publicKey)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Value type passed to privateKeyInPemFormat bypasses nil checks.

ECPrivateKeyInPemFormat and ECPublicKeyInPemFormat accept value types (ecdsa.PrivateKey, ecdsa.PublicKey), but privateKeyInPemFormat only checks for pointer types in its switch. A zero-value struct will fall through to x509.MarshalPKCS8PrivateKey, potentially causing a less informative panic or error instead of the intended "failed to generate PEM formatted private key" message.

🛡️ Proposed fix to handle value types
 // ECPrivateKeyInPemFormat Returns private key in pem format.
 func ECPrivateKeyInPemFormat(privateKey ecdsa.PrivateKey) (string, error) {
-	return privateKeyInPemFormat(privateKey)
+	return privateKeyInPemFormat(&privateKey)
 }

 // ECPublicKeyInPemFormat Returns public key in pem format.
 func ECPublicKeyInPemFormat(publicKey ecdsa.PublicKey) (string, error) {
-	return publicKeyInPemFormat(publicKey)
+	return publicKeyInPemFormat(&publicKey)
 }

Alternatively, add value-type cases to the switch in privateKeyInPemFormat:

case ecdsa.PrivateKey:
    if key.D == nil {
        return "", errors.New("failed to generate PEM formatted private key")
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ocrypto/ec_key_pair.go` around lines 480 - 488, The two exported helpers
(ECPrivateKeyInPemFormat, ECPublicKeyInPemFormat) pass value-type
ecdsa.PrivateKey/ecdsa.PublicKey to privateKeyInPemFormat which only handles
pointer cases, so zero-value structs bypass nil checks; update
privateKeyInPemFormat to add switch cases for the value types (case
ecdsa.PrivateKey: and case ecdsa.PublicKey:) and perform the same validity
checks as for pointers (e.g., for ecdsa.PrivateKey verify key.D != nil and for
ecdsa.PublicKey verify key.X != nil && key.Y != nil), returning the same error
("failed to generate PEM formatted private key"/appropriate public key error)
when those fields are nil to prevent falling through to
x509.MarshalPKCS8PrivateKey with a zero-value.

Comment on lines 165 to 182
// Determine key type based on algorithm
ktype := ocrypto.KeyType(pubKeyInfo.Algorithm)

kasPublicKey, err := ocrypto.FromPublicPEM(pubKeyInfo.PEM)
if err != nil {
return "", "", "", fmt.Errorf("failed to create parse KAS public key: %w", err)
}

if ocrypto.IsECKeyType(ktype) {
// Handle EC key wrapping
return wrapKeyWithEC(ktype, pubKeyInfo.PEM, symKey)
if epk, ok := kasPublicKey.(ocrypto.ECEncryptor); ok {
// Handle EC key wrapping
return wrapKeyWithEC(ktype, epk, symKey)
}
return "", "", "", fmt.Errorf("incorrect key type for %v", ktype)
}
// Handle RSA key wrapping
wrapped, err := wrapKeyWithRSA(pubKeyInfo.PEM, symKey)
wrapped, err := wrapKeyWithRSA(kasPublicKey, symKey)
return wrapped, "wrapped", "", err
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Choose the wrapping flow from kasPublicKey, not pubKeyInfo.Algorithm.

You already parse the PEM on Line 168, but Line 173 still routes by the declared algorithm string. If that field is missing or stale for an EC key, the RSA branch will run, wrapKeyWithRSA will encrypt through the EC encryptor, and no ephemeral public key will be serialized.

🔧 Suggested fix
-	// Determine key type based on algorithm
-	ktype := ocrypto.KeyType(pubKeyInfo.Algorithm)
-
 	kasPublicKey, err := ocrypto.FromPublicPEM(pubKeyInfo.PEM)
 	if err != nil {
 		return "", "", "", fmt.Errorf("failed to create parse KAS public key: %w", err)
 	}
 
-	if ocrypto.IsECKeyType(ktype) {
-		if epk, ok := kasPublicKey.(ocrypto.ECEncryptor); ok {
-			// Handle EC key wrapping
-			return wrapKeyWithEC(ktype, epk, symKey)
-		}
-		return "", "", "", fmt.Errorf("incorrect key type for %v", ktype)
-	}
-	// Handle RSA key wrapping
-	wrapped, err := wrapKeyWithRSA(kasPublicKey, symKey)
-	return wrapped, "wrapped", "", err
+	switch kasPublicKey.Type() {
+	case ocrypto.EC:
+		epk, ok := kasPublicKey.(ocrypto.ECEncryptor)
+		if !ok {
+			return "", "", "", fmt.Errorf("unexpected encryptor type %T", kasPublicKey)
+		}
+		return wrapKeyWithEC(epk.KeyType(), epk, symKey)
+	case ocrypto.RSA:
+		wrapped, err := wrapKeyWithRSA(kasPublicKey, symKey)
+		return wrapped, "wrapped", "", err
+	default:
+		return "", "", "", fmt.Errorf("unsupported KAS public key type: %v", kasPublicKey.KeyType())
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/experimental/tdf/key_access.go` around lines 165 - 182, The code
currently chooses the wrapping branch using pubKeyInfo.Algorithm (ktype) even
after parsing the PEM; instead, base the decision on the parsed kasPublicKey's
runtime type: after ocrypto.FromPublicPEM(...) check if kasPublicKey implements
ocrypto.ECEncryptor (or use a type switch) and call wrapKeyWithEC(ktype, epk,
symKey) when it does, otherwise call wrapKeyWithRSA(kasPublicKey, symKey);
update error paths to report a clear mismatch if the parsed key cannot perform
the expected operations and remove reliance on pubKeyInfo.Algorithm for
branching.

Comment on lines +186 to +201
func wrapKeyWithEC(keyType ocrypto.KeyType, kasPublicKey ocrypto.ECEncryptor, symKey []byte) (string, string, string, error) {
if !ocrypto.IsECKeyType(kasPublicKey.KeyType()) {
return "", "", "", fmt.Errorf("unexpected KAS public key type: %v", kasPublicKey.KeyType())
}

// Get ephemeral private key
ephemeralPrivKey, err := ecKeyPair.PrivateKeyInPemFormat()
wrapped, err := kasPublicKey.Encrypt(symKey)
if err != nil {
return "", "", "", fmt.Errorf("failed to get ephemeral private key: %w", err)
return "", "", "", fmt.Errorf("failed to wrap with %v: %w", keyType, err)
}

// Compute ECDH shared secret
ecdhKey, err := ocrypto.ComputeECDHKey([]byte(ephemeralPrivKey), []byte(kasPublicKeyPEM))
epk, err := kasPublicKey.EphemeralPublicKeyInPemFormat()
if err != nil {
return "", "", "", fmt.Errorf("failed to compute ECDH key: %w", err)
return "", "", "", fmt.Errorf("failed to export ephemeral public key: %w", err)
}

// Derive wrapping key using HKDF
salt := tdfSalt()
wrapKey, err := ocrypto.CalculateHKDF(salt, ecdhKey)
if err != nil {
return "", "", "", fmt.Errorf("failed to derive wrap key: %w", err)
}

// Ensure we have the right length for wrapping, trim if needed, or error if too short
if len(wrapKey) > len(symKey) {
wrapKey = wrapKey[:len(symKey)]
} else if len(wrapKey) < len(symKey) {
return "", "", "", fmt.Errorf("wrap key too short: got %d, expected at least %d",
len(wrapKey), len(symKey))
}

wrapped := make([]byte, len(symKey))
for i := range symKey {
wrapped[i] = symKey[i] ^ wrapKey[i]
}

return string(ocrypto.Base64Encode(wrapped)), "eccWrapped", ephemeralPubKey, nil
return string(ocrypto.Base64Encode(wrapped)), "eccWrapped", epk, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Emit the canonical EC wrapper key type.

Line 201 returns "eccWrapped", but the rest of the codebase serializes EC-wrapped entries as ec-wrapped (sdk/tdf.go Lines 44-45 and lib/ocrypto/asym_encryption.go Lines 25-28). This will make experimental manifests diverge from the main SDK/KAS contract.

🔧 Suggested fix
-	return string(ocrypto.Base64Encode(wrapped)), "eccWrapped", epk, nil
+	return string(ocrypto.Base64Encode(wrapped)), "ec-wrapped", epk, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/experimental/tdf/key_access.go` around lines 186 - 201, The EC wrapper
currently returns the non-canonical string "eccWrapped" from wrapKeyWithEC;
update the return to use the canonical key type "ec-wrapped" so it matches the
rest of the codebase (see wrapKeyWithEC in key_access.go and the canonical uses
in sdk/tdf.go and lib/ocrypto/asym_encryption.go), keeping the other return
values and error handling unchanged.

Comment on lines 196 to +208
func (k *KASClient) handleECKeyResponse(response *kas.RewrapResponse) (map[string][]kaoResult, error) {
kasEphemeralPublicKey := response.GetSessionPublicKey()
clientPrivateKey, err := k.sessionKey.PrivateKeyInPemFormat()
if err != nil {
return nil, fmt.Errorf("failed to get private key: %w", err)
}
ecdhKey, err := ocrypto.ComputeECDHKey([]byte(clientPrivateKey), []byte(kasEphemeralPublicKey))
if err != nil {
return nil, fmt.Errorf("ocrypto.ComputeECDHKey failed: %w", err)
}

digest := sha256.New()
digest.Write([]byte("TDF"))
salt := digest.Sum(nil)
sessionKey, err := ocrypto.CalculateHKDF(salt, ecdhKey)
if err != nil {
return nil, fmt.Errorf("ocrypto.CalculateHKDF failed: %w", err)
kasEphemeralPublicKeyPEM := response.GetSessionPublicKey()
block, _ := pem.Decode([]byte(kasEphemeralPublicKeyPEM))
if block == nil {
return nil, errors.New("failed to decode KAS session public key PEM")
}

aesGcm, err := ocrypto.NewAESGcm(sessionKey)
if err != nil {
return nil, fmt.Errorf("ocrypto.NewAESGcm failed: %w", err)
ecDecryptor, ok := k.sessionKey.(ocrypto.ECDecryptor)
if !ok {
return nil, errors.New("session key is not an EC decryptor")
}

return k.processECResponse(response, aesGcm)
return k.processECResponse(response, ecDecryptor, block.Bytes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding validation for empty session public key.

If response.GetSessionPublicKey() returns an empty string, pem.Decode will return nil for the block, which is caught. However, adding an explicit check for empty input would provide a clearer error message.

💡 Optional: Add explicit empty check
 func (k *KASClient) handleECKeyResponse(response *kas.RewrapResponse) (map[string][]kaoResult, error) {
 	kasEphemeralPublicKeyPEM := response.GetSessionPublicKey()
+	if kasEphemeralPublicKeyPEM == "" {
+		return nil, errors.New("KAS session public key is empty")
+	}
 	block, _ := pem.Decode([]byte(kasEphemeralPublicKeyPEM))
 	if block == nil {
 		return nil, errors.New("failed to decode KAS session public key PEM")
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (k *KASClient) handleECKeyResponse(response *kas.RewrapResponse) (map[string][]kaoResult, error) {
kasEphemeralPublicKey := response.GetSessionPublicKey()
clientPrivateKey, err := k.sessionKey.PrivateKeyInPemFormat()
if err != nil {
return nil, fmt.Errorf("failed to get private key: %w", err)
}
ecdhKey, err := ocrypto.ComputeECDHKey([]byte(clientPrivateKey), []byte(kasEphemeralPublicKey))
if err != nil {
return nil, fmt.Errorf("ocrypto.ComputeECDHKey failed: %w", err)
}
digest := sha256.New()
digest.Write([]byte("TDF"))
salt := digest.Sum(nil)
sessionKey, err := ocrypto.CalculateHKDF(salt, ecdhKey)
if err != nil {
return nil, fmt.Errorf("ocrypto.CalculateHKDF failed: %w", err)
kasEphemeralPublicKeyPEM := response.GetSessionPublicKey()
block, _ := pem.Decode([]byte(kasEphemeralPublicKeyPEM))
if block == nil {
return nil, errors.New("failed to decode KAS session public key PEM")
}
aesGcm, err := ocrypto.NewAESGcm(sessionKey)
if err != nil {
return nil, fmt.Errorf("ocrypto.NewAESGcm failed: %w", err)
ecDecryptor, ok := k.sessionKey.(ocrypto.ECDecryptor)
if !ok {
return nil, errors.New("session key is not an EC decryptor")
}
return k.processECResponse(response, aesGcm)
return k.processECResponse(response, ecDecryptor, block.Bytes)
func (k *KASClient) handleECKeyResponse(response *kas.RewrapResponse) (map[string][]kaoResult, error) {
kasEphemeralPublicKeyPEM := response.GetSessionPublicKey()
if kasEphemeralPublicKeyPEM == "" {
return nil, errors.New("KAS session public key is empty")
}
block, _ := pem.Decode([]byte(kasEphemeralPublicKeyPEM))
if block == nil {
return nil, errors.New("failed to decode KAS session public key PEM")
}
ecDecryptor, ok := k.sessionKey.(ocrypto.ECDecryptor)
if !ok {
return nil, errors.New("session key is not an EC decryptor")
}
return k.processECResponse(response, ecDecryptor, block.Bytes)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/kas_client.go` around lines 196 - 208, In handleECKeyResponse, explicitly
validate that response.GetSessionPublicKey() is not empty before calling
pem.Decode so you can return a clearer error; check the returned string from
kas.RewrapResponse.GetSessionPublicKey(), if it's empty return a descriptive
error like "empty KAS session public key" and only then call pem.Decode,
preserving the existing session key type assertion (k.sessionKey as
ocrypto.ECDecryptor) and the subsequent call to k.processECResponse with
block.Bytes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:kas Key Access Server comp:lib:ocrypto comp:sdk A software development kit, including library, for client applications and inter-service communicati size/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant