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 \ 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/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) }) diff --git a/tpm2/crypto.go b/tpm2/crypto.go index 2f12e0a3..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 @@ -166,14 +164,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/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/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 } 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, }, }, ),