Update dependencies

This commit is contained in:
Ken-Håvard Lieng 2019-06-09 02:01:48 +02:00
parent 7ad76273c0
commit 540efa03c4
485 changed files with 57821 additions and 12429 deletions

21
vendor/github.com/go-acme/lego/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2017 Sebastian Erhart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

69
vendor/github.com/go-acme/lego/acme/api/account.go generated vendored Normal file
View file

@ -0,0 +1,69 @@
package api
import (
"encoding/base64"
"errors"
"fmt"
"github.com/go-acme/lego/acme"
)
type AccountService service
// New Creates a new account.
func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
var account acme.Account
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
location := getLocation(resp)
if len(location) > 0 {
a.core.jws.SetKid(location)
}
if err != nil {
return acme.ExtendedAccount{Location: location}, err
}
return acme.ExtendedAccount{Account: account, Location: location}, nil
}
// NewEAB Creates a new account with an External Account Binding.
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)
}
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)
}
accMsg.ExternalAccountBinding = eabJWS
return a.New(accMsg)
}
// Get Retrieves an account.
func (a *AccountService) Get(accountURL string) (acme.Account, error) {
if len(accountURL) == 0 {
return acme.Account{}, errors.New("account[get]: empty URL")
}
var account acme.Account
_, err := a.core.post(accountURL, acme.Account{}, &account)
if err != nil {
return acme.Account{}, err
}
return account, nil
}
// Deactivate Deactivates an account.
func (a *AccountService) Deactivate(accountURL string) error {
if len(accountURL) == 0 {
return errors.New("account[deactivate]: empty URL")
}
req := acme.Account{Status: acme.StatusDeactivated}
_, err := a.core.post(accountURL, req, nil)
return err
}

166
vendor/github.com/go-acme/lego/acme/api/api.go generated vendored Normal file
View file

@ -0,0 +1,166 @@
package api
import (
"bytes"
"context"
"crypto"
"encoding/json"
"errors"
"fmt"
"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"
)
// Core ACME/LE core API.
type Core struct {
doer *sender.Doer
nonceManager *nonces.Manager
jws *secure.JWS
directory acme.Directory
HTTPClient *http.Client
common service // Reuse a single struct instead of allocating one for each service on the heap.
Accounts *AccountService
Authorizations *AuthorizationService
Certificates *CertificateService
Challenges *ChallengeService
Orders *OrderService
}
// New Creates a new Core.
func New(httpClient *http.Client, userAgent string, caDirURL, kid string, privateKey crypto.PrivateKey) (*Core, error) {
doer := sender.NewDoer(httpClient, userAgent)
dir, err := getDirectory(doer, caDirURL)
if err != nil {
return nil, err
}
nonceManager := nonces.NewManager(doer, dir.NewNonceURL)
jws := secure.NewJWS(privateKey, kid, nonceManager)
c := &Core{doer: doer, nonceManager: nonceManager, jws: jws, directory: dir, HTTPClient: httpClient}
c.common.core = c
c.Accounts = (*AccountService)(&c.common)
c.Authorizations = (*AuthorizationService)(&c.common)
c.Certificates = (*CertificateService)(&c.common)
c.Challenges = (*ChallengeService)(&c.common)
c.Orders = (*OrderService)(&c.common)
return c, nil
}
// post performs an HTTP POST request and parses the response body as JSON,
// into the provided respBody object.
func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, error) {
content, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.New("failed to marshal message")
}
return a.retrievablePost(uri, content, response)
}
// postAsGet performs an HTTP POST ("POST-as-GET") request.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
return a.retrievablePost(uri, []byte{}, response)
}
func (a *Core) retrievablePost(uri string, content []byte, response interface{}) (*http.Response, error) {
// during tests, allow to support ~90% of bad nonce with a minimum of attempts.
bo := backoff.NewExponentialBackOff()
bo.InitialInterval = 200 * time.Millisecond
bo.MaxInterval = 5 * time.Second
bo.MaxElapsedTime = 20 * time.Second
ctx, cancel := context.WithCancel(context.Background())
var resp *http.Response
operation := func() error {
var err error
resp, err = a.signedPost(uri, content, response)
if err != nil {
switch err.(type) {
// Retry if the nonce was invalidated
case *acme.NonceError:
log.Infof("nonce error retry: %s", err)
return err
default:
cancel()
return err
}
}
return nil
}
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
if err != nil {
return nil, err
}
return resp, nil
}
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)
}
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
// nonceErr is ignored to keep the root error.
nonce, nonceErr := nonces.GetFromResponse(resp)
if nonceErr == nil {
a.nonceManager.Push(nonce)
}
return resp, err
}
func (a *Core) signEABContent(newAccountURL, kid string, hmac []byte) ([]byte, error) {
eabJWS, err := a.jws.SignEABContent(newAccountURL, kid, hmac)
if err != nil {
return nil, err
}
return []byte(eabJWS.FullSerialize()), nil
}
// GetKeyAuthorization Gets the key authorization
func (a *Core) GetKeyAuthorization(token string) (string, error) {
return a.jws.GetKeyAuthorization(token)
}
func (a *Core) GetDirectory() acme.Directory {
return a.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)
}
if dir.NewAccountURL == "" {
return dir, errors.New("directory missing new registration URL")
}
if dir.NewOrderURL == "" {
return dir, errors.New("directory missing new order URL")
}
return dir, nil
}

View file

@ -0,0 +1,34 @@
package api
import (
"errors"
"github.com/go-acme/lego/acme"
)
type AuthorizationService service
// Get Gets an authorization.
func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error) {
if len(authzURL) == 0 {
return acme.Authorization{}, errors.New("authorization[get]: empty URL")
}
var authz acme.Authorization
_, err := c.core.postAsGet(authzURL, &authz)
if err != nil {
return acme.Authorization{}, err
}
return authz, nil
}
// Deactivate Deactivates an authorization.
func (c *AuthorizationService) Deactivate(authzURL string) error {
if len(authzURL) == 0 {
return errors.New("authorization[deactivate]: empty URL")
}
var disabledAuth acme.Authorization
_, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth)
return err
}

99
vendor/github.com/go-acme/lego/acme/api/certificate.go generated vendored Normal file
View file

@ -0,0 +1,99 @@
package api
import (
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"net/http"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/log"
)
// maxBodySize is the maximum size of body that we will read.
const maxBodySize = 1024 * 1024
type CertificateService service
// Get Returns the certificate and the issuer certificate.
// 'bundle' is only applied if the issuer is provided by the 'up' link.
func (c *CertificateService) Get(certURL string, bundle bool) ([]byte, []byte, error) {
cert, up, err := c.get(certURL)
if err != nil {
return nil, nil, err
}
// Get issuerCert from bundled response from Let's Encrypt
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
_, issuer := pem.Decode(cert)
if issuer != nil {
return cert, issuer, nil
}
issuer, err = c.getIssuerFromLink(up)
if err != nil {
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err)
} else if len(issuer) > 0 {
// If bundle is true, we want to return a certificate bundle.
// To do this, we append the issuer cert to the issued cert.
if bundle {
cert = append(cert, issuer...)
}
}
return cert, issuer, nil
}
// Revoke Revokes a certificate.
func (c *CertificateService) Revoke(req acme.RevokeCertMessage) error {
_, err := c.core.post(c.core.GetDirectory().RevokeCertURL, req, nil)
return err
}
// get Returns the certificate and the "up" link.
func (c *CertificateService) get(certURL string) ([]byte, string, error) {
if len(certURL) == 0 {
return nil, "", errors.New("certificate[get]: empty URL")
}
resp, err := c.core.postAsGet(certURL, nil)
if err != nil {
return nil, "", err
}
cert, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if err != nil {
return nil, "", err
}
// 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
up := getLink(resp.Header, "up")
return cert, up, err
}
// getIssuerFromLink requests the issuer certificate
func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) {
if len(up) == 0 {
return nil, nil
}
log.Infof("acme: Requesting issuer cert from %s", up)
cert, _, err := c.get(up)
if err != nil {
return nil, err
}
_, err = x509.ParseCertificate(cert)
if err != nil {
return nil, err
}
return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert)), nil
}

45
vendor/github.com/go-acme/lego/acme/api/challenge.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
package api
import (
"errors"
"github.com/go-acme/lego/acme"
)
type ChallengeService service
// New Creates a challenge.
func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) {
if len(chlgURL) == 0 {
return acme.ExtendedChallenge{}, errors.New("challenge[new]: empty URL")
}
// Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`.
// We use an empty struct instance as the postJSON payload here to achieve this result.
var chlng acme.ExtendedChallenge
resp, err := c.core.post(chlgURL, struct{}{}, &chlng)
if err != nil {
return acme.ExtendedChallenge{}, err
}
chlng.AuthorizationURL = getLink(resp.Header, "up")
chlng.RetryAfter = getRetryAfter(resp)
return chlng, nil
}
// Get Gets a challenge.
func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) {
if len(chlgURL) == 0 {
return acme.ExtendedChallenge{}, errors.New("challenge[get]: empty URL")
}
var chlng acme.ExtendedChallenge
resp, err := c.core.postAsGet(chlgURL, &chlng)
if err != nil {
return acme.ExtendedChallenge{}, err
}
chlng.AuthorizationURL = getLink(resp.Header, "up")
chlng.RetryAfter = getRetryAfter(resp)
return chlng, nil
}

View file

@ -0,0 +1,78 @@
package nonces
import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/go-acme/lego/acme/api/internal/sender"
)
// Manager Manages nonces.
type Manager struct {
do *sender.Doer
nonceURL string
nonces []string
sync.Mutex
}
// NewManager Creates a new Manager.
func NewManager(do *sender.Doer, nonceURL string) *Manager {
return &Manager{
do: do,
nonceURL: nonceURL,
}
}
// Pop Pops a nonce.
func (n *Manager) Pop() (string, bool) {
n.Lock()
defer n.Unlock()
if len(n.nonces) == 0 {
return "", false
}
nonce := n.nonces[len(n.nonces)-1]
n.nonces = n.nonces[:len(n.nonces)-1]
return nonce, true
}
// Push Pushes a nonce.
func (n *Manager) Push(nonce string) {
n.Lock()
defer n.Unlock()
n.nonces = append(n.nonces, nonce)
}
// Nonce implement jose.NonceSource
func (n *Manager) Nonce() (string, error) {
if nonce, ok := n.Pop(); ok {
return nonce, nil
}
return n.getNonce()
}
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 GetFromResponse(resp)
}
// GetFromResponse Extracts a nonce from a HTTP response.
func GetFromResponse(resp *http.Response) (string, error) {
if resp == nil {
return "", errors.New("nil response")
}
nonce := resp.Header.Get("Replay-Nonce")
if nonce == "" {
return "", fmt.Errorf("server did not respond with a proper nonce header")
}
return nonce, nil
}

View file

@ -0,0 +1,130 @@
package secure
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/base64"
"fmt"
"github.com/go-acme/lego/acme/api/internal/nonces"
jose "gopkg.in/square/go-jose.v2"
)
// JWS Represents a JWS.
type JWS struct {
privKey crypto.PrivateKey
kid string // Key identifier
nonces *nonces.Manager
}
// NewJWS Create a new JWS.
func NewJWS(privateKey crypto.PrivateKey, kid string, nonceManager *nonces.Manager) *JWS {
return &JWS{
privKey: privateKey,
nonces: nonceManager,
kid: kid,
}
}
// SetKid Sets a key identifier.
func (j *JWS) SetKid(kid string) {
j.kid = kid
}
// SignContent Signs a content with the JWS.
func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
var alg jose.SignatureAlgorithm
switch k := j.privKey.(type) {
case *rsa.PrivateKey:
alg = jose.RS256
case *ecdsa.PrivateKey:
if k.Curve == elliptic.P256() {
alg = jose.ES256
} else if k.Curve == elliptic.P384() {
alg = jose.ES384
}
}
signKey := jose.SigningKey{
Algorithm: alg,
Key: jose.JSONWebKey{Key: j.privKey, KeyID: j.kid},
}
options := jose.SignerOptions{
NonceSource: j.nonces,
ExtraHeaders: map[jose.HeaderKey]interface{}{
"url": url,
},
}
if j.kid == "" {
options.EmbedJWK = true
}
signer, err := jose.NewSigner(signKey, &options)
if err != nil {
return nil, fmt.Errorf("failed to create jose signer -> %v", err)
}
signed, err := signer.Sign(content)
if err != nil {
return nil, fmt.Errorf("failed to sign content -> %v", err)
}
return signed, nil
}
// SignEABContent Signs an external account binding content with the JWS.
func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
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)
}
signer, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
&jose.SignerOptions{
EmbedJWK: false,
ExtraHeaders: map[jose.HeaderKey]interface{}{
"kid": kid,
"url": url,
},
},
)
if err != nil {
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %v", err)
}
signed, err := signer.Sign(jwkJSON)
if err != nil {
return nil, fmt.Errorf("failed to External Account Binding sign content -> %v", err)
}
return signed, nil
}
// GetKeyAuthorization Gets the key authorization for a token.
func (j *JWS) GetKeyAuthorization(token string) (string, error) {
var publicKey crypto.PublicKey
switch k := j.privKey.(type) {
case *ecdsa.PrivateKey:
publicKey = k.Public()
case *rsa.PrivateKey:
publicKey = k.Public()
}
// Generate the Key Authorization for the challenge
jwk := &jose.JSONWebKey{Key: publicKey}
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
if err != nil {
return "", err
}
// unpad the base64URL
keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
return token + "." + keyThumb, nil
}

View file

@ -0,0 +1,146 @@
package sender
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"runtime"
"strings"
"github.com/go-acme/lego/acme"
)
type RequestOption func(*http.Request) error
func contentType(ct string) RequestOption {
return func(req *http.Request) error {
req.Header.Set("Content-Type", ct)
return nil
}
}
type Doer struct {
httpClient *http.Client
userAgent string
}
// NewDoer Creates a new Doer.
func NewDoer(client *http.Client, userAgent string) *Doer {
return &Doer{
httpClient: client,
userAgent: userAgent,
}
}
// Get performs a GET request with a proper User-Agent string.
// If "response" is not provided, callers should close resp.Body when done reading from it.
func (d *Doer) Get(url string, response interface{}) (*http.Response, error) {
req, err := d.newRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return d.do(req, response)
}
// Head performs a HEAD request with a proper User-Agent string.
// The response body (resp.Body) is already closed when this function returns.
func (d *Doer) Head(url string) (*http.Response, error) {
req, err := d.newRequest(http.MethodHead, url, nil)
if err != nil {
return nil, err
}
return d.do(req, nil)
}
// Post performs a POST request with a proper User-Agent string.
// If "response" is not provided, callers should close resp.Body when done reading from it.
func (d *Doer) Post(url string, body io.Reader, bodyType string, response interface{}) (*http.Response, error) {
req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType))
if err != nil {
return nil, err
}
return d.do(req, response)
}
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)
}
req.Header.Set("User-Agent", d.formatUserAgent())
for _, opt := range opts {
err = opt(req)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
}
return req, nil
}
func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, error) {
resp, err := d.httpClient.Do(req)
if err != nil {
return nil, err
}
if err = checkError(req, resp); err != nil {
return resp, err
}
if response != nil {
raw, err := ioutil.ReadAll(resp.Body)
if err != nil {
return resp, err
}
defer resp.Body.Close()
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, nil
}
// formatUserAgent builds and returns the User-Agent string to use in requests.
func (d *Doer) formatUserAgent() string {
ua := fmt.Sprintf("%s %s (%s; %s; %s)", d.userAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
return strings.TrimSpace(ua)
}
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)
}
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))
}
errorDetails.Method = req.Method
errorDetails.URL = req.URL.String()
// Check for errors we handle specifically
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
return &acme.NonceError{ProblemDetails: errorDetails}
}
return errorDetails
}
return nil
}

View file

@ -0,0 +1,14 @@
package sender
// CODE GENERATED AUTOMATICALLY
// THIS FILE MUST NOT BE EDITED BY HAND
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/2.6.0"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "release"
)

65
vendor/github.com/go-acme/lego/acme/api/order.go generated vendored Normal file
View file

@ -0,0 +1,65 @@
package api
import (
"encoding/base64"
"errors"
"github.com/go-acme/lego/acme"
)
type OrderService service
// New Creates a new order.
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
var identifiers []acme.Identifier
for _, domain := range domains {
identifiers = append(identifiers, acme.Identifier{Type: "dns", Value: domain})
}
orderReq := acme.Order{Identifiers: identifiers}
var order acme.Order
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
if err != nil {
return acme.ExtendedOrder{}, err
}
return acme.ExtendedOrder{
Location: resp.Header.Get("Location"),
Order: order,
}, nil
}
// Get Gets an order.
func (o *OrderService) Get(orderURL string) (acme.Order, error) {
if len(orderURL) == 0 {
return acme.Order{}, errors.New("order[get]: empty URL")
}
var order acme.Order
_, err := o.core.postAsGet(orderURL, &order)
if err != nil {
return acme.Order{}, err
}
return order, nil
}
// UpdateForCSR Updates an order for a CSR.
func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.Order, error) {
csrMsg := acme.CSRMessage{
Csr: base64.RawURLEncoding.EncodeToString(csr),
}
var order acme.Order
_, err := o.core.post(orderURL, csrMsg, &order)
if err != nil {
return acme.Order{}, err
}
if order.Status == acme.StatusInvalid {
return acme.Order{}, order.Error
}
return order, nil
}

45
vendor/github.com/go-acme/lego/acme/api/service.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
package api
import (
"net/http"
"regexp"
)
type service struct {
core *Core
}
// getLink get a rel into the Link header
func getLink(header http.Header, rel string) string {
var linkExpr = regexp.MustCompile(`<(.+?)>;\s*rel="(.+?)"`)
for _, link := range header["Link"] {
for _, m := range linkExpr.FindAllStringSubmatch(link, -1) {
if len(m) != 3 {
continue
}
if m[2] == rel {
return m[1]
}
}
}
return ""
}
// getLocation get the value of the header Location
func getLocation(resp *http.Response) string {
if resp == nil {
return ""
}
return resp.Header.Get("Location")
}
// getRetryAfter get the value of the header Retry-After
func getRetryAfter(resp *http.Response) string {
if resp == nil {
return ""
}
return resp.Header.Get("Retry-After")
}

284
vendor/github.com/go-acme/lego/acme/commons.go generated vendored Normal file
View file

@ -0,0 +1,284 @@
// Package acme contains all objects related the ACME endpoints.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16
package acme
import (
"encoding/json"
"time"
)
// Challenge statuses
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.6
const (
StatusPending = "pending"
StatusInvalid = "invalid"
StatusValid = "valid"
StatusProcessing = "processing"
StatusDeactivated = "deactivated"
StatusExpired = "expired"
StatusRevoked = "revoked"
)
// Directory the ACME directory object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
type Directory struct {
NewNonceURL string `json:"newNonce"`
NewAccountURL string `json:"newAccount"`
NewOrderURL string `json:"newOrder"`
NewAuthzURL string `json:"newAuthz"`
RevokeCertURL string `json:"revokeCert"`
KeyChangeURL string `json:"keyChange"`
Meta Meta `json:"meta"`
}
// Meta the ACME meta object (related to Directory).
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
type Meta struct {
// termsOfService (optional, string):
// A URL identifying the current terms of service.
TermsOfService string `json:"termsOfService"`
// website (optional, string):
// An HTTP or HTTPS URL locating a website providing more information about the ACME server.
Website string `json:"website"`
// caaIdentities (optional, array of string):
// The hostnames that the ACME server recognizes as referring to itself
// for the purposes of CAA record validation as defined in [RFC6844].
// Each string MUST represent the same sequence of ASCII code points
// that the server will expect to see as the "Issuer Domain Name" in a CAA issue or issuewild property tag.
// This allows clients to determine the correct issuer domain name to use when configuring CAA records.
CaaIdentities []string `json:"caaIdentities"`
// externalAccountRequired (optional, boolean):
// If this field is present and set to "true",
// then the CA requires that all new- account requests include an "externalAccountBinding" field
// associating the new account with an external account.
ExternalAccountRequired bool `json:"externalAccountRequired"`
}
// ExtendedAccount a extended Account.
type ExtendedAccount struct {
Account
// Contains the value of the response header `Location`
Location string `json:"-"`
}
// 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
type Account struct {
// status (required, string):
// The status of this account.
// Possible values are: "valid", "deactivated", and "revoked".
// The value "deactivated" should be used to indicate client-initiated deactivation
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
Status string `json:"status,omitempty"`
// contact (optional, array of string):
// An array of URLs that the server can use to contact the client for issues related to this account.
// For example, the server may wish to notify the client about server-initiated revocation or certificate expiration.
// For information on supported URL schemes, see Section 7.3
Contact []string `json:"contact,omitempty"`
// termsOfServiceAgreed (optional, boolean):
// Including this field in a new-account request,
// with a value of true, indicates the client's agreement with the terms of service.
// This field is not updateable by the client.
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
// orders (required, string):
// A URL from which a list of orders submitted by this account can be fetched via a POST-as-GET request,
// as described in Section 7.1.2.1.
Orders string `json:"orders,omitempty"`
// onlyReturnExisting (optional, boolean):
// If this field is present with the value "true",
// then the server MUST NOT create a new account if one does not already exist.
// This allows a client to look up an account URL based on an account key (see Section 7.3.1).
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
// externalAccountBinding (optional, object):
// An optional field for binding the new account with an existing non-ACME account (see Section 7.3.4).
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
}
// ExtendedOrder a extended Order.
type ExtendedOrder struct {
Order
// The order URL, contains the value of the response header `Location`
Location string `json:"-"`
}
// Order the ACME order Object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.3
type Order struct {
// status (required, string):
// The status of this order.
// Possible values are: "pending", "ready", "processing", "valid", and "invalid".
Status string `json:"status,omitempty"`
// expires (optional, string):
// The timestamp after which the server will consider this order invalid,
// encoded in the format specified in RFC 3339 [RFC3339].
// This field is REQUIRED for objects with "pending" or "valid" in the status field.
Expires string `json:"expires,omitempty"`
// identifiers (required, array of object):
// An array of identifier objects that the order pertains to.
Identifiers []Identifier `json:"identifiers"`
// notBefore (optional, string):
// The requested value of the notBefore field in the certificate,
// in the date format defined in [RFC3339].
NotBefore string `json:"notBefore,omitempty"`
// notAfter (optional, string):
// The requested value of the notAfter field in the certificate,
// in the date format defined in [RFC3339].
NotAfter string `json:"notAfter,omitempty"`
// error (optional, object):
// The error that occurred while processing the order, if any.
// This field is structured as a problem document [RFC7807].
Error *ProblemDetails `json:"error,omitempty"`
// authorizations (required, array of string):
// For pending orders,
// the authorizations that the client needs to complete before the requested certificate can be issued (see Section 7.5),
// including unexpired authorizations that the client has completed in the past for identifiers specified in the order.
// The authorizations required are dictated by server policy
// and there may not be a 1:1 relationship between the order identifiers and the authorizations required.
// For final orders (in the "valid" or "invalid" state), the authorizations that were completed.
// Each entry is a URL from which an authorization can be fetched with a POST-as-GET request.
Authorizations []string `json:"authorizations,omitempty"`
// finalize (required, string):
// A URL that a CSR must be POSTed to once all of the order's authorizations are satisfied to finalize the order.
// The result of a successful finalization will be the population of the certificate URL for the order.
Finalize string `json:"finalize,omitempty"`
// certificate (optional, string):
// A URL for the certificate that has been issued in response to this order
Certificate string `json:"certificate,omitempty"`
}
// Authorization the ACME authorization object.
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
type Authorization struct {
// status (required, string):
// The status of this authorization.
// Possible values are: "pending", "valid", "invalid", "deactivated", "expired", and "revoked".
Status string `json:"status"`
// expires (optional, string):
// The timestamp after which the server will consider this authorization invalid,
// encoded in the format specified in RFC 3339 [RFC3339].
// This field is REQUIRED for objects with "valid" in the "status" field.
Expires time.Time `json:"expires,omitempty"`
// identifier (required, object):
// The identifier that the account is authorized to represent
Identifier Identifier `json:"identifier,omitempty"`
// challenges (required, array of objects):
// For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier.
// For valid authorizations, the challenge that was validated.
// For invalid authorizations, the challenge that was attempted and failed.
// Each array entry is an object with parameters required to validate the challenge.
// A client should attempt to fulfill one of these challenges,
// and a server should consider any one of the challenges sufficient to make the authorization valid.
Challenges []Challenge `json:"challenges,omitempty"`
// wildcard (optional, boolean):
// For authorizations created as a result of a newOrder request containing a DNS identifier
// with a value that contained a wildcard prefix this field MUST be present, and true.
Wildcard bool `json:"wildcard,omitempty"`
}
// ExtendedChallenge a extended Challenge.
type ExtendedChallenge struct {
Challenge
// Contains the value of the response header `Retry-After`
RetryAfter string `json:"-"`
// Contains the value of the response header `Link` rel="up"
AuthorizationURL string `json:"-"`
}
// 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
type Challenge struct {
// type (required, string):
// The type of challenge encoded in the object.
Type string `json:"type"`
// url (required, string):
// The URL to which a response can be posted.
URL string `json:"url"`
// status (required, string):
// The status of this challenge. Possible values are: "pending", "processing", "valid", and "invalid".
Status string `json:"status"`
// validated (optional, string):
// The time at which the server validated this challenge,
// encoded in the format specified in RFC 3339 [RFC3339].
// This field is REQUIRED if the "status" field is "valid".
Validated time.Time `json:"validated,omitempty"`
// error (optional, object):
// Error that occurred while the server was validating the challenge, if any,
// structured as a problem document [RFC7807].
// Multiple errors can be indicated by using subproblems Section 6.7.1.
// A challenge object with an error MUST have status equal to "invalid".
Error *ProblemDetails `json:"error,omitempty"`
// token (required, string):
// A random value that uniquely identifies the challenge.
// This value MUST have at least 128 bits of entropy.
// 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
Token string `json:"token"`
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#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
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
type CSRMessage struct {
// csr (required, string):
// A CSR encoding the parameters for the certificate being requested [RFC2986].
// The CSR is sent in the base64url-encoded version of the DER format.
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.).
Csr string `json:"csr"`
}
// RevokeCertMessage a certificate revocation message
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.6
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
type RevokeCertMessage struct {
// certificate (required, string):
// The certificate to be revoked, in the base64url-encoded version of the DER format.
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.)
Certificate string `json:"certificate"`
// reason (optional, int):
// One of the revocation reasonCodes defined in Section 5.3.1 of [RFC5280] to be used when generating OCSP responses and CRLs.
// If this field is not set the server SHOULD omit the reasonCode CRL entry extension when generating OCSP responses and CRLs.
// The server MAY disallow a subset of reasonCodes from being used by the user.
// If a request contains a disallowed reasonCode the server MUST reject it with the error type "urn:ietf:params:acme:error:badRevocationReason".
// The problem document detail SHOULD indicate which reasonCodes are allowed.
Reason *uint `json:"reason,omitempty"`
}

58
vendor/github.com/go-acme/lego/acme/errors.go generated vendored Normal file
View file

@ -0,0 +1,58 @@
package acme
import (
"fmt"
)
// Errors types
const (
errNS = "urn:ietf:params:acme:error:"
BadNonceErr = errNS + "badNonce"
)
// 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
type ProblemDetails struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
HTTPStatus int `json:"status,omitempty"`
Instance string `json:"instance,omitempty"`
SubProblems []SubProblem `json:"subproblems,omitempty"`
// additional values to have a better error message (Not defined by the RFC)
Method string `json:"method,omitempty"`
URL string `json:"url,omitempty"`
}
// SubProblem a "subproblems"
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.7.1
type SubProblem struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
Identifier Identifier `json:"identifier,omitempty"`
}
func (p ProblemDetails) Error() string {
msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus)
if len(p.Method) != 0 || len(p.URL) != 0 {
msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)
}
msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)
for _, sub := range p.SubProblems {
msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)
}
if len(p.Instance) == 0 {
msg += ", url: " + p.Instance
}
return msg
}
// NonceError represents the error which is returned
// if the nonce sent by the client was not accepted by the server.
type NonceError struct {
*ProblemDetails
}

256
vendor/github.com/go-acme/lego/certcrypto/crypto.go generated vendored Normal file
View file

@ -0,0 +1,256 @@
package certcrypto
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"
"time"
"golang.org/x/crypto/ocsp"
)
// Constants for all key types we support.
const (
EC256 = KeyType("P256")
EC384 = KeyType("P384")
RSA2048 = KeyType("2048")
RSA4096 = KeyType("4096")
RSA8192 = KeyType("8192")
)
const (
// OCSPGood means that the certificate is valid.
OCSPGood = ocsp.Good
// OCSPRevoked means that the certificate has been deliberately revoked.
OCSPRevoked = ocsp.Revoked
// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
OCSPUnknown = ocsp.Unknown
// OCSPServerFailed means that the OCSP responder failed to process the request.
OCSPServerFailed = ocsp.ServerFailed
)
// Constants for OCSP must staple
var (
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
)
// KeyType represents the key algo as well as the key size or curve to use.
type KeyType string
type DERCertificateBytes []byte
// ParsePEMBundle parses a certificate bundle from top to bottom and returns
// a slice of x509 certificates. This function will error if no certificates are found.
func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
var certificates []*x509.Certificate
var certDERBlock *pem.Block
for {
certDERBlock, bundle = pem.Decode(bundle)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, cert)
}
}
if len(certificates) == 0 {
return nil, errors.New("no certificates were found while parsing the bundle")
}
return certificates, nil
}
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
keyBlock, _ := 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")
}
}
func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
switch keyType {
case EC256:
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case EC384:
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case RSA2048:
return rsa.GenerateKey(rand.Reader, 2048)
case RSA4096:
return rsa.GenerateKey(rand.Reader, 4096)
case RSA8192:
return rsa.GenerateKey(rand.Reader, 8192)
}
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
}
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
template := x509.CertificateRequest{
Subject: pkix.Name{CommonName: domain},
DNSNames: san,
}
if mustStaple {
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
Id: tlsFeatureExtensionOID,
Value: ocspMustStapleFeature,
})
}
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
}
func PEMEncode(data interface{}) []byte {
return pem.EncodeToMemory(PEMBlock(data))
}
func PEMBlock(data interface{}) *pem.Block {
var pemBlock *pem.Block
switch key := data.(type) {
case *ecdsa.PrivateKey:
keyBytes, _ := x509.MarshalECPrivateKey(key)
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
case *rsa.PrivateKey:
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
case *x509.CertificateRequest:
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
case DERCertificateBytes:
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(DERCertificateBytes))}
}
return pemBlock
}
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 pemBlock, nil
}
func PemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
pemBlock, err := pemDecode(pem)
if pemBlock == nil {
return nil, err
}
if pemBlock.Type != "CERTIFICATE REQUEST" {
return nil, fmt.Errorf("PEM block is not a certificate request")
}
return x509.ParseCertificateRequest(pemBlock.Bytes)
}
// ParsePEMCertificate returns Certificate from a PEM encoded certificate.
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
pemBlock, err := pemDecode(cert)
if pemBlock == nil {
return nil, err
}
// from a DER encoded certificate
return x509.ParseCertificate(pemBlock.Bytes)
}
func ExtractDomains(cert *x509.Certificate) []string {
domains := []string{cert.Subject.CommonName}
// Check for SAN certificate
for _, sanDomain := range cert.DNSNames {
if sanDomain == cert.Subject.CommonName {
continue
}
domains = append(domains, sanDomain)
}
return domains
}
func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
domains := []string{csr.Subject.CommonName}
// loop over the SubjectAltName DNS names
for _, sanName := range csr.DNSNames {
if containsSAN(domains, sanName) {
// Duplicate; skip this name
continue
}
// Name is unique
domains = append(domains, sanName)
}
return domains
}
func containsSAN(domains []string, sanName string) bool {
for _, existingName := range domains {
if existingName == sanName {
return true
}
}
return false
}
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
}
func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
if expiration.IsZero() {
expiration = time.Now().Add(365)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "ACME Challenge TEMP",
},
NotBefore: time.Now(),
NotAfter: expiration,
KeyUsage: x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true,
DNSNames: []string{domain},
ExtraExtensions: extensions,
}
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
}

View file

@ -0,0 +1,69 @@
package certificate
import (
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/log"
)
const (
// overallRequestLimit is the overall number of request per second
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
// From the documentation the limitation is 20 requests per second,
// but using 20 as value doesn't work but 18 do
overallRequestLimit = 18
)
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
resc, errc := make(chan acme.Authorization), make(chan domainError)
delay := time.Second / overallRequestLimit
for _, authzURL := range order.Authorizations {
time.Sleep(delay)
go func(authzURL string) {
authz, err := c.core.Authorizations.Get(authzURL)
if err != nil {
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
return
}
resc <- authz
}(authzURL)
}
var responses []acme.Authorization
failures := make(obtainError)
for i := 0; i < len(order.Authorizations); i++ {
select {
case res := <-resc:
responses = append(responses, res)
case err := <-errc:
failures[err.Domain] = err.Error
}
}
for i, auth := range order.Authorizations {
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
}
close(resc)
close(errc)
// be careful to not return an empty failures map;
// even if empty, they become non-nil error values
if len(failures) > 0 {
return responses, failures
}
return responses, nil
}
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)
}
}
}

View file

@ -0,0 +1,522 @@
package certificate
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"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"
"golang.org/x/crypto/ocsp"
"golang.org/x/net/idna"
)
// maxBodySize is the maximum size of body that we will read.
const maxBodySize = 1024 * 1024
// Resource represents a CA issued certificate.
// PrivateKey, Certificate and IssuerCertificate are all
// already PEM encoded and can be directly written to disk.
// Certificate may be a certificate bundle,
// depending on the options supplied to create it.
type Resource struct {
Domain string `json:"domain"`
CertURL string `json:"certUrl"`
CertStableURL string `json:"certStableUrl"`
PrivateKey []byte `json:"-"`
Certificate []byte `json:"-"`
IssuerCertificate []byte `json:"-"`
CSR []byte `json:"-"`
}
// ObtainRequest The request to obtain certificate.
//
// The first domain in domains is used for the CommonName field of the certificate,
// all other domains are added using the Subject Alternate Names extension.
//
// A new private key is generated for every invocation of the function Obtain.
// If you do not want that you can supply your own private key in the privateKey parameter.
// If this parameter is non-nil it will be used instead of generating a new one.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
type ObtainRequest struct {
Domains []string
Bundle bool
PrivateKey crypto.PrivateKey
MustStaple bool
}
type resolver interface {
Solve(authorizations []acme.Authorization) error
}
type CertifierOptions struct {
KeyType certcrypto.KeyType
Timeout time.Duration
}
// Certifier A service to obtain/renew/revoke certificates.
type Certifier struct {
core *api.Core
resolver resolver
options CertifierOptions
}
// NewCertifier creates a Certifier.
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
return &Certifier{
core: core,
resolver: resolver,
options: options,
}
}
// Obtain tries to obtain a single certificate using all domains passed into it.
//
// This function will never return a partial certificate.
// If one domain in the list fails, the whole certificate will fail.
func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
if len(request.Domains) == 0 {
return nil, errors.New("no domains to obtain a certificate for")
}
domains := sanitizeDomain(request.Domains)
if request.Bundle {
log.Infof("[%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
} else {
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
}
order, err := c.core.Orders.New(domains)
if err != nil {
return nil, err
}
authz, err := c.getAuthorizations(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
err = c.resolver.Solve(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(obtainError)
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple)
if err != nil {
for _, auth := range authz {
failures[challenge.GetTargetedDomain(auth)] = err
}
}
// Do not return an empty failures map, because
// it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
//
// The domains are inferred from the CommonName and SubjectAltNames, if any.
// The private key for this CSR is not required.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
//
// This function will never return a partial certificate.
// If one domain in the list fails, the whole certificate will fail.
func (c *Certifier) ObtainForCSR(csr x509.CertificateRequest, bundle bool) (*Resource, error) {
// figure out what domains it concerns
// start with the common name
domains := certcrypto.ExtractDomainsCSR(&csr)
if bundle {
log.Infof("[%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
} else {
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
}
order, err := c.core.Orders.New(domains)
if err != nil {
return nil, err
}
authz, err := c.getAuthorizations(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
err = c.resolver.Solve(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
return nil, err
}
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(obtainError)
cert, err := c.getForCSR(domains, order, bundle, csr.Raw, nil)
if err != nil {
for _, auth := range authz {
failures[challenge.GetTargetedDomain(auth)] = err
}
}
if cert != nil {
// Add the CSR to the certificate so that it can be used for renewals.
cert.CSR = certcrypto.PEMEncode(&csr)
}
// Do not return an empty failures map,
// because it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool) (*Resource, error) {
if privateKey == nil {
var err error
privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType)
if err != nil {
return nil, err
}
}
// 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
// says:
// Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order
// object.
san := []string{commonName}
for _, auth := range order.Identifiers {
if auth.Value != commonName {
san = append(san, auth.Value)
}
}
// TODO: should the CSR be customizable?
csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple)
if err != nil {
return nil, err
}
return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey))
}
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr []byte, privateKeyPem []byte) (*Resource, error) {
respOrder, err := c.core.Orders.UpdateForCSR(order.Finalize, csr)
if err != nil {
return nil, err
}
commonName := domains[0]
certRes := &Resource{
Domain: commonName,
CertURL: respOrder.Certificate,
PrivateKey: privateKeyPem,
}
if respOrder.Status == acme.StatusValid {
// if the certificate is available right away, short cut!
ok, errR := c.checkResponse(respOrder, certRes, bundle)
if errR != nil {
return nil, errR
}
if ok {
return certRes, nil
}
}
timeout := c.options.Timeout
if c.options.Timeout <= 0 {
timeout = 30 * time.Second
}
err = wait.For("certificate", timeout, timeout/60, func() (bool, error) {
ord, errW := c.core.Orders.Get(order.Location)
if errW != nil {
return false, errW
}
done, errW := c.checkResponse(ord, certRes, bundle)
if errW != nil {
return false, errW
}
return done, nil
})
return certRes, err
}
// checkResponse checks to see if the certificate is ready and a link is contained in the response.
//
// If so, loads it into certRes and returns true.
// If the cert is not yet ready, it returns false.
//
// The certRes input should already have the Domain (common name) field populated.
//
// If bundle is true, the certificate will be bundled with the issuer's cert.
func (c *Certifier) checkResponse(order acme.Order, certRes *Resource, bundle bool) (bool, error) {
valid, err := checkOrderStatus(order)
if err != nil || !valid {
return valid, err
}
cert, issuer, err := c.core.Certificates.Get(order.Certificate, bundle)
if err != nil {
return false, err
}
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
certRes.IssuerCertificate = issuer
certRes.Certificate = cert
certRes.CertURL = order.Certificate
certRes.CertStableURL = order.Certificate
return true, nil
}
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
func (c *Certifier) Revoke(cert []byte) error {
certificates, err := certcrypto.ParsePEMBundle(cert)
if err != nil {
return err
}
x509Cert := certificates[0]
if x509Cert.IsCA {
return fmt.Errorf("certificate bundle starts with a CA certificate")
}
revokeMsg := acme.RevokeCertMessage{
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
}
return c.core.Certificates.Revoke(revokeMsg)
}
// Renew takes a Resource and tries to renew the certificate.
//
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
// Please be aware that this function will return a new certificate in ANY case that is not an error.
// If the server does not provide us with a new cert on a GET request to the CertURL
// this function will start a new-cert flow where a new certificate gets generated.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
//
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool) (*Resource, error) {
// Input certificate is PEM encoded.
// Decode it here as we may need the decoded cert later on in the renewal process.
// The input may be a bundle or a single certificate.
certificates, err := certcrypto.ParsePEMBundle(certRes.Certificate)
if err != nil {
return nil, err
}
x509Cert := certificates[0]
if x509Cert.IsCA {
return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", certRes.Domain)
}
// This is just meant to be informal for the user.
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
log.Infof("[%s] acme: Trying renewal with %d hours remaining", certRes.Domain, int(timeLeft.Hours()))
// We always need to request a new certificate to renew.
// Start by checking to see if the certificate was based off a CSR,
// and use that if it's defined.
if len(certRes.CSR) > 0 {
csr, errP := certcrypto.PemDecodeTox509CSR(certRes.CSR)
if errP != nil {
return nil, errP
}
return c.ObtainForCSR(*csr, bundle)
}
var privateKey crypto.PrivateKey
if certRes.PrivateKey != nil {
privateKey, err = certcrypto.ParsePEMPrivateKey(certRes.PrivateKey)
if err != nil {
return nil, err
}
}
query := ObtainRequest{
Domains: certcrypto.ExtractDomains(x509Cert),
Bundle: bundle,
PrivateKey: privateKey,
MustStaple: mustStaple,
}
return c.Obtain(query)
}
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
// the parsed response, and an error, if any.
//
// The returned []byte can be passed directly into the OCSPStaple property of a tls.Certificate.
// If the bundle only contains the issued certificate,
// this function will try to get the issuer certificate from the IssuingCertificateURL in the certificate.
//
// If the []byte and/or ocsp.Response return values are nil, the OCSP status may be assumed OCSPUnknown.
func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
certificates, err := certcrypto.ParsePEMBundle(bundle)
if err != nil {
return nil, nil, err
}
// We expect the certificate slice to be ordered downwards the chain.
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
// which should always be the first two certificates.
// If there's no OCSP server listed in the leaf cert, there's nothing to do.
// And if we have only one certificate so far, we need to get the issuer cert.
issuedCert := certificates[0]
if len(issuedCert.OCSPServer) == 0 {
return nil, nil, errors.New("no OCSP server specified in cert")
}
if len(certificates) == 1 {
// TODO: build fallback. If this fails, check the remaining array entries.
if len(issuedCert.IssuingCertificateURL) == 0 {
return nil, nil, errors.New("no issuing certificate URL")
}
resp, errC := c.core.HTTPClient.Get(issuedCert.IssuingCertificateURL[0])
if errC != nil {
return nil, nil, errC
}
defer resp.Body.Close()
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if errC != nil {
return nil, nil, errC
}
issuerCert, errC := x509.ParseCertificate(issuerBytes)
if errC != nil {
return nil, nil, errC
}
// Insert it into the slice on position 0
// We want it ordered right SRV CRT -> CA
certificates = append(certificates, issuerCert)
}
issuerCert := certificates[1]
// Finally kick off the OCSP request.
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
if err != nil {
return nil, nil, err
}
resp, err := c.core.HTTPClient.Post(issuedCert.OCSPServer[0], "application/ocsp-request", bytes.NewReader(ocspReq))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if err != nil {
return nil, nil, err
}
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
if err != nil {
return nil, nil, err
}
return ocspResBytes, ocspRes, nil
}
// Get attempts to fetch the certificate at the supplied URL.
// The URL is the same as what would normally be supplied at the Resource's CertURL.
//
// The returned Resource will not have the PrivateKey and CSR fields populated as these will not be available.
//
// If bundle is true, the Certificate field in the returned Resource includes the issuer certificate.
func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
cert, issuer, err := c.core.Certificates.Get(url, bundle)
if err != nil {
return nil, err
}
// Parse the returned cert bundle so that we can grab the domain from the common name.
x509Certs, err := certcrypto.ParsePEMBundle(cert)
if err != nil {
return nil, err
}
return &Resource{
Domain: x509Certs[0].Subject.CommonName,
Certificate: cert,
IssuerCertificate: issuer,
CertURL: url,
CertStableURL: url,
}, nil
}
func checkOrderStatus(order acme.Order) (bool, error) {
switch order.Status {
case acme.StatusValid:
return true, nil
case acme.StatusInvalid:
return false, order.Error
default:
return false, nil
}
}
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#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].
//
// https://tools.ietf.org/html/rfc5280#section-7
func sanitizeDomain(domains []string) []string {
var sanitizedDomains []string
for _, domain := range domains {
sanitizedDomain, err := idna.ToASCII(domain)
if err != nil {
log.Infof("skip domain %q: unable to sanitize (punnycode): %v", domain, err)
} else {
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
}
}
return sanitizedDomains
}

30
vendor/github.com/go-acme/lego/certificate/errors.go generated vendored Normal file
View file

@ -0,0 +1,30 @@
package certificate
import (
"bytes"
"fmt"
"sort"
)
// obtainError is returned when there are specific errors available per domain.
type obtainError map[string]error
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
var domains []string
for domain := range e {
domains = append(domains, domain)
}
sort.Strings(domains)
for _, domain := range domains {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
}
return buffer.String()
}
type domainError struct {
Domain string
Error error
}

44
vendor/github.com/go-acme/lego/challenge/challenges.go generated vendored Normal file
View file

@ -0,0 +1,44 @@
package challenge
import (
"fmt"
"github.com/go-acme/lego/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
// 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
// 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 = Type("tls-alpn-01")
)
func (t Type) String() string {
return string(t)
}
func FindChallenge(chlgType Type, authz acme.Authorization) (acme.Challenge, error) {
for _, chlg := range authz.Challenges {
if chlg.Type == string(chlgType) {
return chlg, nil
}
}
return acme.Challenge{}, fmt.Errorf("[%s] acme: unable to find challenge %s", GetTargetedDomain(authz), chlgType)
}
func GetTargetedDomain(authz acme.Authorization) string {
if authz.Wildcard {
return "*." + authz.Identifier.Value
}
return authz.Identifier.Value
}

View file

@ -0,0 +1,16 @@
package dns01
import "github.com/miekg/dns"
// Update FQDN with CNAME if any
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
for _, rr := range r.Answer {
if cn, ok := rr.(*dns.CNAME); ok {
if cn.Hdr.Name == fqdn {
return cn.Target
}
}
}
return fqdn
}

View file

@ -0,0 +1,188 @@
package dns01
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
"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/miekg/dns"
)
const (
// DefaultPropagationTimeout default propagation timeout
DefaultPropagationTimeout = 60 * time.Second
// DefaultPollingInterval default polling interval
DefaultPollingInterval = 2 * time.Second
// DefaultTTL default TTL
DefaultTTL = 120
)
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
type ChallengeOption func(*Challenge) error
// CondOption Conditional challenge option.
func CondOption(condition bool, opt ChallengeOption) ChallengeOption {
if !condition {
// NoOp options
return func(*Challenge) error {
return nil
}
}
return opt
}
// Challenge implements the dns-01 challenge
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
preCheck preCheck
dnsTimeout time.Duration
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
chlg := &Challenge{
core: core,
validate: validate,
provider: provider,
preCheck: newPreCheck(),
dnsTimeout: 10 * time.Second,
}
for _, opt := range opts {
err := opt(chlg)
if err != nil {
log.Infof("challenge option error: %v", err)
}
}
return chlg
}
// PreSolve just submits the txt record to the dns provider.
// It does not validate record propagation, or do anything at all with the acme server.
func (c *Challenge) PreSolve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Preparing to solve DNS-01", domain)
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil {
return err
}
if c.provider == nil {
return fmt.Errorf("[%s] acme: no DNS Provider configured", domain)
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
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 nil
}
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Trying to solve DNS-01", domain)
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil {
return err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
fqdn, value := GetRecord(authz.Identifier.Value, keyAuth)
var timeout, interval time.Duration
switch provider := c.provider.(type) {
case challenge.ProviderTimeout:
timeout, interval = provider.Timeout()
default:
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
}
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
err = wait.For("propagation", timeout, interval, func() (bool, error) {
stop, errP := c.preCheck.call(domain, fqdn, value)
if !stop || errP != nil {
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
}
return stop, errP
})
if err != nil {
return err
}
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}
// CleanUp cleans the challenge.
func (c *Challenge) CleanUp(authz acme.Authorization) error {
log.Infof("[%s] acme: Cleaning DNS-01 challenge", challenge.GetTargetedDomain(authz))
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil {
return err
}
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
return c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
}
func (c *Challenge) Sequential() (bool, time.Duration) {
if p, ok := c.provider.(sequential); ok {
return ok, p.Sequential()
}
return false, 0
}
type sequential interface {
Sequential() time.Duration
}
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge
func GetRecord(domain, keyAuth string) (fqdn string, value string) {
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
// base64URL encoding without padding
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
// Check if the domain has CNAME then return that
if err == nil && r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
}
}
return
}

View file

@ -0,0 +1,59 @@
package dns01
import (
"bufio"
"fmt"
"os"
"time"
)
const (
dnsTemplate = `%s %d IN TXT "%s"`
)
// DNSProviderManual is an implementation of the ChallengeProvider interface
type DNSProviderManual struct{}
// NewDNSProviderManual returns a DNSProviderManual instance.
func NewDNSProviderManual() (*DNSProviderManual, error) {
return &DNSProviderManual{}, nil
}
// Present prints instructions for manually creating the TXT record
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
fqdn, value := GetRecord(domain, keyAuth)
authZone, err := FindZoneByFqdn(fqdn)
if err != nil {
return err
}
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, value)
fmt.Printf("lego: Press 'Enter' when you are done\n")
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
return err
}
// CleanUp prints instructions for manually removing the TXT record
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := GetRecord(domain, keyAuth)
authZone, err := FindZoneByFqdn(fqdn)
if err != nil {
return err
}
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, "...")
return nil
}
// Sequential All DNS challenges for this provider will be resolved sequentially.
// Returns the interval between each iteration.
func (d *DNSProviderManual) Sequential() time.Duration {
return DefaultPropagationTimeout
}

19
vendor/github.com/go-acme/lego/challenge/dns01/fqdn.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package dns01
// ToFqdn converts the name into a fqdn appending a trailing dot.
func ToFqdn(name string) string {
n := len(name)
if n == 0 || name[n-1] == '.' {
return name
}
return name + "."
}
// UnFqdn converts the fqdn into a name removing the trailing dot.
func UnFqdn(name string) string {
n := len(name)
if n != 0 && name[n-1] == '.' {
return name[:n-1]
}
return name
}

View file

@ -0,0 +1,232 @@
package dns01
import (
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/miekg/dns"
)
const defaultResolvConf = "/etc/resolv.conf"
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
var dnsTimeout = 10 * time.Second
var (
fqdnToZone = map[string]string{}
muFqdnToZone sync.Mutex
)
var defaultNameservers = []string{
"google-public-dns-a.google.com:53",
"google-public-dns-b.google.com:53",
}
// recursiveNameservers are used to pre-check DNS propagation
var recursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
func ClearFqdnCache() {
muFqdnToZone.Lock()
fqdnToZone = map[string]string{}
muFqdnToZone.Unlock()
}
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
return func(_ *Challenge) error {
dnsTimeout = timeout
return nil
}
}
func AddRecursiveNameservers(nameservers []string) ChallengeOption {
return func(_ *Challenge) error {
recursiveNameservers = ParseNameservers(nameservers)
return nil
}
}
// getNameservers attempts to get systems nameservers before falling back to the defaults
func getNameservers(path string, defaults []string) []string {
config, err := dns.ClientConfigFromFile(path)
if err != nil || len(config.Servers) == 0 {
return defaults
}
return ParseNameservers(config.Servers)
}
func ParseNameservers(servers []string) []string {
var resolvers []string
for _, resolver := range servers {
// ensure all servers have a port number
if _, _, err := net.SplitHostPort(resolver); err != nil {
resolvers = append(resolvers, net.JoinHostPort(resolver, "53"))
} else {
resolvers = append(resolvers, resolver)
}
}
return resolvers
}
// lookupNameservers returns the authoritative nameservers for the given fqdn.
func lookupNameservers(fqdn string) ([]string, error) {
var authoritativeNss []string
zone, err := FindZoneByFqdn(fqdn)
if err != nil {
return nil, fmt.Errorf("could not determine the zone: %v", err)
}
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
if err != nil {
return nil, err
}
for _, rr := range r.Answer {
if ns, ok := rr.(*dns.NS); ok {
authoritativeNss = append(authoritativeNss, strings.ToLower(ns.Ns))
}
}
if len(authoritativeNss) > 0 {
return authoritativeNss, nil
}
return nil, fmt.Errorf("could not determine authoritative nameservers")
}
// FindZoneByFqdn 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 FindZoneByFqdn(fqdn string) (string, error) {
return FindZoneByFqdnCustom(fqdn, recursiveNameservers)
}
// 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()
// Do we have it cached?
if zone, ok := fqdnToZone[fqdn]; ok {
return zone, nil
}
var err error
var in *dns.Msg
labelIndexes := dns.Split(fqdn)
for _, index := range labelIndexes {
domain := fqdn[index:]
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
if err != nil {
continue
}
if in == nil {
continue
}
switch in.Rcode {
case dns.RcodeSuccess:
// Check if we got a SOA RR in the answer section
if len(in.Answer) == 0 {
continue
}
// CNAME records cannot/should not exist at the root of a zone.
// So we skip a domain when a CNAME is found.
if dnsMsgContainsCNAME(in) {
continue
}
for _, ans := range in.Answer {
if soa, ok := ans.(*dns.SOA); ok {
zone := soa.Hdr.Name
fqdnToZone[fqdn] = zone
return zone, 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 "", fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
}
// dnsMsgContainsCNAME checks for a CNAME answer in msg
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
for _, ans := range msg.Answer {
if _, ok := ans.(*dns.CNAME); ok {
return true
}
}
return false
}
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
m := createDNSMsg(fqdn, rtype, recursive)
var in *dns.Msg
var err error
for _, ns := range nameservers {
in, err = sendDNSQuery(m, ns)
if err == nil && len(in.Answer) > 0 {
break
}
}
return in, err
}
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
m := new(dns.Msg)
m.SetQuestion(fqdn, rtype)
m.SetEdns0(4096, false)
if !recursive {
m.RecursionDesired = false
}
return m
}
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
in, _, err := udp.Exchange(m, ns)
if in != nil && in.Truncated {
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
// If the TCP request succeeds, the err will reset to nil
in, _, err = tcp.Exchange(m, ns)
}
return in, err
}
func formatDNSError(msg *dns.Msg, err error) string {
var parts []string
if msg != nil {
parts = append(parts, dns.RcodeToString[msg.Rcode])
}
if err != nil {
parts = append(parts, fmt.Sprintf("%v", err))
}
if len(parts) > 0 {
return ": " + strings.Join(parts, " ")
}
return ""
}

View file

@ -0,0 +1,127 @@
package dns01
import (
"errors"
"fmt"
"net"
"strings"
"github.com/miekg/dns"
)
// PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready.
type PreCheckFunc func(fqdn, value string) (bool, error)
// WrapPreCheckFunc wraps a PreCheckFunc in order to do extra operations before or after
// the main check, put it in a loop, etc.
type WrapPreCheckFunc func(domain, fqdn, value string, check PreCheckFunc) (bool, error)
// WrapPreCheck Allow to define checks before notifying ACME that the DNS challenge is ready.
func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.checkFunc = wrap
return nil
}
}
// AddPreCheck Allow to define checks before notifying ACME that the DNS challenge is ready.
// Deprecated: use WrapPreCheck instead.
func AddPreCheck(preCheck PreCheckFunc) ChallengeOption {
// Prevent race condition
check := preCheck
return func(chlg *Challenge) error {
chlg.preCheck.checkFunc = func(_, fqdn, value string, _ PreCheckFunc) (bool, error) {
if check == nil {
return false, errors.New("invalid preCheck: preCheck is nil")
}
return check(fqdn, value)
}
return nil
}
}
func DisableCompletePropagationRequirement() ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.requireCompletePropagation = false
return nil
}
}
type preCheck struct {
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
checkFunc WrapPreCheckFunc
// require the TXT record to be propagated to all authoritative name servers
requireCompletePropagation bool
}
func newPreCheck() preCheck {
return preCheck{
requireCompletePropagation: true,
}
}
func (p preCheck) call(domain, fqdn, value string) (bool, error) {
if p.checkFunc == nil {
return p.checkDNSPropagation(fqdn, value)
}
return p.checkFunc(domain, fqdn, value, p.checkDNSPropagation)
}
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
// Initial attempt to resolve at the recursive NS
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
if err != nil {
return false, err
}
if !p.requireCompletePropagation {
return true, nil
}
if r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
}
authoritativeNss, err := lookupNameservers(fqdn)
if err != nil {
return false, err
}
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
}
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
for _, ns := range nameservers {
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
if err != nil {
return false, err
}
if r.Rcode != dns.RcodeSuccess {
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
}
var records []string
var found bool
for _, rr := range r.Answer {
if txt, ok := rr.(*dns.TXT); ok {
record := strings.Join(txt.Txt, "")
records = append(records, record)
if record == value {
found = true
break
}
}
}
if !found {
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s, value: %s]: %s", ns, fqdn, value, strings.Join(records, " ,"))
}
}
return true, nil
}

View file

@ -0,0 +1,65 @@
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"
)
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
// ChallengePath returns the URL path for the `http-01` challenge
func ChallengePath(token string) string {
return "/.well-known/acme-challenge/" + token
}
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
return &Challenge{
core: core,
validate: validate,
provider: provider,
}
}
func (c *Challenge) SetProvider(provider challenge.Provider) {
c.provider = provider
}
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
chlng, err := challenge.FindChallenge(challenge.HTTP01, authz)
if err != nil {
return err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %v", 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)
}
}()
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}

View file

@ -0,0 +1,96 @@
package http01
import (
"fmt"
"net"
"net/http"
"strings"
"github.com/go-acme/lego/log"
)
// ProviderServer implements ChallengeProvider for `http-01` challenge
// It may be instantiated without using the NewProviderServer function if
// you want only to use the default values.
type ProviderServer struct {
iface string
port string
done chan bool
listener net.Listener
}
// NewProviderServer creates a new ProviderServer on the selected interface and port.
// 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}
}
// 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)
}
s.done = make(chan bool)
go s.serve(domain, token, keyAuth)
return nil
}
func (s *ProviderServer) GetAddress() string {
return net.JoinHostPort(s.iface, s.port)
}
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
if s.listener == nil {
return nil
}
s.listener.Close()
<-s.done
return nil
}
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
mux := http.NewServeMux()
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.Host, domain) && r.Method == http.MethodGet {
w.Header().Add("Content-Type", "text/plain")
_, err := w.Write([]byte(keyAuth))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
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)
_, err := w.Write([]byte("TEST"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
})
httpServer := &http.Server{Handler: mux}
// Once httpServer is shut down
// we don't want any lingering connections, so disable KeepAlives.
httpServer.SetKeepAlivesEnabled(false)
err := httpServer.Serve(s.listener)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
log.Println(err)
}
s.done <- true
}

28
vendor/github.com/go-acme/lego/challenge/provider.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package challenge
import "time"
// Provider enables implementing a custom challenge
// provider. Present presents the solution to a challenge available to
// be solved. CleanUp will be called by the challenge if Present ends
// in a non-error state.
type Provider interface {
Present(domain, token, keyAuth string) error
CleanUp(domain, token, keyAuth string) error
}
// ProviderTimeout allows for implementing a
// Provider where an unusually long timeout is required when
// waiting for an ACME challenge to be satisfied, such as when
// checking for DNS record propagation. If an implementor of a
// Provider provides a Timeout method, then the return values
// of the Timeout method will be used when appropriate by the acme
// package. The interval value is the time between checks.
//
// The default values used for timeout and interval are 60 seconds and
// 2 seconds respectively. These are used when no Timeout method is
// defined for the Provider.
type ProviderTimeout interface {
Provider
Timeout() (timeout, interval time.Duration)
}

View file

@ -0,0 +1,25 @@
package resolver
import (
"bytes"
"fmt"
"sort"
)
// obtainError is returned when there are specific errors available per domain.
type obtainError map[string]error
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
var domains []string
for domain := range e {
domains = append(domains, domain)
}
sort.Strings(domains)
for _, domain := range domains {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
}
return buffer.String()
}

View file

@ -0,0 +1,173 @@
package resolver
import (
"fmt"
"time"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/log"
)
// Interface for all challenge solvers to implement.
type solver interface {
Solve(authorization acme.Authorization) error
}
// Interface for challenges like dns, where we can set a record in advance for ALL challenges.
// This saves quite a bit of time vs creating the records and solving them serially.
type preSolver interface {
PreSolve(authorization acme.Authorization) error
}
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
type cleanup interface {
CleanUp(authorization acme.Authorization) error
}
type sequential interface {
Sequential() (bool, time.Duration)
}
// an authz with the solver we have chosen and the index of the challenge associated with it
type selectedAuthSolver struct {
authz acme.Authorization
solver solver
}
type Prober struct {
solverManager *SolverManager
}
func NewProber(solverManager *SolverManager) *Prober {
return &Prober{
solverManager: solverManager,
}
}
// Solve Looks through the challenge combinations to find a solvable match.
// Then solves the challenges in series and returns.
func (p *Prober) Solve(authorizations []acme.Authorization) error {
failures := make(obtainError)
var authSolvers []*selectedAuthSolver
var authSolversSequential []*selectedAuthSolver
// Loop through the resources, basically through the domains.
// First pass just selects a solver for each authz.
for _, authz := range authorizations {
domain := challenge.GetTargetedDomain(authz)
if authz.Status == acme.StatusValid {
// Boulder might recycle recent validated authz (see issue #267)
log.Infof("[%s] acme: authorization already valid; skipping challenge", domain)
continue
}
if solvr := p.solverManager.chooseSolver(authz); solvr != nil {
authSolver := &selectedAuthSolver{authz: authz, solver: solvr}
switch s := solvr.(type) {
case sequential:
if ok, _ := s.Sequential(); ok {
authSolversSequential = append(authSolversSequential, authSolver)
} else {
authSolvers = append(authSolvers, authSolver)
}
default:
authSolvers = append(authSolvers, authSolver)
}
} else {
failures[domain] = fmt.Errorf("[%s] acme: could not determine solvers", domain)
}
}
parallelSolve(authSolvers, failures)
sequentialSolve(authSolversSequential, failures)
// Be careful not to return an empty failures map,
// for even an empty obtainError is a non-nil error value
if len(failures) > 0 {
return failures
}
return nil
}
func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
for i, authSolver := range authSolvers {
// Submit the challenge
domain := challenge.GetTargetedDomain(authSolver.authz)
if solvr, ok := authSolver.solver.(preSolver); ok {
err := solvr.PreSolve(authSolver.authz)
if err != nil {
failures[domain] = err
cleanUp(authSolver.solver, authSolver.authz)
continue
}
}
// Solve challenge
err := authSolver.solver.Solve(authSolver.authz)
if err != nil {
failures[domain] = err
cleanUp(authSolver.solver, authSolver.authz)
continue
}
// Clean challenge
cleanUp(authSolver.solver, authSolver.authz)
if len(authSolvers)-1 > i {
solvr := authSolver.solver.(sequential)
_, interval := solvr.Sequential()
log.Infof("sequence: wait for %s", interval)
time.Sleep(interval)
}
}
}
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
// For all valid preSolvers, first submit the challenges so they have max time to propagate
for _, authSolver := range authSolvers {
authz := authSolver.authz
if solvr, ok := authSolver.solver.(preSolver); ok {
err := solvr.PreSolve(authz)
if err != nil {
failures[challenge.GetTargetedDomain(authz)] = err
}
}
}
defer func() {
// Clean all created TXT records
for _, authSolver := range authSolvers {
cleanUp(authSolver.solver, authSolver.authz)
}
}()
// Finally solve all challenges for real
for _, authSolver := range authSolvers {
authz := authSolver.authz
domain := challenge.GetTargetedDomain(authz)
if failures[domain] != nil {
// already failed in previous loop
continue
}
err := authSolver.solver.Solve(authz)
if err != nil {
failures[domain] = err
}
}
}
func cleanUp(solvr solver, authz acme.Authorization) {
if solvr, ok := solvr.(cleanup); ok {
domain := challenge.GetTargetedDomain(authz)
err := solvr.CleanUp(authz)
if err != nil {
log.Warnf("[%s] acme: error cleaning up: %v ", domain, err)
}
}
}

View file

@ -0,0 +1,169 @@
package resolver
import (
"context"
"errors"
"fmt"
"sort"
"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"
)
type byType []acme.Challenge
func (a byType) Len() int { return len(a) }
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byType) Less(i, j int) bool { return a[i].Type > a[j].Type }
type SolverManager struct {
core *api.Core
solvers map[challenge.Type]solver
}
func NewSolversManager(core *api.Core) *SolverManager {
return &SolverManager{
solvers: map[challenge.Type]solver{},
core: core,
}
}
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
return nil
}
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error {
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p)
return nil
}
// SetDNS01Provider specifies a custom provider p that can solve the given DNS-01 challenge.
func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.ChallengeOption) error {
c.solvers[challenge.DNS01] = dns01.NewChallenge(c.core, validate, p, opts...)
return nil
}
// Remove Remove a challenge type from the available solvers.
func (c *SolverManager) Remove(chlgType challenge.Type) {
delete(c.solvers, chlgType)
}
// Checks all challenges from the server in order and returns the first matching solver.
func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
// Allow to have a deterministic challenge order
sort.Sort(byType(authz.Challenges))
domain := challenge.GetTargetedDomain(authz)
for _, chlg := range authz.Challenges {
if solvr, ok := c.solvers[challenge.Type(chlg.Type)]; ok {
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
return solvr
}
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
}
return nil
}
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)
}
valid, err := checkChallengeStatus(chlng)
if err != nil {
return err
}
if valid {
log.Infof("[%s] The server validated our request", domain)
return nil
}
ra, err := strconv.Atoi(chlng.RetryAfter)
if err != nil {
// The ACME server MUST return a Retry-After.
// If it doesn't, we'll just poll hard.
// Boulder does not implement the ability to retry challenges or the Retry-After header.
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
ra = 5
}
initialInterval := time.Duration(ra) * time.Second
bo := backoff.NewExponentialBackOff()
bo.InitialInterval = initialInterval
bo.MaxInterval = 10 * initialInterval
bo.MaxElapsedTime = 100 * initialInterval
ctx, cancel := context.WithCancel(context.Background())
// After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request.
operation := func() error {
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
if err != nil {
cancel()
return err
}
valid, err := checkAuthorizationStatus(authz)
if err != nil {
cancel()
return err
}
if valid {
log.Infof("[%s] The server validated our request", domain)
return nil
}
return errors.New("the server didn't respond to our request")
}
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
}
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
switch chlng.Status {
case acme.StatusValid:
return true, nil
case acme.StatusPending, acme.StatusProcessing:
return false, nil
case acme.StatusInvalid:
return false, chlng.Error
default:
return false, errors.New("the server returned an unexpected state")
}
}
func checkAuthorizationStatus(authz acme.Authorization) (bool, error) {
switch authz.Status {
case acme.StatusValid:
return true, nil
case acme.StatusPending, acme.StatusProcessing:
return false, nil
case acme.StatusDeactivated, acme.StatusExpired, acme.StatusRevoked:
return false, fmt.Errorf("the authorization state %s", authz.Status)
case acme.StatusInvalid:
for _, chlg := range authz.Challenges {
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
return false, chlg.Error
}
}
return false, fmt.Errorf("the authorization state %s", authz.Status)
default:
return false, errors.New("the server returned an unexpected state")
}
}

View file

@ -0,0 +1,129 @@
package tlsalpn01
import (
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509/pkix"
"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"
)
// 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
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
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
return &Challenge{
core: core,
validate: validate,
provider: provider,
}
}
func (c *Challenge) SetProvider(provider challenge.Provider) {
c.provider = provider
}
// Solve manages the provider to validate and solve the challenge.
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := authz.Identifier.Value
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
if err != nil {
return err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return err
}
err = c.provider.Present(domain, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %v", 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)
}
}()
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}
// ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
// and domain name for the `tls-alpn-01` challenge.
func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
// Compute the SHA-256 digest of the key authorization.
zBytes := sha256.Sum256([]byte(keyAuth))
value, err := asn1.Marshal(zBytes[:sha256.Size])
if err != nil {
return nil, nil, err
}
// 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
extensions := []pkix.Extension{
{
Id: idPeAcmeIdentifierV1,
Critical: true,
Value: value,
},
}
// Generate a new RSA key for the certificates.
tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
if err != nil {
return nil, nil, err
}
rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
if err != nil {
return nil, nil, err
}
// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
return tempCertPEM, rsaPrivatePEM, nil
}
// ChallengeCert returns a certificate with the acmeValidation-v1 extension
// and domain name for the `tls-alpn-01` challenge.
func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
if err != nil {
return nil, err
}
return &cert, nil
}

View file

@ -0,0 +1,95 @@
package tlsalpn01
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"github.com/go-acme/lego/log"
)
const (
// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
ACMETLS1Protocol = "acme-tls/1"
// defaultTLSPort is the port that the ProviderServer will default to
// when no other port is provided.
defaultTLSPort = "443"
)
// ProviderServer implements ChallengeProvider for `TLS-ALPN-01` challenge.
// It may be instantiated without using the NewProviderServer
// if you want only to use the default values.
type ProviderServer struct {
iface string
port string
listener net.Listener
}
// NewProviderServer creates a new ProviderServer on the selected interface and port.
// Setting iface and / or port to an empty string will make the server fall back to
// the "any" interface and port 443 respectively.
func NewProviderServer(iface, port string) *ProviderServer {
return &ProviderServer{iface: iface, port: port}
}
func (s *ProviderServer) GetAddress() string {
return net.JoinHostPort(s.iface, s.port)
}
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
if s.port == "" {
// Fallback to port 443 if the port was not provided.
s.port = defaultTLSPort
}
// Generate the challenge certificate using the provided keyAuth and domain.
cert, err := ChallengeCert(domain, keyAuth)
if err != nil {
return err
}
// Place the generated certificate with the extension into the TLS config
// so that it can serve the correct details.
tlsConf := new(tls.Config)
tlsConf.Certificates = []tls.Certificate{*cert}
// 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
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)
}
// Shut the server down when we're finished.
go func() {
err := http.Serve(s.listener, nil)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
log.Println(err)
}
}()
return nil
}
// CleanUp closes the HTTPS server.
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
if s.listener == nil {
return nil
}
// Server was created, close it.
if err := s.listener.Close(); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}

74
vendor/github.com/go-acme/lego/lego/client.go generated vendored Normal file
View file

@ -0,0 +1,74 @@
package lego
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"
)
// Client is the user-friendly way to ACME
type Client struct {
Certificate *certificate.Certifier
Challenge *resolver.SolverManager
Registration *registration.Registrar
core *api.Core
}
// NewClient creates a new ACME client on behalf of the user.
// The client will depend on the ACME directory located at CADirURL for the rest of its actions.
// A private key of type keyType (see KeyType constants) will be generated when requesting a new certificate if one isn't provided.
func NewClient(config *Config) (*Client, error) {
if config == nil {
return nil, errors.New("a configuration must be provided")
}
_, err := url.Parse(config.CADirURL)
if err != nil {
return nil, err
}
if config.HTTPClient == nil {
return nil, errors.New("the HTTP client cannot be nil")
}
privateKey := config.User.GetPrivateKey()
if privateKey == nil {
return nil, errors.New("private key was nil")
}
var kid string
if reg := config.User.GetRegistration(); reg != nil {
kid = reg.URI
}
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, kid, privateKey)
if err != nil {
return nil, err
}
solversManager := resolver.NewSolversManager(core)
prober := resolver.NewProber(solversManager)
certifier := certificate.NewCertifier(core, prober, certificate.CertifierOptions{KeyType: config.Certificate.KeyType, Timeout: config.Certificate.Timeout})
return &Client{
Certificate: certifier,
Challenge: solversManager,
Registration: registration.NewRegistrar(core, config.User),
core: core,
}, nil
}
// GetToSURL returns the current ToS URL from the Directory
func (c *Client) GetToSURL() string {
return c.core.GetDirectory().Meta.TermsOfService
}
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
func (c *Client) GetExternalAccountRequired() bool {
return c.core.GetDirectory().Meta.ExternalAccountRequired
}

104
vendor/github.com/go-acme/lego/lego/client_config.go generated vendored Normal file
View file

@ -0,0 +1,104 @@
package lego
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"time"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/registration"
)
const (
// caCertificatesEnvVar is the environment variable name that can be used to
// specify the path to PEM encoded CA Certificates that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
// caServerNameEnvVar is the environment variable name that can be used to
// specify the CA server name that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
// LEDirectoryProduction URL to the Let's Encrypt production
LEDirectoryProduction = "https://acme-v02.api.letsencrypt.org/directory"
// LEDirectoryStaging URL to the Let's Encrypt staging
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
)
type Config struct {
CADirURL string
User registration.User
UserAgent string
HTTPClient *http.Client
Certificate CertificateConfig
}
func NewConfig(user registration.User) *Config {
return &Config{
CADirURL: LEDirectoryProduction,
User: user,
HTTPClient: createDefaultHTTPClient(),
Certificate: CertificateConfig{
KeyType: certcrypto.RSA2048,
Timeout: 30 * time.Second,
},
}
}
type CertificateConfig struct {
KeyType certcrypto.KeyType
Timeout time.Duration
}
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
// and potentially a custom *x509.CertPool
// based on the caCertificatesEnvVar environment variable (see the `initCertPool` function)
func createDefaultHTTPClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 15 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
ServerName: os.Getenv(caServerNameEnvVar),
RootCAs: initCertPool(),
},
},
}
}
// initCertPool creates a *x509.CertPool populated with the PEM certificates
// found in the filepath specified in the caCertificatesEnvVar OS environment
// variable. If the caCertificatesEnvVar is not set then initCertPool will
// return nil. If there is an error creating a *x509.CertPool from the provided
// caCertificatesEnvVar value then initCertPool will panic.
func initCertPool() *x509.CertPool {
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
customCAs, err := ioutil.ReadFile(customCACertsPath)
if err != nil {
panic(fmt.Sprintf("error reading %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
}
return certPool
}
return nil
}

59
vendor/github.com/go-acme/lego/log/logger.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
package log
import (
"log"
"os"
)
// Logger is an optional custom logger.
var Logger StdLogger = log.New(os.Stdout, "", log.LstdFlags)
// StdLogger interface for Standard Logger.
type StdLogger interface {
Fatal(args ...interface{})
Fatalln(args ...interface{})
Fatalf(format string, args ...interface{})
Print(args ...interface{})
Println(args ...interface{})
Printf(format string, args ...interface{})
}
// Fatal writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Fatal(args ...interface{}) {
Logger.Fatal(args...)
}
// Fatalf writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Fatalf(format string, args ...interface{}) {
Logger.Fatalf(format, args...)
}
// Print writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Print(args ...interface{}) {
Logger.Print(args...)
}
// Println writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Println(args ...interface{}) {
Logger.Println(args...)
}
// Printf writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Printf(format string, args ...interface{}) {
Logger.Printf(format, args...)
}
// Warnf writes a log entry.
func Warnf(format string, args ...interface{}) {
Printf("[WARN] "+format, args...)
}
// Infof writes a log entry.
func Infof(format string, args ...interface{}) {
Printf("[INFO] "+format, args...)
}

33
vendor/github.com/go-acme/lego/platform/wait/wait.go generated vendored Normal file
View file

@ -0,0 +1,33 @@
package wait
import (
"fmt"
"time"
"github.com/go-acme/lego/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
timeUp := time.After(timeout)
for {
select {
case <-timeUp:
return fmt.Errorf("time limit exceeded: last error: %s", lastErr)
default:
}
stop, err := f()
if stop {
return nil
}
if err != nil {
lastErr = err.Error()
}
time.Sleep(interval)
}
}

146
vendor/github.com/go-acme/lego/registration/registar.go generated vendored Normal file
View file

@ -0,0 +1,146 @@
package registration
import (
"errors"
"net/http"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/acme/api"
"github.com/go-acme/lego/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).
type Resource struct {
Body acme.Account `json:"body,omitempty"`
URI string `json:"uri,omitempty"`
}
type RegisterOptions struct {
TermsOfServiceAgreed bool
}
type RegisterEABOptions struct {
TermsOfServiceAgreed bool
Kid string
HmacEncoded string
}
type Registrar struct {
core *api.Core
user User
}
func NewRegistrar(core *api.Core, user User) *Registrar {
return &Registrar{
core: core,
user: user,
}
}
// Register the current account to the ACME server.
func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
if r == nil || r.user == nil {
return nil, errors.New("acme: cannot register 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.New(accMsg)
if err != nil {
// FIXME seems impossible
errorDetails, ok := err.(acme.ProblemDetails)
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
return nil, err
}
}
return &Resource{URI: account.Location, Body: account.Account}, nil
}
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOptions) (*Resource, error) {
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.NewEAB(accMsg, options.Kid, options.HmacEncoded)
if err != nil {
errorDetails, ok := err.(acme.ProblemDetails)
// FIXME seems impossible
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
return nil, err
}
}
return &Resource{URI: account.Location, Body: account.Account}, nil
}
// QueryRegistration runs a POST request on the client's registration and returns the result.
//
// This is similar to the Register function,
// but acting on an existing registration link and resource.
func (r *Registrar) QueryRegistration() (*Resource, error) {
if r == nil || r.user == nil {
return nil, errors.New("acme: cannot query the registration of a nil client or user")
}
// Log the URL here instead of the email as the email may not be set
log.Infof("acme: Querying account for %s", r.user.GetRegistration().URI)
account, err := r.core.Accounts.Get(r.user.GetRegistration().URI)
if err != nil {
return nil, err
}
return &Resource{
Body: account,
// Location: header is not returned so this needs to be populated off of existing URI
URI: r.user.GetRegistration().URI,
}, nil
}
// DeleteRegistration deletes the client's user registration from the ACME server.
func (r *Registrar) DeleteRegistration() error {
if r == nil || r.user == nil {
return errors.New("acme: cannot unregister a nil client or user")
}
log.Infof("acme: Deleting account for %s", r.user.GetEmail())
return r.core.Accounts.Deactivate(r.user.GetRegistration().URI)
}
// ResolveAccountByKey will attempt to look up an account using the given account key
// and return its registration resource.
func (r *Registrar) ResolveAccountByKey() (*Resource, error) {
log.Infof("acme: Trying to resolve account by key")
accMsg := acme.Account{OnlyReturnExisting: true}
accountTransit, err := r.core.Accounts.New(accMsg)
if err != nil {
return nil, err
}
account, err := r.core.Accounts.Get(accountTransit.Location)
if err != nil {
return nil, err
}
return &Resource{URI: accountTransit.Location, Body: account}, nil
}

13
vendor/github.com/go-acme/lego/registration/user.go generated vendored Normal file
View file

@ -0,0 +1,13 @@
package registration
import (
"crypto"
)
// User interface is to be implemented by users of this library.
// It is used by the client type to get user specific information.
type User interface {
GetEmail() string
GetRegistration() *Resource
GetPrivateKey() crypto.PrivateKey
}