From 56a7652dc942f84ee09fcc0d1dc6d2a053b02090 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 29 Dec 2020 17:22:56 -0800 Subject: [PATCH] wgkey: new package This is a replacement for the key-related parts of the wireguard-go wgcfg package. This is almost a straight copy/paste from the wgcfg package. I have slightly changed some of the exported functions and types to avoid stutter, added and tweaked some comments, and removed some now-unused code. To avoid having wireguard-go depend on this new package, wgcfg will keep its key types. We translate into and out of those types at the last minute. These few remaining uses will be eliminated alongside the rest of the wgcfg package. Signed-off-by: Josh Bleecher Snyder --- cmd/derper/derper.go | 8 +- cmd/tailscale/depaware.txt | 1 + cmd/tailscaled/depaware.txt | 1 + control/controlclient/auto.go | 4 +- control/controlclient/direct.go | 48 ++--- control/controlclient/netmap.go | 5 +- control/controlclient/persist_test.go | 6 +- go.sum | 28 +++ ipn/local.go | 7 +- ipn/prefs_test.go | 4 +- ipn/store.go | 2 +- tailcfg/tailcfg_test.go | 6 +- types/key/key_test.go | 8 +- types/wgkey/key.go | 241 ++++++++++++++++++++++++++ types/wgkey/key_test.go | 111 ++++++++++++ wgengine/magicsock/legacy.go | 3 +- wgengine/magicsock/magicsock.go | 7 +- wgengine/magicsock/magicsock_test.go | 11 +- wgengine/userspace.go | 21 +-- 19 files changed, 455 insertions(+), 67 deletions(-) create mode 100644 types/wgkey/key.go create mode 100644 types/wgkey/key_test.go diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index f328932a2..43c0e84da 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -25,7 +25,6 @@ import ( "strings" "time" - "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/crypto/acme/autocert" "tailscale.com/atomicfile" "tailscale.com/derp" @@ -35,6 +34,7 @@ import ( "tailscale.com/net/stun" "tailscale.com/tsweb" "tailscale.com/types/key" + "tailscale.com/types/wgkey" "tailscale.com/version" ) @@ -51,7 +51,7 @@ var ( ) type config struct { - PrivateKey wgcfg.PrivateKey + PrivateKey wgkey.Private } func loadConfig() config { @@ -77,8 +77,8 @@ func loadConfig() config { } } -func mustNewKey() wgcfg.PrivateKey { - key, err := wgcfg.NewPrivateKey() +func mustNewKey() wgkey.Private { + key, err := wgkey.NewPrivate() if err != nil { log.Fatal(err) } diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 4313d4892..63e51cbc4 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -73,6 +73,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/opt from tailscale.com/control/controlclient+ tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ + tailscale.com/types/wgkey from tailscale.com/control/controlclient+ LW tailscale.com/util/endian from tailscale.com/net/netns+ tailscale.com/util/lineread from tailscale.com/control/controlclient+ tailscale.com/util/systemd from tailscale.com/control/controlclient+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index a09909bf8..8c963e0d7 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -81,6 +81,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/opt from tailscale.com/control/controlclient+ tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ + tailscale.com/types/wgkey from tailscale.com/control/controlclient+ LW tailscale.com/util/endian from tailscale.com/net/netns+ tailscale.com/util/lineread from tailscale.com/control/controlclient+ tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 75a9835eb..7acb5be63 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -17,13 +17,13 @@ import ( "sync" "time" - "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/oauth2" "tailscale.com/logtail/backoff" "tailscale.com/tailcfg" "tailscale.com/types/empty" "tailscale.com/types/logger" "tailscale.com/types/structs" + "tailscale.com/types/wgkey" ) // State is the high-level state of the client. It is used only in @@ -665,7 +665,7 @@ func (c *Client) Shutdown() { // NodePublicKey returns the node public key currently in use. This is // used exclusively in tests. -func (c *Client) TestOnlyNodePublicKey() wgcfg.Key { +func (c *Client) TestOnlyNodePublicKey() wgkey.Key { priv := c.direct.GetPersist() return priv.PrivateNodeKey.Public() } diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 911d2f1e5..4ab7ea03f 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -31,7 +31,6 @@ import ( "sync/atomic" "time" - "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/crypto/nacl/box" "golang.org/x/oauth2" "inet.af/netaddr" @@ -44,6 +43,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/opt" "tailscale.com/types/structs" + "tailscale.com/types/wgkey" "tailscale.com/util/systemd" "tailscale.com/version" "tailscale.com/wgengine/filter" @@ -61,10 +61,10 @@ type Persist struct { // needed. This field should be considered read-only from GUI // frontends. The real value should not be written back in // this field, lest the frontend persist it to disk. - LegacyFrontendPrivateMachineKey wgcfg.PrivateKey `json:"PrivateMachineKey"` + LegacyFrontendPrivateMachineKey wgkey.Private `json:"PrivateMachineKey"` - PrivateNodeKey wgcfg.PrivateKey - OldPrivateNodeKey wgcfg.PrivateKey // needed to request key rotation + PrivateNodeKey wgkey.Private + OldPrivateNodeKey wgkey.Private // needed to request key rotation Provider string LoginName string } @@ -85,7 +85,7 @@ func (p *Persist) Equals(p2 *Persist) bool { } func (p *Persist) Pretty() string { - var mk, ok, nk wgcfg.Key + var mk, ok, nk wgkey.Key if !p.LegacyFrontendPrivateMachineKey.IsZero() { mk = p.LegacyFrontendPrivateMachineKey.Public() } @@ -95,7 +95,7 @@ func (p *Persist) Pretty() string { if !p.PrivateNodeKey.IsZero() { nk = p.PrivateNodeKey.Public() } - ss := func(k wgcfg.Key) string { + ss := func(k wgkey.Key) string { if k.IsZero() { return "" } @@ -115,14 +115,14 @@ type Direct struct { keepAlive bool logf logger.Logf discoPubKey tailcfg.DiscoKey - machinePrivKey wgcfg.PrivateKey + machinePrivKey wgkey.Private debugFlags []string mu sync.Mutex // mutex guards the following fields - serverKey wgcfg.Key + serverKey wgkey.Key persist Persist authKey string - tryingNewKey wgcfg.PrivateKey + tryingNewKey wgkey.Private expiry *time.Time // hostinfo is mutated in-place while mu is held. hostinfo *tailcfg.Hostinfo // always non-nil @@ -133,7 +133,7 @@ type Direct struct { type Options struct { Persist Persist // initial persistent data - MachinePrivateKey wgcfg.PrivateKey // the machine key to use + MachinePrivateKey wgkey.Private // the machine key to use ServerURL string // URL of the tailcontrol server AuthKey string // optional node auth key for auto registration TimeNow func() time.Time // time.Now implementation used by Client @@ -340,7 +340,7 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags, } c.logf("doLogin(regen=%v, hasUrl=%v)", regen, url != "") - if serverKey == (wgcfg.Key{}) { + if serverKey.IsZero() { var err error serverKey, err = loadServerKey(ctx, c.httpc, c.serverURL) if err != nil { @@ -352,12 +352,12 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags, c.mu.Unlock() } - var oldNodeKey wgcfg.Key + var oldNodeKey wgkey.Key if url != "" { } else if regen || persist.PrivateNodeKey.IsZero() { c.logf("Generating a new nodekey.") persist.OldPrivateNodeKey = persist.PrivateNodeKey - key, err := wgcfg.NewPrivateKey() + key, err := wgkey.NewPrivate() if err != nil { c.logf("login keygen: %v", err) return regen, url, err @@ -793,7 +793,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM return nil } -func decode(res *http.Response, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) error { +func decode(res *http.Response, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error { defer res.Body.Close() msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20)) if err != nil { @@ -848,7 +848,7 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}) error { } -func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) error { +func decodeMsg(msg []byte, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error { decrypted, err := decryptMsg(msg, serverKey, mkey) if err != nil { return err @@ -862,7 +862,7 @@ func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.Priv return nil } -func decryptMsg(msg []byte, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]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)) @@ -878,7 +878,7 @@ func decryptMsg(msg []byte, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byt return decrypted, nil } -func encode(v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte, error) { +func encode(v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) { b, err := json.Marshal(v) if err != nil { return nil, err @@ -897,27 +897,27 @@ func encode(v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte return msg, nil } -func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgcfg.Key, error) { +func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgkey.Key, error) { req, err := http.NewRequest("GET", serverURL+"/key", nil) if err != nil { - return wgcfg.Key{}, fmt.Errorf("create control key request: %v", err) + return wgkey.Key{}, fmt.Errorf("create control key request: %v", err) } req = req.WithContext(ctx) res, err := httpc.Do(req) if err != nil { - return wgcfg.Key{}, fmt.Errorf("fetch control key: %v", err) + return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err) } defer res.Body.Close() b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16)) if err != nil { - return wgcfg.Key{}, fmt.Errorf("fetch control key response: %v", err) + return wgkey.Key{}, fmt.Errorf("fetch control key response: %v", err) } if res.StatusCode != 200 { - return wgcfg.Key{}, 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)) } - key, err := wgcfg.ParseHexKey(string(b)) + key, err := wgkey.ParseHex(string(b)) if err != nil { - return wgcfg.Key{}, fmt.Errorf("fetch control key: %v", err) + return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err) } return key, nil } diff --git a/control/controlclient/netmap.go b/control/controlclient/netmap.go index cda3239e4..c9d053449 100644 --- a/control/controlclient/netmap.go +++ b/control/controlclient/netmap.go @@ -17,6 +17,7 @@ import ( "inet.af/netaddr" "tailscale.com/tailcfg" "tailscale.com/types/logger" + "tailscale.com/types/wgkey" "tailscale.com/wgengine/filter" ) @@ -24,7 +25,7 @@ type NetworkMap struct { // Core networking NodeKey tailcfg.NodeKey - PrivateKey wgcfg.PrivateKey + PrivateKey wgkey.Private Expiry time.Time // Name is the DNS name assigned to this node. Name string @@ -241,7 +242,7 @@ const EndpointDiscoSuffix = ".disco.tailscale:12345" func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Config, error) { cfg := &wgcfg.Config{ Name: "tailscale", - PrivateKey: nm.PrivateKey, + PrivateKey: wgcfg.PrivateKey(nm.PrivateKey), Addresses: nm.Addresses, ListenPort: nm.LocalPort, Peers: make([]wgcfg.Peer, 0, len(nm.Peers)), diff --git a/control/controlclient/persist_test.go b/control/controlclient/persist_test.go index c769b5e48..efee06273 100644 --- a/control/controlclient/persist_test.go +++ b/control/controlclient/persist_test.go @@ -8,7 +8,7 @@ import ( "reflect" "testing" - "github.com/tailscale/wireguard-go/wgcfg" + "tailscale.com/types/wgkey" ) func TestPersistEqual(t *testing.T) { @@ -18,8 +18,8 @@ func TestPersistEqual(t *testing.T) { have, persistHandles) } - newPrivate := func() wgcfg.PrivateKey { - k, err := wgcfg.NewPrivateKey() + newPrivate := func() wgkey.Private { + k, err := wgkey.NewPrivate() if err != nil { panic(err) } diff --git a/go.sum b/go.sum index bff9913b7..4d963c8d0 100644 --- a/go.sum +++ b/go.sum @@ -68,7 +68,9 @@ github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/walk v0.0.0-20201110160827-18ea5e372cdb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= @@ -104,6 +106,7 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI= github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw= github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/tailscale/wireguard-go v0.0.0-20201220011020-db78fad0bebf h1:HuBwLWbDNIh/G72KSImSEx+dnd7FPGFI1e60LMJtLjU= @@ -124,6 +127,7 @@ go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7 h1:yeDrXaQ3VRXbTN7lHj70DxW4LdP go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/intern v0.0.0-20201223061701-969c7e87e7cb h1:yuqO0E4bHRsTPUocDpRKXfLE40lwWplVxENQ2WOV7Gc= go4.org/intern v0.0.0-20201223061701-969c7e87e7cb/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= +go4.org/mem v0.0.0-20200706164138-185c595c3ecc/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k= go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk= go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e h1:ExUmGi0ZsQmiVo9giDQqXkr7vreeXPMkOGIusfsfbzI= @@ -131,13 +135,17 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1: go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -153,6 +161,9 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -165,6 +176,7 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -173,10 +185,15 @@ golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -192,6 +209,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q= golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -199,8 +217,11 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqG golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -208,7 +229,11 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wireguard v0.0.20200321-0.20200715051853-507f148e1c42/go.mod h1:GJvYs5O24/ASlwPiRklVnjMx2xQzrOic0DuU6GvYJL4= +golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ56hhpeoESmWzI4Exhx4Y78TpCyXUJur4/c0CoE= golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA= +golang.zx2c4.com/wireguard/windows v0.1.2-0.20201004085714-dd60d0447f81/go.mod h1:GaK5zcgr5XE98WaRzIDilumDBp5/yP8j2kG/LCDnvAM= +golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 h1:nlXPqGA98n+qcq1pwZ28KjM5EsFQvamKS00A+VUeVjs= golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8/go.mod h1:psva4yDnAHLuh7lUzOK7J7bLYxNFfo0iKWz+mi9gzkA= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -219,8 +244,10 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c= honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM= +inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= inet.af/netaddr v0.0.0-20201218162718-658fec415e52/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= inet.af/netaddr v0.0.0-20201223185330-97d366981fac h1:aqMW8vft7VmOIhtQhsTWhAuZzOBGYBv+Otyvwj+VGSU= inet.af/netaddr v0.0.0-20201223185330-97d366981fac/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= @@ -232,3 +259,4 @@ inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf h1:0eHZ8v6j5wIiOVyoYPd70ueZ/R inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= +tailscale.com v1.2.10/go.mod h1:JEJiCce3MHtPCTdX2ahLc4tcnxZ7b5etish1Yt0B6+w= diff --git a/ipn/local.go b/ipn/local.go index d2b2f3c78..5b781542c 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -29,6 +29,7 @@ import ( "tailscale.com/types/empty" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/wgkey" "tailscale.com/util/systemd" "tailscale.com/version" "tailscale.com/wgengine" @@ -83,7 +84,7 @@ type LocalBackend struct { userID string // current controlling user ID (for Windows, primarily) prefs *Prefs inServerMode bool - machinePrivKey wgcfg.PrivateKey + machinePrivKey wgkey.Private state State // hostinfo is mutated in-place while mu is held. hostinfo *tailcfg.Hostinfo @@ -737,7 +738,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) { return nil } - var legacyMachineKey wgcfg.PrivateKey + var legacyMachineKey wgkey.Private if b.prefs.Persist != nil { legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey } @@ -772,7 +773,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) { } else { b.logf("generating new machine key") var err error - b.machinePrivKey, err = wgcfg.NewPrivateKey() + b.machinePrivKey, err = wgkey.NewPrivate() if err != nil { return fmt.Errorf("initializing new machine key: %w", err) } diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 47fc02f6c..9715d7c89 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -13,10 +13,10 @@ import ( "testing" "time" - "github.com/tailscale/wireguard-go/wgcfg" "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/tstest" + "tailscale.com/types/wgkey" "tailscale.com/wgengine/router" ) @@ -348,7 +348,7 @@ func TestPrefsPretty(t *testing.T) { { Prefs{ Persist: &controlclient.Persist{ - PrivateNodeKey: wgcfg.PrivateKey{1: 1}, + PrivateNodeKey: wgkey.Private{1: 1}, }, }, "linux", diff --git a/ipn/store.go b/ipn/store.go index 117c68ca7..acce235d2 100644 --- a/ipn/store.go +++ b/ipn/store.go @@ -24,7 +24,7 @@ var ErrStateNotExist = errors.New("no state with given ID") const ( // MachineKeyStateKey is the key under which we store the machine key, - // in its wgcfg.PrivateKey.MarshalText representation. + // in its wgkey.Private.MarshalText representation. MachineKeyStateKey = StateKey("_machinekey") // GlobalDaemonStateKey is the ipn.StateKey that tailscaled diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index d0e2d6dce..93d3a6fae 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "github.com/tailscale/wireguard-go/wgcfg" "inet.af/netaddr" + "tailscale.com/types/wgkey" ) func fieldsOf(t reflect.Type) (fields []string) { @@ -194,9 +194,9 @@ func TestNodeEqual(t *testing.T) { have, nodeHandles) } - newPublicKey := func(t *testing.T) wgcfg.Key { + newPublicKey := func(t *testing.T) wgkey.Key { t.Helper() - k, err := wgcfg.NewPrivateKey() + k, err := wgkey.NewPrivate() if err != nil { t.Fatal(err) } diff --git a/types/key/key_test.go b/types/key/key_test.go index 4c5c97625..7a2155ef0 100644 --- a/types/key/key_test.go +++ b/types/key/key_test.go @@ -7,7 +7,7 @@ package key import ( "testing" - "github.com/tailscale/wireguard-go/wgcfg" + "tailscale.com/types/wgkey" ) func TestTextUnmarshal(t *testing.T) { @@ -28,10 +28,10 @@ func TestTextUnmarshal(t *testing.T) { func TestClamping(t *testing.T) { t.Run("NewPrivate", func(t *testing.T) { testClamping(t, NewPrivate) }) - // Also test the wgcfg package, as their behavior should match. - t.Run("wgcfg", func(t *testing.T) { + // Also test the wgkey package, as their behavior should match. + t.Run("wgkey", func(t *testing.T) { testClamping(t, func() Private { - k, err := wgcfg.NewPrivateKey() + k, err := wgkey.NewPrivate() if err != nil { t.Fatal(err) } diff --git a/types/wgkey/key.go b/types/wgkey/key.go new file mode 100644 index 000000000..b9ba6deed --- /dev/null +++ b/types/wgkey/key.go @@ -0,0 +1,241 @@ +// Copyright (c) 2020 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 wgkey contains types and helpers for WireGuard keys. +// It is very similar to package tailscale.com/types/key, +// which is also used for curve25519 keys. +// These keys are used for WireGuard clients; +// those keys are used in other curve25519 clients. +package wgkey + +import ( + "bytes" + "crypto/rand" + "crypto/subtle" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "strings" + + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/curve25519" +) + +// Size is the number of bytes in a curve25519 key. +const Size = 32 + +// A Key is a curve25519 key. +// It is used by WireGuard to represent public keys. +type Key [Size]byte + +// NewPreshared generates a new random Key. +func NewPreshared() (*Key, error) { + var k [Size]byte + _, err := rand.Read(k[:]) + if err != nil { + return nil, err + } + return (*Key)(&k), nil +} + +func Parse(b64 string) (*Key, error) { return parseBase64(base64.StdEncoding, b64) } + +func ParseHex(s string) (Key, error) { + b, err := hex.DecodeString(s) + if err != nil { + return Key{}, fmt.Errorf("invalid hex key (%q): %w", s, err) + } + if len(b) != Size { + return Key{}, fmt.Errorf("invalid hex key (%q): length=%d, want %d", s, len(b), Size) + } + + var key Key + copy(key[:], b) + return key, nil +} + +func ParsePrivateHex(v string) (Private, error) { + k, err := ParseHex(v) + if err != nil { + return Private{}, err + } + pk := Private(k) + if pk.IsZero() { + // Do not clamp a zero key, pass the zero through + // (much like NaN propagation) so that IsZero reports + // a useful result. + return pk, nil + } + pk.clamp() + return pk, nil +} + +func (k Key) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) } +func (k Key) String() string { return k.ShortString() } +func (k Key) HexString() string { return hex.EncodeToString(k[:]) } +func (k Key) Equal(k2 Key) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 } + +func (k *Key) ShortString() string { + long := k.Base64() + return "[" + long[0:5] + "]" +} + +func (k *Key) IsZero() bool { + if k == nil { + return true + } + var zeros Key + return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1 +} + +func (k *Key) MarshalJSON() ([]byte, error) { + if k == nil { + return []byte("null"), nil + } + // TODO(josharian): use encoding/hex instead? + buf := new(bytes.Buffer) + fmt.Fprintf(buf, `"%x"`, k[:]) + return buf.Bytes(), nil +} + +func (k *Key) UnmarshalJSON(b []byte) error { + if k == nil { + return errors.New("wgkey.Key: UnmarshalJSON on nil pointer") + } + if len(b) < 3 || b[0] != '"' || b[len(b)-1] != '"' { + return errors.New("wgkey.Key: UnmarshalJSON not given a string") + } + b = b[1 : len(b)-1] + key, err := ParseHex(string(b)) + if err != nil { + return fmt.Errorf("wgkey.Key: UnmarshalJSON: %v", err) + } + copy(k[:], key[:]) + return nil +} + +func (a *Key) LessThan(b *Key) bool { + for i := range a { + if a[i] < b[i] { + return true + } else if a[i] > b[i] { + return false + } + } + return false +} + +// A Private is a curve25519 key. +// It is used by WireGuard to represent private keys. +type Private [Size]byte + +// NewPrivate generates a new curve25519 secret key. +// It conforms to the format described on https://cr.yp.to/ecdh.html. +func NewPrivate() (Private, error) { + k, err := NewPreshared() + if err != nil { + return Private{}, err + } + k[0] &= 248 + k[31] = (k[31] & 127) | 64 + return (Private)(*k), nil +} + +func ParsePrivate(b64 string) (*Private, error) { + k, err := parseBase64(base64.StdEncoding, b64) + return (*Private)(k), err +} + +func (k *Private) String() string { return base64.StdEncoding.EncodeToString(k[:]) } +func (k *Private) HexString() string { return hex.EncodeToString(k[:]) } +func (k *Private) Equal(k2 Private) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 } + +func (k *Private) IsZero() bool { + pk := Key(*k) + return pk.IsZero() +} + +func (k *Private) clamp() { + k[0] &= 248 + k[31] = (k[31] & 127) | 64 +} + +// Public computes the public key matching this curve25519 secret key. +func (k *Private) Public() Key { + pk := Key(*k) + if pk.IsZero() { + panic("Tried to generate emptyPrivate.Public()") + } + var p [Size]byte + curve25519.ScalarBaseMult(&p, (*[Size]byte)(k)) + return (Key)(p) +} + +func (k Private) MarshalText() ([]byte, error) { + // TODO(josharian): use encoding/hex instead? + buf := new(bytes.Buffer) + fmt.Fprintf(buf, `privkey:%x`, k[:]) + return buf.Bytes(), nil +} + +func (k *Private) UnmarshalText(b []byte) error { + s := string(b) + if !strings.HasPrefix(s, `privkey:`) { + return errors.New("wgkey.Private: UnmarshalText not given a private-key string") + } + s = strings.TrimPrefix(s, `privkey:`) + key, err := ParseHex(s) + if err != nil { + return fmt.Errorf("wgkey.Private: UnmarshalText: %v", err) + } + copy(k[:], key[:]) + return nil +} + +func parseBase64(enc *base64.Encoding, s string) (*Key, error) { + k, err := enc.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("invalid key (%q): %w", s, err) + } + if len(k) != Size { + return nil, fmt.Errorf("invalid key (%q): length=%d, want %d", s, len(k), Size) + } + var key Key + copy(key[:], k) + return &key, nil +} + +func ParseSymmetric(b64 string) (Symmetric, error) { + k, err := parseBase64(base64.StdEncoding, b64) + if err != nil { + return Symmetric{}, err + } + return Symmetric(*k), nil +} + +func ParseSymmetricHex(s string) (Symmetric, error) { + b, err := hex.DecodeString(s) + if err != nil { + return Symmetric{}, fmt.Errorf("invalid symmetric hex key (%q): %w", s, err) + } + if len(b) != chacha20poly1305.KeySize { + return Symmetric{}, fmt.Errorf("invalid symmetric hex key length (%q): length=%d, want %d", s, len(b), chacha20poly1305.KeySize) + } + var key Symmetric + copy(key[:], b) + return key, nil +} + +// Symmetric is a chacha20poly1305 key. +// It is used by WireGuard to represent pre-shared symmetric keys. +type Symmetric [chacha20poly1305.KeySize]byte + +func (k Symmetric) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) } +func (k Symmetric) String() string { return "sym:" + k.Base64()[:8] } +func (k Symmetric) HexString() string { return hex.EncodeToString(k[:]) } +func (k Symmetric) IsZero() bool { return k.Equal(Symmetric{}) } +func (k Symmetric) Equal(k2 Symmetric) bool { + return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 +} diff --git a/types/wgkey/key_test.go b/types/wgkey/key_test.go new file mode 100644 index 000000000..9b8632a3b --- /dev/null +++ b/types/wgkey/key_test.go @@ -0,0 +1,111 @@ +// Copyright (c) 2020 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 wgkey + +import ( + "bytes" + "testing" +) + +func TestKeyBasics(t *testing.T) { + k1, err := NewPreshared() + if err != nil { + t.Fatal(err) + } + + b, err := k1.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + t.Run("JSON round-trip", func(t *testing.T) { + // should preserve the keys + k2 := new(Key) + if err := k2.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + if !bytes.Equal(k1[:], k2[:]) { + t.Fatalf("k1 %v != k2 %v", k1[:], k2[:]) + } + if b1, b2 := k1.String(), k2.String(); b1 != b2 { + t.Fatalf("base64-encoded keys do not match: %s, %s", b1, b2) + } + }) + + t.Run("JSON incompatible with PrivateKey", func(t *testing.T) { + k2 := new(Private) + if err := k2.UnmarshalText(b); err == nil { + t.Fatalf("successfully decoded key as private key") + } + }) + + t.Run("second key", func(t *testing.T) { + // A second call to NewPreshared should make a new key. + k3, err := NewPreshared() + if err != nil { + t.Fatal(err) + } + if bytes.Equal(k1[:], k3[:]) { + t.Fatalf("k1 %v == k3 %v", k1[:], k3[:]) + } + // Check for obvious comparables to make sure we are not generating bad strings somewhere. + if b1, b2 := k1.String(), k3.String(); b1 == b2 { + t.Fatalf("base64-encoded keys match: %s, %s", b1, b2) + } + }) +} +func TestPrivateKeyBasics(t *testing.T) { + pri, err := NewPrivate() + if err != nil { + t.Fatal(err) + } + + b, err := pri.MarshalText() + if err != nil { + t.Fatal(err) + } + + t.Run("JSON round-trip", func(t *testing.T) { + // should preserve the keys + pri2 := new(Private) + if err := pri2.UnmarshalText(b); err != nil { + t.Fatal(err) + } + if !bytes.Equal(pri[:], pri2[:]) { + t.Fatalf("pri %v != pri2 %v", pri[:], pri2[:]) + } + if b1, b2 := pri.String(), pri2.String(); b1 != b2 { + t.Fatalf("base64-encoded keys do not match: %s, %s", b1, b2) + } + if pub1, pub2 := pri.Public().String(), pri2.Public().String(); pub1 != pub2 { + t.Fatalf("base64-encoded public keys do not match: %s, %s", pub1, pub2) + } + }) + + t.Run("JSON incompatible with Key", func(t *testing.T) { + k2 := new(Key) + if err := k2.UnmarshalJSON(b); err == nil { + t.Fatalf("successfully decoded private key as key") + } + }) + + t.Run("second key", func(t *testing.T) { + // A second call to New should make a new key. + pri3, err := NewPrivate() + if err != nil { + t.Fatal(err) + } + if bytes.Equal(pri[:], pri3[:]) { + t.Fatalf("pri %v == pri3 %v", pri[:], pri3[:]) + } + // Check for obvious comparables to make sure we are not generating bad strings somewhere. + if b1, b2 := pri.String(), pri3.String(); b1 == b2 { + t.Fatalf("base64-encoded keys match: %s, %s", b1, b2) + } + if pub1, pub2 := pri.Public().String(), pri3.Public().String(); pub1 == pub2 { + t.Fatalf("base64-encoded public keys match: %s, %s", pub1, pub2) + } + }) +} diff --git a/wgengine/magicsock/legacy.go b/wgengine/magicsock/legacy.go index cb61a0a2d..083f313db 100644 --- a/wgengine/magicsock/legacy.go +++ b/wgengine/magicsock/legacy.go @@ -20,6 +20,7 @@ import ( "tailscale.com/ipn/ipnstate" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/wgkey" ) var errNoDestinations = errors.New("magicsock: no destinations") @@ -387,7 +388,7 @@ func (a *addrSet) UpdateDst(new *net.UDPAddr) error { } } - publicKey := wgcfg.Key(a.publicKey) + publicKey := wgkey.Key(a.publicKey) pk := publicKey.ShortString() old := "" if a.curAddr >= 0 { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 8073d01e0..c162da9b0 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -52,6 +52,7 @@ import ( "tailscale.com/types/nettype" "tailscale.com/types/opt" "tailscale.com/types/structs" + "tailscale.com/types/wgkey" "tailscale.com/version" ) @@ -1562,7 +1563,7 @@ Top: } else if asEp != nil { ep = asEp } else { - key := wgcfg.Key(dm.src) + key := wgkey.Key(dm.src) c.logf("magicsock: DERP packet from unknown key: %s", key.ShortString()) // TODO(danderson): after we fail to find a DERP endpoint, we // seem to be falling through to passing the packet to @@ -1952,7 +1953,7 @@ func (c *Conn) SetNetworkUp(up bool) { // // If the private key changes, any DERP connections are torn down & // recreated when needed. -func (c *Conn) SetPrivateKey(privateKey wgcfg.PrivateKey) error { +func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error { c.mu.Lock() defer c.mu.Unlock() @@ -2660,7 +2661,7 @@ func simpleDur(d time.Duration) time.Duration { } func peerShort(k key.Public) string { - k2 := wgcfg.Key(k) + k2 := wgkey.Key(k) return k2.ShortString() } diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 0ca3816c4..7aac0572a 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -42,6 +42,7 @@ import ( "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/types/nettype" + "tailscale.com/types/wgkey" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/tstun" ) @@ -119,7 +120,7 @@ func runDERPAndStun(t *testing.T, logf logger.Logf, l nettype.PacketListener, st // necessary to send and receive packets to test e2e wireguard // happiness. type magicStack struct { - privateKey wgcfg.PrivateKey + privateKey wgkey.Private epCh chan []string // endpoint updates produced by this peer conn *Conn // the magicsock itself tun *tuntest.ChannelTUN // TUN device to send/receive packets @@ -133,7 +134,7 @@ type magicStack struct { func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack { t.Helper() - privateKey, err := wgcfg.NewPrivateKey() + privateKey, err := wgkey.NewPrivate() if err != nil { t.Fatalf("generating private key: %v", err) } @@ -347,7 +348,7 @@ func TestNewConn(t *testing.T) { } defer conn.Close() conn.SetDERPMap(stuntest.DERPMapOf(stunAddr.String())) - conn.SetPrivateKey(wgcfg.PrivateKey(key.NewPrivate())) + conn.SetPrivateKey(wgkey.Private(key.NewPrivate())) conn.Start() go func() { @@ -457,11 +458,11 @@ func makeConfigs(t *testing.T, addrs []netaddr.IPPort) []wgcfg.Config { var addresses [][]netaddr.IPPrefix for i := range addrs { - privKey, err := wgcfg.NewPrivateKey() + privKey, err := wgkey.NewPrivate() if err != nil { t.Fatal(err) } - privKeys = append(privKeys, privKey) + privKeys = append(privKeys, wgcfg.PrivateKey(privKey)) addresses = append(addresses, []netaddr.IPPrefix{ parseCIDR(t, fmt.Sprintf("1.0.0.%d/32", i+1)), diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 13397c60f..f7b403ce1 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -37,6 +37,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/wgkey" "tailscale.com/version" "tailscale.com/version/distro" "tailscale.com/wgengine/filter" @@ -113,9 +114,9 @@ type userspaceEngine struct { closing bool // Close was called (even if we're still closing) statusCallback StatusCallback linkChangeCallback func(major bool, newState *interfaces.State) - peerSequence []wgcfg.Key + peerSequence []wgkey.Key endpoints []string - pingers map[wgcfg.Key]*pinger // legacy pingers for pre-discovery peers + pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers linkState *interfaces.State // Lock ordering: magicsock.Conn.mu, wgLock, then mu. @@ -202,7 +203,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { waitCh: make(chan struct{}), tundev: tstun.WrapTUN(logf, conf.TUN), resolver: tsdns.NewResolver(rconf), - pingers: make(map[wgcfg.Key]*pinger), + pingers: make(map[wgkey.Key]*pinger), } e.localAddrs.Store(map[netaddr.IP]bool{}) e.linkState, _ = getLinkState() @@ -290,7 +291,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { } } if len(ips) > 0 { - go e.pinger(peerKey, ips) + go e.pinger(wgkey.Key(peerKey), ips) } else { logf("[unexpected] peer %s has no single-IP routes: %v", peerKey.ShortString(), allowedIPs) } @@ -487,7 +488,7 @@ func (p *pinger) close() { <-p.done } -func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []netaddr.IP, srcIP netaddr.IP) { +func (p *pinger) run(ctx context.Context, peerKey wgkey.Key, ips []netaddr.IP, srcIP netaddr.IP) { defer func() { p.e.mu.Lock() if p.e.pingers[peerKey] == p { @@ -556,7 +557,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []netaddr.IP, s // // This is only used with legacy peers (before 0.100.0) that don't // have advertised discovery keys. -func (e *userspaceEngine) pinger(peerKey wgcfg.Key, ips []netaddr.IP) { +func (e *userspaceEngine) pinger(peerKey wgkey.Key, ips []netaddr.IP) { e.logf("[v1] generating initial ping traffic to %s (%v)", peerKey.ShortString(), ips) var srcIP netaddr.IP @@ -890,7 +891,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) e.mu.Lock() e.peerSequence = e.peerSequence[:0] for _, p := range cfg.Peers { - e.peerSequence = append(e.peerSequence, p.PublicKey) + e.peerSequence = append(e.peerSequence, wgkey.Key(p.PublicKey)) peerSet[key.Public(p.PublicKey)] = struct{}{} } e.mu.Unlock() @@ -932,7 +933,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) // (which is needed by DERP) before wgdev gets it, as wgdev // will start trying to handshake, which we want to be able to // go over DERP. - if err := e.magicConn.SetPrivateKey(cfg.PrivateKey); err != nil { + if err := e.magicConn.SetPrivateKey(wgkey.Private(cfg.PrivateKey)); err != nil { e.logf("wgengine: Reconfig: SetPrivateKey: %v", err) } e.magicConn.UpdatePeers(peerSet) @@ -1039,7 +1040,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) { errc <- bw.Flush() }() - pp := make(map[wgcfg.Key]*PeerStatus) + pp := make(map[wgkey.Key]*PeerStatus) p := &PeerStatus{} var hst1, hst2, n int64 @@ -1062,7 +1063,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) { log.Fatalf("IpcGetOperation: invalid key %#v", v) } p = &PeerStatus{} - pp[wgcfg.Key(pk)] = p + pp[wgkey.Key(pk)] = p key := tailcfg.NodeKey(pk) p.NodeKey = key