acme

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package acme implements the server side of RFC 8555 — the protocol cert-manager, Traefik, Caddy, certbot and acme.sh all speak.

It is what turns Certio from a place someone goes to click "issue" into infrastructure: a workload points its existing ACME client at this directory and its internal certificates renew themselves, with no operator in the loop and nothing new to install.

The JOSE handling here is deliberately a small hand-written subset rather than a general library. ACME uses exactly one serialisation (flattened JSON JWS), a closed set of signature algorithms, and no encryption at all — and a full JOSE implementation brings along the algorithm agility that has been the source of most JWT vulnerabilities. Nothing below will ever accept "none", or let a header choose which key verifies it.

Index

Constants

View Source
const (
	AlgRS256 = "RS256"
	AlgRS384 = "RS384"
	AlgRS512 = "RS512"
	AlgES256 = "ES256"
	AlgES384 = "ES384"
	AlgES512 = "ES512"
	AlgEdDSA = "EdDSA"
	AlgHS256 = "HS256"
)

Signature algorithms Certio accepts. Anything else is refused by name, so a client using an exotic one gets a clear error rather than a silent failure.

View Source
const (
	StatusPending     = "pending"
	StatusReady       = "ready"
	StatusProcessing  = "processing"
	StatusValid       = "valid"
	StatusInvalid     = "invalid"
	StatusDeactivated = "deactivated"
	StatusExpired     = "expired"
	StatusRevoked     = "revoked"
)

Resource statuses, as RFC 8555 §7.1.6 defines them.

View Source
const (
	ChallengeHTTP01 = "http-01"
	ChallengeDNS01  = "dns-01"
)

Challenge types Certio can validate.

View Source
const (
	ErrAccountDoesNotExist     = "urn:ietf:params:acme:error:accountDoesNotExist"
	ErrBadCSR                  = "urn:ietf:params:acme:error:badCSR"
	ErrBadNonce                = "urn:ietf:params:acme:error:badNonce"
	ErrBadPublicKey            = "urn:ietf:params:acme:error:badPublicKey"
	ErrBadRevocationReason     = "urn:ietf:params:acme:error:badRevocationReason"
	ErrBadSignatureAlgorithm   = "urn:ietf:params:acme:error:badSignatureAlgorithm"
	ErrConnection              = "urn:ietf:params:acme:error:connection"
	ErrDNS                     = "urn:ietf:params:acme:error:dns"
	ErrExternalAccountRequired = "urn:ietf:params:acme:error:externalAccountRequired"
	ErrIncorrectResponse       = "urn:ietf:params:acme:error:incorrectResponse"
	ErrInvalidContact          = "urn:ietf:params:acme:error:invalidContact"
	ErrMalformed               = "urn:ietf:params:acme:error:malformed"
	ErrOrderNotReady           = "urn:ietf:params:acme:error:orderNotReady"
	ErrRateLimited             = "urn:ietf:params:acme:error:rateLimited"
	ErrRejectedIdentifier      = "urn:ietf:params:acme:error:rejectedIdentifier"
	ErrServerInternal          = "urn:ietf:params:acme:error:serverInternal"
	ErrUnauthorized            = "urn:ietf:params:acme:error:unauthorized"
	ErrUnsupportedIdentifier   = "urn:ietf:params:acme:error:unsupportedIdentifier"
	ErrUserActionRequired      = "urn:ietf:params:acme:error:userActionRequired"
	ErrAlreadyRevoked          = "urn:ietf:params:acme:error:alreadyRevoked"
)

Problem type URNs from the IANA registry.

Variables

This section is empty.

Functions

func Base64URL

func Base64URL(data []byte) string

Base64URL exposes the encoding for callers that need to build a token.

func DNSRecordValue

func DNSRecordValue(keyAuthorization string) string

DNSRecordValue is what a dns-01 TXT record must contain: the base64url SHA-256 of the key authorization, not the authorization itself.

func KeyAuthorization

func KeyAuthorization(token string, key *JWK) (string, error)

KeyAuthorization is the value a challenge is proved with (RFC 8555 §8.1).

func ParseJWS

func ParseJWS(body []byte) (*JWS, *ProtectedHeader, []byte, error)

ParseJWS decodes the flattened serialisation and its protected header.

Types

type Account

type Account struct {
	Status  string   `json:"status"`
	Contact []string `json:"contact,omitempty"`
	Orders  string   `json:"orders,omitempty"`
	// TermsOfServiceAgreed is echoed back so a client can confirm what it sent.
	TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
}

Account is an ACME account as the API returns it.

type Authorization

type Authorization struct {
	Identifier Identifier  `json:"identifier"`
	Status     string      `json:"status"`
	Expires    string      `json:"expires,omitempty"`
	Challenges []Challenge `json:"challenges"`
	Wildcard   bool        `json:"wildcard,omitempty"`
}

Authorization is one identifier's proof-of-control state.

type Challenge

type Challenge struct {
	Type      string   `json:"type"`
	URL       string   `json:"url"`
	Status    string   `json:"status"`
	Token     string   `json:"token"`
	Validated string   `json:"validated,omitempty"`
	Error     *Problem `json:"error,omitempty"`
}

Challenge is one way of proving control.

type Directory

type Directory struct {
	NewNonce   string        `json:"newNonce"`
	NewAccount string        `json:"newAccount"`
	NewOrder   string        `json:"newOrder"`
	RevokeCert string        `json:"revokeCert"`
	KeyChange  string        `json:"keyChange"`
	Meta       DirectoryMeta `json:"meta"`
}

Directory is the entry point every ACME client fetches first.

type DirectoryMeta

type DirectoryMeta struct {
	TermsOfService          string   `json:"termsOfService,omitempty"`
	Website                 string   `json:"website,omitempty"`
	CAAIdentities           []string `json:"caaIdentities,omitempty"`
	ExternalAccountRequired bool     `json:"externalAccountRequired,omitempty"`
}

DirectoryMeta carries the human-facing metadata.

type FinalizeRequest

type FinalizeRequest struct {
	CSR string `json:"csr"`
}

FinalizeRequest carries the CSR.

type Identifier

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

Identifier names something a certificate is requested for.

type JWK

type JWK struct {
	Kty string `json:"kty"`
	// EC
	Crv string `json:"crv,omitempty"`
	X   string `json:"x,omitempty"`
	Y   string `json:"y,omitempty"`
	// RSA
	N string `json:"n,omitempty"`
	E string `json:"e,omitempty"`
}

JWK is a public key in JSON form (RFC 7517), restricted to the key types ACME clients actually present.

func DecodeJWK

func DecodeJWK(encoded string) (*JWK, error)

DecodeJWK reads a stored key back.

func (*JWK) Encode

func (k *JWK) Encode() (string, error)

Encode renders a JWK as JSON for storage, so an account key can be compared byte for byte on a later request.

func (*JWK) PublicKey

func (k *JWK) PublicKey() (crypto.PublicKey, error)

PublicKey converts a JWK into a crypto.PublicKey.

func (*JWK) Thumbprint

func (k *JWK) Thumbprint() (string, error)

Thumbprint is the RFC 7638 SHA-256 thumbprint, base64url-encoded.

It is the account's stable identity: the key authorization a challenge is validated against is "<token>.<thumbprint>", which is what ties a DNS record or an HTTP file to the specific account that asked for the certificate rather than to anyone who observed the token.

type JWS

type JWS struct {
	Protected string `json:"protected"`
	Payload   string `json:"payload"`
	Signature string `json:"signature"`
}

JWS is the flattened JSON serialisation ACME requires (RFC 8555 §6.2).

func (*JWS) Verify

func (j *JWS) Verify(header *ProtectedHeader, key crypto.PublicKey) error

Verify checks the signature over the protected header and payload.

func (*JWS) VerifyHMAC

func (j *JWS) VerifyHMAC(alg string, secret []byte) error

VerifyHMAC checks a signature made with a shared secret, which is how external account binding proves the client holds the key an administrator issued out of band.

type NewAccountRequest

type NewAccountRequest struct {
	Contact                []string `json:"contact,omitempty"`
	TermsOfServiceAgreed   bool     `json:"termsOfServiceAgreed,omitempty"`
	OnlyReturnExisting     bool     `json:"onlyReturnExisting,omitempty"`
	ExternalAccountBinding *JWS     `json:"externalAccountBinding,omitempty"`
}

NewAccountRequest is the newAccount payload.

type NewOrderRequest

type NewOrderRequest struct {
	Identifiers []Identifier `json:"identifiers"`
	NotBefore   string       `json:"notBefore,omitempty"`
	NotAfter    string       `json:"notAfter,omitempty"`
}

NewOrderRequest is the newOrder payload.

type Order

type Order struct {
	Status         string       `json:"status"`
	Expires        string       `json:"expires,omitempty"`
	Identifiers    []Identifier `json:"identifiers"`
	NotBefore      string       `json:"notBefore,omitempty"`
	NotAfter       string       `json:"notAfter,omitempty"`
	Error          *Problem     `json:"error,omitempty"`
	Authorizations []string     `json:"authorizations"`
	Finalize       string       `json:"finalize"`
	Certificate    string       `json:"certificate,omitempty"`
}

Order is an order as the API returns it.

type Problem

type Problem struct {
	Type        string      `json:"type"`
	Detail      string      `json:"detail,omitempty"`
	Status      int         `json:"status,omitempty"`
	Identifier  *Identifier `json:"identifier,omitempty"`
	Subproblems []Problem   `json:"subproblems,omitempty"`
}

Problem is RFC 7807 as ACME profiles it (§6.7). Clients parse the type field and act on it, so the URNs below are load-bearing rather than decorative — certbot decides whether to retry from this alone.

func NewProblem

func NewProblem(problemType, format string, args ...any) *Problem

NewProblem builds a problem document with the status the type implies.

func (*Problem) Error

func (p *Problem) Error() string

Error makes Problem usable as an error, so the handlers can return one.

type ProtectedHeader

type ProtectedHeader struct {
	Alg   string `json:"alg"`
	Nonce string `json:"nonce"`
	URL   string `json:"url"`
	// Exactly one of JWK and KID is present: a JWK for the requests that
	// introduce a key (new-account, revoke-cert with the certificate key), a
	// KID — the account URL — for everything afterwards.
	JWK *JWK   `json:"jwk,omitempty"`
	KID string `json:"kid,omitempty"`
}

ProtectedHeader is the subset of the JOSE header ACME defines.

type RevokeRequest

type RevokeRequest struct {
	Certificate string `json:"certificate"`
	Reason      *int   `json:"reason,omitempty"`
}

RevokeRequest asks for a certificate to be revoked.

type UpdateAccountRequest

type UpdateAccountRequest struct {
	Contact []string `json:"contact,omitempty"`
	Status  string   `json:"status,omitempty"`
}

UpdateAccountRequest changes a contact list or deactivates an account.

type Validator

type Validator struct {
	// HTTPClient fetches http-01 challenges. Redirects are followed because
	// RFC 8555 §8.3 allows them, but only a bounded number.
	HTTPClient *http.Client
	// Resolver answers dns-01 lookups. A private PKI usually wants the
	// internal resolver rather than the system one, since the names being
	// validated only exist there.
	Resolver *net.Resolver
	// HTTPPort is the port http-01 is fetched on. RFC 8555 fixes it at 80 and
	// forbids anything else for the public internet, but a private CA on a
	// segmented network sometimes has no choice.
	HTTPPort int
}

Validator proves that whoever asked for a certificate controls the name.

Both challenge types are outbound checks made by the server, which is the only arrangement that means anything: a client asserting it controls a name proves nothing, so Certio goes and looks.

func NewValidator

func NewValidator(resolverAddress string, httpPort int) *Validator

NewValidator builds a validator with sane defaults.

func (*Validator) Validate

func (v *Validator) Validate(ctx context.Context, challengeType, identifier, token, keyAuthorization string) *Problem

Validate performs the challenge and returns a problem when it fails.

The returned error is always a *Problem, because the failure goes straight into the challenge object the client polls — and "connection refused" versus "wrong content" is the difference between a firewall and a misconfiguration.

Jump to

Keyboard shortcuts

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