mirror of
https://github.com/luxfi/pq.git
synced 2026-10-30 15:22:02 +00:00
Decomplects the strict-PQ admission gate from per-package copies.
Before: warp.RequireMLDSACertSetForProfile, zap.RequireAttestationForProfile,
dex.VerifyOrderForProfile, fhe.* each reimplemented the same
three-step shape:
1. mode doesn't require PQ → accept
2. mode requires PQ + no evidence → refuse
3. evidence present → call consumer-specific verifier
Five copies = five places a future Mode arm can silently bypass.
After:
PQEvidencer interface { HasPQEvidence() bool }
Verify = func() error
ValidateMode(mode, evidence, verify) error — one gate, one place
Each consumer makes its input implement HasPQEvidence(), passes a
verify closure capturing whatever inputs the consumer-specific
verification needs, and calls pq.ValidateMode — one line per
gate-site.
Concerns separated:
verification logic → caller's verify closure
policy enforcement → pq.ValidateMode
evidence shape → caller's HasPQEvidence implementation
error vocabulary → pq.ErrClassicalAuthForbidden (one sentinel)
Tests pin all 8 cases: classical-ignores-everything,
strict-PQ-refuses-missing, strict-PQ-evidence-present-calls-verify,
verifier-error-propagates, hybrid-accepts-missing, hybrid-validates-present,
nil-verify, RequireMode-strict-PQ-only shorthand. 8/8 pass.
140 lines
5.3 KiB
Go
140 lines
5.3 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package pq
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// stubEvidence implements PQEvidencer with a configurable bool.
|
|
type stubEvidence struct{ has bool }
|
|
|
|
func (s *stubEvidence) HasPQEvidence() bool { return s.has }
|
|
|
|
// TestValidateMode_Classical_ignoresEverything pins that classical
|
|
// mode never refuses, never calls the verifier. Both with and
|
|
// without evidence; the gate is a no-op.
|
|
func TestValidateMode_Classical_ignoresEverything(t *testing.T) {
|
|
called := false
|
|
verify := func() error { called = true; return errors.New("would have refused") }
|
|
for _, ev := range []PQEvidencer{nil, &stubEvidence{has: false}, &stubEvidence{has: true}} {
|
|
if err := ValidateMode(ModeClassical, ev, verify); err != nil {
|
|
t.Errorf("ModeClassical refused: %v (ev=%v)", err, ev)
|
|
}
|
|
}
|
|
if called {
|
|
t.Error("ModeClassical called the verifier (should never)")
|
|
}
|
|
}
|
|
|
|
// TestValidateMode_StrictPQ_refusesMissingEvidence pins the
|
|
// canonical refusal: strict-PQ + nil evidence + non-nil verifier
|
|
// must return ErrClassicalAuthForbidden WITHOUT calling verify
|
|
// (the evidence-absent fast path).
|
|
func TestValidateMode_StrictPQ_refusesMissingEvidence(t *testing.T) {
|
|
verifyCalled := false
|
|
verify := func() error { verifyCalled = true; return nil }
|
|
for _, ev := range []PQEvidencer{nil, &stubEvidence{has: false}} {
|
|
err := ValidateMode(ModeStrictPQ, ev, verify)
|
|
if !errors.Is(err, ErrClassicalAuthForbidden) {
|
|
t.Errorf("StrictPQ accepted missing evidence: err=%v (ev=%v)", err, ev)
|
|
}
|
|
}
|
|
if verifyCalled {
|
|
t.Error("Verify called even though evidence was absent")
|
|
}
|
|
}
|
|
|
|
// TestValidateMode_StrictPQ_evidencePresent_callsVerify pins the
|
|
// happy path: strict-PQ + evidence-bearing input → call verifier.
|
|
func TestValidateMode_StrictPQ_evidencePresent_callsVerify(t *testing.T) {
|
|
called := false
|
|
verify := func() error { called = true; return nil }
|
|
if err := ValidateMode(ModeStrictPQ, &stubEvidence{has: true}, verify); err != nil {
|
|
t.Fatalf("StrictPQ refused valid evidence: %v", err)
|
|
}
|
|
if !called {
|
|
t.Error("Verify not called even though evidence was present")
|
|
}
|
|
}
|
|
|
|
// TestValidateMode_StrictPQ_verifyErrorPropagates pins that
|
|
// verifier errors flow through verbatim (the gate does NOT
|
|
// rewrap them) so consumers can errors.Is against their own
|
|
// granular failure types.
|
|
func TestValidateMode_StrictPQ_verifyErrorPropagates(t *testing.T) {
|
|
want := errors.New("validator-set: pubkey not registered")
|
|
verify := func() error { return want }
|
|
err := ValidateMode(ModeStrictPQ, &stubEvidence{has: true}, verify)
|
|
if !errors.Is(err, want) {
|
|
t.Errorf("verify error not propagated: %v", err)
|
|
}
|
|
// ErrClassicalAuthForbidden should NOT fire here — the
|
|
// refusal cause is the verifier, not the gate.
|
|
if errors.Is(err, ErrClassicalAuthForbidden) {
|
|
t.Error("verify-error path incorrectly mapped to ErrClassicalAuthForbidden")
|
|
}
|
|
}
|
|
|
|
// TestValidateMode_Hybrid_acceptsMissingEvidence pins the hybrid
|
|
// fallback: missing evidence is NOT refused. The caller is
|
|
// expected to log a stale-PQ warning and validate the classical
|
|
// lane separately.
|
|
func TestValidateMode_Hybrid_acceptsMissingEvidence(t *testing.T) {
|
|
called := false
|
|
verify := func() error { called = true; return nil }
|
|
for _, ev := range []PQEvidencer{nil, &stubEvidence{has: false}} {
|
|
if err := ValidateMode(ModeHybrid, ev, verify); err != nil {
|
|
t.Errorf("Hybrid refused missing evidence: %v (ev=%v)", err, ev)
|
|
}
|
|
}
|
|
if called {
|
|
t.Error("Hybrid called verify when evidence was absent")
|
|
}
|
|
}
|
|
|
|
// TestValidateMode_Hybrid_evidencePresent_validates pins that
|
|
// hybrid + present evidence still calls the verifier — the
|
|
// fallback is to classical only when evidence is ABSENT.
|
|
func TestValidateMode_Hybrid_evidencePresent_validates(t *testing.T) {
|
|
called := false
|
|
verify := func() error { called = true; return nil }
|
|
if err := ValidateMode(ModeHybrid, &stubEvidence{has: true}, verify); err != nil {
|
|
t.Fatalf("Hybrid refused valid evidence: %v", err)
|
|
}
|
|
if !called {
|
|
t.Error("Hybrid did not call verify on present evidence")
|
|
}
|
|
}
|
|
|
|
// TestValidateMode_nilVerify pins that a nil verify is treated
|
|
// as "gate-only, skip verification" — useful for fast-path
|
|
// rejection at the receive boundary BEFORE expensive crypto.
|
|
func TestValidateMode_nilVerify(t *testing.T) {
|
|
// StrictPQ + evidence present + nil verify → accept (gate
|
|
// invariant satisfied, verification deferred).
|
|
if err := ValidateMode(ModeStrictPQ, &stubEvidence{has: true}, nil); err != nil {
|
|
t.Errorf("nil verify with evidence present refused: %v", err)
|
|
}
|
|
// StrictPQ + evidence absent + nil verify → REFUSE (gate
|
|
// invariant violated, verifier irrelevant).
|
|
if err := ValidateMode(ModeStrictPQ, nil, nil); !errors.Is(err, ErrClassicalAuthForbidden) {
|
|
t.Errorf("nil verify with evidence absent did not refuse: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestRequireMode_strictPQOnly pins the dedicated entry point:
|
|
// callers that mean "strict-PQ only, no hybrid fallback" use
|
|
// RequireMode and the gate enforces accordingly without taking
|
|
// a mode parameter.
|
|
func TestRequireMode_strictPQOnly(t *testing.T) {
|
|
if err := RequireMode(nil, nil); !errors.Is(err, ErrClassicalAuthForbidden) {
|
|
t.Errorf("RequireMode accepted nil evidence: %v", err)
|
|
}
|
|
if err := RequireMode(&stubEvidence{has: true}, nil); err != nil {
|
|
t.Errorf("RequireMode refused present evidence: %v", err)
|
|
}
|
|
}
|