Files
gomail/internal/acme/obtain.go
T

68 lines
2.3 KiB
Go
Raw Normal View History

2026-08-09 18:03:09 +01:00
package acme
import (
"fmt"
"time"
)
// Obtain drives the complete ACME issuance flow for one or more domains:
// bootstrap, account registration, order, HTTP-01 challenge response via
// responder, finalize, download. The caller is responsible for mounting
// responder on a listener the CA's HTTP-01 validator can reach at
// http://{domain}/.well-known/acme-challenge/{token} — this function only
// populates the token->response map, it doesn't start any listener itself.
func Obtain(directoryURL, contactEmail string, domains []string, accountKey *AccountKey, responder *ChallengeResponder) (certPEM, keyPEM []byte, err error) {
client := NewClient(directoryURL, accountKey)
if err := client.Bootstrap(); err != nil {
return nil, nil, fmt.Errorf("bootstrap: %w", err)
}
if err := client.NewAccount(contactEmail); err != nil {
return nil, nil, fmt.Errorf("account registration: %w", err)
}
order, err := client.NewOrder(domains)
if err != nil {
return nil, nil, fmt.Errorf("creating order: %w", err)
}
for _, authzURL := range order.Authorizations {
authz, err := client.GetAuthorization(authzURL)
if err != nil {
return nil, nil, fmt.Errorf("fetching authorization: %w", err)
}
if authz.Status == "valid" {
continue // already satisfied (e.g. from a very recent prior order)
}
var httpChallenge *Challenge
for i := range authz.Challenges {
if authz.Challenges[i].Type == "http-01" {
httpChallenge = &authz.Challenges[i]
break
}
}
if httpChallenge == nil {
return nil, nil, fmt.Errorf("no http-01 challenge offered for %s", authz.Identifier.Value)
}
keyAuth := client.KeyAuthorization(httpChallenge.Token)
responder.Set(httpChallenge.Token, keyAuth)
if err := client.RespondToChallenge(httpChallenge.URL); err != nil {
responder.Remove(httpChallenge.Token)
return nil, nil, fmt.Errorf("responding to challenge for %s: %w", authz.Identifier.Value, err)
}
waitErr := client.WaitForAuthorizationValid(authzURL, 30*time.Second)
responder.Remove(httpChallenge.Token)
if waitErr != nil {
return nil, nil, fmt.Errorf("waiting for validation of %s: %w", authz.Identifier.Value, waitErr)
}
}
certPEM, keyPEM, err = client.FinalizeAndDownload(order, domains, 30*time.Second)
if err != nil {
return nil, nil, fmt.Errorf("finalize/download: %w", err)
}
return certPEM, keyPEM, nil
}