agentpass

package module
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Apr 17, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

README

agentpass-go

Go SDK for verifying AgentPass agent identity certificates.

Zero-network, standard-library-only X.509 verification of AgentPass agent certificates with custom trust-level, scope, and issuer extensions.

Install

go get github.com/razashariff/agentpass-go

Usage

package main

import (
	"log"
	"os"

	"github.com/razashariff/agentpass-go"
)

func main() {
	// Load trust anchors (CA certificates you trust)
	pool := agentpass.NewCertPool()
	caPEM, _ := os.ReadFile("/etc/watchman/agentpass-ca.pem")
	if err := pool.AddPEM(caPEM); err != nil {
		log.Fatal(err)
	}

	// Verify an agent certificate from an incoming request
	agentPEM := []byte(getAgentCertFromRequest()) // your HTTP/MCP handler
	verified, err := agentpass.Verify(agentPEM, pool,
		agentpass.WithMinTrust(2),                         // reject L0/L1 agents
		agentpass.WithRequiredScopes("sanctions:search"),   // require specific capability
	)
	if err != nil {
		log.Fatalf("agent rejected: %v", err)
	}

	log.Printf("agent %s (L%d) verified, scopes=%v, issuer=%s",
		verified.AgentID,
		verified.TrustLevel,
		verified.Scopes,
		verified.IssuerID,
	)
}

Architecture

  • No network calls. Verification is pure local crypto (ECDSA P-256 + SHA-256).
  • Trust anchors in config. Same model as TLS root CAs. Callers pick which issuers to trust.
  • Self-hostable registry. Agents can register with agentpass.co.uk (free) or any self-hosted AgentPass registry.
  • Standard library only. No CGo, no unsafe, no external crypto dependencies.

Certificate format

AgentPass agents carry X.509 v3 certificates with three custom extensions:

OID Name Example value
1.3.6.1.4.1.99999.1.1 Trust level "L2"
1.3.6.1.4.1.99999.1.2 Scopes "payments,sanctions:search"
1.3.6.1.4.1.99999.1.3 Issuer ID "dev-001"

Certificates are signed by an AgentPass CA using ECDSA with SHA-256 on the P-256 curve.

Test coverage

ok  github.com/razashariff/agentpass-go  coverage: 84.0% of statements

20 test cases covering: happy path, expired certs, untrusted CA, tampered signatures, missing extensions, bad trust levels, RSA rejection, min-trust policy, scope gating, pinned time verification.

License

Apache License 2.0. See LICENSE file.

(c) 2026 CyberSecAI Ltd.

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

View Source
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

func Now

func Now() time.Time

Now is the default TimeSource used when callers do not supply their own. It is a method rather than a plain time.Now so that it can be passed to Verify as a value of type TimeSource without an adapter.

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

func ParseCertificatePEM(pemBytes []byte) (*Agent, error)

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.

func (*Agent) HasScope

func (a *Agent) HasScope(scope string) bool

HasScope reports whether the agent's scope list contains the named capability. Comparison is case-sensitive to match the format emitted by aptaas/pki.js.

type CertPool

type CertPool struct {
	Pool *x509.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

func (p *CertPool) AddFile(path string) error

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

func (p *CertPool) AddPEM(pemBytes []byte) error

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

func (p *CertPool) Size() int

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

type TimeSource func() time.Time

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.

Directories

Path Synopsis
Package testca builds AgentPass-shaped certificate bundles in memory for tests.
Package testca builds AgentPass-shaped certificate bundles in memory for tests.

Jump to

Keyboard shortcuts

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