mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-21 18:42:36 +00:00
tka: truncate long rotation signature chains
When a rotation signature chain reaches a certain size, remove the oldest rotation signature from the chain before wrapping it in a new rotation signature. Since all previous rotation signatures are signed by the same wrapping pubkey (node's own tailnet lock key), the node can re-construct the chain, re-signing previous rotation signatures. This will satisfy the existing certificate validation logic. Updates #13185 Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:

committed by
Anton Tolchanov

parent
bcc47d91ca
commit
fd6686d81a
52
tka/sig.go
52
tka/sig.go
@@ -372,10 +372,15 @@ func ResignNKS(priv key.NLPrivate, nodeKey key.NodePublic, oldNKS tkatype.Marsha
|
||||
return oldNKS, nil
|
||||
}
|
||||
|
||||
nested, err := maybeTrimRotationSignatureChain(oldSig, priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trimming rotation signature chain: %w", err)
|
||||
}
|
||||
|
||||
newSig := NodeKeySignature{
|
||||
SigKind: SigRotation,
|
||||
Pubkey: nk,
|
||||
Nested: &oldSig,
|
||||
Nested: &nested,
|
||||
}
|
||||
if newSig.Signature, err = priv.SignNKS(newSig.SigHash()); err != nil {
|
||||
return nil, fmt.Errorf("signing NKS: %w", err)
|
||||
@@ -384,6 +389,51 @@ func ResignNKS(priv key.NLPrivate, nodeKey key.NodePublic, oldNKS tkatype.Marsha
|
||||
return newSig.Serialize(), nil
|
||||
}
|
||||
|
||||
// maybeTrimRotationSignatureChain truncates rotation signature chain to ensure
|
||||
// it contains no more than 15 node keys.
|
||||
func maybeTrimRotationSignatureChain(sig NodeKeySignature, priv key.NLPrivate) (NodeKeySignature, error) {
|
||||
if sig.SigKind != SigRotation {
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// Collect all the previous node keys, ordered from newest to oldest.
|
||||
prevPubkeys := [][]byte{sig.Pubkey}
|
||||
nested := sig.Nested
|
||||
for nested != nil {
|
||||
if len(nested.Pubkey) > 0 {
|
||||
prevPubkeys = append(prevPubkeys, nested.Pubkey)
|
||||
}
|
||||
if nested.SigKind != SigRotation {
|
||||
break
|
||||
}
|
||||
nested = nested.Nested
|
||||
}
|
||||
|
||||
// Existing rotation signature with 15 keys is the maximum we can wrap in a
|
||||
// new signature without hitting the CBOR nesting limit of 16 (see
|
||||
// MaxNestedLevels in tka.go).
|
||||
const maxPrevKeys = 15
|
||||
if len(prevPubkeys) <= maxPrevKeys {
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// Create a new rotation signature chain, starting with the original
|
||||
// direct signature.
|
||||
var err error
|
||||
result := nested // original direct signature
|
||||
for i := maxPrevKeys - 2; i >= 0; i-- {
|
||||
result = &NodeKeySignature{
|
||||
SigKind: SigRotation,
|
||||
Pubkey: prevPubkeys[i],
|
||||
Nested: result,
|
||||
}
|
||||
if result.Signature, err = priv.SignNKS(result.SigHash()); err != nil {
|
||||
return sig, fmt.Errorf("signing NKS: %w", err)
|
||||
}
|
||||
}
|
||||
return *result, nil
|
||||
}
|
||||
|
||||
// SignByCredential signs a node public key by a private key which has its
|
||||
// signing authority delegated by a SigCredential signature. This is used by
|
||||
// wrapped auth keys.
|
||||
|
Reference in New Issue
Block a user