ari.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package acme
  2. import (
  3. _ "crypto/sha1"
  4. _ "crypto/sha256"
  5. _ "crypto/sha512"
  6. "crypto/x509"
  7. "encoding/asn1"
  8. "encoding/base64"
  9. "fmt"
  10. "math/rand"
  11. "net/http"
  12. "strconv"
  13. "strings"
  14. "time"
  15. )
  16. // GetRenewalInfo returns the renewal information (if present and supported by
  17. // the ACME server), and a Retry-After time if indicated in the http response
  18. // header.
  19. func (c Client) GetRenewalInfo(cert *x509.Certificate) (RenewalInfo, error) {
  20. if c.dir.RenewalInfo == "" {
  21. return RenewalInfo{}, ErrRenewalInfoNotSupported
  22. }
  23. certID, err := GenerateARICertID(cert)
  24. if err != nil {
  25. return RenewalInfo{}, fmt.Errorf("acme: error generating certificate id: %v", err)
  26. }
  27. renewalURL := c.dir.RenewalInfo
  28. if !strings.HasSuffix(renewalURL, "/") {
  29. renewalURL += "/"
  30. }
  31. renewalURL += certID
  32. var ri RenewalInfo
  33. resp, err := c.get(renewalURL, &ri, http.StatusOK)
  34. if err != nil {
  35. return ri, err
  36. }
  37. defer resp.Body.Close()
  38. ri.RetryAfter, err = parseRetryAfter(resp.Header.Get("Retry-After"))
  39. return ri, err
  40. }
  41. // GenerateARICertID constructs a certificate identifier as described in
  42. // draft-ietf-acme-ari-03, section 4.1.
  43. func GenerateARICertID(cert *x509.Certificate) (string, error) {
  44. if cert == nil {
  45. return "", fmt.Errorf("certificate not found")
  46. }
  47. derBytes, err := asn1.Marshal(cert.SerialNumber)
  48. if err != nil {
  49. return "", err
  50. }
  51. if len(derBytes) < 3 {
  52. return "", fmt.Errorf("invalid DER encoding of serial number")
  53. }
  54. // Extract only the integer bytes from the DER encoded Serial Number
  55. // Skipping the first 2 bytes (tag and length). The result is base64url
  56. // encoded without padding.
  57. serial := base64.RawURLEncoding.EncodeToString(derBytes[2:])
  58. // Convert the Authority Key Identifier to base64url encoding without
  59. // padding.
  60. aki := base64.RawURLEncoding.EncodeToString(cert.AuthorityKeyId)
  61. // Construct the final identifier by concatenating AKI and Serial Number.
  62. return fmt.Sprintf("%s.%s", aki, serial), nil
  63. }
  64. func (r RenewalInfo) ShouldRenewAt(now time.Time, willingToSleep time.Duration) *time.Time {
  65. // Explicitly convert all times to UTC.
  66. now = now.UTC()
  67. start := r.SuggestedWindow.Start.UTC()
  68. end := r.SuggestedWindow.End.UTC()
  69. // Select a uniform random time within the suggested window.
  70. window := end.Sub(start)
  71. randomDuration := time.Duration(rand.Int63n(int64(window)))
  72. randomTime := start.Add(randomDuration)
  73. // If the selected time is in the past, attempt renewal immediately.
  74. if randomTime.Before(now) {
  75. return &now
  76. }
  77. // Otherwise, if the client can schedule itself to attempt renewal at
  78. // exactly the selected time, do so.
  79. willingToSleepUntil := now.Add(willingToSleep)
  80. if willingToSleepUntil.After(randomTime) || willingToSleepUntil.Equal(randomTime) {
  81. return &randomTime
  82. }
  83. return nil
  84. }
  85. // timeNow and implementations support testing
  86. type timeNow interface {
  87. Now() time.Time
  88. }
  89. type currentTimeNow struct{}
  90. func (currentTimeNow) Now() time.Time {
  91. return time.Now()
  92. }
  93. var systemTime timeNow = currentTimeNow{}
  94. func parseRetryAfter(ra string) (time.Time, error) {
  95. retryAfterString := strings.TrimSpace(ra)
  96. if len(retryAfterString) == 0 {
  97. return time.Time{}, nil
  98. }
  99. if retryAfterTime, err := time.Parse(time.RFC1123, retryAfterString); err == nil {
  100. return retryAfterTime, nil
  101. }
  102. if retryAfterInt, err := strconv.Atoi(retryAfterString); err == nil {
  103. return systemTime.Now().Add(time.Second * time.Duration(retryAfterInt)), nil
  104. }
  105. return time.Time{}, fmt.Errorf("invalid time format: %s", retryAfterString)
  106. }