From dc3c15b4c6e2fe4fcd67611d73d49e6832c42fec Mon Sep 17 00:00:00 2001 From: Patrick O'Doherty Date: Mon, 27 Oct 2025 13:18:13 -0700 Subject: [PATCH] control/controlclient: back out HW key attestation (#17664) Temporarily back out the TPM-based hw attestation code while we debug Windows exceptions. Updates tailscale/corp#31269 Signed-off-by: Patrick O'Doherty (cherry picked from commit a760cbe33f4bed64b63c6118808d02b2771ff785) --- control/controlclient/direct.go | 22 --------------- ipn/ipnlocal/hwattest.go | 48 --------------------------------- ipn/ipnlocal/local.go | 1 - ipn/ipnlocal/profiles.go | 10 ------- ipn/ipnlocal/profiles_test.go | 1 - ipn/prefs_test.go | 2 +- types/persist/persist.go | 18 ++----------- types/persist/persist_clone.go | 4 --- types/persist/persist_test.go | 2 +- types/persist/persist_view.go | 10 +++---- 10 files changed, 8 insertions(+), 110 deletions(-) delete mode 100644 ipn/ipnlocal/hwattest.go diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 63a12b249..fe7cc235b 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -7,8 +7,6 @@ import ( "bytes" "cmp" "context" - "crypto" - "crypto/sha256" "encoding/binary" "encoding/json" "errors" @@ -948,26 +946,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap ConnectionHandleForTest: connectionHandleForTest, } - // If we have a hardware attestation key, sign the node key with it and send - // the key & signature in the map request. - if buildfeatures.HasTPM { - if k := persist.AsStruct().AttestationKey; k != nil && !k.IsZero() { - hwPub := key.HardwareAttestationPublicFromPlatformKey(k) - request.HardwareAttestationKey = hwPub - - t := c.clock.Now() - msg := fmt.Sprintf("%d|%s", t.Unix(), nodeKey.String()) - digest := sha256.Sum256([]byte(msg)) - sig, err := k.Sign(nil, digest[:], crypto.SHA256) - if err != nil { - c.logf("failed to sign node key with hardware attestation key: %v", err) - } else { - request.HardwareAttestationKeySignature = sig - request.HardwareAttestationKeySignatureTimestamp = t - } - } - } - var extraDebugFlags []string if buildfeatures.HasAdvertiseRoutes && hi != nil && c.netMon != nil && !c.skipIPForwardingCheck && ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) { diff --git a/ipn/ipnlocal/hwattest.go b/ipn/ipnlocal/hwattest.go deleted file mode 100644 index 2c93cad4c..000000000 --- a/ipn/ipnlocal/hwattest.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !ts_omit_tpm - -package ipnlocal - -import ( - "errors" - - "tailscale.com/feature" - "tailscale.com/types/key" - "tailscale.com/types/logger" - "tailscale.com/types/persist" -) - -func init() { - feature.HookGenerateAttestationKeyIfEmpty.Set(generateAttestationKeyIfEmpty) -} - -// generateAttestationKeyIfEmpty generates a new hardware attestation key if -// none exists. It returns true if a new key was generated and stored in -// p.AttestationKey. -func generateAttestationKeyIfEmpty(p *persist.Persist, logf logger.Logf) (bool, error) { - // attempt to generate a new hardware attestation key if none exists - var ak key.HardwareAttestationKey - if p != nil { - ak = p.AttestationKey - } - - if ak == nil || ak.IsZero() { - var err error - ak, err = key.NewHardwareAttestationKey() - if err != nil { - if !errors.Is(err, key.ErrUnsupported) { - logf("failed to create hardware attestation key: %v", err) - } - } else if ak != nil { - logf("using new hardware attestation key: %v", ak.Public()) - if p == nil { - p = &persist.Persist{} - } - p.AttestationKey = ak - return true, nil - } - } - return false, nil -} diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 36e4ad8a5..8cc74c41e 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1216,7 +1216,6 @@ func stripKeysFromPrefs(p ipn.PrefsView) ipn.PrefsView { p2.Persist.PrivateNodeKey = key.NodePrivate{} p2.Persist.OldPrivateNodeKey = key.NodePrivate{} p2.Persist.NetworkLockKey = key.NLPrivate{} - p2.Persist.AttestationKey = nil return p2.View() } diff --git a/ipn/ipnlocal/profiles.go b/ipn/ipnlocal/profiles.go index 9c2176378..3e80cdaa9 100644 --- a/ipn/ipnlocal/profiles.go +++ b/ipn/ipnlocal/profiles.go @@ -19,9 +19,7 @@ import ( "tailscale.com/ipn" "tailscale.com/ipn/ipnext" "tailscale.com/tailcfg" - "tailscale.com/types/key" "tailscale.com/types/logger" - "tailscale.com/types/persist" "tailscale.com/util/clientmetric" "tailscale.com/util/eventbus" ) @@ -656,14 +654,6 @@ func (pm *profileManager) loadSavedPrefs(k ipn.StateKey) (ipn.PrefsView, error) return ipn.PrefsView{}, err } savedPrefs := ipn.NewPrefs() - - // if supported by the platform, create an empty hardware attestation key to use when deserializing - // to avoid type exceptions from json.Unmarshaling into an interface{}. - hw, _ := key.NewEmptyHardwareAttestationKey() - savedPrefs.Persist = &persist.Persist{ - AttestationKey: hw, - } - if err := ipn.PrefsFromBytes(bs, savedPrefs); err != nil { return ipn.PrefsView{}, fmt.Errorf("parsing saved prefs: %v", err) } diff --git a/ipn/ipnlocal/profiles_test.go b/ipn/ipnlocal/profiles_test.go index deeab2ade..60c92ff8d 100644 --- a/ipn/ipnlocal/profiles_test.go +++ b/ipn/ipnlocal/profiles_test.go @@ -151,7 +151,6 @@ func TestProfileDupe(t *testing.T) { ID: tailcfg.UserID(user), LoginName: fmt.Sprintf("user%d@example.com", user), }, - AttestationKey: nil, } } user1Node1 := newPersist(1, 1) diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 233616409..3339a631c 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -501,7 +501,7 @@ func TestPrefsPretty(t *testing.T) { }, }, "linux", - `Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist{o=, n=[B1VKl] u="" ak=-}}`, + `Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist{o=, n=[B1VKl] u=""}}`, }, { Prefs{ diff --git a/types/persist/persist.go b/types/persist/persist.go index 4b62c79dd..d888a6afb 100644 --- a/types/persist/persist.go +++ b/types/persist/persist.go @@ -26,7 +26,6 @@ type Persist struct { UserProfile tailcfg.UserProfile NetworkLockKey key.NLPrivate NodeID tailcfg.StableNodeID - AttestationKey key.HardwareAttestationKey `json:",omitempty"` // DisallowedTKAStateIDs stores the tka.State.StateID values which // this node will not operate network lock on. This is used to @@ -85,20 +84,11 @@ func (p *Persist) Equals(p2 *Persist) bool { return false } - var pub, p2Pub key.HardwareAttestationPublic - if p.AttestationKey != nil && !p.AttestationKey.IsZero() { - pub = key.HardwareAttestationPublicFromPlatformKey(p.AttestationKey) - } - if p2.AttestationKey != nil && !p2.AttestationKey.IsZero() { - p2Pub = key.HardwareAttestationPublicFromPlatformKey(p2.AttestationKey) - } - return p.PrivateNodeKey.Equal(p2.PrivateNodeKey) && p.OldPrivateNodeKey.Equal(p2.OldPrivateNodeKey) && p.UserProfile.Equal(&p2.UserProfile) && p.NetworkLockKey.Equal(p2.NetworkLockKey) && p.NodeID == p2.NodeID && - pub.Equal(p2Pub) && reflect.DeepEqual(nilIfEmpty(p.DisallowedTKAStateIDs), nilIfEmpty(p2.DisallowedTKAStateIDs)) } @@ -106,16 +96,12 @@ func (p *Persist) Pretty() string { var ( ok, nk key.NodePublic ) - akString := "-" if !p.OldPrivateNodeKey.IsZero() { ok = p.OldPrivateNodeKey.Public() } if !p.PrivateNodeKey.IsZero() { nk = p.PublicNodeKey() } - if p.AttestationKey != nil && !p.AttestationKey.IsZero() { - akString = fmt.Sprintf("%v", p.AttestationKey.Public()) - } - return fmt.Sprintf("Persist{o=%v, n=%v u=%#v ak=%s}", - ok.ShortString(), nk.ShortString(), p.UserProfile.LoginName, akString) + return fmt.Sprintf("Persist{o=%v, n=%v u=%#v}", + ok.ShortString(), nk.ShortString(), p.UserProfile.LoginName) } diff --git a/types/persist/persist_clone.go b/types/persist/persist_clone.go index 9dbe7e0f6..680419ff2 100644 --- a/types/persist/persist_clone.go +++ b/types/persist/persist_clone.go @@ -19,9 +19,6 @@ func (src *Persist) Clone() *Persist { } dst := new(Persist) *dst = *src - if src.AttestationKey != nil { - dst.AttestationKey = src.AttestationKey.Clone() - } dst.DisallowedTKAStateIDs = append(src.DisallowedTKAStateIDs[:0:0], src.DisallowedTKAStateIDs...) return dst } @@ -34,6 +31,5 @@ var _PersistCloneNeedsRegeneration = Persist(struct { UserProfile tailcfg.UserProfile NetworkLockKey key.NLPrivate NodeID tailcfg.StableNodeID - AttestationKey key.HardwareAttestationKey DisallowedTKAStateIDs []string }{}) diff --git a/types/persist/persist_test.go b/types/persist/persist_test.go index 713114b74..dbf2a6d8c 100644 --- a/types/persist/persist_test.go +++ b/types/persist/persist_test.go @@ -21,7 +21,7 @@ func fieldsOf(t reflect.Type) (fields []string) { } func TestPersistEqual(t *testing.T) { - persistHandles := []string{"PrivateNodeKey", "OldPrivateNodeKey", "UserProfile", "NetworkLockKey", "NodeID", "AttestationKey", "DisallowedTKAStateIDs"} + persistHandles := []string{"PrivateNodeKey", "OldPrivateNodeKey", "UserProfile", "NetworkLockKey", "NodeID", "DisallowedTKAStateIDs"} if have := fieldsOf(reflect.TypeFor[Persist]()); !reflect.DeepEqual(have, persistHandles) { t.Errorf("Persist.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, persistHandles) diff --git a/types/persist/persist_view.go b/types/persist/persist_view.go index dbf8294ef..7d1507468 100644 --- a/types/persist/persist_view.go +++ b/types/persist/persist_view.go @@ -89,11 +89,10 @@ func (v *PersistView) UnmarshalJSONFrom(dec *jsontext.Decoder) error { func (v PersistView) PrivateNodeKey() key.NodePrivate { return v.ж.PrivateNodeKey } // needed to request key rotation -func (v PersistView) OldPrivateNodeKey() key.NodePrivate { return v.ж.OldPrivateNodeKey } -func (v PersistView) UserProfile() tailcfg.UserProfile { return v.ж.UserProfile } -func (v PersistView) NetworkLockKey() key.NLPrivate { return v.ж.NetworkLockKey } -func (v PersistView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID } -func (v PersistView) AttestationKey() tailcfg.StableNodeID { panic("unsupported") } +func (v PersistView) OldPrivateNodeKey() key.NodePrivate { return v.ж.OldPrivateNodeKey } +func (v PersistView) UserProfile() tailcfg.UserProfile { return v.ж.UserProfile } +func (v PersistView) NetworkLockKey() key.NLPrivate { return v.ж.NetworkLockKey } +func (v PersistView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID } // DisallowedTKAStateIDs stores the tka.State.StateID values which // this node will not operate network lock on. This is used to @@ -111,6 +110,5 @@ var _PersistViewNeedsRegeneration = Persist(struct { UserProfile tailcfg.UserProfile NetworkLockKey key.NLPrivate NodeID tailcfg.StableNodeID - AttestationKey key.HardwareAttestationKey DisallowedTKAStateIDs []string }{})