Skip to content

EC P-384/P-521 ECDH failure: hardcoded P-256 curve in UncompressECPubKey #3070

Description

@darkhonor

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

  1. Deploy DSP with ec_tdf_enabled: true and key_management: true
  2. 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
  3. Set the P-384 key as the base key:
    tructl $CREDS policy kas-registry key base set \
      --key test-p384 --kas "$KAS_URI"
  4. Encrypt a file using EC wrapping:
    tructl $CREDS encrypt --wrapping-key-algorithm ec:secp384r1 \
      -o testfile.txt testfile.txt
  5. 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

  1. Client encrypts TDF → generates ephemeral EC key pair on P-384 → stores PEM in manifest
  2. KAS rewrap handler parses PEM → compresses ephemeral public key to 49 bytes (P-384 compressed point)
  3. DecryptWithEphemeralKey() receives compressed bytes → x509.ParsePKIXPublicKey() fails (not DER) → falls through to UncompressECPubKey()
  4. UncompressECPubKey(P384, compressedBytes) correctly decompresses X,Y on P-384 curve
  5. Constructs ecdsa.PublicKey{Curve: P256(), X: ..., Y: ...} — P-384 coordinates tagged as P-256
  6. Go's (*ecdsa.PublicKey).ECDH() validates point is on P-256 → fails because P-384 coordinates are not on P-256
  7. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions