diff options
| author | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
|---|---|---|
| committer | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
| commit | 0267fe83b200f1702a2fa0a395442c02a053fadb (patch) | |
| tree | 63654c55225eeb834de59a5a0bc8d19033c6145b /types/key | |
| parent | 87546a5edf6b6503a87eeb2d666baba57398a066 (diff) | |
| download | tailscale-1.78.0.tar.xz tailscale-1.78.0.zip | |
VERSION.txt: this is v1.78.0v1.78.0
Signed-off-by: Nick Khyl <nickk@tailscale.com>
Diffstat (limited to 'types/key')
| -rw-r--r-- | types/key/chal.go | 182 | ||||
| -rw-r--r-- | types/key/control.go | 136 | ||||
| -rw-r--r-- | types/key/control_test.go | 76 | ||||
| -rw-r--r-- | types/key/disco_test.go | 166 | ||||
| -rw-r--r-- | types/key/machine.go | 528 | ||||
| -rw-r--r-- | types/key/machine_test.go | 238 | ||||
| -rw-r--r-- | types/key/nl_test.go | 96 |
7 files changed, 711 insertions, 711 deletions
diff --git a/types/key/chal.go b/types/key/chal.go index 742ac5479..da15dd1f8 100644 --- a/types/key/chal.go +++ b/types/key/chal.go @@ -1,91 +1,91 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import ( - "errors" - - "go4.org/mem" - "tailscale.com/types/structs" -) - -const ( - // chalPublicHexPrefix is the prefix used to identify a - // hex-encoded challenge public key. - // - // This prefix is used in the control protocol, so cannot be - // changed. - chalPublicHexPrefix = "chalpub:" -) - -// ChallengePrivate is a challenge key, used to test whether clients control a -// key they want to prove ownership of. -// -// A ChallengePrivate is ephemeral and not serialized to the disk or network. -type ChallengePrivate struct { - _ structs.Incomparable // because == isn't constant-time - k [32]byte -} - -// NewChallenge creates and returns a new node private key. -func NewChallenge() ChallengePrivate { - return ChallengePrivate(NewNode()) -} - -// Public returns the ChallengePublic for k. -// Panics if ChallengePublic is zero. -func (k ChallengePrivate) Public() ChallengePublic { - pub := NodePrivate(k).Public() - return ChallengePublic(pub) -} - -// MarshalText implements encoding.TextMarshaler, but by returning an error. -// It shouldn't need to be marshalled anywhere. -func (k ChallengePrivate) MarshalText() ([]byte, error) { - return nil, errors.New("refusing to marshal") -} - -// SealToChallenge is like SealTo, but for a ChallengePublic. -func (k NodePrivate) SealToChallenge(p ChallengePublic, cleartext []byte) (ciphertext []byte) { - return k.SealTo(NodePublic(p), cleartext) -} - -// OpenFrom opens the NaCl box ciphertext, which must be a value -// created by NodePrivate.SealToChallenge, and returns the inner cleartext if -// ciphertext is a valid box from p to k. -func (k ChallengePrivate) OpenFrom(p NodePublic, ciphertext []byte) (cleartext []byte, ok bool) { - return NodePrivate(k).OpenFrom(p, ciphertext) -} - -// ChallengePublic is the public portion of a ChallengePrivate. -type ChallengePublic struct { - k [32]byte -} - -// String returns the output of MarshalText as a string. -func (k ChallengePublic) String() string { - bs, err := k.MarshalText() - if err != nil { - panic(err) - } - return string(bs) -} - -// AppendText implements encoding.TextAppender. -func (k ChallengePublic) AppendText(b []byte) ([]byte, error) { - return appendHexKey(b, chalPublicHexPrefix, k.k[:]), nil -} - -// MarshalText implements encoding.TextMarshaler. -func (k ChallengePublic) MarshalText() ([]byte, error) { - return k.AppendText(nil) -} - -// UnmarshalText implements encoding.TextUnmarshaler. -func (k *ChallengePublic) UnmarshalText(b []byte) error { - return parseHex(k.k[:], mem.B(b), mem.S(chalPublicHexPrefix)) -} - -// IsZero reports whether k is the zero value. -func (k ChallengePublic) IsZero() bool { return k == ChallengePublic{} } +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "errors"
+
+ "go4.org/mem"
+ "tailscale.com/types/structs"
+)
+
+const (
+ // chalPublicHexPrefix is the prefix used to identify a
+ // hex-encoded challenge public key.
+ //
+ // This prefix is used in the control protocol, so cannot be
+ // changed.
+ chalPublicHexPrefix = "chalpub:"
+)
+
+// ChallengePrivate is a challenge key, used to test whether clients control a
+// key they want to prove ownership of.
+//
+// A ChallengePrivate is ephemeral and not serialized to the disk or network.
+type ChallengePrivate struct {
+ _ structs.Incomparable // because == isn't constant-time
+ k [32]byte
+}
+
+// NewChallenge creates and returns a new node private key.
+func NewChallenge() ChallengePrivate {
+ return ChallengePrivate(NewNode())
+}
+
+// Public returns the ChallengePublic for k.
+// Panics if ChallengePublic is zero.
+func (k ChallengePrivate) Public() ChallengePublic {
+ pub := NodePrivate(k).Public()
+ return ChallengePublic(pub)
+}
+
+// MarshalText implements encoding.TextMarshaler, but by returning an error.
+// It shouldn't need to be marshalled anywhere.
+func (k ChallengePrivate) MarshalText() ([]byte, error) {
+ return nil, errors.New("refusing to marshal")
+}
+
+// SealToChallenge is like SealTo, but for a ChallengePublic.
+func (k NodePrivate) SealToChallenge(p ChallengePublic, cleartext []byte) (ciphertext []byte) {
+ return k.SealTo(NodePublic(p), cleartext)
+}
+
+// OpenFrom opens the NaCl box ciphertext, which must be a value
+// created by NodePrivate.SealToChallenge, and returns the inner cleartext if
+// ciphertext is a valid box from p to k.
+func (k ChallengePrivate) OpenFrom(p NodePublic, ciphertext []byte) (cleartext []byte, ok bool) {
+ return NodePrivate(k).OpenFrom(p, ciphertext)
+}
+
+// ChallengePublic is the public portion of a ChallengePrivate.
+type ChallengePublic struct {
+ k [32]byte
+}
+
+// String returns the output of MarshalText as a string.
+func (k ChallengePublic) String() string {
+ bs, err := k.MarshalText()
+ if err != nil {
+ panic(err)
+ }
+ return string(bs)
+}
+
+// AppendText implements encoding.TextAppender.
+func (k ChallengePublic) AppendText(b []byte) ([]byte, error) {
+ return appendHexKey(b, chalPublicHexPrefix, k.k[:]), nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (k ChallengePublic) MarshalText() ([]byte, error) {
+ return k.AppendText(nil)
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (k *ChallengePublic) UnmarshalText(b []byte) error {
+ return parseHex(k.k[:], mem.B(b), mem.S(chalPublicHexPrefix))
+}
+
+// IsZero reports whether k is the zero value.
+func (k ChallengePublic) IsZero() bool { return k == ChallengePublic{} }
diff --git a/types/key/control.go b/types/key/control.go index 96021249b..a84359771 100644 --- a/types/key/control.go +++ b/types/key/control.go @@ -1,68 +1,68 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import "encoding/json" - -// ControlPrivate is a Tailscale control plane private key. -// -// It is functionally equivalent to a MachinePrivate, but serializes -// to JSON as a byte array rather than a typed string, because our -// control plane database stores the key that way. -// -// Deprecated: this type should only be used in Tailscale's control -// plane, where existing database serializations require this -// less-good serialization format to persist. Other control plane -// implementations can use MachinePrivate with no downsides. -type ControlPrivate struct { - mkey MachinePrivate // unexported so we can limit the API surface to only exactly what we need -} - -// NewControl generates and returns a new control plane private key. -func NewControl() ControlPrivate { - return ControlPrivate{NewMachine()} -} - -// IsZero reports whether k is the zero value. -func (k ControlPrivate) IsZero() bool { - return k.mkey.IsZero() -} - -// Public returns the MachinePublic for k. -// Panics if ControlPrivate is zero. -func (k ControlPrivate) Public() MachinePublic { - return k.mkey.Public() -} - -// MarshalJSON implements json.Marshaler. -func (k ControlPrivate) MarshalJSON() ([]byte, error) { - return json.Marshal(k.mkey.k) -} - -// UnmarshalJSON implements json.Unmarshaler. -func (k *ControlPrivate) UnmarshalJSON(bs []byte) error { - return json.Unmarshal(bs, &k.mkey.k) -} - -// SealTo wraps cleartext into a NaCl box (see -// golang.org/x/crypto/nacl) to p, authenticated from k, using a -// random nonce. -// -// The returned ciphertext is a 24-byte nonce concatenated with the -// box value. -func (k ControlPrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) { - return k.mkey.SealTo(p, cleartext) -} - -// SharedKey returns the precomputed Nacl box shared key between k and p. -func (k ControlPrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey { - return k.mkey.SharedKey(p) -} - -// OpenFrom opens the NaCl box ciphertext, which must be a value -// created by SealTo, and returns the inner cleartext if ciphertext is -// a valid box from p to k. -func (k ControlPrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) { - return k.mkey.OpenFrom(p, ciphertext) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import "encoding/json"
+
+// ControlPrivate is a Tailscale control plane private key.
+//
+// It is functionally equivalent to a MachinePrivate, but serializes
+// to JSON as a byte array rather than a typed string, because our
+// control plane database stores the key that way.
+//
+// Deprecated: this type should only be used in Tailscale's control
+// plane, where existing database serializations require this
+// less-good serialization format to persist. Other control plane
+// implementations can use MachinePrivate with no downsides.
+type ControlPrivate struct {
+ mkey MachinePrivate // unexported so we can limit the API surface to only exactly what we need
+}
+
+// NewControl generates and returns a new control plane private key.
+func NewControl() ControlPrivate {
+ return ControlPrivate{NewMachine()}
+}
+
+// IsZero reports whether k is the zero value.
+func (k ControlPrivate) IsZero() bool {
+ return k.mkey.IsZero()
+}
+
+// Public returns the MachinePublic for k.
+// Panics if ControlPrivate is zero.
+func (k ControlPrivate) Public() MachinePublic {
+ return k.mkey.Public()
+}
+
+// MarshalJSON implements json.Marshaler.
+func (k ControlPrivate) MarshalJSON() ([]byte, error) {
+ return json.Marshal(k.mkey.k)
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (k *ControlPrivate) UnmarshalJSON(bs []byte) error {
+ return json.Unmarshal(bs, &k.mkey.k)
+}
+
+// SealTo wraps cleartext into a NaCl box (see
+// golang.org/x/crypto/nacl) to p, authenticated from k, using a
+// random nonce.
+//
+// The returned ciphertext is a 24-byte nonce concatenated with the
+// box value.
+func (k ControlPrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
+ return k.mkey.SealTo(p, cleartext)
+}
+
+// SharedKey returns the precomputed Nacl box shared key between k and p.
+func (k ControlPrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
+ return k.mkey.SharedKey(p)
+}
+
+// OpenFrom opens the NaCl box ciphertext, which must be a value
+// created by SealTo, and returns the inner cleartext if ciphertext is
+// a valid box from p to k.
+func (k ControlPrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
+ return k.mkey.OpenFrom(p, ciphertext)
+}
diff --git a/types/key/control_test.go b/types/key/control_test.go index a98a586f3..06e0f36d5 100644 --- a/types/key/control_test.go +++ b/types/key/control_test.go @@ -1,38 +1,38 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import ( - "encoding/json" - "testing" -) - -func TestControlKey(t *testing.T) { - serialized := `{"PrivateKey":[36,132,249,6,73,141,249,49,9,96,49,60,240,217,253,57,3,69,248,64,178,62,121,73,121,88,115,218,130,145,68,254]}` - want := ControlPrivate{ - MachinePrivate{ - k: [32]byte{36, 132, 249, 6, 73, 141, 249, 49, 9, 96, 49, 60, 240, 217, 253, 57, 3, 69, 248, 64, 178, 62, 121, 73, 121, 88, 115, 218, 130, 145, 68, 254}, - }, - } - - var got struct { - PrivateKey ControlPrivate - } - if err := json.Unmarshal([]byte(serialized), &got); err != nil { - t.Fatalf("decoding serialized ControlPrivate: %v", err) - } - - if !got.PrivateKey.mkey.Equal(want.mkey) { - t.Fatalf("Serialized ControlPrivate didn't deserialize as expected, got %v want %v", got.PrivateKey, want) - } - - bs, err := json.Marshal(got) - if err != nil { - t.Fatalf("json reserialization of ControlPrivate failed: %v", err) - } - - if got, want := string(bs), serialized; got != want { - t.Fatalf("ControlPrivate didn't round-trip, got %q want %q", got, want) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestControlKey(t *testing.T) {
+ serialized := `{"PrivateKey":[36,132,249,6,73,141,249,49,9,96,49,60,240,217,253,57,3,69,248,64,178,62,121,73,121,88,115,218,130,145,68,254]}`
+ want := ControlPrivate{
+ MachinePrivate{
+ k: [32]byte{36, 132, 249, 6, 73, 141, 249, 49, 9, 96, 49, 60, 240, 217, 253, 57, 3, 69, 248, 64, 178, 62, 121, 73, 121, 88, 115, 218, 130, 145, 68, 254},
+ },
+ }
+
+ var got struct {
+ PrivateKey ControlPrivate
+ }
+ if err := json.Unmarshal([]byte(serialized), &got); err != nil {
+ t.Fatalf("decoding serialized ControlPrivate: %v", err)
+ }
+
+ if !got.PrivateKey.mkey.Equal(want.mkey) {
+ t.Fatalf("Serialized ControlPrivate didn't deserialize as expected, got %v want %v", got.PrivateKey, want)
+ }
+
+ bs, err := json.Marshal(got)
+ if err != nil {
+ t.Fatalf("json reserialization of ControlPrivate failed: %v", err)
+ }
+
+ if got, want := string(bs), serialized; got != want {
+ t.Fatalf("ControlPrivate didn't round-trip, got %q want %q", got, want)
+ }
+}
diff --git a/types/key/disco_test.go b/types/key/disco_test.go index c62c13cbf..c9d60c828 100644 --- a/types/key/disco_test.go +++ b/types/key/disco_test.go @@ -1,83 +1,83 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import ( - "bytes" - "encoding/json" - "testing" -) - -func TestDiscoKey(t *testing.T) { - k := NewDisco() - if k.IsZero() { - t.Fatal("DiscoPrivate should not be zero") - } - - p := k.Public() - if p.IsZero() { - t.Fatal("DiscoPublic should not be zero") - } - - bs, err := p.MarshalText() - if err != nil { - t.Fatal(err) - } - if !bytes.HasPrefix(bs, []byte("discokey:")) { - t.Fatalf("serialization of public discokey %s has wrong prefix", p) - } - - z := DiscoPublic{} - if !z.IsZero() { - t.Fatal("IsZero(DiscoPublic{}) is false") - } - if s := z.ShortString(); s != "" { - t.Fatalf("DiscoPublic{}.ShortString() is %q, want \"\"", s) - } -} - -func TestDiscoSerialization(t *testing.T) { - serialized := `{ - "Pub":"discokey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765" - }` - - pub := DiscoPublic{ - k: [32]uint8{ - 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83, - 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98, - 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65, - }, - } - - type key struct { - Pub DiscoPublic - } - - var a key - if err := json.Unmarshal([]byte(serialized), &a); err != nil { - t.Fatal(err) - } - if a.Pub != pub { - t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub) - } - - bs, err := json.MarshalIndent(a, "", " ") - if err != nil { - t.Fatal(err) - } - - var b bytes.Buffer - json.Indent(&b, []byte(serialized), "", " ") - if got, want := string(bs), b.String(); got != want { - t.Error("json serialization doesn't roundtrip") - } -} - -func TestDiscoShared(t *testing.T) { - k1, k2 := NewDisco(), NewDisco() - s1, s2 := k1.Shared(k2.Public()), k2.Shared(k1.Public()) - if !s1.Equal(s2) { - t.Error("k1.Shared(k2) != k2.Shared(k1)") - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+)
+
+func TestDiscoKey(t *testing.T) {
+ k := NewDisco()
+ if k.IsZero() {
+ t.Fatal("DiscoPrivate should not be zero")
+ }
+
+ p := k.Public()
+ if p.IsZero() {
+ t.Fatal("DiscoPublic should not be zero")
+ }
+
+ bs, err := p.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.HasPrefix(bs, []byte("discokey:")) {
+ t.Fatalf("serialization of public discokey %s has wrong prefix", p)
+ }
+
+ z := DiscoPublic{}
+ if !z.IsZero() {
+ t.Fatal("IsZero(DiscoPublic{}) is false")
+ }
+ if s := z.ShortString(); s != "" {
+ t.Fatalf("DiscoPublic{}.ShortString() is %q, want \"\"", s)
+ }
+}
+
+func TestDiscoSerialization(t *testing.T) {
+ serialized := `{
+ "Pub":"discokey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765"
+ }`
+
+ pub := DiscoPublic{
+ k: [32]uint8{
+ 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83,
+ 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98,
+ 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65,
+ },
+ }
+
+ type key struct {
+ Pub DiscoPublic
+ }
+
+ var a key
+ if err := json.Unmarshal([]byte(serialized), &a); err != nil {
+ t.Fatal(err)
+ }
+ if a.Pub != pub {
+ t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub)
+ }
+
+ bs, err := json.MarshalIndent(a, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var b bytes.Buffer
+ json.Indent(&b, []byte(serialized), "", " ")
+ if got, want := string(bs), b.String(); got != want {
+ t.Error("json serialization doesn't roundtrip")
+ }
+}
+
+func TestDiscoShared(t *testing.T) {
+ k1, k2 := NewDisco(), NewDisco()
+ s1, s2 := k1.Shared(k2.Public()), k2.Shared(k1.Public())
+ if !s1.Equal(s2) {
+ t.Error("k1.Shared(k2) != k2.Shared(k1)")
+ }
+}
diff --git a/types/key/machine.go b/types/key/machine.go index a05f3cc1f..0dc02574c 100644 --- a/types/key/machine.go +++ b/types/key/machine.go @@ -1,264 +1,264 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import ( - "bytes" - "crypto/subtle" - "encoding/hex" - - "go4.org/mem" - "golang.org/x/crypto/curve25519" - "golang.org/x/crypto/nacl/box" - "tailscale.com/types/structs" -) - -const ( - // machinePrivateHexPrefix is the prefix used to identify a - // hex-encoded machine private key. - // - // This prefix name is a little unfortunate, in that it comes from - // WireGuard's own key types. Unfortunately we're stuck with it for - // machine keys, because we serialize them to disk with this prefix. - machinePrivateHexPrefix = "privkey:" - - // machinePublicHexPrefix is the prefix used to identify a - // hex-encoded machine public key. - // - // This prefix is used in the control protocol, so cannot be - // changed. - machinePublicHexPrefix = "mkey:" -) - -// MachinePrivate is a machine key, used for communication with the -// Tailscale coordination server. -type MachinePrivate struct { - _ structs.Incomparable // == isn't constant-time - k [32]byte -} - -// NewMachine creates and returns a new machine private key. -func NewMachine() MachinePrivate { - var ret MachinePrivate - rand(ret.k[:]) - clamp25519Private(ret.k[:]) - return ret -} - -// IsZero reports whether k is the zero value. -func (k MachinePrivate) IsZero() bool { - return k.Equal(MachinePrivate{}) -} - -// Equal reports whether k and other are the same key. -func (k MachinePrivate) Equal(other MachinePrivate) bool { - return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1 -} - -// Public returns the MachinePublic for k. -// Panics if MachinePrivate is zero. -func (k MachinePrivate) Public() MachinePublic { - if k.IsZero() { - panic("can't take the public key of a zero MachinePrivate") - } - var ret MachinePublic - curve25519.ScalarBaseMult(&ret.k, &k.k) - return ret -} - -// AppendText implements encoding.TextAppender. -func (k MachinePrivate) AppendText(b []byte) ([]byte, error) { - return appendHexKey(b, machinePrivateHexPrefix, k.k[:]), nil -} - -// MarshalText implements encoding.TextMarshaler. -func (k MachinePrivate) MarshalText() ([]byte, error) { - return k.AppendText(nil) -} - -// MarshalText implements encoding.TextUnmarshaler. -func (k *MachinePrivate) UnmarshalText(b []byte) error { - return parseHex(k.k[:], mem.B(b), mem.S(machinePrivateHexPrefix)) -} - -// UntypedBytes returns k, encoded as an untyped 64-character hex -// string. -// -// Deprecated: this function is risky to use, because it produces -// serialized values that do not identify themselves as a -// MachinePrivate, allowing other code to potentially parse it back in -// as the wrong key type. For new uses that don't require this -// specific raw byte serialization, please use -// MarshalText/UnmarshalText. -func (k MachinePrivate) UntypedBytes() []byte { - return bytes.Clone(k.k[:]) -} - -// SealTo wraps cleartext into a NaCl box (see -// golang.org/x/crypto/nacl) to p, authenticated from k, using a -// random nonce. -// -// The returned ciphertext is a 24-byte nonce concatenated with the -// box value. -func (k MachinePrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) { - if k.IsZero() || p.IsZero() { - panic("can't seal with zero keys") - } - var nonce [24]byte - rand(nonce[:]) - return box.Seal(nonce[:], cleartext, &nonce, &p.k, &k.k) -} - -// SharedKey returns the precomputed Nacl box shared key between k and p. -func (k MachinePrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey { - var shared MachinePrecomputedSharedKey - box.Precompute(&shared.k, &p.k, &k.k) - return shared -} - -// MachinePrecomputedSharedKey is a precomputed shared NaCl box shared key. -type MachinePrecomputedSharedKey struct { - k [32]byte -} - -// Seal wraps cleartext into a NaCl box (see -// golang.org/x/crypto/nacl) using the shared key k as generated -// by MachinePrivate.SharedKey. -// -// The returned ciphertext is a 24-byte nonce concatenated with the -// box value. -func (k MachinePrecomputedSharedKey) Seal(cleartext []byte) (ciphertext []byte) { - if k == (MachinePrecomputedSharedKey{}) { - panic("can't seal with zero keys") - } - var nonce [24]byte - rand(nonce[:]) - return box.SealAfterPrecomputation(nonce[:], cleartext, &nonce, &k.k) -} - -// Open opens the NaCl box ciphertext, which must be a value created by -// MachinePrecomputedSharedKey.Seal or MachinePrivate.SealTo, and returns the -// inner cleartext if ciphertext is a valid box for the shared key k. -func (k MachinePrecomputedSharedKey) Open(ciphertext []byte) (cleartext []byte, ok bool) { - if k == (MachinePrecomputedSharedKey{}) { - panic("can't open with zero keys") - } - if len(ciphertext) < 24 { - return nil, false - } - var nonce [24]byte - copy(nonce[:], ciphertext) - return box.OpenAfterPrecomputation(nil, ciphertext[len(nonce):], &nonce, &k.k) -} - -// OpenFrom opens the NaCl box ciphertext, which must be a value -// created by SealTo, and returns the inner cleartext if ciphertext is -// a valid box from p to k. -func (k MachinePrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) { - if k.IsZero() || p.IsZero() { - panic("can't open with zero keys") - } - if len(ciphertext) < 24 { - return nil, false - } - var nonce [24]byte - copy(nonce[:], ciphertext) - return box.Open(nil, ciphertext[len(nonce):], &nonce, &p.k, &k.k) -} - -// MachinePublic is the public portion of a a MachinePrivate. -type MachinePublic struct { - k [32]byte -} - -// MachinePublicFromRaw32 parses a 32-byte raw value as a MachinePublic. -// -// This should be used only when deserializing a MachinePublic from a -// binary protocol. -func MachinePublicFromRaw32(raw mem.RO) MachinePublic { - if raw.Len() != 32 { - panic("input has wrong size") - } - var ret MachinePublic - raw.Copy(ret.k[:]) - return ret -} - -// ParseMachinePublicUntyped parses an untyped 64-character hex value -// as a MachinePublic. -// -// Deprecated: this function is risky to use, because it cannot verify -// that the hex string was intended to be a MachinePublic. This can -// lead to accidentally decoding one type of key as another. For new -// uses that don't require backwards compatibility with the untyped -// string format, please use MarshalText/UnmarshalText. -func ParseMachinePublicUntyped(raw mem.RO) (MachinePublic, error) { - var ret MachinePublic - if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil { - return MachinePublic{}, err - } - return ret, nil -} - -// IsZero reports whether k is the zero value. -func (k MachinePublic) IsZero() bool { - return k == MachinePublic{} -} - -// ShortString returns the Tailscale conventional debug representation -// of a public key: the first five base64 digits of the key, in square -// brackets. -func (k MachinePublic) ShortString() string { - return debug32(k.k) -} - -// UntypedHexString returns k, encoded as an untyped 64-character hex -// string. -// -// Deprecated: this function is risky to use, because it produces -// serialized values that do not identify themselves as a -// MachinePublic, allowing other code to potentially parse it back in -// as the wrong key type. For new uses that don't require backwards -// compatibility with the untyped string format, please use -// MarshalText/UnmarshalText. -func (k MachinePublic) UntypedHexString() string { - return hex.EncodeToString(k.k[:]) -} - -// UntypedBytes returns k, encoded as an untyped 64-character hex -// string. -// -// Deprecated: this function is risky to use, because it produces -// serialized values that do not identify themselves as a -// MachinePublic, allowing other code to potentially parse it back in -// as the wrong key type. For new uses that don't require this -// specific raw byte serialization, please use -// MarshalText/UnmarshalText. -func (k MachinePublic) UntypedBytes() []byte { - return bytes.Clone(k.k[:]) -} - -// String returns the output of MarshalText as a string. -func (k MachinePublic) String() string { - bs, err := k.MarshalText() - if err != nil { - panic(err) - } - return string(bs) -} - -// AppendText implements encoding.TextAppender. -func (k MachinePublic) AppendText(b []byte) ([]byte, error) { - return appendHexKey(b, machinePublicHexPrefix, k.k[:]), nil -} - -// MarshalText implements encoding.TextMarshaler. -func (k MachinePublic) MarshalText() ([]byte, error) { - return k.AppendText(nil) -} - -// MarshalText implements encoding.TextUnmarshaler. -func (k *MachinePublic) UnmarshalText(b []byte) error { - return parseHex(k.k[:], mem.B(b), mem.S(machinePublicHexPrefix)) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "crypto/subtle"
+ "encoding/hex"
+
+ "go4.org/mem"
+ "golang.org/x/crypto/curve25519"
+ "golang.org/x/crypto/nacl/box"
+ "tailscale.com/types/structs"
+)
+
+const (
+ // machinePrivateHexPrefix is the prefix used to identify a
+ // hex-encoded machine private key.
+ //
+ // This prefix name is a little unfortunate, in that it comes from
+ // WireGuard's own key types. Unfortunately we're stuck with it for
+ // machine keys, because we serialize them to disk with this prefix.
+ machinePrivateHexPrefix = "privkey:"
+
+ // machinePublicHexPrefix is the prefix used to identify a
+ // hex-encoded machine public key.
+ //
+ // This prefix is used in the control protocol, so cannot be
+ // changed.
+ machinePublicHexPrefix = "mkey:"
+)
+
+// MachinePrivate is a machine key, used for communication with the
+// Tailscale coordination server.
+type MachinePrivate struct {
+ _ structs.Incomparable // == isn't constant-time
+ k [32]byte
+}
+
+// NewMachine creates and returns a new machine private key.
+func NewMachine() MachinePrivate {
+ var ret MachinePrivate
+ rand(ret.k[:])
+ clamp25519Private(ret.k[:])
+ return ret
+}
+
+// IsZero reports whether k is the zero value.
+func (k MachinePrivate) IsZero() bool {
+ return k.Equal(MachinePrivate{})
+}
+
+// Equal reports whether k and other are the same key.
+func (k MachinePrivate) Equal(other MachinePrivate) bool {
+ return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
+}
+
+// Public returns the MachinePublic for k.
+// Panics if MachinePrivate is zero.
+func (k MachinePrivate) Public() MachinePublic {
+ if k.IsZero() {
+ panic("can't take the public key of a zero MachinePrivate")
+ }
+ var ret MachinePublic
+ curve25519.ScalarBaseMult(&ret.k, &k.k)
+ return ret
+}
+
+// AppendText implements encoding.TextAppender.
+func (k MachinePrivate) AppendText(b []byte) ([]byte, error) {
+ return appendHexKey(b, machinePrivateHexPrefix, k.k[:]), nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (k MachinePrivate) MarshalText() ([]byte, error) {
+ return k.AppendText(nil)
+}
+
+// MarshalText implements encoding.TextUnmarshaler.
+func (k *MachinePrivate) UnmarshalText(b []byte) error {
+ return parseHex(k.k[:], mem.B(b), mem.S(machinePrivateHexPrefix))
+}
+
+// UntypedBytes returns k, encoded as an untyped 64-character hex
+// string.
+//
+// Deprecated: this function is risky to use, because it produces
+// serialized values that do not identify themselves as a
+// MachinePrivate, allowing other code to potentially parse it back in
+// as the wrong key type. For new uses that don't require this
+// specific raw byte serialization, please use
+// MarshalText/UnmarshalText.
+func (k MachinePrivate) UntypedBytes() []byte {
+ return bytes.Clone(k.k[:])
+}
+
+// SealTo wraps cleartext into a NaCl box (see
+// golang.org/x/crypto/nacl) to p, authenticated from k, using a
+// random nonce.
+//
+// The returned ciphertext is a 24-byte nonce concatenated with the
+// box value.
+func (k MachinePrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
+ if k.IsZero() || p.IsZero() {
+ panic("can't seal with zero keys")
+ }
+ var nonce [24]byte
+ rand(nonce[:])
+ return box.Seal(nonce[:], cleartext, &nonce, &p.k, &k.k)
+}
+
+// SharedKey returns the precomputed Nacl box shared key between k and p.
+func (k MachinePrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
+ var shared MachinePrecomputedSharedKey
+ box.Precompute(&shared.k, &p.k, &k.k)
+ return shared
+}
+
+// MachinePrecomputedSharedKey is a precomputed shared NaCl box shared key.
+type MachinePrecomputedSharedKey struct {
+ k [32]byte
+}
+
+// Seal wraps cleartext into a NaCl box (see
+// golang.org/x/crypto/nacl) using the shared key k as generated
+// by MachinePrivate.SharedKey.
+//
+// The returned ciphertext is a 24-byte nonce concatenated with the
+// box value.
+func (k MachinePrecomputedSharedKey) Seal(cleartext []byte) (ciphertext []byte) {
+ if k == (MachinePrecomputedSharedKey{}) {
+ panic("can't seal with zero keys")
+ }
+ var nonce [24]byte
+ rand(nonce[:])
+ return box.SealAfterPrecomputation(nonce[:], cleartext, &nonce, &k.k)
+}
+
+// Open opens the NaCl box ciphertext, which must be a value created by
+// MachinePrecomputedSharedKey.Seal or MachinePrivate.SealTo, and returns the
+// inner cleartext if ciphertext is a valid box for the shared key k.
+func (k MachinePrecomputedSharedKey) Open(ciphertext []byte) (cleartext []byte, ok bool) {
+ if k == (MachinePrecomputedSharedKey{}) {
+ panic("can't open with zero keys")
+ }
+ if len(ciphertext) < 24 {
+ return nil, false
+ }
+ var nonce [24]byte
+ copy(nonce[:], ciphertext)
+ return box.OpenAfterPrecomputation(nil, ciphertext[len(nonce):], &nonce, &k.k)
+}
+
+// OpenFrom opens the NaCl box ciphertext, which must be a value
+// created by SealTo, and returns the inner cleartext if ciphertext is
+// a valid box from p to k.
+func (k MachinePrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
+ if k.IsZero() || p.IsZero() {
+ panic("can't open with zero keys")
+ }
+ if len(ciphertext) < 24 {
+ return nil, false
+ }
+ var nonce [24]byte
+ copy(nonce[:], ciphertext)
+ return box.Open(nil, ciphertext[len(nonce):], &nonce, &p.k, &k.k)
+}
+
+// MachinePublic is the public portion of a a MachinePrivate.
+type MachinePublic struct {
+ k [32]byte
+}
+
+// MachinePublicFromRaw32 parses a 32-byte raw value as a MachinePublic.
+//
+// This should be used only when deserializing a MachinePublic from a
+// binary protocol.
+func MachinePublicFromRaw32(raw mem.RO) MachinePublic {
+ if raw.Len() != 32 {
+ panic("input has wrong size")
+ }
+ var ret MachinePublic
+ raw.Copy(ret.k[:])
+ return ret
+}
+
+// ParseMachinePublicUntyped parses an untyped 64-character hex value
+// as a MachinePublic.
+//
+// Deprecated: this function is risky to use, because it cannot verify
+// that the hex string was intended to be a MachinePublic. This can
+// lead to accidentally decoding one type of key as another. For new
+// uses that don't require backwards compatibility with the untyped
+// string format, please use MarshalText/UnmarshalText.
+func ParseMachinePublicUntyped(raw mem.RO) (MachinePublic, error) {
+ var ret MachinePublic
+ if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil {
+ return MachinePublic{}, err
+ }
+ return ret, nil
+}
+
+// IsZero reports whether k is the zero value.
+func (k MachinePublic) IsZero() bool {
+ return k == MachinePublic{}
+}
+
+// ShortString returns the Tailscale conventional debug representation
+// of a public key: the first five base64 digits of the key, in square
+// brackets.
+func (k MachinePublic) ShortString() string {
+ return debug32(k.k)
+}
+
+// UntypedHexString returns k, encoded as an untyped 64-character hex
+// string.
+//
+// Deprecated: this function is risky to use, because it produces
+// serialized values that do not identify themselves as a
+// MachinePublic, allowing other code to potentially parse it back in
+// as the wrong key type. For new uses that don't require backwards
+// compatibility with the untyped string format, please use
+// MarshalText/UnmarshalText.
+func (k MachinePublic) UntypedHexString() string {
+ return hex.EncodeToString(k.k[:])
+}
+
+// UntypedBytes returns k, encoded as an untyped 64-character hex
+// string.
+//
+// Deprecated: this function is risky to use, because it produces
+// serialized values that do not identify themselves as a
+// MachinePublic, allowing other code to potentially parse it back in
+// as the wrong key type. For new uses that don't require this
+// specific raw byte serialization, please use
+// MarshalText/UnmarshalText.
+func (k MachinePublic) UntypedBytes() []byte {
+ return bytes.Clone(k.k[:])
+}
+
+// String returns the output of MarshalText as a string.
+func (k MachinePublic) String() string {
+ bs, err := k.MarshalText()
+ if err != nil {
+ panic(err)
+ }
+ return string(bs)
+}
+
+// AppendText implements encoding.TextAppender.
+func (k MachinePublic) AppendText(b []byte) ([]byte, error) {
+ return appendHexKey(b, machinePublicHexPrefix, k.k[:]), nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (k MachinePublic) MarshalText() ([]byte, error) {
+ return k.AppendText(nil)
+}
+
+// MarshalText implements encoding.TextUnmarshaler.
+func (k *MachinePublic) UnmarshalText(b []byte) error {
+ return parseHex(k.k[:], mem.B(b), mem.S(machinePublicHexPrefix))
+}
diff --git a/types/key/machine_test.go b/types/key/machine_test.go index 157df9e43..f797ff087 100644 --- a/types/key/machine_test.go +++ b/types/key/machine_test.go @@ -1,119 +1,119 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import ( - "bytes" - "encoding/json" - "strings" - "testing" -) - -func TestMachineKey(t *testing.T) { - k := NewMachine() - if k.IsZero() { - t.Fatal("MachinePrivate should not be zero") - } - - p := k.Public() - if p.IsZero() { - t.Fatal("MachinePublic should not be zero") - } - - bs, err := p.MarshalText() - if err != nil { - t.Fatal(err) - } - if full, got := string(bs), ":"+p.UntypedHexString(); !strings.HasSuffix(full, got) { - t.Fatalf("MachinePublic.UntypedHexString is not a suffix of the typed serialization, got %q want suffix of %q", got, full) - } - - z := MachinePublic{} - if !z.IsZero() { - t.Fatal("IsZero(MachinePublic{}) is false") - } - if s := z.ShortString(); s != "" { - t.Fatalf("MachinePublic{}.ShortString() is %q, want \"\"", s) - } -} - -func TestMachineSerialization(t *testing.T) { - serialized := `{ - "Priv": "privkey:40ab1b58e9076c7a4d9d07291f5edf9d1aa017eb949624ba683317f48a640369", - "Pub":"mkey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765" - }` - - // Carefully check that the expected serialized data decodes and - // reencodes to the expected keys. These types are serialized to - // disk all over the place and need to be stable. - priv := MachinePrivate{ - k: [32]uint8{ - 0x40, 0xab, 0x1b, 0x58, 0xe9, 0x7, 0x6c, 0x7a, 0x4d, 0x9d, 0x7, - 0x29, 0x1f, 0x5e, 0xdf, 0x9d, 0x1a, 0xa0, 0x17, 0xeb, 0x94, - 0x96, 0x24, 0xba, 0x68, 0x33, 0x17, 0xf4, 0x8a, 0x64, 0x3, 0x69, - }, - } - pub := MachinePublic{ - k: [32]uint8{ - 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83, - 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98, - 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65, - }, - } - - type keypair struct { - Priv MachinePrivate - Pub MachinePublic - } - - var a keypair - if err := json.Unmarshal([]byte(serialized), &a); err != nil { - t.Fatal(err) - } - if !a.Priv.Equal(priv) { - t.Errorf("wrong deserialization of private key, got %#v want %#v", a.Priv, priv) - } - if a.Pub != pub { - t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub) - } - - bs, err := json.MarshalIndent(a, "", " ") - if err != nil { - t.Fatal(err) - } - - var b bytes.Buffer - json.Indent(&b, []byte(serialized), "", " ") - if got, want := string(bs), b.String(); got != want { - t.Error("json serialization doesn't roundtrip") - } -} - -func TestSealViaSharedKey(t *testing.T) { - // encrypt a message from a to b - a := NewMachine() - b := NewMachine() - apub, bpub := a.Public(), b.Public() - - shared := a.SharedKey(bpub) - - const clear = "the eagle flies at midnight" - enc := shared.Seal([]byte(clear)) - - back, ok := b.OpenFrom(apub, enc) - if !ok { - t.Fatal("failed to decrypt") - } - if string(back) != clear { - t.Errorf("OpenFrom got %q; want cleartext %q", back, clear) - } - - backShared, ok := shared.Open(enc) - if !ok { - t.Fatal("failed to decrypt from shared key") - } - if string(backShared) != clear { - t.Errorf("Open got %q; want cleartext %q", back, clear) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "encoding/json"
+ "strings"
+ "testing"
+)
+
+func TestMachineKey(t *testing.T) {
+ k := NewMachine()
+ if k.IsZero() {
+ t.Fatal("MachinePrivate should not be zero")
+ }
+
+ p := k.Public()
+ if p.IsZero() {
+ t.Fatal("MachinePublic should not be zero")
+ }
+
+ bs, err := p.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if full, got := string(bs), ":"+p.UntypedHexString(); !strings.HasSuffix(full, got) {
+ t.Fatalf("MachinePublic.UntypedHexString is not a suffix of the typed serialization, got %q want suffix of %q", got, full)
+ }
+
+ z := MachinePublic{}
+ if !z.IsZero() {
+ t.Fatal("IsZero(MachinePublic{}) is false")
+ }
+ if s := z.ShortString(); s != "" {
+ t.Fatalf("MachinePublic{}.ShortString() is %q, want \"\"", s)
+ }
+}
+
+func TestMachineSerialization(t *testing.T) {
+ serialized := `{
+ "Priv": "privkey:40ab1b58e9076c7a4d9d07291f5edf9d1aa017eb949624ba683317f48a640369",
+ "Pub":"mkey:50d20b455ecf12bc453f83c2cfdb2a24925d06cf2598dcaa54e91af82ce9f765"
+ }`
+
+ // Carefully check that the expected serialized data decodes and
+ // reencodes to the expected keys. These types are serialized to
+ // disk all over the place and need to be stable.
+ priv := MachinePrivate{
+ k: [32]uint8{
+ 0x40, 0xab, 0x1b, 0x58, 0xe9, 0x7, 0x6c, 0x7a, 0x4d, 0x9d, 0x7,
+ 0x29, 0x1f, 0x5e, 0xdf, 0x9d, 0x1a, 0xa0, 0x17, 0xeb, 0x94,
+ 0x96, 0x24, 0xba, 0x68, 0x33, 0x17, 0xf4, 0x8a, 0x64, 0x3, 0x69,
+ },
+ }
+ pub := MachinePublic{
+ k: [32]uint8{
+ 0x50, 0xd2, 0xb, 0x45, 0x5e, 0xcf, 0x12, 0xbc, 0x45, 0x3f, 0x83,
+ 0xc2, 0xcf, 0xdb, 0x2a, 0x24, 0x92, 0x5d, 0x6, 0xcf, 0x25, 0x98,
+ 0xdc, 0xaa, 0x54, 0xe9, 0x1a, 0xf8, 0x2c, 0xe9, 0xf7, 0x65,
+ },
+ }
+
+ type keypair struct {
+ Priv MachinePrivate
+ Pub MachinePublic
+ }
+
+ var a keypair
+ if err := json.Unmarshal([]byte(serialized), &a); err != nil {
+ t.Fatal(err)
+ }
+ if !a.Priv.Equal(priv) {
+ t.Errorf("wrong deserialization of private key, got %#v want %#v", a.Priv, priv)
+ }
+ if a.Pub != pub {
+ t.Errorf("wrong deserialization of public key, got %#v want %#v", a.Pub, pub)
+ }
+
+ bs, err := json.MarshalIndent(a, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var b bytes.Buffer
+ json.Indent(&b, []byte(serialized), "", " ")
+ if got, want := string(bs), b.String(); got != want {
+ t.Error("json serialization doesn't roundtrip")
+ }
+}
+
+func TestSealViaSharedKey(t *testing.T) {
+ // encrypt a message from a to b
+ a := NewMachine()
+ b := NewMachine()
+ apub, bpub := a.Public(), b.Public()
+
+ shared := a.SharedKey(bpub)
+
+ const clear = "the eagle flies at midnight"
+ enc := shared.Seal([]byte(clear))
+
+ back, ok := b.OpenFrom(apub, enc)
+ if !ok {
+ t.Fatal("failed to decrypt")
+ }
+ if string(back) != clear {
+ t.Errorf("OpenFrom got %q; want cleartext %q", back, clear)
+ }
+
+ backShared, ok := shared.Open(enc)
+ if !ok {
+ t.Fatal("failed to decrypt from shared key")
+ }
+ if string(backShared) != clear {
+ t.Errorf("Open got %q; want cleartext %q", back, clear)
+ }
+}
diff --git a/types/key/nl_test.go b/types/key/nl_test.go index 75b7765a1..2e10d04ac 100644 --- a/types/key/nl_test.go +++ b/types/key/nl_test.go @@ -1,48 +1,48 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package key - -import ( - "bytes" - "testing" -) - -func TestNLPrivate(t *testing.T) { - p := NewNLPrivate() - - encoded, err := p.MarshalText() - if err != nil { - t.Fatal(err) - } - var decoded NLPrivate - if err := decoded.UnmarshalText(encoded); err != nil { - t.Fatal(err) - } - if !bytes.Equal(decoded.k[:], p.k[:]) { - t.Error("decoded and generated NLPrivate bytes differ") - } - - // Test NLPublic - pub := p.Public() - encoded, err = pub.MarshalText() - if err != nil { - t.Fatal(err) - } - var decodedPub NLPublic - if err := decodedPub.UnmarshalText(encoded); err != nil { - t.Fatal(err) - } - if !bytes.Equal(decodedPub.k[:], pub.k[:]) { - t.Error("decoded and generated NLPublic bytes differ") - } - - // Test decoding with CLI prefix: 'nlpub:' => 'tlpub:' - decodedPub = NLPublic{} - if err := decodedPub.UnmarshalText([]byte(pub.CLIString())); err != nil { - t.Fatal(err) - } - if !bytes.Equal(decodedPub.k[:], pub.k[:]) { - t.Error("decoded and generated NLPublic bytes differ (CLI prefix)") - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package key
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestNLPrivate(t *testing.T) {
+ p := NewNLPrivate()
+
+ encoded, err := p.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var decoded NLPrivate
+ if err := decoded.UnmarshalText(encoded); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(decoded.k[:], p.k[:]) {
+ t.Error("decoded and generated NLPrivate bytes differ")
+ }
+
+ // Test NLPublic
+ pub := p.Public()
+ encoded, err = pub.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var decodedPub NLPublic
+ if err := decodedPub.UnmarshalText(encoded); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
+ t.Error("decoded and generated NLPublic bytes differ")
+ }
+
+ // Test decoding with CLI prefix: 'nlpub:' => 'tlpub:'
+ decodedPub = NLPublic{}
+ if err := decodedPub.UnmarshalText([]byte(pub.CLIString())); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
+ t.Error("decoded and generated NLPublic bytes differ (CLI prefix)")
+ }
+}
|
