Documentation
¶
Overview ¶
Package attack is Keyway's generative, invariant-based JWT attack harness. It replaces a fixed list of hand-written attack tokens with taxonomy-driven generators: each generator emits one or more concrete tokens tagged with the threat (from internal/threats) it exercises and the verdict a *correct* verifier must return. A reference verifier (oracle, built on go-jose) encodes the security invariants, so the corpus is self-validating — every "must reject" token is provably rejected by a known-correct implementation — and Evaluate can then fire the same corpus at a live target and flag any it wrongly accepts.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CoveredThreatIDs ¶
CoveredThreatIDs returns only the threats the harness can detect end-to-end at a single endpoint (self-contained). This is the honest bridge to the coverage taxonomy: it excludes callback-dependent checks (jku/x5u) that are generated but not yet actionable against a live target.
func NewContext ¶
func NewContext() (GenContext, Policy, error)
NewContext builds a GenContext with a local trusted signer and fresh attacker key, plus the matching Policy for the reference oracle (same trust key). Used for offline corpus validation.
Types ¶
type Finding ¶
Finding is a token whose actual verdict differed from the correct one — i.e. an attack token that a verifier wrongly accepted (or a control it wrongly rejected).
func Evaluate ¶
Evaluate runs every token in the corpus through verify and returns the tokens whose actual verdict disagreed with the correct one. Against the reference oracle this must be empty (the corpus is valid); against a vulnerable target it lists exactly what that target got wrong.
func (Finding) Vulnerability ¶
Vulnerability reports whether this finding is an accepted attack (the dangerous direction), as opposed to a rejected control (a functional problem).
type GenContext ¶
type GenContext struct {
Trusted TrustedSigner
AttackerKey *rsa.PrivateKey
Issuer string
Audience string
RequiredClaims []string
Now time.Time
}
GenContext holds what the generators build tokens around: the trusted issuer (a TrustedSigner — a local key offline, or the real issuer's MintFunc when scanning live), an attacker key for forgeries, and the expected issuer/audience /required claims the correct token must carry.
func NewLiveContext ¶
func NewLiveContext(trusted TrustedSigner, issuer, audience string, required []string, now time.Time) (GenContext, error)
NewLiveContext builds a GenContext for scanning a real target: claim-level attacks are signed by the given trusted signer (the real issuer), while forgeries use a fresh attacker key. issuer/audience/required come from the consumer's expected contract so the control token is one the target accepts.
type HTTPTarget ¶
type HTTPTarget struct {
URL string
Method string // default GET
Header string // default "Authorization"
Prefix string // default "Bearer "
AcceptCodes []int // default {200,201,202,204}
Client *http.Client
}
HTTPTarget describes a live endpoint that verifies a bearer token. AcceptCodes are the HTTP statuses that mean "token honored"; everything else is a reject.
type Oracle ¶
type Oracle struct{ Policy Policy }
Oracle is the reference verifier — a from-spec-correct implementation of the invariants, built on go-jose (a mature, independent JOSE library). It pins the algorithm to RS256 and the key to the configured trust key, ignores any key material carried inside the token (jku/x5u/jwk/x5c/kid), and validates claims. Using a real library as the oracle keeps corpus validation from being circular: "a known-correct verifier rejects every attack token."
type Policy ¶
type Policy struct {
TrustedKey *rsa.PublicKey
Issuer string
Audience string
RequiredClaims []string
Now time.Time
Skew time.Duration
}
Policy is what a correctly-configured verifier enforces: a pinned algorithm and trust key, the expected issuer/audience, and any required claims.
type Token ¶
type Token struct {
ThreatID string // maps to internal/threats (e.g. "SIG-01")
Name string // stable, e.g. "alg_none"
Rationale string // the invariant this probes
JWS string // the compact JWS
Expect Verdict // what a correct verifier must do
// SelfContained is true when firing this single token at one endpoint reveals
// a vulnerable target with no extra infrastructure. Checks that are NOT
// self-contained (e.g. jku/x5u, which need Keyway to host an attacker JWKS)
// are still generated and oracle-validated, but do not count toward detection
// coverage until their callback server lands.
SelfContained bool
}
Token is one generated token: the raw JWS, the threat it exercises, and the verdict a correct verifier MUST return. Controls carry Expect=Accept.
func Corpus ¶
func Corpus(c GenContext) ([]Token, error)
Corpus generates the full attack-token corpus for the given context. The control and the claim-level attacks are signed by the trusted issuer (so a live target's signature check passes and its claim validation is what's exercised); forgeries are constructed directly.
type TrustedSigner ¶
type TrustedSigner interface {
// Sign returns a compact JWS validly signed by the trusted issuer over claims.
Sign(claims map[string]any) (string, error)
// PublicKeyPEM returns the issuer's public signing key (PKIX PEM), used to
// build the RS/HS confusion attack. Empty means that generator is skipped.
PublicKeyPEM() string
}
TrustedSigner is the seam between the harness and the *trusted issuer*. The harness signs its control token and its claim-level attacks (bad claims, valid signature) through this, so in production those tokens are signed by the real issuer's key (via the probe engine's MintFunc) and a live target's signature check passes — leaving the claim validation as the thing actually under test. In tests it is backed by a local key so the corpus can be validated offline.