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 }