103 lines
3.2 KiB
Go
103 lines
3.2 KiB
Go
package acme
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// EncodeDNS01KeyAuthorization encodes a key authorization and provides a value to be put in the TXT record for the _acme-challenge DNS entry.
|
|
func EncodeDNS01KeyAuthorization(keyAuth string) string {
|
|
h := sha256.Sum256([]byte(keyAuth))
|
|
return base64.RawURLEncoding.EncodeToString(h[:])
|
|
}
|
|
|
|
// Helper function to determine whether a challenge is "finished" by its status.
|
|
func checkUpdatedChallengeStatus(challenge Challenge) (bool, error) {
|
|
switch challenge.Status {
|
|
case "pending":
|
|
// Challenge objects are created in the "pending" state.
|
|
// TODO: https://github.com/letsencrypt/boulder/issues/3346
|
|
// return true, errors.New("acme: unexpected 'pending' challenge state")
|
|
return false, nil
|
|
|
|
case "processing":
|
|
// They transition to the "processing" state when the client responds to the
|
|
// challenge and the server begins attempting to validate that the client has completed the challenge.
|
|
return false, nil
|
|
|
|
case "valid":
|
|
// If validation is successful, the challenge moves to the "valid" state
|
|
return true, nil
|
|
|
|
case "invalid":
|
|
// if there is an error, the challenge moves to the "invalid" state.
|
|
if challenge.Error.Type != "" {
|
|
return true, challenge.Error
|
|
}
|
|
return true, errors.New("acme: challenge is invalid, no error provided")
|
|
|
|
default:
|
|
return true, fmt.Errorf("acme: unknown challenge status: %s", challenge.Status)
|
|
}
|
|
}
|
|
|
|
// UpdateChallenge responds to a challenge to indicate to the server to complete the challenge.
|
|
func (c Client) UpdateChallenge(account Account, challenge Challenge) (Challenge, error) {
|
|
resp, err := c.post(challenge.URL, account.URL, account.PrivateKey, struct{}{}, &challenge, http.StatusOK)
|
|
if err != nil {
|
|
return challenge, err
|
|
}
|
|
|
|
if loc := resp.Header.Get("Location"); loc != "" {
|
|
challenge.URL = loc
|
|
}
|
|
challenge.AuthorizationURL = fetchLink(resp, "up")
|
|
|
|
if finished, err := checkUpdatedChallengeStatus(challenge); finished {
|
|
return challenge, err
|
|
}
|
|
|
|
pollInterval, pollTimeout := c.getPollingDurations()
|
|
end := time.Now().Add(pollTimeout)
|
|
for {
|
|
if time.Now().After(end) {
|
|
return challenge, errors.New("acme: challenge update timeout")
|
|
}
|
|
time.Sleep(pollInterval)
|
|
|
|
resp, err := c.post(challenge.URL, account.URL, account.PrivateKey, "", &challenge, http.StatusOK)
|
|
if err != nil {
|
|
// i don't think it's worth exiting the loop on this error
|
|
// it could just be connectivity issue that's resolved before the timeout duration
|
|
continue
|
|
}
|
|
|
|
if loc := resp.Header.Get("Location"); loc != "" {
|
|
challenge.URL = loc
|
|
}
|
|
challenge.AuthorizationURL = fetchLink(resp, "up")
|
|
|
|
if finished, err := checkUpdatedChallengeStatus(challenge); finished {
|
|
return challenge, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// FetchChallenge fetches an existing challenge from the given url.
|
|
func (c Client) FetchChallenge(account Account, challengeURL string) (Challenge, error) {
|
|
challenge := Challenge{}
|
|
resp, err := c.post(challengeURL, account.URL, account.PrivateKey, "", &challenge, http.StatusOK)
|
|
if err != nil {
|
|
return challenge, err
|
|
}
|
|
|
|
challenge.URL = resp.Header.Get("Location")
|
|
challenge.AuthorizationURL = fetchLink(resp, "up")
|
|
|
|
return challenge, nil
|
|
}
|