summaryrefslogtreecommitdiffhomepage
path: root/types/key
diff options
context:
space:
mode:
Diffstat (limited to 'types/key')
-rw-r--r--types/key/chal.go182
-rw-r--r--types/key/control.go136
-rw-r--r--types/key/control_test.go76
-rw-r--r--types/key/disco_test.go166
-rw-r--r--types/key/machine.go528
-rw-r--r--types/key/machine_test.go238
-rw-r--r--types/key/nl_test.go96
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)")
+ }
+}