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
- func Base64URL(data []byte) string
- func DNSRecordValue(keyAuthorization string) string
- func KeyAuthorization(token string, key *JWK) (string, error)
- func ParseJWS(body []byte) (*JWS, *ProtectedHeader, []byte, error)
- type Account
- type Authorization
- type Challenge
- type Directory
- type DirectoryMeta
- type FinalizeRequest
- type Identifier
- type JWK
- type JWS
- type NewAccountRequest
- type NewOrderRequest
- type Order
- type Problem
- type ProtectedHeader
- type RevokeRequest
- type UpdateAccountRequest
- type Validator
Constants ¶
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.
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.
const ( ChallengeHTTP01 = "http-01" ChallengeDNS01 = "dns-01" )
Challenge types Certio can validate.
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" 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 DNSRecordValue ¶
DNSRecordValue is what a dns-01 TXT record must contain: the base64url SHA-256 of the key authorization, not the authorization itself.
func KeyAuthorization ¶
KeyAuthorization is the value a challenge is proved with (RFC 8555 §8.1).
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 ¶
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 (*JWK) Encode ¶
Encode renders a JWK as JSON for storage, so an account key can be compared byte for byte on a later request.
func (*JWK) Thumbprint ¶
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).
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 ¶
NewProblem builds a problem document with the status the type implies.
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 ¶
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.