tka,types/key: implement direct node-key signatures

Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
Tom DNetto
2022-07-21 14:45:43 -07:00
committed by Tom
parent c13fab2a67
commit 8cfd775885
5 changed files with 191 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
// 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 tka
import (
"bytes"
"crypto/ed25519"
"errors"
"fmt"
"github.com/fxamacker/cbor/v2"
"github.com/hdevalence/ed25519consensus"
"golang.org/x/crypto/blake2s"
)
// SigKind describes valid NodeKeySignature types.
type SigKind uint8
const (
SigInvalid SigKind = iota
// SigDirect describes a signature over a specific node key, using
// the keyID specified.
SigDirect
)
func (s SigKind) String() string {
switch s {
case SigInvalid:
return "invalid"
case SigDirect:
return "direct"
default:
return fmt.Sprintf("Sig?<%d>", int(s))
}
}
// NodeKeySignature encapsulates a signature that authorizes a specific
// node key, based on verification from keys in the tailnet key authority.
type NodeKeySignature struct {
// SigKind identifies the variety of signature.
SigKind SigKind `cbor:"1,keyasint"`
// Pubkey identifies the public key which is being certified.
Pubkey []byte `cbor:"2,keyasint"`
// KeyID identifies which key in the tailnet key authority should
// be used to verify this signature. Only set for SigDirect and
// SigCredential signature kinds.
KeyID []byte `cbor:"3,keyasint,omitempty"`
// Signature is the packed (R, S) ed25519 signature over the rest
// of the structure.
Signature []byte `cbor:"4,keyasint,omitempty"`
}
// sigHash returns the cryptographic digest which a signature
// is over.
//
// This is a hash of the serialized structure, sans the signature.
// Without this exclusion, the hash used for the signature
// would be circularly dependent on the signature.
func (s NodeKeySignature) sigHash() [blake2s.Size]byte {
dupe := s
dupe.Signature = nil
return blake2s.Sum256(dupe.Serialize())
}
// Serialize returns the given NKS in a serialized format.
func (s *NodeKeySignature) Serialize() []byte {
out := bytes.NewBuffer(make([]byte, 0, 128)) // 64byte sig + 32byte keyID + 32byte headroom
encoder, err := cbor.CTAP2EncOptions().EncMode()
if err != nil {
// Deterministic validation of encoding options, should
// never fail.
panic(err)
}
if err := encoder.NewEncoder(out).Encode(s); err != nil {
// Writing to a bytes.Buffer should never fail.
panic(err)
}
return out.Bytes()
}
// verifySignature checks that the NodeKeySignature is authentic and certified
// by the given verificationKey.
func (s *NodeKeySignature) verifySignature(verificationKey Key) error {
sigHash := s.sigHash()
switch verificationKey.Kind {
case Key25519:
if ed25519consensus.Verify(ed25519.PublicKey(verificationKey.Public), sigHash[:], s.Signature) {
return nil
}
return errors.New("invalid signature")
default:
return fmt.Errorf("unhandled key type: %v", verificationKey.Kind)
}
}
+34
View File
@@ -0,0 +1,34 @@
// 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 tka
import (
"crypto/ed25519"
"testing"
)
func TestSigDirect(t *testing.T) {
nodeKeyPub := []byte{1, 2, 3, 4}
// Verification key (the key used to sign)
pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2}
sig := NodeKeySignature{
SigKind: SigDirect,
KeyID: key.ID(),
Pubkey: nodeKeyPub,
}
sigHash := sig.sigHash()
sig.Signature = ed25519.Sign(priv, sigHash[:])
if sig.sigHash() != sigHash {
t.Errorf("sigHash changed after signing: %x != %x", sig.sigHash(), sigHash)
}
if err := sig.verifySignature(key); err != nil {
t.Fatalf("verifySignature() failed: %v", err)
}
}
+17
View File
@@ -11,6 +11,8 @@ import (
"fmt"
"os"
"sort"
"github.com/fxamacker/cbor/v2"
)
// Authority is a Tailnet Key Authority. This type is the main coupling
@@ -586,3 +588,18 @@ func (a *Authority) Inform(updates []AUM) error {
a.state = c.state
return nil
}
// VerifySignature returns true if the provided nodeKeySignature is signed
// correctly by a trusted key.
func (a *Authority) VerifySignature(nodeKeySignature []byte) error {
var decoded NodeKeySignature
if err := cbor.Unmarshal(nodeKeySignature, &decoded); err != nil {
return fmt.Errorf("unmarshal: %v", err)
}
key, err := a.state.GetKey(decoded.KeyID)
if err != nil {
return fmt.Errorf("key: %v", err)
}
return decoded.verifySignature(key)
}