aicverifier

package module
v0.3.0-rc2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

README

aic-verifier

Identity says who is calling. AIC says what this agent is allowed to do — and proves it offline.

License Go Version Go Reference Status IETF IETF

English · 中文


Why

An API key or a scope list says what is generally allowed. It does not say which agent is acting, on whose authority, within what exact bounds, or what was actually recorded. When the caller is an autonomous agent that can be prompt-injected, replayed, or delegated to, "the platform checks the token" is a promise, not a proof.

aic-verifier turns that promise into an enforcement point: a small Go library you wrap around any HTTP service — or run as a reverse proxy — that, per request, verifies an AIC (Agent Identity Certificate) and decides the exact operation against the capabilities the certificate carries. The verdict is three-valued (allow / allow_unresolved / deny), refusals happen before your handler ever runs, and every decision can be emitted as a recomputable, offline-verifiable evidence record bound to the principal that authorized it.

Enforcement point, not a gateway. aic-verifier is the embeddable admission core of the varwof gateway. It decides and records; routing, proxying beyond the bundled reverse proxy, and execution belong to the caller. aic-exec is the command-execution boundary built on top.

It evaluates the CLC-1.8 revision of the capability language and depends on register v0.6.0 (the CLC reference implementation) and types v0.6.0 (AIC / AIC-JWT structures).


What it does

Area Capability
Credentials mTLS client certificate carrying an AIC X.509 extension, or Authorization: Bearer <AIC-JWT>; AuthMode = MTLSOnly / BearerOnly / MTLSOrBearer
Decision pipeline certificate validity → CRL/OCSP revocation → roles → AIC decision → capability ∩ principal authorization → parameter bounds → allow / allow_unresolved / deny
Capability language CLC-1.8 concrete operations, capability id matching, parameter bounds (max_rows, enums, …), authorization constraints (CIDR, time window, concurrency), residual obligations
Delegation DA / DA-v2 signature verification, delegation chains, EffectiveDelegationCapabilities, DA freshness, principal-key binding, representative-mode rejection
Integration middleware (Handler / AuthMiddleware) around your handler; reverse proxy (NewServer) injecting X-AIC-*; transport-independent DecisionServer (Decide, HTTP, gRPC, admin, health)
Evidence per-decision DSSE-wrapped CLC decision records, plus admission and outcome records; FileSink/SlogSink; key-endorsed signing (EvidenceConfig.Signer / SignKeyFile / Sign, with RequireSignature to fail closed); per-admission nonces; RATS §10 freshness; profiles; requirement binding
Evidence verification VerifyEvidenceDir (structure + signature + decision↔outcome linkage, orphan reporting), VerifyEvidenceEnvelope, VerifyFnFromKey / VerifyFnFromPublicKey (pinned key), RecordSigner.VerifyFn(), LoadEvidenceRecord
Evidence export FileEvidenceExporter → EvidenceBundle v0.1 (manifest / operation / subject / authorization / decision / supervision / Merkle-chained audit / signatures), with the bundle itself signable (EvidenceBundle.Sign / VerifySignature) and human-readable renderings (RenderMarkdown / RenderCSV / RenderText, WriteRendered)
Challenges CLC-CHALLENGE-v1: a remediable refusal answered with RFC 9457 application/problem+json + Retry-After, listing what evidence is missing
Audit Merkle-chained AuditLogger (TSA-signable), VerifyAuditEntry, FilterAuditFile, ArchiveAuditFile
Supervision runtime human approval (ApprovalRequester), break-glass with mandatory recording (OverrideRecorder), RequireApproval trigger, SupervisionPolicy, append-only SupervisionStore
Policy OU→role AuthorizationPolicy, PKCS#7-signed policy files (SignPolicy/VerifySignedPolicy), fail-closed hot reload (ReloadPolicy*, admin token)
Plugins CapabilityPlugin, capability Registry, ConstraintEvaluator, ParameterValidator, GeoResolver — per-Config isolation (several gateways, one process)
Identity hygiene IdentityMode (backends never see the raw cert unless you want them to), log-field masking for serials / emails / tokens / paths
Ops /healthz-style HealthReport, DecisionMetrics, Config.Validate(), TLS helpers, cipher-suite policy, OCSP stapling
Carriers mcp/ subpackage (AIC-gated MCP server) and grpc/ subpackage (AICDecisionService, codec aic-json-v1)

Full Config, record shapes and constants: docs/reference.md.


Quick start (2 minutes)

Four steps; the demo generates its own CA and certificates, so there is nothing to configure first.

1. Get the code and generate demo certificates — a CA plus a server certificate and an agent certificate that carries an AIC extension:

git clone https://github.com/varwof/aic-verifier && cd aic-verifier
go run ./examples/mtls-backend/gen-cert -out ./demo-certs

2. Start the protected service — it terminates mTLS on :9444 and forwards admitted requests to a demo backend on :9081 (started by the same process). Leave it running in this shell:

go run ./examples/mtls-backend --certs ./demo-certs

3. Call it as the agent — in a second shell, present the agent certificate:

curl -sS --cert demo-certs/client-cert.pem --key demo-certs/client-key.pem \
     --cacert demo-certs/ca-cert.pem https://localhost:9444/api
# {"backend":"real-api-mtls","identity":{"X-Forwarded-For":"127.0.0.1, 127.0.0.1"}}

4. Watch a refusal — ask for something the agent is not allowed to do; the request never reaches the backend:

curl -sS --cert demo-certs/client-cert.pem --key demo-certs/client-key.pem \
     --cacert demo-certs/ca-cert.pem https://localhost:9444/api/transfer
# {"code":"access_denied","message":"agent missing required capabilities"}

Stop the demo with Ctrl-C in the first shell. What the request left behind — the decision record, and how to recompute it — is docs/quickstart.md, which also covers calling the API without a client certificate and reading the record back.


Simple examples

Wrap your own handler (middleware)
conf := &aicverifier.Config{
    CACertFile:           "certs/ca-cert.pem",
    AuthMode:             aicverifier.MTLSOnly,
    RequireAIC:           true,
    RequiredCapabilities: []string{"demo/example-v1:api:read"},
}

mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
    ac := aicverifier.FromContext(r.Context()) // agent id, principal, caps, verdict
    fmt.Fprintf(w, "hello %s\n", ac.AgentID)
})

handler, err := conf.Handler(mux) // runs the whole pipeline around mux
if err != nil {
    log.Fatal(err)
}
srv := &http.Server{
    Addr:      ":8443",
    Handler:   handler,
    TLSConfig: muxTLSConfig, // ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: <your CA>
}
log.Fatal(srv.ListenAndServeTLS("certs/server-cert.pem", "certs/server-key.pem"))
Reverse proxy, backend untouched
target, _ := url.Parse("http://127.0.0.1:8080")
server, err := aicverifier.NewServer(conf, []aicverifier.Route{
    {Path: "/api", Target: target, RequiredCapabilities: []string{"demo/example-v1:api:read"}},
})
log.Fatal(server.ListenAndServe(":9444")) // injects X-AIC-* identity headers
Authorize one operation, in-process

A capability can carry parameter bounds; asking for more than the grant allows is a deny, not a warning.

dec, err := aicverifier.AuthorizeOperation(aic, pa,
    "std/database-v1:query:SELECT",
    map[string]any{"limit": 50})       // grant says {"limit": 100} -> allow
switch dec.Verdict {
case semantics.VerdictAllow:
case semantics.VerdictAllowUR:          // residual obligation, not allow
default:                                // VerdictDeny; dec.Reason is normative
}
Bearer AIC-JWT only
conf := &aicverifier.Config{
    JWTCAFile:  "certs/jwt-ca.pem",
    AuthMode:   aicverifier.BearerOnly,
    JWTIssuer:  "aic-verifier-example",     // optional iss pin
    JWTAudience: []string{"myapi"},         // optional aud pin
}
Transport-independent decisions (HTTP + gRPC + in-process agree)
core, _ := aicverifier.NewDecisionServer(conf)
ac, err := core.Decide(ctx, &aicverifier.RequestView{
    BearerToken:     token,
    TransportSecure: true,
})
// same Config -> identical verdict from HTTP, gRPC (grpc.NewDecisionService),
// or any in-process carrier.
Turn on evidence, then verify it offline
signer, _ := aicverifier.LoadRecordSignerFile("/etc/aic/evidence-key.pem", "pep-1")
conf.Evidence = &aicverifier.EvidenceConfig{
    Sink:             &aicverifier.FileSink{Dir: "/var/lib/aic/evidence", RecorderID: "pep-1"},
    Audience:         "https://gateway-a.example",
    TTL:              5 * time.Minute,     // RATS §10.1 freshness clock
    Signer:           signer,              // key-endorse every record (RSA/ECDSA/Ed25519)
    RequireSignature: true,                // refuse to start without a signing key
    Strict:           true,                // fail closed if the sink is down
}
// ...later, off the hot path:
rep, err := aicverifier.VerifyEvidenceDir("/var/lib/aic/evidence",
    signer.VerifyFn())                     // pinned key; verifies sigs + linkage
Export a bundle, sign it, and render it for a human
exporter := &aicverifier.FileEvidenceExporter{
    AuditFile:         "/var/lib/aic/audit.jsonl",
    SupervisionFile:   "/var/lib/aic/supervision.jsonl",
    EvidenceDir:       "/var/lib/aic/evidence",
    Signer:            signer,             // sign the package itself, not just the records
}
bundle, _ := exporter.Export(ctx, aicverifier.EvidenceQuery{AgentID: "agent-001"})
bundle.VerifySignature(signer.VerifyFn())  // a holder re-checks the export
bundle.WriteRendered("/tmp/evidence.md",    aicverifier.RenderMarkdown) // or RenderCSV / RenderText
Answer a remediable refusal with a challenge
conf.Challenges = &aicverifier.ChallengeConfig{
    TTL:        2 * time.Minute,
    Audience:   "https://gateway-a.example",
    RetryAfter: 10 * time.Second,          // becomes the Retry-After header
}
// A denial short on §8.4 evidence returns 403 application/problem+json with a
// CLC-CHALLENGE-v1 body: what is missing, and when a corrected retry is welcome.
Runtime human approval / break-glass
conf.SupervisionPolicy = aicverifier.SupervisionPolicy{RequireRuntimeApproval: true}
conf.ApprovalRequester = myApprover   // nil + RequireRuntimeApproval => startup error
conf.RequireApproval = func(ac *aicverifier.AuthContext, r *http.Request) bool {
    return r.Method != http.MethodGet // route writes through a human
}
// break-glass requires a recorder too, so an unlogged override is never available.
Gate an MCP server
reg, _ := mcp.LoadJSON(registryJSON)
h, _ := mcp.NewHandler(mcp.ServerConfig{ServerName: "aic-tools", Version: "0.1"},
    reg, map[string]mcp.ToolHandler{"echo": echoHandler})
// wrap h with conf.Handler(...) (or conf.AuthMiddleware) to admit by AIC first;
// each tool call can read the verified identity via mcp.AuthContextFromToolContext.

More runnable paths: docs/examples.md and the examples/ tree (mtls-backend, bearer-jwt-backend, mcp-server, mcp-behind-proxy, supervision-demo, showcase, inspect-record, smoke-verify).


The decision pipeline

Per request, one pipeline runs and produces one decision:

  1. Credential — mTLS chain verification, or AIC-JWT parse + verify (bearer never travels in cleartext: the request must arrive over TLS).
  2. Revocation — CRL and/or OCSP (optional; configured per CRLCache / OCSPCache).
  3. Roles — OU→role mapping (AuthorizationPolicy), RequireRoles, admin OU.
  4. AIC decision — capability ∩ principal authorization over the exact operation, including parameter bounds.
  5. Constraints — CIDR / time-window / concurrency (EnforceConstraints); residual obligations the executor must discharge stay allow_unresolved.
  6. Verdict — allow / allow_unresolved / deny, with a stable reason code.

Refusals are typed: *aicverifier.AuthError carries the HTTP status, a stable reason code, the records the refusal produced, and — when presenting evidence could fix it — an RFC 9457 problem document with a CLC-CHALLENGE-v1 challenge.

allow_unresolved is not allow. A recognized-but-unevaluated constraint is never silently promoted; it stays visible and must be discharged at the effect boundary.


Evidence, not logs

A decision is not much use if nobody can check it later. With Evidence set, the SDK emits a CLC decision record (inputs frozen at the canonical boundaries, a digest over them, the verdict and its stable reason), optionally signed and wrapped in a DSSE envelope, through an EvidenceSink. Refusals that never reach the language layer produce an admission record instead, and the proxy or middleware can report what the effect boundary observed as an outcome record.

  • One record per (authority source, operation). A record whose verdict does not reproduce is impossible by construction — it is recomputed from the same grants.
  • Refusals are recorded too, so intent is as auditable as action.
  • Recomputable offline. register/cmd/record -verify re-runs the language over a record the holder did not produce; VerifyEvidenceDir additionally binds each outcome to the decision actually present and reports orphans instead of counting them as consent.
  • Key-endorsed when you want it. EvidenceConfig.Sign appends a DSSE signature over PAE(payloadType, payload) to every record; a deployment that needs "which admission point issued this" gets it, and one that does not pays nothing (records stay content-recomputable, just unsigned).

Details and the profiles that pin the shape: docs/evidence.md.


Compliance fit

The properties above map directly onto hard requirements regulators write down. All of the following are public requirements, matched to what the SDK actually does — not a certification claim.

Requirement (public source) What aic-verifier provides
Strong authentication / unique identity — HIPAA §164.312(d),(a)(2)(i); EO 14028 MFA; 中国网安法 §24 真实身份 mTLS AIC certificate or AIC-JWT, key-bound (SPKI / cnf)
Least privilege / fine-grained authorization — PIPL §51(四); HIPAA §164.312(a)(1); EO 14028 §4(i) per-operation capability ∩ principal authorization with parameter bounds — decided per action, not per session
Least-privilege enforcement at the action boundary — NIST SP 800-207 fail-closed refusal before the handler; allow_unresolved never silently allow
Immutable / attributable audit trail — SEC 17a-4(f); HIPAA §164.312(b); EU AI Act Art 12; 中国网安法 §21 Merkle-chained audit log + DSSE decision records, per-admission nonce, optional TSA timestamp
Evidence verifiable offline / accountable — SEC 17a-4(f)(2)(iv),(f)(3)(v); EU AI Act Art 12(3)(d) records recomputable without the online authority; pinned-key verification; exportable EvidenceBundle
Human oversight / approval — EU AI Act Art 14(4)(5),(26); PIPL §24; 算法推荐规定 §7 RequireApproval → ApprovalRequester, break-glass with mandatory OverrideRecorder, auditable supervision events
Real-time revocation CRL / OCSP in the pipeline; short-lived credentials by design

What this does not do: it constrains no path that bypasses the enforcement point. Evidence proves the admission decision; it does not prove source truth or that a downstream effect succeeded (that is the effect-boundary's job). It is not an accredited certification.


Configuration you will actually touch

Field What it does
CACertFile / JWTCAFile trust anchors for mTLS client certs / bearer tokens
AuthMode MTLSOnly, BearerOnly, MTLSOrBearer (default)
RequireAIC reject certificates that carry no AIC extension
RequiredCapabilities / RequiredOperations capability ids, or concrete operations with parameter bounds, the caller must satisfy
EnforceConstraints evaluate authorization constraints (time window, CIDR, max_rows)
AdmissionConfig CRL/OCSP, roles, SPIFFE, delegation chain, monitoring hooks
Evidence / EvidenceProfile / EvidenceRequirement records, their shape, and the sufficiency bar
Challenge / ChallengeCarrier whether a refusable refusal carries a challenge, and how it is rendered
SupervisionPolicy / ApprovalRequester / OverrideRecorder runtime approval and break-glass
AuthorizationPolicy / Constraints / ParameterValidators per-Config isolation of policy and registries
IdentityMode how much verified identity is disclosed to a backend
ServerOptions / StreamBody / LogFile / Logger proxy server tuning, body streaming, logging

The full list, with types and defaults, is in docs/reference.md and docs/api.md; config.example.json is kept in sync with the JSON surface by CI.


Documentation map

You want to… Start here
Try it in two minutes quickstart.md
Decide between middleware and reverse proxy, and see the code api.md · architecture.md
Turn on evidence and understand what a record means evidence.md
Know every Config field, record shape, constant and version reference.md
Understand why a decision is (or is not) trusted threat-model.md
Put it in production: TLS, keys, monitoring, rotation deployment.md
Run the examples end to end examples.md
Compare SDK vs full gateway, and current non-goals comparison.md

Stability

Before v1.0 the surface is split two ways so an embedder can tell what is safe to build on:

Surface Contract
Frozen until v1.0 — Config, Handler / AuthMiddleware, NewServer + Server.{Listen,Serve,Addr,ListenAndServe,Close}, NewDecisionServer + DecisionServer.{Decide,Health,AdminHandler,ReloadPolicy,Close}, AuthContext, AuthError, DecisionMetrics additive-only: no field is renamed or removed and no default changes without a documented deprecation. The CLC revision such a binary decides with only moves with an explicit CLCRevision bump.
Experimental — plugin/registry hooks (PluginRegistry, CapabilityRegistry, RegisterGeoResolver, parameter validators), supervision/evidence interfaces (ApprovalRequester, OverrideRecorder, EvidenceExporter), and anything under an examples/ or mcp/ package may change in a minor release while the surrounding format stabilises.

Config.Close (also reached through Server.Close / DecisionServer.Close) releases the config-owned background resources — audit logger, nonce-cache cleanup, supervision store, SDK log file. CRL and OCSP refresh loops remain the caller's to stop (CRLCache.Start, StartOCSPStapling).


Requirements

Requirement Why
Go 1.26 or newer the module declares go 1.26; no cgo
github.com/varwof/register v0.6.0 the CLC evaluator and the decision-record format
github.com/varwof/types v0.6.0 AIC / AIC-JWT structures
github.com/varwof/pkcs7 v0.1.1 the semantics-layer detached signature check
github.com/mark3labs/mcp-go v1.0.0 only if you import the mcp subpackage

They come in with go get; there are no local replace directives and nothing is vendored. The SDK itself needs no external service: CRL/OCSP responders and an RFC 3161 timestamp authority are optional and only used when you configure them.

For the demo above you additionally need curl (any mTLS-capable client works) and three free local ports: 9444 (proxy), 9081 (demo backend it starts itself), and later 9443 (the evidence demo).


Repository Role
varwof/types Shared Go types: AIC, AIC-JWT, capabilities
varwof/register Capability registry, PKCS#7 signing, CLC semantics (the reference implementation this SDK evaluates with)
varwof/capability CLC specification and conformance corpus
varwof/aic-agent Consumer-side SDK that mints and carries the credential this verifier checks
varwof/aic-exec AIC-gated command executor built on this admission core
varwof/gateway-core · varwof/gateway Full gateway; this SDK is its embeddable admission core

License

Apache-2.0. See LICENSE.

See also SECURITY.md (vulnerability reporting, guarantees, hardening), CONTRIBUTING.md (development and the check list), and CHANGELOG.md.

Documentation

Overview

Package aic-verifier provides a drop-in Go SDK for HTTP services that need to enforce AIC (Authorization Identity Certificate) authorization, derived from varwof/gateway-core.

It exposes two integration styles with a shared admission pipeline:

  1. Middleware: wrap any http.Handler with aicverifier.AuthMiddleware, and the SDK authenticates every request (mTLS client certificate or a Bearer AIC-JWT) and runs the full AIC decision chain before your handler runs.
  2. Reverse proxy: aicverifier.Server listens on one address and forwards verified requests to a real backend API, injecting the verified client identity to the backend via X-AIC-* headers.

Both styles run the identical pipeline (CRL -> OCSP -> RBAC -> AIC -> constraints -> plugins) so a request admitted by one is admitted by the other.

Package gw provides the shared security engine for varwof gateways.

Capability plugin types (CapabilityPlugin, PluginContext, PluginResult, PluginDecision, HTTPFacts, PluginRegistry) are defined in the types module and re-exported here for backward compatibility.

Index

Constants

View Source
const (
	OCSPFallbackAllow = "allow"
	OCSPFallbackDeny  = "deny"
	OCSPFallbackCRL   = "crl"
)

OCSPFallbackAllow/Deny/CRL are OCSP fallback policy constants.

View Source
const (
	// OutcomeExecuted means the executor established that the effect occurred.
	OutcomeExecuted = "executed"
	// OutcomeFailed means it established the effect did not occur.
	OutcomeFailed = "failed"
	// OutcomeIndeterminate means it could not establish either.
	OutcomeIndeterminate = "indeterminate"
	// OutcomeObserved means a response was observed but the deployment has not
	// classified the effect (the honest default for a proxy that only saw HTTP).
	OutcomeObserved = "observed"
)

Recommended outcome tokens. The field is a plain string: the vocabulary belongs to the execution boundary (AEB), not to this SDK.

View Source
const (
	RoleAdmin  = "gateway:admin"
	RoleOps    = "gateway:ops"
	RoleAudit  = "gateway:audit"
	RoleDeploy = "gateway:deploy"
	RoleRead   = "gateway:read"
	RoleWild   = "gateway:*"
)

RoleAdmin/Ops/Audit/Deploy/Read/Wild are role constants.

View Source
const (
	SupervisionDecisionApproved = pki.SupervisionDecisionApproved
	SupervisionDecisionDenied   = pki.SupervisionDecisionDenied
	SupervisionDecisionPending  = pki.SupervisionDecisionPending
)

Supervision decisions referenced by SupervisionResult.Decision (aliases of the shared pki constants).

View Source
const AdmissionRecordPredicateType = "https://varwof.com/aic/v1/admission-record"

AdmissionRecordPredicateType identifies pipeline-level (pre-language) records.

View Source
const AdmissionRecordVersion = "AIC-ADMISSION-RECORD-v1"

AdmissionRecordVersion is the record revision.

View Source
const BundlePayloadType = "application/vnd.varwof.aic-evidence-bundle.v0.1+json"

BundlePayloadType domain-separates a bundle signature from the record DSSE signatures: the same key never signs two different meanings with the same bytes.

View Source
const CLCRevision = semantics.CLCRevision

CLCRevision is the CLC language revision this SDK decides with.

View Source
const ConstraintAuditRequiredKey = "op:audit:required"

ConstraintAuditRequiredKey is the capabilityId for audit-required operation constraint.

View Source
const ConstraintCIDRKey = "network:cidr"

ConstraintCIDRKey is the capabilityId for IP network range constraint.

View Source
const ConstraintConcurrentKey = "session:max-concurrent"

ConstraintConcurrentKey is the capabilityId for max concurrent connections constraint.

View Source
const ConstraintGeoFenceKey = "geo-fence"

ConstraintGeoFenceKey is the capabilityId for geo fence constraint.

View Source
const ConstraintHardTimeoutKey = "session:hard-timeout"

ConstraintHardTimeoutKey is the capabilityId for session hard timeout constraint.

View Source
const ConstraintIdleTimeoutKey = "session:idle-timeout"

ConstraintIdleTimeoutKey is the capabilityId for session idle timeout constraint.

View Source
const ConstraintReadOnlyKey = "op:readonly"

ConstraintReadOnlyKey is the capabilityId for read-only operation constraint.

View Source
const ConstraintTimeWindowKey = "time:window"

ConstraintTimeWindowKey is the capabilityId for time window constraint.

View Source
const DefaultDAAgeMax = 30 * time.Second

DefaultDAAgeMax is the default value for the DelegationAuthorization.timestamp freshness window (the delegation-authorization validation flow: |now - timestamp| ≤ 30s).

View Source
const DefaultMaskRune = '*'

DefaultMaskRune is the character used to replace sensitive content.

View Source
const DefaultMaxBodyBytes = 1 << 20 // 1 MiB

DefaultMaxBodyBytes is the maximum request body the pipeline reads for capability plugin evaluation (bounded copy; the full body is restored before forwarding or calling the next handler).

View Source
const DefaultMaxChainLength = 8

DefaultMaxChainLength is the default upper bound for maximum delegation chain length (anti-certificate-bomb). Chains exceeding this are rejected (specification maxDepth is set by the top Principal; this serves as the gateway-side hard limit). Real-world Agent delegation depths are typically 2–3 levels; default 8 provides ample margin.

View Source
const HardTimeoutMax = 86400

HardTimeoutMax is the maximum value for session:hard-timeout (seconds).

View Source
const HardTimeoutMin = 60

HardTimeoutMin is the minimum value for session:hard-timeout (seconds).

View Source
const IdleTimeoutMax = 3600

IdleTimeoutMax is the maximum value for session:idle-timeout (seconds).

View Source
const IdleTimeoutMin = 30

IdleTimeoutMin is the minimum value for session:idle-timeout (seconds).

View Source
const MaxConcurrentMax = 1024

MaxConcurrentMax is the maximum value for the max parameter of the max-concurrent constraint.

View Source
const MaxConcurrentMin = 1

MaxConcurrentMin is the minimum value for the max parameter of the max-concurrent constraint.

View Source
const OfflineLifetimeLimit = time.Hour

OfflineLifetimeLimit is the maximum remaining certificate validity enforced in offline mode (G2(b): ≤1h).

View Source
const OutcomeRecordPredicateType = "https://varwof.com/aic/v1/outcome-record"

OutcomeRecordPredicateType identifies execution-boundary records.

View Source
const OutcomeRecordVersion = "AIC-OUTCOME-RECORD-v1"

OutcomeRecordVersion is the record revision.

View Source
const ProblemContentType = "application/problem+json"

ProblemContentType is the RFC 9457 media type.

View Source
const ProblemTypeEvidenceRequired = "https://varwof.com/clc/v1/problems/evidence-required"

ProblemTypeEvidenceRequired identifies this profile's problem type.

View Source
const RolePrefix = "gateway:"

RolePrefix is the gateway role OU prefix.

Variables

View Source
var (
	OIDSigECDSAWithSHA256  = pki.OIDSigECDSAWithSHA256
	OIDSigECDSAWithSHA384  = pki.OIDSigECDSAWithSHA384
	OIDSigECDSAWithSHA512  = pki.OIDSigECDSAWithSHA512
	OIDSigRSAWithSHA256    = pki.OIDSigRSAWithSHA256
	OIDSigRSAWithSHA384    = pki.OIDSigRSAWithSHA384
	OIDSigRSAWithSHA512    = pki.OIDSigRSAWithSHA512
	OIDSigRSAPSSWithSHA256 = pki.OIDSigRSAPSSWithSHA256
	OIDSigEd25519          = pki.OIDSigEd25519
	OIDSHA256              = pki.OIDSHA256
	OIDSHA384              = pki.OIDSHA384
	OIDSHA512              = pki.OIDSHA512
)

OID re-exports — signature algorithm OIDs.

View Source
var (
	AIAOID  = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 1}
	OCSPOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1}
)

AIAOID is the Authority Information Access method OID. OCSPOID is the OCSP responder OID.

View Source
var ErrRecordSignerMismatch = errors.New("aic-verifier: record signature does not verify against the pinned key")

ErrRecordSignerMismatch is returned when a record's signature does not verify against the key a deployment pinned via VerifyFnFromPublicKey.

SecureCipherSuites is the list of secure cipher suites (GCM/CHACHA).

View Source
var Version = "0.3.0-rc2"

Version is the single source of truth for the aic-verifier version. Every consumer reads this value (do not hard-code a version elsewhere).

Released tag: v<Version>. Bump it and tag together (hack/versioncheck.sh enforces that Version is not older than the latest tag).

Functions

func AICFingerprint

func AICFingerprint(cert *x509.Certificate) string

AICFingerprint computes the SHA-256 hex fingerprint of the AIC extension DER encoding in the certificate. Returns empty string if the certificate has no AIC extension (audit field uses omitempty, keeping old entries readable).

func ArchiveAuditFile

func ArchiveAuditFile(path string) error

ArchiveAuditFile archives (compress-rotates) the audit log file.

func AuditDuration

func AuditDuration(start time.Time, entry *AuditEntry)

AuditDuration computes and sets the duration of an audit entry.

func AuthorizeCapabilities

func AuthorizeCapabilities(caps []Capability, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeCapabilities decides a concrete operation against an explicit capability list.

func AuthorizeCapabilitiesWithConstraints

func AuthorizeCapabilitiesWithConstraints(caps, constraints []Capability, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeCapabilitiesWithConstraints decides an operation against a capability list whose grants also carry the connection's authorization constraints.

Constraints are what make the three-valued verdict interesting: the CLC core evaluates a few of them itself (max_rows), recognises the rest (time:window, network:cidr) and reports them as residual obligations rather than silently dropping them — so a caller that reads only "allow" fails closed.

func AuthorizeGrants

func AuthorizeGrants(grants []semantics.Grant, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeGrants decides opID/params against an explicit CLC-v1 grant set.

func AuthorizeOperation

func AuthorizeOperation(aic *AIC, pa *PrincipalAuthorization, opID string, params map[string]any) (semantics.Decision, error)

AuthorizeOperation decides a concrete operation for an admitted connection.

The effective authority is the AIC capability set intersected with the PrincipalAuthorization grants when a PA is present (the P∩C model): the operation has to be authorized by both, so each set is decided and the results are combined — deny wins, and residual obligations union.

func BaseTLSConfig

func BaseTLSConfig(cipherSuites []string, minTLSVersion string) *tls.Config

BaseTLSConfig creates a base TLS configuration.

func BuildCipherSuites

func BuildCipherSuites(names []string) []uint16

BuildCipherSuites builds a cipher suite list from name strings.

func BuildSourceChain added in v0.2.0

func BuildSourceChain(clientCert *x509.Certificate, aic *AIC, userCert *x509.Certificate) (*semantics.SourceChain, error)

BuildSourceChain returns the authorization sources this admission can prove it rested on: the AIC (or plain leaf) certificate it verified, plus the principal certificate when the deployment configured one.

func CanonicalJSON

func CanonicalJSON(v any) ([]byte, error)

CanonicalJSON serializes v with keys sorted lexicographically at every level (map-key order guaranteed by encoding/json) and no extraneous whitespace.

func CheckAuthorizationConstraints

func CheckAuthorizationConstraints(constraints []Capability, clientIP string) error

CheckAuthorizationConstraints performs offline validation of authorizationConstraints. Supports: network:cidr (requires ClientIP), time:window, geo-fence (requires ClientIP), session:max-concurrent, session:hard-timeout, session:idle-timeout, op:readonly, op:audit:required. Unknown constraint types are ignored by default (forward compatible); the caller logs audit warnings; after registering a custom constraint executor (RegisterConstraint), it will be recognized and executed.

func CheckAuthorizationConstraintsAt

func CheckAuthorizationConstraintsAt(constraints []Capability, clientIP, timeHHMM string) error

CheckAuthorizationConstraintsAt is the same as CheckAuthorizationConstraints, but evaluates time-window constraints at a specified UTC time (HH:MM), useful for testing and offline decision demonstrations. When timeHHMM is empty, uses the current time. The tz field in time-window is converted to the corresponding timezone during evaluation.

func CheckDAFreshness

func CheckDAFreshness(ts time.Time, now time.Time, maxAge time.Duration) error

CheckDAFreshness validates that DelegationAuthorization.timestamp is within the freshness window. When now is nil, uses time.Now(); when maxAge <= 0, uses DefaultDAAgeMax. Zero-value timestamp (never set) is treated as expired.

func CheckDelegatedAgentCert

func CheckDelegatedAgentCert(cert *x509.Certificate) string

CheckDelegatedAgentCert validates the legitimacy of a Delegated-Agent certificate (for non-HTTP protocols like TCP). Returns empty string on success, non-empty rejection reason on failure.

A certificate that merely carries the "Delegated-Agent" OU is not legitimate on its own (finding 17): the OU is a plaintext subject attribute anyone can mint, so a Delegated-Agent cert must additionally be core-signed (carry a valid AIC extension) and be within its validity window. Certificates without the OU are not delegated-agent certs and pass through.

func CheckDelegatedAgentHeaders deprecated

func CheckDelegatedAgentHeaders(cert *x509.Certificate, r *http.Request) string

Deprecated: CheckDelegatedAgentHeaders validates the X-Agent-User/X-Agent-TTL headers of a Delegated-Agent certificate (B1 username delegation path). The username cannot be cryptographically bound to the certificate; identity propagation has been changed to B2 (X-Client-Cert-DER certificate passthrough). This function is kept for legacy client compatibility only. Certificates without Delegated-Agent OU pass through directly. Returns empty string on success, non-empty rejection reason on failure.

Security note (G4): The client's X-Agent-User / X-Agent-TTL headers are entirely controlled by the requestor and must never be trusted as the true identity of the proxied user. This function only performs "declarative" validation (prompting ops to configure the delegation channel). The real delegation identity is derived by DelegatedAgentServerIdentity from the core-signed AIC/GatewaySession, and the gateway overwrites X-Agent-User / X-Agent-TTL headers before forwarding.

func CheckRole

func CheckRole(roles []string, allowed []string) bool

CheckRole checks whether the role list contains an allowed role.

func ClientTLSConfig

func ClientTLSConfig(caCertFile, certFile, keyFile string, cipherSuites []string, minTLSVersion string) (*tls.Config, error)

ClientTLSConfig creates an mTLS client-side configuration.

func ConnectionConstraintEvaluator added in v0.2.0

func ConnectionConstraintEvaluator(clientIP string) func(op Operation, unresolved []string) bool

ConnectionConstraintEvaluator returns an UnresolvedEvaluator that discharges the obligations the connection-level constraint registry can evaluate for the given client IP (source CIDRs, time windows, and whatever else is registered).

It exists because the connection-level check and the language-level obligation are two halves of one rule: the language *declares* the constraint, and this evaluator *discharges* it. Passing it is an explicit act — a deployment that does not pass it keeps the §8.4 default, which is fail-closed — so the release is a declared policy rather than an implicit bypass. Anything the registry cannot evaluate (for example `max_rows`) is not discharged here: return false and let the caller decide.

func ConstraintRecheckLoop

func ConstraintRecheckLoop(aicConstraints, paConstraints []Capability, clientIP string, interval time.Duration, done <-chan struct{}, onViolation func(reason string))

ConstraintRecheckLoop periodically re-evaluates authorizationConstraints (G3: constraint timing consistency).

Constraints on long-lived data plane connections like TCP are only checked once at handshake; time-window / revocation constraints that expire over time cease to be effective after crossing the window — for example, a "weekdays 9-18 only" connection established during the night window remains active during the day. This function re-evaluates the authorizationConstraints of both AIC and PrincipalAuthorization at the given interval (using the current time), calling onViolation when a constraint is no longer met (gateway disconnects and audits accordingly). Stops when done is closed (idempotent).

Any single constraint evaluation failure is treated as a violation; a and pa may both be nil/empty (skipping the corresponding set). Callers should ensure onViolation is non-nil (nil internally only logs, no actual action).

func ConstraintStrings

func ConstraintStrings(constraints []Capability) []string

ConstraintStrings renders authorization constraints in CLC form: <scheme>:<type>[:<params-json>], e.g. varwof/constraint-v1:network:cidr:["192.0.2.0/24"].

func DAHash

func DAHash(cert *x509.Certificate) string

DAHash computes the SHA-256 hex hash of the DelegationAuthorization signatureValue in the AIC (authorization evidence fingerprint). Returns empty string if AIC is missing or has no DA signature.

func DAHashFromAIC

func DAHashFromAIC(aic *AIC) string

DAHashFromAIC computes the SHA-256 hash of the DelegationAuthorization signatureValue for a parsed AIC. Returns empty string if no DA signature is present.

func DecodeBase64

func DecodeBase64(s string) ([]byte, error)

DecodeBase64 performs Base64 decoding.

func DecodeDecideResult

func DecodeDecideResult(b []byte) (*AuthContext, *AuthError, error)

DecodeDecideResult parses a wire outcome into (AuthContext, *AuthError, err). A nil ac with a nil auth error on a granted=... means parsing failed.

func DelegatedAgentServerIdentity

func DelegatedAgentServerIdentity(cert *x509.Certificate, principal string) (user string, expiry time.Time, reason string)

DelegatedAgentServerIdentity derives the server-asserted delegation identity from the core-signed certificate extension (AIC), preventing G4 identity spoofing: never trust client-supplied plaintext headers. Returns:

  • user: the server-asserted proxied subject (from AIC.PrincipalUid or cert CN/OU fallback)
  • expiry: delegation validity deadline (zero time.Time{} when no hard-timeout constraint)
  • reason: non-empty rejection reason (illegal conditions other than missing Delegated-Agent OU)

func EffectiveDelegationCapabilities

func EffectiveDelegationCapabilities(chain []*x509.Certificate, principalCaps []pki.Capability, maxChainLen int) ([]pki.Capability, error)

EffectiveDelegationCapabilities validates per-level capability subsets and computes the intersection along the delegation chain.

chain goes top-down: chain[0]=topmost delegated Agent, chain[len-1]=bottom Agent. principalCaps is the effective capabilities P of the original principal (top Principal). Returns C_eff = P ∩ C_1 ∩ … ∩ C_n.

Process: eff = P; for each level C_i, first verify C_i ⊆ eff (permissions only decrease, escalation is rejected), then eff = filterCovered(C_i, eff) (retain this level's capabilities that are authorized by eff).

Note: This function only performs capability semantic validation; signature verification is handled separately by Verify.

func EffectiveDelegationCapabilitiesFromAIC

func EffectiveDelegationCapabilitiesFromAIC(chain []*x509.Certificate, topPrincipal *x509.Certificate, maxChainLen int) ([]pki.Capability, error)

EffectiveDelegationCapabilitiesFromAIC is a convenience entry point: extracts AIC.capabilities from the top Principal certificate as P, then recursively computes the intersection. Chain signature verification is performed separately by the caller.

func EncodeBase64

func EncodeBase64(data []byte) string

EncodeBase64 performs Base64 encoding.

func EncodeDecideRequest

func EncodeDecideRequest(v *RequestView) ([]byte, error)

EncodeDecideRequest serializes a view for the wire.

func EncodeDecideResult

func EncodeDecideResult(ac *AuthContext, err error) ([]byte, error)

EncodeDecideResult serializes an admission outcome for the wire. err may be an *AuthError to carry a denial; any other error is folded into a generic denial so the wire stays typed.

func ExtractOCSPURL

func ExtractOCSPURL(cert *x509.Certificate) string

ExtractOCSPURL extracts the OCSP URL from the certificate's AIA extension.

func ExtractPolicyRoles

func ExtractPolicyRoles(cert *x509.Certificate) []string

ExtractPolicyRoles extracts policy roles from the certificate OU. Uses the global authorization policy's (if set) OU→role mapping to resolve role names; when no policy is set, falls back to the hardcoded ExtractRoles (only identifies gateway: prefix OUs). Returned role names include both policy role names and original gateway:* OUs (if present), for compatibility with both configuration styles.

func ExtractRoles

func ExtractRoles(cert *x509.Certificate) []string

ExtractRoles extracts the gateway role list from the certificate OU.

func ExtractSPIFFEIDFromCert

func ExtractSPIFFEIDFromCert(cert *x509.Certificate) string

ExtractSPIFFEIDFromCert extracts a SPIFFE ID from a certificate's SAN URIs. Returns "" if no SPIFFE URI is found.

func FetchOCSPResponseRaw

func FetchOCSPResponseRaw(cert, issuer *x509.Certificate, ocspURL string) ([]byte, error)

FetchOCSPResponseRaw fetches the raw OCSP response bytes.

func FilterAuditFile

func FilterAuditFile(file string, since time.Time, action string, cn, serial, mapping string) error

FilterAuditFile filters the audit file by conditions and prints matching entries.

func FindStartOffsetByTime

func FindStartOffsetByTime(file string, target time.Time) (int64, error)

FindStartOffsetByTime binary-searches for the file offset corresponding to a given time.

func HasAIC

func HasAIC(cert *x509.Certificate) bool

HasAIC reports whether the certificate carries a valid AIC extension (G2: short-lived certificate identification). Malformed AIC returns false (such certificates are denied in the admission pipeline and will not enter the data plane).

func HasDelegatedAgentOU

func HasDelegatedAgentOU(cert *x509.Certificate) bool

HasDelegatedAgentOU is the exported wrapper of hasDelegatedAgentOU, for gateways to check delegation identity before forwarding.

func HashLeaf

func HashLeaf(data []byte) []byte

HashLeaf computes the SHA256 hash of a Merkle tree leaf node. Finding 22: the leaf is domain-separated (prefix 0x00) from internal node hashes so a leaf hash can never be presented as an internal node hash or vice versa.

func HashNode

func HashNode(left, right []byte) []byte

HashNode computes the SHA256 hash of a Merkle tree internal node. Finding 22: internal nodes use a distinct domain prefix (0x01) so lone-leaf roots (HashLeaf, 0x00) cannot collide with node hashes.

func IsAdminOU

func IsAdminOU(ou string) bool

IsAdminOU checks whether the OU is an admin role (compatible with gateway:admin and bare admin).

func LoadCA

func LoadCA(caCertFile string) (*x509.CertPool, error)

LoadCA loads a CA certificate pool and validates that every certificate in the bundle is a CA.

func LoadCACert

func LoadCACert(caCertFile string) (*x509.Certificate, error)

LoadCACert loads and parses a single CA certificate (first PEM block).

func LoadCAFromFile

func LoadCAFromFile(path string) (*x509.CertPool, error)

LoadCAFromFile loads a PEM CA chain into a CertPool.

func LoadCert

func LoadCert(certFile, keyFile string) (*tls.Certificate, error)

LoadCert loads a TLS certificate key pair.

func LoadEvidenceRecord added in v0.2.0

func LoadEvidenceRecord(path string) (*semantics.DecisionRecord, error)

LoadEvidenceRecord reads a DSSE envelope written by FileSink and returns the decision record it carries, checking both the envelope (structure, subject binding) and the record (re-computation) on the way. It is the bridge from the emission side to the reporting side: an EvidenceBundle gets its authority from this record.

func LoadGatewayPolicy

func LoadGatewayPolicy(policyPath, sigSuffix string, opts *PolicyVerifyOptions, require bool) error

LoadGatewayPolicy loads and sets the gateway authorization policy. If authorization_file is configured, loads it (with signature verification); opts=nil skips signature verification. On successful load, sets the global policy via SetAuthorizationPolicy. On failure, if require=true returns an error; otherwise degrades by keeping the existing policy.

func LogAdmission

func LogAdmission(result AdmissionResult, clientIP string, logger *slog.Logger)

LogAdmission records the admission decision log.

func LogPluginDecision

func LogPluginDecision(logger *AuditLogger, entry PluginAuditEntry)

LogPluginDecision writes a plugin decision event to the audit log. Deny and execution errors are logged as WARN, allow as INFO. When entry.Level is empty, it is inferred from Decision ("allow"→INFO, others→WARN).

func MTLSServerConfig

func MTLSServerConfig(caCertFile string, cert *tls.Certificate, cipherSuites []string, minTLSVersion string) (*tls.Config, error)

MTLSServerConfig creates an mTLS server-side configuration.

func MarshalTSARequest

func MarshalTSARequest(req TimeStampReq) ([]byte, error)

MarshalTSARequest serializes a TSA request to DER.

func MaskCertSerial

func MaskCertSerial(serial string) string

MaskCertSerial masks a certificate serial number, keeping only the last 4 hex chars. Example: "A1:B2:C3:D4:E5:F6" → "**********E5:F6"

func MaskEmail

func MaskEmail(email string) string

MaskEmail masks an email address, keeping domain visible. Example: "alice@example.com" → "a***e@example.com"

func MaskFilePath

func MaskFilePath(path string) string

MaskFilePath masks the filename portion of a path, keeping the directory. Example: "/etc/pki/certs/secret.pem" → "/etc/pki/certs/********.pem"

func MaskString

func MaskString(s string, visible int) string

MaskString replaces all but the last `visible` characters with the mask rune. If the input is shorter than visible, it returns the input unchanged. If visible is 0, the entire string is masked. Returns empty string unchanged.

func MaskToken

func MaskToken(token string) string

MaskToken masks an API token or key, keeping only first and last 4 chars for long tokens, and proportionally fewer for short tokens so that at least half of the token is always masked (finding 21: a 9-char token must not reveal 8 chars). Example: "sk-abc123def456ghi789" → "sk-a*******i789"

func MatchCapability

func MatchCapability(id, pattern string) bool

MatchCapability checks whether a capability matches a pattern (supports * and a:b:* prefix).

func MatchCapabilityPriority

func MatchCapabilityPriority(id, pattern string) int

MatchCapabilityPriority checks whether id matches pattern and returns a five-level priority (pki.MatchPriorityExact .. MatchPriorityGlobal, 0 means no match). Semantics: capabilityId is segmented by ':', '*' matches a single segment, '**' crosses segments, priority: exact(5) > single-segment(4) > multi-segment(3) > scheme(2) > global(1).

func MatchCapabilityRules

func MatchCapabilityRules(id string, rules []pki.CapabilityRule) pki.CapabilityRuleMatch

MatchCapabilityRules makes a decision within a rule set (allow + deny) by priority: takes the highest-priority matching rule; at equal priority, deny takes precedence over allow. Returns Matched=false when no rule matches.

func NeedRevoke

func NeedRevoke(cert *x509.Certificate) bool

NeedRevoke checks whether a certificate needs proactive revocation.

func NewAdmissionEnvelope added in v0.2.0

func NewAdmissionEnvelope(rec AdmissionRecord) (semantics.Envelope, error)

NewAdmissionEnvelope wraps the record in the same DSSE/in-toto envelope the CLC records use, with a pipeline-level predicate type. A consumer that already speaks the envelope needs no new transport, only this predicate.

The statement's subjects are the facts the refusal rested on (client certificate DER, AIC extension, ...), so the envelope is bound to the same material an in-toto consumer would expect — matched purely by digest.

func NewOutcomeEnvelope added in v0.2.0

func NewOutcomeEnvelope(rec OutcomeRecord) (semantics.Envelope, error)

NewOutcomeEnvelope wraps the record in the same DSSE/in-toto envelope used by the decision and admission records; only the predicate type differs.

func NewReplayNonceStore

func NewReplayNonceStore(ttl time.Duration, max int) *memReplayStore

NewReplayNonceStore returns a process-local replay-protection store. Nonces are retained for ttl (default 24h) and at most max entries (default 65536). The store is intended for single-node gateways; multi-node deployments should share a distributed nonce store instead.

Capacity (P1-1): entries older than ttl (their token's replay window has lapsed) are purged lazily; a live marker younger than max token-lifetime is NEVER evicted, because eviction removes the one-time-use marker and lets the captured token become replayable again before it expires. When capacity is exhausted with only live markers remaining, CheckAndAdd fails closed and refuses to record the new nonce (the present request is denied) rather than trade replay protection for availability. The default of 65536 entries is sized far beyond the legitimate nonce rate of a single gateway, so the fail-closed path only triggers under pathological load or misuse; prefer a distributed store if capacity pressure is ever observed.

func NormalizeSerial

func NormalizeSerial(serial *big.Int) string

NormalizeSerial converts a certificate serial number to standard hex format (uppercase, no 0x prefix, zero-padded to 40 characters).

func OfflineLifetimeFor

func OfflineLifetimeFor(ocspFallback string) time.Duration

OfflineLifetimeFor returns the enforced offline limit based on the OCSP fallback policy. OCSPFallbackAllow (fail-open) and OCSPFallbackCRL (crl data can lag a recent revocation) → 1h cap; deny/disabled → 0 (not enforced; deny is fail-closed). Called by the gateway when constructing PipelineConfig.OfflineMaxCertLifetime.

func ParseCertPEM

func ParseCertPEM(data []byte) (*x509.Certificate, error)

ParseCertPEM parses the first certificate from PEM bytes.

func ParseCertPEMFile

func ParseCertPEMFile(path string) (*x509.Certificate, error)

ParseCertPEMFile reads and parses a PEM certificate file.

func ParsePrivateKeyPEM

func ParsePrivateKeyPEM(data []byte) (crypto.Signer, error)

ParsePrivateKeyPEM parses a PEM private key (PKCS#1/PKCS#8/EC/RSA).

func ParsePrivateKeyPEMFile

func ParsePrivateKeyPEMFile(path string) (crypto.Signer, error)

ParsePrivateKeyPEMFile reads and parses a PEM private key file.

func PeerCertRoles

func PeerCertRoles(r *http.Request) []string

PeerCertRoles extracts roles from the first peer certificate in the request's TLS connection. Returns nil if the request has no TLS or no peer certs.

func ProfileSubjectDigest added in v0.2.0

func ProfileSubjectDigest(env semantics.Envelope) (semantics.Digest, bool, error)

ProfileSubjectDigest returns the digest a consumer should look for when it wants to know which evidence shape a statement carries.

func RecorderSubjectDigest added in v0.2.0

func RecorderSubjectDigest(env semantics.Envelope) (semantics.Digest, bool, error)

RecorderSubjectDigest returns the digest of the recorder descriptor that emitted this envelope, when it carries one.

func RegisterConstraint

func RegisterConstraint(ev ConstraintEvaluator) error

RegisterConstraint registers a constraint evaluator in the global registry (extension point).

func RegisterGeoResolver

func RegisterGeoResolver(name string, fn GeoResolver)

RegisterGeoResolver registers a custom geographic resolver (extension point) for use in the geo-fence resolver mode (e.g. third-party geographic databases like ip2region). It is safe to call concurrently with evaluation.

func RegisterParameterValidator

func RegisterParameterValidator(v ParameterValidator) error

RegisterParameterValidator registers a validator in the built-in parameter boundary validator registry.

func RegisterPlugin

func RegisterPlugin(p CapabilityPlugin) error

RegisterPlugin registers a plugin in the global registry.

func ReplaceConstraint

func ReplaceConstraint(ev ConstraintEvaluator) error

ReplaceConstraint replaces an evaluator in the global registry (hot update extension point).

func RequireRoles

func RequireRoles(r *http.Request, allowedRoles []string) bool

RequireRoles checks whether the mTLS peer certificate in the request carries at least one of the given allowedRoles. It is a convenience wrapper around PeerCertRoles + CheckRole for HTTP handler middleware.

func ResetConstraints

func ResetConstraints()

ResetConstraints clears the global registry and re-registers built-in types (for testing only).

func ResetParameterValidators

func ResetParameterValidators()

ResetParameterValidators clears the built-in parameter boundary validator registry (testing only).

func ResetPlugins

func ResetPlugins()

ResetPlugins clears the global registry (testing only).

func SanitizeString

func SanitizeString(s string) string

SanitizeString removes non-printable characters and trims whitespace. Useful for sanitizing user input before logging.

func ServerTLSConfig

func ServerTLSConfig(cert *tls.Certificate, cipherSuites []string, minTLSVersion string) *tls.Config

ServerTLSConfig creates a server-side TLS configuration.

func SetAuthorizationPolicy

func SetAuthorizationPolicy(p *AuthorizationPolicy)

SetAuthorizationPolicy sets the global authorization policy.

func SetGlobalCapabilityRegistry

func SetGlobalCapabilityRegistry(cr CapabilityRegistry)

SetGlobalCapabilityRegistry sets the package-level default capability registry. Passing nil clears it (disables capability registration validation).

func SetIdentityHeaderMode

func SetIdentityHeaderMode(r *http.Request, mode IdentityHeaderMode)

SetIdentityHeaderMode is kept for the per-request (middleware) integration style. Security note: callers must only pass an identity mode derived from server configuration, never from a client-supplied header value; the reverse proxy reads its mode from Config.IdentityMode so clients cannot influence how much identity is disclosed to backends.

func SignPolicy

func SignPolicy(policyData []byte, cert *x509.Certificate, signer crypto.Signer) ([]byte, error)

SignPolicy uses an admin identity to create a PKCS#7 detached signature (SHA-256) for policy data, returning .sig DER. signer must be a crypto.Signer (supports RSA/ECDSA/Ed25519).

func SignerHasAdminOU

func SignerHasAdminOU(cert *x509.Certificate) bool

SignerHasAdminOU checks whether the signer certificate carries the admin OU.

func StartOCSPStapling

func StartOCSPStapling(tlsCert *tls.Certificate, cfg *tls.Config, caCertFile string, stopCh <-chan struct{}, translator Translator, lang string)

StartOCSPStapling starts the OCSP stapling background refresh.

func SubjectDigestByName added in v0.2.0

func SubjectDigestByName(env semantics.Envelope, name string) (semantics.Digest, bool, error)

SubjectDigestByName reads a named subject digest back out of an envelope, for the CLC statement and for the pipeline/outcome statements alike.

func SynthesizeCertFromJWT

func SynthesizeCertFromJWT(outer *aicjwt.OuterClaims) (*x509.Certificate, error)

SynthesizeCertFromJWT builds an X.509 certificate carrying the AIC claims of an AIC-JWT, so downstream certificate-based pipeline stages (CheckAdmission, capability matching, audit) work unchanged.

func TLSVersionFromString

func TLSVersionFromString(s string) uint16

TLSVersionFromString converts a version string to a TLS version number.

func ToGrant

func ToGrant(c Capability) (semantics.Grant, error)

ToGrant converts a declared capability (AIC or PrincipalAuthorization) into a CLC-v1 grant. The identifier grammar and params shape are validated, so a declaration that cannot be decided is reported instead of silently ignored.

func ToGrantSet

func ToGrantSet(caps []Capability) ([]semantics.Grant, error)

ToGrantSet converts a capability list into a CLC-v1 grant set.

func ValidateAIC

func ValidateAIC(aic *AIC) error

ValidateAIC delegates to pki-types.

func VerifyAuditEntry

func VerifyAuditEntry(data []byte, tsaClient *TSAClient) error

VerifyAuditEntry verifies the TSA timestamp signature of an audit entry.

func VerifyBundle

func VerifyBundle(bundle *CredentialBundle, roots *x509.CertPool) error

VerifyBundle verifies the credential bundle dual chain:

  • Agent chain → trust root (default client authentication EKU);
  • Principal chain → same trust root;
  • keyHash match: AIC.PrincipalUid.KeyHash == SHA256(Principal SPKI).

roots is the trust root pool; if empty, falls back to bundle.CACerts. Returns an error if any verification step fails.

func VerifyDelegationAuth

func VerifyDelegationAuth(aic *AIC, userCert *x509.Certificate) error

VerifyDelegationAuth verifies the validity of a DelegationAuthorization signature. aic must contain a non-empty DelegationAuthorization; userCert is the authorizing user's certificate. The signed content is the DelegationAuthTBS DER encoding (a specific subset, not the entire AIC).

func VerifyDelegationChain

func VerifyDelegationChain(chain []*x509.Certificate, topPrincipal *x509.Certificate, maxDepth int) error

VerifyDelegationChain is a convenience entry point: creates a default verifier and verifies the chain. chain goes from top to bottom: chain[0]=top-level delegating Agent, chain[len-1]=bottom-level Agent. maxDepth is set by the top Principal.

func VerifyDelegationChainWithCaps

func VerifyDelegationChainWithCaps(chain []*x509.Certificate, topPrincipal *x509.Certificate, principalCaps []pki.Capability, maxDepth, maxChainLen int) ([]pki.Capability, error)

VerifyDelegationChainWithCaps verifies a multi-level delegation chain and computes the effective capability intersection:

  • Basic signature verification (bottom-up per level) and depth limits;
  • Anti-cycle (serial number deduplication within chain);
  • Anti-certificate-bomb (chain length upper bound);
  • Per-level capability subset validation + C_eff recursive intersection.

maxDepth is set by the top Principal (≤0 means no limit); maxChainLen is the gateway-side hard upper bound (≤0 uses DefaultMaxChainLength). Returns C_eff = P ∩ C_1 ∩ … ∩ C_n.

func VerifyFnFromKey

func VerifyFnFromKey(pub crypto.PublicKey) func(keyID string, pae, sig []byte) error

VerifyFnFromKey returns the DSSE verification callback that VerifyEvidenceDir / VerifyEvidenceEnvelope expect, pinned to one trusted public key. It closes the "who trusts the record signer" question with one line instead of hand-rolled crypto per call site: the key passed here is the trust anchor, the unauthenticated keyid label is deliberately ignored.

ECDSA (P-256 / P-384 / P-521, raw r‖s or ASN.1 DER over SHA-256), RSA (PKCS#1 v1.5 / SHA-256) and Ed25519 (over the PAE itself) are supported, matching what RecordSigner produces.

func VerifyFnFromPublicKey added in v0.2.0

func VerifyFnFromPublicKey(pub *ecdsa.PublicKey) func(keyID string, pae, sig []byte) error

VerifyFnFromPublicKey is the ECDSA-specialised form of VerifyFnFromKey, kept for callers that already hold an *ecdsa.PublicKey.

func VerifyLayer2

func VerifyLayer2(chain []*x509.Certificate, cfg *PipelineConfig, roles []string) (AdmissionResult, *Layer2Result)

VerifyLayer2 performs Layer 2 representation verification: AIC parsing + PA parsing + delegation representation check (completed within CheckAdmission). Returns AdmissionResult and Layer2Result.

func VerifyPrincipalKeyHash

func VerifyPrincipalKeyHash(agent, principal *x509.Certificate) error

VerifyPrincipalKeyHash verifies that AIC.PrincipalUid.KeyHash matches the SHA-256 of the principal certificate's SPKI. Returns an error (fail-close) if keyHash is missing or empty.

func VerifyProof

func VerifyProof(leaf []byte, proof []ProofStep, root []byte) bool

VerifyProof verifies a Merkle audit proof.

func VerifyProofBounded

func VerifyProofBounded(leaf []byte, proof []ProofStep, root []byte, maxProofLen int) bool

VerifyProofBounded is like VerifyProof but enforces a maximum proof length (finding 22): a proof for a tree with n leaves has at most ceil(log2 n) steps, so any longer proof is rejected regardless of hash match.

func VerifySPIFFESAN

func VerifySPIFFESAN(cert *x509.Certificate, expectedID string) bool

VerifySPIFFESAN validates that a certificate carries the expected SPIFFE ID in its SAN URIs.

func VerifySignedPolicy

func VerifySignedPolicy(sigDER, policyData []byte, roots *x509.CertPool, requireAdminOU bool) (*x509.Certificate, error)

VerifySignedPolicy verifies a PKCS#7 detached signature (policyData is the raw policy bytes). Returns the signer certificate on success, for further OU/status checks by the caller.

Types

type AIC

type AIC = pki.AIC

── Type aliases ──

func ParseAIC

func ParseAIC(cert *x509.Certificate) (*AIC, error)

ParseAIC delegates to pki-types.

type AdmissionConfig

type AdmissionConfig struct {
	// RequireAIC when set to true rejects connections without AIC extension.
	RequireAIC bool
	// RequiredProtocol requires the Agent to have the specified protocol capability (empty = no check).
	RequiredProtocol string
	// RequiredRuleId requires the Agent to have the specified CapabilityId permission (empty = no check).
	RequiredRuleId string
	// RequiredCapabilities requires the Agent to have all specified CapabilityIds (empty = no check).
	RequiredCapabilities []string
	// Operations are the concrete actions (capability id + parameters) this
	// request wants to perform.  When set, each is decided with the CLC core
	// against the effective authority — the AIC capabilities intersected with
	// the PrincipalAuthorization grants — so parameter bounds take part in the
	// decision instead of matching capability ids alone.
	// Operation decisions are recorded in AdmissionResult.OperationDecisions so
	// upstream can surface verdict / reason / unresolved to downstream.
	Operations []Operation
	// UnresolvedEvaluator is the §8.4 residual-obligation release hook.  When
	// an operation's CLC decision lands on allow_unresolved (recognized but
	// not-evaluated constraint, e.g. time:window), the default is fail-closed
	// deny.  This evaluator can confirm those obligations out-of-band: returning
	// true releases the operation — the call site must still honor the residual
	// obligations (semantics.Decision.Unresolved) at runtime.  nil (default)
	// keeps fail-closed deny.
	UnresolvedEvaluator func(op Operation, unresolved []string) bool
	// DischargeObligations enables the strict consumer-side obligation rule of
	// CLC §8.4 with XACML 3.0 §2.13/§7.2.1: before an allow_unresolved decision
	// can be released, every obligation identity must be one this deployment
	// understands and can and will discharge (ObligationsUnderstood).  An
	// obligation it does not understand fails closed with obligation_unknown
	// instead of being handed to UnresolvedEvaluator as opaque text.  false
	// (default) keeps the legacy path, where UnresolvedEvaluator alone decides.
	DischargeObligations bool
	// ObligationsUnderstood lists the obligation (scheme,type) identities this
	// deployment understands and can and will discharge, e.g.
	// "varwof/constraint-v1:time".  Only consulted when DischargeObligations is
	// true.
	ObligationsUnderstood []string
	// RequireFreshDecisionContext requires the deployment to pin a freshness
	// context (RATS §10: explicit clock, nonce, or epoch id) and requires it to
	// still be fresh when an operation is allowed.  A missing, stale, or
	// future-dated context denies with a stable reason code; false (default)
	// keeps the pre-context behaviour.
	RequireFreshDecisionContext bool
	// DecisionContext is the pinned freshness input (semantics.DecisionContext).
	// Required when RequireFreshDecisionContext is set.
	DecisionContext *semantics.DecisionContext
	// DisallowRepresentative when set to true rejects DelegationRepresentative mode connections.
	DisallowRepresentative bool
	// RequireUserPermission when set to true rejects connections without UserPermission extension.
	RequireUserPermission bool
	// RejectOverflow when set to true rejects AIC containing CapabilityIds not authorized by UserPermission.
	RejectOverflow bool
	// RequireUserAuth when set to true requires DelegationAuthorization signature verification in the AIC.
	RequireUserAuth bool
	// EnforceCapSizeConstraints when set to true validates Capability field lengths (schemeId 1-128, capabilityId 1-256, parameters 0-4096).
	EnforceCapSizeConstraints bool
	// NonceCache is used for DelegationAuthorization nonce replay protection.
	// nil means skip nonce replay check (not recommended).
	NonceCache *NonceCache
	// EnforceSize32 when set to true validates Nonce is exactly 32 bytes.
	EnforceSize32 bool
	// UserCert is the authorized user's certificate for verifying DelegationAuthorization signatures.
	// When nil, if UserCertResolver is not nil, resolves via KeyHash from varwof-core;
	// otherwise falls back to the connection peer certificate (only agent == user can verify).
	UserCert *x509.Certificate
	// UserCertResolver resolves user certificates based on PrincipalUid.KeyHash (via varwof-core API).
	// Automatically called when UserCert is nil and RequireUserAuth is true.
	UserCertResolver func(keyHash []byte) (*x509.Certificate, error)
	// EnforceConstraints when set to true enforces authorizationConstraints (CIDR/time window/concurrency).
	EnforceConstraints bool
	// StrictConstraints when set to true directly rejects connections with unregistered constraint types
	// in authorizationConstraints (unknown capabilityId under constraint/constraint-v1 scheme),
	// fail-closed. Default false only logs audit warnings for unknown constraints and ignores them
	// (forward compatible, strict mode).
	StrictConstraints bool
	// ConstraintRegistry selects the constraint evaluator registry this
	// admission uses.  nil falls back to the package-global registry
	// (RegisterConstraint), so one process can host several gateways with
	// distinct constraint sets via per-Config registries.
	ConstraintRegistry *ConstraintRegistry
	// ClientIP is used for authorizationConstraints allowed-cidr checks.
	ClientIP string
	// AuditLogger is used for authorization decision audit logging. When non-nil, logs warnings
	// for unknown constraint types, etc.
	AuditLogger *AuditLogger
	// CheckDAAge when set to true validates DelegationAuthorization.timestamp freshness
	// (|now - timestamp| ≤ DAAgeMax). Default off — specification delegates lifecycle validation
	// to X.509 NotAfter (dev-docs/aic/06-delegation-auth.md §validation flow (gateway runtime));
	// deployments requiring stricter time window defense can enable this.
	CheckDAAge bool
	// DAAgeMax is the DA timestamp freshness window (|now - timestamp| ≤ DAAgeMax).
	// Only effective when CheckDAAge=true; <=0 uses DefaultDAAgeMax (30 seconds).
	DAAgeMax time.Duration
	// CredentialBundle is the client-submitted credential bundle (agent, principal and CA chains).
	// When RequireUserAuth is true and UserCert is nil, prioritizes the Principal certificate
	// from the credential bundle for DA signature verification (including keyHash cross-validation);
	// falls back to UserCertResolver when missing.
	CredentialBundle *CredentialBundle
}

AdmissionConfig is the configuration for the admission engine.

func (AdmissionConfig) Validate

func (c AdmissionConfig) Validate() error

Validate checks the configuration for conflicting options.

type AdmissionFact added in v0.2.0

type AdmissionFact struct {
	Type   string           `json:"type"`
	Digest semantics.Digest `json:"digest"`
	Note   string           `json:"note,omitempty"`
}

AdmissionFact is one content-addressed fact that decided the outcome: the client certificate, the AIC extension, the requested operations, and so on. Only digests travel here — the material stays where it was verified.

type AdmissionIdentity added in v0.2.0

type AdmissionIdentity struct {
	Principal string `json:"principal,omitempty"`
	AgentID   string `json:"agentId,omitempty"`
	Serial    string `json:"serial,omitempty"`
	SPIFFEID  string `json:"spiffeId,omitempty"`
}

AdmissionIdentity is who the request claimed to be, as far as the pipeline got.

type AdmissionRecord added in v0.2.0

type AdmissionRecord struct {
	Ver        string            `json:"ver"`
	Stage      string            `json:"stage"`
	Outcome    string            `json:"outcome"` // refused
	Reason     string            `json:"reason,omitempty"`
	RecorderID string            `json:"recorderId,omitempty"`
	At         time.Time         `json:"at"`
	Identity   AdmissionIdentity `json:"identity"`
	Facts      []AdmissionFact   `json:"facts,omitempty"`
}

AdmissionRecord says what the pipeline decided before (or instead of) reaching the language layer, and why. It never claims a CLC verdict.

func NewAdmissionRecord added in v0.2.0

func NewAdmissionRecord(ctx EvidenceContext, err *AuthError, facts []AdmissionFact) AdmissionRecord

NewAdmissionRecord builds a pipeline-level record for a refusal.

func ParseAdmissionEnvelope added in v0.2.0

func ParseAdmissionEnvelope(env semantics.Envelope) (AdmissionRecord, error)

ParseAdmissionEnvelope decodes and structurally checks a pipeline-level record.

func (AdmissionRecord) Digest added in v0.2.0

func (r AdmissionRecord) Digest() (semantics.Digest, error)

Digest identifies the record (over its canonical JSON).

type AdmissionResult

type AdmissionResult struct {
	Decision               DecisionResult
	Reason                 string
	AIC                    *AIC
	PrincipalAuthorization *PrincipalAuthorization
	PrincipalUid           string
	// EffectiveCaps is the P∩C (AIC declarations ∩ PA grants) intersection result,
	// preserving full Capability (including SchemeId/Parameters). When no PA is present,
	// equals the full AIC declarations. Phase two plugin evaluation only acts on this set
	// — declarations outside the intersection (including unrelated schemes) do not participate
	// in decisions or block connections (operation-level mapping).
	EffectiveCaps []Capability
	// OperationDecisions records the per-operation CLC verdicts when
	// cfg.Operations was set.  Populated on the allow path (and kept on the
	// deny path for the operation that caused it) so upstream can surface
	// verdict / reason / unresolved to downstream.
	OperationDecisions []OperationDecision
	// Sources is the authorization chain this admission can prove it rested on
	// (CLC SourceChain): the verified leaf certificate, plus the principal
	// certificate when configured.  Nil when no certificate was presented.
	Sources *semantics.SourceChain
}

AdmissionResult contains the complete admission check result.

func CheckAdmission

func CheckAdmission(cert *x509.Certificate, cfg AdmissionConfig) AdmissionResult
  1. Parse GatewaySession extension
  2. Verify AgentType is in the allowed list
  3. Check protocol capability match
  4. Check RuleId permission
  5. Check required capability subset
  6. Check delegation mode
  7. Parse UserPermission extension
  8. Check merge with UserPermission

Returns AdmissionResult; caller decides whether to allow based on the Decision field.

type AlgorithmIdentifier

type AlgorithmIdentifier = pki.AlgorithmIdentifier

── Type aliases ──

type ApprovalRequester

type ApprovalRequester interface {
	// Request asks a human to approve (or deny) the operation described by
	// risk.  A nil result or a denied/pending decision denies the request.
	Request(ctx context.Context, risk RiskAssessment) (*SupervisionResult, error)
}

ApprovalRequester is the mid-operation (runtime) human approval hook. It is invoked when the admission decision path decides a request needs on-site human approval. The default (nil) fails closed: the request is denied with approval_required.

type AuditAction

type AuditAction string

AuditAction represents the action type of an audit event.

const (
	// ActionConnected indicates the client has connected.
	ActionConnected AuditAction = "connected"
	// ActionDisconnected indicates the client has disconnected.
	ActionDisconnected AuditAction = "disconnected"
	// ActionDenied indicates the connection was rejected.
	ActionDenied AuditAction = "denied"
	// ActionRevoked indicates the certificate has been revoked.
	ActionRevoked AuditAction = "revoked"
	// ActionProxied indicates the proxy forwarding has been established.
	ActionProxied AuditAction = "proxied"
	// ActionCompleted indicates the proxy forwarding has completed.
	ActionCompleted AuditAction = "completed"
	// ActionNoRoute indicates no matching route was found.
	ActionNoRoute AuditAction = "no_route"
	// ActionWSConnect indicates a WebSocket has been connected.
	ActionWSConnect AuditAction = "ws_connect"
	// ActionWSClose indicates a WebSocket has been closed.
	ActionWSClose AuditAction = "ws_close"
	// ActionPluginDecision indicates a plugin decision has been executed.
	ActionPluginDecision AuditAction = "plugin_decision"
	// ActionUnknownConstraint indicates an unknown constraint type was ignored (forward compatibility).
	ActionUnknownConstraint AuditAction = "unknown_constraint"
)

type AuditChain

type AuditChain struct {
	// contains filtered or unexported fields
}

AuditChain manages a sequence of Merkle tree batches for audit trail integrity.

func NewAuditChain

func NewAuditChain(batchSize int, onSeal func(root []byte)) *AuditChain

NewAuditChain creates an audit chain.

func (*AuditChain) BatchCount

func (c *AuditChain) BatchCount() int

BatchCount returns the number of sealed batches.

func (*AuditChain) Dump

func (c *AuditChain) Dump() string

Dump exports a text summary of all batches.

func (*AuditChain) GetTree

func (c *AuditChain) GetTree(batchNumber int) *SealedTree

GetTree returns the sealed tree for a given batch number.

func (*AuditChain) LatestRoot

func (c *AuditChain) LatestRoot() string

LatestRoot returns the root hash (hex) of the most recent batch.

func (*AuditChain) LatestRootBytes

func (c *AuditChain) LatestRootBytes() []byte

LatestRootBytes returns the root hash (raw bytes) of the most recent batch.

func (*AuditChain) Seal

func (c *AuditChain) Seal(entries [][]byte, previousRoot string) *SealedTree

Seal seals a batch of audit entries into a Merkle tree.

func (*AuditChain) SealChecked

func (c *AuditChain) SealChecked(entries [][]byte, previousRoot string) (*SealedTree, error)

SealChecked seals a batch like Seal but enforces chain continuity (finding 8): when the chain is non-empty, previousRoot must equal the latest root; passing an empty previousRoot for a non-empty chain is refused. Tamper-evidence is only meaningful if every batch links to its predecessor.

func (*AuditChain) Verify

func (c *AuditChain) Verify(batchNumber int, leaf []byte, proof []ProofStep) (bool, error)

Verify verifies the audit proof for a given batch.

func (*AuditChain) VerifyContinuity

func (c *AuditChain) VerifyContinuity() error

VerifyContinuity checks that every batch (after the first) links to its predecessor's root, so a reordered/inserted/deleted batch is detected (finding 8).

func (*AuditChain) VerifyJSON

func (c *AuditChain) VerifyJSON(req *VerifyRequest) *VerifyResponse

VerifyJSON verifies an audit proof based on a JSON request.

type AuditEntry

type AuditEntry struct {
	Time           string   `json:"time"`
	Action         string   `json:"action"`
	SrcIP          string   `json:"src_ip"`
	ClientCN       string   `json:"client_cn,omitempty"`
	ClientSerial   string   `json:"client_serial,omitempty"`
	Roles          []string `json:"roles,omitempty"`
	Mapping        string   `json:"mapping"`
	Target         string   `json:"target"`
	TargetID       string   `json:"target_id,omitempty"`
	Duration       string   `json:"duration,omitempty"`
	DenyReason     string   `json:"deny_reason,omitempty"`
	BytesIn        int64    `json:"bytes_in,omitempty"`
	BytesOut       int64    `json:"bytes_out,omitempty"`
	TraceId        string   `json:"trace_id,omitempty"`
	SessionId      string   `json:"session_id,omitempty"`
	GatewayId      string   `json:"gateway_id,omitempty"`
	Protocol       string   `json:"protocol,omitempty"`
	AgentId        string   `json:"agent_id,omitempty"`
	SPIFFEID       string   `json:"spiffe_id,omitempty"`
	PrincipalUid   string   `json:"principal_uid,omitempty"`
	DelegationMode int      `json:"delegation_mode,omitempty"`
	Decision       string   `json:"decision,omitempty"`
	Capabilities   []string `json:"capabilities,omitempty"`
	// Level is the audit entry level (INFO/WARN/ERROR). Plugin decisions: allow=INFO,
	// deny/execution error=WARN.
	Level string `json:"level,omitempty"`
	// DaHash is the SHA-256 hex hash of the DelegationAuthorization signatureValue
	// (authorization evidence fingerprint, Task 4: binding authorization evidence to action records).
	DaHash string `json:"da_hash,omitempty"`
	// AICFingerprint is the SHA-256 hex of the AIC extension DER encoding (Task 4).
	AICFingerprint string `json:"aic_fingerprint,omitempty"`
	// PolicyVersion is the policy version effective at decision time (Task 5a: binding decision records to policy version).
	// 0 when PolicyManager is not enabled (omitempty omits from output).
	PolicyVersion uint64 `json:"policy_version,omitempty"`
	// RecordDigest is the input digest (hex) of the evidence record this
	// decision produced (refs[0].Digest).  It pins the audit line to the
	// replayable record chain so the two no longer drift apart; empty when the
	// decision produced no record.
	RecordDigest string `json:"record_digest,omitempty"`
}

AuditEntry is an audit log entry that records connection or decision events.

func NewAuditEntryDenied

func NewAuditEntryDenied(srcIP, mappingName, target, reason string, cert *x509.Certificate) AuditEntry

NewAuditEntryDenied creates an audit entry for a denied connection.

func NewAuditEntryFromConn

func NewAuditEntryFromConn(srcIP, mappingName, target string, cert *x509.Certificate) AuditEntry

NewAuditEntryFromConn creates an audit entry from connection information.

func ReadAuditEntries

func ReadAuditEntries(file string, filter AuditFilter) ([]AuditEntry, error)

ReadAuditEntries reads audit entries by filter.

func (*AuditEntry) SetV12Fields

func (e *AuditEntry) SetV12Fields(protocol, gatewayId, traceId, sessionId, decision string)

SetV12Fields sets the v1.2 spec extension fields for the audit log.

func (*AuditEntry) WithEvidenceFingerprints

func (e *AuditEntry) WithEvidenceFingerprints(cert *x509.Certificate) *AuditEntry

WithEvidenceFingerprints populates the audit entry's authorization evidence fingerprint fields (Task 4). Returns the original entry for method chaining.

type AuditFilter

type AuditFilter struct {
	Since    time.Time
	Until    time.Time
	Limit    int
	Offset   int
	Sort     string
	Action   string
	ClientCN string
	Serial   string
	Mapping  string
}

AuditFilter is the audit log query filter.

type AuditLogger

type AuditLogger struct {
	// contains filtered or unexported fields
}

AuditLogger is the audit log writer that writes in JSON Lines format.

func NewAuditLogger

func NewAuditLogger(file string, tsa *TSAClient, maxSize int64, maxBak int) (*AuditLogger, error)

NewAuditLogger creates an audit log writer (returns nil if file is empty).

func (*AuditLogger) Close

func (l *AuditLogger) Close() error

Close closes the audit log writer, draining buffered entries. It is idempotent: closing an already-closed logger returns nil instead of re-draining the closed channel (which would spin and write garbage forever).

func (*AuditLogger) Dropped

func (l *AuditLogger) Dropped() int64

Dropped returns the number of audit entries discarded because the buffer was full (M6). Data-plane Log calls must never block, so overflow is dropped and counted rather than stalling the caller.

func (*AuditLogger) File

func (l *AuditLogger) File() string

File returns the audit log file path.

func (*AuditLogger) Log

func (l *AuditLogger) Log(entry AuditEntry)

Log enqueues an audit entry. It never blocks the caller (M6): if the buffer is full the entry is dropped and counted. This prevents a slow audit sink (e.g. TSA) from stalling the data plane. Security-critical entries (WARN/ERROR, revocation/denial actions) are never dropped on the fast path: Log waits up to a bounded timeout for them so an attacker flooding the log cannot evict the evidence of its own activity (finding 15).

type AuditVerifier

type AuditVerifier struct {
	// contains filtered or unexported fields
}

AuditVerifier verifies audit log entries via TSA timestamps.

type AuthContext

type AuthContext struct {
	// ClientCert is the admitted client certificate (real mTLS peer or a
	// synthesized carrier for Bearer AIC-JWT).
	ClientCert *x509.Certificate
	// Principal is the AIC PrincipalUid string.
	Principal string
	// AgentID is the AIC AgentId.
	AgentID string
	// SPIFFEID is the SPIFFE ID from the certificate SAN (empty if none).
	SPIFFEID string
	// Roles are the extracted policy roles.
	Roles []string
	// Capabilities lists the admitted AIC capability ids.
	Capabilities []string
	// AIC is the parsed AIC extension (nil for non-AIC certificates).
	AIC *AIC
	// Bearer reports whether the request was authenticated by a JWT bearer.
	Bearer bool
	// Serial is the normalized client certificate serial.
	Serial string
	// Verdict is the overall CLC verdict for the requested operations (B3):
	// "allow" when every operation was authorized outright, "allow_unresolved"
	// when one or more carried §8.4 residual obligations (released or pending).
	// Empty when no Operations were configured.
	Verdict string
	// Reason is the aggregated CLC reason for the verdict.
	Reason string
	// Unresolved lists recognized-but-unevaluated constraints carried by an
	// allow_unresolved verdict (§8.4 residual-obligation channel).
	Unresolved []string
	// OperationDecisions is the per-operation CLC verdict detail (B3).
	OperationDecisions []OperationDecision
	// Evidence points at the decision records this admission produced (digest,
	// verdict and, for file-like sinks, where it was written).  Empty when
	// evidence emission is not configured.
	Evidence []RecordRef
	// Satisfaction is the evidence-requirement evaluation for this request
	// (nil when no requirement is configured): satisfied / violated / unknown,
	// with the per-constraint detail and the missing roles.  It answers "was
	// enough evidence presented", not "is this action authorized".
	Satisfaction *semantics.RequirementResult
}

AuthContext is the verified identity attached to the request context after a successful admission.

func AuthContextFromHeaders

func AuthContextFromHeaders(r *http.Request) *AuthContext

AuthContextFromHeaders reconstructs a minimal AuthContext from the identity headers the reverse proxy injects after admission (X-AIC-Agent-Id, X-AIC-Principal-Uid, X-AIC-Capabilities-Full, X-Agent-ID). It is meant for backends that run behind the aic-verifier proxy on a trusted network: the proxy strips this whole header namespace from client input and only re-emits values it verified, so a loopback-only backend may trust them. Returns nil when no identity headers are present, so callers can fail closed.

Capabilities are restored as full scheme:capabilityId identifiers into both Capabilities (the bare capability ids, as regular admission produces) and AIC.Capabilities (with SchemeId/CapabilityId split for FullID matching). The rest of the AuthContext (ClientCert, DA evidence, cross-signed chains) is not recoverable from headers and stays empty — authorization on such a backend must not depend on data the proxy did not forward.

func FromContext

func FromContext(ctx context.Context) *AuthContext

FromContext retrieves the verified AuthContext from a request context. Returns nil when the request did not pass through a aic-verifier middleware.

type AuthError

type AuthError struct {
	Code    ErrorCode
	Status  int
	Message string
	// Stage, when set, overrides the AdmissionRecord stage for this refusal
	// (the default is Code.String()).  The middleware's pre-language refusals
	// keep the default; the reverse proxy names the layer that refused
	// ("route_denied", "method_not_allowed", "capability_denied", ...) so one
	// proxy can tell its refusals apart.
	Stage string
	// Evidence points at the records a refused request still produced, so the
	// caller can log or forward them next to the challenge.
	Evidence []RecordRef
	// Problem, when set, is written as an RFC 9457 problem document instead of
	// the SDK's compact JSON error — the carrier for CLC-CHALLENGE-v1.
	Problem *ProblemDetails
	// Satisfaction, when the refusal is an unsatisfied evidence requirement, is
	// the machine-readable result (violated / unknown with the missing roles),
	// so a caller can act on *why* the evidence bar was not met without parsing
	// the challenge back out.
	Satisfaction *semantics.RequirementResult
}

AuthError is the typed admission failure.

func AsAuthError

func AsAuthError(err error) *AuthError

func (*AuthError) Error

func (e *AuthError) Error() string

type AuthMode

type AuthMode int

AuthMode selects which credential transports are accepted.

const (
	// MTLSOnly accepts requests authenticated by an mTLS client certificate.
	MTLSOnly AuthMode = iota
	// BearerOnly accepts requests authenticated by an Authorization: Bearer AIC-JWT.
	BearerOnly
	// MTLSOrBearer accepts either an mTLS client certificate or a Bearer AIC-JWT,
	// matching gateway-core semantics. mTLS takes precedence when both are present.
	MTLSOrBearer
)

type AuthorizationPolicy

type AuthorizationPolicy struct {
	Version           string                     `json:"version"`
	Roles             map[string]PolicyRole      `json:"roles"`
	OUMapping         map[string]string          `json:"ou_mapping"`
	GatewayNamespaces map[string]PolicyNamespace `json:"gateway_namespaces"`
	// CapabilityParameters is the parameter default values map derived by gen-authz from
	// capability.json. Key is "scheme:capability_id" (e.g., "varwof/gateway-v1:admin:config").
	CapabilityParameters map[string]map[string]any `json:"capability_parameters,omitempty"`
}

AuthorizationPolicy is the runtime model for the gateway authorization policy (authz.json). Structurally identical to varwof-core auth/policy.go, but kept as an independent implementation to maintain lib's zero new external dependency policy.

func GetAuthorizationPolicy

func GetAuthorizationPolicy() *AuthorizationPolicy

GetAuthorizationPolicy returns the current global authorization policy (may be nil).

func LoadAuthorizationPolicy

func LoadAuthorizationPolicy(policyPath, sigSuffix string, opts *PolicyVerifyOptions) (*AuthorizationPolicy, error)

LoadAuthorizationPolicy loads the authorization policy from a file. If opts is non-nil and the signature file (policyPath+sigSuffix) exists, signature verification is performed first.

func ParseAuthorizationPolicy

func ParseAuthorizationPolicy(data []byte) (*AuthorizationPolicy, error)

ParseAuthorizationPolicy parses the policy JSON.

func (*AuthorizationPolicy) HasGrant

func (p *AuthorizationPolicy) HasGrant(role, capability string) bool

HasGrant checks whether a role has a given capability (supports wildcards).

func (*AuthorizationPolicy) HasParamDefault

func (p *AuthorizationPolicy) HasParamDefault(scheme, capID, param string) (any, bool)

HasParamDefault checks whether a parameter has a default value (for overflow validation).

func (*AuthorizationPolicy) IntersectGrants

func (p *AuthorizationPolicy) IntersectGrants(roles []string, aicCapIds []string) []string

IntersectGrants returns the subset of aicCapIds that matches any role grants.

func (*AuthorizationPolicy) ParamDefaults

func (p *AuthorizationPolicy) ParamDefaults(scheme, capID string) map[string]any

ParamDefaults returns the parameter defaults for a given scheme:capability_id (gen-authz derived). Returns nil if not found.

func (*AuthorizationPolicy) RoleByOU

func (p *AuthorizationPolicy) RoleByOU(ou string) string

RoleByOU maps a certificate OU to a role name.

func (*AuthorizationPolicy) RoleGrants

func (p *AuthorizationPolicy) RoleGrants(role string) []string

RoleGrants returns the grants list for a role.

type CRLCache

type CRLCache struct {
	// contains filtered or unexported fields
}

CRLCache is a CRL cache that supports periodic refresh and forced reload.

func NewCRLCache

func NewCRLCache(caCert *x509.Certificate, url string, refreshSec int, translator Translator, lang string) *CRLCache

NewCRLCache creates a CRL cache instance.

func (*CRLCache) ForceRefresh

func (c *CRLCache) ForceRefresh() error

ForceRefresh forces an immediate CRL cache refresh.

func (*CRLCache) IsRevoked

func (c *CRLCache) IsRevoked(caDN string, serial *big.Int) (bool, error)

IsRevoked checks whether a given certificate serial number has been revoked.

func (*CRLCache) IsRevokedCert

func (c *CRLCache) IsRevokedCert(cert *x509.Certificate) (bool, error)

IsRevokedCert checks whether the given certificate is revoked, matching the certificate's issuer against this cache's CA robustly (finding 13): the raw issuer bytes are compared first, falling back to a structural RDN comparison that tolerates RDN ordering/formatting differences. A certificate not issued by this cache's CA is not covered by it and returns not-revoked.

func (*CRLCache) LastRefresh

func (c *CRLCache) LastRefresh() time.Time

LastRefresh returns the time of the last successful refresh.

func (*CRLCache) Start

func (c *CRLCache) Start(stop <-chan struct{})

Start starts the CRL periodic refresh loop.

func (*CRLCache) Stats

func (c *CRLCache) Stats() (int, time.Time, time.Time)

Stats returns CRL cache statistics (revocation count, this update, next update).

type CRLRevokedFunc

type CRLRevokedFunc func(caDN string, serial *big.Int) (bool, error)

CRLRevokedFunc reports whether the certificate serial has been revoked by the CA identified by caDN. An error must fail closed (the OCSP crl fallback cannot prove the certificate valid when the CRL cannot be consulted).

type Capability

type Capability = pki.Capability

── Type aliases ──

func ConstraintToCapability added in v0.2.0

func ConstraintToCapability(c string) (Capability, bool)

ConstraintToCapability is the inverse of ConstraintStrings: it maps a CLC constraint string back to the (scheme, capability, parameters) triple the connection-level registry evaluates. A string this SDK cannot map is reported as false so callers fail closed.

type CapabilityPlugin

type CapabilityPlugin = pki.CapabilityPlugin

CapabilityPlugin is the interface for all capability plugins.

type CapabilityRegistry

type CapabilityRegistry interface {
	// ValidateCapability validates the full identifier "scheme:capability_id".
	ValidateCapability(formatted string) error
	// Enabled reports whether the registry has been loaded.
	Enabled() bool
}

CapabilityRegistry is the capability registration validation interface (single source of truth). Gateway data plane validates AIC-declared capabilities against the registry during admission pipeline (RunAccessPipeline) phase one: unregistered scheme/capability is treated as an illegal declaration.

Injected by gateways (gateway-*/protocol modules): internally holds a register.Registry (embedded + disk override), atomically replaced after SIGHUP hot reload.

Returns nil when the capability is registered; returns an error when unregistered (caller rejects the connection).

func GetGlobalCapabilityRegistry

func GetGlobalCapabilityRegistry() CapabilityRegistry

GetGlobalCapabilityRegistry returns the current package-level capability registry (may be nil).

type ChallengeCarrier added in v0.2.0

type ChallengeCarrier interface {
	// Write renders problem as the response body.  The header may be modified
	// before the body is written; returning an error leaves the response
	// partially written and the caller falls back to a minimal JSON error.
	Write(w http.ResponseWriter, problem *ProblemDetails) error
}

ChallengeCarrier writes the HTTP response for a refusal that carries a challenge. The problem is the machine-readable "what is still needed"; the carrier decides how that is conveyed (RFC 9457 problem+json by default, but a deployment that routes through an aggregator may want its own shape). The SDK sets Retry-After from the challenge's retry lower bound before calling Write, so a non-standard carrier still keeps the bound visible. The carrier owns status, content type and body; Write must be safe for concurrent use.

var DefaultChallengeCarrier ChallengeCarrier = problemJSONCarrier{}

DefaultChallengeCarrier is the problem+json carrier used when a Config does not set ChallengeCarrier.

type ChallengeConfig added in v0.2.0

type ChallengeConfig struct {
	// TTL bounds how long the challenge may drive a retry.
	TTL time.Duration
	// Audience names the relying party the challenge is addressed to.
	Audience string
	// RetryAfter, when set, becomes the challenge's retry lower bound (and the
	// Retry-After header).  It exists because retrying a refusal can amplify
	// load: the client is told when a corrected presentation is welcome.
	RetryAfter time.Duration
	// ObtainHints tell the requester where the missing item can be obtained.
	ObtainHints []semantics.ObtainHint
	// Now, NewID and NewNonce are injectable for tests.  Defaults: time.Now,
	// and 16 random bytes hex-encoded (the nonce must be unpredictable; single
	// use of the retry is the enforcement point's business).
	Now      func() time.Time
	NewID    func() string
	NewNonce func() string
}

ChallengeConfig controls whether and how refusals carry a challenge.

type Config

type Config struct {
	// TLSCertFile / TLSKeyFile are the server certificate used to terminate TLS
	// (required for the reverse-proxy server in TLS/mTLS mode).
	TLSCertFile string
	TLSKeyFile  string

	// CACertFile is the CA bundle for mTLS client certificate verification.
	// Empty disables mTLS (Bearer-only or plaintext listeners).
	CACertFile string
	// JWTCAFile is a PEM file with the CA certificates used to build the AIC-JWT
	// trust root. Empty disables Bearer authentication.
	JWTCAFile string

	// BackendRootCA, when non-empty, is the root CA bundle (one or more PEM
	// files, comma/space separated) trusted for the reverse proxy's outbound
	// TLS to HTTPS backends. Appended to the system roots when set; empty uses
	// system roots only.
	BackendRootCA string
	// JWTIssuer, when non-empty, requires the AIC-JWT iss claim to match.
	JWTIssuer string
	// JWTAudience, when non-empty, requires the AIC-JWT aud claim to include one.
	JWTAudience []string
	// ReplayProtection enables one-time-use replay protection on bearer tokens
	// (default true).  When enabled every verified token's jti is single-use:
	// a compliant client mints one token per request (aic-agent local Key mode
	// does this); a pre-minted token shared across requests will be rejected as
	// a replay on its second use.  A token not minted per request and no
	// client-side per-request mint → set this to false.
	ReplayProtection *bool

	// IdentityMode selects how much verified client identity is forwarded to
	// backends (default IdentityForwardClientCert). This is fixed at startup
	// and MUST come from configuration — it is never read from a client-supplied
	// request header, so clients cannot downgrade the identity disclosed to a
	// backend.
	IdentityMode IdentityHeaderMode

	// AuthMode selects accepted credential transports (default MTLSOrBearer).
	AuthMode AuthMode

	// RequireAIC rejects clients whose certificate carries no AIC extension.
	RequireAIC bool
	// RequiredCapabilities requires the agent to hold all listed CapabilityIds.
	RequiredCapabilities []string
	// AdminToken, when non-empty, is the shared secret that must be presented
	// (Authorization: Bearer) to POST /reload on the AdminHandler.  Empty and
	// no AdminTokenFile → /reload is refused (fail-closed): the policy hot
	// reload seam is never left unauthenticated, so a listener that accidentally
	// exposes the admin mux cannot be used to install an arbitrary policy.
	AdminToken string
	// AdminTokenFile, when non-empty, loads AdminToken from a secrets file at
	// startup (trailing newline trimmed).  Precedence: AdminToken wins.
	AdminTokenFile string
	// RequiredOperations are the concrete actions (id + parameters) this
	// service authorizes.  Unlike RequiredCapabilities, parameter bounds are
	// part of the decision (CLC): an operation asking for more than the grant
	// allows is denied.
	RequiredOperations []Operation
	// UnresolvedEvaluator is the §8.4 residual-obligation release hook for
	// allow_unresolved CLC decisions; forwarded to PipelineConfig → AdmissionConfig.
	// Set programmatically — not parsed from JSON.
	UnresolvedEvaluator func(op Operation, unresolved []string) bool
	// DischargeObligations / ObligationsUnderstood enable the strict
	// consumer-side obligation rule (CLC §8.4 + XACML §2.13/§7.2.1); forwarded
	// to PipelineConfig → AdmissionConfig.  Set programmatically — not parsed
	// from JSON.
	DischargeObligations  bool
	ObligationsUnderstood []string
	// RequireFreshDecisionContext / DecisionContext pin the RATS §10 freshness
	// input for allowed operations; forwarded to PipelineConfig →
	// AdmissionConfig.
	RequireFreshDecisionContext bool
	DecisionContext             *semantics.DecisionContext
	// Challenges enables the CLC-CHALLENGE-v1 carrier: a denial caused by
	// unmet §8.4 residual obligations is answered with 403 +
	// application/problem+json carrying the challenge, and its retry lower
	// bound is mapped to Retry-After.  nil (default) keeps the compact JSON
	// error.
	Challenges *ChallengeConfig
	// ChallengeCarrier shapes the HTTP response for a refusal that carries a
	// challenge.  nil (default) uses the built-in RFC 9457
	// application/problem+json carrier.  A custom carrier may change the body
	// shape; the SDK still sets Retry-After from the challenge's retry bound
	// before delegating, so the lower bound survives regardless.
	ChallengeCarrier ChallengeCarrier
	// EvidenceRequirement is the relying party's evidence sufficiency bar
	// (CLC-REQUIREMENT-v1).  The SDK never takes it from the request: it is this
	// deployment's configuration.  When set, every admitted request is also
	// evaluated against it (via EvidenceFacts), a refusal carries the machine
	// readable challenge, and the emitted records bind the requirement digest.
	EvidenceRequirement *semantics.Requirement
	// EvidenceFacts supplies the evidence facts presented with a request.  The
	// SDK does not parse evidence artifacts; the deployment hands over facts its
	// own verifiers established (type, protected subject id, issuance time,
	// whether it reached VERIFIED).  An error is fail-closed.
	EvidenceFacts func(r *http.Request, ac *AuthContext) ([]semantics.EvidenceFact, error)
	// EvidenceProfile names the evidence shape this deployment emits, e.g.
	// "clc-decision+admission+outcome@1".  It is resolved at configuration time
	// (an unknown name is a configuration error) and fills the shape parts of
	// Evidence: which payloads are produced, whether records carry a freshness
	// context and the source chain, and which requirement is bound.  Changing
	// the emitted shape is a value here, not an edit to the decision path.
	EvidenceProfile string
	// Evidence, when set, freezes a CLC decision record for every decided
	// operation and hands it to a sink (structured log by default, or one DSSE
	// envelope per record on disk).  nil (default) emits nothing — a deployment
	// that only decides online pays no bytes.  Records are produced per source
	// (AIC capabilities, principal authorization); the combined verdict stays in
	// AuthContext.
	Evidence *EvidenceConfig
	// DisallowRepresentative rejects DelegationRepresentative-mode AIC.
	DisallowRepresentative bool
	// RequireUserAuth requires DelegationAuthorization signature verification.
	RequireUserAuth bool
	// EnforceConstraints enforces authorizationConstraints (CIDR / time window /
	// concurrency).
	EnforceConstraints bool

	// UserCert is the authorized user certificate for DA signature verification.
	// UserCertResolver resolves a user certificate by principal KeyHash when
	// RequireUserAuth needs DA signature verification. If both are nil, only
	// self-issued DA (agent == user) verifies.
	UserCert         *x509.Certificate
	UserCertResolver func(keyHash []byte) (*x509.Certificate, error)

	// CRLCache / OCSPCache plug in revocation checking. Nil disables that check.
	CRLCache  *CRLCache
	OCSPCache *OCSPCache

	// AuditLogger records admission decisions. Nil disables audit logging.
	AuditLogger *AuditLogger
	// NonceCache provides anti-replay protection for DA nonces. Nil disables it.
	NonceCache *NonceCache

	// PluginRegistry registers capability plugins consulted during admission.
	// Nil disables phase-one plugin decisions.
	PluginRegistry *PluginRegistry
	// CapabilityRegistry validates AIC-declared capabilities are registered.
	// Nil falls back to the global registry.
	CapabilityRegistry CapabilityRegistry
	// AuthorizationPolicy, when non-nil, selects the OU→role mapping this
	// gateway uses instead of the package-global policy (per-Config
	// isolation: several gateways in one process can hold distinct policies).
	// Nil falls back to SetAuthorizationPolicy's global.
	AuthorizationPolicy *AuthorizationPolicy
	// Constraints, when non-nil, selects the constraint evaluator registry this
	// gateway uses instead of the package-global registry (per-Config
	// isolation). Nil falls back to the global registry.
	Constraints *ConstraintRegistry
	// ParameterValidators, when non-nil, selects the parameter boundary
	// validator registry (e.g. MaxRowsValidator) this gateway uses.  Nil keeps
	// parameter boundary validation disabled (opt-in at the global default).
	ParameterValidators *ParameterValidatorRegistry

	// Logger is the structured logger (default slog.Default()).
	Logger *slog.Logger
	// LogFile, when non-empty, appends the SDK's own log output to this file
	// (created with 0644 if missing) instead of stdout.
	LogFile string
	// StreamBody when false (default) limits the copied evaluation body to
	// DefaultMaxBodyBytes. Services streaming large bodies should set this true.
	StreamBody bool

	// Hooks install lifecycle callbacks (see Hooks). This is the reserved
	// extension point for callers that need to observe or veto decisions.
	Hooks *Hooks

	// ApprovalRequester is the mid-operation runtime human approval hook.  A
	// nil requester denies requests flagged by RequireApproval with
	// approval_required.
	ApprovalRequester ApprovalRequester
	// OverrideRecorder records break-glass / override events.  A nil recorder
	// (together with AllowBreakGlass=true) is a startup error: an unlogged
	// break-glass is never available.
	OverrideRecorder OverrideRecorder
	// SupervisionPolicy gates runtime approval, break-glass and evidence
	// export (startup validation rules in newAuthenticator).
	SupervisionPolicy SupervisionPolicy
	// SupervisionStore persists supervision events (append-only JSONL).  Nil
	// skips event persistence; decision outcomes stay fail-closed regardless.
	SupervisionStore *SupervisionStore
	// EvidenceExporter exports evidence bundles for post-operation attribution.
	EvidenceExporter EvidenceExporter
	// RequireApproval, when non-nil, is consulted per request.  Returning true
	// routes the request through ApprovalRequester before it is admitted; nil
	// (or false) admits directly.  The same trigger point is reserved for the
	// LLM semantic gate.
	RequireApproval func(ctx *AuthContext, r *http.Request) bool

	// AuditLogFile / AuditTSAURL / SupervisionLogFile are the file-based
	// supervision inputs read from JSON configuration (see fileConfig).  They
	// are the configuration mirror of the SupervisionStore / audit chain: the
	// built-in EvidenceExporter (FileEvidenceExporter) reads them together
	// with the audit file to build evidence bundles.  Code-injected stores
	// (SupervisionStore, AuditLogger) take precedence when both are set.
	AuditLogFile       string
	AuditTSAURL        string
	SupervisionLogFile string

	// ServerOptions tunes the embedded http.Server used in proxy style
	// (Server.ListenAndServe). Zero values fall back to safe defaults.
	ServerOptions *ServerOptions
	// contains filtered or unexported fields
}

Config is the full aic-verifier configuration. All fields are optional; the zero value disables every check except the raw transport decision pipeline.

func LoadConfigFile

func LoadConfigFile(path string) (*Config, error)

LoadConfigFile reads a JSON configuration file into a Config. Unknown fields are rejected so typos surface as errors instead of silently ignored options.

func ParseConfig

func ParseConfig(data []byte) (*Config, error)

ParseConfig decodes JSON configuration bytes into a Config.

func (*Config) AuthMiddleware

func (c *Config) AuthMiddleware(next http.Handler) http.Handler

AuthMiddleware mirrors Handler but panics on an invalid config, so it can be used inline in http.Server{Handler: ...}. Use Handler when errors must be handled explicitly.

func (*Config) Close

func (c *Config) Close() error

Close releases every resource the Config owns: the nonce cache's cleanup goroutine, the supervision store, the audit logger (draining buffered entries), and the SDK log file. It is idempotent — each child close is itself idempotent — so Server.Close and DecisionServer.Close can cascade to it without the caller tracking which pieces were wired.

Close stops components the caller supplied through Config fields; it owns the *lifecycle*, not the memory. CRL and OCSP refresh loops are run by the caller's own stop channel (CRLCache.Start, StartOCSPStapling) and are therefore not cascaded here.

Config deliberately carries no lock so it stays copyable; the individual child closes provide the idempotency. Do not call Close concurrently with itself on the same Config.

func (*Config) CloseLogger

func (c *Config) CloseLogger() error

CloseLogger releases the file opened for LogFile (no-op when none set).

func (*Config) Handler

func (c *Config) Handler(next http.Handler) (http.Handler, error)

Handler builds a http.Handler that protects next with the AIC admission pipeline. On success the verified client identity is attached to the request context and pass-through headers are set on req.Header. When Evidence.EmitOutcome is set and next does not report outcomes itself (see outcomeSelfReporting), the middleware observes the downstream handler and reports the outcome (observed + status).

func (*Config) Health

func (c *Config) Health() HealthReport

Health builds the readiness report for the middleware path (Config.Handler / Config.AuthMiddleware), where no DecisionServer handle exists. It reads the same counter set the middleware records into (Config.Metrics), so a service embedding the middleware can serve liveness/readiness without constructing a DecisionServer.

func (*Config) Metrics

func (c *Config) Metrics() *DecisionMetrics

Metrics returns the admission counter set this Config records decisions into, creating it on first use. Middleware deployments (Config.Handler / Config.AuthMiddleware) that have no DecisionServer handle read their counters and readiness through here (see also Config.Health).

func (*Config) Validate

func (c *Config) Validate() error

Validate performs the static configuration checks that otherwise surface only at construction time. It is side-effect-free — no CA pools, JWT verifiers or material files are loaded — and safe to call from CI. Material loading still happens when a handler or server is built (Handler, NewServer).

type ConstraintContext

type ConstraintContext struct {
	// ClientIP is used for source-address-based constraints such as allowed-cidr / geo-fence.
	ClientIP string
	// Now is the evaluation time; defaults to current UTC time. Can be injected for testing and offline decisions.
	Now time.Time
}

ConstraintContext is the runtime context provided during constraint evaluation.

type ConstraintEvaluator

type ConstraintEvaluator interface {
	// CapabilityId returns the constraint type identifier handled by this evaluator.
	CapabilityId() string
	// Evaluate evaluates a single constraint. The cap's SchemeId has already been filtered
	// to constraint / constraint-v1 by the caller.
	Evaluate(cap *Capability, ctx *ConstraintContext) error
}

ConstraintEvaluator evaluates a single constraint from authorizationConstraints. Returns a non-nil error if the constraint is not satisfied; the gateway denies the connection.

type ConstraintRegistry

type ConstraintRegistry struct {
	// contains filtered or unexported fields
}

ConstraintRegistry registers/looks up constraint evaluators by capabilityId. It provides an extensible constraint type registration mechanism: when adding a new constraint type, only the corresponding evaluator needs to be registered without modifying the certificate ASN.1 structure or gateway core routing code.

func NewConstraintRegistry

func NewConstraintRegistry() *ConstraintRegistry

NewConstraintRegistry creates an empty registry.

func (*ConstraintRegistry) Find

func (r *ConstraintRegistry) Find(capabilityId string) (ConstraintEvaluator, error)

Find looks up an evaluator by capabilityId.

func (*ConstraintRegistry) Keys

func (r *ConstraintRegistry) Keys() []string

Keys returns the list of registered capabilityIds (for metrics/audit).

func (*ConstraintRegistry) Len

func (r *ConstraintRegistry) Len() int

Len returns the number of registered constraint evaluators.

func (*ConstraintRegistry) Register

Register registers a constraint evaluator. Returns an error if the same capabilityId is registered twice.

func (*ConstraintRegistry) Remove

func (r *ConstraintRegistry) Remove(capabilityId string)

Remove removes an evaluator. After removal, unknown types revert to the "unknown constraint" semantics (ignored by default).

func (*ConstraintRegistry) Replace

Replace atomically replaces a registered evaluator (for hot updates). Registers the evaluator if not already registered.

func (*ConstraintRegistry) Reset

func (r *ConstraintRegistry) Reset()

Reset clears the registry (for testing only).

type ContentInfo

type ContentInfo struct {
	ContentType asn1.ObjectIdentifier
	Content     asn1.RawValue `asn1:"explicit,tag:0"`
}

ContentInfo is CMS content information.

type CredentialBundle

type CredentialBundle struct {
	// AgentChain is the agent certificate chain containing AIC.
	// chain[0]=Agent, subsequent entries are intermediate/root CAs.
	AgentChain []*x509.Certificate
	// PrincipalChain is the principal certificate chain containing PA.
	// chain[0]=Principal (independently issued, not in the agent chain),
	// subsequent entries are intermediate CAs (optional).
	PrincipalChain []*x509.Certificate
	// CACerts are optional CA certs carried in the bundle for informational
	// purposes. They are never used as trust anchors (finding 20): callers must
	// supply operator-configured roots via VerifyBundle.
	CACerts []*x509.Certificate
}

CredentialBundle is the credential bundle submitted by the client (Agent certificate chain + Principal certificate chain + CA).

func NewCredentialBundle

func NewCredentialBundle(agentChain, principalChain, caCerts []*x509.Certificate) (*CredentialBundle, error)

NewCredentialBundle constructs a credential bundle. Returns an error if either chain is empty.

func ParseCredentialBundlePEM

func ParseCredentialBundlePEM(data []byte) (*CredentialBundle, error)

ParseCredentialBundlePEM parses a credential bundle from PEM data (order: agent chain first, principal second, CA chain last). Certificates are classified by extensions: AIC-containing → Agent chain, PA-containing → Principal chain, rest → CA. The parsed result must be verified via VerifyBundle before use.

func (*CredentialBundle) Agent

func (b *CredentialBundle) Agent() *x509.Certificate

Agent returns the Agent certificate (contains AIC, chain[0]).

func (*CredentialBundle) Principal

func (b *CredentialBundle) Principal() *x509.Certificate

Principal returns the Principal certificate (contains PA, chain[0]).

type DecideRequestDTO

type DecideRequestDTO struct {
	CertChainDER    [][]byte            `json:"cert_chain,omitempty"`
	BearerToken     string              `json:"bearer_token,omitempty"`
	TransportSecure bool                `json:"transport_secure,omitempty"`
	VerifiedCertDER []byte              `json:"verified_cert,omitempty"`
	Method          string              `json:"method,omitempty"`
	Path            string              `json:"path,omitempty"`
	RawQuery        string              `json:"raw_query,omitempty"`
	Headers         map[string][]string `json:"headers,omitempty"`
	ClientIP        string              `json:"client_ip,omitempty"`
	Body            []byte              `json:"body,omitempty"`
}

DecideRequestDTO is the wire form of a RequestView. Cert chains are DER bytes (leaf first, base64 through JSON). A carrier that already verified the credential sends verified_cert instead of a chain.

func (*DecideRequestDTO) ToView

func (d *DecideRequestDTO) ToView() (*RequestView, error)

ToView converts the wire request back into a RequestView for the decision core. The presented leaf (for refusal recording) is the first chain entry.

type DecideResultDTO

type DecideResultDTO struct {
	Granted            bool                         `json:"granted"`
	Code               string                       `json:"code,omitempty"`
	Status             int                          `json:"status,omitempty"`
	Message            string                       `json:"message,omitempty"`
	Principal          string                       `json:"principal,omitempty"`
	AgentID            string                       `json:"agent_id,omitempty"`
	SpiffeID           string                       `json:"spiffe_id,omitempty"`
	Roles              []string                     `json:"roles,omitempty"`
	Capabilities       []string                     `json:"capabilities,omitempty"`
	AICDER             []byte                       `json:"aic_der,omitempty"`
	Bearer             bool                         `json:"bearer,omitempty"`
	Serial             string                       `json:"serial,omitempty"`
	Verdict            string                       `json:"verdict,omitempty"`
	Reason             string                       `json:"reason,omitempty"`
	Unresolved         []string                     `json:"unresolved,omitempty"`
	OperationDecisions []OperationDecision          `json:"operation_decisions,omitempty"`
	Evidence           []RecordRef                  `json:"evidence,omitempty"`
	Satisfaction       *semantics.RequirementResult `json:"satisfaction,omitempty"`
	ClientCertDER      []byte                       `json:"client_cert_der,omitempty"`
}

DecideResultDTO is the wire form of an admission decision: the granted AuthContext mirror, or the denial (code/status/message). AIC travels as its ASN.1 DER so the document needs no aic-verifier type dependency to be inspected by peers.

func (*DecideResultDTO) FromAuthContext

func (d *DecideResultDTO) FromAuthContext(ac *AuthContext)

FromAuthContext fills the DTO from an admitted decision.

func (*DecideResultDTO) ToAuthContext

func (d *DecideResultDTO) ToAuthContext() (*AuthContext, error)

ToAuthContext reconstructs the decision's identity payload for peers that need the full AuthContext shape (certificate re-parsed from the DER). The reconstructed context is read-oriented: hooks and stores never travel.

type DecisionMetrics

type DecisionMetrics struct {
	StartedAt    time.Time
	DecideTotal  atomic.Uint64
	Granted      atomic.Uint64
	Denied       atomic.Uint64
	ConfigReload atomic.Uint64
	// contains filtered or unexported fields
}

DecisionMetrics is the light administration counter set of one decision server: admission totals plus reload/health bookkeeping, safe for concurrent use. A nil *DecisionMetrics is a no-op (zero-value authenticators do not count).

func NewDecisionMetrics

func NewDecisionMetrics() *DecisionMetrics

NewDecisionMetrics creates a counter set with its start clock.

func (*DecisionMetrics) Healthy

func (m *DecisionMetrics) Healthy() bool

Healthy reports whether the server is safe to direct new admission traffic at: everything served is used as-is; when the most recent decision failed it reports false so a load balancer can drain.

func (*DecisionMetrics) Snapshot

func (m *DecisionMetrics) Snapshot() MetricsSnapshot

Snapshot returns a consistent view of the counters.

type DecisionResult

type DecisionResult int

DecisionResult is the result of a connection admission decision.

const (
	// DecisionAllow means admission is allowed.
	DecisionAllow DecisionResult = iota
	// DecisionDeny means admission is denied.
	DecisionDeny DecisionResult = iota
	// DecisionNeedAuth means additional authentication is required.
	DecisionNeedAuth
)

type DecisionServer

type DecisionServer struct {
	// contains filtered or unexported fields
}

DecisionServer is the transport-independent admission entry point: build one per gateway (per Config), share it across HTTP, gRPC, queue and in-process carriers to guarantee they all make the identical decision. It is a thin wrapper over the same authenticator the HTTP middleware uses, so a single per-Config isolation applies across protocols.

func NewDecisionServer

func NewDecisionServer(c *Config) (*DecisionServer, error)

NewDecisionServer builds a DecisionServer from a Config (equivalent to the HTTP middleware's authenticator construction).

func (*DecisionServer) AdminHandler

func (s *DecisionServer) AdminHandler() http.Handler

AdminHandler serves the light administration endpoints on one mux:

GET  /healthz  readiness + counters (see Health)
GET  /readyz   alias of /healthz (Kubernetes convention)
GET  /health   alias of /healthz
GET  /metrics  counter snapshot
POST /reload   install a policy from {"policy": {…}} (validated, atomic)

Mount it yourself (e.g. a management listener); it never multiplexes with the admission handler.

func (*DecisionServer) Close

func (s *DecisionServer) Close() error

Close releases the config's owned resources (audit logger, nonce cache, supervision store, log file) via Config.Close. It is idempotent.

func (*DecisionServer) Decide

func (s *DecisionServer) Decide(ctx context.Context, view *RequestView) (*AuthContext, error)

Decide is the transport-neutral decision entry point.

func (*DecisionServer) Health

func (s *DecisionServer) Health(ctx context.Context) HealthReport

Health builds the readiness report for one decision server.

func (*DecisionServer) ReloadPolicy

func (s *DecisionServer) ReloadPolicy(p *AuthorizationPolicy) error

ReloadPolicy atomically installs a new authorization policy for the next admission. It validates the policy first; an invalid policy is rejected and the running snapshot keeps serving.

func (*DecisionServer) ReloadPolicyFromFile

func (s *DecisionServer) ReloadPolicyFromFile(policyPath, sigSuffix string, opts *PolicyVerifyOptions) error

ReloadPolicyFromFile loads and verifies a signed gateway policy (authz.json + detached PKCS#7 signature) and installs it like ReloadPolicy. opts nil skips signature verification (plain JSON policy), which is fine for the admin endpoint but not for production policy rotation.

type DelegationAuthTBS

type DelegationAuthTBS = pki.DelegationAuthTBS

── Type aliases ──

type DelegationAuthorization

type DelegationAuthorization = pki.DelegationAuthorization

── Type aliases ──

type DelegationChainVerifier

type DelegationChainVerifier struct {
	// MaxDepth is the maximum delegation depth allowed by the top Principal (including intermediate Agent B etc.).
	MaxDepth int
	// MaxChainLength is the hard upper limit to prevent certificate bomb attacks:
	// ≤0 means no extra limit (only constrained by MaxDepth).
	MaxChainLength int
}

DelegationChainVerifier verifies multi-level delegation chains (Zhang→Scheduler-A→Worker-B→…).

Multi-level delegation reuses the same DelegationAuthorization structure (spec dev-docs/aic/06-delegation-auth.md §Multi-level delegation chain, FUTURE reserved): each Agent's AIC contains a DelegationAuthorization signed by the previous level (delegator) certificate's private key. Verification proceeds bottom-up:

chain[i].AIC.DA signed by chain[i-1].cert → chain[i-1].AIC.DA signed by chain[i-2].cert
→ … → chain[0].AIC.DA signed by topPrincipal certificate

chainDepth is the number of delegation certificates in the chain (excluding the top Principal); maxDepth is set by the top Principal; exceeding it results in rejection. No new ASN.1 types needed; the entire chain is verifiable offline.

func (*DelegationChainVerifier) Verify

func (v *DelegationChainVerifier) Verify(chain []*x509.Certificate, topPrincipal *x509.Certificate) error

Verify verifies the delegation chain bottom-up starting from workerCert. chain is the certificate list from top to bottom: chain[0]=Scheduler-A (top-level delegating Agent), chain[len-1]=Worker-B (bottom-level Agent). Each level's AIC.DA is signed by the previous level's certificate.

Verification steps:

  1. Each certificate must contain an AIC with non-empty DA;
  2. Each level's AIC.DA signer = previous certificate (SPKI hash cross-validation);
  3. Top-level chain[0].AIC.DA signer = topPrincipal certificate;
  4. Chain depth (len(chain)) must ≤ MaxDepth;
  5. Entire chain verified offline, no external service dependency.

type DelegationMode

type DelegationMode = pki.DelegationMode

── Type aliases ──

const (
	DelegationAuthorized     DelegationMode = 0
	DelegationRepresentative DelegationMode = 1
)

DelegationMode values.

type DelegationPolicy

type DelegationPolicy = pki.DelegationPolicy

── Type aliases ──

type ErrorCode

type ErrorCode int

ErrorCode identifies the kind of admission failure.

const (
	ErrNoCredential ErrorCode = iota
	ErrDenied
	ErrNoVerifier
	ErrBearerNeedsTLS
	ErrInvalidBearer
	ErrChainInvalid
	ErrConfig
)

func ParseErrorCode

func ParseErrorCode(s string) (ErrorCode, error)

parseErrorCode maps a wire code string back to the ErrorCode enum.

func (ErrorCode) String

func (c ErrorCode) String() string

type EvidenceAuditChain

type EvidenceAuditChain struct {
	Entries    []EvidenceAuditEntry `json:"entries,omitempty"`
	MerkleRoot string               `json:"merkleRoot,omitempty"`
	Anchor     string               `json:"anchor,omitempty"` // self | tsa | externalLog
	TSA        string               `json:"tsa,omitempty"`
}

EvidenceAuditChain is the tamper-evidence chain (v0.1 §3 auditChain).

type EvidenceAuditEntry

type EvidenceAuditEntry struct {
	Seq       int64  `json:"seq"`
	Action    string `json:"action"`
	Actor     string `json:"actor"`
	Ts        string `json:"ts"`
	EntryHash string `json:"entryHash"`
	PrevHash  string `json:"prevHash"`
}

EvidenceAuditEntry is a chained audit line digest. prevHash of the first entry is empty.

type EvidenceAuthorization

type EvidenceAuthorization struct {
	DelegationMode string               `json:"delegationMode,omitempty"`
	Principal      string               `json:"principal,omitempty"`
	GrantRef       *EvidenceGrantRef    `json:"grantRef,omitempty"`
	Capabilities   []EvidenceCapability `json:"capabilities,omitempty"`
	Constraints    string               `json:"constraints,omitempty"`
	Ceiling        string               `json:"ceiling,omitempty"`
	ValidFrom      string               `json:"validFrom,omitempty"`
	ExpiresAt      string               `json:"expiresAt,omitempty"`
}

EvidenceAuthorization records who authorized and the boundary (v0.1 §3 authorization).

type EvidenceBundle

type EvidenceBundle struct {
	Manifest      EvidenceManifest           `json:"manifest"`
	Operation     EvidenceOperation          `json:"operation"`
	Subject       EvidenceSubject            `json:"subject"`
	Authorization EvidenceAuthorization      `json:"authorization"`
	Decision      EvidenceDecision           `json:"decision"`
	Supervision   []EvidenceSupervisionEvent `json:"supervision"`
	AuditChain    EvidenceAuditChain         `json:"auditChain"`
	Signatures    map[string]json.RawMessage `json:"signatures"`
}

EvidenceBundle is the exported evidence package. Field layout follows the v0.1 specification: manifest/operation/subject/authorization/decision/ supervision/auditChain/signatures.

func (*EvidenceBundle) CheckDecisions added in v0.2.0

func (b *EvidenceBundle) CheckDecisions() error

CheckDecisions verifies that the bundle's decision section agrees with the record it carries (when it carries one). A bundle without a record is not wrong, but it is a report — not replayable evidence.

func (*EvidenceBundle) JSON

func (b *EvidenceBundle) JSON() ([]byte, error)

JSON serializes the bundle as canonical JSON (keys sorted lexicographically at every level, no extraneous whitespace). The output is reproducible and safe to digest.

func (*EvidenceBundle) Render

func (b *EvidenceBundle) Render(w io.Writer, format RenderFormat) error

Render writes the bundle in the requested human-readable format (the empty format defaults to Markdown). It is a presentation view: the JSON bundle remains the canonical, verifiable artifact.

func (*EvidenceBundle) RenderCSV

func (b *EvidenceBundle) RenderCSV(w io.Writer) error

RenderCSV writes a flat section,field,value table. encoding/csv handles quoting, so a value with commas, quotes or newlines (actor names, paths) is emitted correctly for spreadsheets and forensic importers.

func (*EvidenceBundle) RenderMarkdown

func (b *EvidenceBundle) RenderMarkdown(w io.Writer) error

RenderMarkdown writes a sectioned Markdown document with one table per section. Cell values are escaped so an actor name or path containing a pipe or newline cannot break — or inject into — the table.

func (*EvidenceBundle) RenderText

func (b *EvidenceBundle) RenderText(w io.Writer) error

RenderText writes a plain-text rendering grouped by section.

func (*EvidenceBundle) Sign

func (b *EvidenceBundle) Sign(keyID string, sign func(pae []byte) ([]byte, error)) error

Sign appends a signature over PAE(BundlePayloadType, SigningBytes()) — the same pre-authentication encoding the record envelopes use, so one verifier covers both. keyID names the signing key (unauthenticated hint); entries are keyed by it, so signing twice with the same id replaces rather than stacks.

func (*EvidenceBundle) SigningBytes

func (b *EvidenceBundle) SigningBytes() ([]byte, error)

SigningBytes returns the canonical serialization of the bundle with the Signatures map emptied — the exact bytes a bundle signature covers. Emptying it first is what lets signatures not have to cover themselves.

func (*EvidenceBundle) VerifySignature

func (b *EvidenceBundle) VerifySignature(verify func(keyID string, pae, sig []byte) error) error

VerifySignature requires at least one bundle signature to verify over the signing bytes (PAE of the bundle without its signatures). A bundle with no signatures, or none that verify, fails — a bundle is evidence of who issued it only when key-endorsed. verify is a RecordSigner.VerifyFn / VerifyFnFromKey callback.

func (*EvidenceBundle) WriteRendered

func (b *EvidenceBundle) WriteRendered(path string, format RenderFormat) error

WriteRendered renders the bundle to a file, creating it with 0600 (evidence is not world-readable). It is the "download to a path an auditor can open" entry point.

type EvidenceCapability

type EvidenceCapability struct {
	Scheme       string `json:"scheme,omitempty"`
	CapabilityID string `json:"capabilityId"`
	Parameters   string `json:"parameters,omitempty"`
}

EvidenceCapability is a capability sub-set entry with parameter boundary summary.

type EvidenceConfig added in v0.2.0

type EvidenceConfig struct {
	// Sink receives every record.  Nil uses SlogSink with the SDK logger.
	Sink EvidenceSink
	// Strict makes an emission failure deny the request (fail-closed).  The
	// default (false) logs and continues: the evidence layer is opt-in, and a
	// broken sink should not silently take a service down — a deployment that
	// needs the record more than the request sets this true.
	Strict bool
	// TTL, when non-zero, pins a RATS §10.1 explicit-clock freshness input on the
	// record (At=now, MaxAgeSec=TTL) in addition to the per-admission nonce that
	// every record carries (RATS §10.2).  Zero pins no clock: the record's context
	// then identifies the admission instance by nonce alone.
	TTL time.Duration
	// Audience names the relying party the evidence is addressed to.
	Audience string
	// RecorderID identifies this admission point when several of them record
	// the same traffic.  It is a deployment property, not a language field.
	RecorderID string
	// Now is injectable for tests.
	Now func() time.Time
	// OnError is called when emission fails, so a deployment can count or page
	// on evidence gaps instead of only finding them in a log.  Nil logs at
	// error level.
	OnError func(ctx EvidenceContext, err error)
	// Gaps, when set, counts every emission that failed to leave a record.  It
	// makes "the evidence layer itself lost evidence" a queryable meter (not a
	// log-scan archaeology problem): gap events call OnError for paging and
	// increment Gaps for reporting.  Nil disables the counter.
	Gaps *GapCounter
	// Sign, when set, appends a DSSE signature over PAE(payloadType, payload) to
	// every emitted envelope — decision, admission and outcome alike.  nil
	// (.default) leaves records unsigned: content-recomputable, but carrying no
	// key that endorses "which admission point issued this".  The signing
	// primitive is caller-supplied (DSSE deliberately does not pick one); KeyID
	// is an unauthenticated hint, never trusted on its own.  A signing failure
	// follows the existing emission rules: OnError is called, and Strict denies
	// the request.
	//
	// Prefer Signer over Sign for new deployments: Signer carries its own key id
	// and public key, so verification needs no separate key exchange.  Sign is
	// kept for callers that already hold a signing closure.
	Sign  func(pae []byte) ([]byte, error)
	KeyID string
	// Signer, when set, key-endorses every emitted envelope and supersedes
	// Sign/KeyID.  Supplying a key is all it takes to sign — there is no
	// separate on-switch, because an unsigned compliance record is exactly the
	// gap this closes.  Load one from a PEM file with LoadRecordSignerFile, or
	// wrap an HSM/KMS with NewRecordSigner.
	Signer *RecordSigner
	// SignKeyFile, when set and Signer is nil, is a PEM private key (PKCS#1 /
	// PKCS#8 / SEC1; RSA, ECDSA or Ed25519) loaded once when the handler is
	// built and used with KeyID.  It is sugar for Signer =
	// LoadRecordSignerFile(SignKeyFile, KeyID).
	SignKeyFile string
	// RequireSignature, when true, refuses to build without a signing key
	// (Signer / Sign / SignKeyFile): a deployment whose evidence is worthless
	// unless key-endorsed fails closed at configuration time, not silently at
	// read time.
	RequireSignature bool
	// EmitOutcome, when true, makes the request path report an outcome record
	// after every admitted request: the reverse proxy observes the backend
	// (a response → observed + status; a transport failure → indeterminate) and
	// the middleware observes the downstream handler (a returned handler →
	// observed + status).  It is an opt-in effect-side channel: classification
	// of executed/failed stays with the deployment.  The decision, admission
	// and standing evidence budget are unaffected.
	EmitOutcome bool
	// Requirement, when set, is bound into each record as its requirement
	// digest: a record then says *which* sufficiency bar was applied, not just
	// what was decided.
	Requirement *semantics.Requirement
	// Profile is the declared evidence shape (EvidenceProfile).  When set, every
	// emitted envelope is tagged with the profile's content-addressed identity,
	// so a consumer can tell which shape it received.
	Profile *EvidenceProfile
	// Recorder, when set, is published as an `evidence-recorder` subject: a
	// consumer holding the descriptor can tell which admission point emitted a
	// record, and one that does not can still tell whether two records came from
	// the same recorder.  RecorderID alone stays a display hint.
	Recorder *RecorderDescriptor
	// contains filtered or unexported fields
}

EvidenceConfig turns on record emission at this admission point.

type EvidenceContainer added in v0.2.0

type EvidenceContainer string

EvidenceContainer names the envelope shape.

const (
	// ContainerDSSEInToto is the DSSE JSON envelope carrying an in-toto
	// Statement.  It is the only container implemented today.
	ContainerDSSEInToto EvidenceContainer = "dsse+in-toto"
)

type EvidenceContext added in v0.2.0

type EvidenceContext struct {
	// Facts are the content-addressed facts the outcome rested on; pipeline
	// records carry them into the statement's subjects.
	Facts       []AdmissionFact
	RecorderID  string
	OperationID string
	Method      string
	Path        string
	TraceID     string
	Principal   string
	AgentID     string
	Serial      string
	Outcome     EvidenceOutcome
	At          time.Time
}

EvidenceContext is what a sink needs to *use* a record: which request it came from, who it was about, and whether that request was admitted or refused. The record itself carries the decision; this carries the correlation.

type EvidenceDecision

type EvidenceDecision struct {
	// Decision mirrors the CLC verdict.  When Record is present this field is
	// derived from it (allow | allow_unresolved | deny) and MUST NOT disagree:
	// a bundle whose summary contradicts its record is not evidence.
	Decision string `json:"decision,omitempty"`
	// RecordDigest is the record's input digest (hex) — the stable identifier
	// of "which decision this bundle is about".
	RecordDigest string `json:"recordDigest,omitempty"`
	// Record is the CLC Decision Record itself: frozen inputs, verdict and
	// residual obligations, independently re-computable by any holder.  The
	// other fields in this section are a reader's summary of it; the record is
	// the authority.
	Record                *semantics.DecisionRecord `json:"record,omitempty"`
	MatchedPolicy         string                    `json:"matchedPolicy,omitempty"`
	ReasonCodes           []string                  `json:"reasonCodes,omitempty"`
	EvaluatedCapabilities []string                  `json:"evaluatedCapabilities,omitempty"`
	PdpContext            string                    `json:"pdpContext,omitempty"`
}

EvidenceDecision is the PDP/PEP decision record (v0.1 §3 decision).

func (*EvidenceDecision) ApplyRecord added in v0.2.0

func (d *EvidenceDecision) ApplyRecord(rec *semantics.DecisionRecord) error

ApplyRecord attaches a CLC decision record to this section and derives the summary fields from it. Deriving (rather than trusting the audit text) is what keeps the bundle from carrying a decision of its own.

type EvidenceExporter

type EvidenceExporter interface {
	// Export returns a self-contained, canonical-serializable evidence bundle.
	Export(ctx context.Context, q EvidenceQuery) (*EvidenceBundle, error)
}

EvidenceExporter produces an evidence bundle (evidence-bundle v0.1) for a query, merging the audit chain with supervision events.

type EvidenceFailure added in v0.2.0

type EvidenceFailure struct {
	Path   string `json:"path"`
	Reason string `json:"reason"`
}

EvidenceFailure names one file that did not verify and why.

type EvidenceGrantRef

type EvidenceGrantRef struct {
	Ref    string `json:"ref"`
	Digest string `json:"digest"`
}

EvidenceGrantRef is a reference + digest to the governing DA.

type EvidenceKind added in v0.2.0

type EvidenceKind string

EvidenceKind names which payload an envelope carries.

const (
	// KindDecision is a language-layer CLC decision record.
	KindDecision EvidenceKind = "clc-decision"
	// KindAdmission is a pipeline-level admission record.
	KindAdmission EvidenceKind = "aic-admission"
	// KindOutcome is an execution-boundary outcome record.
	KindOutcome EvidenceKind = "aic-outcome"
)

func CheckEvidenceEnvelope added in v0.2.0

func CheckEvidenceEnvelope(env semantics.Envelope) (EvidenceKind, error)

CheckEvidenceEnvelope validates either payload type through one entry point, dispatching on the statement's predicate type. A CLC record is checked by the language (structure, subject binding, re-computation); a pipeline record is parsed and shape-checked here.

func VerifyEvidenceEnvelope added in v0.2.0

func VerifyEvidenceEnvelope(env semantics.Envelope, verify func(keyID string, pae, sig []byte) error) (EvidenceKind, error)

VerifyEvidenceEnvelope validates an envelope of any of the three payload kinds. Structural shipping is always performed (dispatched on the statement's predicate type). When verify is non-nil, at least one signature must verify over the DSSE PAE — mirroring Envelope.VerifyRecord's multi-signature rule: the first signature that verifies wins, KeyID is passed through as an unauthenticated hint, and a tampered payload fails because its PAE no longer matches what was signed.

type EvidenceManifest

type EvidenceManifest struct {
	Schema     string    `json:"schema"`
	Version    string    `json:"version"`
	BundleID   string    `json:"bundleId"`
	ExportedAt time.Time `json:"exportedAt"`
	Generator  string    `json:"generator"`
	HashAlg    string    `json:"hashAlg"`
	PolicyRef  string    `json:"policyRef,omitempty"`
}

EvidenceManifest is the bundle's own metadata (v0.1 §3 manifest).

type EvidenceOperation

type EvidenceOperation struct {
	OperationID     string  `json:"operationId,omitempty"`
	StartedAt       string  `json:"startedAt,omitempty"`
	FinishedAt      string  `json:"finishedAt,omitempty"`
	Resource        string  `json:"resource,omitempty"`
	Action          string  `json:"action,omitempty"`
	RequestedParams Summary `json:"requestedParams,omitempty"`
	Outcome         string  `json:"outcome,omitempty"` // permit | deny | error
}

EvidenceOperation identifies the audited operation and its outcome (v0.1 §3 operation).

type EvidenceOutcome added in v0.2.0

type EvidenceOutcome string

EvidenceOutcome says what happened to the request the record belongs to.

const (
	// EvidenceAdmitted means the request was admitted.
	EvidenceAdmitted EvidenceOutcome = "admitted"
	// EvidenceRefused means the request was refused (the record says why).
	EvidenceRefused EvidenceOutcome = "refused"
)

type EvidenceProfile added in v0.2.0

type EvidenceProfile struct {
	// Name is the profile identifier, e.g. "clc-decision+admission+outcome@1".
	Name string
	// Container is the envelope shape.
	Container EvidenceContainer
	// Decision emits CLC decision records (the language layer).
	Decision bool
	// Admission emits pipeline-level records for refusals that never reached the
	// language layer.
	Admission bool
	// Outcome allows an execution-boundary sink to report effect records.
	Outcome bool
	// Sources includes the authorization source chain in decision records.
	Sources bool
	// Freshness pins a RATS §10 explicit clock on each record for this duration
	// (0 = no context).
	Freshness time.Duration
	// Requirement binds the relying party's sufficiency bar into each record.
	Requirement *semantics.Requirement
}

EvidenceProfile declares which evidence a deployment emits.

func LookupEvidenceProfile added in v0.2.0

func LookupEvidenceProfile(name string) (EvidenceProfile, error)

LookupEvidenceProfile resolves a named profile. An unknown name is an error: a deployment that asks for a shape we do not produce must hear about it at configuration time, not discover it later from missing records.

func (EvidenceProfile) Apply added in v0.2.0

Apply fills an EvidenceConfig from the profile. The plumbing (sink, strict, error hook, recorder id) stays with the deployment; the shape comes from here.

func (EvidenceProfile) ID added in v0.2.0

ID is the profile's identity, content-addressed over everything that shapes the emitted bytes. Two deployments that emit the same shape share an ID.

func (EvidenceProfile) Validate added in v0.2.0

func (p EvidenceProfile) Validate() error

Validate checks the profile's own consistency.

type EvidenceQuery

type EvidenceQuery struct {
	// OperationID is the preferred correlation key (server-side request id).
	OperationID string
	// DaHash correlates by delegation authorization fingerprint.
	DaHash string
	// AgentID correlates by agent identifier.
	AgentID string
	// TimeRange bounds the export window.  Nil means no time bound.
	TimeRange *TimeRange
	// IncludeSupervision merges supervision events into the bundle (default
	// false keeps the bundle audit-only).
	IncludeSupervision bool
	// Record is the CLC decision record this bundle is about.  When set, it is
	// attached to the bundle's decision section as the authority, and the
	// summary fields (Decision, ReasonCodes) are derived from it rather than
	// from the audit text.  Callers obtain it from the evidence sink (see
	// LoadEvidenceRecord).
	Record *semantics.DecisionRecord
}

EvidenceQuery selects the evidence to export for post-operation attribution.

type EvidenceSink added in v0.2.0

type EvidenceSink interface {
	// Emit receives a language-layer decision record.
	Emit(ctx EvidenceContext, rec semantics.DecisionRecord, env semantics.Envelope) (RecordRef, error)
	// EmitAdmission receives a pipeline-level record: a refusal that happened
	// before the language layer (credential, chain, revocation, missing
	// capability).  It is a separate method because it is a separate payload
	// type — a sink that only understands decisions can still implement it as
	// "store the envelope and move on".
	EmitAdmission(ctx EvidenceContext, rec AdmissionRecord, env semantics.Envelope) (RecordRef, error)
}

EvidenceSink receives a frozen decision record and its transport envelope, and reports where it kept it. Implementations must be safe for concurrent use.

type EvidenceSubject

type EvidenceSubject struct {
	AgentID                string `json:"agentId,omitempty"`
	KeyBinding             string `json:"keyBinding,omitempty"`
	TransportIdentity      string `json:"transportIdentity,omitempty"`
	PresentedCredentialRef string `json:"presentedCredentialRef,omitempty"`
}

EvidenceSubject records who executed (v0.1 §3 subject).

type EvidenceSupervisionEvent

type EvidenceSupervisionEvent struct {
	Type        string `json:"type"` // consent | denied | stepUp | approval | breakGlass | override
	OccurredAt  string `json:"occurredAt"`
	Actor       string `json:"actor"`
	DecisionRef string `json:"decisionRef,omitempty"`
	EvidenceRef string `json:"evidenceRef,omitempty"`
}

EvidenceSupervisionEvent is one human-intervention event (v0.1 §3 supervision). decisionRef mirrors the shared operation_id correlation key.

type EvidenceVerifyReport added in v0.2.0

type EvidenceVerifyReport struct {
	// Total is the number of record envelopes scanned.
	Total int
	// Decision / Admission / Outcome are the per-kind record counts.  Outcome
	// counts only outcomes whose decisionDigest resolved to a decision record
	// in the same directory: an orphan (unresolved) outcome is counted
	// separately and reported as a gap, never as consent.
	Decision  int
	Admission int
	Outcome   int
	// OrphanOutcome is the number of outcome records whose decisionDigest was
	// empty or did not resolve to a decision record in this directory.  Each
	// orphan is also listed in Failures with the file path, so a linkage gap is
	// as visible as a record that does not verify.
	OrphanOutcome int
	// Failures lists every file that did not verify: malformed JSON, an
	// envelope of an unknown predicate type, a decision that no longer
	// recomputes, an outcome whose decisionDigest does not resolve, or (when
	// verify was supplied) a record with no valid signature.  Empty means the
	// directory verified clean — and every outcome in it is linked.
	Failures []EvidenceFailure
}

EvidenceVerifyReport is the aggregated result of VerifyEvidenceDir.

func VerifyEvidenceDir added in v0.2.0

func VerifyEvidenceDir(dir string, verify func(keyID string, pae, sig []byte) error) (EvidenceVerifyReport, error)

VerifyEvidenceDir scans dir for evidence record envelopes and verifies each one, dispatching on its statement's predicate type: a CLC decision must re-compute, and an admission / outcome record must parse and be shape-invariant (see CheckEvidenceEnvelope). Outcomes are additionally checked for linkage: their decisionDigest must resolve to a decision record found in the same directory, or they are counted as orphans and listed in Failures — an outcome that cannot be tied to the decision it claims to have followed is a gap, not consent. When verify is non-nil, every envelope must also carry at least one signature that verifies over its DSSE PAE (see VerifyEvidenceEnvelope). A deployment that already trusts the emission point's key passes VerifyFnFromPublicKey here and the key question is settled without writing crypto.

The scan is best-effort per file: a broken or unrecognized file is collected in Failures, not returned as an error — an evidence directory with one bad record must be auditable, not unreadable. Only a failure to read the directory itself surfaces as (Report{}, err). A directory without records verifies clean (all-zero report).

type ExtField

type ExtField = pki.ExtField

── Type aliases ──

type ExternalPolicyRef

type ExternalPolicyRef = pki.ExternalPolicyRef

── Type aliases ──

type FileEvidenceExporter

type FileEvidenceExporter struct {
	// AuditFile is the audit JSON Lines file (SignedAuditEntry stream).
	AuditFile string
	// SupervisionFile is the supervision event JSON Lines file.  Empty means
	// supervision is unavailable; querying with IncludeSupervision then yields
	// no supervision events.
	SupervisionFile string
	// EvidenceDir is the evidence record directory (the FileSink directory).
	// When set and no Record was passed in the query, the exporter resolves the
	// anchor from the audit chain (record_digest), loads the decision record
	// envelope by digest and attaches it to the bundle.  A digest that resolves
	// to nothing is a gap: the export fails instead of silently degrading to a
	// report-only bundle.
	EvidenceDir string
	// Generator is the bundle.generator label; empty defaults to the package
	// version.
	Generator string
	// Signer, when set, key-endorses every exported bundle: the package itself
	// carries a signature over its canonical bytes (sans signatures), so a
	// holder can verify the export was produced by this key, not assembled by
	// hand.  nil leaves the bundle unsigned (a report).
	Signer *RecordSigner
	// Now overrides the export clock (tests).
	Now func() time.Time
}

FileEvidenceExporter is the built-in minimal exporter: it reads the audit chain (SignedAuditEntry JSONL) and optionally the supervision event store, filters them by EvidenceQuery, and builds an evidence-bundle v0.1 with a Merkle root over the exported line digests.

func NewFileEvidenceExporter

func NewFileEvidenceExporter(auditFile, supervisionFile string) *FileEvidenceExporter

NewFileEvidenceExporter builds the built-in exporter over the given audit and supervision JSONL files.

func (*FileEvidenceExporter) Export

Export implements EvidenceExporter.

type FileSink added in v0.2.0

type FileSink struct {
	Dir string
	// RecorderID is prefixed to the file name when set.
	RecorderID string
	// contains filtered or unexported fields
}

FileSink writes one DSSE envelope per record, named by its input digest so the same decision recorded twice overwrites itself instead of accumulating.

func (*FileSink) Emit added in v0.2.0

Emit implements EvidenceSink.

func (*FileSink) EmitAdmission added in v0.2.0

func (s *FileSink) EmitAdmission(ctx EvidenceContext, rec AdmissionRecord, env semantics.Envelope) (RecordRef, error)

EmitAdmission implements EvidenceSink for pipeline-level records; the file name carries an "admission-" marker so the two payload types never collide.

func (*FileSink) EmitOutcome added in v0.2.0

func (s *FileSink) EmitOutcome(ctx EvidenceContext, rec OutcomeRecord, env semantics.Envelope) (RecordRef, error)

EmitOutcome implements OutcomeSink for pipelines that want every artifact on disk; the file name carries an "outcome-" marker so the payload types never collide.

type GapCounter added in v0.2.0

type GapCounter struct {
	// contains filtered or unexported fields
}

GapCounter is an atomically counted evidence-gap meter: how often an emission that was supposed to leave a record failed to do so. Safe for concurrent use from any number of admission points.

func (*GapCounter) Count added in v0.2.0

func (g *GapCounter) Count() int64

Count returns how many emissions failed to leave a record since the counter was created.

func (*GapCounter) Inc added in v0.2.0

func (g *GapCounter) Inc()

Inc registers one failed emission.

type GeoResolver

type GeoResolver func(ip string) (string, error)

GeoResolver resolves a geographic region identifier (e.g. "CN-SHA") from a source IP. Registered with the geo-fence evaluator to make region resolution pluggable (built-in inline table; third-party databases like ip2region can self-register).

type HTTPFacts

type HTTPFacts = pki.HTTPFacts

HTTPFacts carries per-request HTTP facts for capability plugins.

type HealthReport

type HealthReport struct {
	OK            bool            `json:"ok"`
	Module        string          `json:"module"`
	StartTime     string          `json:"start_time"`
	UptimeSeconds int64           `json:"uptime_seconds"`
	LastErrorAt   string          `json:"last_error_at,omitempty"`
	LastOKAt      string          `json:"last_ok_at,omitempty"`
	Metrics       MetricsSnapshot `json:"metrics"`
}

HealthReport is the /healthz payload: module identity, readiness and the counters so one curl shows both liveness and the decision mix.

type Hooks

type Hooks struct {
	// Authenticated runs on every admitted request, before the upstream handler
	// (middleware style) or backend proxy (proxy style) is invoked. Returning a
	// non-nil error denies the request with 403 and the error text is logged.
	Authenticated func(ctx *AuthContext, r *http.Request) error

	// Denied runs when a request is rejected by the admission pipeline. err is
	// the typed *AuthError carrying the error code and HTTP status.
	Denied func(r *http.Request, err *AuthError)

	// Forwarded runs after an admitted request returned from the backend (proxy
	// style only). resp is non-nil when the upstream responded; it is nil when
	// the reverse proxy failed before a response was produced.
	Forwarded func(r *http.Request, resp *http.Response)
}

Hooks are optional lifecycle callbacks a service operator can plug into the request pipeline. Every hook is invoked synchronously; nil hooks are skipped. This is the reserved extension point for callers that need to observe or veto AIC decisions without writing a full capability plugin.

type IdentityHeaderMode

type IdentityHeaderMode int

IdentityHeaderMode selects how much identity detail is forwarded to backends.

const (
	// IdentityMinimal strips every identity header (backend trusts the proxy only).
	IdentityMinimal IdentityHeaderMode = iota
	// IdentityForwardClientCert forwards the full client certificate (DER) plus
	// the X-AIC-* structured views.
	IdentityForwardClientCert
	// IdentityXForwarded sets only X-Forwarded-Client-* (CN/O/OU/serial).
	IdentityXForwarded
	// IdentityAIC only sets the X-AIC-* structured headers.
	IdentityAIC
)

type JWTVerifier

type JWTVerifier struct {
	// contains filtered or unexported fields
}

JWTVerifier verifies AIC-JWT bearer tokens against a trust root built from CA certificates (same kid convention as the X.509 carrier: base64url SHA-256 of the certificate SPKI). On success it returns a synthesized X.509 certificate carrying the token's AIC extension, so the existing pipeline (RunAccessPipeline / CheckAdmission) admits a bearer request exactly like a certificate-authenticated one.

A bare NewJWTVerifier only verifies the token signature/expiry. For production use the gateway must call SetBearerPolicy (issuer/audience binding + replay protection) and pass per-request proof-of-possession via VerifyBearer options; without those the bearer is replayable until exp by any holder (finding 5).

func LoadJWTVerifier

func LoadJWTVerifier(caFiles ...string) (*JWTVerifier, error)

LoadJWTVerifier reads one or more PEM CA certificate files (comma or space separated paths) and builds a JWT verifier from them. An empty spec returns a nil verifier (bearer auth disabled).

func NewJWTVerifier

func NewJWTVerifier(cas []*x509.Certificate) *JWTVerifier

NewJWTVerifier builds a verifier from CA certificates. kid for each CA is base64url(SHA-256(SubjectPublicKeyInfo)) — the same binding core publishes on /.well-known/jwks.json.

func (*JWTVerifier) SetBearerPolicy

func (v *JWTVerifier) SetBearerPolicy(expectedIssuer string, expectedAudience []string, nonces aicjwt.NonceStore)

SetBearerPolicy installs the static bearer-token policy applied to every verification: the expected issuer, acceptable audiences, and a replay nonce store. Configure all three for production; leaving issuer/audience empty or the nonce store nil keeps those checks off (finding 5).

func (*JWTVerifier) VerifyBearer

func (v *JWTVerifier) VerifyBearer(token string, now time.Time, opts ...JWTVerifyOptions) (*x509.Certificate, *aicjwt.OuterClaims, error)

VerifyBearer validates a Bearer AIC-JWT and returns a synthesized certificate carrying the AIC claims, plus the raw outer claims. opts carry per-request checks (proof-of-possession, revocation); the verifier's static policy (issuer/audience/replay) is always applied.

type JWTVerifyOptions

type JWTVerifyOptions struct {
	// ExpectedIssuer, when non-empty, requires outer.iss == ExpectedIssuer.
	ExpectedIssuer string
	// ExpectedAudience, when non-empty, requires the token aud to include one.
	ExpectedAudience []string
	// PresenterKey, when non-nil, enforces cnf proof-of-possession: the token
	// must be bound to this public key (e.g. the mTLS peer cert key).
	PresenterKey crypto.PublicKey
	// NonceStore, when non-nil, provides one-time-use replay protection on the
	// DA nonce (finding 5).
	NonceStore aicjwt.NonceStore
	// RequireJtiNonceMatch requires outer.jti == DA nonce.
	RequireJtiNonceMatch bool
	// StatusChecker, when non-nil, checks issuer/principal for revocation.
	StatusChecker aicjwt.StatusChecker
}

JWTVerifyOptions carries the runtime verification parameters for a single bearer token. These activate the checks Validate supports but that a bare call leaves unset (finding 5).

type Layer1Result

type Layer1Result struct {
	Verified bool
	Reason   string
	Roles    []string
}

Layer1Result is the Layer 1 identity verification result (Agent Cert + CA Chain + RBAC).

func VerifyLayer1

func VerifyLayer1(chain []*x509.Certificate, cfg *PipelineConfig) *Layer1Result

VerifyLayer1 performs Layer 1 identity verification: certificate chain validity (validity period) + RBAC roles (AllowRoles matching). Cryptographic certificate chain verification is done by the TLS layer (VerifyPeerCertificate); this layer performs application-level identity checks (validity period + roles). Returns roles for use by subsequent layers.

type Layer2Result

type Layer2Result struct {
	Verified               bool
	Reason                 string
	AIC                    *AIC
	PrincipalAuthorization *PrincipalAuthorization
	PrincipalUid           string
}

Layer2Result is the Layer 2 representation verification result (+ Principal Cert + PA).

type Layer3Result

type Layer3Result struct {
	Verified bool
	Reason   string
}

Layer3Result is the Layer 3 online authorization verification result (OCSP/CRL/Policy Server).

func VerifyLayer3

func VerifyLayer3(chain []*x509.Certificate, cfg *PipelineConfig) *Layer3Result

VerifyLayer3 performs Layer 3 online authorization verification: CRL/OCSP revocation freshness check + optional PolicyServer online policy check.

type MerkleTree

type MerkleTree struct {
	// contains filtered or unexported fields
}

MerkleTree is a Merkle hash tree used for tamper-proof audit chains.

func NewMerkleTree

func NewMerkleTree(leaves [][]byte) *MerkleTree

NewMerkleTree creates a Merkle tree from leaf data.

func (*MerkleTree) Proof

func (m *MerkleTree) Proof(leafIndex int) ([]ProofStep, error)

Proof computes the audit proof path for a given leaf index.

func (*MerkleTree) Root

func (m *MerkleTree) Root() []byte

Root returns the Merkle tree root hash.

func (*MerkleTree) RootHex

func (m *MerkleTree) RootHex() string

RootHex returns the root hash as a hex-encoded string.

type MessageImprint

type MessageImprint struct {
	HashAlgorithm AlgorithmIdentifier
	HashedMessage []byte
}

MessageImprint is a message digest imprint.

type MetricsSnapshot

type MetricsSnapshot struct {
	UptimeSeconds int64  `json:"uptime_seconds"`
	DecideTotal   uint64 `json:"decide_total"`
	Granted       uint64 `json:"granted"`
	Denied        uint64 `json:"denied"`
	ConfigReloads uint64 `json:"config_reloads"`
}

MetricsSnapshot is the serializable counter set (/metrics payload).

type NonceCache

type NonceCache struct {
	// contains filtered or unexported fields
}

NonceCache provides a nonce replay protection cache (v1.4 §3.2). Thread-safe with automatic cleanup of expired entries.

func NewNonceCache

func NewNonceCache() *NonceCache

NewNonceCache creates a NonceCache and starts automatic cleanup (hourly, retaining entries within 24h).

func (*NonceCache) CheckAndAdd

func (nc *NonceCache) CheckAndAdd(scope string, nonce []byte) bool

func (*NonceCache) Len

func (nc *NonceCache) Len() int

Len returns the current number of nonces in the cache (for testing and monitoring only).

func (*NonceCache) Stop

func (nc *NonceCache) Stop()

Stop stops the background cleanup goroutine. It is idempotent: calling it more than once (e.g. Config.Close after an explicit Stop) does not panic on a double channel close.

type OCSPCache

type OCSPCache struct {
	// contains filtered or unexported fields
}

OCSPCache is an OCSP response cache with request coalescing.

func NewOCSPCache

func NewOCSPCache(ttl time.Duration, fallback string, translator Translator, lang string) *OCSPCache

NewOCSPCache creates an OCSP cache instance.

func (*OCSPCache) Check

func (c *OCSPCache) Check(cert, issuer *x509.Certificate) error

Check queries the OCSP responder and caches the result.

func (*OCSPCache) Flush

func (c *OCSPCache) Flush()

Flush clears the OCSP cache.

func (*OCSPCache) SetCRLChecker

func (c *OCSPCache) SetCRLChecker(fn CRLRevokedFunc)

SetCRLChecker installs the CRL revocation lookup used by the "crl" OCSP fallback. It must be set for OCSPFallbackCRL to fail closed instead of silently allowing (finding 3).

func (*OCSPCache) Stats

func (c *OCSPCache) Stats() (int, int)

Stats returns the count of good/revoked entries in the OCSP cache.

type OfflineRBAC

type OfflineRBAC struct {
	// contains filtered or unexported fields
}

OfflineRBAC provides offline RBAC decision capability for degraded scenarios where the core is unreachable. Injected with a role list extracted from certificates or configuration at construction time via NewOfflineRBAC.

func NewOfflineRBAC

func NewOfflineRBAC(roles []string) *OfflineRBAC

NewOfflineRBAC creates an offline RBAC instance storing the specified roles.

func NewOfflineRBACFromCert

func NewOfflineRBACFromCert(cert *x509.Certificate) *OfflineRBAC

NewOfflineRBACFromCert creates an offline RBAC instance by extracting roles from a certificate's OU.

func (*OfflineRBAC) CheckRole

func (r *OfflineRBAC) CheckRole(allowed []string) bool

CheckRole checks whether roles contains at least one role from allowed. If roles contains gateway:*, it passes immediately. Supports gateway:* wildcard in allowed.

type Operation

type Operation = semantics.Operation

Operation is a concrete action to authorize: a capability id plus the parameters the caller wants to use.

type OperationDecision

type OperationDecision struct {
	ID         string         `json:"id"`
	Params     map[string]any `json:"params,omitempty"`
	Verdict    string         `json:"verdict"` // semantics.VerdictAllow / VerdictDeny / VerdictAllowUR
	Reason     string         `json:"reason,omitempty"`
	Unresolved []string       `json:"unresolved,omitempty"`
	// Grants are the effective grant set the operation was decided over: the
	// AIC capability set on the delegated path, the principal's own grants on
	// the direct path, each already carrying the connection's authorization
	// constraints.  Surfacing them here means a downstream consumer can recompute
	// the verdict without parsing an evidence record file.
	Grants []semantics.Grant `json:"grants,omitempty"`
	// Released is true when an allow_unresolved operation was admitted because
	// the deployment's UnresolvedEvaluator confirmed the residual obligations.
	Released bool `json:"released,omitempty"`
}

OperationDecision records the CLC verdict for one requested operation, so admission results and the AuthContext can expose verdict / reason / unresolved (spec B3) instead of only the final allow/deny.

type OutcomeRecord added in v0.2.0

type OutcomeRecord struct {
	Ver     string    `json:"ver"`
	Outcome string    `json:"outcome"`
	At      time.Time `json:"at"`
	// DecisionDigest points back at the decision record this outcome followed
	// (hex of that record's input digest).  Empty means the linkage was not
	// established — which a consumer should treat as a gap, not as consent.
	DecisionDigest string `json:"decisionDigest,omitempty"`
	// OperationID is the action this outcome is about.
	OperationID string `json:"operationId,omitempty"`
	// RecorderID identifies the execution boundary that reported it.
	RecorderID string `json:"recorderId,omitempty"`
	// StatusCode is the transport-level result when there was one (HTTP).
	StatusCode int `json:"statusCode,omitempty"`
	// Note is a bounded, deployment-supplied description.
	Note string `json:"note,omitempty"`
	// Identity is who executed it, as the boundary established it.
	Identity AdmissionIdentity `json:"identity,omitempty"`
	// Facts are the digests of the material the outcome rests on.
	Facts []AdmissionFact `json:"facts,omitempty"`
}

OutcomeRecord says what the execution boundary observed, and which decision it followed. It is evidence about the effect, not about authority.

func ParseOutcomeEnvelope added in v0.2.0

func ParseOutcomeEnvelope(env semantics.Envelope) (OutcomeRecord, error)

ParseOutcomeEnvelope decodes and shape-checks an outcome record.

func (OutcomeRecord) Digest added in v0.2.0

func (r OutcomeRecord) Digest() (semantics.Digest, error)

recordDigest identifies the outcome record.

type OutcomeSink added in v0.2.0

type OutcomeSink interface {
	EmitOutcome(ctx EvidenceContext, rec OutcomeRecord, env semantics.Envelope) (RecordRef, error)
}

OutcomeSink receives execution-boundary records. A deployment that has no execution boundary simply does not configure one.

type OverrideRecorder

type OverrideRecorder interface {
	// Record persists a break-glass / override event.  It does not authorize
	// the operation by itself.
	Record(ctx context.Context, risk RiskAssessment, actor, reason string) (*SupervisionResult, error)
}

OverrideRecorder is the break-glass / override recording point. The SDK only provides the record point: whether break-glass is allowed is the operator's policy (SupervisionPolicy.AllowBreakGlass). When policy allows break-glass and the recorder is nil, startup validation fails, so an unlogged break-glass is never available.

type PKIStatusInfo

type PKIStatusInfo struct {
	Status int
}

PKIStatusInfo is PKI status information.

type ParameterValidator

type ParameterValidator interface {
	// Scheme returns the schemeId associated with this validator.
	Scheme() string
	// Validate checks whether declared (agent/certificate declaration) falls within
	// granted (principal authorization boundary). Returns a non-nil error if out of bounds
	// (includes the specific parameter name and boundary).
	Validate(granted, declared Capability) error
}

ParameterValidator validates whether a capability declaration's parameters fall within the authorized boundary. Implemented per schemeId (one Scheme → one validator).

var MaxRowsValidator ParameterValidator = maxRowsValidator{}

MaxRowsValidator is an exported instance of the max_rows parameter boundary validator (for registration: NewParameterValidatorRegistry().Register(MaxRowsValidator)).

type ParameterValidatorRegistry

type ParameterValidatorRegistry struct {
	// contains filtered or unexported fields
}

ParameterValidatorRegistry manages registration and lookup of parameter boundary validators.

func BuiltinParameterValidators

func BuiltinParameterValidators() *ParameterValidatorRegistry

BuiltinParameterValidators returns the built-in parameter boundary validator registry.

func NewParameterValidatorRegistry

func NewParameterValidatorRegistry() *ParameterValidatorRegistry

NewParameterValidatorRegistry creates an empty parameter boundary validator registry.

func (*ParameterValidatorRegistry) Find

Find looks up the parameter boundary validator for the given schemeId.

func (*ParameterValidatorRegistry) Keys

func (r *ParameterValidatorRegistry) Keys() []string

Keys returns all registered scheme IDs.

func (*ParameterValidatorRegistry) Len

Len returns the number of registered validators.

func (*ParameterValidatorRegistry) Register

Register registers a parameter boundary validator.

func (*ParameterValidatorRegistry) Reset

func (r *ParameterValidatorRegistry) Reset()

Reset clears the registry.

func (*ParameterValidatorRegistry) ValidateCapability

func (r *ParameterValidatorRegistry) ValidateCapability(granted, declared Capability) error

ValidateCapability validates a capability declaration against the authorized boundary using the given registry. Unregistered schemes are allowed (no parameter boundary rules; capability-level intersection was already performed upstream).

type PermissionDef

type PermissionDef = pki.PermissionDef

── Type aliases ──

type PermissionLevel

type PermissionLevel = pki.PermissionLevel

── Type aliases ──

const (
	PermissionAuto             PermissionLevel = 0
	PermissionRequiresApproval PermissionLevel = 1
)

PermissionLevel constants.

type PipelineCheck

type PipelineCheck int

PipelineCheck is the certificate chain check scope type.

const (
	CheckFullChain PipelineCheck = iota
	CheckLeafOnly
)

CheckFullChain/CheckLeafOnly are certificate chain check scope constants.

type PipelineConfig

type PipelineConfig struct {
	// CRLCache is the CRL cache instance.
	CRLCache *CRLCache
	// OCSPCache is the OCSP cache instance.
	OCSPCache *OCSPCache
	// AllowRoles is the list of allowed RBAC roles.
	AllowRoles []string
	// CheckScope controls the CA scope check mode.
	CheckScope PipelineCheck
	// MaxConnsPerCert is the maximum connections per certificate.
	MaxConnsPerCert int
	// RequireAIC requires the client to hold an AIC certificate.
	RequireAIC bool
	// RequireSPIFFE requires the client certificate to carry a SPIFFE ID
	// SAN URI. When set, connections without a SPIFFE ID are rejected.
	RequireSPIFFE bool
	// AllowedSPIFFEIDs is an optional exact-match allowlist of SPIFFE IDs.
	// Empty means no allowlist restriction.
	AllowedSPIFFEIDs []string
	// SPIFFETrustDomain when non-empty requires the client SPIFFE ID to
	// belong to this trust domain (e.g. "varwof.com").
	SPIFFETrustDomain string
	// RequiredProtocol is the transport protocol required by the client.
	RequiredProtocol string
	// RequiredRuleId is the required matching route rule ID.
	RequiredRuleId string
	// RequiredCapabilities is the list of capabilities the client must possess.
	RequiredCapabilities []string
	// Operations are the concrete actions (capability id + parameters) this
	// request wants to perform.  When set, each one is decided with the CLC
	// core against the effective authority — the AIC capabilities intersected
	// with the PrincipalAuthorization grants — so parameter bounds take part in
	// the decision instead of matching capability ids alone.
	Operations []Operation
	// UnresolvedEvaluator is the §8.4 residual-obligation release hook for
	// allow_unresolved CLC decisions; forwarded to AdmissionConfig (see
	// AdmissionConfig.UnresolvedEvaluator).
	UnresolvedEvaluator func(op Operation, unresolved []string) bool
	// DischargeObligations / ObligationsUnderstood enable the strict
	// consumer-side obligation rule; forwarded to AdmissionConfig (see
	// AdmissionConfig.DischargeObligations).
	DischargeObligations  bool
	ObligationsUnderstood []string
	// RequireFreshDecisionContext / DecisionContext pin the RATS §10 freshness
	// input; forwarded to AdmissionConfig (see
	// AdmissionConfig.RequireFreshDecisionContext).
	RequireFreshDecisionContext bool
	DecisionContext             *semantics.DecisionContext
	// DisallowRepresentative disallows delegated representative mode.
	DisallowRepresentative bool
	// RequireUserPermission requires user authorization signature.
	RequireUserPermission bool
	// RejectOverflow rejects when connection limit is exceeded.
	RejectOverflow bool
	// RequireUserAuth requires user authentication.
	RequireUserAuth bool
	// EnforceCapSizeConstraints enforces capability size constraints.
	EnforceCapSizeConstraints bool
	// EnforceSize32 enforces the 32-byte size constraint.
	EnforceSize32 bool
	// CapabilityPluginRegistry is the capability plugin registry.
	CapabilityPluginRegistry *PluginRegistry
	// CapabilityRegistry is the capability registration validation (single source of truth).
	// When non-nil, phase one performs registration validation on AIC-declared capabilities:
	// unregistered → reject connection.
	// nil means disabled (backward compatible).
	CapabilityRegistry CapabilityRegistry
	// CapabilityPluginResolver selects a capability plugin registry by agent identifier
	// (task 5b: branch control/canary).
	// When non-nil, it takes precedence over CapabilityPluginRegistry: the returned registry
	// is used for phase one plugin evaluation, and the returned version number (if non-zero)
	// overrides PolicyVersion for audit binding. Agents matching a branch use the branch
	// version policy; others fall back to the currently active version.
	CapabilityPluginResolver func(agentID string) (version uint64, reg *PluginRegistry)
	// AuditLogger is the audit log recorder.
	AuditLogger *AuditLogger
	// NonceCache is the anti-replay nonce cache.
	NonceCache *NonceCache
	// ClientIP is the client IP address for authorization constraint checks.
	ClientIP string
	// UserCert is the authorized user's certificate, used for DelegationAuthorization
	// signature verification.
	UserCert *x509.Certificate
	// UserCertResolver resolves a user certificate by PrincipalUid.KeyHash
	// (fetched via varwof-core API). Automatically called when UserCert is nil.
	UserCertResolver func(keyHash []byte) (*x509.Certificate, error)
	// EnforceConstraints, when true, enforces authorizationConstraints.
	EnforceConstraints bool
	// StrictConstraints, when true, fails-closed on unknown constraint types.
	StrictConstraints bool
	// AuthorizationPolicy, when non-nil, selects the OU→role mapping this
	// pipeline uses instead of the package-global policy.  nil falls back to
	// SetAuthorizationPolicy's global.
	AuthorizationPolicy *AuthorizationPolicy
	// ConstraintRegistry, when non-nil, selects the constraint evaluator
	// registry this pipeline uses instead of the package-global registry.
	ConstraintRegistry *ConstraintRegistry
	// ParameterValidators is the parameter boundary validator registry.
	// When non-nil, after the P∩C intersection, parameters of AIC declarations and PA
	// authorizations are compared one by one against the boundary; out-of-bounds → reject.
	ParameterValidators *ParameterValidatorRegistry
	// PolicyServer is the Layer 3 online authorization policy server.
	// Called by VerifyLayer3/VerifyTrustLayers; not used by RunAccessPipeline.
	PolicyServer PolicyServer
	// CredentialBundle is the client-submitted credential bundle (agent, principal and CA chains).
	// When RequireUserAuth is enabled, the Principal certificate is preferentially extracted
	// from the credential bundle for DA signature verification.
	CredentialBundle *CredentialBundle
	// PolicyVersion is the policy version effective at decision time (task 5a: decision
	// records bound to policy version).
	// Filled by the gateway from PolicyManager.CurrentVersion() when constructing PipelineConfig;
	// when 0, the audit entry omits this field.
	PolicyVersion uint64
	// RiskMonitor is the high-risk behavior monitor (2026-08-15). When non-nil, the pipeline
	// automatically records violation signals at behavioral rejection points (parameter overflow /
	// plugin deny / CIDR out-of-bounds); once a rule threshold is reached, the gateway's
	// injected OnAction callback executes kick + revocation.
	RiskMonitor *RiskMonitor
	// OfflineMaxCertLifetime is the maximum remaining certificate validity enforced in offline
	// mode (G2(b)).
	// When >0 (e.g., 1h): revocation checks use fail-open (OCSP fallback=allow / CRL unreachable)
	// in offline scenarios; client certificates with remaining validity exceeding this value are
	// rejected — prevents offline fail-open windows from diluting "short-lived certificate"
	// semantics with long-lived certificates. 0 = not enforced.
	OfflineMaxCertLifetime time.Duration
	// HTTPFacts carries per-request HTTP facts (method/path/query/
	// headers) that are copied into the PluginContext for capability
	// plugins. nil means no HTTP facts (TCP/TLS admission path).
	HTTPFacts *HTTPFacts
}

PipelineConfig is the unified admission pipeline configuration.

type PipelineResult

type PipelineResult struct {
	Granted    bool
	DenyReason string
	Roles      []string
	Principal  string
	Serial     string
	AgentId    string
	// SPIFFEID is the SPIFFE ID extracted from the client certificate SAN
	// URI (empty when the certificate carries no SPIFFE URI).
	SPIFFEID string
	// AIC is the AIC extension carried by the admitted connection (parsed result). G3 long-lived
	// connection periodic review requires its AuthorizationConstraints.
	AIC *AIC
	// PrincipalAuthorization is the principal authorization extension carried by the connection
	// (source of the P∩C intersection).
	PrincipalAuthorization *PrincipalAuthorization
	// CLCVerdict / CLCReason / CLCUnresolved aggregate the per-operation CLC
	// decisions when cfg.Operations was set: "allow" when every requested
	// operation was authorized outright, "allow_unresolved" when one or more
	// carried §8.4 residual obligations released by the deployment's
	// UnresolvedEvaluator.  Empty when no operations were configured.
	CLCVerdict    string
	CLCReason     string
	CLCUnresolved []string
	// OperationDecisions is the per-operation CLC verdict detail (B3).
	OperationDecisions []OperationDecision
}

PipelineResult is the admission pipeline execution result.

func RunAccessPipeline

func RunAccessPipeline(chain []*x509.Certificate, cfg *PipelineConfig) *PipelineResult

RunAccessPipeline executes the unified admission pipeline (CRL→OCSP→RBAC→decision engine).

func VerifyTrustLayers

func VerifyTrustLayers(chain []*x509.Certificate, cfg *PipelineConfig) *PipelineResult

VerifyTrustLayers executes three-layer trust verification in combination (L1 → L2 → L3), equivalent to the explicit layered entry point of RunAccessPipeline. Returns denial on any layer failure.

type PluginAuditEntry

type PluginAuditEntry struct {
	Scheme       string `json:"scheme"`
	CapabilityID string `json:"capability_id"`
	Decision     string `json:"decision"`
	Reason       string `json:"reason"`
	ClientCN     string `json:"client_cn,omitempty"`
	Principal    string `json:"principal,omitempty"`
	// Level is the audit level (INFO/WARN). Allow→INFO, deny/execution error→WARN.
	// Empty value defaults to INFO.
	Level string `json:"level,omitempty"`
	// DaHash is the SHA-256 hash of the DelegationAuthorization signatureValue
	// (Task 4: plugin decision entries also bind authorization evidence fingerprints).
	DaHash string `json:"da_hash,omitempty"`
	// PolicyVersion is the policy version effective at decision time (Task 5a).
	PolicyVersion uint64 `json:"policy_version,omitempty"`
}

PluginAuditEntry records audit fields for plugin decision events.

type PluginContext

type PluginContext = pki.PluginContext

PluginContext is the context during plugin execution.

type PluginDecision

type PluginDecision = pki.PluginDecision

PluginDecision represents the decision result after plugin execution.

const (
	PluginAllow  PluginDecision = pki.PluginAllow
	PluginDeny   PluginDecision = pki.PluginDeny
	PluginBypass PluginDecision = pki.PluginBypass
)

PluginAllow/Deny/Bypass are plugin decision constants.

type PluginRegistry

type PluginRegistry = pki.PluginRegistry

PluginRegistry manages plugin registration and lookup.

func NewPluginRegistry

func NewPluginRegistry() *PluginRegistry

NewPluginRegistry creates a new empty registry.

type PluginResult

type PluginResult = pki.PluginResult

PluginResult is the return result after plugin execution.

func CheckOperationCapability

func CheckOperationCapability(reg *PluginRegistry, cap *Capability, ctx *PluginContext) (*PluginResult, error)

CheckOperationCapability executes phase two (operation layer) plugin decisions. Called by the gateway before processing a specific operation (e.g., HTTP route, TCP tunnel, UDP target), making decisions only for the capability corresponding to that operation:

  • Operation scheme has no plugin → fail-closed reject (gateway declares service but no decision rule configured; cannot allow uncontrolled operations);
  • Plugin deny → reject;
  • Plugin allow/bypass → allow.

Complementary to phase one (P∩C intersection + scheme alignment inside RunAccessPipeline): phase one ignores unrelated schemes to allow connections; phase two is fail-closed for operations that will actually be executed.

Returns (PluginResult, error): error indicates only internal execution errors (e.g., webhook call failure); Decision==PluginDeny means the operation is rejected, and the caller should block the operation and record an audit entry accordingly.

func ExecutePlugin

func ExecutePlugin(schemeID string, cap *pki.Capability, ctx *PluginContext) (*PluginResult, error)

ExecutePlugin is a convenience wrapper for findPlugin + Execute.

type PolicyNamespace

type PolicyNamespace struct {
	DisplayName string   `json:"display_name"`
	Prefix      string   `json:"prefix"`
	Grants      []string `json:"grants"`
}

PolicyNamespace defines the grants for a gateway namespace.

type PolicyRole

type PolicyRole struct {
	DisplayName string   `json:"display_name"`
	Profiles    []string `json:"profiles"`
	Grants      []string `json:"grants"`
	Scope       []string `json:"scope,omitempty"`
}

PolicyRole defines a single role's display name, profiles, grants, and scope.

type PolicyServer

type PolicyServer interface {
	// Name returns the policy server name (for auditing).
	Name() string
	// CheckOnline checks online authorization (leaf certificate + parsed AIC).
	// Returns nil for authorization granted; non-nil for denial (with reason).
	CheckOnline(leaf *x509.Certificate, aic *AIC) error
}

PolicyServer is the Layer 3 online authorization policy server interface (Layer 3). Online authorization verification includes revocation freshness (OCSP/CRL, handled by PipelineConfig's cache instances) and policy server policy checks (if configured).

type PolicySigningConfig

type PolicySigningConfig struct {
	// Enabled enables policy signature verification.
	Enabled bool `json:"enabled,omitempty"`
	// CAFile is the trusted CA chain PEM (defaults to tls_client_ca).
	CAFile string `json:"ca_file,omitempty"`
	// RequireAdminOU requires the signer to have admin OU (nil=defaults to true).
	RequireAdminOU *bool `json:"require_admin_ou,omitempty"`
	// Require: true=reject if signature is missing; false=degrade with warning if missing.
	Require bool `json:"require,omitempty"`
	// SigSuffix is the signature file suffix (default ".sig").
	SigSuffix string `json:"sig_suffix,omitempty"`
}

PolicySigningConfig configures PKCS#7 detached signature verification for the gateway authz.json policy file. Structurally identical to varwof-core's policy_signing configuration.

func (*PolicySigningConfig) BuildPolicyVerifyOptions

func (ps *PolicySigningConfig) BuildPolicyVerifyOptions(tlsClientCA string) (*PolicyVerifyOptions, error)

BuildPolicyVerifyOptions builds signature verification parameters from PolicySigningConfig. Returns nil if signature verification is not enabled (signing disabled). tlsClientCA is used as the default fallback when CAFile is empty.

type PolicySigningIdentity

type PolicySigningIdentity struct {
	Cert *x509.Certificate
	Key  crypto.Signer
}

PolicySigningIdentity describes the admin identity used to sign the policy file.

func LoadPolicySigningIdentity

func LoadPolicySigningIdentity(certPEM, keyPEM string) (*PolicySigningIdentity, error)

LoadPolicySigningIdentity loads the signer certificate and private key from PEM files.

type PolicyVerifyOptions

type PolicyVerifyOptions struct {
	// Roots is the trusted CA chain (Issuing CA + Root CA) for verifying the signer certificate.
	Roots *x509.CertPool
	// RequireAdminOU, when true, requires the signer certificate OU to contain the admin role.
	RequireAdminOU bool
}

PolicyVerifyOptions describes the external parameters needed for signature verification.

type PrincipalAuthorization

type PrincipalAuthorization = pki.PrincipalAuthorization

── Type aliases ──

func ParseUserPermissionExtension

func ParseUserPermissionExtension(cert *x509.Certificate) (*PrincipalAuthorization, error)

ParseUserPermissionExtension delegates to pki-types.

type PrincipalUid

type PrincipalUid = pki.PrincipalUid

── Type aliases ──

func MakePrincipalUidFromCert

func MakePrincipalUidFromCert(realm, identifier string, certDER []byte) PrincipalUid

MakePrincipalUidFromCert constructs a PrincipalUid from a certificate DER (KeyHash = SPKI SHA-256, per spec §PrincipalUid).

func ParsePrincipalUid

func ParsePrincipalUid(s string) (PrincipalUid, error)

ParsePrincipalUid delegates to pki-types.

type ProblemDetails added in v0.2.0

type ProblemDetails struct {
	Type      string               `json:"type"`
	Title     string               `json:"title,omitempty"`
	Status    int                  `json:"status"`
	Detail    string               `json:"detail,omitempty"`
	Instance  string               `json:"instance,omitempty"`
	Challenge *semantics.Challenge `json:"challenge,omitempty"`
}

ProblemDetails is an RFC 9457 problem document carrying a CLC challenge.

type ProofStep

type ProofStep struct {
	Sibling []byte `json:"sibling"`
	Left    bool   `json:"left"`
}

ProofStep is a single step in a Merkle audit proof, containing a sibling hash and direction.

type ProofStepJSON

type ProofStepJSON struct {
	Sibling string `json:"sibling"`
	Left    bool   `json:"left"`
}

ProofStepJSON is the JSON representation of an audit proof step.

type Reason

type Reason = pki.Reason

── Type aliases ──

type RecordRef added in v0.2.0

type RecordRef struct {
	Digest  string `json:"digest"`  // hex of the record's input digest
	Verdict string `json:"verdict"` // the CLC verdict recorded
	Path    string `json:"path,omitempty"`
}

RecordRef is where a record ended up, so a caller can point at it (audit entry, downstream log, response) without carrying the whole record.

func EmitDecisionRecords added in v0.2.0

func EmitDecisionRecords(cfg *EvidenceConfig, sinkCtx EvidenceContext, cert *x509.Certificate, aic *AIC, pa *PrincipalAuthorization, userCert *x509.Certificate, ops []OperationDecision) ([]RecordRef, error)

EmitDecisionRecords freezes one record per (source, operation) and hands it to the sink, returning where each record went. The verdict in each record is recomputed by RecordWith from the same grants and operation, so a record that does not reproduce is impossible by construction.

sinkCtx carries the correlation (which request, who, admitted or refused); the per-operation fields are filled in here.

func EmitOperationEvidence

func EmitOperationEvidence(cfg *EvidenceConfig, ctx EvidenceContext, cert *x509.Certificate, aic *AIC, op OperationDecision) ([]RecordRef, error)

EmitOperationEvidence freezes and (when configured) signs one CLC decision record for a single projected operation, returning where it went. It is the execution-boundary entry point (aic-exec): a component that adjudicates a concrete operation outside the admission pipeline leaves the same replayable record the pipeline would, without carrying the whole pipeline's inputs. The record's verdict is recomputed from aic's grants, so a record that does not reproduce is impossible by construction.

func ReportOutcome added in v0.2.0

func ReportOutcome(sink OutcomeSink, cfg *EvidenceConfig, ctx EvidenceContext, rec OutcomeRecord) (RecordRef, error)

ReportOutcome sends an outcome record through the deployment's sink, filling in the recorder and timestamp when the caller left them out.

type RecordSigner

type RecordSigner struct {
	// contains filtered or unexported fields
}

RecordSigner is the key that endorses emitted evidence records: a crypto.Signer paired with the key id published in the DSSE signature. It also carries the matching public key, so a holder can verify a record with VerifyFn without a separate out-of-band key exchange.

func LoadRecordSignerFile

func LoadRecordSignerFile(path, keyID string) (*RecordSigner, error)

LoadRecordSignerFile loads a PEM private key (PKCS#1, PKCS#8 or SEC1; RSA, ECDSA or Ed25519) and wraps it as a record signer.

func NewRecordSigner

func NewRecordSigner(keyID string, signer crypto.Signer) (*RecordSigner, error)

NewRecordSigner wraps any crypto.Signer as a record signer. HSM/KMS-backed implementations that only expose signing plug in here.

func (*RecordSigner) KeyID

func (s *RecordSigner) KeyID() string

KeyID is the unauthenticated hint published alongside each signature; it names a key but is never trusted on its own.

func (*RecordSigner) Public

func (s *RecordSigner) Public() crypto.PublicKey

Public returns the verification key matching the signer.

func (*RecordSigner) Sign

func (s *RecordSigner) Sign(pae []byte) ([]byte, error)

Sign implements the EvidenceConfig signing callback over the DSSE PAE. RSA and ECDSA sign the SHA-256 digest of the PAE; Ed25519 signs the PAE itself.

func (*RecordSigner) VerifyFn

func (s *RecordSigner) VerifyFn() func(keyID string, pae, sig []byte) error

VerifyFn returns the DSSE verification callback pinned to this signer's public key, for VerifyEvidenceDir / VerifyEvidenceEnvelope.

type RecorderDescriptor added in v0.2.0

type RecorderDescriptor struct {
	ID   string `json:"id"`
	Kind string `json:"kind,omitempty"`
	Note string `json:"note,omitempty"`
}

RecorderDescriptor identifies the admission point that emitted a record. It is published out of band (one descriptor per deployment); the envelope then carries its digest as a subject, so a consumer holding the descriptor can tell which recorder produced the record — and one that does not hold it can still tell that two records came from the same or different recorders.

func (RecorderDescriptor) Digest added in v0.2.0

func (r RecorderDescriptor) Digest() (semantics.Digest, error)

Digest is the recorder's content-addressed identity.

type RenderFormat

type RenderFormat string

RenderFormat selects a human-readable rendering of an evidence bundle.

const (
	// RenderMarkdown is a sectioned, printable Markdown document.
	RenderMarkdown RenderFormat = "markdown"
	// RenderCSV is a flat section,field,value table for spreadsheets and
	// forensic tooling.
	RenderCSV RenderFormat = "csv"
	// RenderText is a plain-text rendering for tickets and terminals.
	RenderText RenderFormat = "text"
)

type RequestView

type RequestView struct {
	// Credential — at most one of the three shapes is used:
	// CertChain is the mTLS peer certificate chain as presented on the wire.
	CertChain []*x509.Certificate
	// BearerToken is a Bearer AIC-JWT carried out-of-transport (the verifier
	// parses and verifies it here). Requires TransportSecure.
	BearerToken string
	// VerifiedCert is a pre-verified client leaf supplied by an adapter that
	// already owns credential verification (it bypasses chain verification;
	// only pipeline checks apply).  For Bearer views it is the synthesized
	// cert the binding wants downstream to see.
	VerifiedCert *x509.Certificate

	// TransportSecure reports that the request arrived over a TLS-protected
	// transport.  Bearer credentials are refused when false (mirrors the HTTP
	// rule that a Bearer token never travels in cleartext).
	TransportSecure bool

	// Request facts (HTTP field semantics; harmless when empty for non-HTTP
	// carriers).  RawQuery is the raw query string; Header carries the
	// trace/pass-through headers (X-Request-Id, break-glass).
	Method   string
	Path     string
	RawQuery string
	Header   http.Header
	// ClientIP is the transport peer address used by constraint evaluation.
	ClientIP string
	// Body is the request body, already bounded (DefaultMaxBodyBytes).
	Body []byte

	// PresentedCert is the raw leaf presented on the wire by an mTLS peer,
	// kept even when it is later rejected (refusal-evidence recording wants
	// the exact bytes presented).  nil for Bearer views.
	PresentedCert *x509.Certificate

	// Hooks replace the deployment's *http.Request-keyed hooks for carriers
	// that cannot build one (gRPC, queue adapters).  When nil and HTTPAdapter
	// is set, the configured EvidenceFacts / RequireApproval hooks run with the
	// adaptee request instead.
	EvidenceFactsWith   func(ac *AuthContext) ([]semantics.EvidenceFact, error)
	RequireApprovalWith func(ac *AuthContext) bool

	// HTTPAdapter is set by the HTTP middleware to the originating request so
	// existing deployment hooks keep working unchanged.  It is never part of
	// the wire payload.
	HTTPAdapter *http.Request
}

RequestView is the transport-neutral description of an admission request: the input of the Decide decision core. It carries a wire credential — an mTLS peer chain or a Bearer AIC-JWT — plus the request facts the admission pipeline needs (operations, HTTP facts for capability plugins, client IP, bounded body). Carriers that already verified the credential pass the verified leaf in VerifiedCert and leave CertChain empty.

func DecodeDecideRequest

func DecodeDecideRequest(b []byte) (*RequestView, error)

DecodeDecideRequest parses a wire request back into a view.

func (*RequestView) ToDTO

func (v *RequestView) ToDTO() *DecideRequestDTO

ToDTO renders the view's carrier-writable fields. The adapter hooks (EvidenceFactsWith / RequireApprovalWith / HTTPAdapter) never cross the wire.

func (*RequestView) TraceID

func (v *RequestView) TraceID() string

TraceID returns the standard trace header the deployment propagates.

type ResourceScope

type ResourceScope = pki.ResourceScope

── Type aliases ──

type RiskAssessment

type RiskAssessment struct {
	// OperationID is the server-side per-request tracking id (correlation key).
	OperationID string `json:"operation_id"`
	// AgentID is the AIC agent identifier.
	AgentID string `json:"agent_id,omitempty"`
	// PrincipalUid is the verified principal UID.
	PrincipalUid string `json:"principal_uid,omitempty"`
	// DAHash is the sha256 hex hash of the signed DelegationAuthorization.
	DAHash string `json:"da_hash,omitempty"`
	// Capabilities are the admitted capability ids.
	Capabilities []string `json:"capabilities,omitempty"`
	// Operation is a short human-readable operation label (e.g. "POST /trade").
	Operation string `json:"operation"`
	// RequestedParams is the redacted summary of the request parameters.
	RequestedParams Summary `json:"requested_params,omitempty"`
	// Violations list deterministic-rule risks not yet covered semantically.
	Violations []string `json:"violations,omitempty"`
	// Semantic is the optional LLM semantic verdict for this request.
	Semantic *SemanticVerdict `json:"semantic,omitempty"`
	// PolicyVersion is the policy version effective at decision time.
	PolicyVersion uint64 `json:"policy_version,omitempty"`
}

RiskAssessment is the snapshot of a request that needs human supervision. It is passed to ApprovalRequester.Request, OverrideRecorder.Record and attached to supervision events. JSON is snake_case and defaults to omitempty so a minimal assessment stays small.

type RiskMonitor

type RiskMonitor struct {
	// contains filtered or unexported fields
}

RiskMonitor maintains per-agent violation counts and enforces rules automatically. Thread-safe; nil receiver methods are no-ops (safe to call when gateway is not configured).

func NewRiskMonitor

func NewRiskMonitor(cfg RiskMonitorConfig) *RiskMonitor

NewRiskMonitor creates a risk monitor.

func (*RiskMonitor) RecordViolation

func (m *RiskMonitor) RecordViolation(v RiskViolation) bool

RecordViolation records a behavioral violation and evaluates rules; triggers the enforcement callback when the threshold is reached. Returns whether enforcement was triggered. Returns false for nil receiver.

func (*RiskMonitor) Rules

func (m *RiskMonitor) Rules() []RiskRule

Rules returns a copy of the current rule list.

func (*RiskMonitor) SetRules

func (m *RiskMonitor) SetRules(rules []RiskRule)

SetRules hot-swaps the rule set (called during SIGHUP hot-reload).

func (*RiskMonitor) Violations

func (m *RiskMonitor) Violations(agentId string) int

Violations returns the cumulative violation count for an agent within the window (used for monitoring display when no enforcement has been triggered). Returns 0 for nil receiver.

type RiskMonitorConfig

type RiskMonitorConfig struct {
	// Rules is the list of risk rules.
	Rules []RiskRule `json:"rules"`
	// OnAction is the enforcement callback (gateway injects: execute disconnect + revoke).
	// When nil, only logging is performed.
	OnAction func(agentId, action, reason string)
	// Logger is the structured logger; uses slog.Default() when nil.
	Logger *slog.Logger
}

RiskMonitorConfig is the configuration for RiskMonitor.

type RiskRule

type RiskRule struct {
	// Name is the rule name.
	Name string `json:"name"`
	// Signals is the list of behavioral signal types that trigger the rule (any hit counts).
	Signals []string `json:"signals"`
	// Threshold is the violation count threshold within the window; reaching it triggers enforcement.
	Threshold int `json:"threshold"`
	// WindowSeconds is the counting window in seconds, default 60.
	WindowSeconds int `json:"window_seconds,omitempty"`
	// Action is the enforcement action: disconnect (kick) or revoke (kick + revoke).
	Action string `json:"action"`
	// Reason is the risk reason description for audit records.
	Reason string `json:"reason"`
}

RiskRule is a single risk rule.

type RiskViolation

type RiskViolation struct {
	// AgentId is the violating agent.
	AgentId string `json:"agent_id,omitempty"`
	// Signal is the risk signal type (e.g. cap_overflow / abnormal_rate / out_of_window).
	Signal string `json:"signal"`
	// CapabilityId is the associated capability identifier (optional).
	CapabilityId string `json:"capability_id,omitempty"`
	// Details provides supplementary description.
	Details string `json:"details,omitempty"`
	// At is the violation time (Unix seconds).
	At int64 `json:"at"`
}

RiskViolation describes a recorded behavioral violation (risk signal).

type RoleDef

type RoleDef = pki.RoleDef

── Type aliases ──

type RotatingFile

type RotatingFile struct {
	// contains filtered or unexported fields
}

RotatingFile is an auto-rotating file that supports size-based rotation and backup count limits.

func NewRotatingFile

func NewRotatingFile(path string, maxSize int64, maxBak int) (*RotatingFile, error)

NewRotatingFile creates an auto-rotating file instance.

func (*RotatingFile) Close

func (r *RotatingFile) Close() error

Close closes the rotating file.

func (*RotatingFile) Write

func (r *RotatingFile) Write(p []byte) (int, error)

Write implements io.Writer with automatic rotation.

type Route

type Route struct {
	// Path is the URL path prefix to match (e.g. "/api/v1"). Requests under this
	// prefix are forwarded to Target with their path preserved.
	Path string
	// Target is the backend base URL (e.g. "http://127.0.0.1:8080").
	Target *url.URL
	// AllowMethods, when non-empty, restricts accepted HTTP methods.
	AllowMethods []string
	// RequiredCapabilities requires the admitted agent to hold these capability
	// ids before the request is forwarded.
	RequiredCapabilities []string
}

Route is a reverse-proxy routing rule.

type SPIFFEID

type SPIFFEID struct {
	TrustDomain string
	Path        string
}

SPIFFEID represents a parsed SPIFFE identity.

func ExtractSPIFFEID

func ExtractSPIFFEID(cert *x509.Certificate) *SPIFFEID

ExtractSPIFFEID extracts a parsed SPIFFE ID from a certificate's SAN URIs. Returns nil if no valid SPIFFE URI is found.

func ParseSPIFFEID

func ParseSPIFFEID(id string) (*SPIFFEID, error)

ParseSPIFFEID parses a SPIFFE ID string into its components (trust domain, path).

func (*SPIFFEID) Equal

func (s *SPIFFEID) Equal(other *SPIFFEID) bool

Equal checks whether two SPIFFEID values are identical.

func (*SPIFFEID) String

func (s *SPIFFEID) String() string

String returns the SPIFFE ID in URI format.

type SealedTree

type SealedTree struct {
	BatchNumber int    `json:"batch"`
	Timestamp   string `json:"timestamp"`
	Previous    string `json:"previous_root"`
	Root        string `json:"root"`
	Size        int    `json:"size"`
}

SealedTree is a sealed Merkle tree batch with root hash and predecessor link.

type SemanticVerdict

type SemanticVerdict struct {
	// Model is the model identifier that produced the verdict.
	Model string `json:"model"`
	// Version is the model/policy version.
	Version string `json:"version"`
	// PromptHash is a hex digest of the evaluation prompt (evidence binding).
	PromptHash string `json:"prompt_hash"`
	// InputDigest is a hex digest of the evaluated input (optional).
	InputDigest string `json:"input_digest,omitempty"`
	// OutputJSON is the raw model output (JSON document, base64 in JSON).
	OutputJSON []byte `json:"output_json,omitempty"`
	// Decision is the semantic decision: allow | deny | refer.
	Decision string `json:"decision"`
	// Confidence is the model-reported confidence (0..1), optional.
	Confidence float64 `json:"confidence,omitempty"`
	// Failed reports that the semantic evaluation itself errored (timeout,
	// transport failure).  A failed gate must never fail-open; it maps to a
	// deny/refer at the caller.
	Failed bool `json:"failed,omitempty"`
	// Err is the evaluation error text when Failed is set.
	Err string `json:"err,omitempty"`
}

SemanticVerdict is the output of an optional LLM semantic gate. The SDK does not call an LLM itself; integrations that do (or that enforce semantic policies) populate this and attach it to a RiskAssessment for the human approver.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is an AIC-protected HTTP reverse proxy. It terminates TLS (optionally mTLS), runs the admission pipeline on every request, and forwards admitted requests to the matching backend, injecting the verified client identity.

Listener state is set once by Listen and read concurrently by Addr and Close, so it is guarded by mu. Prefer Listen + Addr + Serve over ListenAndServe when the caller needs the bound address (e.g. port 0) without polling.

func NewServer

func NewServer(c *Config, routes []Route) (*Server, error)

NewServer builds an AIC-protected reverse proxy server.

func (*Server) Addr

func (s *Server) Addr() net.Addr

Addr returns the address the server is listening on, or nil before Listen. It is safe to call concurrently with Listen and Close.

func (*Server) Close

func (s *Server) Close(ctx context.Context) error

Close gracefully shuts down the server, releases the listener, and closes the Config's owned resources (audit logger, nonce cache, supervision store, log file) via Config.Close. It is safe to call before Listen (no-op) and more than once. Closing the listener as well as calling Shutdown means a listener obtained from Listen but never handed to Serve is still released.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler exposes the server as a plain http.Handler (for embedding into an existing http.Server). Callers managing TLS themselves should use this.

func (*Server) Listen

func (s *Server) Listen(addr string) (net.Listener, error)

Listen binds the reverse-proxy listener on addr (TLS when TLSCertFile/TLSKeyFile are configured, mTLS when CACertFile is also set) and returns it. It does not begin serving; pass the listener to Serve. Use this instead of ListenAndServe when the bound address is needed before serving (a ":0" ephemeral port, for example) — read it with Addr.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(addr string) error

ListenAndServe binds addr and serves until Close or a fatal serve error. It is Listen followed by Serve; callers that need the bound address should call those two directly.

func (*Server) Serve

func (s *Server) Serve(ln net.Listener) error

Serve serves on a listener returned by Listen until Shutdown or Close. It reports http.ErrServerClosed on a graceful shutdown. A listener not obtained from Listen (no HTTP server prepared) is a configuration error.

type ServerOptions

type ServerOptions struct {
	ReadTimeout       time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration
	MaxHeaderBytes    int
}

ServerOptions tunes the embedded http.Server of the reverse proxy.

type SignedAuditEntry

type SignedAuditEntry struct {
	Entry AuditEntry `json:"entry"`
	TST   string     `json:"tst,omitempty"`
}

SignedAuditEntry is an audit entry with TSA timestamp signature.

type SignedSupervisionEvent

type SignedSupervisionEvent struct {
	Event pki.SupervisionEvent `json:"event"`
	TST   string               `json:"tst,omitempty"`
}

SignedSupervisionEvent is a supervision event with an optional RFC 3161 TSA timestamp attestation, mirroring SignedAuditEntry.

type SlogSink added in v0.2.0

type SlogSink struct {
	Logger *slog.Logger
}

SlogSink writes a compact summary of each record to a structured logger.

func (SlogSink) Emit added in v0.2.0

Emit implements EvidenceSink.

func (SlogSink) EmitAdmission added in v0.2.0

func (s SlogSink) EmitAdmission(ctx EvidenceContext, rec AdmissionRecord, env semantics.Envelope) (RecordRef, error)

EmitAdmission implements EvidenceSink for pipeline-level records.

func (SlogSink) EmitOutcome added in v0.2.0

func (s SlogSink) EmitOutcome(ctx EvidenceContext, rec OutcomeRecord, env semantics.Envelope) (RecordRef, error)

EmitOutcome implements OutcomeSink for the structured-logger fallback.

type Summary

type Summary string

Summary is a redacted summary of the requested operation parameters. It is derived from a digest form and never carries raw parameter values, reusing the audit mask concept (see mask.go). The empty Summary means nothing was included.

func NewSummaryFromBody

func NewSummaryFromBody(body []byte) Summary

NewSummaryFromBody produces a masked digest summary of a request body. An empty body yields an empty Summary.

type SupervisionPolicy

type SupervisionPolicy struct {
	// RequireRuntimeApproval mandates that requests flagged by RequireApproval
	// go through the ApprovalRequester.  When true and ApprovalRequester is
	// nil, startup validation fails.
	RequireRuntimeApproval bool
	// AllowBreakGlass enables break-glass overrides.  When true and
	// OverrideRecorder is nil, startup validation fails.
	AllowBreakGlass bool
	// RequireEvidenceExport mandates an EvidenceExporter.  When true and
	// EvidenceExporter is nil, startup validation fails.
	RequireEvidenceExport bool
}

SupervisionPolicy gates the supervision features. Pre-operation (DA) supervision is aic-agent side and intentionally absent here.

type SupervisionQuery

type SupervisionQuery struct {
	OperationID string
	DaHash      string
	AgentID     string
	Type        pki.SupervisionEventType
	TimeRange   *TimeRange
	Limit       int
}

SupervisionQuery filters supervision events read from the store. Empty fields are ignored. Type filters on a specific SupervisionEventType.

type SupervisionResult

type SupervisionResult struct {
	// Decision is approved | denied | pending.
	Decision string `json:"decision"`
	// Approver identifies the human that decided.
	Approver string `json:"approver,omitempty"`
	// Reason explains the decision.
	Reason string `json:"reason,omitempty"`
	// EvidenceRef references an external approval/review ticket.
	EvidenceRef string `json:"evidence_ref,omitempty"`
	// DecidedAt is when the decision was made.
	DecidedAt time.Time `json:"decided_at"`
}

SupervisionResult is the outcome of a human (or system) supervision step.

func (*SupervisionResult) Approved

func (r *SupervisionResult) Approved() bool

Approved reports whether the result is an explicit approval.

type SupervisionStore

type SupervisionStore struct {
	// contains filtered or unexported fields
}

SupervisionStore is the append-only supervision event store: JSON Lines with the same durability rules as the audit chain (audit.go). Critical events are written synchronously and can be TSA-attested. A nil store disables event persistence; the decision path stays fail-closed regardless.

func NewSupervisionStore

func NewSupervisionStore(file string, tsa *TSAClient, maxSize int64, maxBak int) (*SupervisionStore, error)

NewSupervisionStore creates an append-only event store at file. When file is empty it returns a nil store (no persistence). tsa, when non-nil, signs every recorded event with a timestamp token.

func (*SupervisionStore) Close

func (s *SupervisionStore) Close() error

Close closes the store file. Further Record calls fail. It is idempotent: a second Close returns nil instead of re-closing the file (which would return "file already closed" and make Config.Close non-idempotent).

func (*SupervisionStore) File

func (s *SupervisionStore) File() string

File returns the store file path (empty for a nil store).

func (*SupervisionStore) Query

Query reads supervision events from the store in append order and filters them. Events are returned newest-correlation-friendly in file order; an empty result is a non-nil empty slice. A nil store returns an error.

func (*SupervisionStore) Record

func (s *SupervisionStore) Record(ev *pki.SupervisionEvent) error

Record validates and synchronously appends a supervision event. TSA attestation is best-effort: a signing failure records the event unsigned and returns the underlying TSA error only if the write itself also failed.

type TSAClient

type TSAClient struct {
	// URL is the TSA service address.
	URL string
	// CACert is the CA certificate (for verifying TSA response signatures).
	CACert *x509.Certificate
	// HTTPClient is the HTTP client.
	HTTPClient *http.Client
	// SignFunc is a custom signing function (replaces HTTP calls).
	SignFunc func(data []byte) ([]byte, error)
	// contains filtered or unexported fields
}

TSAClient is an RFC 3161 timestamp client.

func NewTSAClient

func NewTSAClient(url string) *TSAClient

NewTSAClient creates a timestamp client.

func (*TSAClient) SetCACert

func (t *TSAClient) SetCACert(certFile string) error

SetCACert sets the TSA CA certificate.

func (*TSAClient) SetMaxTSTAge

func (t *TSAClient) SetMaxTSTAge(d time.Duration)

SetMaxTSTAge overrides the accepted TST age window. Defaults to 1h.

func (*TSAClient) Sign

func (t *TSAClient) Sign(data []byte) (tstDER []byte, err error)

Sign performs an RFC 3161 timestamp signature on data.

func (*TSAClient) Verify

func (t *TSAClient) Verify(entryJSON, tstDER []byte) error

Verify verifies a timestamp token.

type TSTInfo

type TSTInfo struct {
	Version        int
	Policy         asn1.ObjectIdentifier
	MessageImprint MessageImprint
	SerialNumber   int
	GenTime        time.Time
	Accuracy       asn1.RawValue `asn1:"optional"`
	Ordering       bool          `asn1:"optional,default:false"`
	Nonce          *int          `asn1:"optional"`
	TSA            asn1.RawValue `asn1:"optional,explicit,tag:0"`
}

TSTInfo is the timestamp token information.

func UnmarshalTimestampToken

func UnmarshalTimestampToken(data []byte) (*TSTInfo, error)

UnmarshalTimestampToken parses a timestamp token from DER.

type TimeRange

type TimeRange struct {
	Start time.Time `json:"start,omitempty"`
	End   time.Time `json:"end,omitempty"`
}

TimeRange bounds a query window. Zero values are unbounded on that end.

type TimeStampReq

type TimeStampReq struct {
	Version        int
	MessageImprint MessageImprint
	ReqPolicy      asn1.ObjectIdentifier `asn1:"optional"`
	Nonce          *int                  `asn1:"optional"`
	CertReq        bool                  `asn1:"optional,default:false"`
	Extensions     []asn1.RawValue       `asn1:"optional,set"`
}

TimeStampReq is an RFC 3161 timestamp request.

type TimeStampResp

type TimeStampResp struct {
	Status         PKIStatusInfo
	TimeStampToken asn1.RawValue `asn1:"optional"`
}

TimeStampResp is an RFC 3161 timestamp response.

type Translator

type Translator interface {
	T(lang, key string, args ...any) string
}

Translator is the internationalization translation interface.

type TrustLayer

type TrustLayer int

TrustLayer represents a layer in the three-layer trust model.

const (
	Layer1Identity TrustLayer = iota
	Layer2Representation
	Layer3OnlineAuthorization
)

Layer1/2/3 three-layer trust model layer constants.

func (TrustLayer) String

func (l TrustLayer) String() string

String returns the layer name.

type UserPermission

type UserPermission = pki.UserPermission

── Type aliases ──

type VerifyRequest

type VerifyRequest struct {
	Batch int             `json:"batch"`
	Leaf  string          `json:"leaf"`
	Proof []ProofStepJSON `json:"proof"`
}

VerifyRequest is an audit verification request.

type VerifyResponse

type VerifyResponse struct {
	Valid bool   `json:"valid"`
	Error string `json:"error,omitempty"`
}

VerifyResponse is an audit verification response.

Directories

Path Synopsis
examples
bearer-jwt-backend command
Command bearer-jwt-backend demonstrates the aic-verifier SDK protecting a real HTTP API:
Command bearer-jwt-backend demonstrates the aic-verifier SDK protecting a real HTTP API:
bearer-jwt-backend/gen-bearer command
Command gen-bearer generates a CA keypair and signs a sample AIC-JWT bearer token for the bearer-jwt-backend example.
Command gen-bearer generates a CA keypair and signs a sample AIC-JWT bearer token for the bearer-jwt-backend example.
inspect-record command
Command inspect-record reads a decision record written by the SDK and prints what was decided, why, and the digest a third party recomputes.
Command inspect-record reads a decision record written by the SDK and prints what was decided, why, and the digest a third party recomputes.
mcp-behind-proxy command
Command mcp-behind-proxy runs the AIC-gated MCP server in its canonical deployment topology: the aic-verifier reverse proxy terminates TLS, runs the admission pipeline and forwards each admitted request to a loopback-only MCP backend, which trusts the proxy's server-asserted X-AIC-* identity headers (aic-verifier/mcp TrustProxy mode) instead of re-verifying a credential.
Command mcp-behind-proxy runs the AIC-gated MCP server in its canonical deployment topology: the aic-verifier reverse proxy terminates TLS, runs the admission pipeline and forwards each admitted request to a loopback-only MCP backend, which trusts the proxy's server-asserted X-AIC-* identity headers (aic-verifier/mcp TrustProxy mode) instead of re-verifying a credential.
mcp-server command
Command mcp-server demonstrates an AIC-gated MCP (Model Context Protocol) server on top of aic-verifier.
Command mcp-server demonstrates an AIC-gated MCP (Model Context Protocol) server on top of aic-verifier.
mtls-backend command
Command mtls-backend demonstrates the aic-verifier SDK protecting a real HTTP API with mTLS client certificate + AIC authorization:
Command mtls-backend demonstrates the aic-verifier SDK protecting a real HTTP API with mTLS client certificate + AIC authorization:
mtls-backend/gen-cert command
Command gen-mtls generates a demo CA, a server certificate, and an mTLS client certificate carrying an AIC extension, for the mtls-backend example.
Command gen-mtls generates a demo CA, a server certificate, and an mTLS client certificate carrying an AIC extension, for the mtls-backend example.
showcase command
Command showcase runs the whole AIC + CLC + decision-record path on a local machine and prints each step, so the claims in docs/comparison.md can be seen rather than asserted:
Command showcase runs the whole AIC + CLC + decision-record path on a local machine and prints each step, so the claims in docs/comparison.md can be seen rather than asserted:
smoke-verify command
Command smoke-verify runs a minimal aic-verifier-protected HTTP service against a real varwof PKI (no demo CA), for smoke testing the SDK.
Command smoke-verify runs a minimal aic-verifier-protected HTTP service against a real varwof PKI (no demo CA), for smoke testing the SDK.
supervision-demo
Package superv provides the shared supervision demonstration helpers used by the mtls-backend and bearer-jwt-backend examples: a DemoApprover that implements aicverifier.ApprovalRequester and a wire helper that turns the aic-verifier supervision config (policy + store + evidence export + trigger) into a runnable demo.
Package superv provides the shared supervision demonstration helpers used by the mtls-backend and bearer-jwt-backend examples: a DemoApprover that implements aicverifier.ApprovalRequester and a wire helper that turns the aic-verifier supervision config (policy + store + evidence export + trigger) into a runnable demo.
Package grpc provides the reference gRPC binding for the aic-verifier decision core.
Package grpc provides the reference gRPC binding for the aic-verifier decision core.
Package mcp implements an AIC-gated MCP (Model Context Protocol) server for aic-verifier: an HTTP/Streamable transport carrying an embedded MCP server (github.com/mark3labs/mcp-go), guarded by the aic-verifier admission pipeline.
Package mcp implements an AIC-gated MCP (Model Context Protocol) server for aic-verifier: an HTTP/Streamable transport carrying an embedded MCP server (github.com/mark3labs/mcp-go), guarded by the aic-verifier admission pipeline.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL