From 48959eb67ac0883cb01681301968f7d304bea39b Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 7 Jan 2026 19:45:08 +0100 Subject: [PATCH 1/4] crypto: deprecate ECCPoint, implement ECCBytes The Go crypto APIs for ECC is moving away from using big.Int to pure byte arrays. Deprecate ECCPoint in favour of ECCBytes. Removed the duplicate internal getXY function. Signed-off-by: Morten Linderud --- tpm2/crypto.go | 18 ++++++++++++++++-- tpm2/labeled_kem_ecc.go | 16 ++-------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tpm2/crypto.go b/tpm2/crypto.go index 2f12e0a3..66bce1a6 100644 --- a/tpm2/crypto.go +++ b/tpm2/crypto.go @@ -166,14 +166,28 @@ func ECDHPub(parms *TPMSECCParms, pub *TPMSECCPoint) (*ecdh.PublicKey, error) { } // ECCPoint returns an uncompressed ECC Point +// Deprecated: [big.Int] is being deprecated in the ECC libraries. Use ECCBytes instead. func ECCPoint(pubKey *ecdh.PublicKey) (*big.Int, *big.Int, error) { + x, y, err := ECCBytes(pubKey) + if err != nil { + return nil, nil, err + } + return big.NewInt(0).SetBytes(x), big.NewInt(0).SetBytes(y), nil +} + +// ECCPoints returns an uncompressed ECC Point +func ECCBytes(pubKey *ecdh.PublicKey) ([]byte, []byte, error) { b := pubKey.Bytes() + // 0x04 denotes an uncompressed ECC key + // https://datatracker.ietf.org/doc/rfc5480/ + if len(b) == 0 || b[0] != 0x04 { + return nil, nil, fmt.Errorf("could not decode %x as an uncompressed point", b) + } size, err := elementLength(pubKey.Curve()) if err != nil { return nil, nil, fmt.Errorf("ECCPoint: %w", err) } - return big.NewInt(0).SetBytes(b[1 : size+1]), - big.NewInt(0).SetBytes(b[size+1:]), nil + return b[1 : size+1], b[size+1:], nil } func elementLength(c ecdh.Curve) (int, error) { diff --git a/tpm2/labeled_kem_ecc.go b/tpm2/labeled_kem_ecc.go index 0c36df4d..0587944e 100644 --- a/tpm2/labeled_kem_ecc.go +++ b/tpm2/labeled_kem_ecc.go @@ -3,7 +3,6 @@ package tpm2 import ( "crypto/ecdh" "errors" - "fmt" "io" ) @@ -46,17 +45,6 @@ func importECCEncapsulationKey(pub *TPMTPublic) (*eccKey, error) { }, nil } -// getXY gets the big-endian X/Y coordinates as full-length buffers. -func getXY(pub *ecdh.PublicKey) ([]byte, []byte, error) { - // Check and strip the leading 0x04 byte, which indicates an uncompressed ECC point. - rawPub := pub.Bytes() - if len(rawPub) == 0 || rawPub[0] != 0x04 { - return nil, nil, fmt.Errorf("%w: could not decode %x as an uncompressed point", ErrBadEphemeralKey, rawPub) - } - rawPub = rawPub[1:] - return rawPub[:len(rawPub)/2], rawPub[len(rawPub)/2:], nil -} - // Encapsulate implements LabeledEncapsulationKey. func (pub *eccKey) Encapsulate(random io.Reader, label string) (secret []byte, ciphertext []byte, err error) { ephemeralPriv, err := pub.eccPub.Curve().GenerateKey(random) @@ -72,11 +60,11 @@ func (pub *eccKey) encapsulateDerandomized(ephPrivate *ecdh.PrivateKey, label st if err != nil { return nil, nil, err } - pubX, _, err := getXY(pub.eccPub) + pubX, _, err := ECCBytes(pub.eccPub) if err != nil { return nil, nil, err } - ephX, ephY, err := getXY(ephPrivate.PublicKey()) + ephX, ephY, err := ECCBytes(ephPrivate.PublicKey()) if err != nil { return nil, nil, err } From 4bd1cb09547e2247b9b0479c484d7c61d00201d3 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Wed, 7 Jan 2026 19:47:58 +0100 Subject: [PATCH 2/4] tpm2: remove X/Y/D access usage in ECC keys Go 1.26 is going to deprecate the X/Y/D fields. Move all usage of this into the new byte handling and new functions. Signed-off-by: Morten Linderud --- go.mod | 2 +- tpm2/crypto.go | 24 +++++++++++------------- tpm2/crypto_test.go | 14 +++++++++----- tpm2/test/ecdh_test.go | 6 +++--- tpm2/test/import_test.go | 12 +++++++++--- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 04ef7316..946c04c3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/google/go-tpm -go 1.22 +go 1.25.0 require ( github.com/google/go-cmp v0.5.9 diff --git a/tpm2/crypto.go b/tpm2/crypto.go index 66bce1a6..32b3b42f 100644 --- a/tpm2/crypto.go +++ b/tpm2/crypto.go @@ -58,14 +58,11 @@ func Priv(public TPMTPublic, sensitive TPMTSensitive) (crypto.PrivateKey, error) return nil, fmt.Errorf("failed to retrieve the ECC") } - D := new(big.Int).SetBytes(d.Buffer) - - ecdsaKey := &ecdsa.PrivateKey{ - PublicKey: *publicKey, - D: D, + privateKey, err = ecdsa.ParseRawPrivateKey(publicKey.Curve, d.Buffer) + if err != nil { + return nil, fmt.Errorf("failed parsing ECC private key: %v", err) } - privateKey = ecdsaKey default: return nil, fmt.Errorf("unsupported public key type: %v", public.Type) } @@ -145,18 +142,19 @@ func ECDSAPub(parms *TPMSECCParms, pub *TPMSECCPoint) (*ecdsa.PublicKey, error) return nil, fmt.Errorf("unknown curve: %v", parms.CurveID) } - pubKey := ecdsa.PublicKey{ - Curve: c, - X: big.NewInt(0).SetBytes(pub.X.Buffer), - Y: big.NewInt(0).SetBytes(pub.Y.Buffer), - } + // Create a buffer with the uncompressed ECC public key + data := make([]byte, 0, len(pub.X.Buffer)+len(pub.Y.Buffer)+1) + // 0x04 denotes an uncompressed ECC key + // https://datatracker.ietf.org/doc/rfc5480/ + data = append(data, 0x04) + data = append(data, pub.X.Buffer...) + data = append(data, pub.Y.Buffer...) - return &pubKey, nil + return ecdsa.ParseUncompressedPublicKey(c, data) } // ECDHPub converts a TPM ECC public key into one recognized by the ecdh package func ECDHPub(parms *TPMSECCParms, pub *TPMSECCPoint) (*ecdh.PublicKey, error) { - pubKey, err := ECDSAPub(parms, pub) if err != nil { return nil, err diff --git a/tpm2/crypto_test.go b/tpm2/crypto_test.go index ac33bbdb..37f7eade 100644 --- a/tpm2/crypto_test.go +++ b/tpm2/crypto_test.go @@ -40,6 +40,8 @@ func TestPriv(t *testing.T) { rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048) ecdsaKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + ecdhKey, _ := ecdsaKey.ECDH() + eccPointX, eccPointY, _ := ECCBytes(ecdhKey.PublicKey()) seed := make([]byte, crypto.SHA256.New().Size()) rand.Read(seed) @@ -119,7 +121,7 @@ func TestPriv(t *testing.T) { }, Sensitive: NewTPMUSensitiveComposite( TPMAlgECC, - &TPM2BECCParameter{Buffer: ecdsaKey.D.FillBytes(make([]byte, len(ecdsaKey.D.Bytes())))}, + &TPM2BECCParameter{Buffer: ecdhKey.Bytes()}, ), }, public: TPMTPublic{ @@ -157,10 +159,10 @@ func TestPriv(t *testing.T) { TPMAlgECC, &TPMSECCPoint{ X: TPM2BECCParameter{ - Buffer: ecdsaKey.X.Bytes(), + Buffer: eccPointX, }, Y: TPM2BECCParameter{ - Buffer: ecdsaKey.Y.Bytes(), + Buffer: eccPointY, }, }, ), @@ -236,6 +238,8 @@ func TestPub(t *testing.T) { rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048) ecdsaKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + ecdhKey, _ := ecdsaKey.ECDH() + X, Y, _ := ECCBytes(ecdhKey.PublicKey()) tests := map[string]struct { public TPMTPublic @@ -321,10 +325,10 @@ func TestPub(t *testing.T) { TPMAlgECC, &TPMSECCPoint{ X: TPM2BECCParameter{ - Buffer: ecdsaKey.X.Bytes(), + Buffer: X, }, Y: TPM2BECCParameter{ - Buffer: ecdsaKey.Y.Bytes(), + Buffer: Y, }, }, ), diff --git a/tpm2/test/ecdh_test.go b/tpm2/test/ecdh_test.go index d2bbce8f..b899f804 100644 --- a/tpm2/test/ecdh_test.go +++ b/tpm2/test/ecdh_test.go @@ -85,13 +85,13 @@ func TestECDH(t *testing.T) { if err != nil { t.Fatalf("could not create the SW key: %v", err) } - x, y, err := ECCPoint(swPriv.PublicKey()) + x, y, err := ECCBytes(swPriv.PublicKey()) if err != nil { t.Fatalf("could not get SW key point: %v", err) } swPub := TPMSECCPoint{ - X: TPM2BECCParameter{Buffer: x.FillBytes(make([]byte, 32))}, - Y: TPM2BECCParameter{Buffer: y.FillBytes(make([]byte, 32))}, + X: TPM2BECCParameter{Buffer: x}, + Y: TPM2BECCParameter{Buffer: y}, } // Calculate Z based on the SW priv * TPM pub diff --git a/tpm2/test/import_test.go b/tpm2/test/import_test.go index 5745f4fc..087b3afa 100644 --- a/tpm2/test/import_test.go +++ b/tpm2/test/import_test.go @@ -42,12 +42,18 @@ func TestCleartextImport(t *testing.T) { if err != nil { t.Fatalf("failed to generate ecdsa key: %v", err) } + ecdhPK, _ := pk.ECDH() + + X, Y, err := ECCBytes(ecdhPK.PublicKey()) + if err != nil { + t.Fatalf("failed to get ecc bytes from key: %v", err) + } sens2B := Marshal(TPMTSensitive{ SensitiveType: TPMAlgECC, Sensitive: NewTPMUSensitiveComposite( TPMAlgECC, - &TPM2BECCParameter{Buffer: pk.D.FillBytes(make([]byte, 32))}, + &TPM2BECCParameter{Buffer: ecdhPK.Bytes()}, ), }) @@ -87,10 +93,10 @@ func TestCleartextImport(t *testing.T) { TPMAlgECC, &TPMSECCPoint{ X: TPM2BECCParameter{ - Buffer: pk.X.FillBytes(make([]byte, 32)), + Buffer: X, }, Y: TPM2BECCParameter{ - Buffer: pk.Y.FillBytes(make([]byte, 32)), + Buffer: Y, }, }, ), From 75ef75c02d9bacad8b1024ff85cbbded0d582356 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Fri, 9 Jan 2026 22:57:15 +0100 Subject: [PATCH 3/4] legacy/tpm: deprecate legacy ecdsa fields Signed-off-by: Morten Linderud --- legacy/tpm2/encoding_test.go | 19 ++++++++------ legacy/tpm2/structures.go | 48 +++++++++++++++++++++++++++++++---- legacy/tpm2/test/tpm2_test.go | 48 ++++++++++++++++++++++++++++------- 3 files changed, 93 insertions(+), 22 deletions(-) diff --git a/legacy/tpm2/encoding_test.go b/legacy/tpm2/encoding_test.go index 517769f2..c475dabd 100644 --- a/legacy/tpm2/encoding_test.go +++ b/legacy/tpm2/encoding_test.go @@ -17,8 +17,7 @@ package tpm2 import ( "bytes" "crypto" - "crypto/ecdsa" - "crypto/elliptic" + "crypto/ecdh" "crypto/rand" "encoding/hex" "reflect" @@ -402,7 +401,11 @@ func TestEncodeTPMLPCRSelection(t *testing.T) { } func TestECCParamsEncodeDecode(t *testing.T) { - pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + pk, err := ecdh.P256().GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + X, Y, err := ECCBytes(pk.PublicKey()) if err != nil { t.Fatal(err) } @@ -412,7 +415,7 @@ func TestECCParamsEncodeDecode(t *testing.T) { Hash: AlgSHA1, }, CurveID: CurveNISTP256, - Point: ECPoint{XRaw: pk.PublicKey.X.Bytes(), YRaw: pk.PublicKey.Y.Bytes()}, + Point: ECPoint{XRaw: X, YRaw: Y}, } buf, err := params.encode() @@ -424,11 +427,11 @@ func TestECCParamsEncodeDecode(t *testing.T) { t.Fatalf("decodeECCParams: %v", err) } - if params.Point.X().Cmp(pk.PublicKey.X) != 0 { - t.Fatalf("the deserialized X(big.Int) from ECCParams didn't match the X(Int) generated by ecdsa. got: %+v\nwant: %+v", params.Point.X(), pk.PublicKey.X) + if !bytes.Equal(params.Point.X().Bytes(), X) { + t.Fatalf("the deserialized X(bytes[]) from ECCParams didn't match the X(bytes[]) generated by ecdsa. got: %+v\nwant: %+v", params.Point.XRaw, X) } - if params.Point.Y().Cmp(pk.PublicKey.Y) != 0 { - t.Fatalf("the deserialized Y(big.Int) from ECCParams didn't match the Y(Int) generated by ecdsa. got: %+v\nwant: %+v", params.Point.Y(), pk.PublicKey.Y) + if !bytes.Equal(params.Point.Y().Bytes(), Y) { + t.Fatalf("the deserialized Y(bytes[]) from ECCParams didn't match the Y(bytes[]) generated by ecdsa. got: %+v\nwant: %+v", params.Point.YRaw, Y) } if params.Point.X().Cmp(got.Point.X()) != 0 { t.Fatalf("the deserialized X(big.Int) from ECCParams after encode/decode didn't match the X(big.Int) before. got: %+v\nwant: %+v", got.Point.X(), params.Point.X()) diff --git a/legacy/tpm2/structures.go b/legacy/tpm2/structures.go index 6df9f7f0..704b354e 100644 --- a/legacy/tpm2/structures.go +++ b/legacy/tpm2/structures.go @@ -17,6 +17,7 @@ package tpm2 import ( "bytes" "crypto" + "crypto/ecdh" "crypto/ecdsa" "crypto/rsa" "encoding/binary" @@ -113,11 +114,16 @@ func (p Public) Key() (crypto.PublicKey, error) { if !ok { return nil, fmt.Errorf("can't map TPM EC curve ID 0x%x to Go elliptic.Curve value", p.ECCParameters.CurveID) } - pubKey = &ecdsa.PublicKey{ - X: p.ECCParameters.Point.X(), - Y: p.ECCParameters.Point.Y(), - Curve: curve, - } + + // Create a buffer with the uncompressed ECC public key + data := make([]byte, 0, len(p.ECCParameters.Point.XRaw)+len(p.ECCParameters.Point.YRaw)+1) + // 0x04 denotes an uncompressed ECC key + // https://datatracker.ietf.org/doc/rfc5480/ + data = append(data, 0x04) + data = append(data, p.ECCParameters.Point.XRaw...) + data = append(data, p.ECCParameters.Point.YRaw...) + + return ecdsa.ParseUncompressedPublicKey(curve, data) default: return nil, fmt.Errorf("unsupported public key type 0x%x", p.Type) } @@ -265,6 +271,38 @@ func decodeRSAParams(in *bytes.Buffer) (*RSAParams, error) { return ¶ms, nil } +// Code from tpm2/crypto.go +func elementLength(c ecdh.Curve) (int, error) { + switch c { + case ecdh.P256(): + // crypto/internal/nistec/fiat.p256ElementLen + return 32, nil + case ecdh.P384(): + // crypto/internal/nistec/fiat.p384ElementLen + return 48, nil + case ecdh.P521(): + // crypto/internal/nistec/fiat.p521ElementLen + return 66, nil + default: + return 0, fmt.Errorf("unknown element length for curve: %v", c) + } +} + +// ECCBytes returns an uncompressed ECC Point +func ECCBytes(pubKey *ecdh.PublicKey) ([]byte, []byte, error) { + b := pubKey.Bytes() + // 0x04 denotes an uncompressed ECC key + // https://datatracker.ietf.org/doc/rfc5480/ + if len(b) == 0 || b[0] != 0x04 { + return nil, nil, fmt.Errorf("could not decode %x as an uncompressed point", b) + } + size, err := elementLength(pubKey.Curve()) + if err != nil { + return nil, nil, fmt.Errorf("ECCPoint: %w", err) + } + return b[1 : size+1], b[size+1:], nil +} + // ECCParams represents parameters of an ECC key pair: // both the TPMS_ECC_PARMS and the TPMS_ECC_POINT. // diff --git a/legacy/tpm2/test/tpm2_test.go b/legacy/tpm2/test/tpm2_test.go index a5dd3b25..2fb329ee 100644 --- a/legacy/tpm2/test/tpm2_test.go +++ b/legacy/tpm2/test/tpm2_test.go @@ -586,6 +586,14 @@ func TestLoadExternalPublicKey(t *testing.T) { if err != nil { t.Fatal(err) } + ecdhKey, err := pk.ECDH() + if err != nil { + t.Fatal(err) + } + X, Y, err := ECCBytes(ecdhKey.PublicKey()) + if err != nil { + t.Fatal(err) + } public := Public{ Type: AlgECC, NameAlg: AlgSHA1, @@ -596,12 +604,12 @@ func TestLoadExternalPublicKey(t *testing.T) { Hash: AlgSHA1, }, CurveID: CurveNISTP256, - Point: ECPoint{XRaw: pk.PublicKey.X.Bytes(), YRaw: pk.PublicKey.Y.Bytes()}, + Point: ECPoint{XRaw: X, YRaw: Y}, }, } private := Private{ Type: AlgECC, - Sensitive: pk.D.Bytes(), + Sensitive: ecdhKey.Bytes(), } run(t, public, private) }) @@ -813,6 +821,14 @@ func TestCertifyExternalKey(t *testing.T) { if err != nil { t.Fatal(err) } + ecdhPK, err := pk.ECDH() + if err != nil { + t.Fatal(err) + } + X, Y, err := ECCBytes(ecdhPK.PublicKey()) + if err != nil { + t.Fatal(err) + } public := Public{ Type: AlgECC, NameAlg: AlgSHA1, @@ -823,12 +839,12 @@ func TestCertifyExternalKey(t *testing.T) { Hash: AlgSHA1, }, CurveID: CurveNISTP256, - Point: ECPoint{XRaw: pk.PublicKey.X.Bytes(), YRaw: pk.PublicKey.Y.Bytes()}, + Point: ECPoint{XRaw: X, YRaw: Y}, }, } private := Private{ Type: AlgECC, - Sensitive: pk.D.Bytes(), + Sensitive: ecdhPK.Bytes(), } run(t, public, private) }) @@ -1463,9 +1479,18 @@ func TestCreateAndCertifyCreationECC(t *testing.T) { t.Logf("Public: %v", p) } - var pkEcdsa ecdsa.PublicKey var hsh hash.Hash - pkEcdsa = ecdsa.PublicKey{Curve: elliptic.P256(), X: p.ECCParameters.Point.X(), Y: p.ECCParameters.Point.Y()} + pk, err := p.Key() + if err != nil { + t.Fatal(err) + } + + // Convert to ecdsa.PublicKey + pkEcdsa, ok := pk.(*ecdsa.PublicKey) + if !ok { + t.Fatalf("Failed getting *ecdsa.PublicKey") + } + signHash, err := p.ECCParameters.Sign.Hash.Hash() if err != nil { t.Fatalf("Hash failed: %v", err) @@ -1473,7 +1498,7 @@ func TestCreateAndCertifyCreationECC(t *testing.T) { hsh = signHash.New() hsh.Write(attestation) - if !ecdsa.Verify(&pkEcdsa, hsh.Sum(nil), signature.ECC.R, signature.ECC.S) { + if !ecdsa.Verify(pkEcdsa, hsh.Sum(nil), signature.ECC.R, signature.ECC.S) { t.Fatalf("Verify failed") } } @@ -1771,6 +1796,11 @@ func TestReadPublicKey(t *testing.T) { if err != nil { t.Fatal(err) } + ecdhPK, err := pk.ECDH() + if err != nil { + t.Fatal(err) + } + X, Y, err := ECCBytes(ecdhPK.PublicKey()) public := Public{ Type: AlgECC, NameAlg: AlgSHA1, @@ -1781,12 +1811,12 @@ func TestReadPublicKey(t *testing.T) { Hash: AlgSHA1, }, CurveID: CurveNISTP256, - Point: ECPoint{XRaw: pk.PublicKey.X.Bytes(), YRaw: pk.PublicKey.Y.Bytes()}, + Point: ECPoint{XRaw: X, YRaw: Y}, }, } private := Private{ Type: AlgECC, - Sensitive: pk.D.Bytes(), + Sensitive: ecdhPK.Bytes(), } run(t, public, private, &pk.PublicKey) }) From 9f1f3e60e4d888f54b9d034d32ac8f0337348b03 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sat, 10 Jan 2026 00:10:09 +0100 Subject: [PATCH 4/4] Dockerfile: update to 1.25 Signed-off-by: Morten Linderud --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d236165f..f4073e4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22 +FROM golang:1.25 # We need OpenSSL headers to build the simulator RUN apt-get update && apt-get install -y \ libssl-dev \