diff --git a/shared/management/certposture/challenge_test.go b/shared/management/certposture/challenge_test.go index 91cb9d8b2..a526799c9 100644 --- a/shared/management/certposture/challenge_test.go +++ b/shared/management/certposture/challenge_test.go @@ -51,3 +51,55 @@ func TestChallenger_HonoursAShortenedWindow(t *testing.T) { assert.ErrorIs(t, c.verifyNonce(nonce, peerKey, issued.Add(3*short)), ErrNonceExpired, "a shortened window must actually expire the nonce sooner") } + +// heldNonceAlwaysAccepted walks ten windows and, at every step, checks the nonce the peer +// would be holding if it were re-stamped every period starting at offset. It returns how +// many of those checks would have rejected the peer. +func heldNonceAlwaysAccepted(c *Challenger, peerKey []byte, period, offset time.Duration) int { + start := time.Unix(0, 0).UTC().Add(offset) + var rejected int + for elapsed := time.Duration(0); elapsed < 10*Window; elapsed += 7 * time.Minute { + lastRefresh := start.Add(elapsed / period * period) + if c.verifyNonce(c.Nonce(peerKey, lastRefresh), peerKey, start.Add(elapsed)) != nil { + rejected++ + } + } + return rejected +} + +// TestNonce_StaysValidWhenRestampedWithinAWindow is the reason management keeps no per-peer +// nonce state. A nonce carries the window it was minted in, not the instant, and is accepted +// for that window and the next. A peer re-stamped at least once per window therefore can +// never be holding one that has fallen outside the accepted pair, whoever it is and whenever +// it was last served. There is nothing to track and nothing to search for: the guarantee +// comes from the cadence alone. +// +// The offsets matter: each account is given a phase of its own so a restart does not fan out +// to every account at once, so the property has to hold off the window boundary too. +func TestNonce_StaysValidWhenRestampedWithinAWindow(t *testing.T) { + c := &Challenger{secret: []byte("secret"), window: Window} + peerKey := []byte("peer-key") + + for _, period := range []time.Duration{Window / 3, Window / 2, Window} { + for _, offset := range []time.Duration{0, Window / 7, Window / 2, Window - time.Minute} { + t.Run(period.String()+"+"+offset.String(), func(t *testing.T) { + assert.Zero(t, heldNonceAlwaysAccepted(c, peerKey, period, offset), + "a peer re-stamped every %v is never left holding an expired nonce", period) + }) + } + } +} + +func TestNonce_ExpiresWhenRestampedTooSlowly(t *testing.T) { + // The counterpart, which is what gives the test above its teeth. Note the aligned case + // does not fail: a cadence of exactly two windows that lands on the boundaries is + // covered by the grace window. Off the boundary it is not, and real refreshes are off + // the boundary by design. + c := &Challenger{secret: []byte("secret"), window: Window} + peerKey := []byte("peer-key") + + assert.Zero(t, heldNonceAlwaysAccepted(c, peerKey, 2*Window, 0), + "two windows exactly on the boundary happens to be covered by the grace window") + assert.NotZero(t, heldNonceAlwaysAccepted(c, peerKey, 2*Window, Window/2), + "the same cadence off the boundary must leave the peer rejected for a stretch") +}