mirror of
https://github.com/tailscale/tailscale.git
synced 2025-09-21 05:31:36 +02:00
tailcfg: add HardwareAttestationKey to MapRequest (#17102)
Extend the client state management to generate a hardware attestation key if none exists. Extend MapRequest with HardwareAttestationKey{,Signature} fields that optionally contain the public component of the hardware attestation key and a signature of the node's node key using it. This will be used by control to associate hardware attesation keys with node identities on a TOFU basis. Updates tailscale/corp#31269 Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
This commit is contained in:
parent
17ffa80138
commit
510830ca7a
@ -1360,6 +1360,13 @@ type MapRequest struct {
|
|||||||
NodeKey key.NodePublic
|
NodeKey key.NodePublic
|
||||||
DiscoKey key.DiscoPublic
|
DiscoKey key.DiscoPublic
|
||||||
|
|
||||||
|
// HardwareAttestationKey is the public key of the node's hardware-backed
|
||||||
|
// identity attestation key, if any.
|
||||||
|
HardwareAttestationKey key.HardwareAttestationPublic `json:",omitzero"`
|
||||||
|
// HardwareAttestationKeySignature is the signature of the NodeKey
|
||||||
|
// serialized using MarshalText using its hardware attestation key, if any.
|
||||||
|
HardwareAttestationKeySignature []byte `json:",omitempty"`
|
||||||
|
|
||||||
// Stream is whether the client wants to receive multiple MapResponses over
|
// Stream is whether the client wants to receive multiple MapResponses over
|
||||||
// the same HTTP connection.
|
// the same HTTP connection.
|
||||||
//
|
//
|
||||||
|
@ -5,12 +5,19 @@ package key
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"go4.org/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupported = fmt.Errorf("key type not supported on this platform")
|
var ErrUnsupported = fmt.Errorf("key type not supported on this platform")
|
||||||
|
|
||||||
|
const hardwareAttestPublicHexPrefix = "hwattestpub:"
|
||||||
|
|
||||||
// HardwareAttestationKey describes a hardware-backed key that is used to
|
// HardwareAttestationKey describes a hardware-backed key that is used to
|
||||||
// identify a node. Implementation details will
|
// identify a node. Implementation details will
|
||||||
// vary based on the platform in use (SecureEnclave for Apple, TPM for
|
// vary based on the platform in use (SecureEnclave for Apple, TPM for
|
||||||
@ -20,10 +27,96 @@ type HardwareAttestationKey interface {
|
|||||||
crypto.Signer
|
crypto.Signer
|
||||||
json.Marshaler
|
json.Marshaler
|
||||||
json.Unmarshaler
|
json.Unmarshaler
|
||||||
|
io.Closer
|
||||||
|
Clone() HardwareAttestationKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// HardwareAttestationPublicFromPlatformKey creates a HardwareAttestationPublic
|
||||||
|
// for communicating the public component of the hardware attestation key
|
||||||
|
// with control and other nodes.
|
||||||
|
func HardwareAttestationPublicFromPlatformKey(k HardwareAttestationKey) HardwareAttestationPublic {
|
||||||
|
if k == nil {
|
||||||
|
return HardwareAttestationPublic{}
|
||||||
|
}
|
||||||
|
pub := k.Public()
|
||||||
|
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
panic("hardware attestation key is not ECDSA")
|
||||||
|
}
|
||||||
|
return HardwareAttestationPublic{k: ecdsaPub}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HardwareAttestationPublic is the public key counterpart to
|
||||||
|
// HardwareAttestationKey.
|
||||||
|
type HardwareAttestationPublic struct {
|
||||||
|
k *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k HardwareAttestationPublic) Equal(o HardwareAttestationPublic) bool {
|
||||||
|
if k.k == nil || o.k == nil {
|
||||||
|
return k.k == o.k
|
||||||
|
}
|
||||||
|
return k.k.X.Cmp(o.k.X) == 0 && k.k.Y.Cmp(o.k.Y) == 0 && k.k.Curve == o.k.Curve
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero reports whether k is the zero value.
|
||||||
|
func (k HardwareAttestationPublic) IsZero() bool {
|
||||||
|
return k.k == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the hex-encoded public key with a type prefix.
|
||||||
|
func (k HardwareAttestationPublic) String() string {
|
||||||
|
bs, err := k.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
|
func (k HardwareAttestationPublic) MarshalText() ([]byte, error) {
|
||||||
|
if k.k == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return k.AppendText(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler. It expects a typed prefix
|
||||||
|
// followed by a hex encoded representation of k.
|
||||||
|
func (k *HardwareAttestationPublic) UnmarshalText(b []byte) error {
|
||||||
|
if len(b) == 0 {
|
||||||
|
*k = HardwareAttestationPublic{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kb := make([]byte, 65)
|
||||||
|
if err := parseHex(kb, mem.B(b), mem.S(hardwareAttestPublicHexPrefix)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), kb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
k.k = pk
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifier returns the ECDSA public key for verifying signatures made by k.
|
||||||
|
func (k HardwareAttestationPublic) Verifier() *ecdsa.PublicKey {
|
||||||
|
return k.k
|
||||||
}
|
}
|
||||||
|
|
||||||
// emptyHardwareAttestationKey is a function that returns an empty
|
// emptyHardwareAttestationKey is a function that returns an empty
|
||||||
// HardwareAttestationKey suitable for use with JSON unmarshalling.
|
// HardwareAttestationKey suitable for use with JSON unmarshaling.
|
||||||
var emptyHardwareAttestationKey func() HardwareAttestationKey
|
var emptyHardwareAttestationKey func() HardwareAttestationKey
|
||||||
|
|
||||||
// createHardwareAttestationKey is a function that creates a new
|
// createHardwareAttestationKey is a function that creates a new
|
||||||
@ -50,7 +143,7 @@ func RegisterHardwareAttestationKeyFns(emptyFn func() HardwareAttestationKey, cr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEmptyHardwareAttestationKey returns an empty HardwareAttestationKey
|
// NewEmptyHardwareAttestationKey returns an empty HardwareAttestationKey
|
||||||
// suitable for JSON unmarshalling.
|
// suitable for JSON unmarshaling.
|
||||||
func NewEmptyHardwareAttestationKey() (HardwareAttestationKey, error) {
|
func NewEmptyHardwareAttestationKey() (HardwareAttestationKey, error) {
|
||||||
if emptyHardwareAttestationKey == nil {
|
if emptyHardwareAttestationKey == nil {
|
||||||
return nil, ErrUnsupported
|
return nil, ErrUnsupported
|
||||||
|
Loading…
x
Reference in New Issue
Block a user