mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 14:05:39 +00:00
client,cmd/tailscale,ipn,tka,types: implement tka initialization flow
This PR implements the client-side of initializing network-lock with the Coordination server. Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
parent
18edd79421
commit
facafd8819
@ -36,6 +36,7 @@
|
|||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tka"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultLocalClient is the default LocalClient when using the legacy
|
// defaultLocalClient is the default LocalClient when using the legacy
|
||||||
@ -680,6 +681,42 @@ func (lc *LocalClient) Ping(ctx context.Context, ip netip.Addr, pingtype tailcfg
|
|||||||
return pr, nil
|
return pr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkLockStatus fetches information about the tailnet key authority, if one is configured.
|
||||||
|
func (lc *LocalClient) NetworkLockStatus(ctx context.Context) (*ipnstate.NetworkLockStatus, error) {
|
||||||
|
body, err := lc.send(ctx, "GET", "/localapi/v0/tka/status", 200, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error: %w", err)
|
||||||
|
}
|
||||||
|
pr := new(ipnstate.NetworkLockStatus)
|
||||||
|
if err := json.Unmarshal(body, pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkLockInit initializes the tailnet key authority.
|
||||||
|
func (lc *LocalClient) NetworkLockInit(ctx context.Context, keys []tka.Key) (*ipnstate.NetworkLockStatus, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
type initRequest struct {
|
||||||
|
Keys []tka.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(&b).Encode(initRequest{Keys: keys}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := lc.send(ctx, "POST", "/localapi/v0/tka/init", 200, &b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr := new(ipnstate.NetworkLockStatus)
|
||||||
|
if err := json.Unmarshal(body, pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
// tailscaledConnectHint gives a little thing about why tailscaled (or
|
// tailscaledConnectHint gives a little thing about why tailscaled (or
|
||||||
// platform equivalent) is not answering localapi connections.
|
// platform equivalent) is not answering localapi connections.
|
||||||
//
|
//
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depaware)
|
tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depaware)
|
||||||
|
|
||||||
|
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||||
|
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||||
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
||||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
@ -12,6 +16,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||||
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||||
go4.org/netipx from tailscale.com/wgengine/filter
|
go4.org/netipx from tailscale.com/wgengine/filter
|
||||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||||
@ -46,6 +51,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
tailscale.com/safesocket from tailscale.com/client/tailscale
|
tailscale.com/safesocket from tailscale.com/client/tailscale
|
||||||
tailscale.com/syncs from tailscale.com/cmd/derper+
|
tailscale.com/syncs from tailscale.com/cmd/derper+
|
||||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||||
|
tailscale.com/tka from tailscale.com/client/tailscale
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||||
@ -76,7 +82,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
tailscale.com/wgengine/filter from tailscale.com/types/netmap
|
tailscale.com/wgengine/filter from tailscale.com/types/netmap
|
||||||
golang.org/x/crypto/acme from golang.org/x/crypto/acme/autocert
|
golang.org/x/crypto/acme from golang.org/x/crypto/acme/autocert
|
||||||
golang.org/x/crypto/acme/autocert from tailscale.com/cmd/derper
|
golang.org/x/crypto/acme/autocert from tailscale.com/cmd/derper
|
||||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/argon2 from tailscale.com/tka
|
||||||
|
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box+
|
||||||
|
golang.org/x/crypto/blake2s from tailscale.com/tka
|
||||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
||||||
golang.org/x/crypto/chacha20poly1305 from crypto/tls
|
golang.org/x/crypto/chacha20poly1305 from crypto/tls
|
||||||
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
||||||
@ -133,6 +141,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
embed from crypto/internal/nistec+
|
embed from crypto/internal/nistec+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
|
encoding/base32 from tailscale.com/tka
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
encoding/binary from compress/gzip+
|
encoding/binary from compress/gzip+
|
||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
|
@ -169,6 +169,7 @@ func Run(args []string) (err error) {
|
|||||||
fileCmd,
|
fileCmd,
|
||||||
bugReportCmd,
|
bugReportCmd,
|
||||||
certCmd,
|
certCmd,
|
||||||
|
netlockCmd,
|
||||||
},
|
},
|
||||||
FlagSet: rootfs,
|
FlagSet: rootfs,
|
||||||
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||||
|
101
cmd/tailscale/cli/network-lock.go
Normal file
101
cmd/tailscale/cli/network-lock.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
|
"tailscale.com/tka"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
var netlockCmd = &ffcli.Command{
|
||||||
|
Name: "lock",
|
||||||
|
ShortUsage: "lock <sub-command> <arguments>",
|
||||||
|
ShortHelp: "Manipulate the tailnet key authority",
|
||||||
|
Subcommands: []*ffcli.Command{nlInitCmd, nlStatusCmd},
|
||||||
|
Exec: runNetworkLockStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
var nlInitCmd = &ffcli.Command{
|
||||||
|
Name: "init",
|
||||||
|
ShortUsage: "init <public-key>...",
|
||||||
|
ShortHelp: "Initialize the tailnet key authority",
|
||||||
|
Exec: runNetworkLockInit,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runNetworkLockInit(ctx context.Context, args []string) error {
|
||||||
|
st, err := localClient.NetworkLockStatus(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fixTailscaledConnectError(err)
|
||||||
|
}
|
||||||
|
if st.Enabled {
|
||||||
|
return errors.New("network-lock is already enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the set of initially-trusted keys.
|
||||||
|
// Keys are specified using their key.NLPublic.MarshalText representation,
|
||||||
|
// with an optional '?<votes>' suffix.
|
||||||
|
var keys []tka.Key
|
||||||
|
for i, a := range args {
|
||||||
|
var key key.NLPublic
|
||||||
|
spl := strings.SplitN(a, "?", 2)
|
||||||
|
if err := key.UnmarshalText([]byte(spl[0])); err != nil {
|
||||||
|
return fmt.Errorf("parsing key %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := tka.Key{
|
||||||
|
Kind: tka.Key25519,
|
||||||
|
Public: key.Verifier(),
|
||||||
|
Votes: 1,
|
||||||
|
}
|
||||||
|
if len(spl) > 1 {
|
||||||
|
votes, err := strconv.Atoi(spl[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing key %d votes: %v", i+1, err)
|
||||||
|
}
|
||||||
|
k.Votes = uint(votes)
|
||||||
|
}
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := localClient.NetworkLockInit(ctx, keys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Status: %+v\n\n", status)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var nlStatusCmd = &ffcli.Command{
|
||||||
|
Name: "status",
|
||||||
|
ShortUsage: "status",
|
||||||
|
ShortHelp: "Outputs the state of network lock",
|
||||||
|
Exec: runNetworkLockStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runNetworkLockStatus(ctx context.Context, args []string) error {
|
||||||
|
st, err := localClient.NetworkLockStatus(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fixTailscaledConnectError(err)
|
||||||
|
}
|
||||||
|
if st.Enabled {
|
||||||
|
fmt.Println("Network-lock is ENABLED.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Network-lock is NOT enabled.")
|
||||||
|
}
|
||||||
|
p, err := st.PublicKey.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("our public-key: %s\n", p)
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,9 +1,13 @@
|
|||||||
tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware)
|
tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware)
|
||||||
|
|
||||||
|
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
|
||||||
|
filippo.io/edwards25519/field from filippo.io/edwards25519
|
||||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
||||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||||
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
||||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
@ -26,6 +30,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||||
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
|
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
|
||||||
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
💣 go4.org/mem from tailscale.com/derp+
|
💣 go4.org/mem from tailscale.com/derp+
|
||||||
go4.org/netipx from tailscale.com/wgengine/filter
|
go4.org/netipx from tailscale.com/wgengine/filter
|
||||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||||
@ -69,6 +74,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/syncs from tailscale.com/net/netcheck+
|
tailscale.com/syncs from tailscale.com/net/netcheck+
|
||||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||||
|
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||||
@ -100,8 +106,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/wgengine/filter from tailscale.com/types/netmap
|
tailscale.com/wgengine/filter from tailscale.com/types/netmap
|
||||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/argon2 from tailscale.com/tka
|
||||||
golang.org/x/crypto/blake2s from tailscale.com/control/controlbase
|
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box+
|
||||||
|
golang.org/x/crypto/blake2s from tailscale.com/control/controlbase+
|
||||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
||||||
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
|
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
|
||||||
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
||||||
@ -162,6 +169,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
embed from tailscale.com/cmd/tailscale/cli+
|
embed from tailscale.com/cmd/tailscale/cli+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
|
encoding/base32 from tailscale.com/tka
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
encoding/binary from compress/gzip+
|
encoding/binary from compress/gzip+
|
||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
|
226
ipn/ipnlocal/network-lock.go
Normal file
226
ipn/ipnlocal/network-lock.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// 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 ipnlocal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/envknob"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
|
"tailscale.com/logtail/backoff"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tka"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
"tailscale.com/types/tkatype"
|
||||||
|
)
|
||||||
|
|
||||||
|
var networkLockAvailable = envknob.Bool("TS_EXPERIMENTAL_NETWORK_LOCK")
|
||||||
|
|
||||||
|
// CanSupportNetworkLock returns true if tailscaled is able to operate
|
||||||
|
// a local tailnet key authority (and hence enforce network lock).
|
||||||
|
func (b *LocalBackend) CanSupportNetworkLock() bool {
|
||||||
|
if b.tka != nil {
|
||||||
|
// The TKA is being used, so yeah its supported.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.TailscaleVarRoot() != "" {
|
||||||
|
// Theres a var root (aka --statedir), so if network lock gets
|
||||||
|
// initialized we have somewhere to store our AUMs. Thats all
|
||||||
|
// we need.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkLockStatus returns a structure describing the state of the
|
||||||
|
// tailnet key authority, if any.
|
||||||
|
func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
||||||
|
if b.tka == nil {
|
||||||
|
return &ipnstate.NetworkLockStatus{
|
||||||
|
Enabled: false,
|
||||||
|
PublicKey: b.nlPrivKey.Public(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var head [32]byte
|
||||||
|
h := b.tka.Head()
|
||||||
|
copy(head[:], h[:])
|
||||||
|
|
||||||
|
return &ipnstate.NetworkLockStatus{
|
||||||
|
Enabled: true,
|
||||||
|
Head: &head,
|
||||||
|
PublicKey: b.nlPrivKey.Public(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkLockInit enables network-lock for the tailnet, with the tailnets'
|
||||||
|
// key authority initialized to trust the provided keys.
|
||||||
|
//
|
||||||
|
// Initialization involves two RPCs with control, termed 'begin' and 'finish'.
|
||||||
|
// The Begin RPC transmits the genesis Authority Update Message, which
|
||||||
|
// encodes the initial state of the authority, and the list of all nodes
|
||||||
|
// needing signatures is returned as a response.
|
||||||
|
// The Finish RPC submits signatures for all these nodes, at which point
|
||||||
|
// Control has everything it needs to atomically enable network lock.
|
||||||
|
func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
|
||||||
|
if b.tka != nil {
|
||||||
|
return errors.New("network-lock is already initialized")
|
||||||
|
}
|
||||||
|
if !networkLockAvailable {
|
||||||
|
return errors.New("this is an experimental feature in your version of tailscale - Please upgrade to the latest to use this.")
|
||||||
|
}
|
||||||
|
if !b.CanSupportNetworkLock() {
|
||||||
|
return errors.New("network-lock is not supported in this configuration. Did you supply a --statedir?")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a genesis AUM representing trust in the provided keys.
|
||||||
|
// We use an in-memory tailchonk because we don't want to commit to
|
||||||
|
// the filesystem until we've finished the initialization sequence,
|
||||||
|
// just in case something goes wrong.
|
||||||
|
_, genesisAUM, err := tka.Create(&tka.Mem{}, tka.State{
|
||||||
|
Keys: keys,
|
||||||
|
// TODO(tom): Actually plumb a real disablement value.
|
||||||
|
DisablementSecrets: [][]byte{bytes.Repeat([]byte{1}, 32)},
|
||||||
|
}, b.nlPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("tka.Create: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.logf("Generated genesis AUM to initialize network lock, trusting the following keys:")
|
||||||
|
for i, k := range genesisAUM.State.Keys {
|
||||||
|
b.logf(" - key[%d] = nlpub:%x with %d votes", i, k.Public, k.Votes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 1/2 of initialization: Transmit the genesis AUM to Control.
|
||||||
|
initResp, err := b.tkaInitBegin(genesisAUM)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("tka init-begin RPC: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our genesis AUM was accepted but before Control turns on enforcement of
|
||||||
|
// node-key signatures, we need to sign keys for all the existing nodes.
|
||||||
|
// If we don't get these signatures ahead of time, everyone will loose
|
||||||
|
// connectivity because control won't have any signatures to send which
|
||||||
|
// satisfy network-lock checks.
|
||||||
|
var sigs []tkatype.MarshaledSignature
|
||||||
|
for _, nkp := range initResp.NeedSignatures {
|
||||||
|
nks, err := signNodeKey(nkp, b.nlPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generating signature: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigs = append(sigs, nks.Serialize())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize enablement by transmitting signature for all nodes to Control.
|
||||||
|
_, err = b.tkaInitFinish(sigs)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func signNodeKey(nk key.NodePublic, signer key.NLPrivate) (*tka.NodeKeySignature, error) {
|
||||||
|
p, err := nk.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := tka.NodeKeySignature{
|
||||||
|
SigKind: tka.SigDirect,
|
||||||
|
KeyID: signer.KeyID(),
|
||||||
|
Pubkey: p,
|
||||||
|
}
|
||||||
|
sig.Signature, err = signer.SignNKS(sig.SigHash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("signature failed: %w", err)
|
||||||
|
}
|
||||||
|
return &sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) tkaInitBegin(aum tka.AUM) (*tailcfg.TKAInitBeginResponse, error) {
|
||||||
|
var req bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitBeginRequest{
|
||||||
|
GenesisAUM: aum.Serialize(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("encoding request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
bo := backoff.NewBackoff("tka-init-begin", b.logf, 5*time.Second)
|
||||||
|
for {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("ctx: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://unused/machine/tka/init/begin", &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("req: %w", err)
|
||||||
|
}
|
||||||
|
res, err := b.DoNoiseRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
bo.BackOff(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
body, _ := io.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
return nil, fmt.Errorf("request returned (%d): %s", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
a := new(tailcfg.TKAInitBeginResponse)
|
||||||
|
err = json.NewDecoder(res.Body).Decode(a)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) tkaInitFinish(nks []tkatype.MarshaledSignature) (*tailcfg.TKAInitFinishResponse, error) {
|
||||||
|
var req bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitFinishRequest{
|
||||||
|
Signatures: nks,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("encoding request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
bo := backoff.NewBackoff("tka-init-finish", b.logf, 5*time.Second)
|
||||||
|
for {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("ctx: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://unused/machine/tka/init/finish", &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("req: %w", err)
|
||||||
|
}
|
||||||
|
res, err := b.DoNoiseRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
bo.BackOff(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
body, _ := io.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
return nil, fmt.Errorf("request returned (%d): %s", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
a := new(tailcfg.TKAInitFinishResponse)
|
||||||
|
err = json.NewDecoder(res.Body).Decode(a)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,21 @@ type Status struct {
|
|||||||
User map[tailcfg.UserID]tailcfg.UserProfile
|
User map[tailcfg.UserID]tailcfg.UserProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkLockStatus represents whether network-lock is enabled,
|
||||||
|
// along with details about the locally-known state of the tailnet
|
||||||
|
// key authority.
|
||||||
|
type NetworkLockStatus struct {
|
||||||
|
// Enabled is true if network lock is enabled.
|
||||||
|
Enabled bool
|
||||||
|
|
||||||
|
// Head describes the AUM hash of the leaf AUM. Head is nil
|
||||||
|
// if network lock is not enabled.
|
||||||
|
Head *[32]byte
|
||||||
|
|
||||||
|
// PublicKey describes the nodes' network-lock public key.
|
||||||
|
PublicKey key.NLPublic
|
||||||
|
}
|
||||||
|
|
||||||
// TailnetStatus is information about a Tailscale network ("tailnet").
|
// TailnetStatus is information about a Tailscale network ("tailnet").
|
||||||
type TailnetStatus struct {
|
type TailnetStatus struct {
|
||||||
// Name is the name of the network that's currently in use.
|
// Name is the name of the network that's currently in use.
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/netutil"
|
"tailscale.com/net/netutil"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tka"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
@ -150,6 +151,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.serveIDToken(w, r)
|
h.serveIDToken(w, r)
|
||||||
case "/localapi/v0/upload-client-metrics":
|
case "/localapi/v0/upload-client-metrics":
|
||||||
h.serveUploadClientMetrics(w, r)
|
h.serveUploadClientMetrics(w, r)
|
||||||
|
case "/localapi/v0/tka/status":
|
||||||
|
h.serveTkaStatus(w, r)
|
||||||
|
case "/localapi/v0/tka/init":
|
||||||
|
h.serveTkaInit(w, r)
|
||||||
case "/":
|
case "/":
|
||||||
io.WriteString(w, "tailscaled\n")
|
io.WriteString(w, "tailscaled\n")
|
||||||
default:
|
default:
|
||||||
@ -791,6 +796,58 @@ type clientMetricJSON struct {
|
|||||||
json.NewEncoder(w).Encode(struct{}{})
|
json.NewEncoder(w).Encode(struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveTkaStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitRead {
|
||||||
|
http.Error(w, "lock status access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "use Get", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.MarshalIndent(h.b.NetworkLockStatus(), "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "JSON encoding error", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveTkaInit(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitWrite {
|
||||||
|
http.Error(w, "lock init access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "use POST", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type initRequest struct {
|
||||||
|
Keys []tka.Key
|
||||||
|
}
|
||||||
|
var req initRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "invalid JSON body", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.b.NetworkLockInit(req.Keys); err != nil {
|
||||||
|
http.Error(w, "initialization failed: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.MarshalIndent(h.b.NetworkLockStatus(), "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "JSON encoding error", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(j)
|
||||||
|
}
|
||||||
|
|
||||||
func defBool(a string, def bool) bool {
|
func defBool(a string, def bool) bool {
|
||||||
if a == "" {
|
if a == "" {
|
||||||
return def
|
return def
|
||||||
|
@ -1826,6 +1826,32 @@ type PeerChange struct {
|
|||||||
Capabilities *[]string `json:",omitempty"`
|
Capabilities *[]string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TKAInitBeginRequest submits a genesis AUM to seed the creation of the
|
||||||
|
// tailnet's key authority.
|
||||||
|
type TKAInitBeginRequest struct {
|
||||||
|
NodeID NodeID
|
||||||
|
|
||||||
|
GenesisAUM tkatype.MarshaledAUM
|
||||||
|
}
|
||||||
|
|
||||||
|
// TKAInitBeginResponse describes a set of NodeKeys which must be signed to
|
||||||
|
// complete initialization of the tailnets' key authority.
|
||||||
|
type TKAInitBeginResponse struct {
|
||||||
|
NodeID NodeID
|
||||||
|
|
||||||
|
NeedSignatures []key.NodePublic
|
||||||
|
}
|
||||||
|
|
||||||
|
// TKAInitFinishRequest finalizes initialization of the tailnet key authority
|
||||||
|
// by submitting node-key signatures for all existing nodes.
|
||||||
|
type TKAInitFinishRequest struct {
|
||||||
|
Signatures []tkatype.MarshaledSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// TKAInitFinishResponse describes the successful enablement of the tailnet's
|
||||||
|
// key authority.
|
||||||
|
type TKAInitFinishResponse struct{}
|
||||||
|
|
||||||
// DerpMagicIP is a fake WireGuard endpoint IP address that means to
|
// DerpMagicIP is a fake WireGuard endpoint IP address that means to
|
||||||
// use DERP. When used (in the Node.DERP field), the port number of
|
// use DERP. When used (in the Node.DERP field), the port number of
|
||||||
// the WireGuard endpoint is the DERP region ID number to use.
|
// the WireGuard endpoint is the DERP region ID number to use.
|
||||||
|
@ -216,7 +216,7 @@ func (a *AUM) StaticValidate() error {
|
|||||||
// We would implement encoding.BinaryMarshaler, except that would
|
// We would implement encoding.BinaryMarshaler, except that would
|
||||||
// unfortunately get called by the cbor marshaller resulting in infinite
|
// unfortunately get called by the cbor marshaller resulting in infinite
|
||||||
// recursion.
|
// recursion.
|
||||||
func (a *AUM) Serialize() []byte {
|
func (a *AUM) Serialize() tkatype.MarshaledAUM {
|
||||||
// Why CBOR and not something like JSON?
|
// Why CBOR and not something like JSON?
|
||||||
//
|
//
|
||||||
// The main function of an AUM is to carry signed data. Signatures are
|
// The main function of an AUM is to carry signed data. Signatures are
|
||||||
|
@ -158,7 +158,7 @@ func TestSerialization(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
t.Run(tc.Name, func(t *testing.T) {
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
data := tc.AUM.Serialize()
|
data := []byte(tc.AUM.Serialize())
|
||||||
if diff := cmp.Diff(tc.Expect, data); diff != "" {
|
if diff := cmp.Diff(tc.Expect, data); diff != "" {
|
||||||
t.Errorf("serialization differs (-want, +got):\n%s", diff)
|
t.Errorf("serialization differs (-want, +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
// Types implementing Signer can sign update messages.
|
// Types implementing Signer can sign update messages.
|
||||||
type Signer interface {
|
type Signer interface {
|
||||||
|
// SignAUM returns signatures for the AUM encoded by the given AUMSigHash.
|
||||||
SignAUM(tkatype.AUMSigHash) ([]tkatype.Signature, error)
|
SignAUM(tkatype.AUMSigHash) ([]tkatype.Signature, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,9 @@ func (k Key) StaticValidate() error {
|
|||||||
if k.Votes > 4096 {
|
if k.Votes > 4096 {
|
||||||
return fmt.Errorf("excessive key weight: %d > 4096", k.Votes)
|
return fmt.Errorf("excessive key weight: %d > 4096", k.Votes)
|
||||||
}
|
}
|
||||||
|
if k.Votes == 0 {
|
||||||
|
return errors.New("key votes must be non-zero")
|
||||||
|
}
|
||||||
|
|
||||||
// We have an arbitrary upper limit on the amount
|
// We have an arbitrary upper limit on the amount
|
||||||
// of metadata that can be associated with a key, so
|
// of metadata that can be associated with a key, so
|
||||||
|
@ -55,13 +55,13 @@ type NodeKeySignature struct {
|
|||||||
Signature []byte `cbor:"4,keyasint,omitempty"`
|
Signature []byte `cbor:"4,keyasint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// sigHash returns the cryptographic digest which a signature
|
// SigHash returns the cryptographic digest which a signature
|
||||||
// is over.
|
// is over.
|
||||||
//
|
//
|
||||||
// This is a hash of the serialized structure, sans the signature.
|
// This is a hash of the serialized structure, sans the signature.
|
||||||
// Without this exclusion, the hash used for the signature
|
// Without this exclusion, the hash used for the signature
|
||||||
// would be circularly dependent on the signature.
|
// would be circularly dependent on the signature.
|
||||||
func (s NodeKeySignature) sigHash() [blake2s.Size]byte {
|
func (s NodeKeySignature) SigHash() [blake2s.Size]byte {
|
||||||
dupe := s
|
dupe := s
|
||||||
dupe.Signature = nil
|
dupe.Signature = nil
|
||||||
return blake2s.Sum256(dupe.Serialize())
|
return blake2s.Sum256(dupe.Serialize())
|
||||||
@ -100,7 +100,7 @@ func (s *NodeKeySignature) Unserialize(data []byte) error {
|
|||||||
// verifySignature checks that the NodeKeySignature is authentic and certified
|
// verifySignature checks that the NodeKeySignature is authentic and certified
|
||||||
// by the given verificationKey.
|
// by the given verificationKey.
|
||||||
func (s *NodeKeySignature) verifySignature(verificationKey Key) error {
|
func (s *NodeKeySignature) verifySignature(verificationKey Key) error {
|
||||||
sigHash := s.sigHash()
|
sigHash := s.SigHash()
|
||||||
switch verificationKey.Kind {
|
switch verificationKey.Kind {
|
||||||
case Key25519:
|
case Key25519:
|
||||||
if ed25519consensus.Verify(ed25519.PublicKey(verificationKey.Public), sigHash[:], s.Signature) {
|
if ed25519consensus.Verify(ed25519.PublicKey(verificationKey.Public), sigHash[:], s.Signature) {
|
||||||
|
@ -23,11 +23,11 @@ func TestSigDirect(t *testing.T) {
|
|||||||
KeyID: key.ID(),
|
KeyID: key.ID(),
|
||||||
Pubkey: nodeKeyPub,
|
Pubkey: nodeKeyPub,
|
||||||
}
|
}
|
||||||
sigHash := sig.sigHash()
|
sigHash := sig.SigHash()
|
||||||
sig.Signature = ed25519.Sign(priv, sigHash[:])
|
sig.Signature = ed25519.Sign(priv, sigHash[:])
|
||||||
|
|
||||||
if sig.sigHash() != sigHash {
|
if sig.SigHash() != sigHash {
|
||||||
t.Errorf("sigHash changed after signing: %x != %x", sig.sigHash(), sigHash)
|
t.Errorf("sigHash changed after signing: %x != %x", sig.SigHash(), sigHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sig.verifySignature(key); err != nil {
|
if err := sig.verifySignature(key); err != nil {
|
||||||
@ -44,7 +44,7 @@ func TestSigSerializeUnserialize(t *testing.T) {
|
|||||||
KeyID: key.ID(),
|
KeyID: key.ID(),
|
||||||
Pubkey: nodeKeyPub,
|
Pubkey: nodeKeyPub,
|
||||||
}
|
}
|
||||||
sigHash := sig.sigHash()
|
sigHash := sig.SigHash()
|
||||||
sig.Signature = ed25519.Sign(priv, sigHash[:])
|
sig.Signature = ed25519.Sign(priv, sigHash[:])
|
||||||
|
|
||||||
var decoded NodeKeySignature
|
var decoded NodeKeySignature
|
||||||
|
@ -82,7 +82,7 @@ func (k NLPrivate) KeyID() tkatype.KeyID {
|
|||||||
return pub[:]
|
return pub[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignAUM implements tka.UpdateSigner.
|
// SignAUM implements tka.Signer.
|
||||||
func (k NLPrivate) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, error) {
|
func (k NLPrivate) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, error) {
|
||||||
return []tkatype.Signature{{
|
return []tkatype.Signature{{
|
||||||
KeyID: k.KeyID(),
|
KeyID: k.KeyID(),
|
||||||
@ -90,6 +90,11 @@ func (k NLPrivate) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, err
|
|||||||
}}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignNKS signs the tka.NodeKeySignature identified by sigHash.
|
||||||
|
func (k NLPrivate) SignNKS(sigHash tkatype.NKSSigHash) ([]byte, error) {
|
||||||
|
return ed25519.Sign(ed25519.PrivateKey(k.k[:]), sigHash[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
// NLPublic is the public portion of a a NLPrivate.
|
// NLPublic is the public portion of a a NLPrivate.
|
||||||
type NLPublic struct {
|
type NLPublic struct {
|
||||||
k [ed25519.PublicKeySize]byte
|
k [ed25519.PublicKeySize]byte
|
||||||
|
@ -22,10 +22,17 @@
|
|||||||
// MarshaledSignature represents a marshaled tka.NodeKeySignature.
|
// MarshaledSignature represents a marshaled tka.NodeKeySignature.
|
||||||
type MarshaledSignature []byte
|
type MarshaledSignature []byte
|
||||||
|
|
||||||
|
// MarshaledAUM represents a marshaled tka.AUM.
|
||||||
|
type MarshaledAUM []byte
|
||||||
|
|
||||||
// AUMSigHash represents the BLAKE2s digest of an Authority Update
|
// AUMSigHash represents the BLAKE2s digest of an Authority Update
|
||||||
// Message (AUM), sans any signatures.
|
// Message (AUM), sans any signatures.
|
||||||
type AUMSigHash [32]byte
|
type AUMSigHash [32]byte
|
||||||
|
|
||||||
|
// NKSSigHash represents the BLAKE2s digest of a Node-Key Signature (NKS),
|
||||||
|
// sans the Signature field if present.
|
||||||
|
type NKSSigHash [32]byte
|
||||||
|
|
||||||
// Signature describes a signature over an AUM, which can be verified
|
// Signature describes a signature over an AUM, which can be verified
|
||||||
// using the key referenced by KeyID.
|
// using the key referenced by KeyID.
|
||||||
type Signature struct {
|
type Signature struct {
|
||||||
|
@ -14,4 +14,9 @@ func TestSigHashSize(t *testing.T) {
|
|||||||
if len(sigHash) != blake2s.Size {
|
if len(sigHash) != blake2s.Size {
|
||||||
t.Errorf("AUMSigHash is wrong size: got %d, want %d", len(sigHash), blake2s.Size)
|
t.Errorf("AUMSigHash is wrong size: got %d, want %d", len(sigHash), blake2s.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nksHash NKSSigHash
|
||||||
|
if len(nksHash) != blake2s.Size {
|
||||||
|
t.Errorf("NKSSigHash is wrong size: got %d, want %d", len(nksHash), blake2s.Size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user