Revert "types/key: add MachinePrivate and MachinePublic."

Broke the tailscale control plane due to surprise different serialization.

This reverts commit 4fdb88efe1.
pull/2792/head
David Anderson 3 years ago
parent 4fdb88efe1
commit 61c3b98a24

@ -77,7 +77,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+ golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/hkdf from crypto/tls golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/derp+ golang.org/x/crypto/nacl/box from tailscale.com/derp
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+ golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+

@ -189,7 +189,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+ golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/hkdf from crypto/tls golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/derp+ golang.org/x/crypto/nacl/box from tailscale.com/control/controlclient+
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+ golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+

@ -7,6 +7,7 @@ package controlclient
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
@ -27,7 +28,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"go4.org/mem" "golang.org/x/crypto/nacl/box"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/health" "tailscale.com/health"
@ -41,7 +42,6 @@ import (
"tailscale.com/net/tlsdial" "tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy" "tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/types/opt" "tailscale.com/types/opt"
@ -62,14 +62,14 @@ type Direct struct {
logf logger.Logf logf logger.Logf
linkMon *monitor.Mon // or nil linkMon *monitor.Mon // or nil
discoPubKey tailcfg.DiscoKey discoPubKey tailcfg.DiscoKey
getMachinePrivKey func() (key.MachinePrivate, error) getMachinePrivKey func() (wgkey.Private, error)
debugFlags []string debugFlags []string
keepSharerAndUserSplit bool keepSharerAndUserSplit bool
skipIPForwardingCheck bool skipIPForwardingCheck bool
pinger Pinger pinger Pinger
mu sync.Mutex // mutex guards the following fields mu sync.Mutex // mutex guards the following fields
serverKey key.MachinePublic serverKey wgkey.Key
persist persist.Persist persist persist.Persist
authKey string authKey string
tryingNewKey wgkey.Private tryingNewKey wgkey.Private
@ -84,7 +84,7 @@ type Direct struct {
type Options struct { type Options struct {
Persist persist.Persist // initial persistent data Persist persist.Persist // initial persistent data
GetMachinePrivateKey func() (key.MachinePrivate, error) // returns the machine key to use GetMachinePrivateKey func() (wgkey.Private, error) // returns the machine key to use
ServerURL string // URL of the tailcontrol server ServerURL string // URL of the tailcontrol server
AuthKey string // optional node auth key for auto registration AuthKey string // optional node auth key for auto registration
TimeNow func() time.Time // time.Now implementation used by Client TimeNow func() time.Time // time.Now implementation used by Client
@ -320,7 +320,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
if err != nil { if err != nil {
return regen, opt.URL, err return regen, opt.URL, err
} }
c.logf("control server key %s from %s", serverKey.ShortString(), c.serverURL) c.logf("control server key %s from %s", serverKey.HexString(), c.serverURL)
c.mu.Lock() c.mu.Lock()
c.serverKey = serverKey c.serverKey = serverKey
@ -398,13 +398,13 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("RegisterRequest: %s", j) c.logf("RegisterRequest: %s", j)
} }
bodyData, err := encode(request, serverKey, machinePrivKey) bodyData, err := encode(request, &serverKey, &machinePrivKey)
if err != nil { if err != nil {
return regen, opt.URL, err return regen, opt.URL, err
} }
body := bytes.NewReader(bodyData) body := bytes.NewReader(bodyData)
u := fmt.Sprintf("%s/machine/%s", c.serverURL, machinePrivKey.Public().UntypedHexString()) u := fmt.Sprintf("%s/machine/%s", c.serverURL, machinePrivKey.Public().HexString())
req, err := http.NewRequest("POST", u, body) req, err := http.NewRequest("POST", u, body)
if err != nil { if err != nil {
return regen, opt.URL, err return regen, opt.URL, err
@ -422,7 +422,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
res.StatusCode, strings.TrimSpace(string(msg))) res.StatusCode, strings.TrimSpace(string(msg)))
} }
resp := tailcfg.RegisterResponse{} resp := tailcfg.RegisterResponse{}
if err := decode(res, &resp, serverKey, machinePrivKey); err != nil { if err := decode(res, &resp, &serverKey, &machinePrivKey); err != nil {
c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err) c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
return regen, opt.URL, fmt.Errorf("register request: %v", err) return regen, opt.URL, fmt.Errorf("register request: %v", err)
} }
@ -636,7 +636,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
request.ReadOnly = true request.ReadOnly = true
} }
bodyData, err := encode(request, serverKey, machinePrivKey) bodyData, err := encode(request, &serverKey, &machinePrivKey)
if err != nil { if err != nil {
vlogf("netmap: encode: %v", err) vlogf("netmap: encode: %v", err)
return err return err
@ -645,9 +645,9 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
machinePubKey := machinePrivKey.Public() machinePubKey := tailcfg.MachineKey(machinePrivKey.Public())
t0 := time.Now() t0 := time.Now()
u := fmt.Sprintf("%s/machine/%s/map", serverURL, machinePubKey.UntypedHexString()) u := fmt.Sprintf("%s/machine/%s/map", serverURL, machinePubKey.HexString())
req, err := http.NewRequestWithContext(ctx, "POST", u, bytes.NewReader(bodyData)) req, err := http.NewRequestWithContext(ctx, "POST", u, bytes.NewReader(bodyData))
if err != nil { if err != nil {
@ -734,7 +734,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
vlogf("netmap: read body after %v", time.Since(t0).Round(time.Millisecond)) vlogf("netmap: read body after %v", time.Since(t0).Round(time.Millisecond))
var resp tailcfg.MapResponse var resp tailcfg.MapResponse
if err := c.decodeMsg(msg, &resp, machinePrivKey); err != nil { if err := c.decodeMsg(msg, &resp, &machinePrivKey); err != nil {
vlogf("netmap: decode error: %v") vlogf("netmap: decode error: %v")
return err return err
} }
@ -830,7 +830,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
return nil return nil
} }
func decode(res *http.Response, v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate) error { func decode(res *http.Response, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error {
defer res.Body.Close() defer res.Body.Close()
msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20)) msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil { if err != nil {
@ -849,14 +849,14 @@ var (
var jsonEscapedZero = []byte(`\u0000`) var jsonEscapedZero = []byte(`\u0000`)
func (c *Direct) decodeMsg(msg []byte, v interface{}, machinePrivKey key.MachinePrivate) error { func (c *Direct) decodeMsg(msg []byte, v interface{}, machinePrivKey *wgkey.Private) error {
c.mu.Lock() c.mu.Lock()
serverKey := c.serverKey serverKey := c.serverKey
c.mu.Unlock() c.mu.Unlock()
decrypted, ok := machinePrivKey.OpenFrom(serverKey, msg) decrypted, err := decryptMsg(msg, &serverKey, machinePrivKey)
if !ok { if err != nil {
return errors.New("cannot decrypt response") return err
} }
var b []byte var b []byte
if c.newDecompressor == nil { if c.newDecompressor == nil {
@ -888,10 +888,10 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}, machinePrivKey key.Machine
} }
func decodeMsg(msg []byte, v interface{}, serverKey key.MachinePublic, machinePrivKey key.MachinePrivate) error { func decodeMsg(msg []byte, v interface{}, serverKey *wgkey.Key, machinePrivKey *wgkey.Private) error {
decrypted, ok := machinePrivKey.OpenFrom(serverKey, msg) decrypted, err := decryptMsg(msg, serverKey, machinePrivKey)
if !ok { if err != nil {
return errors.New("cannot decrypt response") return err
} }
if bytes.Contains(decrypted, jsonEscapedZero) { if bytes.Contains(decrypted, jsonEscapedZero) {
log.Printf("[unexpected] zero byte in controlclient decodeMsg into %T: %q", v, decrypted) log.Printf("[unexpected] zero byte in controlclient decodeMsg into %T: %q", v, decrypted)
@ -902,7 +902,23 @@ func decodeMsg(msg []byte, v interface{}, serverKey key.MachinePublic, machinePr
return nil return nil
} }
func encode(v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate) ([]byte, error) { func decryptMsg(msg []byte, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) {
var nonce [24]byte
if len(msg) < len(nonce)+1 {
return nil, fmt.Errorf("response missing nonce, len=%d", len(msg))
}
copy(nonce[:], msg)
msg = msg[len(nonce):]
pub, pri := (*[32]byte)(serverKey), (*[32]byte)(mkey)
decrypted, ok := box.Open(nil, msg, &nonce, pub, pri)
if !ok {
return nil, fmt.Errorf("cannot decrypt response (len %d + nonce %d = %d)", len(msg), len(nonce), len(msg)+len(nonce))
}
return decrypted, nil
}
func encode(v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { if err != nil {
return nil, err return nil, err
@ -912,32 +928,38 @@ func encode(v interface{}, serverKey key.MachinePublic, mkey key.MachinePrivate)
log.Printf("MapRequest: %s", b) log.Printf("MapRequest: %s", b)
} }
} }
return mkey.SealTo(serverKey, b), nil var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
pub, pri := (*[32]byte)(serverKey), (*[32]byte)(mkey)
msg := box.Seal(nonce[:], b, &nonce, pub, pri)
return msg, nil
} }
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (key.MachinePublic, error) { func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgkey.Key, error) {
req, err := http.NewRequest("GET", serverURL+"/key", nil) req, err := http.NewRequest("GET", serverURL+"/key", nil)
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("create control key request: %v", err) return wgkey.Key{}, fmt.Errorf("create control key request: %v", err)
} }
req = req.WithContext(ctx) req = req.WithContext(ctx)
res, err := httpc.Do(req) res, err := httpc.Do(req)
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %v", err) return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err)
} }
defer res.Body.Close() defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16)) b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16))
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key response: %v", err) return wgkey.Key{}, fmt.Errorf("fetch control key response: %v", err)
} }
if res.StatusCode != 200 { if res.StatusCode != 200 {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b)) return wgkey.Key{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b))
} }
k, err := key.ParseMachinePublicUntyped(mem.B(b)) key, err := wgkey.ParseHex(string(b))
if err != nil { if err != nil {
return key.MachinePublic{}, fmt.Errorf("fetch control key: %v", err) return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err)
} }
return k, nil return key, nil
} }
// Debug contains temporary internal-only debug knobs. // Debug contains temporary internal-only debug knobs.
@ -1185,13 +1207,13 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
return errors.New("getMachinePrivKey returned zero key") return errors.New("getMachinePrivKey returned zero key")
} }
bodyData, err := encode(req, serverKey, machinePrivKey) bodyData, err := encode(req, &serverKey, &machinePrivKey)
if err != nil { if err != nil {
return err return err
} }
body := bytes.NewReader(bodyData) body := bytes.NewReader(bodyData)
u := fmt.Sprintf("%s/machine/%s/set-dns", c.serverURL, machinePrivKey.Public().UntypedHexString()) u := fmt.Sprintf("%s/machine/%s/set-dns", c.serverURL, machinePrivKey.Public().HexString())
hreq, err := http.NewRequestWithContext(ctx, "POST", u, body) hreq, err := http.NewRequestWithContext(ctx, "POST", u, body)
if err != nil { if err != nil {
return err return err
@ -1206,7 +1228,7 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg))) return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
} }
var setDNSRes struct{} // no fields yet var setDNSRes struct{} // no fields yet
if err := decode(res, &setDNSRes, serverKey, machinePrivKey); err != nil { if err := decode(res, &setDNSRes, &serverKey, &machinePrivKey); err != nil {
c.logf("error decoding SetDNSResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err) c.logf("error decoding SetDNSResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
return fmt.Errorf("set-dns-response: %v", err) return fmt.Errorf("set-dns-response: %v", err)
} }

@ -15,7 +15,7 @@ import (
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/wgkey"
) )
func TestNewDirect(t *testing.T) { func TestNewDirect(t *testing.T) {
@ -23,12 +23,15 @@ func TestNewDirect(t *testing.T) {
ni := tailcfg.NetInfo{LinkType: "wired"} ni := tailcfg.NetInfo{LinkType: "wired"}
hi.NetInfo = &ni hi.NetInfo = &ni
k := key.NewMachine() key, err := wgkey.NewPrivate()
if err != nil {
t.Error(err)
}
opts := Options{ opts := Options{
ServerURL: "https://example.com", ServerURL: "https://example.com",
Hostinfo: hi, Hostinfo: hi,
GetMachinePrivateKey: func() (key.MachinePrivate, error) { GetMachinePrivateKey: func() (wgkey.Private, error) {
return k, nil return key, nil
}, },
} }
c, err := NewDirect(opts) c, err := NewDirect(opts)
@ -99,12 +102,15 @@ func TestTsmpPing(t *testing.T) {
ni := tailcfg.NetInfo{LinkType: "wired"} ni := tailcfg.NetInfo{LinkType: "wired"}
hi.NetInfo = &ni hi.NetInfo = &ni
k := key.NewMachine() key, err := wgkey.NewPrivate()
if err != nil {
t.Error(err)
}
opts := Options{ opts := Options{
ServerURL: "https://example.com", ServerURL: "https://example.com",
Hostinfo: hi, Hostinfo: hi,
GetMachinePrivateKey: func() (key.MachinePrivate, error) { GetMachinePrivateKey: func() (wgkey.Private, error) {
return k, nil return key, nil
}, },
} }

@ -12,7 +12,6 @@ import (
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
@ -32,7 +31,7 @@ type mapSession struct {
privateNodeKey wgkey.Private privateNodeKey wgkey.Private
logf logger.Logf logf logger.Logf
vlogf logger.Logf vlogf logger.Logf
machinePubKey key.MachinePublic machinePubKey tailcfg.MachineKey
keepSharerAndUserSplit bool // see Options.KeepSharerAndUserSplit keepSharerAndUserSplit bool // see Options.KeepSharerAndUserSplit
// Fields storing state over the the coards of multiple MapResponses. // Fields storing state over the the coards of multiple MapResponses.

@ -10,7 +10,7 @@ import (
"fmt" "fmt"
"time" "time"
"tailscale.com/types/key" "tailscale.com/types/wgkey"
) )
var ( var (
@ -20,7 +20,7 @@ var (
// HashRegisterRequest generates the hash required sign or verify a // HashRegisterRequest generates the hash required sign or verify a
// tailcfg.RegisterRequest with tailcfg.SignatureV1. // tailcfg.RegisterRequest with tailcfg.SignatureV1.
func HashRegisterRequest(ts time.Time, serverURL string, deviceCert []byte, serverPubKey, machinePubKey key.MachinePublic) []byte { func HashRegisterRequest(ts time.Time, serverURL string, deviceCert []byte, serverPubKey, machinePubKey wgkey.Key) []byte {
h := crypto.SHA256.New() h := crypto.SHA256.New()
// hash.Hash.Write never returns an error, so we don't check for one here. // hash.Hash.Write never returns an error, so we don't check for one here.

@ -21,7 +21,7 @@ import (
"github.com/tailscale/certstore" "github.com/tailscale/certstore"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/wgkey"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
) )
@ -125,7 +125,7 @@ func findIdentity(subject string, st certstore.Store) (certstore.Identity, []*x5
// using that identity's public key. In addition to the signature, the full // using that identity's public key. In addition to the signature, the full
// certificate chain is included so that the control server can validate the // certificate chain is included so that the control server can validate the
// certificate from a copy of the root CA's certificate. // certificate from a copy of the root CA's certificate.
func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey key.MachinePublic) (err error) { func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) (err error) {
defer func() { defer func() {
if err != nil { if err != nil {
err = fmt.Errorf("signRegisterRequest: %w", err) err = fmt.Errorf("signRegisterRequest: %w", err)

@ -9,10 +9,10 @@ package controlclient
import ( import (
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/wgkey"
) )
// signRegisterRequest on non-supported platforms always returns errNoCertStore. // signRegisterRequest on non-supported platforms always returns errNoCertStore.
func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey key.MachinePublic) error { func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) error {
return errNoCertStore return errNoCertStore
} }

@ -110,7 +110,7 @@ type LocalBackend struct {
userID string // current controlling user ID (for Windows, primarily) userID string // current controlling user ID (for Windows, primarily)
prefs *ipn.Prefs prefs *ipn.Prefs
inServerMode bool inServerMode bool
machinePrivKey key.MachinePrivate machinePrivKey wgkey.Private
state ipn.State state ipn.State
capFileSharing bool // whether netMap contains the file sharing capability capFileSharing bool // whether netMap contains the file sharing capability
// hostinfo is mutated in-place while mu is held. // hostinfo is mutated in-place while mu is held.
@ -293,7 +293,7 @@ func (b *LocalBackend) Prefs() *ipn.Prefs {
defer b.mu.Unlock() defer b.mu.Unlock()
p := b.prefs.Clone() p := b.prefs.Clone()
if p != nil && p.Persist != nil { if p != nil && p.Persist != nil {
p.Persist.LegacyFrontendPrivateMachineKey = key.MachinePrivate{} p.Persist.LegacyFrontendPrivateMachineKey = wgkey.Private{}
p.Persist.PrivateNodeKey = wgkey.Private{} p.Persist.PrivateNodeKey = wgkey.Private{}
p.Persist.OldPrivateNodeKey = wgkey.Private{} p.Persist.OldPrivateNodeKey = wgkey.Private{}
} }
@ -1239,22 +1239,22 @@ func (b *LocalBackend) popBrowserAuthNow() {
// For testing lazy machine key generation. // For testing lazy machine key generation.
var panicOnMachineKeyGeneration, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_PANIC_MACHINE_KEY")) var panicOnMachineKeyGeneration, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_PANIC_MACHINE_KEY"))
func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePrivate, error) { func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (wgkey.Private, error) {
var cache atomic.Value var cache atomic.Value
return func() (key.MachinePrivate, error) { return func() (wgkey.Private, error) {
if panicOnMachineKeyGeneration { if panicOnMachineKeyGeneration {
panic("machine key generated") panic("machine key generated")
} }
if v, ok := cache.Load().(key.MachinePrivate); ok { if v, ok := cache.Load().(wgkey.Private); ok {
return v, nil return v, nil
} }
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if v, ok := cache.Load().(key.MachinePrivate); ok { if v, ok := cache.Load().(wgkey.Private); ok {
return v, nil return v, nil
} }
if err := b.initMachineKeyLocked(); err != nil { if err := b.initMachineKeyLocked(); err != nil {
return key.MachinePrivate{}, err return wgkey.Private{}, err
} }
cache.Store(b.machinePrivKey) cache.Store(b.machinePrivKey)
return b.machinePrivKey, nil return b.machinePrivKey, nil
@ -1272,7 +1272,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
return nil return nil
} }
var legacyMachineKey key.MachinePrivate var legacyMachineKey wgkey.Private
if b.prefs.Persist != nil { if b.prefs.Persist != nil {
legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey
} }
@ -1285,7 +1285,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
if b.machinePrivKey.IsZero() { if b.machinePrivKey.IsZero() {
return fmt.Errorf("invalid zero key stored in %v key of %v", ipn.MachineKeyStateKey, b.store) return fmt.Errorf("invalid zero key stored in %v key of %v", ipn.MachineKeyStateKey, b.store)
} }
if !legacyMachineKey.IsZero() && !legacyMachineKey.Equal(b.machinePrivKey) { if !legacyMachineKey.IsZero() && !bytes.Equal(legacyMachineKey[:], b.machinePrivKey[:]) {
b.logf("frontend-provided legacy machine key ignored; used value from server state") b.logf("frontend-provided legacy machine key ignored; used value from server state")
} }
return nil return nil
@ -1306,7 +1306,11 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
b.machinePrivKey = legacyMachineKey b.machinePrivKey = legacyMachineKey
} else { } else {
b.logf("generating new machine key") b.logf("generating new machine key")
b.machinePrivKey = key.NewMachine() var err error
b.machinePrivKey, err = wgkey.NewPrivate()
if err != nil {
return fmt.Errorf("initializing new machine key: %w", err)
}
} }
keyText, _ = b.machinePrivKey.MarshalText() keyText, _ = b.machinePrivKey.MarshalText()
@ -2600,7 +2604,7 @@ func (b *LocalBackend) OperatorUserID() string {
// TestOnlyPublicKeys returns the current machine and node public // TestOnlyPublicKeys returns the current machine and node public
// keys. Used in tests only to facilitate automated node authorization // keys. Used in tests only to facilitate automated node authorization
// in the test harness. // in the test harness.
func (b *LocalBackend) TestOnlyPublicKeys() (machineKey key.MachinePublic, nodeKey tailcfg.NodeKey) { func (b *LocalBackend) TestOnlyPublicKeys() (machineKey tailcfg.MachineKey, nodeKey tailcfg.NodeKey) {
b.mu.Lock() b.mu.Lock()
prefs := b.prefs prefs := b.prefs
machinePrivKey := b.machinePrivKey machinePrivKey := b.machinePrivKey
@ -2612,7 +2616,7 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey key.MachinePublic, nodeK
mk := machinePrivKey.Public() mk := machinePrivKey.Public()
nk := prefs.Persist.PrivateNodeKey.Public() nk := prefs.Persist.PrivateNodeKey.Public()
return mk, tailcfg.NodeKey(nk) return tailcfg.MachineKey(mk), tailcfg.NodeKey(nk)
} }
func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) { func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) {

@ -17,7 +17,6 @@ import (
"tailscale.com/syncs" "tailscale.com/syncs"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/empty" "tailscale.com/types/empty"
"tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/types/persist" "tailscale.com/types/persist"
@ -94,7 +93,7 @@ type mockControl struct {
calls []string calls []string
authBlocked bool authBlocked bool
persist persist.Persist persist persist.Persist
machineKey key.MachinePrivate machineKey wgkey.Private
} }
func newMockControl() *mockControl { func newMockControl() *mockControl {

@ -77,6 +77,9 @@ func (u StableNodeID) IsZero() bool {
return u == "" return u == ""
} }
// MachineKey is the curve25519 public key for a machine.
type MachineKey [32]byte
// NodeKey is the curve25519 public key for a node. // NodeKey is the curve25519 public key for a node.
type NodeKey [32]byte type NodeKey [32]byte
@ -154,7 +157,7 @@ type Node struct {
Key NodeKey Key NodeKey
KeyExpiry time.Time KeyExpiry time.Time
Machine key.MachinePublic Machine MachineKey
DiscoKey DiscoKey DiscoKey DiscoKey
Addresses []netaddr.IPPrefix // IP addresses of this Node directly Addresses []netaddr.IPPrefix // IP addresses of this Node directly
AllowedIPs []netaddr.IPPrefix // range of IP addresses to route to this node AllowedIPs []netaddr.IPPrefix // range of IP addresses to route to this node
@ -1075,6 +1078,11 @@ type Debug struct {
DisableUPnP opt.Bool `json:",omitempty"` DisableUPnP opt.Bool `json:",omitempty"`
} }
func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }
func (k MachineKey) MarshalText() ([]byte, error) { return keyMarshalText("mkey:", k), nil }
func (k MachineKey) HexString() string { return fmt.Sprintf("%x", k[:]) }
func (k *MachineKey) UnmarshalText(text []byte) error { return keyUnmarshalText(k[:], "mkey:", text) }
func appendKey(base []byte, prefix string, k [32]byte) []byte { func appendKey(base []byte, prefix string, k [32]byte) []byte {
ret := append(base, make([]byte, len(prefix)+64)...) ret := append(base, make([]byte, len(prefix)+64)...)
buf := ret[len(base):] buf := ret[len(base):]
@ -1108,6 +1116,9 @@ func (k *NodeKey) UnmarshalText(text []byte) error { return keyUnmarshalText(k[:
// IsZero reports whether k is the zero value. // IsZero reports whether k is the zero value.
func (k NodeKey) IsZero() bool { return k == NodeKey{} } func (k NodeKey) IsZero() bool { return k == NodeKey{} }
// IsZero reports whether k is the zero value.
func (k MachineKey) IsZero() bool { return k == MachineKey{} }
func (k DiscoKey) String() string { return fmt.Sprintf("discokey:%x", k[:]) } func (k DiscoKey) String() string { return fmt.Sprintf("discokey:%x", k[:]) }
func (k DiscoKey) MarshalText() ([]byte, error) { return keyMarshalText("discokey:", k), nil } func (k DiscoKey) MarshalText() ([]byte, error) { return keyMarshalText("discokey:", k), nil }
func (k *DiscoKey) UnmarshalText(text []byte) error { return keyUnmarshalText(k[:], "discokey:", text) } func (k *DiscoKey) UnmarshalText(text []byte) error { return keyUnmarshalText(k[:], "discokey:", text) }

@ -9,7 +9,6 @@ package tailcfg
import ( import (
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/types/key"
"tailscale.com/types/opt" "tailscale.com/types/opt"
"tailscale.com/types/structs" "tailscale.com/types/structs"
"time" "time"
@ -74,7 +73,7 @@ var _NodeNeedsRegeneration = Node(struct {
Sharer UserID Sharer UserID
Key NodeKey Key NodeKey
KeyExpiry time.Time KeyExpiry time.Time
Machine key.MachinePublic Machine MachineKey
DiscoKey DiscoKey DiscoKey DiscoKey
Addresses []netaddr.IPPrefix Addresses []netaddr.IPPrefix
AllowedIPs []netaddr.IPPrefix AllowedIPs []netaddr.IPPrefix

@ -13,7 +13,6 @@ import (
"time" "time"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/types/key"
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
"tailscale.com/version" "tailscale.com/version"
) )
@ -214,7 +213,6 @@ func TestNodeEqual(t *testing.T) {
return k.Public() return k.Public()
} }
n1 := newPublicKey(t) n1 := newPublicKey(t)
m1 := key.NewMachine().Public()
now := time.Now() now := time.Now()
tests := []struct { tests := []struct {
@ -292,13 +290,13 @@ func TestNodeEqual(t *testing.T) {
true, true,
}, },
{ {
&Node{Machine: m1}, &Node{Machine: MachineKey(n1)},
&Node{Machine: key.NewMachine().Public()}, &Node{Machine: MachineKey(newPublicKey(t))},
false, false,
}, },
{ {
&Node{Machine: m1}, &Node{Machine: MachineKey(n1)},
&Node{Machine: m1}, &Node{Machine: MachineKey(n1)},
true, true,
}, },
{ {
@ -395,6 +393,14 @@ func TestNetInfoFields(t *testing.T) {
} }
} }
func TestMachineKeyMarshal(t *testing.T) {
var k1, k2 MachineKey
for i := range k1 {
k1[i] = byte(i)
}
testKey(t, "mkey:", k1, &k2)
}
func TestNodeKeyMarshal(t *testing.T) { func TestNodeKeyMarshal(t *testing.T) {
var k1, k2 NodeKey var k1, k2 NodeKey
for i := range k1 { for i := range k1 {

@ -26,13 +26,14 @@ import (
"time" "time"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"go4.org/mem" "golang.org/x/crypto/nacl/box"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/wgkey"
) )
const msgLimit = 1 << 20 // encrypted message length limit const msgLimit = 1 << 20 // encrypted message length limit
@ -56,8 +57,8 @@ type Server struct {
mu sync.Mutex mu sync.Mutex
inServeMap int inServeMap int
cond *sync.Cond // lazily initialized by condLocked cond *sync.Cond // lazily initialized by condLocked
pubKey key.MachinePublic pubKey wgkey.Key
privKey key.MachinePrivate privKey wgkey.Private
nodes map[tailcfg.NodeKey]*tailcfg.Node nodes map[tailcfg.NodeKey]*tailcfg.Node
users map[tailcfg.NodeKey]*tailcfg.User users map[tailcfg.NodeKey]*tailcfg.User
logins map[tailcfg.NodeKey]*tailcfg.Login logins map[tailcfg.NodeKey]*tailcfg.Login
@ -198,21 +199,25 @@ func (s *Server) serveUnhandled(w http.ResponseWriter, r *http.Request) {
go panic(fmt.Sprintf("testcontrol.Server received unhandled request: %s", got.Bytes())) go panic(fmt.Sprintf("testcontrol.Server received unhandled request: %s", got.Bytes()))
} }
func (s *Server) publicKey() key.MachinePublic { func (s *Server) publicKey() wgkey.Key {
pub, _ := s.keyPair() pub, _ := s.keyPair()
return pub return pub
} }
func (s *Server) privateKey() key.MachinePrivate { func (s *Server) privateKey() wgkey.Private {
_, priv := s.keyPair() _, priv := s.keyPair()
return priv return priv
} }
func (s *Server) keyPair() (pub key.MachinePublic, priv key.MachinePrivate) { func (s *Server) keyPair() (pub wgkey.Key, priv wgkey.Private) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.pubKey.IsZero() { if s.pubKey.IsZero() {
s.privKey = key.NewMachine() var err error
s.privKey, err = wgkey.NewPrivate()
if err != nil {
go panic(err) // bring down test, even if in http.Handler
}
s.pubKey = s.privKey.Public() s.pubKey = s.privKey.Public()
} }
return s.pubKey, s.privKey return s.pubKey, s.privKey
@ -221,7 +226,7 @@ func (s *Server) keyPair() (pub key.MachinePublic, priv key.MachinePrivate) {
func (s *Server) serveKey(w http.ResponseWriter, r *http.Request) { func (s *Server) serveKey(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(200) w.WriteHeader(200)
io.WriteString(w, s.publicKey().UntypedHexString()) io.WriteString(w, s.publicKey().HexString())
} }
func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) { func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
@ -232,11 +237,12 @@ func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
mkeyStr = mkeyStr[:i] mkeyStr = mkeyStr[:i]
} }
mkey, err := key.ParseMachinePublicUntyped(mem.S(mkeyStr)) key, err := wgkey.ParseHex(mkeyStr)
if err != nil { if err != nil {
http.Error(w, "bad machine key hex", 400) http.Error(w, "bad machine key hex", 400)
return return
} }
mkey := tailcfg.MachineKey(key)
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, "POST required", 400) http.Error(w, "POST required", 400)
@ -275,7 +281,7 @@ func (s *Server) AddFakeNode() {
s.nodes = make(map[tailcfg.NodeKey]*tailcfg.Node) s.nodes = make(map[tailcfg.NodeKey]*tailcfg.Node)
} }
nk := tailcfg.NodeKey(key.NewPrivate().Public()) nk := tailcfg.NodeKey(key.NewPrivate().Public())
mk := key.NewMachine().Public() mk := tailcfg.MachineKey(key.NewPrivate().Public())
dk := tailcfg.DiscoKey(key.NewPrivate().Public()) dk := tailcfg.DiscoKey(key.NewPrivate().Public())
id := int64(binary.LittleEndian.Uint64(nk[:])) id := int64(binary.LittleEndian.Uint64(nk[:]))
ip := netaddr.IPv4(nk[0], nk[1], nk[2], nk[3]) ip := netaddr.IPv4(nk[0], nk[1], nk[2], nk[3])
@ -392,7 +398,7 @@ func (s *Server) CompleteAuth(authPathOrURL string) bool {
return true return true
} }
func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.MachinePublic) { func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey tailcfg.MachineKey) {
msg, err := ioutil.ReadAll(io.LimitReader(r.Body, msgLimit)) msg, err := ioutil.ReadAll(io.LimitReader(r.Body, msgLimit))
if err != nil { if err != nil {
r.Body.Close() r.Body.Close()
@ -557,7 +563,7 @@ func (s *Server) InServeMap() int {
return s.inServeMap return s.inServeMap
} }
func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.MachinePublic) { func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey tailcfg.MachineKey) {
s.incrInServeMap(1) s.incrInServeMap(1)
defer s.incrInServeMap(-1) defer s.incrInServeMap(-1)
ctx := r.Context() ctx := r.Context()
@ -735,7 +741,7 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
return res, nil return res, nil
} }
func (s *Server) sendMapMsg(w http.ResponseWriter, mkey key.MachinePublic, compress bool, msg interface{}) error { func (s *Server) sendMapMsg(w http.ResponseWriter, mkey tailcfg.MachineKey, compress bool, msg interface{}) error {
resBytes, err := s.encode(mkey, compress, msg) resBytes, err := s.encode(mkey, compress, msg)
if err != nil { if err != nil {
return err return err
@ -759,12 +765,21 @@ func (s *Server) sendMapMsg(w http.ResponseWriter, mkey key.MachinePublic, compr
return nil return nil
} }
func (s *Server) decode(mkey key.MachinePublic, msg []byte, v interface{}) error { func (s *Server) decode(mkey tailcfg.MachineKey, msg []byte, v interface{}) error {
if len(msg) == msgLimit { if len(msg) == msgLimit {
return errors.New("encrypted message too long") return errors.New("encrypted message too long")
} }
decrypted, ok := s.privateKey().OpenFrom(mkey, msg) var nonce [24]byte
if len(msg) < len(nonce)+1 {
return errors.New("missing nonce")
}
copy(nonce[:], msg)
msg = msg[len(nonce):]
priv := s.privateKey()
pub, pri := (*[32]byte)(&mkey), (*[32]byte)(&priv)
decrypted, ok := box.Open(nil, msg, &nonce, pub, pri)
if !ok { if !ok {
return errors.New("can't decrypt request") return errors.New("can't decrypt request")
} }
@ -781,7 +796,7 @@ var zstdEncoderPool = &sync.Pool{
}, },
} }
func (s *Server) encode(mkey key.MachinePublic, compress bool, v interface{}) (b []byte, err error) { func (s *Server) encode(mkey tailcfg.MachineKey, compress bool, v interface{}) (b []byte, err error) {
var isBytes bool var isBytes bool
if b, isBytes = v.([]byte); !isBytes { if b, isBytes = v.([]byte); !isBytes {
b, err = json.Marshal(v) b, err = json.Marshal(v)
@ -795,7 +810,14 @@ func (s *Server) encode(mkey key.MachinePublic, compress bool, v interface{}) (b
encoder.Close() encoder.Close()
zstdEncoderPool.Put(encoder) zstdEncoderPool.Put(encoder)
} }
return s.privateKey().SealTo(mkey, b), nil var nonce [24]byte
if _, err := io.ReadFull(crand.Reader, nonce[:]); err != nil {
panic(err)
}
priv := s.privateKey()
pub, pri := (*[32]byte)(&mkey), (*[32]byte)(&priv)
msgData := box.Seal(nonce[:], b, &nonce, pub, pri)
return msgData, nil
} }
// filterInvalidIPv6Endpoints removes invalid IPv6 endpoints from eps, // filterInvalidIPv6Endpoints removes invalid IPv6 endpoints from eps,

@ -2,27 +2,21 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package key defines some types for the various keys Tailscale uses. // Package key defines some types related to curve25519 keys.
package key package key
import ( import (
crand "crypto/rand"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io"
"go4.org/mem" "go4.org/mem"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
) )
// Private represents a curve25519 private key of unspecified purpose. // Private represents a curve25519 private key.
//
// Deprecated: this key type has been used for several different
// keypairs, which are used in different protocols. This makes it easy
// to accidentally use the wrong key for a particular purpose, because
// the type system doesn't protect you. Please define dedicated key
// types for each purpose (e.g. communication with control, disco,
// wireguard...) instead, even if they are a Curve25519 value under
// the hood.
type Private [32]byte type Private [32]byte
// Private reports whether p is the zero value. // Private reports whether p is the zero value.
@ -31,8 +25,11 @@ func (p Private) IsZero() bool { return p == Private{} }
// NewPrivate returns a new private key. // NewPrivate returns a new private key.
func NewPrivate() Private { func NewPrivate() Private {
var p Private var p Private
rand(p[:]) if _, err := io.ReadFull(crand.Reader, p[:]); err != nil {
clamp25519Private(p[:]) panic(err)
}
p[0] &= 248
p[31] = (p[31] & 127) | 64
return p return p
} }
@ -42,14 +39,6 @@ func NewPrivate() Private {
func (k Private) B32() *[32]byte { return (*[32]byte)(&k) } func (k Private) B32() *[32]byte { return (*[32]byte)(&k) }
// Public represents a curve25519 public key. // Public represents a curve25519 public key.
//
// Deprecated: this key type has been used for several different
// keypairs, which are used in different protocols. This makes it easy
// to accidentally use the wrong key for a particular purpose, because
// the type system doesn't protect you. Please define dedicated key
// types for each purpose (e.g. communication with control, disco,
// wireguard...) instead, even if they are a Curve25519 value under
// the hood.
type Public [32]byte type Public [32]byte
// Public reports whether p is the zero value. // Public reports whether p is the zero value.
@ -117,3 +106,17 @@ func NewPublicFromHexMem(m mem.RO) (Public, error) {
} }
return p, nil return p, nil
} }
// fromHexChar converts a hex character into its value and a success flag.
func fromHexChar(c byte) (byte, bool) {
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}

@ -5,70 +5,50 @@
package key package key
import ( import (
"bytes"
"encoding"
"reflect"
"testing" "testing"
"tailscale.com/types/wgkey"
) )
type tmu interface { func TestTextUnmarshal(t *testing.T) {
encoding.TextMarshaler p := Public{1, 2}
encoding.TextUnmarshaler text, err := p.MarshalText()
if err != nil {
t.Fatal(err)
} }
var p2 Public
func TestTextMarshal(t *testing.T) { if err := p2.UnmarshalText(text); err != nil {
// Check that keys roundtrip correctly through marshaling, and t.Fatal(err)
// cannot be unmarshaled as other key types.
type keyMaker func() (random, zero tmu)
keys := []keyMaker{
func() (tmu, tmu) { k := NewMachine(); return &k, &MachinePrivate{} },
func() (tmu, tmu) { k := NewMachine().Public(); return &k, &MachinePublic{} },
func() (tmu, tmu) { k := NewPrivate().Public(); return &k, &Public{} },
} }
for i, kf := range keys { if p != p2 {
k1, k2 := kf() t.Fatalf("mismatch; got %x want %x", p2, p)
// Sanity check: both k's should have the same type, k2 should
// be the zero value.
if t1, t2 := reflect.ValueOf(k1).Elem().Type(), reflect.ValueOf(k2).Elem().Type(); t1 != t2 {
t.Fatalf("got two keys of different types %T and %T", t1, t2)
} }
if !reflect.ValueOf(k2).Elem().IsZero() {
t.Fatal("k2 is not the zero value")
} }
// All keys should marshal successfully. func TestClamping(t *testing.T) {
t1, err := k1.MarshalText() t.Run("NewPrivate", func(t *testing.T) { testClamping(t, NewPrivate) })
if err != nil {
t.Fatalf("MarshalText(%#v): %v", k1, err)
}
// Marshalling should round-trip. // Also test the wgkey package, as their behavior should match.
if err := k2.UnmarshalText(t1); err != nil { t.Run("wgkey", func(t *testing.T) {
t.Fatalf("UnmarshalText(MarshalText(%#v)): %v", k1, err) testClamping(t, func() Private {
} k, err := wgkey.NewPrivate()
if !reflect.DeepEqual(k1, k2) {
t.Fatalf("UnmarshalText(MarshalText(k1)) changed\n old: %#v\n new: %#v", k1, k2)
}
// And the text representation should also roundtrip.
t2, err := k2.MarshalText()
if err != nil { if err != nil {
t.Fatalf("MarshalText(%#v): %v", k2, err) t.Fatal(err)
} }
if !bytes.Equal(t1, t2) { return Private(k)
t.Fatal("MarshalText(k1) != MarshalText(k2)") })
})
} }
// No other key type should be able to unmarshal the text of a func testClamping(t *testing.T, newKey func() Private) {
// different key. for i := 0; i < 100; i++ {
for j, otherkf := range keys { k := newKey()
if i == j { if k[0]&0b111 != 0 {
continue t.Fatalf("Bogus clamping in first byte: %#08b", k[0])
} return
_, otherk := otherkf()
if err := otherk.UnmarshalText(t1); err == nil {
t.Fatalf("key %#v can unmarshal as %#v (marshaled form %q)", k1, otherk, t1)
} }
if k[31]>>6 != 1 {
t.Fatalf("Bogus clamping in last byte: %#08b", k[0])
} }
} }
} }

@ -1,173 +0,0 @@
// Copyright (c) 2021 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 key
import (
"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 MachinePublic 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
}
// MarshalText implements encoding.TextMarshaler.
func (k MachinePrivate) MarshalText() ([]byte, error) {
return toHex(k.k[:], machinePrivateHexPrefix), nil
}
// MarshalText implements encoding.TextUnmarshaler.
func (k *MachinePrivate) UnmarshalText(b []byte) error {
return parseHex(k.k[:], mem.B(b), mem.S(machinePrivateHexPrefix))
}
// 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)
}
// 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
}
// 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[:])
}
// 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)
}
// MarshalText implements encoding.TextMarshaler.
func (k MachinePublic) MarshalText() ([]byte, error) {
return toHex(k.k[:], machinePublicHexPrefix), nil
}
// MarshalText implements encoding.TextUnmarshaler.
func (k *MachinePublic) UnmarshalText(b []byte) error {
return parseHex(k.k[:], mem.B(b), mem.S(machinePublicHexPrefix))
}

@ -1,92 +0,0 @@
// Copyright (c) 2021 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 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")
}
}

@ -1,111 +0,0 @@
// Copyright (c) 2021 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 key
import (
crand "crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"go4.org/mem"
)
// rand fills b with cryptographically strong random bytes. Panics if
// no random bytes are available.
func rand(b []byte) {
if _, err := io.ReadFull(crand.Reader, b[:]); err != nil {
panic(fmt.Sprintf("unable to read random bytes from OS: %v", err))
}
}
// clamp25519 clamps b, which must be a 32-byte Curve25519 private
// key, to a safe value.
//
// The clamping effectively constrains the key to a number between
// 2^251 and 2^252-1, which is then multiplied by 8 (the cofactor of
// Curve25519). This produces a value that doesn't have any unsafe
// properties when doing operations like ScalarMult.
//
// See
// https://web.archive.org/web/20210228105330/https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/
// for a more in-depth explanation of the constraints that led to this
// clamping requirement.
//
// PLEASE NOTE that not all Curve25519 values require clamping. When
// implementing a new key type that uses Curve25519, you must evaluate
// whether that particular key's use requires clamping. Here are some
// existing uses and whether you should clamp private keys at
// creation.
//
// - NaCl box: yes, clamp at creation.
// - WireGuard (userspace uapi or kernel): no, do not clamp.
// - Noise protocols: no, do not clamp.
func clamp25519Private(b []byte) {
b[0] &= 248
b[31] = (b[31] & 127) | 64
}
func toHex(k []byte, prefix string) []byte {
ret := make([]byte, len(prefix)+len(k)*2)
copy(ret, prefix)
hex.Encode(ret[len(prefix):], k)
return ret
}
// parseHex decodes a key string of the form "<prefix><hex string>"
// into out. The prefix must match, and the decoded base64 must fit
// exactly into out.
//
// Note the errors in this function deliberately do not echo the
// contents of in, because it might be a private key or part of a
// private key.
func parseHex(out []byte, in, prefix mem.RO) error {
if !mem.HasPrefix(in, prefix) {
return fmt.Errorf("key hex string doesn't have expected type prefix %s", prefix.StringCopy())
}
in = in.SliceFrom(prefix.Len())
if want := len(out) * 2; in.Len() != want {
return fmt.Errorf("key hex has the wrong size, got %d want %d", in.Len(), want)
}
for i := range out {
a, ok1 := fromHexChar(in.At(i*2 + 0))
b, ok2 := fromHexChar(in.At(i*2 + 1))
if !ok1 || !ok2 {
return errors.New("invalid hex character in key")
}
out[i] = (a << 4) | b
}
return nil
}
// fromHexChar converts a hex character into its value and a success flag.
func fromHexChar(c byte) (byte, bool) {
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}
// debug32 returns the Tailscale conventional debug representation of
// a key: the first five base64 digits of the key, in square brackets.
func debug32(k [32]byte) string {
if k == [32]byte{} {
return ""
}
var b [45]byte // 32 bytes expands to 44 bytes in base64, plus 1 for the leading '['
base64.StdEncoding.Encode(b[1:], k[:])
b[0] = '['
b[6] = ']'
return string(b[:7])
}

@ -1,38 +0,0 @@
// Copyright (c) 2021 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 key
import (
"bytes"
"testing"
)
func TestRand(t *testing.T) {
var bs [32]byte
rand(bs[:])
if bs == [32]byte{} {
t.Fatal("rand didn't provide randomness")
}
var bs2 [32]byte
rand(bs2[:])
if bytes.Equal(bs[:], bs2[:]) {
t.Fatal("rand returned the same data twice")
}
}
func TestClamp25519Private(t *testing.T) {
for i := 0; i < 100; i++ {
var k [32]byte
rand(k[:])
clamp25519Private(k[:])
if k[0]&0b111 != 0 {
t.Fatalf("Bogus clamping in first byte: %#08b", k[0])
return
}
if k[31]>>6 != 1 {
t.Fatalf("Bogus clamping in last byte: %#08b", k[0])
}
}
}

@ -14,7 +14,6 @@ import (
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
) )
@ -35,7 +34,7 @@ type NetworkMap struct {
Addresses []netaddr.IPPrefix // same as tailcfg.Node.Addresses (IP addresses of this Node directly) Addresses []netaddr.IPPrefix // same as tailcfg.Node.Addresses (IP addresses of this Node directly)
LocalPort uint16 // used for debugging LocalPort uint16 // used for debugging
MachineStatus tailcfg.MachineStatus MachineStatus tailcfg.MachineStatus
MachineKey key.MachinePublic MachineKey tailcfg.MachineKey
Peers []*tailcfg.Node // sorted by Node.ID Peers []*tailcfg.Node // sorted by Node.ID
DNS tailcfg.DNSConfig DNS tailcfg.DNSConfig
Hostinfo tailcfg.Hostinfo Hostinfo tailcfg.Hostinfo

@ -8,7 +8,6 @@ package persist
import ( import (
"fmt" "fmt"
"tailscale.com/types/key"
"tailscale.com/types/structs" "tailscale.com/types/structs"
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
) )
@ -29,7 +28,7 @@ type Persist struct {
// needed. This field should be considered read-only from GUI // needed. This field should be considered read-only from GUI
// frontends. The real value should not be written back in // frontends. The real value should not be written back in
// this field, lest the frontend persist it to disk. // this field, lest the frontend persist it to disk.
LegacyFrontendPrivateMachineKey key.MachinePrivate `json:"PrivateMachineKey"` LegacyFrontendPrivateMachineKey wgkey.Private `json:"PrivateMachineKey"`
PrivateNodeKey wgkey.Private PrivateNodeKey wgkey.Private
OldPrivateNodeKey wgkey.Private // needed to request key rotation OldPrivateNodeKey wgkey.Private // needed to request key rotation
@ -53,10 +52,7 @@ func (p *Persist) Equals(p2 *Persist) bool {
} }
func (p *Persist) Pretty() string { func (p *Persist) Pretty() string {
var ( var mk, ok, nk wgkey.Key
mk key.MachinePublic
ok, nk wgkey.Key
)
if !p.LegacyFrontendPrivateMachineKey.IsZero() { if !p.LegacyFrontendPrivateMachineKey.IsZero() {
mk = p.LegacyFrontendPrivateMachineKey.Public() mk = p.LegacyFrontendPrivateMachineKey.Public()
} }
@ -73,5 +69,5 @@ func (p *Persist) Pretty() string {
return k.ShortString() return k.ShortString()
} }
return fmt.Sprintf("Persist{lm=%v, o=%v, n=%v u=%#v}", return fmt.Sprintf("Persist{lm=%v, o=%v, n=%v u=%#v}",
mk.ShortString(), ss(ok), ss(nk), p.LoginName) ss(mk), ss(ok), ss(nk), p.LoginName)
} }

@ -7,7 +7,6 @@
package persist package persist
import ( import (
"tailscale.com/types/key"
"tailscale.com/types/structs" "tailscale.com/types/structs"
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
) )
@ -27,7 +26,7 @@ func (src *Persist) Clone() *Persist {
// tailscale.com/cmd/cloner -type Persist // tailscale.com/cmd/cloner -type Persist
var _PersistNeedsRegeneration = Persist(struct { var _PersistNeedsRegeneration = Persist(struct {
_ structs.Incomparable _ structs.Incomparable
LegacyFrontendPrivateMachineKey key.MachinePrivate LegacyFrontendPrivateMachineKey wgkey.Private
PrivateNodeKey wgkey.Private PrivateNodeKey wgkey.Private
OldPrivateNodeKey wgkey.Private OldPrivateNodeKey wgkey.Private
Provider string Provider string

@ -8,7 +8,6 @@ import (
"reflect" "reflect"
"testing" "testing"
"tailscale.com/types/key"
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
) )
@ -35,7 +34,6 @@ func TestPersistEqual(t *testing.T) {
} }
return k return k
} }
m1 := key.NewMachine()
k1 := newPrivate() k1 := newPrivate()
tests := []struct { tests := []struct {
a, b *Persist a, b *Persist
@ -47,13 +45,13 @@ func TestPersistEqual(t *testing.T) {
{&Persist{}, &Persist{}, true}, {&Persist{}, &Persist{}, true},
{ {
&Persist{LegacyFrontendPrivateMachineKey: m1}, &Persist{LegacyFrontendPrivateMachineKey: k1},
&Persist{LegacyFrontendPrivateMachineKey: key.NewMachine()}, &Persist{LegacyFrontendPrivateMachineKey: newPrivate()},
false, false,
}, },
{ {
&Persist{LegacyFrontendPrivateMachineKey: m1}, &Persist{LegacyFrontendPrivateMachineKey: k1},
&Persist{LegacyFrontendPrivateMachineKey: m1}, &Persist{LegacyFrontendPrivateMachineKey: k1},
true, true,
}, },

Loading…
Cancel
Save