acmez

package module
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2024 License: Apache-2.0 Imports: 26 Imported by: 1

README

acmez - ACME client library for Go

godoc

ACMEz ("ack-measy" or "acme-zee", whichever you prefer) is a fully-compliant RFC 8555 (ACME) implementation in pure Go. It is lightweight, has an elegant Go API, and its retry logic is highly robust against external errors. ACMEz is suitable for large-scale enterprise deployments. It also supports common IETF-standardized ACME extensions.

NOTE: This module is for getting certificates, not managing certificates. Most users probably want certificate management (keeping certificates renewed) rather than to interface directly with ACME. Developers who want to use certificates in their long-running Go programs should use CertMagic instead; or, if their program is not written in Go, Caddy can be used to manage certificates (even without running an HTTP or TLS server if needed).

This module has two primary packages:

  • acmez is a high-level wrapper for getting certificates. It implements the ACME order flow described in RFC 8555 including challenge solving using pluggable solvers.
  • acme is a low-level RFC 8555 implementation that provides the fundamental ACME operations, mainly useful if you have advanced or niche requirements.

In other words, the acmez package is porcelain while the acme package is plumbing (to use git's terminology).

Features

  • Simple, elegant Go API
  • Thoroughly documented with spec citations
  • Robust to external errors
  • Structured error values ("problems" as defined in RFC 7807)
  • Smart retries (resilient against network and server hiccups)
  • Challenge plasticity (randomized challenges, and will retry others if one fails)
  • Context cancellation (suitable for high-frequency config changes or reloads)
  • Highly flexible and customizable
  • External Account Binding (EAB) support
  • Tested with numerous ACME CAs (more than just Let's Encrypt)
  • Implements niche aspects of RFC 8555 (such as alt cert chains and account key rollover)
  • Efficient solving of large SAN lists (e.g. for slow DNS record propagation)
  • Utility functions for solving challenges
  • ACME Renewal Information (ARI) support (draft-ietf-acme-ari-03)

Install

go get github.com/mholt/acmez/v2

Examples

See the examples folder for tutorials on how to use either package. Most users should follow the porcelain guide to get started.

Challenge solvers

The acmez package is "bring-your-own-solver." It provides helper utilities for http-01, dns-01, and tls-alpn-01 challenges, but does not actually solve them for you. You must write or use an implementation of acmez.Solver in order to get certificates. How this is done depends on your environment/situation.

However, you can find a general-purpose dns-01 solver in CertMagic, which uses libdns packages to integrate with numerous DNS providers. You can use it like this:

// minimal example using Cloudflare
solver := &certmagic.DNS01Solver{
	DNSManager: certmagic.DNSManager{
		DNSProvider: &cloudflare.Provider{APIToken: "topsecret"},
	},
}
client := acmez.Client{
	ChallengeSolvers: map[string]acmez.Solver{
		acme.ChallengeTypeDNS01: solver,
	},
	// ...
}

If you're implementing a tls-alpn-01 solver, the acmez package can help. It has the constant ACMETLS1Protocol which you can use to identify challenge handshakes by inspecting the ClientHello's ALPN extension. Simply complete the handshake using a certificate from the acmez.TLSALPN01ChallengeCert() function to solve the challenge.

History

In 2014, the ISRG was finishing the development of its automated CA infrastructure: the first of its kind to become publicly-trusted, under the name Let's Encrypt, which used a young protocol called ACME to automate domain validation and certificate issuance.

Meanwhile, a project called Caddy was being developed which would be the first and only web server to use HTTPS automatically and by default. To make that possible, another project called lego was commissioned by the Caddy project to become of the first-ever ACME client libraries, and the first client written in Go. It was made by Sebastian Erhart (xenolf), and on day 1 of Let's Encrypt's public beta, Caddy used lego to obtain its first certificate automatically at startup, making Caddy and lego the first-ever integrated ACME client.

Since then, Caddy has seen use in production longer than any other ACME client integration, and is well-known for being one of the most robust and reliable HTTPS implementations available today.

A few years later, Caddy's novel auto-HTTPS logic was extracted into a library called CertMagic to be usable by any Go program. Caddy would continue to use CertMagic, which implemented the certificate automation and management logic on top of the low-level certificate obtain logic that lego provided.

Soon thereafter, the lego project shifted maintainership and the goals and vision of the project diverged from those of Caddy's use case of managing tens of thousands of certificates per instance. Eventually, the original Caddy author announced work on a new ACME client library in Go that satisfied Caddy's harsh requirements for large-scale enterprise deployments, lean builds, and simple API. This work exceeded expectations and finally came to fruition in 2020 as ACMEz. It is much more lightweight with zero core dependencies, has a simple and elegant code base, and is thoroughly documented and easy to build upon.

[!NOTE] This is not an official repository of the Caddy Web Server organization.


(c) 2020 Matthew Holt

Documentation

Overview

Package acmez implements the higher-level flow of the ACME specification, RFC 8555: https://tools.ietf.org/html/rfc8555, specifically the sequence in Section 7.1 (page 21).

It makes it easy to obtain certificates with various challenge types using pluggable challenge solvers, and provides some handy utilities for implementing solvers and using the certificates. It DOES NOT manage certificates, it only gets them from the ACME server.

NOTE: This package's primary purpose is to get a certificate, not manage it. Most users actually want to *manage* certificates over the lifetime of long-running programs such as HTTPS or TLS servers, and should use CertMagic instead: https://github.com/caddyserver/certmagic.

COMPATIBILITY: Exported identifiers that are related to draft specifications are subject to change or removal without a major version bump.

Index

Constants

View Source
const ACMETLS1Protocol = "acme-tls/1"

ACMETLS1Protocol is the ALPN value for the TLS-ALPN challenge handshake. See RFC 8737 §6.2.

Variables

This section is empty.

Functions

func MailReplyChallengeResponse

func MailReplyChallengeResponse(c acme.Challenge, mailSubject string, messageId string, replyTo string) string

MailReplyChallengeResponse builds an email response body including headers to reply to the email-reply-00 challenge email. This function only builds the email body; sending the message has to be performed by the caller of this function. The mailSubject and messageId come from the challenge mail, and if there is no reply-to header in the challenge email, the replyTo parameter should be empty.

func NewCSR

func NewCSR(privateKey crypto.Signer, sans []string) (*x509.CertificateRequest, error)

NewCSR creates and signs a Certificate Signing Request (CSR) for the given subject identifiers (SANs) with the private key.

If you need extensions or other customizations, this function is too opinionated. Instead, create a new(x509.CertificateRequest), then fill out the relevant fields (SANs, extensions, etc.), send it into x509.CreateCertificateRequest(), then pass that result into x509.ParseCertificateRequest() to get the final, parsed CSR. We chose this API to offer the most common convenience functions, but also to give users advanced flexibility when needed, all while reducing allocations from encoding & decoding each CSR and minimizing having to pass the private key around.

Supported SAN types are IPs, email addresses, URIs, and DNS names.

EXPERIMENTAL: This API is subject to change or removal without a major version bump.

func TLSALPN01ChallengeCert

func TLSALPN01ChallengeCert(challenge acme.Challenge) (*tls.Certificate, error)

TLSALPN01ChallengeCert creates a certificate that can be used for handshakes while solving the tls-alpn-01 challenge. See RFC 8737 §3.

Types

type CSRSource

type CSRSource interface {
	// CSR returns a Certificate Signing Request that will be
	// given to the ACME server. This function is called after
	// an ACME challenge completion and before order finalization.
	//
	// The returned CSR must have the Raw field populated with the
	// DER-encoded certificate request signed by the private key.
	// Typically this involves creating a template CSR, then calling
	// x509.CreateCertificateRequest(), then x509.ParseCertificateRequest()
	// on the output. That should return a valid CSR. The NewCSR()
	// function in this package does this for you, but if you need more
	// control you should make it yourself.
	//
	// The Subject CommonName field is NOT considered.
	CSR(context.Context, []acme.Identifier) (*x509.CertificateRequest, error)
}

CSRSource is an interface that provides users of this package the ability to provide a CSR as part of the ACME flow. This allows the final CSR to be provided just before the Order is finalized, which is useful for certain challenge types (e.g. device-attest-01, where the key used for signing the CSR doesn't exist until the challenge has been validated).

EXPERIMENTAL: Subject to change (though unlikely, and nothing major).

func StaticCSR

func StaticCSR(csr *x509.CertificateRequest) CSRSource

StaticCSR returns a CSRSource that simply returns the input CSR.

type Client

type Client struct {
	*acme.Client

	// Map of solvers keyed by name of the challenge type.
	ChallengeSolvers map[string]Solver
}

Client is a high-level API for ACME operations. It wraps a lower-level ACME client with useful functions to make common flows easier, especially for the issuance of certificates.

func (*Client) ObtainCertificate

func (c *Client) ObtainCertificate(ctx context.Context, params OrderParameters) ([]acme.Certificate, error)

ObtainCertificate obtains all certificate chains from the ACME server resulting from the given order parameters. The private key passed in must be the one that was (or will be) used to sign the CSR. The order parameters must be fully populated with an account, a list of subject identifiers, and a CSR source; and the list of subject identifiers must exactly match those in the CSR.

The method implements every single part of the ACME flow described in RFC 8555 §7.1 with the exception of "Create account" because account management is outside the scope of certificate issuance. The account's status MUST be "valid" in order to succeed.

func (*Client) ObtainCertificateForSANs

func (c *Client) ObtainCertificateForSANs(ctx context.Context, account acme.Account, certPrivateKey crypto.Signer, sans []string) ([]acme.Certificate, error)

ObtainCertificateForSANs is a light wrapper over ObtainCertificate that generates a simple CSR for the identifiers given in the list of SANs using the given private key; then it obtains a certificate right away. If you require customizing the parameters of the order, use ObtainCertificate instead.

type OrderParameters

type OrderParameters struct {
	// The ACME account with which to perform certificate operations.
	// It should already be registered with the server and have a
	// "valid" status.
	Account acme.Account

	// The list of identifiers for which to issue the certificate.
	// Identifiers may become Subject Alternate Names (SANs) in the
	// certificate. This slice must be consistent with the SANs
	// listed in the CSR. The OrderFromCSR() function can be
	// called to ensure consistency in most cases.
	//
	// Supported identifier types are currently: dns, ip,
	// permanent-identifier, and hardware-module.
	Identifiers []acme.Identifier

	// CSR is a type that can provide the Certificate Signing
	// Request, which is needed when finalizing the ACME order.
	// It is invoked after challenges have completed and before
	// finalization.
	CSR CSRSource

	// Optionally customize the lifetime of the certificate by
	// specifying the NotBefore and/or NotAfter dates for the
	// certificate. Not all CAs support this. Check your CA's
	// ACME service documentation.
	NotBefore, NotAfter time.Time

	// Set this to the old certificate if a certificate is being renewed.
	//
	// DRAFT: EXPERIMENTAL ARI DRAFT SPEC. Subject to change/removal.
	Replaces *x509.Certificate
}

OrderParameters contains high-level input parameters for ACME transactions, the state of which are represented by Order objects. This type is used as a convenient high-level way to convey alk the configuration needed to obtain a certificate (except the private key, which is provided separately to prevent inadvertent exposure of secret material) through ACME in one consolidated value.

Account, Identifiers, and CSR fields are REQUIRED.

func OrderParametersFromCSR

func OrderParametersFromCSR(account acme.Account, csr *x509.CertificateRequest) (OrderParameters, error)

OrderParametersFromCSR makes a valid OrderParameters from the given CSR. If necessary, the returned parameters may be further customized before using.

EXPERIMENTAL: This API is subject to change or removal without a major version bump.

type Payloader

type Payloader interface {
	Payload(context.Context, acme.Challenge) (any, error)
}

Payloader is an optional interface for Solvers to implement. Its purpose is to return the payload sent to the CA when responding to a challenge. This interface is applicable when responding to "device-attest-01" challenges

If implemented, it will be called after Present() and if a Waiter is implemented it will be called after Wait(), just before the challenge is initiated with the server.

Implementations MUST honor context cancellation.

type Solver

type Solver interface {
	// Present is called just before a challenge is initiated.
	// The implementation MUST prepare anything that is necessary
	// for completing the challenge; for example, provisioning
	// an HTTP resource, TLS certificate, or a DNS record.
	//
	// It MUST return quickly. If presenting the challenge token
	// will take time, then the implementation MUST do the
	// minimum amount of work required in this method, and
	// SHOULD additionally implement the Waiter interface.
	// For example, a DNS challenge solver might make a quick
	// HTTP request to a provider's API to create a new DNS
	// record, but it might be several minutes or hours before
	// the DNS record propagates. The API request should be
	// done in Present(), and waiting for propagation should
	// be done in Wait().
	// Another example is the email-reply-00 challenge, because
	// it can take a while for an ACME server to send a challenge
	// email and for it to arrive at the email client.
	Present(context.Context, acme.Challenge) error

	// CleanUp is called after a challenge is finished, whether
	// successful or not. It MUST free/remove any resources it
	// allocated/created during Present. It SHOULD NOT require
	// that Present ran successfully. It MUST return quickly.
	CleanUp(context.Context, acme.Challenge) error
}

Solver is a type that can solve ACME challenges. All implementations MUST honor context cancellation.

type Waiter

type Waiter interface {
	Wait(context.Context, acme.Challenge) error
}

Waiter is an optional interface for Solvers to implement. Its primary purpose is to help ensure the challenge can be solved before the server gives up trying to verify the challenge.

If implemented, it will be called after Present() but just before the challenge is initiated with the server. It blocks until the challenge is ready to be solved. (For example, waiting on a DNS record to propagate.) This allows challenges to succeed that would normally fail because they take too long to set up (i.e. the ACME server would give up polling DNS or the client would timeout its polling). By separating Present() from Wait(), it allows the slow part of all solvers to begin up front, rather than waiting on each solver one at a time.

It MUST NOT do anything exclusive of Present() that is required for the challenge to succeed. In other words, if Present() is called but Wait() is not, then the challenge should still be able to succeed assuming infinite time.

Implementations MUST honor context cancellation.

Directories

Path Synopsis
Package acme fully implements the ACME protocol specification as described in RFC 8555: https://tools.ietf.org/html/rfc8555.
Package acme fully implements the ACME protocol specification as described in RFC 8555: https://tools.ietf.org/html/rfc8555.
examples

Jump to

Keyboard shortcuts

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