R-1280: a signed job's validity window is at most 24 h — the verifier, the root wrapper and felhom-opsign. Red-proved. Commit only, not released.
gates / gates (push) Successful in 1m42s
gates / gates (push) Successful in 1m42s
This commit is contained in:
1 parent
feaaa0e8ff
commit
d912307bb7
7 files changed
+58
No files matched your search
@@ -22,6 +22,8 @@ var (
|
||||
ErrExpired = errors.New("authz: op expired")
|
||||
// ErrNotYetValid: now < issued_at (minus clock-skew tolerance).
|
||||
ErrNotYetValid = errors.New("authz: op not yet valid")
|
||||
// ErrWindowTooLong: expires_at - issued_at > MaxWindow (R-1280).
|
||||
ErrWindowTooLong = errors.New("authz: op validity window too long")
|
||||
// ErrReplay: the nonce was already recorded in the window.
|
||||
ErrReplay = errors.New("authz: replay (nonce already seen)")
|
||||
)
|
||||
@@ -20,6 +20,11 @@ const Namespace = "felhom-op-v1"
|
||||
// the validity window stays an honest upper bound.
|
||||
const DefaultClockSkew = 2 * time.Minute
|
||||
|
||||
// MaxWindow is the longest validity window a signed op may carry (R-1280, D44 #22, operator 2026-10-25): a job signed
|
||||
// with a long window by mistake would stay usable that long, and the nonce store would keep its record as long. The
|
||||
// root wrapper (configs/felhom-os-apply, MAX_WINDOW_S) and felhom-opsign (-ttl) hold the same 24 h.
|
||||
const MaxWindow = 24 * time.Hour
|
||||
|
||||
// KeyRole tags a pinned operator key (doc 04 §3 two-key model).
|
||||
type KeyRole string
|
||||
|
||||
@@ -150,6 +155,9 @@ func (v *Verifier) Verify(blob, sigArmored []byte) (*VerifiedOp, error) {
|
||||
if now.Before(op.IssuedAt.Add(-v.ClockSkew)) {
|
||||
return nil, fmt.Errorf("%w: issued_at=%s now=%s", ErrNotYetValid, op.IssuedAt, now)
|
||||
}
|
||||
if w := op.ExpiresAt.Sub(op.IssuedAt); w > MaxWindow {
|
||||
return nil, fmt.Errorf("%w: issued_at=%s expires_at=%s (%s, the most is %s)", ErrWindowTooLong, op.IssuedAt, op.ExpiresAt, w, MaxWindow)
|
||||
}
|
||||
if now.After(op.ExpiresAt) {
|
||||
return nil, fmt.Errorf("%w: expires_at=%s now=%s", ErrExpired, op.ExpiresAt, now)
|
||||
}
|
||||
|
||||
@@ -117,6 +117,26 @@ func TestVerify_RejectsPerStage(t *testing.T) {
|
||||
rejects(t, err, ErrTarget)
|
||||
})
|
||||
|
||||
// R-1280: a window longer than 24 h is refused even while it is current; exactly 24 h is accepted.
|
||||
// RED-PROOF: drop the MaxWindow check → the 24 h + 1 s case verifies.
|
||||
t.Run("window longer than 24 h", func(t *testing.T) {
|
||||
blob := canonicalBlob("guest_destroy", "demo-felhom", "9001", "op", "n-win-1", "{}",
|
||||
refNow.Add(-time.Minute), refNow.Add(-time.Minute).Add(MaxWindow+time.Second))
|
||||
sig := mintArmor(t, pub.Marshal(), Namespace, "sha512", blob, sign)
|
||||
v := atRefNow(New(signerSet(pub, "op"), NewMemoryNonceStore(), "demo-felhom"))
|
||||
_, err := v.Verify(blob, sig)
|
||||
rejects(t, err, ErrWindowTooLong)
|
||||
})
|
||||
t.Run("window of exactly 24 h is accepted", func(t *testing.T) {
|
||||
blob := canonicalBlob("guest_destroy", "demo-felhom", "9001", "op", "n-win-2", "{}",
|
||||
refNow.Add(-time.Minute), refNow.Add(-time.Minute).Add(MaxWindow))
|
||||
sig := mintArmor(t, pub.Marshal(), Namespace, "sha512", blob, sign)
|
||||
v := atRefNow(New(signerSet(pub, "op"), NewMemoryNonceStore(), "demo-felhom"))
|
||||
if _, err := v.Verify(blob, sig); err != nil {
|
||||
t.Fatalf("a 24 h window must verify: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("expired", func(t *testing.T) {
|
||||
blob := canonicalBlob("guest_destroy", "demo-felhom", "9001", "op", "n-exp-1", "{}",
|
||||
refNow.Add(-2*time.Hour), refNow.Add(-time.Hour))
|
||||
|
||||
Reference in new issue
Block a user