mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
tka,types/key: implement NLPrivate glue for tailnet key authority keys
Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
parent
7a74466998
commit
023d4e2216
24
tka/aum.go
24
tka/aum.go
@ -77,20 +77,20 @@ func (k AUMKind) String() string {
|
|||||||
// AUM describes an Authority Update Message.
|
// AUM describes an Authority Update Message.
|
||||||
//
|
//
|
||||||
// The rules for adding new types of AUMs (MessageKind):
|
// The rules for adding new types of AUMs (MessageKind):
|
||||||
// - CBOR key IDs must never be changed.
|
// - CBOR key IDs must never be changed.
|
||||||
// - New AUM types must not change semantics that are manipulated by other
|
// - New AUM types must not change semantics that are manipulated by other
|
||||||
// AUM types.
|
// AUM types.
|
||||||
// - The serialization of existing data cannot change (in other words, if
|
// - The serialization of existing data cannot change (in other words, if
|
||||||
// an existing serialization test in aum_test.go fails, you need to try a
|
// an existing serialization test in aum_test.go fails, you need to try a
|
||||||
// different approach).
|
// different approach).
|
||||||
//
|
//
|
||||||
// The rules for adding new fields are as follows:
|
// The rules for adding new fields are as follows:
|
||||||
// - Must all be optional.
|
// - Must all be optional.
|
||||||
// - An unset value must not result in serialization overhead. This is
|
// - An unset value must not result in serialization overhead. This is
|
||||||
// necessary so the serialization of older AUMs stays the same.
|
// necessary so the serialization of older AUMs stays the same.
|
||||||
// - New processing semantics of the new fields must be compatible with the
|
// - New processing semantics of the new fields must be compatible with the
|
||||||
// behavior of old clients (which will ignore the field).
|
// behavior of old clients (which will ignore the field).
|
||||||
// - No floats!
|
// - No floats!
|
||||||
type AUM struct {
|
type AUM struct {
|
||||||
MessageKind AUMKind `cbor:"1,keyasint"`
|
MessageKind AUMKind `cbor:"1,keyasint"`
|
||||||
PrevAUMHash []byte `cbor:"2,keyasint"`
|
PrevAUMHash []byte `cbor:"2,keyasint"`
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Types implementing Signer can sign update messages.
|
||||||
|
type Signer interface {
|
||||||
|
SignAUM(*AUM) error
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateBuilder implements a builder for changes to the tailnet
|
// UpdateBuilder implements a builder for changes to the tailnet
|
||||||
// key authority.
|
// key authority.
|
||||||
//
|
//
|
||||||
@ -15,7 +20,7 @@
|
|||||||
// must then be applied to all Authority objects using Inform().
|
// must then be applied to all Authority objects using Inform().
|
||||||
type UpdateBuilder struct {
|
type UpdateBuilder struct {
|
||||||
a *Authority
|
a *Authority
|
||||||
signer func(*AUM) error
|
signer Signer
|
||||||
|
|
||||||
state State
|
state State
|
||||||
parent AUMHash
|
parent AUMHash
|
||||||
@ -29,7 +34,7 @@ func (b *UpdateBuilder) mkUpdate(update AUM) error {
|
|||||||
update.PrevAUMHash = prevHash
|
update.PrevAUMHash = prevHash
|
||||||
|
|
||||||
if b.signer != nil {
|
if b.signer != nil {
|
||||||
if err := b.signer(&update); err != nil {
|
if err := b.signer.SignAUM(&update); err != nil {
|
||||||
return fmt.Errorf("signing failed: %v", err)
|
return fmt.Errorf("signing failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +106,7 @@ func (b *UpdateBuilder) Finalize() ([]AUM, error) {
|
|||||||
// Updates are specified by calling methods on the returned UpdatedBuilder.
|
// Updates are specified by calling methods on the returned UpdatedBuilder.
|
||||||
// Call Finalize() when you are done to obtain the specific update messages
|
// Call Finalize() when you are done to obtain the specific update messages
|
||||||
// which actuate the changes.
|
// which actuate the changes.
|
||||||
func (a *Authority) NewUpdater(signer func(*AUM) error) *UpdateBuilder {
|
func (a *Authority) NewUpdater(signer Signer) *UpdateBuilder {
|
||||||
return &UpdateBuilder{
|
return &UpdateBuilder{
|
||||||
a: a,
|
a: a,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
|
@ -5,11 +5,19 @@
|
|||||||
package tka
|
package tka
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type signer25519 ed25519.PrivateKey
|
||||||
|
|
||||||
|
func (s signer25519) SignAUM(update *AUM) error {
|
||||||
|
update.sign25519(ed25519.PrivateKey(s))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthorityBuilderAddKey(t *testing.T) {
|
func TestAuthorityBuilderAddKey(t *testing.T) {
|
||||||
pub, priv := testingKey25519(t, 1)
|
pub, priv := testingKey25519(t, 1)
|
||||||
key := Key{Kind: Key25519, Public: pub, Votes: 2}
|
key := Key{Kind: Key25519, Public: pub, Votes: 2}
|
||||||
@ -17,7 +25,7 @@ func TestAuthorityBuilderAddKey(t *testing.T) {
|
|||||||
a, _, err := Create(&Mem{}, State{
|
a, _, err := Create(&Mem{}, State{
|
||||||
Keys: []Key{key},
|
Keys: []Key{key},
|
||||||
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
||||||
}, priv)
|
}, signer25519(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create() failed: %v", err)
|
t.Fatalf("Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -25,10 +33,7 @@ func TestAuthorityBuilderAddKey(t *testing.T) {
|
|||||||
pub2, _ := testingKey25519(t, 2)
|
pub2, _ := testingKey25519(t, 2)
|
||||||
key2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
|
key2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
|
||||||
|
|
||||||
b := a.NewUpdater(func(update *AUM) error {
|
b := a.NewUpdater(signer25519(priv))
|
||||||
update.sign25519(priv)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := b.AddKey(key2); err != nil {
|
if err := b.AddKey(key2); err != nil {
|
||||||
t.Fatalf("AddKey(%v) failed: %v", key2, err)
|
t.Fatalf("AddKey(%v) failed: %v", key2, err)
|
||||||
}
|
}
|
||||||
@ -56,15 +61,12 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
|
|||||||
a, _, err := Create(&Mem{}, State{
|
a, _, err := Create(&Mem{}, State{
|
||||||
Keys: []Key{key, key2},
|
Keys: []Key{key, key2},
|
||||||
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
||||||
}, priv)
|
}, signer25519(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create() failed: %v", err)
|
t.Fatalf("Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b := a.NewUpdater(func(update *AUM) error {
|
b := a.NewUpdater(signer25519(priv))
|
||||||
update.sign25519(priv)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := b.RemoveKey(key2.ID()); err != nil {
|
if err := b.RemoveKey(key2.ID()); err != nil {
|
||||||
t.Fatalf("RemoveKey(%v) failed: %v", key2, err)
|
t.Fatalf("RemoveKey(%v) failed: %v", key2, err)
|
||||||
}
|
}
|
||||||
@ -90,15 +92,12 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
|
|||||||
a, _, err := Create(&Mem{}, State{
|
a, _, err := Create(&Mem{}, State{
|
||||||
Keys: []Key{key},
|
Keys: []Key{key},
|
||||||
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
||||||
}, priv)
|
}, signer25519(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create() failed: %v", err)
|
t.Fatalf("Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b := a.NewUpdater(func(update *AUM) error {
|
b := a.NewUpdater(signer25519(priv))
|
||||||
update.sign25519(priv)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := b.SetKeyVote(key.ID(), 5); err != nil {
|
if err := b.SetKeyVote(key.ID(), 5); err != nil {
|
||||||
t.Fatalf("SetKeyVote(%v) failed: %v", key.ID(), err)
|
t.Fatalf("SetKeyVote(%v) failed: %v", key.ID(), err)
|
||||||
}
|
}
|
||||||
@ -128,15 +127,12 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
|
|||||||
a, _, err := Create(&Mem{}, State{
|
a, _, err := Create(&Mem{}, State{
|
||||||
Keys: []Key{key},
|
Keys: []Key{key},
|
||||||
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
||||||
}, priv)
|
}, signer25519(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create() failed: %v", err)
|
t.Fatalf("Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b := a.NewUpdater(func(update *AUM) error {
|
b := a.NewUpdater(signer25519(priv))
|
||||||
update.sign25519(priv)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := b.SetKeyMeta(key.ID(), map[string]string{"b": "c"}); err != nil {
|
if err := b.SetKeyMeta(key.ID(), map[string]string{"b": "c"}); err != nil {
|
||||||
t.Fatalf("SetKeyMeta(%v) failed: %v", key, err)
|
t.Fatalf("SetKeyMeta(%v) failed: %v", key, err)
|
||||||
}
|
}
|
||||||
@ -166,7 +162,7 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
|
|||||||
a, _, err := Create(&Mem{}, State{
|
a, _, err := Create(&Mem{}, State{
|
||||||
Keys: []Key{key},
|
Keys: []Key{key},
|
||||||
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
||||||
}, priv)
|
}, signer25519(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create() failed: %v", err)
|
t.Fatalf("Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -174,10 +170,7 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
|
|||||||
pub2, _ := testingKey25519(t, 2)
|
pub2, _ := testingKey25519(t, 2)
|
||||||
key2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
|
key2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
|
||||||
|
|
||||||
b := a.NewUpdater(func(update *AUM) error {
|
b := a.NewUpdater(signer25519(priv))
|
||||||
update.sign25519(priv)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := b.AddKey(key2); err != nil {
|
if err := b.AddKey(key2); err != nil {
|
||||||
t.Fatalf("AddKey(%v) failed: %v", key2, err)
|
t.Fatalf("AddKey(%v) failed: %v", key2, err)
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,15 @@ type testChain struct {
|
|||||||
//
|
//
|
||||||
// Input is expected to be a graph & tweaks, looking like this:
|
// Input is expected to be a graph & tweaks, looking like this:
|
||||||
//
|
//
|
||||||
// G1 -> A -> B
|
// G1 -> A -> B
|
||||||
// | -> C
|
// | -> C
|
||||||
//
|
//
|
||||||
// which defines AUMs G1, A, B, and C; with G1 having no parent, A having
|
// which defines AUMs G1, A, B, and C; with G1 having no parent, A having
|
||||||
// G1 as a parent, and both B & C having A as a parent.
|
// G1 as a parent, and both B & C having A as a parent.
|
||||||
//
|
//
|
||||||
// Tweaks are specified like this:
|
// Tweaks are specified like this:
|
||||||
//
|
//
|
||||||
// <AUM>.<tweak> = <value>
|
// <AUM>.<tweak> = <value>
|
||||||
//
|
//
|
||||||
// for example: G1.hashSeed = 2
|
// for example: G1.hashSeed = 2
|
||||||
//
|
//
|
||||||
|
@ -53,9 +53,8 @@ type Key struct {
|
|||||||
|
|
||||||
// Clone makes an independent copy of Key.
|
// Clone makes an independent copy of Key.
|
||||||
//
|
//
|
||||||
// NOTE: There is a difference between a nil slice and an empty
|
// NOTE: There is a difference between a nil slice and an empty slice for encoding purposes,
|
||||||
// slice for encoding purposes, so an implementation of Clone()
|
// so an implementation of Clone() must take care to preserve this.
|
||||||
// must take care to preserve this.
|
|
||||||
func (k Key) Clone() Key {
|
func (k Key) Clone() Key {
|
||||||
out := k
|
out := k
|
||||||
|
|
||||||
|
10
tka/state.go
10
tka/state.go
@ -53,8 +53,8 @@ func (s State) GetKey(key KeyID) (Key, error) {
|
|||||||
// Clone makes an independent copy of State.
|
// Clone makes an independent copy of State.
|
||||||
//
|
//
|
||||||
// NOTE: There is a difference between a nil slice and an empty
|
// NOTE: There is a difference between a nil slice and an empty
|
||||||
// slice for encoding purposes, so an implementation of Clone()
|
// slice for encoding purposes, so an implementation of Clone()
|
||||||
// must take care to preserve this.
|
// must take care to preserve this.
|
||||||
func (s State) Clone() State {
|
func (s State) Clone() State {
|
||||||
out := State{}
|
out := State{}
|
||||||
|
|
||||||
@ -117,9 +117,9 @@ func (s State) checkDisablement(secret []byte) bool {
|
|||||||
// to the current state.
|
// to the current state.
|
||||||
//
|
//
|
||||||
// Specifically, the rules are:
|
// Specifically, the rules are:
|
||||||
// - The last AUM hash must match (transitively, this implies that this
|
// - The last AUM hash must match (transitively, this implies that this
|
||||||
// update follows the last update message applied to the state machine)
|
// update follows the last update message applied to the state machine)
|
||||||
// - Or, the state machine knows no parent (its brand new).
|
// - Or, the state machine knows no parent (its brand new).
|
||||||
func (s State) parentMatches(update AUM) bool {
|
func (s State) parentMatches(update AUM) bool {
|
||||||
if s.LastAUMHash == nil {
|
if s.LastAUMHash == nil {
|
||||||
return true
|
return true
|
||||||
|
28
tka/tka.go
28
tka/tka.go
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -99,9 +98,10 @@ func computeChainCandidates(storage Chonk, lastKnownOldest *AUMHash, maxIter int
|
|||||||
// AUM in the chain, possibly applying fork resolution logic.
|
// AUM in the chain, possibly applying fork resolution logic.
|
||||||
//
|
//
|
||||||
// In other words: given an AUM with 3 children like this:
|
// In other words: given an AUM with 3 children like this:
|
||||||
// / - 1
|
//
|
||||||
// P - 2
|
// / - 1
|
||||||
// \ - 3
|
// P - 2
|
||||||
|
// \ - 3
|
||||||
//
|
//
|
||||||
// pickNextAUM will determine and return the correct branch.
|
// pickNextAUM will determine and return the correct branch.
|
||||||
//
|
//
|
||||||
@ -354,13 +354,13 @@ func computeActiveAncestor(storage Chonk, chains []chain) (AUMHash, error) {
|
|||||||
// the ancestor.
|
// the ancestor.
|
||||||
//
|
//
|
||||||
// The algorithm is as follows:
|
// The algorithm is as follows:
|
||||||
// 1. Determine all possible 'head' (like in git) states.
|
// 1. Determine all possible 'head' (like in git) states.
|
||||||
// 2. Filter these possible chains based on whether the ancestor was
|
// 2. Filter these possible chains based on whether the ancestor was
|
||||||
// formerly (in a previous run) part of the chain.
|
// formerly (in a previous run) part of the chain.
|
||||||
// 3. Compute the state of the state machine at this ancestor. This is
|
// 3. Compute the state of the state machine at this ancestor. This is
|
||||||
// needed for fast-forward, as each update operates on the state of
|
// needed for fast-forward, as each update operates on the state of
|
||||||
// the update preceeding it.
|
// the update preceeding it.
|
||||||
// 4. Iteratively apply updates till we reach head ('fast forward').
|
// 4. Iteratively apply updates till we reach head ('fast forward').
|
||||||
func computeActiveChain(storage Chonk, lastKnownOldest *AUMHash, maxIter int) (chain, error) {
|
func computeActiveChain(storage Chonk, lastKnownOldest *AUMHash, maxIter int) (chain, error) {
|
||||||
chains, err := computeChainCandidates(storage, lastKnownOldest, maxIter)
|
chains, err := computeChainCandidates(storage, lastKnownOldest, maxIter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -473,7 +473,7 @@ func Open(storage Chonk) (*Authority, error) {
|
|||||||
//
|
//
|
||||||
// Do not use this to initialize a TKA that already exists, use Open()
|
// Do not use this to initialize a TKA that already exists, use Open()
|
||||||
// or Bootstrap() instead.
|
// or Bootstrap() instead.
|
||||||
func Create(storage Chonk, state State, signer ed25519.PrivateKey) (*Authority, AUM, error) {
|
func Create(storage Chonk, state State, signer Signer) (*Authority, AUM, error) {
|
||||||
// Generate & sign a checkpoint, our genesis update.
|
// Generate & sign a checkpoint, our genesis update.
|
||||||
genesis := AUM{
|
genesis := AUM{
|
||||||
MessageKind: AUMCheckpoint,
|
MessageKind: AUMCheckpoint,
|
||||||
@ -483,7 +483,9 @@ func Create(storage Chonk, state State, signer ed25519.PrivateKey) (*Authority,
|
|||||||
// This serves as an easy way to validate the given state.
|
// This serves as an easy way to validate the given state.
|
||||||
return nil, AUM{}, fmt.Errorf("invalid state: %v", err)
|
return nil, AUM{}, fmt.Errorf("invalid state: %v", err)
|
||||||
}
|
}
|
||||||
genesis.sign25519(signer)
|
if err := signer.SignAUM(&genesis); err != nil {
|
||||||
|
return nil, AUM{}, fmt.Errorf("signing failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
a, err := Bootstrap(storage, genesis)
|
a, err := Bootstrap(storage, genesis)
|
||||||
return a, genesis, err
|
return a, genesis, err
|
||||||
|
@ -301,7 +301,7 @@ func TestCreateBootstrapAuthority(t *testing.T) {
|
|||||||
a1, genesisAUM, err := Create(&Mem{}, State{
|
a1, genesisAUM, err := Create(&Mem{}, State{
|
||||||
Keys: []Key{key},
|
Keys: []Key{key},
|
||||||
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
DisablementSecrets: [][]byte{disablementKDF([]byte{1, 2, 3})},
|
||||||
}, priv)
|
}, signer25519(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create() failed: %v", err)
|
t.Fatalf("Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
74
types/key/nl.go
Normal file
74
types/key/nl.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
|
||||||
|
"go4.org/mem"
|
||||||
|
"tailscale.com/tka"
|
||||||
|
"tailscale.com/types/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// nlPrivateHexPrefix is the prefix used to identify a
|
||||||
|
// hex-encoded network-lock key.
|
||||||
|
nlPrivateHexPrefix = "nlpriv:"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NLPrivate is a node-managed network-lock key, used for signing
|
||||||
|
// node-key signatures and authority update messages.
|
||||||
|
type NLPrivate struct {
|
||||||
|
_ structs.Incomparable // because == isn't constant-time
|
||||||
|
k [ed25519.PrivateKeySize]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNLPrivate creates and returns a new network-lock key.
|
||||||
|
func NewNLPrivate() NLPrivate {
|
||||||
|
// ed25519.GenerateKey 'clamps' the key, not that it
|
||||||
|
// matters given we don't do Diffie-Hellman.
|
||||||
|
_, priv, err := ed25519.GenerateKey(nil) // nil == crypto/rand
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out NLPrivate
|
||||||
|
copy(out.k[:], priv)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (k *NLPrivate) UnmarshalText(b []byte) error {
|
||||||
|
return parseHex(k.k[:], mem.B(b), mem.S(nlPrivateHexPrefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
|
func (k NLPrivate) MarshalText() ([]byte, error) {
|
||||||
|
return toHex(k.k[:], nlPrivateHexPrefix), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public returns the public component of this key.
|
||||||
|
func (k NLPrivate) Public() ed25519.PublicKey {
|
||||||
|
return ed25519.PrivateKey(k.k[:]).Public().(ed25519.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyID returns an identifier for this key.
|
||||||
|
func (k NLPrivate) KeyID() tka.KeyID {
|
||||||
|
return tka.Key{
|
||||||
|
Kind: tka.Key25519,
|
||||||
|
Public: k.Public(),
|
||||||
|
}.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignAUM implements tka.UpdateSigner.
|
||||||
|
func (k NLPrivate) SignAUM(a *tka.AUM) error {
|
||||||
|
sigHash := a.SigHash()
|
||||||
|
|
||||||
|
a.Signatures = append(a.Signatures, tka.Signature{
|
||||||
|
KeyID: k.KeyID(),
|
||||||
|
Signature: ed25519.Sign(k.k[:], sigHash[:]),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
47
types/key/nl_test.go
Normal file
47
types/key/nl_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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 key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tailscale.com/tka"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNLPrivate(t *testing.T) {
|
||||||
|
p := NewNLPrivate()
|
||||||
|
|
||||||
|
encoded, err := p.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var decoded NLPrivate
|
||||||
|
if err := decoded.UnmarshalText(encoded); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(decoded.k[:], p.k[:]) {
|
||||||
|
t.Error("decoded and generated NLPrivate bytes differ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that NLPrivate implements tka.Signer by making a new
|
||||||
|
// authority.
|
||||||
|
k := tka.Key{Kind: tka.Key25519, Public: p.Public(), Votes: 1}
|
||||||
|
_, aum, err := tka.Create(&tka.Mem{}, tka.State{
|
||||||
|
Keys: []tka.Key{k},
|
||||||
|
DisablementSecrets: [][]byte{bytes.Repeat([]byte{1}, 32)},
|
||||||
|
}, p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("tka.Create() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the generated genesis AUM was signed.
|
||||||
|
if got, want := len(aum.Signatures), 1; got != want {
|
||||||
|
t.Fatalf("len(signatures) = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
if err := aum.Signatures[0].Verify(aum.SigHash(), k); err != nil {
|
||||||
|
t.Errorf("signature did not verify: %v", err)
|
||||||
|
}
|
||||||
|
}
|
@ -42,9 +42,9 @@ func rand(b []byte) {
|
|||||||
// existing uses and whether you should clamp private keys at
|
// existing uses and whether you should clamp private keys at
|
||||||
// creation.
|
// creation.
|
||||||
//
|
//
|
||||||
// - NaCl box: yes, clamp at creation.
|
// - NaCl box: yes, clamp at creation.
|
||||||
// - WireGuard (userspace uapi or kernel): no, do not clamp.
|
// - WireGuard (userspace uapi or kernel): no, do not clamp.
|
||||||
// - Noise protocols: no, do not clamp.
|
// - Noise protocols: no, do not clamp.
|
||||||
func clamp25519Private(b []byte) {
|
func clamp25519Private(b []byte) {
|
||||||
b[0] &= 248
|
b[0] &= 248
|
||||||
b[31] = (b[31] & 127) | 64
|
b[31] = (b[31] & 127) | 64
|
||||||
|
Loading…
Reference in New Issue
Block a user