// 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" "tailscale.com/types/tkatype" ) // 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. // // We would implement encoding.BinaryMarshaler, except that would // unfortunately get called by the cbor marshaller resulting in infinite // recursion. func (s *NodeKeySignature) Serialize() tkatype.MarshaledSignature { 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() } // Unserialize decodes bytes representing a marshaled NKS. // // We would implement encoding.BinaryUnmarshaler, except that would // unfortunately get called by the cbor unmarshaller resulting in infinite // recursion. func (s *NodeKeySignature) Unserialize(data []byte) error { dec, _ := cborDecOpts.DecMode() return dec.Unmarshal(data, s) } // 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) } }