68 lines
2.3 KiB
Go
68 lines
2.3 KiB
Go
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
|
||
|
|
}
|