challenge.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package acme
  2. import (
  3. "crypto/sha256"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "time"
  9. )
  10. // EncodeDNS01KeyAuthorization encodes a key authorization and provides a value to be put in the TXT record for the _acme-challenge DNS entry.
  11. func EncodeDNS01KeyAuthorization(keyAuth string) string {
  12. h := sha256.Sum256([]byte(keyAuth))
  13. return base64.RawURLEncoding.EncodeToString(h[:])
  14. }
  15. // Helper function to determine whether a challenge is "finished" by its status.
  16. func checkUpdatedChallengeStatus(challenge Challenge) (bool, error) {
  17. switch challenge.Status {
  18. case "pending":
  19. // Challenge objects are created in the "pending" state.
  20. // TODO: https://github.com/letsencrypt/boulder/issues/3346
  21. // return true, errors.New("acme: unexpected 'pending' challenge state")
  22. return false, nil
  23. case "processing":
  24. // They transition to the "processing" state when the client responds to the
  25. // challenge and the server begins attempting to validate that the client has completed the challenge.
  26. return false, nil
  27. case "valid":
  28. // If validation is successful, the challenge moves to the "valid" state
  29. return true, nil
  30. case "invalid":
  31. // if there is an error, the challenge moves to the "invalid" state.
  32. if challenge.Error.Type != "" {
  33. return true, challenge.Error
  34. }
  35. return true, errors.New("acme: challenge is invalid, no error provided")
  36. default:
  37. return true, fmt.Errorf("acme: unknown challenge status: %s", challenge.Status)
  38. }
  39. }
  40. // UpdateChallenge responds to a challenge to indicate to the server to complete the challenge.
  41. func (c Client) UpdateChallenge(account Account, challenge Challenge) (Challenge, error) {
  42. resp, err := c.post(challenge.URL, account.URL, account.PrivateKey, struct{}{}, &challenge, http.StatusOK)
  43. if err != nil {
  44. return challenge, err
  45. }
  46. if loc := resp.Header.Get("Location"); loc != "" {
  47. challenge.URL = loc
  48. }
  49. challenge.AuthorizationURL = fetchLink(resp, "up")
  50. if finished, err := checkUpdatedChallengeStatus(challenge); finished {
  51. return challenge, err
  52. }
  53. pollInterval, pollTimeout := c.getPollingDurations()
  54. end := time.Now().Add(pollTimeout)
  55. for {
  56. if time.Now().After(end) {
  57. return challenge, errors.New("acme: challenge update timeout")
  58. }
  59. time.Sleep(pollInterval)
  60. resp, err := c.post(challenge.URL, account.URL, account.PrivateKey, "", &challenge, http.StatusOK)
  61. if err != nil {
  62. // i don't think it's worth exiting the loop on this error
  63. // it could just be connectivity issue that's resolved before the timeout duration
  64. continue
  65. }
  66. if loc := resp.Header.Get("Location"); loc != "" {
  67. challenge.URL = loc
  68. }
  69. challenge.AuthorizationURL = fetchLink(resp, "up")
  70. if finished, err := checkUpdatedChallengeStatus(challenge); finished {
  71. return challenge, err
  72. }
  73. }
  74. }
  75. // FetchChallenge fetches an existing challenge from the given url.
  76. func (c Client) FetchChallenge(account Account, challengeURL string) (Challenge, error) {
  77. challenge := Challenge{}
  78. resp, err := c.post(challengeURL, account.URL, account.PrivateKey, "", &challenge, http.StatusOK)
  79. if err != nil {
  80. return challenge, err
  81. }
  82. challenge.URL = resp.Header.Get("Location")
  83. challenge.AuthorizationURL = fetchLink(resp, "up")
  84. return challenge, nil
  85. }