Summary
EC-based TDF decrypt (rewrap) operations fail with "ecdh failure: ecdsa: invalid public key" for all EC keys using curves other than P-256 (secp256r1). This includes P-384 (secp384r1) and P-521 (secp521r1). The root cause is a hardcoded elliptic.P256() in UncompressECPubKey() that ignores the actual curve parameter passed to the function.
Environment
- DSP Version: v2.7.4-dev-fips (Helm chart data-security-platform v0.9.1)
- Configuration:
ec_tdf_enabled: true
key_management: true
- Root key: AES-256 (32-byte hex), delivered via YAML config
- Keys managed via
tructl policy kas-registry key (both import and create --mode local)
Steps to Reproduce
- Deploy DSP with
ec_tdf_enabled: true and key_management: true
- Create an EC key on P-384 (secp384r1):
tructl $CREDS policy kas-registry key create \
--kas "$KAS_URI" --key-id test-p384 \
--algorithm ec:secp384r1 --mode local
- Set the P-384 key as the base key:
tructl $CREDS policy kas-registry key base set \
--key test-p384 --kas "$KAS_URI"
- Encrypt a file using EC wrapping:
tructl $CREDS encrypt --wrapping-key-algorithm ec:secp384r1 \
-o testfile.txt testfile.txt
- Attempt to decrypt:
tructl $CREDS decrypt -o testfile-decrypted.txt testfile.txt.tdf
Result: Decrypt fails.
Client error:
ERROR Failed to decrypt file: reader.WriteTo failed: splitKey.unable to
reconstruct split key: kao unwrap failed for split
{https://kas.example.com/kas }: invalid_argument: request error
rpc error: code = InvalidArgument desc = bad request
Server-side log:
{"level":"error","msg":"failed to decrypt EC key","error":"failed to decrypt with ephemeral key: ecdh failure: ecdsa: invalid public key"}
Expected Behavior
EC decrypt should succeed for P-384 and P-521 keys, just as it does for P-256.
Actual Behavior
EC decrypt fails for all non-P-256 curves. The failure occurs in the ECDH operation after successful AES-GCM key unwrap. This affects both imported keys and server-generated keys (--mode local), confirming the issue is not related to key import format.
Test Matrix
| Wrapping Algorithm |
Key Curve |
Encrypt |
Decrypt |
Status |
ec:secp256r1 |
P-256 |
PASS |
PASS |
Working |
ec:secp384r1 |
P-384 |
PASS |
FAIL |
ecdh failure: ecdsa: invalid public key |
rsa:2048 |
RSA |
PASS |
PASS |
Working |
Root Cause
The bug is in lib/ocrypto/ec_key_pair.go in the UncompressECPubKey function:
func UncompressECPubKey(curve elliptic.Curve, compressedPubKey []byte) (*ecdsa.PublicKey, error) {
x, y := elliptic.UnmarshalCompressed(curve, compressedPubKey)
if x == nil {
return nil, errors.New("failed to unmarshal compressed public key")
}
ephemeralECDSAPublicKey := &ecdsa.PublicKey{
Curve: elliptic.P256(), // <--- BUG: Hardcoded to P-256
X: x,
Y: y,
}
return ephemeralECDSAPublicKey, nil
}
The Curve field is hardcoded to elliptic.P256() instead of using the curve parameter passed into the function.
Failure Sequence
- Client encrypts TDF → generates ephemeral EC key pair on P-384 → stores PEM in manifest
- KAS rewrap handler parses PEM → compresses ephemeral public key to 49 bytes (P-384 compressed point)
DecryptWithEphemeralKey() receives compressed bytes → x509.ParsePKIXPublicKey() fails (not DER) → falls through to UncompressECPubKey()
UncompressECPubKey(P384, compressedBytes) correctly decompresses X,Y on P-384 curve
- Constructs
ecdsa.PublicKey{Curve: P256(), X: ..., Y: ...} — P-384 coordinates tagged as P-256
- Go's
(*ecdsa.PublicKey).ECDH() validates point is on P-256 → fails because P-384 coordinates are not on P-256
- Returns
"ecdsa: invalid public key", wrapped as "ecdh failure: ecdsa: invalid public key"
Proposed Fix
One-line change in lib/ocrypto/ec_key_pair.go:
ephemeralECDSAPublicKey := &ecdsa.PublicKey{
Curve: curve, // Use the passed curve parameter, not hardcoded P-256
X: x,
Y: y,
}
Impact
- P-384 and P-521 EC keys are completely non-functional for TDF encrypt/decrypt when
ec_tdf_enabled: true and key_management: true
- Significant for federal/DoD deployments where NIST SP 800-131A Rev 2 and CNSA 2.0 require P-384 as the minimum EC curve
- P-256 works as a temporary workaround but does not meet P-384 requirements for higher security profiles
- RSA operations are unaffected
Related Code Paths
| File |
Function |
Role |
lib/ocrypto/ec_key_pair.go |
UncompressECPubKey() |
Bug location |
lib/ocrypto/asym_decryption.go |
ECDecryptor.DecryptWithEphemeralKey() |
Calls UncompressECPubKey on fallback path |
service/kas/access/rewrap.go |
verifyRewrapRequests() |
Compresses ephemeral key before passing to decrypt |
service/internal/security/basic_manager.go |
Decrypt() |
Key-managed decrypt path |
sdk/tdf.go |
generateWrapKeyWithEC() |
Client-side ephemeral key generation (PEM) |
Summary
EC-based TDF decrypt (rewrap) operations fail with
"ecdh failure: ecdsa: invalid public key"for all EC keys using curves other than P-256 (secp256r1). This includes P-384 (secp384r1) and P-521 (secp521r1). The root cause is a hardcodedelliptic.P256()inUncompressECPubKey()that ignores the actualcurveparameter passed to the function.Environment
ec_tdf_enabled: truekey_management: truetructl policy kas-registry key(bothimportandcreate --mode local)Steps to Reproduce
ec_tdf_enabled: trueandkey_management: truetructl $CREDS encrypt --wrapping-key-algorithm ec:secp384r1 \ -o testfile.txt testfile.txttructl $CREDS decrypt -o testfile-decrypted.txt testfile.txt.tdfResult: Decrypt fails.
Client error:
Server-side log:
{"level":"error","msg":"failed to decrypt EC key","error":"failed to decrypt with ephemeral key: ecdh failure: ecdsa: invalid public key"}Expected Behavior
EC decrypt should succeed for P-384 and P-521 keys, just as it does for P-256.
Actual Behavior
EC decrypt fails for all non-P-256 curves. The failure occurs in the ECDH operation after successful AES-GCM key unwrap. This affects both imported keys and server-generated keys (
--mode local), confirming the issue is not related to key import format.Test Matrix
ec:secp256r1ec:secp384r1ecdh failure: ecdsa: invalid public keyrsa:2048Root Cause
The bug is in
lib/ocrypto/ec_key_pair.goin theUncompressECPubKeyfunction:The
Curvefield is hardcoded toelliptic.P256()instead of using thecurveparameter passed into the function.Failure Sequence
DecryptWithEphemeralKey()receives compressed bytes →x509.ParsePKIXPublicKey()fails (not DER) → falls through toUncompressECPubKey()UncompressECPubKey(P384, compressedBytes)correctly decompresses X,Y on P-384 curveecdsa.PublicKey{Curve: P256(), X: ..., Y: ...}— P-384 coordinates tagged as P-256(*ecdsa.PublicKey).ECDH()validates point is on P-256 → fails because P-384 coordinates are not on P-256"ecdsa: invalid public key", wrapped as"ecdh failure: ecdsa: invalid public key"Proposed Fix
One-line change in
lib/ocrypto/ec_key_pair.go:Impact
ec_tdf_enabled: trueandkey_management: trueRelated Code Paths
lib/ocrypto/ec_key_pair.goUncompressECPubKey()lib/ocrypto/asym_decryption.goECDecryptor.DecryptWithEphemeralKey()UncompressECPubKeyon fallback pathservice/kas/access/rewrap.goverifyRewrapRequests()service/internal/security/basic_manager.goDecrypt()sdk/tdf.gogenerateWrapKeyWithEC()