Update server dependencies

This commit is contained in:
Ken-Håvard Lieng 2020-04-29 04:23:32 +02:00
parent c704ebb042
commit 1794e2680a
369 changed files with 23554 additions and 6306 deletions

View file

@ -5,7 +5,7 @@ import (
"errors"
"fmt"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/v3/acme"
)
type AccountService service
@ -31,12 +31,12 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
func (a *AccountService) NewEAB(accMsg acme.Account, kid string, hmacEncoded string) (acme.ExtendedAccount, error) {
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
if err != nil {
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %v", err)
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %w", err)
}
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
if err != nil {
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %v", err)
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %w", err)
}
accMsg.ExternalAccountBinding = eabJWS
@ -57,6 +57,20 @@ func (a *AccountService) Get(accountURL string) (acme.Account, error) {
return account, nil
}
// Update Updates an account.
func (a *AccountService) Update(accountURL string, req acme.Account) (acme.ExtendedAccount, error) {
if len(accountURL) == 0 {
return acme.ExtendedAccount{}, errors.New("account[update]: empty URL")
}
var account acme.ExtendedAccount
_, err := a.core.post(accountURL, req, &account)
if err != nil {
return acme.ExtendedAccount{}, err
}
return account, nil
}
// Deactivate Deactivates an account.
func (a *AccountService) Deactivate(accountURL string) error {
if len(accountURL) == 0 {

View file

@ -10,12 +10,12 @@ import (
"net/http"
"time"
"github.com/cenkalti/backoff"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api/internal/nonces"
"github.com/go-acme/lego/acme/api/internal/secure"
"github.com/go-acme/lego/acme/api/internal/sender"
"github.com/go-acme/lego/log"
"github.com/cenkalti/backoff/v4"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api/internal/nonces"
"github.com/go-acme/lego/v3/acme/api/internal/secure"
"github.com/go-acme/lego/v3/acme/api/internal/sender"
"github.com/go-acme/lego/v3/log"
)
// Core ACME/LE core API.
@ -71,7 +71,7 @@ func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response,
}
// postAsGet performs an HTTP POST ("POST-as-GET") request.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
// https://tools.ietf.org/html/rfc8555#section-6.3
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
return a.retrievablePost(uri, []byte{}, response)
}
@ -93,7 +93,6 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
switch err.(type) {
// Retry if the nonce was invalidated
case *acme.NonceError:
log.Infof("nonce error retry: %s", err)
return err
default:
cancel()
@ -104,7 +103,11 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
return nil
}
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
notify := func(err error, duration time.Duration) {
log.Infof("retry due to: %v", err)
}
err := backoff.RetryNotify(operation, backoff.WithContext(bo, ctx), notify)
if err != nil {
return nil, err
}
@ -115,7 +118,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) {
signedContent, err := a.jws.SignContent(uri, content)
if err != nil {
return nil, fmt.Errorf("failed to post JWS message -> failed to sign content -> %v", err)
return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err)
}
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
@ -152,7 +155,7 @@ func (a *Core) GetDirectory() acme.Directory {
func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) {
var dir acme.Directory
if _, err := do.Get(caDirURL, &dir); err != nil {
return dir, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
return dir, fmt.Errorf("get directory at '%s': %w", caDirURL, err)
}
if dir.NewAccountURL == "" {

View file

@ -3,7 +3,7 @@ package api
import (
"errors"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/v3/acme"
)
type AuthorizationService service

View file

@ -7,9 +7,9 @@ import (
"io/ioutil"
"net/http"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/certcrypto"
"github.com/go-acme/lego/v3/log"
)
// maxBodySize is the maximum size of body that we will read.
@ -71,7 +71,7 @@ func (c *CertificateService) get(certURL string) ([]byte, string, error) {
// The issuer certificate link may be supplied via an "up" link
// in the response headers of a new certificate.
// See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
// See https://tools.ietf.org/html/rfc8555#section-7.4.2
up := getLink(resp.Header, "up")
return cert, up, err

View file

@ -3,7 +3,7 @@ package api
import (
"errors"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/v3/acme"
)
type ChallengeService service

View file

@ -6,7 +6,7 @@ import (
"net/http"
"sync"
"github.com/go-acme/lego/acme/api/internal/sender"
"github.com/go-acme/lego/v3/acme/api/internal/sender"
)
// Manager Manages nonces.
@ -57,7 +57,7 @@ func (n *Manager) Nonce() (string, error) {
func (n *Manager) getNonce() (string, error) {
resp, err := n.do.Head(n.nonceURL)
if err != nil {
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %v", err)
return "", fmt.Errorf("failed to get nonce from HTTP HEAD: %w", err)
}
return GetFromResponse(resp)
@ -71,7 +71,7 @@ func GetFromResponse(resp *http.Response) (string, error) {
nonce := resp.Header.Get("Replay-Nonce")
if nonce == "" {
return "", fmt.Errorf("server did not respond with a proper nonce header")
return "", errors.New("server did not respond with a proper nonce header")
}
return nonce, nil

View file

@ -8,7 +8,7 @@ import (
"encoding/base64"
"fmt"
"github.com/go-acme/lego/acme/api/internal/nonces"
"github.com/go-acme/lego/v3/acme/api/internal/nonces"
jose "gopkg.in/square/go-jose.v2"
)
@ -65,12 +65,12 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e
signer, err := jose.NewSigner(signKey, &options)
if err != nil {
return nil, fmt.Errorf("failed to create jose signer -> %v", err)
return nil, fmt.Errorf("failed to create jose signer: %w", err)
}
signed, err := signer.Sign(content)
if err != nil {
return nil, fmt.Errorf("failed to sign content -> %v", err)
return nil, fmt.Errorf("failed to sign content: %w", err)
}
return signed, nil
}
@ -80,7 +80,7 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
jwk := jose.JSONWebKey{Key: j.privKey}
jwkJSON, err := jwk.Public().MarshalJSON()
if err != nil {
return nil, fmt.Errorf("acme: error encoding eab jwk key: %v", err)
return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err)
}
signer, err := jose.NewSigner(
@ -94,12 +94,12 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
},
)
if err != nil {
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %v", err)
return nil, fmt.Errorf("failed to create External Account Binding jose signer: %w", err)
}
signed, err := signer.Sign(jwkJSON)
if err != nil {
return nil, fmt.Errorf("failed to External Account Binding sign content -> %v", err)
return nil, fmt.Errorf("failed to External Account Binding sign content: %w", err)
}
return signed, nil

View file

@ -9,7 +9,7 @@ import (
"runtime"
"strings"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/v3/acme"
)
type RequestOption func(*http.Request) error
@ -70,7 +70,7 @@ func (d *Doer) Post(url string, body io.Reader, bodyType string, response interf
func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
req, err := http.NewRequest(method, uri, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("User-Agent", d.formatUserAgent())
@ -78,7 +78,7 @@ func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOpt
for _, opt := range opts {
err = opt(req)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
return nil, fmt.Errorf("failed to create request: %w", err)
}
}
@ -105,7 +105,7 @@ func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, erro
err = json.Unmarshal(raw, response)
if err != nil {
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %v", raw, response, err)
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %w", raw, response, err)
}
}
@ -120,16 +120,15 @@ func (d *Doer) formatUserAgent() string {
func checkError(req *http.Request, resp *http.Response) error {
if resp.StatusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%d :: %s :: %s :: %v", resp.StatusCode, req.Method, req.URL, err)
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
}
var errorDetails *acme.ProblemDetails
err = json.Unmarshal(body, &errorDetails)
if err != nil {
return fmt.Errorf("%d ::%s :: %s :: %v :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
}
errorDetails.Method = req.Method

View file

@ -5,7 +5,7 @@ package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/2.6.0"
ourUserAgent = "xenolf-acme/3.6.0"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release

View file

@ -4,7 +4,7 @@ import (
"encoding/base64"
"errors"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/v3/acme"
)
type OrderService service

View file

@ -1,5 +1,5 @@
// Package acme contains all objects related the ACME endpoints.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16
// https://tools.ietf.org/html/rfc8555
package acme
import (
@ -8,7 +8,7 @@ import (
)
// Challenge statuses
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.6
// https://tools.ietf.org/html/rfc8555#section-7.1.6
const (
StatusPending = "pending"
StatusInvalid = "invalid"
@ -20,7 +20,7 @@ const (
)
// Directory the ACME directory object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
type Directory struct {
NewNonceURL string `json:"newNonce"`
NewAccountURL string `json:"newAccount"`
@ -32,7 +32,7 @@ type Directory struct {
}
// Meta the ACME meta object (related to Directory).
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
type Meta struct {
// termsOfService (optional, string):
// A URL identifying the current terms of service.
@ -65,8 +65,8 @@ type ExtendedAccount struct {
}
// Account the ACME account Object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.2
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3
// - https://tools.ietf.org/html/rfc8555#section-7.1.2
// - https://tools.ietf.org/html/rfc8555#section-7.3
type Account struct {
// status (required, string):
// The status of this account.
@ -111,7 +111,7 @@ type ExtendedOrder struct {
}
// Order the ACME order Object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.3
// - https://tools.ietf.org/html/rfc8555#section-7.1.3
type Order struct {
// status (required, string):
// The status of this order.
@ -164,7 +164,7 @@ type Order struct {
}
// Authorization the ACME authorization object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
// - https://tools.ietf.org/html/rfc8555#section-7.1.4
type Authorization struct {
// status (required, string):
// The status of this authorization.
@ -206,8 +206,8 @@ type ExtendedChallenge struct {
}
// Challenge the ACME challenge object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.5
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8
// - https://tools.ietf.org/html/rfc8555#section-7.1.5
// - https://tools.ietf.org/html/rfc8555#section-8
type Challenge struct {
// type (required, string):
// The type of challenge encoded in the object.
@ -240,23 +240,23 @@ type Challenge struct {
// It MUST NOT contain any characters outside the base64url alphabet,
// and MUST NOT include base64 padding characters ("=").
// See [RFC4086] for additional information on randomness requirements.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
// https://tools.ietf.org/html/rfc8555#section-8.3
// https://tools.ietf.org/html/rfc8555#section-8.4
Token string `json:"token"`
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.1
// https://tools.ietf.org/html/rfc8555#section-8.1
KeyAuthorization string `json:"keyAuthorization"`
}
// Identifier the ACME identifier object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-9.7.7
// - https://tools.ietf.org/html/rfc8555#section-9.7.7
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
// CSRMessage Certificate Signing Request
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.4
// - https://tools.ietf.org/html/rfc8555#section-7.4
type CSRMessage struct {
// csr (required, string):
// A CSR encoding the parameters for the certificate being requested [RFC2986].
@ -266,7 +266,7 @@ type CSRMessage struct {
}
// RevokeCertMessage a certificate revocation message
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.6
// - https://tools.ietf.org/html/rfc8555#section-7.6
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
type RevokeCertMessage struct {
// certificate (required, string):

View file

@ -12,7 +12,7 @@ const (
// ProblemDetails the problem details object
// - https://tools.ietf.org/html/rfc7807#section-3.1
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3.3
// - https://tools.ietf.org/html/rfc8555#section-7.3.3
type ProblemDetails struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
@ -26,7 +26,7 @@ type ProblemDetails struct {
}
// SubProblem a "subproblems"
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.7.1
// - https://tools.ietf.org/html/rfc8555#section-6.7.1
type SubProblem struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`

View file

@ -3,6 +3,7 @@ package certcrypto
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
@ -13,6 +14,7 @@ import (
"errors"
"fmt"
"math/big"
"strings"
"time"
"golang.org/x/crypto/ocsp"
@ -77,17 +79,35 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
return certificates, nil
}
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
// Borrowed from Go standard library, to handle various private key and PEM block types.
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
keyBlock, _ := pem.Decode(key)
keyBlockDER, _ := pem.Decode(key)
switch keyBlock.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(keyBlock.Bytes)
default:
return nil, errors.New("unknown PEM header value")
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
}
if key, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
return key, nil
}
if key, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
switch key := key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
return key, nil
default:
return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
}
}
if key, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
return key, nil
}
return nil, errors.New("failed to parse private key")
}
func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
@ -147,7 +167,7 @@ func PEMBlock(data interface{}) *pem.Block {
func pemDecode(data []byte) (*pem.Block, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, fmt.Errorf("PEM decode did not yield a valid block. Is the certificate in the right format?")
return nil, errors.New("PEM decode did not yield a valid block. Is the certificate in the right format?")
}
return pemBlock, nil
@ -160,7 +180,7 @@ func PemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
}
if pemBlock.Type != "CERTIFICATE REQUEST" {
return nil, fmt.Errorf("PEM block is not a certificate request")
return nil, errors.New("PEM block is not a certificate request")
}
return x509.ParseCertificateRequest(pemBlock.Bytes)
@ -179,7 +199,10 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
}
func ExtractDomains(cert *x509.Certificate) []string {
domains := []string{cert.Subject.CommonName}
var domains []string
if cert.Subject.CommonName != "" {
domains = append(domains, cert.Subject.CommonName)
}
// Check for SAN certificate
for _, sanDomain := range cert.DNSNames {
@ -193,7 +216,10 @@ func ExtractDomains(cert *x509.Certificate) []string {
}
func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
domains := []string{csr.Subject.CommonName}
var domains []string
if csr.Subject.CommonName != "" {
domains = append(domains, csr.Subject.CommonName)
}
// loop over the SubjectAltName DNS names
for _, sanName := range csr.DNSNames {

View file

@ -3,8 +3,8 @@ package certificate
import (
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/log"
)
const (
@ -61,9 +61,21 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
}
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
for _, auth := range order.Authorizations {
if err := c.core.Authorizations.Deactivate(auth); err != nil {
log.Infof("Unable to deactivated authorizations: %s", auth)
for _, authzURL := range order.Authorizations {
auth, err := c.core.Authorizations.Get(authzURL)
if err != nil {
log.Infof("Unable to get the authorization for: %s", authzURL)
continue
}
if auth.Status == acme.StatusValid {
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
continue
}
log.Infof("Deactivating auth: %s", authzURL)
if c.core.Authorizations.Deactivate(authzURL) != nil {
log.Infof("Unable to deactivate the authorization: %s", authzURL)
}
}
}

View file

@ -12,12 +12,12 @@ import (
"strings"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/platform/wait"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/certcrypto"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/log"
"github.com/go-acme/lego/v3/platform/wait"
"golang.org/x/crypto/ocsp"
"golang.org/x/net/idna"
)
@ -210,8 +210,8 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
// Determine certificate name(s) based on the authorization resources
commonName := domains[0]
// ACME draft Section 7.4 "Applying for Certificate Issuance"
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
// https://tools.ietf.org/html/rfc8555#section-7.4
// says:
// Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order
@ -317,7 +317,7 @@ func (c *Certifier) Revoke(cert []byte) error {
x509Cert := certificates[0]
if x509Cert.IsCA {
return fmt.Errorf("certificate bundle starts with a CA certificate")
return errors.New("certificate bundle starts with a CA certificate")
}
revokeMsg := acme.RevokeCertMessage{
@ -502,7 +502,7 @@ func checkOrderStatus(order acme.Order) (bool, error) {
}
}
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
// https://tools.ietf.org/html/rfc8555#section-7.1.4
// The domain name MUST be encoded
// in the form in which it would appear in a certificate. That is, it
// MUST be encoded according to the rules in Section 7 of [RFC5280].

View file

@ -10,7 +10,7 @@ import (
type obtainError map[string]error
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
var domains []string
for domain := range e {

View file

@ -3,22 +3,22 @@ package challenge
import (
"fmt"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/v3/acme"
)
// Type is a string that identifies a particular challenge type and version of ACME challenge.
type Type string
const (
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.3
// Note: ChallengePath returns the URL path to fulfill this challenge
HTTP01 = Type("http-01")
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.4
// Note: GetRecord returns a DNS record which will fulfill this challenge
DNS01 = Type("dns-01")
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07
TLSALPN01 = Type("tls-alpn-01")
)

View file

@ -8,11 +8,11 @@ import (
"strconv"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/platform/wait"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/log"
"github.com/go-acme/lego/v3/platform/wait"
"github.com/miekg/dns"
)
@ -93,7 +93,7 @@ func (c *Challenge) PreSolve(authz acme.Authorization) error {
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %s", domain, err)
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
}
return nil

View file

@ -1,6 +1,7 @@
package dns01
import (
"errors"
"fmt"
"net"
"strings"
@ -16,8 +17,8 @@ const defaultResolvConf = "/etc/resolv.conf"
var dnsTimeout = 10 * time.Second
var (
fqdnToZone = map[string]string{}
muFqdnToZone sync.Mutex
fqdnSoaCache = map[string]*soaCacheEntry{}
muFqdnSoaCache sync.Mutex
)
var defaultNameservers = []string{
@ -28,11 +29,31 @@ var defaultNameservers = []string{
// recursiveNameservers are used to pre-check DNS propagation
var recursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
// soaCacheEntry holds a cached SOA record (only selected fields)
type soaCacheEntry struct {
zone string // zone apex (a domain name)
primaryNs string // primary nameserver for the zone apex
expires time.Time // time when this cache entry should be evicted
}
func newSoaCacheEntry(soa *dns.SOA) *soaCacheEntry {
return &soaCacheEntry{
zone: soa.Hdr.Name,
primaryNs: soa.Ns,
expires: time.Now().Add(time.Duration(soa.Refresh) * time.Second),
}
}
// isExpired checks whether a cache entry should be considered expired.
func (cache *soaCacheEntry) isExpired() bool {
return time.Now().After(cache.expires)
}
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
func ClearFqdnCache() {
muFqdnToZone.Lock()
fqdnToZone = map[string]string{}
muFqdnToZone.Unlock()
muFqdnSoaCache.Lock()
fqdnSoaCache = map[string]*soaCacheEntry{}
muFqdnSoaCache.Unlock()
}
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
@ -78,7 +99,7 @@ func lookupNameservers(fqdn string) ([]string, error) {
zone, err := FindZoneByFqdn(fqdn)
if err != nil {
return nil, fmt.Errorf("could not determine the zone: %v", err)
return nil, fmt.Errorf("could not determine the zone: %w", err)
}
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
@ -95,7 +116,23 @@ func lookupNameservers(fqdn string) ([]string, error) {
if len(authoritativeNss) > 0 {
return authoritativeNss, nil
}
return nil, fmt.Errorf("could not determine authoritative nameservers")
return nil, errors.New("could not determine authoritative nameservers")
}
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
func FindPrimaryNsByFqdn(fqdn string) (string, error) {
return FindPrimaryNsByFqdnCustom(fqdn, recursiveNameservers)
}
// FindPrimaryNsByFqdnCustom determines the primary nameserver of the zone apex for the given fqdn
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
soa, err := lookupSoaByFqdn(fqdn, nameservers)
if err != nil {
return "", err
}
return soa.primaryNs, nil
}
// FindZoneByFqdn determines the zone apex for the given fqdn
@ -107,14 +144,32 @@ func FindZoneByFqdn(fqdn string) (string, error) {
// FindZoneByFqdnCustom determines the zone apex for the given fqdn
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
muFqdnToZone.Lock()
defer muFqdnToZone.Unlock()
soa, err := lookupSoaByFqdn(fqdn, nameservers)
if err != nil {
return "", err
}
return soa.zone, nil
}
// Do we have it cached?
if zone, ok := fqdnToZone[fqdn]; ok {
return zone, nil
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
muFqdnSoaCache.Lock()
defer muFqdnSoaCache.Unlock()
// Do we have it cached and is it still fresh?
if ent := fqdnSoaCache[fqdn]; ent != nil && !ent.isExpired() {
return ent, nil
}
ent, err := fetchSoaByFqdn(fqdn, nameservers)
if err != nil {
return nil, err
}
fqdnSoaCache[fqdn] = ent
return ent, nil
}
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
var err error
var in *dns.Msg
@ -134,7 +189,6 @@ func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
switch in.Rcode {
case dns.RcodeSuccess:
// Check if we got a SOA RR in the answer section
if len(in.Answer) == 0 {
continue
}
@ -147,20 +201,18 @@ func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
for _, ans := range in.Answer {
if soa, ok := ans.(*dns.SOA); ok {
zone := soa.Hdr.Name
fqdnToZone[fqdn] = zone
return zone, nil
return newSoaCacheEntry(soa), nil
}
}
case dns.RcodeNameError:
// NXDOMAIN
default:
// Any response code other than NOERROR and NXDOMAIN is treated as error
return "", fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
}
}
return "", fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
}
// dnsMsgContainsCNAME checks for a CNAME answer in msg

View file

@ -0,0 +1,184 @@
package http01
import (
"fmt"
"net/http"
"strings"
)
// A domainMatcher tries to match a domain (the one we're requesting a certificate for)
// in the HTTP request coming from the ACME validation servers.
// This step is part of DNS rebind attack prevention,
// where the webserver matches incoming requests to a list of domain the server acts authoritative for.
//
// The most simple check involves finding the domain in the HTTP Host header;
// this is what hostMatcher does.
// Use it, when the http01.ProviderServer is directly reachable from the internet,
// or when it operates behind a transparent proxy.
//
// In many (reverse) proxy setups, Apache and NGINX traditionally move the Host header to a new header named X-Forwarded-Host.
// Use arbitraryMatcher("X-Forwarded-Host") in this case,
// or the appropriate header name for other proxy servers.
//
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
// The header value has a different format, so you should use forwardedMatcher
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
// https://tools.ietf.org/html/rfc7239
//
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
// meaning that
// X-Header: a
// X-Header: b
// is equal to
// X-Header: a, b
//
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
// have in common that they only match against the first value in such lists.
type domainMatcher interface {
// matches checks whether the request is valid for the given domain.
matches(request *http.Request, domain string) bool
// name returns the header name used in the check.
// This is primarily used to create meaningful error messages.
name() string
}
// hostMatcher checks whether (*net/http).Request.Host starts with a domain name.
type hostMatcher struct{}
func (m *hostMatcher) name() string {
return "Host"
}
func (m *hostMatcher) matches(r *http.Request, domain string) bool {
return strings.HasPrefix(r.Host, domain)
}
// hostMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
type arbitraryMatcher string
func (m arbitraryMatcher) name() string {
return string(m)
}
func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
return strings.HasPrefix(r.Header.Get(m.name()), domain)
}
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
// See https://tools.ietf.org/html/rfc7239 for details.
type forwardedMatcher struct{}
func (m *forwardedMatcher) name() string {
return "Forwarded"
}
func (m *forwardedMatcher) matches(r *http.Request, domain string) bool {
fwds, err := parseForwardedHeader(r.Header.Get(m.name()))
if err != nil {
return false
}
if len(fwds) == 0 {
return false
}
host := fwds[0]["host"]
return strings.HasPrefix(host, domain)
}
// parsing requires some form of state machine
func parseForwardedHeader(s string) (elements []map[string]string, err error) {
cur := make(map[string]string)
key := ""
val := ""
inquote := false
pos := 0
l := len(s)
for i := 0; i < l; i++ {
r := rune(s[i])
if inquote {
if r == '"' {
cur[key] = s[pos:i]
key = ""
pos = i
inquote = false
}
continue
}
switch {
case r == '"': // start of quoted-string
if key == "" {
return nil, fmt.Errorf("unexpected quoted string as pos %d", i)
}
inquote = true
pos = i + 1
case r == ';': // end of forwarded-pair
cur[key] = s[pos:i]
key = ""
i = skipWS(s, i)
pos = i + 1
case r == '=': // end of token
key = strings.ToLower(strings.TrimFunc(s[pos:i], isWS))
i = skipWS(s, i)
pos = i + 1
case r == ',': // end of forwarded-element
if key != "" {
if val == "" {
val = s[pos:i]
}
cur[key] = val
}
elements = append(elements, cur)
cur = make(map[string]string)
key = ""
val = ""
i = skipWS(s, i)
pos = i + 1
case tchar(r) || isWS(r): // valid token character or whitespace
continue
default:
return nil, fmt.Errorf("invalid token character at pos %d: %c", i, r)
}
}
if inquote {
return nil, fmt.Errorf("unterminated quoted-string at pos %d", len(s))
}
if key != "" {
if pos < len(s) {
val = s[pos:]
}
cur[key] = val
}
if len(cur) > 0 {
elements = append(elements, cur)
}
return elements, nil
}
func tchar(r rune) bool {
return strings.ContainsRune("!#$%&'*+-.^_`|~", r) ||
'0' <= r && r <= '9' ||
'a' <= r && r <= 'z' ||
'A' <= r && r <= 'Z'
}
func skipWS(s string, i int) int {
for isWS(rune(s[i+1])) {
i++
}
return i
}
func isWS(r rune) bool {
return strings.ContainsRune(" \t\v\r\n", r)
}

View file

@ -3,10 +3,10 @@ package http01
import (
"fmt"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/log"
)
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
@ -51,12 +51,12 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %v", domain, err)
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
}
defer func() {
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v", domain, err)
log.Warnf("[%s] acme: cleaning up failed: %v", domain, err)
}
}()

View file

@ -4,9 +4,10 @@ import (
"fmt"
"net"
"net/http"
"net/textproto"
"strings"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/log"
)
// ProviderServer implements ChallengeProvider for `http-01` challenge
@ -15,6 +16,7 @@ import (
type ProviderServer struct {
iface string
port string
matcher domainMatcher
done chan bool
listener net.Listener
}
@ -23,19 +25,19 @@ type ProviderServer struct {
// Setting iface and / or port to an empty string will make the server fall back to
// the "any" interface and port 80 respectively.
func NewProviderServer(iface, port string) *ProviderServer {
return &ProviderServer{iface: iface, port: port}
if port == "" {
port = "80"
}
return &ProviderServer{iface: iface, port: port, matcher: &hostMatcher{}}
}
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
if s.port == "" {
s.port = "80"
}
var err error
s.listener, err = net.Listen("tcp", s.GetAddress())
if err != nil {
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
}
s.done = make(chan bool)
@ -57,14 +59,38 @@ func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
return nil
}
// SetProxyHeader changes the validation of incoming requests.
// By default, s matches the "Host" header value to the domain name.
//
// When the server runs behind a proxy server, this is not the correct place to look at;
// Apache and NGINX have traditionally moved the original Host header into a new header named "X-Forwarded-Host".
// Other webservers might use different names;
// and RFC7239 has standadized a new header named "Forwarded" (with slightly different semantics).
//
// The exact behavior depends on the value of headerName:
// - "" (the empty string) and "Host" will restore the default and only check the Host header
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://tools.ietf.org/html/rfc7239
// - any other value will check the header value with the same name
func (s *ProviderServer) SetProxyHeader(headerName string) {
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
case "", "Host":
s.matcher = &hostMatcher{}
case "Forwarded":
s.matcher = &forwardedMatcher{}
default:
s.matcher = arbitraryMatcher(h)
}
}
func (s *ProviderServer) serve(domain, token, keyAuth string) {
path := ChallengePath(token)
// The handler validates the HOST header and request type.
// For validation it then writes the token the server returned with the challenge
// The incoming request must will be validated to prevent DNS rebind attacks.
// We only respond with the keyAuth, when we're receiving a GET requests with
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
mux := http.NewServeMux()
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.Host, domain) && r.Method == http.MethodGet {
if r.Method == http.MethodGet && s.matcher.matches(r, domain) {
w.Header().Add("Content-Type", "text/plain")
_, err := w.Write([]byte(keyAuth))
if err != nil {
@ -73,7 +99,7 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) {
}
log.Infof("[%s] Served key authentication", domain)
} else {
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method)
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
_, err := w.Write([]byte("TEST"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -10,7 +10,7 @@ import (
type obtainError map[string]error
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
var domains []string
for domain := range e {

View file

@ -4,9 +4,9 @@ import (
"fmt"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/log"
)
// Interface for all challenge solvers to implement.
@ -167,7 +167,7 @@ func cleanUp(solvr solver, authz acme.Authorization) {
domain := challenge.GetTargetedDomain(authz)
err := solvr.CleanUp(authz)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v ", domain, err)
log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err)
}
}
}

View file

@ -8,14 +8,14 @@ import (
"strconv"
"time"
"github.com/cenkalti/backoff"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/challenge/http01"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/log"
"github.com/cenkalti/backoff/v4"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/challenge/http01"
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
"github.com/go-acme/lego/v3/log"
)
type byType []acme.Challenge
@ -79,7 +79,7 @@ func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
func validate(core *api.Core, domain string, chlg acme.Challenge) error {
chlng, err := core.Challenges.New(chlg.URL)
if err != nil {
return fmt.Errorf("failed to initiate challenge: %v", err)
return fmt.Errorf("failed to initiate challenge: %w", err)
}
valid, err := checkChallengeStatus(chlng)

View file

@ -8,15 +8,15 @@ import (
"encoding/asn1"
"fmt"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/certcrypto"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/log"
)
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.1
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
@ -57,12 +57,12 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
err = c.provider.Present(domain, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %v", challenge.GetTargetedDomain(authz), err)
return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
}
defer func() {
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v", challenge.GetTargetedDomain(authz), err)
log.Warnf("[%s] acme: cleaning up failed: %v", challenge.GetTargetedDomain(authz), err)
}
}()
@ -83,7 +83,7 @@ func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
// Add the keyAuth digest as the acmeValidation-v1 extension
// (marked as critical such that it won't be used by non-ACME software).
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-3
extensions := []pkix.Extension{
{
Id: idPeAcmeIdentifierV1,

View file

@ -7,7 +7,7 @@ import (
"net/http"
"strings"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/log"
)
const (
@ -60,13 +60,13 @@ func (s *ProviderServer) Present(domain, token, keyAuth string) error {
// We must set that the `acme-tls/1` application level protocol is supported
// so that the protocol negotiation can succeed. Reference:
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.2
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
tlsConf.NextProtos = []string{ACMETLS1Protocol}
// Create the listener with the created tls.Config.
s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
if err != nil {
return fmt.Errorf("could not start HTTPS server for challenge -> %v", err)
return fmt.Errorf("could not start HTTPS server for challenge: %w", err)
}
// Shut the server down when we're finished.

View file

@ -4,10 +4,10 @@ import (
"errors"
"net/url"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge/resolver"
"github.com/go-acme/lego/registration"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/certificate"
"github.com/go-acme/lego/v3/challenge/resolver"
"github.com/go-acme/lego/v3/registration"
)
// Client is the user-friendly way to ACME

View file

@ -10,8 +10,8 @@ import (
"os"
"time"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/registration"
"github.com/go-acme/lego/v3/certcrypto"
"github.com/go-acme/lego/v3/registration"
)
const (

View file

@ -4,19 +4,19 @@ import (
"fmt"
"time"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/log"
)
// For polls the given function 'f', once every 'interval', up to 'timeout'.
func For(msg string, timeout, interval time.Duration, f func() (bool, error)) error {
log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
var lastErr string
var lastErr error
timeUp := time.After(timeout)
for {
select {
case <-timeUp:
return fmt.Errorf("time limit exceeded: last error: %s", lastErr)
return fmt.Errorf("time limit exceeded: last error: %w", lastErr)
default:
}
@ -25,7 +25,7 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er
return nil
}
if err != nil {
lastErr = err.Error()
lastErr = err
}
time.Sleep(interval)

View file

@ -4,14 +4,14 @@ import (
"errors"
"net/http"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/log"
"github.com/go-acme/lego/v3/acme"
"github.com/go-acme/lego/v3/acme/api"
"github.com/go-acme/lego/v3/log"
)
// Resource represents all important information about a registration
// of which the client needs to keep track itself.
// Deprecated: will be remove in the future (acme.ExtendedAccount).
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
type Resource struct {
Body acme.Account `json:"body,omitempty"`
URI string `json:"uri,omitempty"`
@ -115,6 +115,30 @@ func (r *Registrar) QueryRegistration() (*Resource, error) {
}, nil
}
// UpdateRegistration update the user registration on the ACME server.
func (r *Registrar) UpdateRegistration(options RegisterOptions) (*Resource, error) {
if r == nil || r.user == nil {
return nil, errors.New("acme: cannot update a nil client or user")
}
accMsg := acme.Account{
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
Contact: []string{},
}
if r.user.GetEmail() != "" {
log.Infof("acme: Registering account for %s", r.user.GetEmail())
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
}
account, err := r.core.Accounts.Update(r.user.GetRegistration().URI, accMsg)
if err != nil {
return nil, err
}
return &Resource{URI: account.Location, Body: account.Account}, nil
}
// DeleteRegistration deletes the client's user registration from the ACME server.
func (r *Registrar) DeleteRegistration() error {
if r == nil || r.user == nil {