ipn/ipnlocal: discard node keys that have been rotated out
A non-signing node can be allowed to re-sign its new node keys following key renewal/rotation (e.g. via `tailscale up --force-reauth`). To be able to do this, node's TLK is written into WrappingPubkey field of the initial SigDirect signature, signed by a signing node. The intended use of this field implies that, for each WrappingPubkey, we typically expect to have at most one active node with a signature tracing back to that key. Multiple valid signatures referring to the same WrappingPubkey can occur if a client's state has been cloned, but it's something we explicitly discourage and don't support: https://tailscale.com/s/clone This change propagates rotation details (wrapping public key, a list of previous node keys that have been rotated out) to netmap processing, and adds tracking of obsolete node keys that, when found, will get filtered out. Updates tailscale/corp#19764 Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:
committed by
Anton Tolchanov
parent
42cfbf427c
commit
01847e0123
+75
@@ -304,3 +304,78 @@ func (s *NodeKeySignature) verifySignature(nodeKey key.NodePublic, verificationK
|
||||
return fmt.Errorf("unhandled signature type: %v", s.SigKind)
|
||||
}
|
||||
}
|
||||
|
||||
// RotationDetails holds additional information about a nodeKeySignature
|
||||
// of kind SigRotation.
|
||||
type RotationDetails struct {
|
||||
// PrevNodeKeys is a list of node keys which have been rotated out.
|
||||
PrevNodeKeys []key.NodePublic
|
||||
|
||||
// WrappingPubkey is the public key which has been authorized to sign
|
||||
// this rotating signature.
|
||||
WrappingPubkey []byte
|
||||
}
|
||||
|
||||
// rotationDetails returns the RotationDetails for a SigRotation signature.
|
||||
func (s *NodeKeySignature) rotationDetails() (*RotationDetails, error) {
|
||||
if s.SigKind != SigRotation {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sri := &RotationDetails{}
|
||||
nested := s.Nested
|
||||
for nested != nil {
|
||||
if len(nested.Pubkey) > 0 {
|
||||
var nestedPub key.NodePublic
|
||||
if err := nestedPub.UnmarshalBinary(nested.Pubkey); err != nil {
|
||||
return nil, fmt.Errorf("nested pubkey: %v", err)
|
||||
}
|
||||
sri.PrevNodeKeys = append(sri.PrevNodeKeys, nestedPub)
|
||||
}
|
||||
if nested.SigKind != SigRotation {
|
||||
break
|
||||
}
|
||||
nested = nested.Nested
|
||||
}
|
||||
sri.WrappingPubkey = nested.WrappingPubkey
|
||||
return sri, nil
|
||||
}
|
||||
|
||||
// ResignNKS re-signs a node-key signature for a new node-key.
|
||||
//
|
||||
// This only matters on network-locked tailnets, because node-key signatures are
|
||||
// how other nodes know that a node-key is authentic. When the node-key is
|
||||
// rotated then the existing signature becomes invalid, so this function is
|
||||
// responsible for generating a new wrapping signature to certify the new node-key.
|
||||
//
|
||||
// The signature itself is a SigRotation signature, which embeds the old signature
|
||||
// and certifies the new node-key as a replacement for the old by signing the new
|
||||
// signature with RotationPubkey (which is the node's own network-lock key).
|
||||
func ResignNKS(priv key.NLPrivate, nodeKey key.NodePublic, oldNKS tkatype.MarshaledSignature) (tkatype.MarshaledSignature, error) {
|
||||
var oldSig NodeKeySignature
|
||||
if err := oldSig.Unserialize(oldNKS); err != nil {
|
||||
return nil, fmt.Errorf("decoding NKS: %w", err)
|
||||
}
|
||||
|
||||
nk, err := nodeKey.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshalling node-key: %w", err)
|
||||
}
|
||||
|
||||
if bytes.Equal(nk, oldSig.Pubkey) {
|
||||
// The old signature is valid for the node-key we are using, so just
|
||||
// use it verbatim.
|
||||
return oldNKS, nil
|
||||
}
|
||||
|
||||
newSig := NodeKeySignature{
|
||||
SigKind: SigRotation,
|
||||
Pubkey: nk,
|
||||
Nested: &oldSig,
|
||||
}
|
||||
if newSig.Signature, err = priv.SignNKS(newSig.SigHash()); err != nil {
|
||||
return nil, fmt.Errorf("signing NKS: %w", err)
|
||||
}
|
||||
|
||||
return newSig.Serialize(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user