sctx

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jan 29, 2026 License: MIT Imports: 22 Imported by: 0

README

sctx

CI Status codecov Go Report Card CodeQL Go Reference License Go Version Release

Certificate-based security contexts for Go.

Turn mTLS certificates into typed authorization tokens with permissions, metadata, and delegatable guards.

Certificates Become Capabilities

Your PKI already establishes identity. sctx turns that identity into authorization.

// Define how certificates map to permissions
admin.SetPolicy(func(cert *x509.Certificate) (*sctx.Context[UserMeta], error) {
    return &sctx.Context[UserMeta]{
        Permissions: permissionsFromOU(cert.Subject.OrganizationalUnit),
        Metadata:    UserMeta{TenantID: cert.Subject.Organization[0]},
        ExpiresAt:   time.Now().Add(time.Hour),
    }, nil
})

// Client proves key possession, gets a signed token
assertion, _ := sctx.CreateAssertion(clientKey, clientCert)
token, _ := admin.Generate(ctx, clientCert, assertion)

// Token holder creates guards for specific permissions
guard, _ := admin.CreateGuard(ctx, token, "api:read", "api:write")

// Guards validate other tokens
err := guard.Validate(ctx, incomingToken)

No JWT parsing. No external identity provider. Your certificates are your identity system.

Install

go get github.com/zoobzio/sctx

Requires Go 1.24+.

Quick Start

package main

import (
    "context"
    "crypto/ed25519"
    "crypto/x509"
    "fmt"
    "time"

    "github.com/zoobzio/sctx"
)

type UserMeta struct {
    Role     string
    TenantID string
}

func main() {
    ctx := context.Background()

    // Create the admin service with your signing key and trusted CAs
    admin, _ := sctx.NewAdminService[UserMeta](privateKey, trustedCAPool)

    // Define authorization policy: certificate -> context
    admin.SetPolicy(func(cert *x509.Certificate) (*sctx.Context[UserMeta], error) {
        return &sctx.Context[UserMeta]{
            Permissions: []string{"read", "write"},
            Metadata:    UserMeta{Role: "admin", TenantID: cert.Subject.Organization[0]},
            ExpiresAt:   time.Now().Add(time.Hour),
        }, nil
    })

    // Client creates assertion proving key possession
    assertion, _ := sctx.CreateAssertion(clientKey, clientCert)

    // Admin generates signed token
    token, _ := admin.Generate(ctx, clientCert, assertion)

    // Create a guard for specific permissions
    guard, _ := admin.CreateGuard(ctx, token, "read")

    // Validate tokens against the guard
    if err := guard.Validate(ctx, token); err != nil {
        fmt.Println("Access denied:", err)
        return
    }

    fmt.Println("Access granted")
}

Capabilities

Feature Description Docs
Policy-Driven Authorization Transform certificates into contexts with custom policies Policy
Permission Guards Create guards that check for required permissions Guards
Type-Safe Metadata Generic contexts with compile-time type checking Concepts
Instant Revocation Revoke contexts by certificate fingerprint Revocation
Observability Capitan events for all operations Events
Testing Utilities Deterministic testing with mock certificates Testing

Why sctx?

  • Certificate-native — Built on mTLS, no separate identity layer
  • Type-safe — Generic metadata with compile-time checking
  • Delegatable — Token holders create guards for others to use
  • Zero shared secrets — Only public keys needed for validation
  • Instantly revocable — Revoke by fingerprint, all guards reject immediately
  • Observable — Every operation emits capitan events

PKI-Native Authorization

sctx enables a pattern: certificates establish identity, policies define permissions, guards enforce access.

Your mTLS infrastructure already verifies who clients are. sctx adds what they can do.

// In your mTLS handler — certificate is already verified
func handler(w http.ResponseWriter, r *http.Request) {
    cert := r.TLS.PeerCertificates[0]

    // Turn certificate into authorization token
    assertion, _ := sctx.CreateAssertion(clientKey, cert)
    token, _ := admin.Generate(r.Context(), cert, assertion)

    // Check permissions with a guard
    if err := readGuard.Validate(r.Context(), token); err != nil {
        http.Error(w, "Forbidden", http.StatusForbidden)
        return
    }

    // Authorized — proceed
}

One certificate, one policy, full authorization. No JWT middleware. No token introspection endpoints.

Documentation

  • Overview — Design philosophy and architecture
Learn
Guides
  • Policy — Certificate-to-context transformation
  • Guards — Permission checking and delegation
  • Events — Observability with capitan
  • Testing — Testing with mock certificates
Cookbook
Reference
  • API — Complete function documentation
  • Events — Capitan signal reference

Contributing

See CONTRIBUTING.md for guidelines. Run make help for available commands.

License

MIT License — see LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidKey          = errors.New("invalid private key")
	ErrAdminAlreadyCreated = errors.New("admin service already created - only one admin allowed per application instance")
	ErrNoPolicy            = errors.New("no context policy configured")
)
View Source
var (
	ErrInvalidSignature = errors.New("invalid signature")
	ErrExpiredContext   = errors.New("context has expired")
	ErrInvalidContext   = errors.New("invalid context format")
)
View Source
var (
	// TokenGenerated is emitted when a token is created from a certificate.
	// Fields: FingerprintKey, CommonNameKey, PermissionsKey.
	TokenGenerated = capitan.NewSignal("sctx.token.generated", "Security token created from certificate")

	// TokenVerified is emitted when a token is successfully verified.
	// Fields: FingerprintKey.
	TokenVerified = capitan.NewSignal("sctx.token.verified", "Security token successfully verified")

	// TokenRejected is emitted when token verification fails.
	// Fields: FingerprintKey, ErrorKey.
	TokenRejected = capitan.NewSignal("sctx.token.rejected", "Security token verification failed")
)

Token lifecycle signals.

View Source
var (
	// GuardCreated is emitted when a guard is created.
	// Fields: GuardIDKey, FingerprintKey, RequiredPermsKey.
	GuardCreated = capitan.NewSignal("sctx.guard.created", "Permission guard created by token holder")

	// GuardValidated is emitted when guard validation succeeds.
	// Fields: GuardIDKey, FingerprintKey.
	GuardValidated = capitan.NewSignal("sctx.guard.validated", "Guard validation succeeded for token")

	// GuardRejected is emitted when guard validation fails.
	// Fields: GuardIDKey, FingerprintKey, ErrorKey.
	GuardRejected = capitan.NewSignal("sctx.guard.rejected", "Guard validation failed for token")
)

Guard lifecycle signals.

View Source
var (
	// AssertionValidated is emitted when an assertion passes validation.
	// Fields: FingerprintKey.
	AssertionValidated = capitan.NewSignal("sctx.assertion.validated", "Assertion validation succeeded")

	// AssertionRejected is emitted when assertion validation fails.
	// Fields: FingerprintKey, ErrorKey.
	AssertionRejected = capitan.NewSignal("sctx.assertion.rejected", "Assertion validation failed")
)

Assertion signals.

View Source
var (
	// CacheStored is emitted when a context is stored in cache.
	// Fields: FingerprintKey.
	CacheStored = capitan.NewSignal("sctx.cache.stored", "Security context stored in cache")

	// CacheHit is emitted when a cache lookup succeeds.
	// Fields: FingerprintKey.
	CacheHit = capitan.NewSignal("sctx.cache.hit", "Cache lookup found existing context")

	// CacheMiss is emitted when a cache lookup fails.
	// Fields: FingerprintKey.
	CacheMiss = capitan.NewSignal("sctx.cache.miss", "Cache lookup did not find context")

	// CacheDeleted is emitted when a context is deleted from cache.
	// Fields: FingerprintKey.
	CacheDeleted = capitan.NewSignal("sctx.cache.deleted", "Security context deleted from cache")

	// CacheExpired is emitted when a context expires during cleanup.
	// Fields: FingerprintKey.
	CacheExpired = capitan.NewSignal("sctx.cache.expired", "Security context expired during cache cleanup")

	// CacheEvicted is emitted when a context is evicted due to cache size limits.
	// Fields: FingerprintKey.
	CacheEvicted = capitan.NewSignal("sctx.cache.evicted", "Security context evicted due to cache size limit")
)

Cache operation signals.

View Source
var (
	// Identity fields.
	FingerprintKey = capitan.NewStringKey("fingerprint")
	CommonNameKey  = capitan.NewStringKey("common_name")

	// Permission fields.
	PermissionsKey   = capitan.NewStringKey("permissions")
	RequiredPermsKey = capitan.NewStringKey("required_permissions")

	// Guard fields.
	GuardIDKey = capitan.NewStringKey("guard_id")

	// Error field.
	ErrorKey = capitan.NewStringKey("error")

	// Time fields.
	ExpiresAtKey  = capitan.NewTimeKey("expires_at")
	DurationMsKey = capitan.NewInt64Key("duration_ms")
)

Event field keys.

View Source
var (
	// CertificateRejected is emitted when certificate verification fails.
	// Fields: CommonNameKey, ErrorKey.
	CertificateRejected = capitan.NewSignal("sctx.certificate.rejected", "Certificate verification failed")
)

Certificate signals.

View Source
var (
	// ContextRevoked is emitted when a context is manually revoked.
	// Fields: FingerprintKey.
	ContextRevoked = capitan.NewSignal("sctx.context.revoked", "Security context manually revoked")
)

Context lifecycle signals.

View Source
var (
	// PrincipalCreated is emitted when a principal is established from a certificate.
	// Fields: FingerprintKey, CommonNameKey.
	PrincipalCreated = capitan.NewSignal("sctx.principal.created", "Authenticated principal established from certificate")
)

Principal lifecycle signals.

Functions

func GenerateKeyPair

func GenerateKeyPair(algorithm CryptoAlgorithm) (crypto.PublicKey, crypto.PrivateKey, error)

GenerateKeyPair generates a key pair for the specified algorithm.

func GetFingerprint added in v0.0.4

func GetFingerprint(cert *x509.Certificate) string

GetFingerprint calculates the SHA256 fingerprint of a certificate.

func ValidateAlgorithm

func ValidateAlgorithm(algorithm CryptoAlgorithm) error

ValidateAlgorithm checks if the algorithm is supported.

func ValidateAssertion

func ValidateAssertion[M any](ctx context.Context, assertion SignedAssertion, cert *x509.Certificate, admin *adminService[M]) error

ValidateAssertion performs complete assertion validation using direct function calls.

Types

type Admin

type Admin[M any] interface {
	// Generate creates a token for the given certificate and assertion
	Generate(ctx context.Context, cert *x509.Certificate, assertion SignedAssertion) (SignedToken, error)

	// CreateGuard creates a guard for validating tokens with required permissions
	CreateGuard(ctx context.Context, token SignedToken, requiredPermissions ...string) (Guard, error)

	// SetGuardCreationPermissions sets permissions required to create guards
	SetGuardCreationPermissions(perms []string)

	// SetPolicy sets the context policy function
	SetPolicy(policy ContextPolicy[M]) error

	// SetCache replaces the context cache implementation
	SetCache(cache ContextCache[M]) error

	// RevokeByFingerprint revokes a context by certificate fingerprint
	RevokeByFingerprint(ctx context.Context, fingerprint string) error

	// GetContext retrieves a context by fingerprint
	GetContext(ctx context.Context, fingerprint string) (*Context[M], bool)

	// ActiveCount returns the number of active contexts
	ActiveCount() int
}

Admin is the interface for admin operations.

func NewAdminService

func NewAdminService[M any](privateKey crypto.PrivateKey, trustedCAs *x509.CertPool) (Admin[M], error)

NewAdminService creates a new admin service instance - only one admin allowed per application instance.

type AssertionClaims

type AssertionClaims struct {
	IssuedAt    time.Time `json:"iat"`
	ExpiresAt   time.Time `json:"exp"`
	Nonce       string    `json:"nonce"`
	Purpose     string    `json:"purpose"`
	Fingerprint string    `json:"fingerprint"` // Must match certificate
}

AssertionClaims contains the claims within an assertion.

type AssertionContext

type AssertionContext struct {
	Assertion   SignedAssertion
	Certificate *x509.Certificate
}

AssertionContext is used for validation processing.

type CacheOptions added in v0.0.4

type CacheOptions struct {
	// MaxSize is the maximum number of entries. 0 means unbounded.
	MaxSize int
	// CleanupInterval is how often to clean expired entries.
	// Defaults to 5 minutes if not specified.
	CleanupInterval time.Duration
}

CacheOptions configures cache behavior.

func DefaultCacheOptions added in v0.0.4

func DefaultCacheOptions() CacheOptions

DefaultCacheOptions returns sensible defaults (unbounded for backward compat).

type CertificateInfo

type CertificateInfo struct {
	// Subject fields
	CommonName         string   `json:"cn,omitempty"`
	Organization       []string `json:"o,omitempty"`
	OrganizationalUnit []string `json:"ou,omitempty"`
	Country            string   `json:"c,omitempty"`
	Province           string   `json:"st,omitempty"`
	Locality           string   `json:"l,omitempty"`
	StreetAddress      []string `json:"street,omitempty"`
	PostalCode         []string `json:"postal,omitempty"`

	// Certificate metadata
	SerialNumber string    `json:"serial"`
	NotBefore    time.Time `json:"notBefore"`
	NotAfter     time.Time `json:"notAfter"`

	// Issuer fields
	Issuer             string   `json:"issuer,omitempty"`
	IssuerOrganization []string `json:"issuerOrg,omitempty"`

	// Extensions
	KeyUsage       []string `json:"keyUsage,omitempty"`
	DNSNames       []string `json:"dnsNames,omitempty"`
	EmailAddresses []string `json:"emails,omitempty"`
	URIs           []string `json:"uris,omitempty"`
	IPAddresses    []string `json:"ips,omitempty"`
}

This replaces storing the full x509.Certificate for better serialization.

type Context

type Context[M any] struct {
	IssuedAt               time.Time
	ExpiresAt              time.Time
	Metadata               M
	CertificateInfo        CertificateInfo
	CertificateFingerprint string
	Permissions            []string
}

Context contains the security context information.

func (*Context[M]) Clone

func (c *Context[M]) Clone() *Context[M]

Clone creates a deep copy of the context for parallel processing.

func (*Context[M]) HasPermission

func (c *Context[M]) HasPermission(scope string) bool

HasPermission checks if the context data includes a specific permission scope.

func (*Context[M]) IsExpired

func (c *Context[M]) IsExpired() bool

IsExpired checks if the context data has expired.

type ContextCache

type ContextCache[M any] interface {
	// Get retrieves an active context by certificate fingerprint
	Get(ctx context.Context, fingerprint string) (*Context[M], bool)

	// Store stores or updates an active context
	Store(ctx context.Context, fingerprint string, sctx *Context[M])

	// Delete removes an active token
	Delete(ctx context.Context, fingerprint string) error

	// Start begins the cleanup goroutine
	Start(shutdown chan struct{}, wg *sync.WaitGroup)

	// Count returns the number of active contexts
	Count() int
}

ContextCache manages active contexts with automatic cleanup.

func NewBoundedMemoryCache added in v0.0.4

func NewBoundedMemoryCache[M any](opts CacheOptions) ContextCache[M]

NewBoundedMemoryCache creates a cache with optional size limits.

func NewMemoryContextCache added in v0.0.4

func NewMemoryContextCache[M any](cleanupInterval time.Duration) ContextCache[M]

NewMemoryContextCache creates a new in-memory context cache. cleanupInterval specifies how often expired contexts are cleaned up. If cleanupInterval is 0, defaults to 5 minutes.

type ContextGuard

type ContextGuard[M any] func(context.Context, *Context[M]) (*Context[M], error)

ContextGuard is a function that enriches a context in the pipeline.

func GrantPermissions

func GrantPermissions[M any](permissions ...string) ContextGuard[M]

GrantPermissions adds permissions to the context.

func RequireCertField

func RequireCertField[M any](field, expected string) ContextGuard[M]

RequireCertField ensures a certificate field matches an expected value.

func RequireCertPattern

func RequireCertPattern[M any](field, pattern string) ContextGuard[M]

RequireCertPattern ensures a certificate field matches a regex pattern.

func SetContext

func SetContext[M any](opts ContextOptions) ContextGuard[M]

SetContext sets multiple context fields at once for efficiency.

type ContextOptions

type ContextOptions struct {
	Expiry      *time.Duration
	Permissions []string
}

ContextOptions contains optional fields to set on a context.

type ContextPolicy

type ContextPolicy[M any] func(cert *x509.Certificate) (*Context[M], error)

ContextPolicy defines how to transform certificates into security contexts.

func DefaultContextPolicy

func DefaultContextPolicy[M any]() ContextPolicy[M]

DefaultContextPolicy provides a simple default policy that sets basic permissions and expiry.

type CryptoAlgorithm

type CryptoAlgorithm string

CryptoAlgorithm represents the supported cryptographic algorithms.

const (
	// CryptoEd25519 is the default high-performance algorithm (30% faster than ECDSA).
	CryptoEd25519 CryptoAlgorithm = "ed25519"

	// CryptoECDSAP256 is the FIPS 140-2 compliant algorithm for government/compliance requirements.
	CryptoECDSAP256 CryptoAlgorithm = "ecdsa-p256"

	// DefaultCryptoAlgorithm prioritizes performance - governments can opt into compliance.
	DefaultCryptoAlgorithm = CryptoEd25519
)

func DetectAlgorithmFromPrivateKey

func DetectAlgorithmFromPrivateKey(privateKey crypto.PrivateKey) (CryptoAlgorithm, error)

DetectAlgorithmFromPrivateKey determines the algorithm from a private key.

func DetectAlgorithmFromPublicKey

func DetectAlgorithmFromPublicKey(publicKey crypto.PublicKey) (CryptoAlgorithm, error)

DetectAlgorithmFromPublicKey determines the algorithm from a public key.

type CryptoSigner

type CryptoSigner interface {
	// Sign signs the provided data
	Sign(data []byte) ([]byte, error)

	// Verify verifies a signature against data and public key
	Verify(data []byte, signature []byte, publicKey crypto.PublicKey) bool

	// Algorithm returns the algorithm identifier
	Algorithm() CryptoAlgorithm

	// PublicKey returns the public key
	PublicKey() crypto.PublicKey

	// KeyType returns a string description for debugging
	KeyType() string
}

CryptoSigner provides algorithm-agnostic cryptographic operations.

func NewCryptoSigner

func NewCryptoSigner(algorithm CryptoAlgorithm, privateKey crypto.PrivateKey) (CryptoSigner, error)

NewCryptoSigner creates a new crypto signer for the specified algorithm.

type Guard

type Guard interface {
	ID() string
	Validate(ctx context.Context, tokens ...SignedToken) error
	Permissions() []string
}

Guard validates tokens against required permissions.

type NonceOptions added in v0.0.4

type NonceOptions struct {
	// MaxSize is the maximum number of nonces. 0 means unbounded.
	MaxSize int
}

NonceOptions configures nonce cache behavior.

func DefaultNonceOptions added in v0.0.4

func DefaultNonceOptions() NonceOptions

DefaultNonceOptions returns sensible defaults.

type Principal added in v1.0.1

type Principal interface {
	// Token returns the principal's signed token.
	Token() SignedToken

	// Inject embeds the principal's token into a context.Context.
	Inject(ctx context.Context) context.Context

	// Guard creates a context-aware guard that extracts tokens from context.Context.
	Guard(ctx context.Context, requiredPermissions ...string) (Guard, error)
}

Principal represents an authenticated in-process consumer with a defined role.

func NewPrincipal added in v1.0.1

func NewPrincipal[M any](ctx context.Context, admin Admin[M], privateKey crypto.PrivateKey, cert *x509.Certificate) (Principal, error)

NewPrincipal creates a principal by authenticating a certificate against the admin. It creates an assertion, generates a token, and returns a ready-to-use principal.

type SignedAssertion

type SignedAssertion struct {
	Claims    AssertionClaims
	Signature []byte
}

SignedAssertion represents a signed claim proving private key possession.

func CreateAssertion

func CreateAssertion(privateKey crypto.PrivateKey, cert *x509.Certificate) (SignedAssertion, error)

CreateAssertion helps clients create properly signed assertions.

type SignedToken

type SignedToken string

SignedToken is an opaque, tamper-proof security token. It contains a signed reference to a cached context. Tokens can only be created by the ContextService and must be verified before use.

func TokenFromContext added in v1.0.1

func TokenFromContext(ctx context.Context) (SignedToken, bool)

TokenFromContext extracts a SignedToken from context.Context.

Jump to

Keyboard shortcuts

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