From db02a4664547a7d1d82650e28bd84e5eb4b243ef Mon Sep 17 00:00:00 2001 From: Patrick O'Doherty Date: Wed, 24 Sep 2025 10:20:41 -0700 Subject: [PATCH] types/key: Update HardwareAttestationPublic representation (#17233) Sidestep cmd/viewer incompatibility hiccups with HardwareAttestationPublic type due to its *ecdsa.PublicKey inner member by serializing the key to a byte slice instead. Updates tailscale/corp#31269 Signed-off-by: Patrick O'Doherty --- types/key/hardware_attestation.go | 55 +++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/types/key/hardware_attestation.go b/types/key/hardware_attestation.go index ead077a5d..ac3914ab2 100644 --- a/types/key/hardware_attestation.go +++ b/types/key/hardware_attestation.go @@ -7,6 +7,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/elliptic" + "crypto/subtle" "encoding/json" "fmt" "io" @@ -18,11 +19,13 @@ var ErrUnsupported = fmt.Errorf("key type not supported on this platform") const hardwareAttestPublicHexPrefix = "hwattestpub:" +const pubkeyLength = 65 // uncompressed P-256 + // HardwareAttestationKey describes a hardware-backed key that is used to // identify a node. Implementation details will // vary based on the platform in use (SecureEnclave for Apple, TPM for // Windows/Linux, Android Hardware-backed Keystore). -// This key can only be marshalled and unmarshalled on the same machine. +// This key can only be marshalled and unmarshaled on the same machine. type HardwareAttestationKey interface { crypto.Signer json.Marshaler @@ -43,25 +46,41 @@ func HardwareAttestationPublicFromPlatformKey(k HardwareAttestationKey) Hardware if !ok { panic("hardware attestation key is not ECDSA") } - return HardwareAttestationPublic{k: ecdsaPub} + bytes, err := ecdsaPub.Bytes() + if err != nil { + panic(err) + } + if len(bytes) != pubkeyLength { + panic("hardware attestation key is not uncompressed ECDSA P-256") + } + var ecdsaPubArr [pubkeyLength]byte + copy(ecdsaPubArr[:], bytes) + return HardwareAttestationPublic{k: ecdsaPubArr} } // HardwareAttestationPublic is the public key counterpart to // HardwareAttestationKey. type HardwareAttestationPublic struct { - k *ecdsa.PublicKey + k [pubkeyLength]byte } -func (k HardwareAttestationPublic) Equal(o HardwareAttestationPublic) bool { - if k.k == nil || o.k == nil { - return k.k == o.k +func (k *HardwareAttestationPublic) Clone() *HardwareAttestationPublic { + if k == nil { + return nil } - return k.k.X.Cmp(o.k.X) == 0 && k.k.Y.Cmp(o.k.Y) == 0 && k.k.Curve == o.k.Curve + var out HardwareAttestationPublic + copy(out.k[:], k.k[:]) + return &out +} + +func (k HardwareAttestationPublic) Equal(o HardwareAttestationPublic) bool { + return subtle.ConstantTimeCompare(k.k[:], o.k[:]) == 1 } // IsZero reports whether k is the zero value. func (k HardwareAttestationPublic) IsZero() bool { - return k.k == nil + var zero [pubkeyLength]byte + return k.k == zero } // String returns the hex-encoded public key with a type prefix. @@ -75,7 +94,7 @@ func (k HardwareAttestationPublic) String() string { // MarshalText implements encoding.TextMarshaler. func (k HardwareAttestationPublic) MarshalText() ([]byte, error) { - if k.k == nil { + if k.IsZero() { return nil, nil } return k.AppendText(nil) @@ -89,30 +108,30 @@ func (k *HardwareAttestationPublic) UnmarshalText(b []byte) error { return nil } - kb := make([]byte, 65) + kb := make([]byte, pubkeyLength) if err := parseHex(kb, mem.B(b), mem.S(hardwareAttestPublicHexPrefix)); err != nil { return err } - pk, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), kb) + _, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), kb) if err != nil { return err } - k.k = pk + copy(k.k[:], kb) return nil } func (k HardwareAttestationPublic) AppendText(dst []byte) ([]byte, error) { - b, err := k.k.Bytes() - if err != nil { - return nil, err - } - return appendHexKey(dst, hardwareAttestPublicHexPrefix, b), nil + return appendHexKey(dst, hardwareAttestPublicHexPrefix, k.k[:]), nil } // Verifier returns the ECDSA public key for verifying signatures made by k. func (k HardwareAttestationPublic) Verifier() *ecdsa.PublicKey { - return k.k + pk, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), k.k[:]) + if err != nil { + panic(err) + } + return pk } // emptyHardwareAttestationKey is a function that returns an empty