Documentation
¶
Overview ¶
Package agentpass verifies AgentPass agent identity certificates.
AgentPass is an open identity layer for AI agents making regulated payments. Each agent holds an ECDSA P-256 X.509 certificate issued by a registered AgentPass Certificate Authority. The certificate carries three custom extensions describing the agent trust level (L0-L4), its authorised scopes, and the issuing developer.
This package is a zero-network Go implementation of the AgentPass verification logic. Callers provide a pool of trusted CA certificates and a candidate agent certificate; the package performs standard X.509 chain verification using the Go standard library and extracts the AgentPass trust extensions for the caller.
Minimal example ¶
pool := agentpass.NewCertPool()
if err := pool.AddPEM(caPEM); err != nil {
log.Fatal(err)
}
agent, err := agentpass.Verify(agentPEM, pool, agentpass.Now())
if err != nil {
log.Fatalf("agent rejected: %v", err)
}
log.Printf("agent %s verified at trust level L%d", agent.AgentID, agent.TrustLevel)
Design notes ¶
- No network I/O. Revocation checks (OCSP/CRL) are explicitly out of scope for this package; callers that need them should wrap the verifier with their own revocation pipeline.
- No global state. The verifier is safe for concurrent use.
- Standard-library crypto only. No reflection, no unsafe, no runtime code generation.
- Constant-time cryptographic comparisons are handled by crypto/ecdsa inside the standard library.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidPEM is returned when the input bytes cannot be // interpreted as a PEM-encoded X.509 certificate. ErrInvalidPEM = errors.New("agentpass: input is not a valid PEM certificate block") // ErrNoTrustAnchors is returned when Verify is called with an // empty trust anchor pool. ErrNoTrustAnchors = errors.New("agentpass: trust anchor pool is empty") // ErrChainInvalid is returned when the candidate certificate // does not chain to any of the configured trust anchors. ErrChainInvalid = errors.New("agentpass: certificate does not chain to any trusted issuer") // ErrExpired is returned when the candidate certificate is // outside its NotBefore/NotAfter window at verification time. ErrExpired = errors.New("agentpass: certificate is expired or not yet valid") // ErrMissingAgentExtensions is returned when the candidate // certificate chains correctly but does not carry the required // AgentPass custom extensions (trust level, scope, issuer). ErrMissingAgentExtensions = errors.New("agentpass: certificate lacks required AgentPass extensions") // ErrInvalidTrustLevel is returned when the trust-level // extension is present but its value cannot be parsed as L0-L4. ErrInvalidTrustLevel = errors.New("agentpass: trust-level extension malformed") // ErrUnsupportedAlgorithm is returned when the candidate // certificate is not signed with the ECDSA + SHA-256 scheme // used by AgentPass. ErrUnsupportedAlgorithm = errors.New("agentpass: certificate is not ECDSA-SHA256") // ErrTrustLevelBelowMinimum is returned by VerifyMinTrust when // a certificate verifies successfully but its trust level is // below the caller-specified minimum. ErrTrustLevelBelowMinimum = errors.New("agentpass: agent trust level below configured minimum") )
Sentinel errors returned by Verify and the PEM helpers.
Callers can match against these with errors.Is to drive their own auditing and rate-limiting decisions without having to string-match error messages.
Functions ¶
Types ¶
type Agent ¶
type Agent struct {
// X509 is the underlying certificate. Retained so callers that
// want to do additional inspection (fingerprinting, pinning,
// custom extension handling) can do so without re-parsing.
X509 *x509.Certificate
// AgentID is the stable identifier embedded in the certificate
// Common Name. In AgentPass the CN is formatted as
// "<agentName> (<agentID>)" and AgentID is the parenthesised
// portion.
AgentID string
// AgentName is the display name embedded in the certificate
// Common Name (the portion before the parenthesised ID).
AgentName string
// TrustLevel is the L0-L4 tier recorded in the custom
// trust-level extension.
TrustLevel int
// Scopes is the list of authorised capabilities recorded in
// the custom scope extension. May be empty.
Scopes []string
// IssuerID is the developer/issuer identifier recorded in the
// custom issuer extension. This is a free-form string chosen by
// the CA and is not the same thing as the certificate issuer DN.
IssuerID string
// Serial is the certificate serial number rendered as decimal.
Serial string
// NotBefore and NotAfter mirror the X.509 validity window.
NotBefore time.Time
NotAfter time.Time
}
Agent is the parsed, unverified view of an AgentPass agent certificate. Callers should only trust fields on an Agent that came back from Verify; ParseCertificate returns an Agent that has been syntactically validated but whose signature has NOT been checked against any trust anchor.
func ParseCertificatePEM ¶
ParseCertificatePEM decodes a PEM-encoded X.509 certificate and returns the syntactically parsed AgentPass view. Chain validity, time validity, and trust level are NOT checked; callers that want a safe-to-trust result should use Verify instead.
ParseCertificatePEM is exposed primarily for tooling and tests; it is not the expected entry point for production code.
type CertPool ¶
CertPool wraps *x509.CertPool with AgentPass-specific helpers for loading trust anchors from PEM bytes or files. It is a thin convenience layer; callers that already have a *x509.CertPool for other reasons can use Verify with that pool directly via the underlying CertPool field.
func NewCertPool ¶
func NewCertPool() *CertPool
NewCertPool returns an empty CertPool ready to accept trust anchors. Nothing is shared between pools, so two pools created from the same source are independent.
func (*CertPool) AddFile ¶
AddFile loads a PEM bundle from the local filesystem and adds every CERTIFICATE block to the pool. Errors from os.ReadFile are passed through so operators can distinguish "permission denied" from "no certs in file".
The path is resolved relative to the current working directory of the calling process; callers running under systemd or in containers should pass absolute paths to avoid surprises.
func (*CertPool) AddPEM ¶
AddPEM parses the supplied bytes as one or more PEM-encoded CERTIFICATE blocks and adds each parsed certificate to the pool. Non-CERTIFICATE PEM blocks are ignored so that callers can pass a bundle that also contains keys or CSRs without error.
An error is returned only if no valid CERTIFICATE block was found. This keeps misconfigurations loud without punishing callers for unrelated junk at the bottom of a bundle.
func (*CertPool) Size ¶
Size reports how many CA certificates are in the pool. Zero is a useful sentinel for callers that want to refuse to start if no trust anchors are configured.
The underlying *x509.CertPool does not expose a count directly, so this value is tracked by AddPEM/AddFile. It is advisory: certificates added via the Pool field directly will not be counted. For strict accounting, add anchors only through the CertPool methods.
type TimeSource ¶
TimeSource returns the current time used by the verifier. It is an indirection so that tests can pin the verification clock to a fixed moment and so that embedders that run in environments with unreliable wall clocks can supply their own trusted time source.
The default TimeSource is Now, which simply returns time.Now().
type Verified ¶
type Verified struct {
Agent
// Chains holds the verified certificate chains returned by the
// standard library verifier. It is useful for callers that want
// to log or inspect which trust anchor accepted the agent.
Chains [][]*x509.Certificate
}
Verified is returned by Verify when a certificate has been syntactically parsed, cryptographically chain-verified against a trust anchor, and confirmed to carry the required AgentPass extensions.
func Verify ¶
func Verify(pemBytes []byte, pool *CertPool, opts ...VerifyOption) (*Verified, error)
Verify parses a PEM-encoded agent certificate, confirms that it chains to a certificate in pool, confirms it is within its validity window, confirms it carries the AgentPass custom extensions, and (optionally) confirms policy such as minimum trust level and required scopes.
On success Verify returns a *Verified containing the agent's parsed identity along with the verified certificate chain. On failure Verify returns one of the sentinel errors defined in errors.go wrapped with additional context using fmt.Errorf %w, so callers can use errors.Is to classify failures.
type VerifyOption ¶
type VerifyOption func(*verifyConfig)
VerifyOption configures an individual call to Verify. Options are cumulative: pass as many as needed in any order.
func WithMinTrust ¶
func WithMinTrust(level int) VerifyOption
WithMinTrust rejects agents whose trust level is below level. Level must be in the range 0-4; values outside this range are clamped to the nearest bound. Callers typically set this to 2 in production to reject unverified (L0) and developer-sandbox (L1) agents.
func WithRequiredScopes ¶
func WithRequiredScopes(scopes ...string) VerifyOption
WithRequiredScopes rejects agents that lack any of the listed scopes. Useful for Watchman-style integrations that want to demand e.g. WithRequiredScopes("sanctions:search") before accepting a screening request.
func WithTime ¶
func WithTime(now TimeSource) VerifyOption
WithTime pins the verification clock. Useful in tests and in embedders that get their time from an authoritative source.