acmeserver

package module
v0.1.0 Latest Latest
Warning

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

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

README

go-acme-server

GitHub Release Go Reference License Tests Interoperability Supply chain OpenSSF Scorecard

go-acme-server is a Go library for embedding an ACME server into CA and PKI applications.

The library owns the protocol: accounts, orders, authorizations, challenges, finalization, certificate retrieval and revocation as specified in RFC 8555. The host application supplies the parts that differ between deployments:

  • a Store that persists resources and background work, with memstore for tests and examples
  • an Issuer and a Revoker that call the host CA
  • one Validator per challenge type, with HTTP-01, DNS-01, TLS-ALPN-01 and tkauth-01 implementations in challenge
  • optional policy hooks, external account binding keys and directory metadata

The result is an http.Handler mounted behind the host's HTTPS origin and a worker that runs next to it. Standard ACME clients such as Certbot, lego, acmez and cert-manager then obtain certificates from the host CA.

Contents

Scope

Standard Coverage Enabled by
RFC 8555 ACME Accounts, external account binding, key rollover, orders, HTTP-01, DNS-01, finalization, certificates, revocation Always
RFC 8737 TLS-ALPN-01 Challenge validation A ChallengeTLSALPN01 validator
RFC 8738 IP identifiers IPv4 and IPv6 identifiers through HTTP-01 and TLS-ALPN-01 Config.IPIdentifiers
RFC 9447 and RFC 9448 tkauth-01 with TNAuthList identifiers Config.TNAuthListIdentifiers
RFC 9773 Renewal information and replaces Config.RenewalInfo

Account keys may use ES256, ES384, ES512, RS256 or EdDSA. External account bindings use HS256, HS384 or HS512. Independent clients verify the behavior, see Tested clients. The API may still change before v1.0.0 as described in the compatibility policy.

Limitations
  • No pre-authorization. The directory omits newAuthz and every order receives fresh authorizations, so clients validate each identifier again for every order.
  • No alternate chains. The certificate response carries one chain and no rel="alternate" link.
  • No certificate profiles, dns-account-01, short-term automatic renewal, delegation, subdomain authorizations or email, onion and device identifiers. Working group drafts are not exposed in the public API.
  • No rate limits. Policy and IssuancePolicy are the places to refuse accounts, orders or issuance.
  • No TLS termination or CA. The host serves the handler behind its HTTPS origin, signs with its own CA and supplies durable storage. memstore keeps everything in memory.

Known limitations explains the consequences of each gap.

Getting started

Create a Server, mount it under its base URL and run its worker. Both the handler and Run are required. Without Run, challenges are never validated and orders are never issued. Ready reports work that has waited too long for a worker.

srv, err := acmeserver.New(acmeserver.Config{
	BaseURL:    "https://ca.example.com/acme/",
	Store:      memstore.New(),
	Nonces:     nonce.New(nonce.Options{}),
	Issuer:     myCA,
	Revoker:    myCA,
	Validators: map[acmeserver.ChallengeType]acmeserver.Validator{acmeserver.ChallengeHTTP01: myHTTP01},
})
if err != nil {
	log.Fatal(err)
}
go srv.Run(ctx)
http.Handle("/acme/", srv)

BaseURL must use https unless AllowInsecureBaseURL is set for local tests. Signed request URLs must match it exactly, so run the handler behind the public origin it advertises.

The getting started guide builds a complete local server in one file, issues a certificate with lego and names what to replace before production. The package example in example_test.go shows the same wiring in Go documentation form.

Documentation

Full documentation is at misiektoja.github.io/go-acme-server.

The package documentation on pkg.go.dev describes every exported type. Repository files:

Support

SUPPORT.md directs usage questions, bug reports, feature requests and security reports. Check the troubleshooting page and gather the versions, the problem document and the server log before posting.

License

Licensed under Apache-2.0.

Documentation

Overview

Package acmeserver provides the ACME protocol layer for embedding an ACME server into CA and PKI applications. Certificate issuance, persistence and policy decisions are supplied by the host application through interfaces, so the package depends on no particular CA, database or deployment.

Embedding

New validates a Config and returns a Server that implements http.Handler. Mount it at the path of Config.BaseURL behind the HTTPS origin that URL names, because signed request URLs must match it exactly. Run processes the persisted validation and issuance work and must run in this or another process on the same Store, otherwise challenges are never validated and orders are never issued. Ready reports work that has waited too long for a worker. The package example shows a complete host.

Host interfaces

A Store persists accounts, orders, authorizations, challenges, certificates and background tasks. Its operations are atomic and guarded by resource revisions and task fences, which is what lets several workers share one store. The memstore package is the in-memory implementation for tests and examples and the storetest package checks a host adapter against the contract.

An Issuer signs certificates and a Revoker revokes them. Both receive a durable OperationID that is stored before the first call and repeated on every retry, so the host CA deduplicates. An Issuer must enforce IssueRequest.Deadline and RecoveryOnly. A chain that fails publication checks is kept in Order.UnpublishedResult for host reconciliation.

A Validator checks one challenge type. The challenge package implements HTTP-01, DNS-01, TLS-ALPN-01 and tkauth-01. A GrantingValidator reports what a proof authorizes beyond control of the identifier, which tkauth-01 uses for CA certificates and validity bounds.

Policy reviews new accounts and orders, IssuancePolicy reviews the first issuance dispatch and ExternalAccountKeys verifies external account bindings.

Extensions

RFC 8738 IP identifiers, RFC 9448 TNAuthList identifiers with the RFC 9447 tkauth-01 challenge and RFC 9773 renewal information are off until the matching Config field enables them.

Documentation

Step-by-step guides for hosts are at https://misiektoja.github.io/go-acme-server/.

Example

Shows how a host mounts the handler and runs the worker. Both are required.

package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"net/netip"
	"os"
	"os/signal"

	acmeserver "github.com/misiektoja/go-acme-server"
	"github.com/misiektoja/go-acme-server/challenge"
	"github.com/misiektoja/go-acme-server/memstore"
	"github.com/misiektoja/go-acme-server/nonce"
)

// A placeholder for the host CA. Real hosts sign with their own CA and deduplicate by OperationID.
type exampleCA struct{}

// Refuses every request in this example.
func (exampleCA) Issue(context.Context, acmeserver.IssueRequest) (acmeserver.IssueResult, error) {
	return acmeserver.IssueResult{Rejected: acmeserver.NewProblem(acmeserver.ErrorServerInternal, "not implemented")}, nil
}

// Refuses every request in this example.
func (exampleCA) Revoke(context.Context, acmeserver.RevokeRequest) error {
	return errors.New("not implemented")
}

// Shows how a host mounts the handler and runs the worker. Both are required.
func main() {
	resolver, err := challenge.NewResolver(challenge.ResolverOptions{
		Servers: []netip.AddrPort{netip.MustParseAddrPort("192.0.2.53:53")},
	})
	if err != nil {
		log.Fatal(err)
	}
	http01, err := challenge.NewHTTP01(challenge.HTTPOptions{
		Network: challenge.NetworkOptions{Resolver: resolver},
	})
	if err != nil {
		log.Fatal(err)
	}
	srv, err := acmeserver.New(acmeserver.Config{
		BaseURL: "https://ca.example.com/acme/",
		Store:   memstore.New(),
		Nonces:  nonce.New(nonce.Options{}),
		Issuer:  exampleCA{},
		Revoker: exampleCA{},
		Validators: map[acmeserver.ChallengeType]acmeserver.Validator{
			acmeserver.ChallengeHTTP01: http01,
		},
		Meta: acmeserver.DirectoryMeta{Website: "https://ca.example.com"},
	})
	if err != nil {
		log.Fatal(err)
	}
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()
	go func() {
		if err := srv.Run(ctx); err != nil {
			log.Print(err)
		}
	}()
	mux := http.NewServeMux()
	mux.Handle("/acme/", srv)
	// http.ListenAndServeTLS(":443", "cert.pem", "key.pem", mux) would serve the directory at
	// https://ca.example.com/acme/directory.
	_ = mux
}

Index

Examples

Constants

View Source
const (
	DefaultMaxRequestBody        = 64 << 10
	DefaultOrderLifetime         = 7 * 24 * time.Hour
	DefaultAuthorizationLifetime = 30 * 24 * time.Hour
	DefaultMaxIdentifiers        = 100
	DefaultDetachedWriteTimeout  = 30 * time.Second
)

Defaults used when Config leaves a field zero.

View Source
const DefaultRenewalRetryAfter = 6 * time.Hour

The Retry-After of renewalInfo responses when the advisor sets none.

View Source
const ProblemNamespace = "urn:ietf:params:acme:error:"

Prefixes every ACME error type in a problem document.

View Source
const TKAuthTypeATC = "atc"

The only Authority Token subtype this server offers, see RFC 9447 section 4.

Variables

View Source
var (
	ErrNotFound         = errors.New("acmeserver: resource not found")
	ErrConflict         = errors.New("acmeserver: resource already exists")
	ErrRevisionMismatch = errors.New("acmeserver: resource revision mismatch")
	// Returned by CreateOrder when order.Replaces names a certificate that another order, which
	// is not invalid at order.CreatedAt, already replaced.
	ErrAlreadyReplaced = errors.New("acmeserver: certificate already replaced")
)

Errors a Store returns for contract violations. Backend failures must use other errors.

Functions

This section is empty.

Types

type Account

type Account struct {
	ID     string
	Status AccountStatus
	// The account key. KeyThumbprint is its RFC 7638 thumbprint, unique among accounts.
	Key                  crypto.PublicKey
	KeyThumbprint        string
	Contact              []string
	TermsOfServiceAgreed bool
	// The identity the host derived from a verified external account binding.
	ExternalAccountID string
	// The single-use claim on that binding, set when Config.SingleUseExternalAccounts is on.
	// Stores keep non-empty claims unique among accounts.
	ExternalAccountClaim string
	CreatedAt            time.Time
	// Increases on every successful update and guards concurrent modification.
	Revision uint64
}

A stored ACME account. Its ID is opaque and appears only in the account URL.

type AccountStatus

type AccountStatus string

The status of an account resource, see RFC 8555 section 7.1.2.

const (
	AccountValid       AccountStatus = "valid"
	AccountDeactivated AccountStatus = "deactivated"
	AccountRevoked     AccountStatus = "revoked"
)

Account statuses.

func (AccountStatus) CanTransition

func (s AccountStatus) CanTransition(next AccountStatus) bool

Reports whether RFC 8555 section 7.1.6 allows the status to move to next.

func (AccountStatus) Known

func (s AccountStatus) Known() bool

Reports whether the status is one the protocol defines.

type AccountStore

type AccountStore interface {
	// Stores a new account with revision 1. It returns ErrConflict when the ID, the key
	// thumbprint or a non-empty ExternalAccountClaim is already in use.
	CreateAccount(ctx context.Context, account *Account) error
	// Returns the account with the given ID or ErrNotFound.
	Account(ctx context.Context, id string) (*Account, error)
	// Returns the account whose key has the given thumbprint or ErrNotFound.
	AccountByKey(ctx context.Context, thumbprint string) (*Account, error)
	// Replaces the stored account when the revisions match and increments
	// account.Revision. It returns ErrRevisionMismatch, ErrNotFound or ErrConflict otherwise.
	UpdateAccount(ctx context.Context, account *Account) error
}

Persists accounts. Every method is atomic and returns copies the caller owns.

type AllowAll

type AllowAll struct{}

A Policy that accepts every request.

func (AllowAll) NewAccount

func (AllowAll) NewAccount(context.Context, *Account) error

Accepts the account.

func (AllowAll) NewOrder

func (AllowAll) NewOrder(context.Context, *Account, *Order) error

Accepts the order.

type Authorization

type Authorization struct {
	ID         string
	AccountID  string
	OrderID    string
	Identifier Identifier
	Status     AuthorizationStatus
	Expires    time.Time
	// Marks a wildcard authorization. Identifier then holds the base name.
	Wildcard     bool
	ChallengeIDs []string
	// Records that the successful challenge also authorized a CA certificate for the identifier.
	CACertificate bool
	// The time after which the proof no longer covers a certificate, zero when unbounded.
	GrantExpires time.Time
	CreatedAt    time.Time
	Revision     uint64
}

A stored authorization resource. Each authorization belongs to exactly one order.

type AuthorizationStatus

type AuthorizationStatus string

The status of an authorization resource, see RFC 8555 section 7.1.4.

const (
	AuthorizationPending     AuthorizationStatus = "pending"
	AuthorizationValid       AuthorizationStatus = "valid"
	AuthorizationInvalid     AuthorizationStatus = "invalid"
	AuthorizationDeactivated AuthorizationStatus = "deactivated"
	AuthorizationExpired     AuthorizationStatus = "expired"
	AuthorizationRevoked     AuthorizationStatus = "revoked"
)

Authorization statuses.

func (AuthorizationStatus) CanTransition

func (s AuthorizationStatus) CanTransition(next AuthorizationStatus) bool

Reports whether RFC 8555 section 7.1.6 allows the status to move to next.

func (AuthorizationStatus) Known

func (s AuthorizationStatus) Known() bool

Reports whether the status is one the protocol defines.

func (AuthorizationStatus) Terminal

func (s AuthorizationStatus) Terminal() bool

Reports whether no further transition is possible.

type Certificate

type Certificate struct {
	// The base64url SHA-256 digest of the DER leaf, which names the certificate resource.
	ID        string
	AccountID string
	OrderID   string
	// The opaque reference returned by the host CA.
	CAReference string
	// Holds the DER leaf certificate followed by the DER issuer chain the client receives.
	Chain     [][]byte
	NotBefore time.Time
	NotAfter  time.Time
	Revoked   bool
	RevokedAt time.Time
	// The CRL reason code recorded at revocation.
	RevocationReason int
	// Identifies the revocation the host CA receives. It is committed with the reason before the
	// first CA call and reused by every retry, so the CA can deduplicate.
	RevocationOperationID string
	// When the revocation was recorded, before the CA call. RevokedAt is set after the CA succeeded.
	RevocationRequestedAt time.Time
	// The RFC 9773 identifier built from the leaf's authority key identifier and serial number.
	// It is empty when the leaf has no authority key identifier. Stores keep non-empty values
	// unique among certificates.
	RenewalID string
	// The order that claimed this certificate as its predecessor through replaces.
	ReplacedByOrderID string
	// The validation evidence the issuer received, kept for host audit needs.
	Validations []Validation
	CreatedAt   time.Time
	Revision    uint64
}

A stored issued certificate together with its chain.

type CertificateStore

type CertificateStore interface {
	// Returns the certificate with the given ID or ErrNotFound.
	Certificate(ctx context.Context, id string) (*Certificate, error)
	// Returns the certificate with the given non-empty RenewalID or ErrNotFound.
	CertificateByRenewalID(ctx context.Context, renewalID string) (*Certificate, error)
	// Replaces the stored certificate when the revisions match and increments cert.Revision.
	UpdateCertificate(ctx context.Context, cert *Certificate) error
}

Persists issued certificates.

type Challenge

type Challenge struct {
	ID              string
	AuthorizationID string
	AccountID       string
	Type            ChallengeType
	Status          ChallengeStatus
	Token           string
	// The Authority Token a tkauth-01 response carried, see RFC 9448 section 4.
	AuthorityToken string
	// The account key thumbprint captured at response time, so a key rollover
	// does not change an in-flight validation.
	KeyThumbprint string
	Validated     time.Time
	// Records why validation failed.
	Error    *Problem
	Revision uint64
}

A stored challenge resource.

type ChallengeStatus

type ChallengeStatus string

The status of a challenge resource, see RFC 8555 section 7.1.5.

const (
	ChallengePending    ChallengeStatus = "pending"
	ChallengeProcessing ChallengeStatus = "processing"
	ChallengeValid      ChallengeStatus = "valid"
	ChallengeInvalid    ChallengeStatus = "invalid"
)

Challenge statuses.

func (ChallengeStatus) CanTransition

func (s ChallengeStatus) CanTransition(next ChallengeStatus) bool

Reports whether the status may move to next, including pending to invalid per erratum 5732.

func (ChallengeStatus) Known

func (s ChallengeStatus) Known() bool

Reports whether the status is one the protocol defines.

func (ChallengeStatus) Terminal

func (s ChallengeStatus) Terminal() bool

Reports whether no further transition is possible.

type ChallengeType

type ChallengeType string

Names a challenge mechanism registered with IANA.

const (
	ChallengeHTTP01    ChallengeType = "http-01"
	ChallengeDNS01     ChallengeType = "dns-01"
	ChallengeTLSALPN01 ChallengeType = "tls-alpn-01"
	// Answered with an Authority Token instead of a network proof.
	ChallengeTKAuth01 ChallengeType = "tkauth-01"
)

Challenge types from RFC 8555 section 8, RFC 8737 and RFC 9447.

type Clock

type Clock interface {
	Now() time.Time
}

Supplies the current time.

func SystemClock

func SystemClock() Clock

Returns a Clock backed by the wall clock.

type ClockFunc

type ClockFunc func() time.Time

Adapts a function to the Clock interface.

func (ClockFunc) Now

func (f ClockFunc) Now() time.Time

Returns the time reported by the function.

type Config

type Config struct {
	// The absolute URL the handler is served under, including any path prefix.
	// It must use https unless AllowInsecureBaseURL is set.
	BaseURL string
	// Accepts an http BaseURL for tests and local examples.
	AllowInsecureBaseURL bool
	// Persists resources and background work.
	Store Store
	// Issues and consumes the single-use request nonces.
	Nonces NonceManager
	// Defaults to the system clock.
	Clock Clock
	// Receives operational events. It defaults to a logger that discards everything.
	Logger *slog.Logger
	// Optional directory metadata. Nothing is advertised when it is empty.
	Meta DirectoryMeta
	// Bounds the size of a request body in bytes. Zero selects DefaultMaxRequestBody.
	MaxRequestBody int64
	// Signs certificates for finalized orders.
	Issuer Issuer
	// Revokes issued certificates.
	Revoker Revoker
	// The challenge types offered to clients. Only configured types appear in authorizations.
	Validators map[ChallengeType]Validator
	// Verifies external account bindings. Required when Meta.ExternalAccountRequired is set.
	ExternalAccounts ExternalAccountKeys
	// Binds each external account key identifier to at most one account. The claim is committed
	// with the account, and a later newAccount with the same identifier and another key is refused.
	SingleUseExternalAccounts bool
	// Reviews new accounts and orders. Defaults to AllowAll.
	Policy Policy
	// Reviews current issuance policy before dispatch, with the accepted CSR and validation evidence.
	IssuancePolicy IssuancePolicy
	// Refuses new accounts that do not agree to Meta.TermsOfService.
	RequireTermsOfServiceAgreed bool
	// Accepts IP identifiers as specified in RFC 8738.
	IPIdentifiers bool
	// Accepts TNAuthList identifiers as specified in RFC 9448. It needs a tkauth-01 validator.
	TNAuthListIdentifiers bool
	// The https URL offered as token-authority on tkauth-01 challenges, see RFC 9447 section 3.
	// It is optional, and clients fall back to their own configuration when it is empty.
	TokenAuthority string
	// How long a new order and its pending authorizations stay valid.
	OrderLifetime time.Duration
	// How long a validated authorization stays valid.
	AuthorizationLifetime time.Duration
	// Bounds the identifiers of one order.
	MaxIdentifiers int
	// Bounds a store write that has to finish after the client request it belongs to has ended,
	// such as recording a revocation the Revoker already carried out. Zero selects
	// DefaultDetachedWriteTimeout.
	DetachedWriteTimeout time.Duration
	// Serves RFC 9773 renewal information and accepts replaces on new orders when set.
	// LifetimeRenewal is the built-in advisor. Nil leaves the extension off.
	RenewalInfo RenewalAdvisor
	// Tunes Run and Ready.
	Workers WorkerConfig
}

Configures a Server. Store, Nonces, Issuer, Revoker and at least one validator are required.

type DirectoryMeta

type DirectoryMeta struct {
	// The URL of the current terms of service.
	TermsOfService string
	// The URL of the CA's website.
	Website string
	// The hostnames the CA recognizes in CAA issue and issuewild records.
	CAAIdentities []string
	// Tells clients that newAccount needs an external account binding.
	ExternalAccountRequired bool
}

The optional metadata object of the directory, see RFC 8555 section 7.1.1.

type ErrorType

type ErrorType string

Names an ACME error without its URN namespace.

const (
	ErrorAccountDoesNotExist     ErrorType = "accountDoesNotExist"
	ErrorAlreadyReplaced         ErrorType = "alreadyReplaced"
	ErrorAlreadyRevoked          ErrorType = "alreadyRevoked"
	ErrorBadCSR                  ErrorType = "badCSR"
	ErrorBadNonce                ErrorType = "badNonce"
	ErrorBadPublicKey            ErrorType = "badPublicKey"
	ErrorBadRevocationReason     ErrorType = "badRevocationReason"
	ErrorBadSignatureAlgorithm   ErrorType = "badSignatureAlgorithm"
	ErrorCAA                     ErrorType = "caa"
	ErrorCompound                ErrorType = "compound"
	ErrorConnection              ErrorType = "connection"
	ErrorDNS                     ErrorType = "dns"
	ErrorDNSSEC                  ErrorType = "dnssec"
	ErrorExternalAccountRequired ErrorType = "externalAccountRequired"
	ErrorIncorrectResponse       ErrorType = "incorrectResponse"
	ErrorInvalidContact          ErrorType = "invalidContact"
	ErrorMalformed               ErrorType = "malformed"
	ErrorOrderNotReady           ErrorType = "orderNotReady"
	ErrorRateLimited             ErrorType = "rateLimited"
	ErrorRejectedIdentifier      ErrorType = "rejectedIdentifier"
	ErrorServerInternal          ErrorType = "serverInternal"
	ErrorTLS                     ErrorType = "tls"
	ErrorUnauthorized            ErrorType = "unauthorized"
	ErrorUnsupportedContact      ErrorType = "unsupportedContact"
	ErrorUnsupportedIdentifier   ErrorType = "unsupportedIdentifier"
	ErrorUserActionRequired      ErrorType = "userActionRequired"
)

Error types registered by RFC 8555 section 6.7 and RFC 9773 section 5.

func (ErrorType) URN

func (t ErrorType) URN() string

Returns the fully qualified problem type.

type ExternalAccountKeys

type ExternalAccountKeys interface {
	// Returns the MAC key for the key identifier or ErrNotFound.
	MACKey(ctx context.Context, keyID string) ([]byte, error)
}

Supplies the MAC keys that verify external account bindings, see RFC 8555 section 7.3.4.

type GrantingValidator

type GrantingValidator interface {
	Validator
	ValidateGrant(ctx context.Context, req ValidationRequest) (ValidationGrant, error)
}

Reports what a response authorizes in addition to checking it. A validator that only proves control of an identifier implements Validator alone, and the server then grants nothing.

type Identifier

type Identifier struct {
	Type  IdentifierType `json:"type"`
	Value string         `json:"value"`
}

An ACME identifier object.

func NormalizeIdentifiers

func NormalizeIdentifiers(ids []Identifier) ([]Identifier, error)

Normalizes every identifier and rejects duplicates in the normalized set.

func (Identifier) IsWildcard

func (id Identifier) IsWildcard() bool

Reports whether the identifier is a DNS name whose leftmost label is a wildcard.

func (Identifier) Normalize

func (id Identifier) Normalize() (Identifier, error)

Validates the identifier syntax and returns its canonical form. Errors are *Problem values with type unsupportedIdentifier, malformed or rejectedIdentifier. Issuance policy is left to the host.

func (Identifier) String

func (id Identifier) String() string

Returns the type and value joined by a colon.

type IdentifierType

type IdentifierType string

Names the kind of subject an order or authorization refers to.

const (
	IdentifierDNS IdentifierType = "dns"
	IdentifierIP  IdentifierType = "ip"
	// A base64url encoded DER TN Authorization List, see RFC 8226 section 9.
	IdentifierTNAuthList IdentifierType = "TNAuthList"
)

Identifier types registered by RFC 8555 section 9.7.7, RFC 8738 and RFC 9448.

type IssuancePolicy

type IssuancePolicy interface {
	AuthorizeIssuance(ctx context.Context, req IssueRequest) error
}

Reviews current policy before the first durable issuance dispatch.

type IssuanceState

type IssuanceState struct {
	OperationID  string
	AuthorizedAt time.Time
	Deadline     time.Time
	Validations  []Validation
}

Preserves the authorization deadline and evidence across uncertain issuance attempts.

type IssueRequest

type IssueRequest struct {
	OperationID string
	AccountID   string
	AccountURL  string
	OrderID     string
	// The parsed certificate request and its DER encoding. The signature and the identifier
	// set were already checked against the order.
	CSR    *x509.CertificateRequest
	CSRDER []byte
	// The normalized identifiers the order covers.
	Identifiers []Identifier
	// The requested validity. Zero values mean the host decides. An authority token expiry
	// narrows NotAfter. The issuer may return a shorter validity, but the leaf must fit inside
	// the requested window and must be valid already unless NotBefore is in the future.
	NotBefore time.Time
	NotAfter  time.Time
	// How each identifier was validated.
	Validations []Validation
	// The earliest order or authorization expiry, after which new signing is forbidden.
	Deadline time.Time
	// Allows only recovery of an existing result, never a new signing operation.
	RecoveryOnly bool
}

What the host CA receives when an order is ready for issuance. The same OperationID is presented on every attempt for one order, so the issuer can deduplicate retries.

type IssueResult

type IssueResult struct {
	// The DER leaf certificate followed by the DER issuer chain.
	Chain [][]byte
	// An opaque host CA reference retained with issued or unpublished results.
	CAReference string
	// Reports that the CA has not decided yet. The worker asks again after RetryAfter.
	Pending    bool
	RetryAfter time.Duration
	// A final refusal that becomes the order error.
	Rejected *Problem
}

The outcome of an issuance attempt. Exactly one of Chain, Pending and Rejected is set.

type Issuer

type Issuer interface {
	Issue(ctx context.Context, req IssueRequest) (IssueResult, error)
}

Issues or recovers one durable result per operation ID, enforcing Deadline and RecoveryOnly for new signing.

type LifetimeRenewal

type LifetimeRenewal struct {
	// Fractions of the lifetime at which the window starts and ends. Zero values mean two
	// thirds and five sixths.
	Start float64
	End   float64
	// Copied into every response.
	ExplanationURL string
	RetryAfter     time.Duration
}

A RenewalAdvisor that places the window at fixed fractions of the certificate lifetime and asks for immediate renewal of revoked certificates.

func (LifetimeRenewal) RenewalInfo

func (l LifetimeRenewal) RenewalInfo(_ context.Context, cert *Certificate) (RenewalInfo, error)

Returns the window for the certificate. A revoked certificate gets a window that opened at its revocation, so clients renew at once.

type NonceManager

type NonceManager interface {
	// Returns a fresh nonce in base64url form.
	Issue(ctx context.Context) (string, error)
	// Invalidates the nonce and reports whether it was valid. A nonce is valid exactly once.
	Consume(ctx context.Context, nonce string) (bool, error)
}

Issues and consumes the single-use nonces that protect requests against replay.

type Order

type Order struct {
	ID          string
	AccountID   string
	Status      OrderStatus
	Expires     time.Time
	Identifiers []Identifier
	// The requested validity. Zero values mean unset.
	NotBefore        time.Time
	NotAfter         time.Time
	AuthorizationIDs []string
	// Records why the order became invalid.
	Error *Problem
	// The DER request accepted at finalization. It never changes once set.
	CSR           []byte
	CertificateID string
	// The durable authorization decision made before the first CA call.
	Issuance *IssuanceState
	// A CA result withheld from clients and retained for host reconciliation.
	UnpublishedResult *IssueResult
	// The RFC 9773 identifier of the certificate this order replaces, set when the client sent
	// replaces and Config.RenewalInfo is enabled. Stores mark that certificate replaced by this
	// order when the order is created.
	Replaces  string
	CreatedAt time.Time
	Revision  uint64
}

A stored order resource.

func (*Order) StatusAt

func (o *Order) StatusAt(now time.Time) OrderStatus

Returns the status clients see at now, treating an expired pending or ready order as invalid.

type OrderStatus

type OrderStatus string

The status of an order resource, see RFC 8555 section 7.1.3.

const (
	OrderPending    OrderStatus = "pending"
	OrderReady      OrderStatus = "ready"
	OrderProcessing OrderStatus = "processing"
	OrderValid      OrderStatus = "valid"
	OrderInvalid    OrderStatus = "invalid"
)

Order statuses.

func (OrderStatus) CanTransition

func (s OrderStatus) CanTransition(next OrderStatus) bool

Reports whether RFC 8555 section 7.1.6 allows the status to move to next.

func (OrderStatus) Known

func (s OrderStatus) Known() bool

Reports whether the status is one the protocol defines.

func (OrderStatus) Terminal

func (s OrderStatus) Terminal() bool

Reports whether no further transition is possible.

type OrderStore

type OrderStore interface {
	// Stores an order together with its authorizations and challenges, all at revision 1.
	// It returns ErrConflict when any ID is already in use. When order.Replaces is set, the
	// same operation marks the certificate with that RenewalID as replaced by the order,
	// returning ErrNotFound when no such certificate exists and ErrAlreadyReplaced when an order
	// that is not invalid at order.CreatedAt already replaced it, see RFC 9773 section 5.
	CreateOrder(ctx context.Context, order *Order, authzs []*Authorization, challenges []*Challenge) error
	// Returns the order with the given ID or ErrNotFound.
	Order(ctx context.Context, id string) (*Order, error)
	// Returns up to limit order IDs of the account in creation order, starting after the
	// given ID. An empty after starts at the beginning.
	OrderIDs(ctx context.Context, accountID, after string, limit int) ([]string, error)
	// Returns the authorization with the given ID or ErrNotFound.
	Authorization(ctx context.Context, id string) (*Authorization, error)
	// Replaces the stored authorization when the revisions match and increments authz.Revision.
	// When the authorization reaches a terminal status, the same operation marks its order invalid
	// unless issuance was already dispatched.
	UpdateAuthorization(ctx context.Context, authz *Authorization) error
	// Reports whether the account holds a valid unexpired authorization for every identifier,
	// read in one consistent snapshot. Revocation by another account relies on it.
	AuthorizedFor(ctx context.Context, accountID string, identifiers []Identifier, now time.Time) (bool, error)
	// Returns the challenge with the given ID or ErrNotFound.
	Challenge(ctx context.Context, id string) (*Challenge, error)
}

Persists orders with their authorizations and challenges.

type Policy

type Policy interface {
	// Reviews a new account. The key, contacts and external account identity are set.
	NewAccount(ctx context.Context, account *Account) error
	// Reviews a new order after identifier normalization. The policy may change NotBefore and
	// NotAfter but not the identifiers.
	NewOrder(ctx context.Context, account *Account, order *Order) error
}

Reviews requests before the server stores their result. A returned *Problem is sent to the client, any other error is reported as serverInternal. Nil methods are not allowed, embed AllowAll to accept everything.

type Problem

type Problem struct {
	Type   ErrorType
	Detail string
	// Overrides the default HTTP status of the type when it is not zero.
	Status int
	// Names the identifier a subproblem refers to.
	Identifier  *Identifier
	Subproblems []*Problem
	// Lists the supported signature algorithms in a badSignatureAlgorithm problem.
	Algorithms []string
	// Sets the Retry-After header. It is not serialized.
	RetryAfter time.Duration
}

An ACME problem document, see RFC 7807 and RFC 8555 section 6.7. It implements error.

func AsProblem

func AsProblem(err error) (*Problem, bool)

Returns the Problem in the error chain of err, if there is one.

func Compound

func Compound(detail string, subproblems ...*Problem) *Problem

Returns a compound problem that carries the given subproblems.

func NewProblem

func NewProblem(t ErrorType, detail string) *Problem

Returns a problem of the given type with the default HTTP status of that type.

func Problemf

func Problemf(t ErrorType, format string, args ...any) *Problem

Returns a problem whose detail is formatted from the arguments.

func (*Problem) Error

func (p *Problem) Error() string

Returns the fully qualified type followed by the detail.

func (*Problem) HTTPStatus

func (p *Problem) HTTPStatus() int

Returns the status the problem is written with.

func (*Problem) MarshalJSON

func (p *Problem) MarshalJSON() ([]byte, error)

Writes the problem document with fully qualified types.

func (*Problem) UnmarshalJSON

func (p *Problem) UnmarshalJSON(data []byte) error

Reads a problem document and strips the ACME namespace from its types.

func (*Problem) WithIdentifier

func (p *Problem) WithIdentifier(id Identifier) *Problem

Returns a copy that names the affected identifier.

func (*Problem) WithRetryAfter

func (p *Problem) WithRetryAfter(d time.Duration) *Problem

Returns a copy that asks the client to wait before retrying.

func (*Problem) WithStatus

func (p *Problem) WithStatus(status int) *Problem

Returns a copy that overrides the HTTP status.

type RenewalAdvisor

type RenewalAdvisor interface {
	RenewalInfo(ctx context.Context, cert *Certificate) (RenewalInfo, error)
}

Suggests when a certificate should be renewed, see RFC 9773. Setting Config.RenewalInfo advertises the renewalInfo resource and accepts replaces on new orders.

type RenewalInfo

type RenewalInfo struct {
	// The window in which the client should renew. End must be later than Start.
	Start time.Time
	End   time.Time
	// An optional page explaining the window.
	ExplanationURL string
	// How long clients wait before asking again. Zero means DefaultRenewalRetryAfter.
	RetryAfter time.Duration
}

The renewalInfo object of RFC 9773 section 4.2 with the Retry-After the response carries.

type RevokeRequest

type RevokeRequest struct {
	// Stable across every attempt to revoke one certificate. It is stored before the first call.
	OperationID string
	Certificate *x509.Certificate
	DER         []byte
	// The CRL reason code recorded with the operation. A retry keeps the first recorded reason.
	Reason int
	// The account that requested the revocation. Empty when the certificate key signed the request.
	AccountID string
}

What the host CA receives for a revocation.

type Revoker

type Revoker interface {
	Revoke(ctx context.Context, req RevokeRequest) error
}

Revokes certificates. Revoke returns nil only after the CA recorded the revocation durably. A returned *Problem is sent to the client, any other error is reported as serverInternal. The CA must deduplicate by OperationID because a client retries after an uncertain answer.

type Server

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

Serves the ACME resources below Config.BaseURL. Mount it at the base path, for example http.Handle("/acme/", srv), and run Run in the same or another process. Without Run, challenges are never validated and orders are never issued.

func New

func New(cfg Config) (*Server, error)

Validates the configuration and returns a Server. It starts no background work.

func (*Server) Ready

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

Reports whether accepted work is being picked up. It returns an error when tasks have waited longer than WorkerConfig.StaleAfter without a claim, which means no Run is active.

func (*Server) Run

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

Processes persisted validation and issuance work until ctx is canceled. Without a worker in this process or another process on the same store, challenges and orders never progress. Canceling ctx stops new claims but does not interrupt a task already in flight, so Run can take several WorkerConfig.TaskTimeout budgets to return when a validator, issuer or store stops responding. Waiting for it is optional: a claimed task is leased and its commit is fenced, so another worker picks it up once the lease lapses.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

Routes a request to the resource named by its path.

type Store

The persistence contract a host supplies. Operations must be free of side effects a retry would repeat.

type Task

type Task struct {
	ID        string
	Kind      TaskKind
	TargetID  string
	AccountID string
	// The earliest time a worker may claim the task.
	RunAt time.Time
	// The number of claims so far.
	Attempts int
	// The end of the current lease. Zero when no worker holds the task.
	LeaseUntil time.Time
	// Increases on every claim. Completion calls must present the current value.
	Fence     uint64
	CreatedAt time.Time
}

A unit of persisted background work. Run claims tasks with a lease and a fence so a worker that lost its lease cannot commit a stale result.

type TaskKind

type TaskKind string

Names what a background task does.

const (
	// Validates the challenge named by Task.TargetID.
	TaskValidate TaskKind = "validate"
	// Issues the certificate for the order named by Task.TargetID.
	TaskIssue TaskKind = "issue"
)

Task kinds.

type Validation

type Validation struct {
	Identifier Identifier
	Type       ChallengeType
	Validated  time.Time
	// Reports that the validation authorized a CA certificate rather than an end-entity one.
	CACertificate bool
	// The latest acceptable certificate expiry under this proof, zero when unbounded.
	GrantExpires time.Time
}

Records how one identifier of an order was validated.

type ValidationGrant

type ValidationGrant struct {
	// Allows the order to be finalized with a certificate request that asks for a CA
	// certificate, see RFC 9448 section 6.
	CACertificate bool
	// Bounds the validity of certificates issued on this proof, see RFC 9447 section 7. The zero
	// value leaves the validity to the order and the issuer.
	Expires time.Time
}

What a successful challenge response authorizes beyond control of the identifier.

type ValidationRequest

type ValidationRequest struct {
	Challenge  Challenge
	Identifier Identifier
	// Marks a wildcard authorization. Identifier then holds the base name.
	Wildcard bool
	// The token joined with the account key thumbprint, see RFC 8555 section 8.1.
	KeyAuthorization string
	// The RFC 7638 thumbprint of the account key captured when the client responded.
	AccountKeyThumbprint string
	// The Authority Token of a tkauth-01 response, empty for every other challenge type.
	AuthorityToken string
}

What a validator receives for one challenge.

type Validator

type Validator interface {
	Validate(ctx context.Context, req ValidationRequest) error
}

Checks a challenge response. A nil result marks the challenge valid, a returned *Problem marks it invalid and any other error is retried until the attempt limit.

type WorkStore

type WorkStore interface {
	// Stores the challenge when its revision matches and enqueues the task in the same
	// operation. It returns ErrConflict when the task ID is in use.
	AcceptChallenge(ctx context.Context, challenge *Challenge, task *Task) error
	// Stores the order when its revision matches and enqueues the task in the same operation.
	FinalizeOrder(ctx context.Context, order *Order, task *Task) error
	// Stores order.Issuance as the durable dispatch decision after checking the task fence and the
	// revision and status of the order, the account and every authorization.
	BeginIssuance(ctx context.Context, task *Task, order *Order, account *Account, authzs []*Authorization) error
	// Leases the runnable task with the earliest RunAt, increments its fence and attempts
	// and returns a copy. It returns ErrNotFound when no task is runnable at now.
	ClaimTask(ctx context.Context, now, leaseUntil time.Time) (*Task, error)
	// Releases the lease and stores task.RunAt for a later claim.
	RescheduleTask(ctx context.Context, task *Task) error
	// Removes the task without touching any resource.
	FinishTask(ctx context.Context, task *Task) error
	// Stores the challenge, authorization and order when every revision matches and removes the
	// task, all in one operation. The order revision advances even when its fields are unchanged.
	CompleteValidation(ctx context.Context, task *Task, challenge *Challenge, authz *Authorization, order *Order) error
	// Stores the order when its revision matches, creates the certificate when it is not nil
	// and removes the task, all in one operation. It returns ErrConflict when the certificate
	// ID or a non-empty RenewalID is in use.
	CompleteIssuance(ctx context.Context, task *Task, order *Order, cert *Certificate) error
	// Counts tasks that were runnable at or before the given time and hold no lease past it.
	PendingTasks(ctx context.Context, before time.Time) (int, error)
}

Persists background work together with the resource changes that create or finish it. Every task method that takes a claimed task checks task.Fence against the stored fence and returns ErrRevisionMismatch when another claim superseded it.

type WorkerConfig

type WorkerConfig struct {
	// The number of tasks processed at the same time. Defaults to 4.
	Concurrency int
	// How often an idle worker looks for work. Defaults to one second.
	PollInterval time.Duration
	// How long a claimed task stays leased. Defaults to two minutes.
	Lease time.Duration
	// Bounds each phase of a task separately: one validator or issuer call and the store
	// operations that record its outcome. A task run uses several of these budgets in
	// sequence. Defaults to 30 seconds.
	TaskTimeout time.Duration
	// Limits validation and pre-dispatch policy retries to five by default without limiting issuance recovery.
	MaxAttempts int
	// The first retry delay, doubled on every further attempt. Defaults to five seconds.
	RetryDelay time.Duration
	// How long accepted work may wait unclaimed before Ready reports a problem. Defaults to
	// one minute.
	StaleAfter time.Duration
	// Marks that another process runs Run against the same store, which silences the warning
	// logged when work is accepted while Run is inactive here.
	External bool
}

Configures Run. Zero values select the defaults.

Directories

Path Synopsis
Package challenge provides ACME network validators with explicit resolver and egress policy.
Package challenge provides ACME network validators with explicit resolver and egress policy.
internal
jws
Package jws parses and verifies the restricted JWS profile of RFC 8555 section 6.2.
Package jws parses and verifies the restricted JWS profile of RFC 8555 section 6.2.
Package memstore provides an in-memory acmeserver.Store for tests and examples.
Package memstore provides an in-memory acmeserver.Store for tests and examples.
Package nonce provides a bounded single-process implementation of acmeserver.NonceManager.
Package nonce provides a bounded single-process implementation of acmeserver.NonceManager.
Package storetest checks an acmeserver.Store against the persistence contract.
Package storetest checks an acmeserver.Store against the persistence contract.

Jump to

Keyboard shortcuts

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