diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 365336ca9..d30a71433 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -1,13 +1,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware) - filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus - filippo.io/edwards25519/field from filippo.io/edwards25519 W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+ W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy - github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/hdevalence/ed25519consensus from tailscale.com/tka L github.com/josharian/native from github.com/mdlayher/netlink+ L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink @@ -30,7 +26,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli - github.com/x448/float16 from github.com/fxamacker/cbor/v2 💣 go4.org/mem from tailscale.com/derp+ go4.org/netipx from tailscale.com/wgengine/filter W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+ @@ -73,7 +68,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+ tailscale.com/syncs from tailscale.com/net/netcheck+ tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+ - tailscale.com/tka from tailscale.com/types/key W tailscale.com/tsconst from tailscale.com/net/interfaces 💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate tailscale.com/tstime/rate from tailscale.com/wgengine/filter @@ -89,6 +83,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/persist from tailscale.com/ipn tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/structs from tailscale.com/ipn+ + tailscale.com/types/tkatype from tailscale.com/types/key tailscale.com/types/views from tailscale.com/tailcfg+ tailscale.com/util/clientmetric from tailscale.com/net/netcheck+ tailscale.com/util/cloudenv from tailscale.com/net/dnscache+ @@ -102,9 +97,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ tailscale.com/wgengine/filter from tailscale.com/types/netmap - golang.org/x/crypto/argon2 from tailscale.com/tka - golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box+ - golang.org/x/crypto/blake2s from tailscale.com/control/controlbase+ + golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box + golang.org/x/crypto/blake2s from tailscale.com/control/controlbase golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ @@ -162,7 +156,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep embed from tailscale.com/cmd/tailscale/cli+ encoding from encoding/json+ encoding/asn1 from crypto/x509+ - encoding/base32 from tailscale.com/tka encoding/base64 from encoding/json+ encoding/binary from compress/gzip+ encoding/hex from crypto/x509+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 6aefa3040..26674fe24 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -244,7 +244,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/syncs from tailscale.com/net/netcheck+ tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+ LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh - tailscale.com/tka from tailscale.com/types/key+ + tailscale.com/tka from tailscale.com/ipn/ipnlocal+ W tailscale.com/tsconst from tailscale.com/net/interfaces tailscale.com/tstime from tailscale.com/wgengine/magicsock 💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+ @@ -263,6 +263,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/persist from tailscale.com/control/controlclient+ tailscale.com/types/preftype from tailscale.com/ipn+ tailscale.com/types/structs from tailscale.com/control/controlclient+ + tailscale.com/types/tkatype from tailscale.com/tka+ tailscale.com/types/views from tailscale.com/ipn/ipnlocal+ tailscale.com/util/clientmetric from tailscale.com/control/controlclient+ tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+ diff --git a/tka/aum.go b/tka/aum.go index fd6aaa8d4..7fcd04da9 100644 --- a/tka/aum.go +++ b/tka/aum.go @@ -12,15 +12,12 @@ import ( "github.com/fxamacker/cbor/v2" "golang.org/x/crypto/blake2s" + "tailscale.com/types/tkatype" ) // AUMHash represents the BLAKE2s digest of an Authority Update Message (AUM). type AUMHash [blake2s.Size]byte -// AUMSigHash represents the BLAKE2s digest of an Authority Update -// Message (AUM), sans any signatures. -type AUMSigHash [blake2s.Size]byte - // AUMKind describes valid AUM types. type AUMKind uint8 @@ -100,7 +97,7 @@ type AUM struct { // KeyID references a public key which is part of the key authority. // This field is used for RemoveKey and UpdateKey AUMs. - KeyID KeyID `cbor:"4,keyasint,omitempty"` + KeyID tkatype.KeyID `cbor:"4,keyasint,omitempty"` // State describes the full state of the key authority. // This field is used for Checkpoint AUMs. @@ -118,7 +115,7 @@ type AUM struct { // Signatures lists the signatures over this AUM. // CBOR key 23 is the last key which can be encoded as a single byte. - Signatures []Signature `cbor:"23,keyasint,omitempty"` + Signatures []tkatype.Signature `cbor:"23,keyasint,omitempty"` } // StaticValidate returns a nil error if the AUM is well-formed. @@ -230,7 +227,7 @@ func (a *AUM) Hash() AUMHash { // This is identical to Hash() except the Signatures are not // serialized. Without this, the hash used for signatures // would be circularly dependent on the signatures. -func (a AUM) SigHash() AUMSigHash { +func (a AUM) SigHash() tkatype.AUMSigHash { dupe := a dupe.Signatures = nil return blake2s.Sum256(dupe.Serialize()) @@ -250,7 +247,7 @@ func (a *AUM) sign25519(priv ed25519.PrivateKey) { key := Key{Kind: Key25519, Public: priv.Public().(ed25519.PublicKey)} sigHash := a.SigHash() - a.Signatures = append(a.Signatures, Signature{ + a.Signatures = append(a.Signatures, tkatype.Signature{ KeyID: key.ID(), Signature: ed25519.Sign(priv, sigHash[:]), }) diff --git a/tka/aum_test.go b/tka/aum_test.go index 6fb14cc3e..78d178d8b 100644 --- a/tka/aum_test.go +++ b/tka/aum_test.go @@ -11,6 +11,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/google/go-cmp/cmp" "golang.org/x/crypto/blake2s" + "tailscale.com/types/tkatype" ) func TestSerialization(t *testing.T) { @@ -137,7 +138,7 @@ func TestSerialization(t *testing.T) { }, { "Signature", - AUM{MessageKind: AUMAddKey, Signatures: []Signature{{KeyID: []byte{1}}}}, + AUM{MessageKind: AUMAddKey, Signatures: []tkatype.Signature{{KeyID: []byte{1}}}}, []byte{ 0xa3, // major type 5 (map), 3 items 0x01, // |- major type 0 (int), value 1 (first key, MessageKind) @@ -198,7 +199,7 @@ func TestAUMWeight(t *testing.T) { { "Key unknown", AUM{ - Signatures: []Signature{{KeyID: fakeKeyID[:]}}, + Signatures: []tkatype.Signature{{KeyID: fakeKeyID[:]}}, }, State{}, 0, @@ -206,7 +207,7 @@ func TestAUMWeight(t *testing.T) { { "Unary key", AUM{ - Signatures: []Signature{{KeyID: key.ID()}}, + Signatures: []tkatype.Signature{{KeyID: key.ID()}}, }, State{ Keys: []Key{key}, @@ -216,7 +217,7 @@ func TestAUMWeight(t *testing.T) { { "Multiple keys", AUM{ - Signatures: []Signature{{KeyID: key.ID()}, {KeyID: key2.ID()}}, + Signatures: []tkatype.Signature{{KeyID: key.ID()}, {KeyID: key2.ID()}}, }, State{ Keys: []Key{key, key2}, @@ -226,7 +227,7 @@ func TestAUMWeight(t *testing.T) { { "Double use", AUM{ - Signatures: []Signature{{KeyID: key.ID()}, {KeyID: key.ID()}}, + Signatures: []tkatype.Signature{{KeyID: key.ID()}, {KeyID: key.ID()}}, }, State{ Keys: []Key{key}, @@ -255,7 +256,7 @@ func TestAUMHashes(t *testing.T) { sigHash1 := aum.SigHash() aumHash1 := aum.Hash() - aum.Signatures = []Signature{{KeyID: []byte{1, 2, 3, 4}}} + aum.Signatures = []tkatype.Signature{{KeyID: []byte{1, 2, 3, 4}}} sigHash2 := aum.SigHash() aumHash2 := aum.Hash() if len(aum.Signatures) != 1 { diff --git a/tka/builder.go b/tka/builder.go index 7cbf7bb0c..500fcbb25 100644 --- a/tka/builder.go +++ b/tka/builder.go @@ -6,11 +6,13 @@ package tka import ( "fmt" + + "tailscale.com/types/tkatype" ) // Types implementing Signer can sign update messages. type Signer interface { - SignAUM(*AUM) error + SignAUM(tkatype.AUMSigHash) ([]tkatype.Signature, error) } // UpdateBuilder implements a builder for changes to the tailnet @@ -34,9 +36,11 @@ func (b *UpdateBuilder) mkUpdate(update AUM) error { update.PrevAUMHash = prevHash if b.signer != nil { - if err := b.signer.SignAUM(&update); err != nil { + sigs, err := b.signer.SignAUM(update.SigHash()) + if err != nil { return fmt.Errorf("signing failed: %v", err) } + update.Signatures = append(update.Signatures, sigs...) } if err := update.StaticValidate(); err != nil { return fmt.Errorf("generated update was invalid: %v", err) @@ -61,7 +65,7 @@ func (b *UpdateBuilder) AddKey(key Key) error { } // RemoveKey removes a key from the authority. -func (b *UpdateBuilder) RemoveKey(keyID KeyID) error { +func (b *UpdateBuilder) RemoveKey(keyID tkatype.KeyID) error { if _, err := b.state.GetKey(keyID); err != nil { return fmt.Errorf("failed reading key %x: %v", keyID, err) } @@ -69,7 +73,7 @@ func (b *UpdateBuilder) RemoveKey(keyID KeyID) error { } // SetKeyVote updates the number of votes of an existing key. -func (b *UpdateBuilder) SetKeyVote(keyID KeyID, votes uint) error { +func (b *UpdateBuilder) SetKeyVote(keyID tkatype.KeyID, votes uint) error { if _, err := b.state.GetKey(keyID); err != nil { return fmt.Errorf("failed reading key %x: %v", keyID, err) } @@ -80,7 +84,7 @@ func (b *UpdateBuilder) SetKeyVote(keyID KeyID, votes uint) error { // // TODO(tom): Provide an API to update specific values rather than the whole // map. -func (b *UpdateBuilder) SetKeyMeta(keyID KeyID, meta map[string]string) error { +func (b *UpdateBuilder) SetKeyMeta(keyID tkatype.KeyID, meta map[string]string) error { if _, err := b.state.GetKey(keyID); err != nil { return fmt.Errorf("failed reading key %x: %v", keyID, err) } diff --git a/tka/builder_test.go b/tka/builder_test.go index 34cca18a4..b3c599700 100644 --- a/tka/builder_test.go +++ b/tka/builder_test.go @@ -9,13 +9,19 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "tailscale.com/types/tkatype" ) type signer25519 ed25519.PrivateKey -func (s signer25519) SignAUM(update *AUM) error { - update.sign25519(ed25519.PrivateKey(s)) - return nil +func (s signer25519) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, error) { + priv := ed25519.PrivateKey(s) + key := Key{Kind: Key25519, Public: priv.Public().(ed25519.PublicKey)} + + return []tkatype.Signature{{ + KeyID: key.ID(), + Signature: ed25519.Sign(priv, sigHash[:]), + }}, nil } func TestAuthorityBuilderAddKey(t *testing.T) { diff --git a/tka/chaintest_test.go b/tka/chaintest_test.go index 79cc2ca74..252702bd8 100644 --- a/tka/chaintest_test.go +++ b/tka/chaintest_test.go @@ -15,6 +15,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "tailscale.com/types/tkatype" ) // chaintest_test.go implements test helpers for concisely describing @@ -265,7 +266,7 @@ func (c *testChain) makeAUM(v *testchainNode) AUM { sigHash := aum.SigHash() for _, key := range c.SignAllKeys { - aum.Signatures = append(aum.Signatures, Signature{ + aum.Signatures = append(aum.Signatures, tkatype.Signature{ KeyID: c.Key[key].ID(), Signature: ed25519.Sign(c.KeyPrivs[key], sigHash[:]), }) @@ -274,7 +275,7 @@ func (c *testChain) makeAUM(v *testchainNode) AUM { // If the aum was specified as being signed by some key, then // sign it using that key. if key := v.SignedWith; key != "" { - aum.Signatures = append(aum.Signatures, Signature{ + aum.Signatures = append(aum.Signatures, tkatype.Signature{ KeyID: c.Key[key].ID(), Signature: ed25519.Sign(c.KeyPrivs[key], sigHash[:]), }) diff --git a/tka/key.go b/tka/key.go index c4a6b49c4..aeb2a461a 100644 --- a/tka/key.go +++ b/tka/key.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/hdevalence/ed25519consensus" + "tailscale.com/types/tkatype" ) // KeyKind describes the different varieties of a Key. @@ -73,12 +74,12 @@ func (k Key) Clone() Key { return out } -func (k Key) ID() KeyID { +func (k Key) ID() tkatype.KeyID { switch k.Kind { // Because 25519 public keys are so short, we just use the 32-byte // public as their 'key ID'. case Key25519: - return KeyID(k.Public) + return tkatype.KeyID(k.Public) default: panic("unsupported key kind") } @@ -112,21 +113,9 @@ func (k Key) StaticValidate() error { return nil } -// KeyID references a verification key stored in the key authority. -// -// For 25519 keys: The 32-byte public key. -type KeyID []byte - -// Signature describes a signature over an AUM, which can be verified -// using the key referenced by KeyID. -type Signature struct { - KeyID KeyID `cbor:"1,keyasint"` - Signature []byte `cbor:"2,keyasint"` -} - // Verify returns a nil error if the signature is valid over the // provided AUM BLAKE2s digest, using the given key. -func (s *Signature) Verify(aumDigest AUMSigHash, key Key) error { +func signatureVerify(s *tkatype.Signature, aumDigest tkatype.AUMSigHash, key Key) error { // NOTE(tom): Even if we can compute the public from the KeyID, // its possible for the KeyID to be attacker-controlled // so we should use the public contained in the state machine. diff --git a/tka/key_test.go b/tka/key_test.go index 0b8b0ee4b..dc7c73ee6 100644 --- a/tka/key_test.go +++ b/tka/key_test.go @@ -10,6 +10,8 @@ import ( "encoding/binary" "math/rand" "testing" + + "tailscale.com/types/tkatype" ) // returns a random source based on the test name + extraSeed. @@ -41,24 +43,24 @@ func TestVerify25519(t *testing.T) { MessageKind: AUMRemoveKey, KeyID: []byte{1, 2, 3, 4}, // Signatures is set to crap so we are sure its ignored in the sigHash computation. - Signatures: []Signature{{KeyID: []byte{45, 42}}}, + Signatures: []tkatype.Signature{{KeyID: []byte{45, 42}}}, } sigHash := aum.SigHash() - aum.Signatures = []Signature{ + aum.Signatures = []tkatype.Signature{ { KeyID: key.ID(), Signature: ed25519.Sign(priv, sigHash[:]), }, } - if err := aum.Signatures[0].Verify(aum.SigHash(), key); err != nil { + if err := signatureVerify(&aum.Signatures[0], aum.SigHash(), key); err != nil { t.Errorf("signature verification failed: %v", err) } // Make sure it fails with a different public key. pub2, _ := testingKey25519(t, 2) key2 := Key{Kind: Key25519, Public: pub2} - if err := aum.Signatures[0].Verify(aum.SigHash(), key2); err == nil { + if err := signatureVerify(&aum.Signatures[0], aum.SigHash(), key2); err == nil { t.Error("signature verification with different key did not fail") } } diff --git a/tka/scenario_test.go b/tka/scenario_test.go index 43bcaae8f..eb96f1ec5 100644 --- a/tka/scenario_test.go +++ b/tka/scenario_test.go @@ -204,7 +204,7 @@ func TestScenarioHelpers(t *testing.T) { if _, ok := n.AUMs["L3"]; !ok { t.Errorf("node n is missing %s", "L3") } - if err := n.AUMs["L3"].Signatures[0].Verify(n.AUMs["L3"].SigHash(), *s.defaultKey); err != nil { + if err := signatureVerify(&n.AUMs["L3"].Signatures[0], n.AUMs["L3"].SigHash(), *s.defaultKey); err != nil { t.Errorf("chained AUM was not signed: %v", err) } diff --git a/tka/sig.go b/tka/sig.go index f6bb20bd3..0cb59ee9d 100644 --- a/tka/sig.go +++ b/tka/sig.go @@ -13,6 +13,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/hdevalence/ed25519consensus" "golang.org/x/crypto/blake2s" + "tailscale.com/types/tkatype" ) // SigKind describes valid NodeKeySignature types. @@ -67,7 +68,7 @@ func (s NodeKeySignature) sigHash() [blake2s.Size]byte { } // Serialize returns the given NKS in a serialized format. -func (s *NodeKeySignature) Serialize() []byte { +func (s *NodeKeySignature) Serialize() tkatype.MarshaledSignature { out := bytes.NewBuffer(make([]byte, 0, 128)) // 64byte sig + 32byte keyID + 32byte headroom encoder, err := cbor.CTAP2EncOptions().EncMode() if err != nil { diff --git a/tka/state.go b/tka/state.go index b19ad3c6f..80b93c583 100644 --- a/tka/state.go +++ b/tka/state.go @@ -10,6 +10,7 @@ import ( "fmt" "golang.org/x/crypto/argon2" + "tailscale.com/types/tkatype" ) // ErrNoSuchKey is returned if the key referenced by a KeyID does not exist. @@ -40,7 +41,7 @@ type State struct { } // GetKey returns the trusted key with the specified KeyID. -func (s State) GetKey(key KeyID) (Key, error) { +func (s State) GetKey(key tkatype.KeyID) (Key, error) { for _, k := range s.Keys { if bytes.Equal(k.ID(), key) { return k, nil diff --git a/tka/tka.go b/tka/tka.go index b126a9a57..58772bf45 100644 --- a/tka/tka.go +++ b/tka/tka.go @@ -13,6 +13,7 @@ import ( "sort" "github.com/fxamacker/cbor/v2" + "tailscale.com/types/tkatype" ) // Authority is a Tailnet Key Authority. This type is the main coupling @@ -416,7 +417,7 @@ func aumVerify(aum AUM, state State, isGenesisAUM bool) error { if err != nil { return fmt.Errorf("bad keyID on signature %d: %v", i, err) } - if err := sig.Verify(sigHash, key); err != nil { + if err := signatureVerify(&sig, sigHash, key); err != nil { return fmt.Errorf("signature %d: %v", i, err) } } @@ -485,9 +486,11 @@ func Create(storage Chonk, state State, signer Signer) (*Authority, AUM, error) // This serves as an easy way to validate the given state. return nil, AUM{}, fmt.Errorf("invalid state: %v", err) } - if err := signer.SignAUM(&genesis); err != nil { + sigs, err := signer.SignAUM(genesis.SigHash()) + if err != nil { return nil, AUM{}, fmt.Errorf("signing failed: %v", err) } + genesis.Signatures = append(genesis.Signatures, sigs...) a, err := Bootstrap(storage, genesis) return a, genesis, err @@ -591,7 +594,7 @@ func (a *Authority) Inform(updates []AUM) error { // VerifySignature returns true if the provided nodeKeySignature is signed // correctly by a trusted key. -func (a *Authority) VerifySignature(nodeKeySignature []byte) error { +func (a *Authority) VerifySignature(nodeKeySignature tkatype.MarshaledSignature) error { var decoded NodeKeySignature if err := cbor.Unmarshal(nodeKeySignature, &decoded); err != nil { return fmt.Errorf("unmarshal: %v", err) diff --git a/tka/tka_test.go b/tka/tka_test.go index 92d12e74f..a72c433c8 100644 --- a/tka/tka_test.go +++ b/tka/tka_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "tailscale.com/types/tkatype" ) func TestComputeChainCandidates(t *testing.T) { @@ -232,7 +233,7 @@ func TestOpenAuthority(t *testing.T) { i2, i2H := fakeAUM(t, 2, &i1H) i3, i3H := fakeAUM(t, 5, &i2H) - l2, l2H := fakeAUM(t, AUM{MessageKind: AUMNoOp, KeyID: []byte{7}, Signatures: []Signature{{KeyID: key.ID()}}}, &i3H) + l2, l2H := fakeAUM(t, AUM{MessageKind: AUMNoOp, KeyID: []byte{7}, Signatures: []tkatype.Signature{{KeyID: key.ID()}}}, &i3H) l3, l3H := fakeAUM(t, 4, &i3H) g2, g2H := fakeAUM(t, 8, nil) diff --git a/types/key/nl.go b/types/key/nl.go index e9092d437..5d3144658 100644 --- a/types/key/nl.go +++ b/types/key/nl.go @@ -9,8 +9,8 @@ import ( "crypto/subtle" "go4.org/mem" - "tailscale.com/tka" "tailscale.com/types/structs" + "tailscale.com/types/tkatype" ) const ( @@ -68,23 +68,26 @@ func (k NLPrivate) Public() NLPublic { } // KeyID returns an identifier for this key. -func (k NLPrivate) KeyID() tka.KeyID { - pub := k.Public() - return tka.Key{ - Kind: tka.Key25519, - Public: pub.k[:], - }.ID() +func (k NLPrivate) KeyID() tkatype.KeyID { + // The correct way to compute this is: + // return tka.Key{ + // Kind: tka.Key25519, + // Public: pub.k[:], + // }.ID() + // + // However, under the hood the key id for a 25519 + // key is just the public key, so we avoid the + // dependency on tka by just doing this ourselves. + pub := k.Public().k + return pub[:] } // SignAUM implements tka.UpdateSigner. -func (k NLPrivate) SignAUM(a *tka.AUM) error { - sigHash := a.SigHash() - - a.Signatures = append(a.Signatures, tka.Signature{ +func (k NLPrivate) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, error) { + return []tkatype.Signature{{ KeyID: k.KeyID(), - Signature: ed25519.Sign(k.k[:], sigHash[:]), - }) - return nil + Signature: ed25519.Sign(ed25519.PrivateKey(k.k[:]), sigHash[:]), + }}, nil } // NLPublic is the public portion of a a NLPrivate. diff --git a/types/key/nl_test.go b/types/key/nl_test.go index ed69a1fd1..a8dc43ca1 100644 --- a/types/key/nl_test.go +++ b/types/key/nl_test.go @@ -6,6 +6,7 @@ package key import ( "bytes" + "crypto/ed25519" "testing" "tailscale.com/tka" @@ -55,7 +56,14 @@ func TestNLPrivate(t *testing.T) { if got, want := len(aum.Signatures), 1; got != want { t.Fatalf("len(signatures) = %d, want %d", got, want) } - if err := aum.Signatures[0].Verify(aum.SigHash(), k); err != nil { - t.Errorf("signature did not verify: %v", err) + sigHash := aum.SigHash() + if ok := ed25519.Verify(pub.Verifier(), sigHash[:], aum.Signatures[0].Signature); !ok { + t.Error("signature did not verify") + } + + // We manually compute the keyID, so make sure its consistent with + // tka.Key.ID(). + if !bytes.Equal(k.ID(), p.KeyID()) { + t.Errorf("private.KeyID() & tka KeyID differ: %x != %x", k.ID(), p.KeyID()) } } diff --git a/types/tkatype/tkatype.go b/types/tkatype/tkatype.go new file mode 100644 index 000000000..c20ab119a --- /dev/null +++ b/types/tkatype/tkatype.go @@ -0,0 +1,34 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tkatype defines types for working with the tka package. +// +// Do not add extra dependencies to this package unless they are tiny, +// because this package encodes wire types that should be lightweight to use. +package tkatype + +// KeyID references a verification key stored in the key authority. A keyID +// uniquely identifies a key. KeyIDs are all 32 bytes. +// +// For 25519 keys: We just use the 32-byte public key. +// +// Even though this is a 32-byte value, we use a byte slice because +// CBOR-encoded byte slices have a different prefix to CBOR-encoded arrays. +// Encoding as a byte slice allows us to change the size in the future if we +// ever need to. +type KeyID []byte + +// MarshaledSignature represents a marshaled tka.NodeKeySignature. +type MarshaledSignature []byte + +// AUMSigHash represents the BLAKE2s digest of an Authority Update +// Message (AUM), sans any signatures. +type AUMSigHash [32]byte + +// Signature describes a signature over an AUM, which can be verified +// using the key referenced by KeyID. +type Signature struct { + KeyID KeyID `cbor:"1,keyasint"` + Signature []byte `cbor:"2,keyasint"` +} diff --git a/types/tkatype/tkatype_test.go b/types/tkatype/tkatype_test.go new file mode 100644 index 000000000..59153fb59 --- /dev/null +++ b/types/tkatype/tkatype_test.go @@ -0,0 +1,17 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tkatype + +import ( + "golang.org/x/crypto/blake2s" + "testing" +) + +func TestSigHashSize(t *testing.T) { + var sigHash AUMSigHash + if len(sigHash) != blake2s.Size { + t.Errorf("AUMSigHash is wrong size: got %d, want %d", len(sigHash), blake2s.Size) + } +}