Skip to content

Commit

Permalink
Merge tag 'v1.68.2' into sunos-1.68
Browse files Browse the repository at this point in the history
Release 1.68.2
  • Loading branch information
nshalman committed Jul 4, 2024
2 parents e2fc774 + c79c500 commit c061a7c
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 127 deletions.
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.68.1
1.68.2
53 changes: 3 additions & 50 deletions control/controlclient/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"bufio"
"bytes"
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
Expand Down Expand Up @@ -473,7 +471,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
tryingNewKey := c.tryingNewKey
serverKey := c.serverLegacyKey
serverNoiseKey := c.serverNoiseKey
authKey, isWrapped, wrappedSig, wrappedKey := decodeWrappedAuthkey(c.authKey, c.logf)
authKey, isWrapped, wrappedSig, wrappedKey := tka.DecodeWrappedAuthkey(c.authKey, c.logf)
hi := c.hostInfoLocked()
backendLogID := hi.BackendLogID
expired := !c.expiry.IsZero() && c.expiry.Before(c.clock.Now())
Expand Down Expand Up @@ -565,18 +563,10 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
// We were given a wrapped pre-auth key, which means that in addition
// to being a regular pre-auth key there was a suffix with information to
// generate a tailnet-lock signature.
nk, err := tryingNewKey.Public().MarshalBinary()
nodeKeySignature, err = tka.SignByCredential(wrappedKey, wrappedSig, tryingNewKey.Public())
if err != nil {
return false, "", nil, fmt.Errorf("marshalling node-key: %w", err)
return false, "", nil, err
}
sig := &tka.NodeKeySignature{
SigKind: tka.SigRotation,
Pubkey: nk,
Nested: wrappedSig,
}
sigHash := sig.SigHash()
sig.Signature = ed25519.Sign(wrappedKey, sigHash[:])
nodeKeySignature = sig.Serialize()
}

if backendLogID == "" {
Expand Down Expand Up @@ -1620,43 +1610,6 @@ func (c *Direct) ReportHealthChange(sys health.Subsystem, sysErr error) {
res.Body.Close()
}

// decodeWrappedAuthkey separates wrapping information from an authkey, if any.
// In all cases the authkey is returned, sans wrapping information if any.
//
// If the authkey is wrapped, isWrapped returns true, along with the wrapping signature
// and private key.
func decodeWrappedAuthkey(key string, logf logger.Logf) (authKey string, isWrapped bool, sig *tka.NodeKeySignature, priv ed25519.PrivateKey) {
authKey, suffix, found := strings.Cut(key, "--TL")
if !found {
return key, false, nil, nil
}
sigBytes, privBytes, found := strings.Cut(suffix, "-")
if !found {
logf("decoding wrapped auth-key: did not find delimiter")
return key, false, nil, nil
}

rawSig, err := base64.RawStdEncoding.DecodeString(sigBytes)
if err != nil {
logf("decoding wrapped auth-key: signature decode: %v", err)
return key, false, nil, nil
}
rawPriv, err := base64.RawStdEncoding.DecodeString(privBytes)
if err != nil {
logf("decoding wrapped auth-key: priv decode: %v", err)
return key, false, nil, nil
}

sig = new(tka.NodeKeySignature)
if err := sig.Unserialize([]byte(rawSig)); err != nil {
logf("decoding wrapped auth-key: signature: %v", err)
return key, false, nil, nil
}
priv = ed25519.PrivateKey(rawPriv)

return authKey, true, sig, priv
}

func addLBHeader(req *http.Request, nodeKey key.NodePublic) {
if !nodeKey.IsZero() {
req.Header.Add(tailcfg.LBHeader, nodeKey.String())
Expand Down
40 changes: 0 additions & 40 deletions control/controlclient/direct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package controlclient

import (
"crypto/ed25519"
"encoding/json"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -147,42 +146,3 @@ func TestTsmpPing(t *testing.T) {
t.Fatal(err)
}
}

func TestDecodeWrappedAuthkey(t *testing.T) {
k, isWrapped, sig, priv := decodeWrappedAuthkey("tskey-32mjsdkdsffds9o87dsfkjlh", nil)
if want := "tskey-32mjsdkdsffds9o87dsfkjlh"; k != want {
t.Errorf("decodeWrappedAuthkey(<unwrapped-key>).key = %q, want %q", k, want)
}
if isWrapped {
t.Error("decodeWrappedAuthkey(<unwrapped-key>).isWrapped = true, want false")
}
if sig != nil {
t.Errorf("decodeWrappedAuthkey(<unwrapped-key>).sig = %v, want nil", sig)
}
if priv != nil {
t.Errorf("decodeWrappedAuthkey(<unwrapped-key>).priv = %v, want nil", priv)
}

k, isWrapped, sig, priv = decodeWrappedAuthkey("tskey-auth-k7UagY1CNTRL-ZZZZZ--TLpAEDA1ggnXuw4/fWnNWUwcoOjLemhOvml1juMl5lhLmY5sBUsj8EWEAfL2gdeD9g8VDw5tgcxCiHGlEb67BgU2DlFzZApi4LheLJraA+pYjTGChVhpZz1iyiBPD+U2qxDQAbM3+WFY0EBlggxmVqG53Hu0Rg+KmHJFMlUhfgzo+AQP6+Kk9GzvJJOs4-k36RdoSFqaoARfQo0UncHAV0t3YTqrkD5r/z2jTrE43GZWobnce7RGD4qYckUyVSF+DOj4BA/r4qT0bO8kk6zg", nil)
if want := "tskey-auth-k7UagY1CNTRL-ZZZZZ"; k != want {
t.Errorf("decodeWrappedAuthkey(<wrapped-key>).key = %q, want %q", k, want)
}
if !isWrapped {
t.Error("decodeWrappedAuthkey(<wrapped-key>).isWrapped = false, want true")
}

if sig == nil {
t.Fatal("decodeWrappedAuthkey(<wrapped-key>).sig = nil, want non-nil signature")
}
sigHash := sig.SigHash()
if !ed25519.Verify(sig.KeyID, sigHash[:], sig.Signature) {
t.Error("signature failed to verify")
}

// Make sure the private is correct by using it.
someSig := ed25519.Sign(priv, []byte{1, 2, 3, 4})
if !ed25519.Verify(sig.WrappingPubkey, []byte{1, 2, 3, 4}, someSig) {
t.Error("failed to use priv")
}

}
14 changes: 11 additions & 3 deletions ipn/ipnlocal/network-lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
// - for each SigRotation signature, all previous node keys referenced by the
// nested signatures are marked as obsolete.
// - if there are multiple SigRotation signatures tracing back to the same
// wrapping pubkey (e.g. if a node is cloned with all its keys), we keep
// just one of them, marking the others as obsolete.
// wrapping pubkey of the initial SigDirect signature (e.g. if a node is
// cloned with all its keys), we keep just one of them, marking the others as
// obsolete.
type rotationTracker struct {
// obsolete is the set of node keys that are obsolete due to key rotation.
// users of rotationTracker should use the obsoleteKeys method for complete results.
Expand All @@ -165,14 +166,21 @@ type sigRotationDetails struct {
func (r *rotationTracker) addRotationDetails(np key.NodePublic, d *tka.RotationDetails) {
r.obsolete.Make()
r.obsolete.AddSlice(d.PrevNodeKeys)
if d.InitialSig.SigKind != tka.SigDirect {
// Only enforce uniqueness of chains originating from a SigDirect
// signature. Chains that begin with a SigCredential can legitimately
// start from the same wrapping pubkey when multiple nodes join the
// network using the same reusable auth key.
return
}
rd := sigRotationDetails{
np: np,
numPrevKeys: len(d.PrevNodeKeys),
}
if r.byWrappingKey == nil {
r.byWrappingKey = make(map[string][]sigRotationDetails)
}
wp := string(d.WrappingPubkey)
wp := string(d.InitialSig.WrappingPubkey)
r.byWrappingKey[wp] = append(r.byWrappingKey[wp], rd)
}

Expand Down
76 changes: 56 additions & 20 deletions ipn/ipnlocal/network-lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,11 @@ func TestTKAFilterNetmap(t *testing.T) {
t.Fatalf("tka.Create() failed: %v", err)
}

b := &LocalBackend{
logf: t.Logf,
tka: &tkaState{authority: authority},
}

n1, n2, n3, n4, n5 := key.NewNode(), key.NewNode(), key.NewNode(), key.NewNode(), key.NewNode()
n1GoodSig, err := signNodeKey(tailcfg.TKASignInfo{NodePublic: n1.Public()}, nlPriv)
if err != nil {
Expand Down Expand Up @@ -585,6 +590,29 @@ func TestTKAFilterNetmap(t *testing.T) {

n5Rotated, n5RotatedSig := resign(n5nl, n5InitialSig.Serialize())

nodeFromAuthKey := func(authKey string) (key.NodePrivate, tkatype.MarshaledSignature) {
_, isWrapped, sig, priv := tka.DecodeWrappedAuthkey(authKey, t.Logf)
if !isWrapped {
t.Errorf("expected wrapped key")
}

node := key.NewNode()
nodeSig, err := tka.SignByCredential(priv, sig, node.Public())
if err != nil {
t.Error(err)
}
return node, nodeSig
}

preauth, err := b.NetworkLockWrapPreauthKey("tskey-auth-k7UagY1CNTRL-ZZZZZ", nlPriv)
if err != nil {
t.Fatal(err)
}

// Two nodes created using the same auth key, both should be valid.
n60, n60Sig := nodeFromAuthKey(preauth)
n61, n61Sig := nodeFromAuthKey(preauth)

nm := &netmap.NetworkMap{
Peers: nodeViews([]*tailcfg.Node{
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
Expand All @@ -593,18 +621,18 @@ func TestTKAFilterNetmap(t *testing.T) {
{ID: 4, Key: n4.Public(), KeySignature: n4Sig.Serialize()}, // messed-up signature
{ID: 50, Key: n5.Public(), KeySignature: n5InitialSig.Serialize()}, // rotated
{ID: 51, Key: n5Rotated.Public(), KeySignature: n5RotatedSig},
{ID: 60, Key: n60.Public(), KeySignature: n60Sig},
{ID: 61, Key: n61.Public(), KeySignature: n61Sig},
}),
}

b := &LocalBackend{
logf: t.Logf,
tka: &tkaState{authority: authority},
}
b.tkaFilterNetmapLocked(nm)

want := nodeViews([]*tailcfg.Node{
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
{ID: 51, Key: n5Rotated.Public(), KeySignature: n5RotatedSig},
{ID: 60, Key: n60.Public(), KeySignature: n60Sig},
{ID: 61, Key: n61.Public(), KeySignature: n61Sig},
})
nodePubComparer := cmp.Comparer(func(x, y key.NodePublic) bool {
return x.Raw32() == y.Raw32()
Expand Down Expand Up @@ -1182,6 +1210,14 @@ func TestRotationTracker(t *testing.T) {
raw32 := [32]byte{idx}
return key.NodePublicFromRaw32(go4mem.B(raw32[:]))
}

rd := func(initialKind tka.SigKind, wrappingKey []byte, prevKeys ...key.NodePublic) *tka.RotationDetails {
return &tka.RotationDetails{
InitialSig: &tka.NodeKeySignature{SigKind: initialKind, WrappingPubkey: wrappingKey},
PrevNodeKeys: prevKeys,
}
}

n1, n2, n3, n4, n5 := newNK(1), newNK(2), newNK(3), newNK(4), newNK(5)

pk1, pk2, pk3 := []byte{1}, []byte{2}, []byte{3}
Expand All @@ -1201,46 +1237,46 @@ func TestRotationTracker(t *testing.T) {
{
name: "single_prev_key",
addDetails: []addDetails{
{np: n1, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n2}, WrappingPubkey: pk1}},
{np: n1, details: rd(tka.SigDirect, pk1, n2)},
},
want: set.SetOf([]key.NodePublic{n2}),
},
{
name: "several_prev_keys",
addDetails: []addDetails{
{np: n1, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n2}, WrappingPubkey: pk1}},
{np: n3, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n4}, WrappingPubkey: pk2}},
{np: n2, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n3, n4}, WrappingPubkey: pk1}},
{np: n1, details: rd(tka.SigDirect, pk1, n2)},
{np: n3, details: rd(tka.SigDirect, pk2, n4)},
{np: n2, details: rd(tka.SigDirect, pk1, n3, n4)},
},
want: set.SetOf([]key.NodePublic{n2, n3, n4}),
},
{
name: "several_per_pubkey_latest_wins",
addDetails: []addDetails{
{np: n2, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1}, WrappingPubkey: pk3}},
{np: n3, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2}, WrappingPubkey: pk3}},
{np: n4, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2, n3}, WrappingPubkey: pk3}},
{np: n5, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n4}, WrappingPubkey: pk3}},
{np: n2, details: rd(tka.SigDirect, pk3, n1)},
{np: n3, details: rd(tka.SigDirect, pk3, n1, n2)},
{np: n4, details: rd(tka.SigDirect, pk3, n1, n2, n3)},
{np: n5, details: rd(tka.SigDirect, pk3, n4)},
},
want: set.SetOf([]key.NodePublic{n1, n2, n3, n4}),
},
{
name: "several_per_pubkey_same_chain_length_all_rejected",
addDetails: []addDetails{
{np: n2, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1}, WrappingPubkey: pk3}},
{np: n3, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2}, WrappingPubkey: pk3}},
{np: n4, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2}, WrappingPubkey: pk3}},
{np: n5, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2}, WrappingPubkey: pk3}},
{np: n2, details: rd(tka.SigDirect, pk3, n1)},
{np: n3, details: rd(tka.SigDirect, pk3, n1, n2)},
{np: n4, details: rd(tka.SigDirect, pk3, n1, n2)},
{np: n5, details: rd(tka.SigDirect, pk3, n1, n2)},
},
want: set.SetOf([]key.NodePublic{n1, n2, n3, n4, n5}),
},
{
name: "several_per_pubkey_longest_wins",
addDetails: []addDetails{
{np: n2, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1}, WrappingPubkey: pk3}},
{np: n3, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2}, WrappingPubkey: pk3}},
{np: n4, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2}, WrappingPubkey: pk3}},
{np: n5, details: &tka.RotationDetails{PrevNodeKeys: []key.NodePublic{n1, n2, n3}, WrappingPubkey: pk3}},
{np: n2, details: rd(tka.SigDirect, pk3, n1)},
{np: n3, details: rd(tka.SigDirect, pk3, n1, n2)},
{np: n4, details: rd(tka.SigDirect, pk3, n1, n2)},
{np: n5, details: rd(tka.SigDirect, pk3, n1, n2, n3)},
},
want: set.SetOf([]key.NodePublic{n1, n2, n3, n4}),
},
Expand Down
Loading

0 comments on commit c061a7c

Please sign in to comment.