| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- package acme
- import (
- _ "crypto/sha1"
- _ "crypto/sha256"
- _ "crypto/sha512"
- "crypto/x509"
- "encoding/asn1"
- "encoding/base64"
- "fmt"
- "math/rand"
- "net/http"
- "strconv"
- "strings"
- "time"
- )
- // GetRenewalInfo returns the renewal information (if present and supported by
- // the ACME server), and a Retry-After time if indicated in the http response
- // header.
- func (c Client) GetRenewalInfo(cert *x509.Certificate) (RenewalInfo, error) {
- if c.dir.RenewalInfo == "" {
- return RenewalInfo{}, ErrRenewalInfoNotSupported
- }
- certID, err := GenerateARICertID(cert)
- if err != nil {
- return RenewalInfo{}, fmt.Errorf("acme: error generating certificate id: %v", err)
- }
- renewalURL := c.dir.RenewalInfo
- if !strings.HasSuffix(renewalURL, "/") {
- renewalURL += "/"
- }
- renewalURL += certID
- var ri RenewalInfo
- resp, err := c.get(renewalURL, &ri, http.StatusOK)
- if err != nil {
- return ri, err
- }
- defer resp.Body.Close()
- ri.RetryAfter, err = parseRetryAfter(resp.Header.Get("Retry-After"))
- return ri, err
- }
- // GenerateARICertID constructs a certificate identifier as described in
- // draft-ietf-acme-ari-03, section 4.1.
- func GenerateARICertID(cert *x509.Certificate) (string, error) {
- if cert == nil {
- return "", fmt.Errorf("certificate not found")
- }
- derBytes, err := asn1.Marshal(cert.SerialNumber)
- if err != nil {
- return "", err
- }
- if len(derBytes) < 3 {
- return "", fmt.Errorf("invalid DER encoding of serial number")
- }
- // Extract only the integer bytes from the DER encoded Serial Number
- // Skipping the first 2 bytes (tag and length). The result is base64url
- // encoded without padding.
- serial := base64.RawURLEncoding.EncodeToString(derBytes[2:])
- // Convert the Authority Key Identifier to base64url encoding without
- // padding.
- aki := base64.RawURLEncoding.EncodeToString(cert.AuthorityKeyId)
- // Construct the final identifier by concatenating AKI and Serial Number.
- return fmt.Sprintf("%s.%s", aki, serial), nil
- }
- func (r RenewalInfo) ShouldRenewAt(now time.Time, willingToSleep time.Duration) *time.Time {
- // Explicitly convert all times to UTC.
- now = now.UTC()
- start := r.SuggestedWindow.Start.UTC()
- end := r.SuggestedWindow.End.UTC()
- // Select a uniform random time within the suggested window.
- window := end.Sub(start)
- randomDuration := time.Duration(rand.Int63n(int64(window)))
- randomTime := start.Add(randomDuration)
- // If the selected time is in the past, attempt renewal immediately.
- if randomTime.Before(now) {
- return &now
- }
- // Otherwise, if the client can schedule itself to attempt renewal at
- // exactly the selected time, do so.
- willingToSleepUntil := now.Add(willingToSleep)
- if willingToSleepUntil.After(randomTime) || willingToSleepUntil.Equal(randomTime) {
- return &randomTime
- }
- return nil
- }
- // timeNow and implementations support testing
- type timeNow interface {
- Now() time.Time
- }
- type currentTimeNow struct{}
- func (currentTimeNow) Now() time.Time {
- return time.Now()
- }
- var systemTime timeNow = currentTimeNow{}
- func parseRetryAfter(ra string) (time.Time, error) {
- retryAfterString := strings.TrimSpace(ra)
- if len(retryAfterString) == 0 {
- return time.Time{}, nil
- }
- if retryAfterTime, err := time.Parse(time.RFC1123, retryAfterString); err == nil {
- return retryAfterTime, nil
- }
- if retryAfterInt, err := strconv.Atoi(retryAfterString); err == nil {
- return systemTime.Now().Add(time.Second * time.Duration(retryAfterInt)), nil
- }
- return time.Time{}, fmt.Errorf("invalid time format: %s", retryAfterString)
- }
|