mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
Merge pull request #649 from erikwilson/update-dynamiclistener
Update dynamiclistener
This commit is contained in:
commit
33a59542df
@ -225,7 +225,8 @@ import:
|
||||
- package: github.com/prometheus/procfs
|
||||
version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259
|
||||
- package: github.com/rancher/dynamiclistener
|
||||
version: 4716ac2362986f28bede3f3caf5d1ce347da55b0
|
||||
version: c08b499d17195fbc2c1764b21c322951811629a5
|
||||
repo: https://github.com/erikwilson/rancher-dynamiclistener.git
|
||||
- package: github.com/rancher/helm-controller
|
||||
version: v0.2.1
|
||||
- package: github.com/rancher/remotedialer
|
||||
|
@ -13,7 +13,7 @@ k8s.io/kubernetes v1.14.4-k3s.1 ht
|
||||
|
||||
github.com/rancher/wrangler 7737c167e16514a38229bc64c839cee8cd14e6d3
|
||||
github.com/rancher/wrangler-api v0.1.4
|
||||
github.com/rancher/dynamiclistener 4716ac2362986f28bede3f3caf5d1ce347da55b0
|
||||
github.com/rancher/dynamiclistener c08b499d17195fbc2c1764b21c322951811629a5 https://github.com/erikwilson/rancher-dynamiclistener.git
|
||||
github.com/rancher/remotedialer 4a5a661be67697d6369df54ef62d5a30b0385697
|
||||
github.com/rancher/helm-controller v0.2.1
|
||||
github.com/matryer/moq ee5226d43009 https://github.com/rancher/moq.git
|
||||
|
5
vendor/github.com/rancher/dynamiclistener/read.go
generated
vendored
5
vendor/github.com/rancher/dynamiclistener/read.go
generated
vendored
@ -26,11 +26,6 @@ func ReadTLSConfig(userConfig *UserConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
userConfig.Mode = "https"
|
||||
if len(userConfig.Domains) > 0 {
|
||||
userConfig.Mode = "acme"
|
||||
}
|
||||
|
||||
valid := false
|
||||
if userConfig.Key != "" && userConfig.Cert != "" {
|
||||
valid = true
|
||||
|
490
vendor/github.com/rancher/dynamiclistener/server.go
generated
vendored
490
vendor/github.com/rancher/dynamiclistener/server.go
generated
vendored
@ -1,37 +1,27 @@
|
||||
package dynamiclistener
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/md5"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
cert "github.com/rancher/dynamiclistener/cert"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
const (
|
||||
httpsMode = "https"
|
||||
acmeMode = "acme"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
@ -39,28 +29,28 @@ type server struct {
|
||||
|
||||
userConfig UserConfig
|
||||
listenConfigStorage ListenerConfigStorage
|
||||
certs map[string]*tls.Certificate
|
||||
ips *lru.Cache
|
||||
tlsCert *tls.Certificate
|
||||
|
||||
ips map[string]bool
|
||||
domains map[string]bool
|
||||
cn string
|
||||
|
||||
listeners []net.Listener
|
||||
servers []*http.Server
|
||||
|
||||
// dynamic config change on refresh
|
||||
activeCert *tls.Certificate
|
||||
activeCA *x509.Certificate
|
||||
activeCAKey crypto.Signer
|
||||
activeCAKeyString string
|
||||
domains map[string]bool
|
||||
activeCA *x509.Certificate
|
||||
activeCAKey crypto.Signer
|
||||
}
|
||||
|
||||
func NewServer(listenConfigStorage ListenerConfigStorage, config UserConfig) (ServerInterface, error) {
|
||||
s := &server{
|
||||
userConfig: config,
|
||||
listenConfigStorage: listenConfigStorage,
|
||||
certs: map[string]*tls.Certificate{},
|
||||
cn: "cattle",
|
||||
}
|
||||
|
||||
s.ips, _ = lru.New(20)
|
||||
s.ips = map[string]bool{}
|
||||
s.domains = map[string]bool{}
|
||||
|
||||
if err := s.userConfigure(); err != nil {
|
||||
return nil, err
|
||||
@ -81,16 +71,7 @@ func (s *server) CACert() (string, error) {
|
||||
if s.userConfig.CACerts != "" {
|
||||
return s.userConfig.CACerts, nil
|
||||
}
|
||||
status, err := s.listenConfigStorage.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if status.CACert == "" {
|
||||
return "", fmt.Errorf("ca cert not found")
|
||||
}
|
||||
|
||||
return status.CACert, nil
|
||||
return "", fmt.Errorf("ca cert not found")
|
||||
}
|
||||
|
||||
func marshalPrivateKey(privateKey crypto.Signer) (string, []byte, error) {
|
||||
@ -127,78 +108,25 @@ func newPrivateKey() (crypto.Signer, error) {
|
||||
return caKeyIFace.(crypto.Signer), nil
|
||||
}
|
||||
|
||||
func (s *server) save() {
|
||||
if s.activeCert != nil {
|
||||
return
|
||||
func (s *server) save() (_err error) {
|
||||
defer func() {
|
||||
if _err != nil {
|
||||
logrus.Errorf("Saving cert error: %s", _err)
|
||||
}
|
||||
}()
|
||||
|
||||
certStr, err := certToString(s.tlsCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
changed := false
|
||||
cfg, err := s.listenConfigStorage.Get()
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
cfg.GeneratedCerts = map[string]string{s.cn: certStr}
|
||||
|
||||
if cfg.GeneratedCerts == nil {
|
||||
cfg.GeneratedCerts = map[string]string{}
|
||||
}
|
||||
|
||||
if cfg.KnownIPs == nil {
|
||||
cfg.KnownIPs = map[string]bool{}
|
||||
}
|
||||
|
||||
for key, cert := range s.certs {
|
||||
certStr, err := certToString(cert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if cfg.GeneratedCerts[key] != certStr {
|
||||
cfg.GeneratedCerts[key] = certStr
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, obj := range s.ips.Keys() {
|
||||
ip, _ := obj.(string)
|
||||
if !cfg.KnownIPs[ip] {
|
||||
cfg.KnownIPs[ip] = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.CAKey == "" && s.activeCAKey != nil && s.activeCA != nil {
|
||||
caCertBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&caCertBuffer, &pem.Block{
|
||||
Type: cert.CertificateBlockType,
|
||||
Bytes: s.activeCA.Raw,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
caKeyBuffer := bytes.Buffer{}
|
||||
keyType, keyBytes, err := marshalPrivateKey(s.activeCAKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := pem.Encode(&caKeyBuffer, &pem.Block{
|
||||
Type: keyType,
|
||||
Bytes: keyBytes,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.CACert = string(caCertBuffer.Bytes())
|
||||
cfg.CAKey = string(caKeyBuffer.Bytes())
|
||||
s.activeCAKeyString = cfg.CAKey
|
||||
changed = true
|
||||
}
|
||||
|
||||
if changed {
|
||||
s.listenConfigStorage.Set(cfg)
|
||||
}
|
||||
_, err = s.listenConfigStorage.Set(cfg)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *server) userConfigure() error {
|
||||
@ -206,39 +134,42 @@ func (s *server) userConfigure() error {
|
||||
s.userConfig.HTTPSPort = 8443
|
||||
}
|
||||
|
||||
if s.userConfig.Mode == "" {
|
||||
if len(s.userConfig.Domains) > 0 {
|
||||
s.userConfig.Mode = acmeMode
|
||||
} else {
|
||||
s.userConfig.Mode = httpsMode
|
||||
}
|
||||
}
|
||||
|
||||
s.domains = map[string]bool{}
|
||||
for _, d := range s.userConfig.Domains {
|
||||
s.domains[d] = true
|
||||
}
|
||||
|
||||
if s.userConfig.Key != "" && s.userConfig.Cert != "" {
|
||||
cert, err := tls.X509KeyPair([]byte(s.userConfig.Cert), []byte(s.userConfig.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
for _, ip := range s.userConfig.KnownIPs {
|
||||
if netIP := net.ParseIP(ip); netIP != nil {
|
||||
s.ips[ip] = true
|
||||
}
|
||||
s.activeCert = &cert
|
||||
s.userConfig.Mode = httpsMode
|
||||
return s.reload()
|
||||
}
|
||||
|
||||
for _, ip := range s.userConfig.KnownIPs {
|
||||
netIP := net.ParseIP(ip)
|
||||
if netIP != nil {
|
||||
s.ips.Add(ip, netIP)
|
||||
if bindAddress := net.ParseIP(s.userConfig.BindAddress); bindAddress != nil {
|
||||
s.ips[s.userConfig.BindAddress] = true
|
||||
}
|
||||
|
||||
if s.activeCA == nil && s.activeCAKey == nil {
|
||||
if s.userConfig.CACerts != "" && s.userConfig.CAKey != "" {
|
||||
ca, err := cert.ParseCertsPEM([]byte(s.userConfig.CACerts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := cert.ParsePrivateKeyPEM([]byte(s.userConfig.CAKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.activeCA = ca[0]
|
||||
s.activeCAKey = key.(crypto.Signer)
|
||||
} else {
|
||||
ca, key, err := genCA()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.activeCA = ca
|
||||
s.activeCAKey = key
|
||||
}
|
||||
}
|
||||
bindAddress := net.ParseIP(s.userConfig.BindAddress)
|
||||
if bindAddress != nil {
|
||||
s.ips.Add(s.userConfig.BindAddress, bindAddress)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -259,61 +190,76 @@ func genCA() (*x509.Certificate, crypto.Signer, error) {
|
||||
return caCert, caKey, nil
|
||||
}
|
||||
|
||||
func (s *server) Update(status *ListenerStatus) error {
|
||||
func (s *server) Update(status *ListenerStatus) (_err error) {
|
||||
s.Lock()
|
||||
defer s.getCertificate(&tls.ClientHelloInfo{ServerName: "localhost"})
|
||||
|
||||
if status.CACert != "" && status.CAKey != "" && s.activeCAKeyString != status.CAKey {
|
||||
cert, err := tls.X509KeyPair([]byte(status.CACert), []byte(status.CAKey))
|
||||
if err != nil {
|
||||
s.Unlock()
|
||||
return err
|
||||
defer func() {
|
||||
s.Unlock()
|
||||
if _err != nil {
|
||||
logrus.Errorf("Update cert error: %s", _err)
|
||||
}
|
||||
s.activeCAKey = cert.PrivateKey.(crypto.Signer)
|
||||
s.activeCAKeyString = status.CAKey
|
||||
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
s.Unlock()
|
||||
return err
|
||||
if s.tlsCert == nil {
|
||||
s.getCertificate(&tls.ClientHelloInfo{ServerName: "localhost"})
|
||||
}
|
||||
s.activeCA = x509Cert
|
||||
s.certs = map[string]*tls.Certificate{}
|
||||
}()
|
||||
|
||||
certString := status.GeneratedCerts[s.cn]
|
||||
tlsCert, err := stringToCert(certString)
|
||||
if err != nil {
|
||||
logrus.Errorf("Update cert unable to convert string to cert: %s", err)
|
||||
s.tlsCert = nil
|
||||
}
|
||||
if tlsCert != nil {
|
||||
s.tlsCert = tlsCert
|
||||
for i, certBytes := range tlsCert.Certificate {
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
logrus.Errorf("Update cert %d parse error: %s", i, err)
|
||||
s.tlsCert = nil
|
||||
break
|
||||
}
|
||||
|
||||
for ipStr := range status.KnownIPs {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if len(ip) > 0 {
|
||||
s.ips.ContainsOrAdd(ipStr, ip)
|
||||
ips := map[string]bool{}
|
||||
for _, ip := range cert.IPAddresses {
|
||||
ips[ip.String()] = true
|
||||
}
|
||||
|
||||
domains := map[string]bool{}
|
||||
for _, domain := range cert.DNSNames {
|
||||
domains[domain] = true
|
||||
}
|
||||
|
||||
if !(reflect.DeepEqual(ips, s.ips) && reflect.DeepEqual(domains, s.domains)) {
|
||||
subset := true
|
||||
for ip := range s.ips {
|
||||
if !ips[ip] {
|
||||
subset = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if subset {
|
||||
for domain := range s.domains {
|
||||
if !domains[domain] {
|
||||
subset = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !subset {
|
||||
s.tlsCert = nil
|
||||
}
|
||||
for ip := range ips {
|
||||
s.ips[ip] = true
|
||||
}
|
||||
for domain := range domains {
|
||||
s.domains[domain] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, certString := range status.GeneratedCerts {
|
||||
cert := stringToCert(certString)
|
||||
if cert != nil {
|
||||
s.certs[key] = cert
|
||||
}
|
||||
}
|
||||
|
||||
s.Unlock()
|
||||
return s.reload()
|
||||
}
|
||||
|
||||
func (s *server) hostPolicy(ctx context.Context, host string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.domains[host] {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("acme/autocert: host not configured")
|
||||
}
|
||||
|
||||
func (s *server) prompt(tos string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *server) shutdown() error {
|
||||
for _, listener := range s.listeners {
|
||||
if err := listener.Close(); err != nil {
|
||||
@ -339,114 +285,53 @@ func (s *server) reload() error {
|
||||
return err
|
||||
}
|
||||
|
||||
switch s.userConfig.Mode {
|
||||
case acmeMode:
|
||||
if err := s.serveACME(); err != nil {
|
||||
return err
|
||||
}
|
||||
case httpsMode:
|
||||
if err := s.serveHTTPS(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.serveHTTPS(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) ipMapKey() string {
|
||||
len := s.ips.Len()
|
||||
keys := s.ips.Keys()
|
||||
if len == 0 {
|
||||
return fmt.Sprintf("local/%d", len)
|
||||
} else if len == 1 {
|
||||
return fmt.Sprintf("local/%s", keys[0])
|
||||
}
|
||||
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
l, _ := keys[i].(string)
|
||||
r, _ := keys[j].(string)
|
||||
return l < r
|
||||
})
|
||||
if len < 6 {
|
||||
return fmt.Sprintf("local/%v", keys)
|
||||
}
|
||||
|
||||
digest := md5.New()
|
||||
for _, k := range keys {
|
||||
s, _ := k.(string)
|
||||
digest.Write([]byte(s))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("local/%v", hex.EncodeToString(digest.Sum(nil)))
|
||||
}
|
||||
|
||||
func (s *server) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
func (s *server) getCertificate(hello *tls.ClientHelloInfo) (_servingCert *tls.Certificate, _err error) {
|
||||
s.Lock()
|
||||
if s.activeCert != nil {
|
||||
s.Unlock()
|
||||
return s.activeCert, nil
|
||||
}
|
||||
|
||||
changed := false
|
||||
|
||||
defer func() {
|
||||
defer s.Unlock()
|
||||
|
||||
if _err != nil {
|
||||
logrus.Errorf("Get certificate error: %s", _err)
|
||||
return
|
||||
}
|
||||
|
||||
if changed {
|
||||
s.save()
|
||||
}
|
||||
}()
|
||||
defer s.Unlock()
|
||||
|
||||
mapKey := hello.ServerName
|
||||
cn := hello.ServerName
|
||||
dnsNames := []string{cn}
|
||||
ipBased := false
|
||||
var ips []net.IP
|
||||
|
||||
if cn == "" {
|
||||
mapKey = s.ipMapKey()
|
||||
ipBased = true
|
||||
if hello.ServerName != "" && !s.domains[hello.ServerName] {
|
||||
s.tlsCert = nil
|
||||
s.domains[hello.ServerName] = true
|
||||
}
|
||||
|
||||
serverNameCert, ok := s.certs[mapKey]
|
||||
if ok {
|
||||
return serverNameCert, nil
|
||||
if s.tlsCert != nil {
|
||||
return s.tlsCert, nil
|
||||
}
|
||||
|
||||
if ipBased {
|
||||
cn = "cattle"
|
||||
for _, ipStr := range s.ips.Keys() {
|
||||
ip := net.ParseIP(ipStr.(string))
|
||||
if len(ip) > 0 {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
ips := []net.IP{}
|
||||
for ipStr := range s.ips {
|
||||
if ip := net.ParseIP(ipStr); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
changed = true
|
||||
|
||||
if s.activeCA == nil {
|
||||
if s.userConfig.CACerts != "" && s.userConfig.CAKey != "" {
|
||||
ca, err := cert.ParseCertsPEM([]byte(s.userConfig.CACerts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := cert.ParsePrivateKeyPEM([]byte(s.userConfig.CAKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.activeCA = ca[0]
|
||||
s.activeCAKey = key.(crypto.Signer)
|
||||
} else {
|
||||
ca, key, err := genCA()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.activeCA = ca
|
||||
s.activeCAKey = key
|
||||
}
|
||||
dnsNames := []string{}
|
||||
for domain := range s.domains {
|
||||
dnsNames = append(dnsNames, domain)
|
||||
}
|
||||
|
||||
cfg := cert.Config{
|
||||
CommonName: cn,
|
||||
CommonName: s.cn,
|
||||
Organization: s.activeCA.Subject.Organization,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
AltNames: cert.AltNames{
|
||||
@ -472,23 +357,31 @@ func (s *server) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, e
|
||||
PrivateKey: key,
|
||||
}
|
||||
|
||||
s.certs[mapKey] = tlsCert
|
||||
changed = true
|
||||
s.tlsCert = tlsCert
|
||||
return tlsCert, nil
|
||||
}
|
||||
|
||||
func (s *server) cacheIPHandler(handler http.Handler) http.Handler {
|
||||
func (s *server) cacheHandler(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
h, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
h = req.Host
|
||||
}
|
||||
|
||||
ip := net.ParseIP(h)
|
||||
if len(ip) > 0 {
|
||||
if ok, _ := s.ips.ContainsOrAdd(h, ip); ok {
|
||||
go s.save()
|
||||
s.Lock()
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
if !s.ips[h] {
|
||||
s.ips[h] = true
|
||||
s.tlsCert = nil
|
||||
}
|
||||
} else {
|
||||
if !s.domains[h] {
|
||||
s.domains[h] = true
|
||||
s.tlsCert = nil
|
||||
}
|
||||
}
|
||||
s.Unlock()
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
})
|
||||
@ -508,7 +401,7 @@ func (s *server) serveHTTPS() error {
|
||||
|
||||
logger := logrus.StandardLogger()
|
||||
server := &http.Server{
|
||||
Handler: s.cacheIPHandler(s.Handler()),
|
||||
Handler: s.cacheHandler(s.Handler()),
|
||||
ErrorLog: log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags),
|
||||
}
|
||||
|
||||
@ -522,7 +415,7 @@ func (s *server) serveHTTPS() error {
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Handler: s.cacheIPHandler(httpRedirect(s.Handler())),
|
||||
Handler: s.cacheHandler(httpRedirect(s.Handler())),
|
||||
ErrorLog: log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags),
|
||||
}
|
||||
|
||||
@ -598,97 +491,48 @@ func (s *server) newListener(ip string, port int, config *tls.Config) (net.Liste
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (s *server) serveACME() error {
|
||||
manager := autocert.Manager{
|
||||
Cache: autocert.DirCache("certs-cache"),
|
||||
Prompt: s.prompt,
|
||||
HostPolicy: s.hostPolicy,
|
||||
}
|
||||
conf := &tls.Config{
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if hello.ServerName == "localhost" || hello.ServerName == "" {
|
||||
newHello := *hello
|
||||
newHello.ServerName = s.userConfig.Domains[0]
|
||||
return manager.GetCertificate(&newHello)
|
||||
}
|
||||
return manager.GetCertificate(hello)
|
||||
},
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
|
||||
if s.userConfig.HTTPPort > 0 {
|
||||
httpListener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPPort, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Handler: manager.HTTPHandler(nil),
|
||||
ErrorLog: log.New(logrus.StandardLogger().Writer(), "", log.LstdFlags),
|
||||
}
|
||||
s.servers = append(s.servers, httpServer)
|
||||
go func() {
|
||||
if err := httpServer.Serve(httpListener); err != nil {
|
||||
logrus.Errorf("http server returned err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
httpsListener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPSPort, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpsServer := &http.Server{
|
||||
Handler: s.Handler(),
|
||||
ErrorLog: log.New(logrus.StandardLogger().Writer(), "", log.LstdFlags),
|
||||
}
|
||||
s.servers = append(s.servers, httpsServer)
|
||||
go func() {
|
||||
if err := httpsServer.Serve(httpsListener); err != nil {
|
||||
logrus.Errorf("https server returned err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringToCert(certString string) *tls.Certificate {
|
||||
func stringToCert(certString string) (*tls.Certificate, error) {
|
||||
parts := strings.Split(certString, "#")
|
||||
if len(parts) != 2 {
|
||||
return nil
|
||||
return nil, errors.New("Unable to split cert into two parts")
|
||||
}
|
||||
|
||||
certPart, keyPart := parts[0], parts[1]
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(keyPart)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := cert.ParsePrivateKeyPEM(keyBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certBytes, err := base64.StdEncoding.DecodeString(certPart)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: key,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func certToString(cert *tls.Certificate) (string, error) {
|
||||
_, keyBytes, err := marshalPrivateKey(cert.PrivateKey.(crypto.Signer))
|
||||
keyType, keyBytes, err := marshalPrivateKey(cert.PrivateKey.(crypto.Signer))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
privateKeyPemBlock := &pem.Block{
|
||||
Type: keyType,
|
||||
Bytes: keyBytes,
|
||||
}
|
||||
pemBytes := pem.EncodeToMemory(privateKeyPemBlock)
|
||||
|
||||
certString := base64.StdEncoding.EncodeToString(cert.Certificate[0])
|
||||
keyString := base64.StdEncoding.EncodeToString(keyBytes)
|
||||
keyString := base64.StdEncoding.EncodeToString(pemBytes)
|
||||
return certString + "#" + keyString, nil
|
||||
}
|
||||
|
||||
|
911
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
911
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
@ -1,911 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package acme provides an implementation of the
|
||||
// Automatic Certificate Management Environment (ACME) spec.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02 for details.
|
||||
//
|
||||
// Most common scenarios will want to use autocert subdirectory instead,
|
||||
// which provides automatic access to certificates from Let's Encrypt
|
||||
// and any other ACME-based CA.
|
||||
//
|
||||
// This package is a work in progress and makes no API stability promises.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||
const LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
|
||||
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||
|
||||
const (
|
||||
maxChainLen = 5 // max depth and breadth of a certificate chain
|
||||
maxCertSize = 1 << 20 // max size of a certificate, in bytes
|
||||
|
||||
// Max number of collected nonces kept in memory.
|
||||
// Expect usual peak of 1 or 2.
|
||||
maxNonces = 100
|
||||
)
|
||||
|
||||
// Client is an ACME client.
|
||||
// The only required field is Key. An example of creating a client with a new key
|
||||
// is as follows:
|
||||
//
|
||||
// key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// client := &Client{Key: key}
|
||||
//
|
||||
type Client struct {
|
||||
// Key is the account key used to register with a CA and sign requests.
|
||||
// Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey.
|
||||
Key crypto.Signer
|
||||
|
||||
// HTTPClient optionally specifies an HTTP client to use
|
||||
// instead of http.DefaultClient.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// DirectoryURL points to the CA directory endpoint.
|
||||
// If empty, LetsEncryptURL is used.
|
||||
// Mutating this value after a successful call of Client's Discover method
|
||||
// will have no effect.
|
||||
DirectoryURL string
|
||||
|
||||
// RetryBackoff computes the duration after which the nth retry of a failed request
|
||||
// should occur. The value of n for the first call on failure is 1.
|
||||
// The values of r and resp are the request and response of the last failed attempt.
|
||||
// If the returned value is negative or zero, no more retries are done and an error
|
||||
// is returned to the caller of the original method.
|
||||
//
|
||||
// Requests which result in a 4xx client error are not retried,
|
||||
// except for 400 Bad Request due to "bad nonce" errors and 429 Too Many Requests.
|
||||
//
|
||||
// If RetryBackoff is nil, a truncated exponential backoff algorithm
|
||||
// with the ceiling of 10 seconds is used, where each subsequent retry n
|
||||
// is done after either ("Retry-After" + jitter) or (2^n seconds + jitter),
|
||||
// preferring the former if "Retry-After" header is found in the resp.
|
||||
// The jitter is a random value up to 1 second.
|
||||
RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration
|
||||
|
||||
dirMu sync.Mutex // guards writes to dir
|
||||
dir *Directory // cached result of Client's Discover method
|
||||
|
||||
noncesMu sync.Mutex
|
||||
nonces map[string]struct{} // nonces collected from previous responses
|
||||
}
|
||||
|
||||
// Discover performs ACME server discovery using c.DirectoryURL.
|
||||
//
|
||||
// It caches successful result. So, subsequent calls will not result in
|
||||
// a network round-trip. This also means mutating c.DirectoryURL after successful call
|
||||
// of this method will have no effect.
|
||||
func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
c.dirMu.Lock()
|
||||
defer c.dirMu.Unlock()
|
||||
if c.dir != nil {
|
||||
return *c.dir, nil
|
||||
}
|
||||
|
||||
dirURL := c.DirectoryURL
|
||||
if dirURL == "" {
|
||||
dirURL = LetsEncryptURL
|
||||
}
|
||||
res, err := c.get(ctx, dirURL, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
c.addNonce(res.Header)
|
||||
|
||||
var v struct {
|
||||
Reg string `json:"new-reg"`
|
||||
Authz string `json:"new-authz"`
|
||||
Cert string `json:"new-cert"`
|
||||
Revoke string `json:"revoke-cert"`
|
||||
Meta struct {
|
||||
Terms string `json:"terms-of-service"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caa-identities"`
|
||||
}
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
c.dir = &Directory{
|
||||
RegURL: v.Reg,
|
||||
AuthzURL: v.Authz,
|
||||
CertURL: v.Cert,
|
||||
RevokeURL: v.Revoke,
|
||||
Terms: v.Meta.Terms,
|
||||
Website: v.Meta.Website,
|
||||
CAA: v.Meta.CAA,
|
||||
}
|
||||
return *c.dir, nil
|
||||
}
|
||||
|
||||
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
|
||||
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
|
||||
// with a different duration.
|
||||
// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain.
|
||||
//
|
||||
// In the case where CA server does not provide the issued certificate in the response,
|
||||
// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
|
||||
// In such a scenario, the caller can cancel the polling with ctx.
|
||||
//
|
||||
// CreateCert returns an error if the CA's response or chain was unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
|
||||
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
CSR string `json:"csr"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}{
|
||||
Resource: "new-cert",
|
||||
CSR: base64.RawURLEncoding.EncodeToString(csr),
|
||||
}
|
||||
now := timeNow()
|
||||
req.NotBefore = now.Format(time.RFC3339)
|
||||
if exp > 0 {
|
||||
req.NotAfter = now.Add(exp).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, c.Key, c.dir.CertURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
curl := res.Header.Get("Location") // cert permanent URL
|
||||
if res.ContentLength == 0 {
|
||||
// no cert in the body; poll until we get it
|
||||
cert, err := c.FetchCert(ctx, curl, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
// slurp issued cert and CA chain, if requested
|
||||
cert, err := c.responseCert(ctx, res, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
|
||||
// FetchCert retrieves already issued certificate from the given url, in DER format.
|
||||
// It retries the request until the certificate is successfully retrieved,
|
||||
// context is cancelled by the caller or an error response is received.
|
||||
//
|
||||
// The returned value will also contain the CA (issuer) certificate if the bundle argument is true.
|
||||
//
|
||||
// FetchCert returns an error if the CA's response or chain was unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid
|
||||
// and has expected features.
|
||||
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.responseCert(ctx, res, bundle)
|
||||
}
|
||||
|
||||
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
|
||||
//
|
||||
// The key argument, used to sign the request, must be authorized
|
||||
// to revoke the certificate. It's up to the CA to decide which keys are authorized.
|
||||
// For instance, the key pair of the certificate may be authorized.
|
||||
// If the key is nil, c.Key is used instead.
|
||||
func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := &struct {
|
||||
Resource string `json:"resource"`
|
||||
Cert string `json:"certificate"`
|
||||
Reason int `json:"reason"`
|
||||
}{
|
||||
Resource: "revoke-cert",
|
||||
Cert: base64.RawURLEncoding.EncodeToString(cert),
|
||||
Reason: int(reason),
|
||||
}
|
||||
if key == nil {
|
||||
key = c.Key
|
||||
}
|
||||
res, err := c.post(ctx, key, c.dir.RevokeURL, body, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
|
||||
// during account registration. See Register method of Client for more details.
|
||||
func AcceptTOS(tosURL string) bool { return true }
|
||||
|
||||
// Register creates a new account registration by following the "new-reg" flow.
|
||||
// It returns the registered account. The account is not modified.
|
||||
//
|
||||
// The registration may require the caller to agree to the CA's Terms of Service (TOS).
|
||||
// If so, and the account has not indicated the acceptance of the terms (see Account for details),
|
||||
// Register calls prompt with a TOS URL provided by the CA. Prompt should report
|
||||
// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
|
||||
func (c *Client) Register(ctx context.Context, a *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
if a, err = c.doReg(ctx, c.dir.RegURL, "new-reg", a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accept bool
|
||||
if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms {
|
||||
accept = prompt(a.CurrentTerms)
|
||||
}
|
||||
if accept {
|
||||
a.AgreedTerms = a.CurrentTerms
|
||||
a, err = c.UpdateReg(ctx, a)
|
||||
}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// GetReg retrieves an existing registration.
|
||||
// The url argument is an Account URI.
|
||||
func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
|
||||
a, err := c.doReg(ctx, url, "reg", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.URI = url
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// UpdateReg updates an existing registration.
|
||||
// It returns an updated account copy. The provided account is not modified.
|
||||
func (c *Client) UpdateReg(ctx context.Context, a *Account) (*Account, error) {
|
||||
uri := a.URI
|
||||
a, err := c.doReg(ctx, uri, "reg", a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.URI = uri
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Authorize performs the initial step in an authorization flow.
|
||||
// The caller will then need to choose from and perform a set of returned
|
||||
// challenges using c.Accept in order to successfully complete authorization.
|
||||
//
|
||||
// If an authorization has been previously granted, the CA may return
|
||||
// a valid authorization (Authorization.Status is StatusValid). If so, the caller
|
||||
// need not fulfill any challenge and can proceed to requesting a certificate.
|
||||
func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type authzID struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Identifier authzID `json:"identifier"`
|
||||
}{
|
||||
Resource: "new-authz",
|
||||
Identifier: authzID{Type: "dns", Value: domain},
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v wireAuthz
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
if v.Status != StatusPending && v.Status != StatusValid {
|
||||
return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
|
||||
}
|
||||
return v.authorization(res.Header.Get("Location")), nil
|
||||
}
|
||||
|
||||
// GetAuthorization retrieves an authorization identified by the given URL.
|
||||
//
|
||||
// If a caller needs to poll an authorization until its status is final,
|
||||
// see the WaitAuthorization method.
|
||||
func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var v wireAuthz
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
return v.authorization(url), nil
|
||||
}
|
||||
|
||||
// RevokeAuthorization relinquishes an existing authorization identified
|
||||
// by the given URL.
|
||||
// The url argument is an Authorization.URI value.
|
||||
//
|
||||
// If successful, the caller will be required to obtain a new authorization
|
||||
// using the Authorize method before being able to request a new certificate
|
||||
// for the domain associated with the authorization.
|
||||
//
|
||||
// It does not revoke existing certificates.
|
||||
func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Status string `json:"status"`
|
||||
Delete bool `json:"delete"`
|
||||
}{
|
||||
Resource: "authz",
|
||||
Status: "deactivated",
|
||||
Delete: true,
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, url, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitAuthorization polls an authorization at the given URL
|
||||
// until it is in one of the final states, StatusValid or StatusInvalid,
|
||||
// the ACME CA responded with a 4xx error code, or the context is done.
|
||||
//
|
||||
// It returns a non-nil Authorization only if its Status is StatusValid.
|
||||
// In all other cases WaitAuthorization returns an error.
|
||||
// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
|
||||
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
for {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var raw wireAuthz
|
||||
err = json.NewDecoder(res.Body).Decode(&raw)
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case err != nil:
|
||||
// Skip and retry.
|
||||
case raw.Status == StatusValid:
|
||||
return raw.authorization(url), nil
|
||||
case raw.Status == StatusInvalid:
|
||||
return nil, raw.error(url)
|
||||
}
|
||||
|
||||
// Exponential backoff is implemented in c.get above.
|
||||
// This is just to prevent continuously hitting the CA
|
||||
// while waiting for a final authorization status.
|
||||
d := retryAfter(res.Header.Get("Retry-After"))
|
||||
if d == 0 {
|
||||
// Given that the fastest challenges TLS-SNI and HTTP-01
|
||||
// require a CA to make at least 1 network round trip
|
||||
// and most likely persist a challenge state,
|
||||
// this default delay seems reasonable.
|
||||
d = time.Second
|
||||
}
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return nil, ctx.Err()
|
||||
case <-t.C:
|
||||
// Retry.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetChallenge retrieves the current status of an challenge.
|
||||
//
|
||||
// A client typically polls a challenge status using this method.
|
||||
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
v := wireChallenge{URI: url}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
return v.challenge(), nil
|
||||
}
|
||||
|
||||
// Accept informs the server that the client accepts one of its challenges
|
||||
// previously obtained with c.Authorize.
|
||||
//
|
||||
// The server will then perform the validation asynchronously.
|
||||
func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) {
|
||||
auth, err := keyAuth(c.Key.Public(), chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"type"`
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}{
|
||||
Resource: "challenge",
|
||||
Type: chal.Type,
|
||||
Auth: auth,
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, chal.URI, req, wantStatus(
|
||||
http.StatusOK, // according to the spec
|
||||
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v wireChallenge
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
return v.challenge(), nil
|
||||
}
|
||||
|
||||
// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.
|
||||
// A TXT record containing the returned value must be provisioned under
|
||||
// "_acme-challenge" name of the domain being validated.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) DNS01ChallengeRecord(token string) (string, error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||
}
|
||||
|
||||
// HTTP01ChallengeResponse returns the response for an http-01 challenge.
|
||||
// Servers should respond with the value to HTTP requests at the URL path
|
||||
// provided by HTTP01ChallengePath to validate the challenge and prove control
|
||||
// over a domain name.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) HTTP01ChallengeResponse(token string) (string, error) {
|
||||
return keyAuth(c.Key.Public(), token)
|
||||
}
|
||||
|
||||
// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge
|
||||
// should be provided by the servers.
|
||||
// The response value can be obtained with HTTP01ChallengeResponse.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) HTTP01ChallengePath(token string) string {
|
||||
return "/.well-known/acme-challenge/" + token
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name.
|
||||
//
|
||||
// The implementation is incomplete in that the returned value is a single certificate,
|
||||
// computed only for Z0 of the key authorization. ACME CAs are expected to update
|
||||
// their implementations to use the newer version, TLS-SNI-02.
|
||||
// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name of the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
h := hex.EncodeToString(b[:])
|
||||
name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
|
||||
cert, err = tlsChallengeCert([]string{name}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, name, nil
|
||||
}
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-SNI-02 see
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
|
||||
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b = sha256.Sum256([]byte(ka))
|
||||
h = hex.EncodeToString(b[:])
|
||||
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
|
||||
|
||||
cert, err = tlsChallengeCert([]string{sanA, sanB}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, sanA, nil
|
||||
}
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-ALPN-01 see
|
||||
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||
// has been specified.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
shasum := sha256.Sum256([]byte(ka))
|
||||
extValue, err := asn1.Marshal(shasum[:])
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
acmeExtension := pkix.Extension{
|
||||
Id: idPeACMEIdentifierV1,
|
||||
Critical: true,
|
||||
Value: extValue,
|
||||
}
|
||||
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
|
||||
var newOpt []CertOption
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case *certOptTemplate:
|
||||
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||
tmpl = &t
|
||||
default:
|
||||
newOpt = append(newOpt, o)
|
||||
}
|
||||
}
|
||||
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
}
|
||||
|
||||
// doReg sends all types of registration requests.
|
||||
// The type of request is identified by typ argument, which is a "resource"
|
||||
// in the ACME spec terms.
|
||||
//
|
||||
// A non-nil acct argument indicates whether the intention is to mutate data
|
||||
// of the Account. Only Contact and Agreement of its fields are used
|
||||
// in such cases.
|
||||
func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
Agreement string `json:"agreement,omitempty"`
|
||||
}{
|
||||
Resource: typ,
|
||||
}
|
||||
if acct != nil {
|
||||
req.Contact = acct.Contact
|
||||
req.Agreement = acct.AgreedTerms
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, url, req, wantStatus(
|
||||
http.StatusOK, // updates and deletes
|
||||
http.StatusCreated, // new account creation
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v struct {
|
||||
Contact []string
|
||||
Agreement string
|
||||
Authorizations string
|
||||
Certificates string
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
var tos string
|
||||
if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 {
|
||||
tos = v[0]
|
||||
}
|
||||
var authz string
|
||||
if v := linkHeader(res.Header, "next"); len(v) > 0 {
|
||||
authz = v[0]
|
||||
}
|
||||
return &Account{
|
||||
URI: res.Header.Get("Location"),
|
||||
Contact: v.Contact,
|
||||
AgreedTerms: v.Agreement,
|
||||
CurrentTerms: tos,
|
||||
Authz: authz,
|
||||
Authorizations: v.Authorizations,
|
||||
Certificates: v.Certificates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// popNonce returns a nonce value previously stored with c.addNonce
|
||||
// or fetches a fresh one from the given URL.
|
||||
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
if len(c.nonces) == 0 {
|
||||
return c.fetchNonce(ctx, url)
|
||||
}
|
||||
var nonce string
|
||||
for nonce = range c.nonces {
|
||||
delete(c.nonces, nonce)
|
||||
break
|
||||
}
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// clearNonces clears any stored nonces
|
||||
func (c *Client) clearNonces() {
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
c.nonces = make(map[string]struct{})
|
||||
}
|
||||
|
||||
// addNonce stores a nonce value found in h (if any) for future use.
|
||||
func (c *Client) addNonce(h http.Header) {
|
||||
v := nonceFromHeader(h)
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
if len(c.nonces) >= maxNonces {
|
||||
return
|
||||
}
|
||||
if c.nonces == nil {
|
||||
c.nonces = make(map[string]struct{})
|
||||
}
|
||||
c.nonces[v] = struct{}{}
|
||||
}
|
||||
|
||||
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
||||
r, err := http.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := c.doNoRetry(ctx, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
nonce := nonceFromHeader(resp.Header)
|
||||
if nonce == "" {
|
||||
if resp.StatusCode > 299 {
|
||||
return "", responseError(resp)
|
||||
}
|
||||
return "", errors.New("acme: nonce not found")
|
||||
}
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
func nonceFromHeader(h http.Header) string {
|
||||
return h.Get("Replay-Nonce")
|
||||
}
|
||||
|
||||
func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bool) ([][]byte, error) {
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: response stream: %v", err)
|
||||
}
|
||||
if len(b) > maxCertSize {
|
||||
return nil, errors.New("acme: certificate is too big")
|
||||
}
|
||||
cert := [][]byte{b}
|
||||
if !bundle {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Append CA chain cert(s).
|
||||
// At least one is required according to the spec:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1
|
||||
up := linkHeader(res.Header, "up")
|
||||
if len(up) == 0 {
|
||||
return nil, errors.New("acme: rel=up link not found")
|
||||
}
|
||||
if len(up) > maxChainLen {
|
||||
return nil, errors.New("acme: rel=up link is too large")
|
||||
}
|
||||
for _, url := range up {
|
||||
cc, err := c.chainCert(ctx, url, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert = append(cert, cc...)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// chainCert fetches CA certificate chain recursively by following "up" links.
|
||||
// Each recursive call increments the depth by 1, resulting in an error
|
||||
// if the recursion level reaches maxChainLen.
|
||||
//
|
||||
// First chainCert call starts with depth of 0.
|
||||
func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte, error) {
|
||||
if depth >= maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too deep")
|
||||
}
|
||||
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) > maxCertSize {
|
||||
return nil, errors.New("acme: certificate is too big")
|
||||
}
|
||||
chain := [][]byte{b}
|
||||
|
||||
uplink := linkHeader(res.Header, "up")
|
||||
if len(uplink) > maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too large")
|
||||
}
|
||||
for _, up := range uplink {
|
||||
cc, err := c.chainCert(ctx, up, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chain = append(chain, cc...)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// linkHeader returns URI-Reference values of all Link headers
|
||||
// with relation-type rel.
|
||||
// See https://tools.ietf.org/html/rfc5988#section-5 for details.
|
||||
func linkHeader(h http.Header, rel string) []string {
|
||||
var links []string
|
||||
for _, v := range h["Link"] {
|
||||
parts := strings.Split(v, ";")
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if !strings.HasPrefix(p, "rel=") {
|
||||
continue
|
||||
}
|
||||
if v := strings.Trim(p[4:], `"`); v == rel {
|
||||
links = append(links, strings.Trim(parts[0], "<>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
// keyAuth generates a key authorization string for a given token.
|
||||
func keyAuth(pub crypto.PublicKey, token string) (string, error) {
|
||||
th, err := JWKThumbprint(pub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", token, th), nil
|
||||
}
|
||||
|
||||
// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges.
|
||||
func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||
return &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(24 * time.Hour),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
}
|
||||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// To create a cert with a custom key pair, specify WithKey option.
|
||||
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
var key crypto.Signer
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case *certOptKey:
|
||||
if key != nil {
|
||||
return tls.Certificate{}, errors.New("acme: duplicate key option")
|
||||
}
|
||||
key = o.key
|
||||
case *certOptTemplate:
|
||||
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||
tmpl = &t
|
||||
default:
|
||||
// package's fault, if we let this happen:
|
||||
panic(fmt.Sprintf("unsupported option type %T", o))
|
||||
}
|
||||
}
|
||||
if key == nil {
|
||||
var err error
|
||||
if key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.Certificate{
|
||||
Certificate: [][]byte{der},
|
||||
PrivateKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodePEM returns b encoded as PEM with block of type typ.
|
||||
func encodePEM(typ string, b []byte) []byte {
|
||||
pb := &pem.Block{Type: typ, Bytes: b}
|
||||
return pem.EncodeToMemory(pb)
|
||||
}
|
||||
|
||||
// timeNow is useful for testing for fixed current time.
|
||||
var timeNow = time.Now
|
1092
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
1092
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
File diff suppressed because it is too large
Load Diff
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
@ -1,130 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ErrCacheMiss is returned when a certificate is not found in cache.
|
||||
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
||||
|
||||
// Cache is used by Manager to store and retrieve previously obtained certificates
|
||||
// and other account data as opaque blobs.
|
||||
//
|
||||
// Cache implementations should not rely on the key naming pattern. Keys can
|
||||
// include any printable ASCII characters, except the following: \/:*?"<>|
|
||||
type Cache interface {
|
||||
// Get returns a certificate data for the specified key.
|
||||
// If there's no such key, Get returns ErrCacheMiss.
|
||||
Get(ctx context.Context, key string) ([]byte, error)
|
||||
|
||||
// Put stores the data in the cache under the specified key.
|
||||
// Underlying implementations may use any data storage format,
|
||||
// as long as the reverse operation, Get, results in the original data.
|
||||
Put(ctx context.Context, key string, data []byte) error
|
||||
|
||||
// Delete removes a certificate data from the cache under the specified key.
|
||||
// If there's no such key in the cache, Delete returns nil.
|
||||
Delete(ctx context.Context, key string) error
|
||||
}
|
||||
|
||||
// DirCache implements Cache using a directory on the local filesystem.
|
||||
// If the directory does not exist, it will be created with 0700 permissions.
|
||||
type DirCache string
|
||||
|
||||
// Get reads a certificate data from the specified file name.
|
||||
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
||||
name = filepath.Join(string(d), name)
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
data, err = ioutil.ReadFile(name)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// Put writes the certificate data to the specified file name.
|
||||
// The file will be created with 0600 permissions.
|
||||
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||
if err := os.MkdirAll(string(d), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
defer close(done)
|
||||
var tmp string
|
||||
if tmp, err = d.writeTempFile(name, data); err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Don't overwrite the file if the context was canceled.
|
||||
default:
|
||||
newName := filepath.Join(string(d), name)
|
||||
err = os.Rename(tmp, newName)
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete removes the specified file name.
|
||||
func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||
name = filepath.Join(string(d), name)
|
||||
var (
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
err = os.Remove(name)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
||||
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
||||
// TempFile uses 0600 permissions
|
||||
f, err := ioutil.TempFile(string(d), prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := f.Write(b); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), f.Close()
|
||||
}
|
160
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
160
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
@ -1,160 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewListener returns a net.Listener that listens on the standard TLS
|
||||
// port (443) on all interfaces and returns *tls.Conn connections with
|
||||
// LetsEncrypt certificates for the provided domain or domains.
|
||||
//
|
||||
// It enables one-line HTTPS servers:
|
||||
//
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
//
|
||||
// NewListener is a convenience function for a common configuration.
|
||||
// More complex or custom configurations can use the autocert.Manager
|
||||
// type instead.
|
||||
//
|
||||
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
||||
// Service. If domains is not empty, the provided domains are passed
|
||||
// to HostWhitelist. If domains is empty, the listener will do
|
||||
// LetsEncrypt challenges for any requested domain, which is not
|
||||
// recommended.
|
||||
//
|
||||
// Certificates are cached in a "golang-autocert" directory under an
|
||||
// operating system-specific cache or temp directory. This may not
|
||||
// be suitable for servers spanning multiple machines.
|
||||
//
|
||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||
// should only be used with servers that support HTTP/2.
|
||||
//
|
||||
// The returned Listener also enables TCP keep-alives on the accepted
|
||||
// connections. The returned *tls.Conn are returned before their TLS
|
||||
// handshake has completed.
|
||||
func NewListener(domains ...string) net.Listener {
|
||||
m := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
}
|
||||
if len(domains) > 0 {
|
||||
m.HostPolicy = HostWhitelist(domains...)
|
||||
}
|
||||
dir := cacheDir()
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
||||
} else {
|
||||
m.Cache = DirCache(dir)
|
||||
}
|
||||
return m.Listener()
|
||||
}
|
||||
|
||||
// Listener listens on the standard TLS port (443) on all interfaces
|
||||
// and returns a net.Listener returning *tls.Conn connections.
|
||||
//
|
||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||
// should only be used with servers that support HTTP/2.
|
||||
//
|
||||
// The returned Listener also enables TCP keep-alives on the accepted
|
||||
// connections. The returned *tls.Conn are returned before their TLS
|
||||
// handshake has completed.
|
||||
//
|
||||
// Unlike NewListener, it is the caller's responsibility to initialize
|
||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||
func (m *Manager) Listener() net.Listener {
|
||||
ln := &listener{
|
||||
m: m,
|
||||
conf: &tls.Config{
|
||||
GetCertificate: m.GetCertificate, // bonus: panic on nil m
|
||||
NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
|
||||
},
|
||||
}
|
||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||
return ln
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
m *Manager
|
||||
conf *tls.Config
|
||||
|
||||
tcpListener net.Listener
|
||||
tcpListenErr error
|
||||
}
|
||||
|
||||
func (ln *listener) Accept() (net.Conn, error) {
|
||||
if ln.tcpListenErr != nil {
|
||||
return nil, ln.tcpListenErr
|
||||
}
|
||||
conn, err := ln.tcpListener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
|
||||
// Because Listener is a convenience function, help out with
|
||||
// this too. This is not possible for the caller to set once
|
||||
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
||||
// If callers don't want this, they can do things the manual
|
||||
// way and tweak as needed. But this is what net/http does
|
||||
// itself, so copy that. If net/http changes, we can change
|
||||
// here too.
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||
|
||||
return tls.Server(tcpConn, ln.conf), nil
|
||||
}
|
||||
|
||||
func (ln *listener) Addr() net.Addr {
|
||||
if ln.tcpListener != nil {
|
||||
return ln.tcpListener.Addr()
|
||||
}
|
||||
// net.Listen failed. Return something non-nil in case callers
|
||||
// call Addr before Accept:
|
||||
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
||||
}
|
||||
|
||||
func (ln *listener) Close() error {
|
||||
if ln.tcpListenErr != nil {
|
||||
return ln.tcpListenErr
|
||||
}
|
||||
return ln.tcpListener.Close()
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
}
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
func cacheDir() string {
|
||||
const base = "golang-autocert"
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return filepath.Join(homeDir(), "Library", "Caches", base)
|
||||
case "windows":
|
||||
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
||||
if v := os.Getenv(ev); v != "" {
|
||||
return filepath.Join(v, base)
|
||||
}
|
||||
}
|
||||
// Worst case:
|
||||
return filepath.Join(homeDir(), base)
|
||||
}
|
||||
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
||||
return filepath.Join(xdg, base)
|
||||
}
|
||||
return filepath.Join(homeDir(), ".cache", base)
|
||||
}
|
141
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
141
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
@ -1,141 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
||||
const renewJitter = time.Hour
|
||||
|
||||
// domainRenewal tracks the state used by the periodic timers
|
||||
// renewing a single domain's cert.
|
||||
type domainRenewal struct {
|
||||
m *Manager
|
||||
ck certKey
|
||||
key crypto.Signer
|
||||
|
||||
timerMu sync.Mutex
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// start starts a cert renewal timer at the time
|
||||
// defined by the certificate expiration time exp.
|
||||
//
|
||||
// If the timer is already started, calling start is a noop.
|
||||
func (dr *domainRenewal) start(exp time.Time) {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer != nil {
|
||||
return
|
||||
}
|
||||
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
||||
}
|
||||
|
||||
// stop stops the cert renewal timer.
|
||||
// If the timer is already stopped, calling stop is a noop.
|
||||
func (dr *domainRenewal) stop() {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer == nil {
|
||||
return
|
||||
}
|
||||
dr.timer.Stop()
|
||||
dr.timer = nil
|
||||
}
|
||||
|
||||
// renew is called periodically by a timer.
|
||||
// The first renew call is kicked off by dr.start.
|
||||
func (dr *domainRenewal) renew() {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
// TODO: rotate dr.key at some point?
|
||||
next, err := dr.do(ctx)
|
||||
if err != nil {
|
||||
next = renewJitter / 2
|
||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||
}
|
||||
dr.timer = time.AfterFunc(next, dr.renew)
|
||||
testDidRenewLoop(next, err)
|
||||
}
|
||||
|
||||
// updateState locks and replaces the relevant Manager.state item with the given
|
||||
// state. It additionally updates dr.key with the given state's key.
|
||||
func (dr *domainRenewal) updateState(state *certState) {
|
||||
dr.m.stateMu.Lock()
|
||||
defer dr.m.stateMu.Unlock()
|
||||
dr.key = state.key
|
||||
dr.m.state[dr.ck] = state
|
||||
}
|
||||
|
||||
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
||||
// Instead, it requests a new certificate independently and, upon success,
|
||||
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
||||
//
|
||||
// It may lock and update the Manager.state if the expiration date of the currently
|
||||
// cached cert is far enough in the future.
|
||||
//
|
||||
// The returned value is a time interval after which the renewal should occur again.
|
||||
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
// a race is likely unavoidable in a distributed environment
|
||||
// but we try nonetheless
|
||||
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
||||
next := dr.next(tlscert.Leaf.NotAfter)
|
||||
if next > dr.m.renewBefore()+renewJitter {
|
||||
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||
if ok {
|
||||
state := &certState{
|
||||
key: signer,
|
||||
cert: tlscert.Certificate,
|
||||
leaf: tlscert.Leaf,
|
||||
}
|
||||
dr.updateState(state)
|
||||
return next, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
state := &certState{
|
||||
key: dr.key,
|
||||
cert: der,
|
||||
leaf: leaf,
|
||||
}
|
||||
tlscert, err := state.tlscert()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dr.updateState(state)
|
||||
return dr.next(leaf.NotAfter), nil
|
||||
}
|
||||
|
||||
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
||||
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
|
||||
// add a bit of randomness to renew deadline
|
||||
n := pseudoRand.int63n(int64(renewJitter))
|
||||
d -= time.Duration(n)
|
||||
if d < 0 {
|
||||
return 0
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
var testDidRenewLoop = func(next time.Duration, err error) {}
|
281
vendor/golang.org/x/crypto/acme/http.go
generated
vendored
281
vendor/golang.org/x/crypto/acme/http.go
generated
vendored
@ -1,281 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// retryTimer encapsulates common logic for retrying unsuccessful requests.
|
||||
// It is not safe for concurrent use.
|
||||
type retryTimer struct {
|
||||
// backoffFn provides backoff delay sequence for retries.
|
||||
// See Client.RetryBackoff doc comment.
|
||||
backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
|
||||
// n is the current retry attempt.
|
||||
n int
|
||||
}
|
||||
|
||||
func (t *retryTimer) inc() {
|
||||
t.n++
|
||||
}
|
||||
|
||||
// backoff pauses the current goroutine as described in Client.RetryBackoff.
|
||||
func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
|
||||
d := t.backoffFn(t.n, r, res)
|
||||
if d <= 0 {
|
||||
return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
|
||||
}
|
||||
wakeup := time.NewTimer(d)
|
||||
defer wakeup.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-wakeup.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) retryTimer() *retryTimer {
|
||||
f := c.RetryBackoff
|
||||
if f == nil {
|
||||
f = defaultBackoff
|
||||
}
|
||||
return &retryTimer{backoffFn: f}
|
||||
}
|
||||
|
||||
// defaultBackoff provides default Client.RetryBackoff implementation
|
||||
// using a truncated exponential backoff algorithm,
|
||||
// as described in Client.RetryBackoff.
|
||||
//
|
||||
// The n argument is always bounded between 1 and 30.
|
||||
// The returned value is always greater than 0.
|
||||
func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||
const max = 10 * time.Second
|
||||
var jitter time.Duration
|
||||
if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
|
||||
// Set the minimum to 1ms to avoid a case where
|
||||
// an invalid Retry-After value is parsed into 0 below,
|
||||
// resulting in the 0 returned value which would unintentionally
|
||||
// stop the retries.
|
||||
jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
|
||||
}
|
||||
if v, ok := res.Header["Retry-After"]; ok {
|
||||
return retryAfter(v[0]) + jitter
|
||||
}
|
||||
|
||||
if n < 1 {
|
||||
n = 1
|
||||
}
|
||||
if n > 30 {
|
||||
n = 30
|
||||
}
|
||||
d := time.Duration(1<<uint(n-1))*time.Second + jitter
|
||||
if d > max {
|
||||
return max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// retryAfter parses a Retry-After HTTP header value,
|
||||
// trying to convert v into an int (seconds) or use http.ParseTime otherwise.
|
||||
// It returns zero value if v cannot be parsed.
|
||||
func retryAfter(v string) time.Duration {
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
return time.Duration(i) * time.Second
|
||||
}
|
||||
t, err := http.ParseTime(v)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return t.Sub(timeNow())
|
||||
}
|
||||
|
||||
// resOkay is a function that reports whether the provided response is okay.
|
||||
// It is expected to keep the response body unread.
|
||||
type resOkay func(*http.Response) bool
|
||||
|
||||
// wantStatus returns a function which reports whether the code
|
||||
// matches the status code of a response.
|
||||
func wantStatus(codes ...int) resOkay {
|
||||
return func(res *http.Response) bool {
|
||||
for _, code := range codes {
|
||||
if code == res.StatusCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// get issues an unsigned GET request to the specified URL.
|
||||
// It returns a non-error value only when ok reports true.
|
||||
//
|
||||
// get retries unsuccessful attempts according to c.RetryBackoff
|
||||
// until the context is done or a non-retriable error is received.
|
||||
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||
retry := c.retryTimer()
|
||||
for {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := c.doNoRetry(ctx, req)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case ok(res):
|
||||
return res, nil
|
||||
case isRetriable(res.StatusCode):
|
||||
retry.inc()
|
||||
resErr := responseError(res)
|
||||
res.Body.Close()
|
||||
// Ignore the error value from retry.backoff
|
||||
// and return the one from last retry, as received from the CA.
|
||||
if retry.backoff(ctx, req, res) != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
default:
|
||||
defer res.Body.Close()
|
||||
return nil, responseError(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// post issues a signed POST request in JWS format using the provided key
|
||||
// to the specified URL.
|
||||
// It returns a non-error value only when ok reports true.
|
||||
//
|
||||
// post retries unsuccessful attempts according to c.RetryBackoff
|
||||
// until the context is done or a non-retriable error is received.
|
||||
// It uses postNoRetry to make individual requests.
|
||||
func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
|
||||
retry := c.retryTimer()
|
||||
for {
|
||||
res, req, err := c.postNoRetry(ctx, key, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok(res) {
|
||||
return res, nil
|
||||
}
|
||||
resErr := responseError(res)
|
||||
res.Body.Close()
|
||||
switch {
|
||||
// Check for bad nonce before isRetriable because it may have been returned
|
||||
// with an unretriable response code such as 400 Bad Request.
|
||||
case isBadNonce(resErr):
|
||||
// Consider any previously stored nonce values to be invalid.
|
||||
c.clearNonces()
|
||||
case !isRetriable(res.StatusCode):
|
||||
return nil, resErr
|
||||
}
|
||||
retry.inc()
|
||||
// Ignore the error value from retry.backoff
|
||||
// and return the one from last retry, as received from the CA.
|
||||
if err := retry.backoff(ctx, req, res); err != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// postNoRetry signs the body with the given key and POSTs it to the provided url.
|
||||
// The body argument must be JSON-serializable.
|
||||
// It is used by c.post to retry unsuccessful attempts.
|
||||
func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
|
||||
nonce, err := c.popNonce(ctx, url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
b, err := jwsEncodeJSON(body, key, nonce)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/jose+json")
|
||||
res, err := c.doNoRetry(ctx, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.addNonce(res.Header)
|
||||
return res, req, nil
|
||||
}
|
||||
|
||||
// doNoRetry issues a request req, replacing its context (if any) with ctx.
|
||||
func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
res, err := c.httpClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Prefer the unadorned context error.
|
||||
// (The acme package had tests assuming this, previously from ctxhttp's
|
||||
// behavior, predating net/http supporting contexts natively)
|
||||
// TODO(bradfitz): reconsider this in the future. But for now this
|
||||
// requires no test updates.
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
||||
// isBadNonce reports whether err is an ACME "badnonce" error.
|
||||
func isBadNonce(err error) bool {
|
||||
// According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
|
||||
// However, ACME servers in the wild return their versions of the error.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
|
||||
// and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66.
|
||||
ae, ok := err.(*Error)
|
||||
return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
|
||||
}
|
||||
|
||||
// isRetriable reports whether a request can be retried
|
||||
// based on the response status code.
|
||||
//
|
||||
// Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code.
|
||||
// Callers should parse the response and check with isBadNonce.
|
||||
func isRetriable(code int) bool {
|
||||
return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
// responseError creates an error of Error type from resp.
|
||||
func responseError(resp *http.Response) error {
|
||||
// don't care if ReadAll returns an error:
|
||||
// json.Unmarshal will fail in that case anyway
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
e := &wireError{Status: resp.StatusCode}
|
||||
if err := json.Unmarshal(b, e); err != nil {
|
||||
// this is not a regular error response:
|
||||
// populate detail with anything we received,
|
||||
// e.Status will already contain HTTP response code value
|
||||
e.Detail = string(b)
|
||||
if e.Detail == "" {
|
||||
e.Detail = resp.Status
|
||||
}
|
||||
}
|
||||
return e.error(resp.Header)
|
||||
}
|
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
@ -1,153 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
_ "crypto/sha512" // need for EC keys
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format.
|
||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, nonce string) ([]byte, error) {
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alg, sha := jwsHasher(key)
|
||||
if alg == "" || !sha.Available() {
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce)
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := base64.RawURLEncoding.EncodeToString(cs)
|
||||
hash := sha.New()
|
||||
hash.Write([]byte(phead + "." + payload))
|
||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
// https://tools.ietf.org/html/rfc7517
|
||||
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||
n := pub.N
|
||||
e := big.NewInt(int64(pub.E))
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||
), nil
|
||||
case *ecdsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
||||
p := pub.Curve.Params()
|
||||
n := p.BitSize / 8
|
||||
if p.BitSize%8 != 0 {
|
||||
n++
|
||||
}
|
||||
x := pub.X.Bytes()
|
||||
if n > len(x) {
|
||||
x = append(make([]byte, n-len(x)), x...)
|
||||
}
|
||||
y := pub.Y.Bytes()
|
||||
if n > len(y) {
|
||||
y = append(make([]byte, n-len(y)), y...)
|
||||
}
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
||||
p.Name,
|
||||
base64.RawURLEncoding.EncodeToString(x),
|
||||
base64.RawURLEncoding.EncodeToString(y),
|
||||
), nil
|
||||
}
|
||||
return "", ErrUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsSign signs the digest using the given key.
|
||||
// It returns ErrUnsupportedKey if the key type is unknown.
|
||||
// The hash is used only for RSA keys.
|
||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return key.Sign(rand.Reader, digest, hash)
|
||||
case *ecdsa.PrivateKey:
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rb, sb := r.Bytes(), s.Bytes()
|
||||
size := key.Params().BitSize / 8
|
||||
if size%8 > 0 {
|
||||
size++
|
||||
}
|
||||
sig := make([]byte, size*2)
|
||||
copy(sig[size-len(rb):], rb)
|
||||
copy(sig[size*2-len(sb):], sb)
|
||||
return sig, nil
|
||||
}
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||
// to use for signing a digest with the provided key.
|
||||
// It returns ("", 0) if the key is not supported.
|
||||
func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return "RS256", crypto.SHA256
|
||||
case *ecdsa.PrivateKey:
|
||||
switch key.Params().Name {
|
||||
case "P-256":
|
||||
return "ES256", crypto.SHA256
|
||||
case "P-384":
|
||||
return "ES384", crypto.SHA384
|
||||
case "P-521":
|
||||
return "ES512", crypto.SHA512
|
||||
}
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// JWKThumbprint creates a JWK thumbprint out of pub
|
||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
||||
jwk, err := jwkEncode(pub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(jwk))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||
}
|
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
@ -1,329 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ACME server response statuses used to describe Authorization and Challenge states.
|
||||
const (
|
||||
StatusUnknown = "unknown"
|
||||
StatusPending = "pending"
|
||||
StatusProcessing = "processing"
|
||||
StatusValid = "valid"
|
||||
StatusInvalid = "invalid"
|
||||
StatusRevoked = "revoked"
|
||||
)
|
||||
|
||||
// CRLReasonCode identifies the reason for a certificate revocation.
|
||||
type CRLReasonCode int
|
||||
|
||||
// CRL reason codes as defined in RFC 5280.
|
||||
const (
|
||||
CRLReasonUnspecified CRLReasonCode = 0
|
||||
CRLReasonKeyCompromise CRLReasonCode = 1
|
||||
CRLReasonCACompromise CRLReasonCode = 2
|
||||
CRLReasonAffiliationChanged CRLReasonCode = 3
|
||||
CRLReasonSuperseded CRLReasonCode = 4
|
||||
CRLReasonCessationOfOperation CRLReasonCode = 5
|
||||
CRLReasonCertificateHold CRLReasonCode = 6
|
||||
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
||||
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
||||
CRLReasonAACompromise CRLReasonCode = 10
|
||||
)
|
||||
|
||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
||||
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
||||
|
||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
||||
type Error struct {
|
||||
// StatusCode is The HTTP status code generated by the origin server.
|
||||
StatusCode int
|
||||
// ProblemType is a URI reference that identifies the problem type,
|
||||
// typically in a "urn:acme:error:xxx" form.
|
||||
ProblemType string
|
||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string
|
||||
// Header is the original server error response headers.
|
||||
// It may be nil.
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
}
|
||||
|
||||
// AuthorizationError indicates that an authorization for an identifier
|
||||
// did not succeed.
|
||||
// It contains all errors from Challenge items of the failed Authorization.
|
||||
type AuthorizationError struct {
|
||||
// URI uniquely identifies the failed Authorization.
|
||||
URI string
|
||||
|
||||
// Identifier is an AuthzID.Value of the failed Authorization.
|
||||
Identifier string
|
||||
|
||||
// Errors is a collection of non-nil error values of Challenge items
|
||||
// of the failed Authorization.
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (a *AuthorizationError) Error() string {
|
||||
e := make([]string, len(a.Errors))
|
||||
for i, err := range a.Errors {
|
||||
e[i] = err.Error()
|
||||
}
|
||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
// RateLimit reports whether err represents a rate limit error and
|
||||
// any Retry-After duration returned by the server.
|
||||
//
|
||||
// See the following for more details on rate limiting:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
||||
func RateLimit(err error) (time.Duration, bool) {
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
// Some CA implementations may return incorrect values.
|
||||
// Use case-insensitive comparison.
|
||||
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
||||
return 0, false
|
||||
}
|
||||
if e.Header == nil {
|
||||
return 0, true
|
||||
}
|
||||
return retryAfter(e.Header.Get("Retry-After")), true
|
||||
}
|
||||
|
||||
// Account is a user account. It is associated with a private key.
|
||||
type Account struct {
|
||||
// URI is the account unique ID, which is also a URL used to retrieve
|
||||
// account data from the CA.
|
||||
URI string
|
||||
|
||||
// Contact is a slice of contact info used during registration.
|
||||
Contact []string
|
||||
|
||||
// The terms user has agreed to.
|
||||
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
||||
// to the actual Terms of Service of the CA.
|
||||
AgreedTerms string
|
||||
|
||||
// Actual terms of a CA.
|
||||
CurrentTerms string
|
||||
|
||||
// Authz is the authorization URL used to initiate a new authz flow.
|
||||
Authz string
|
||||
|
||||
// Authorizations is a URI from which a list of authorizations
|
||||
// granted to this account can be fetched via a GET request.
|
||||
Authorizations string
|
||||
|
||||
// Certificates is a URI from which a list of certificates
|
||||
// issued for this account can be fetched via a GET request.
|
||||
Certificates string
|
||||
}
|
||||
|
||||
// Directory is ACME server discovery data.
|
||||
type Directory struct {
|
||||
// RegURL is an account endpoint URL, allowing for creating new
|
||||
// and modifying existing accounts.
|
||||
RegURL string
|
||||
|
||||
// AuthzURL is used to initiate Identifier Authorization flow.
|
||||
AuthzURL string
|
||||
|
||||
// CertURL is a new certificate issuance endpoint URL.
|
||||
CertURL string
|
||||
|
||||
// RevokeURL is used to initiate a certificate revocation flow.
|
||||
RevokeURL string
|
||||
|
||||
// Term is a URI identifying the current terms of service.
|
||||
Terms string
|
||||
|
||||
// Website is an HTTP or HTTPS URL locating a website
|
||||
// providing more information about the ACME server.
|
||||
Website string
|
||||
|
||||
// CAA consists of lowercase hostname elements, which the ACME server
|
||||
// recognises as referring to itself for the purposes of CAA record validation
|
||||
// as defined in RFC6844.
|
||||
CAA []string
|
||||
}
|
||||
|
||||
// Challenge encodes a returned CA challenge.
|
||||
// Its Error field may be non-nil if the challenge is part of an Authorization
|
||||
// with StatusInvalid.
|
||||
type Challenge struct {
|
||||
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
||||
Type string
|
||||
|
||||
// URI is where a challenge response can be posted to.
|
||||
URI string
|
||||
|
||||
// Token is a random value that uniquely identifies the challenge.
|
||||
Token string
|
||||
|
||||
// Status identifies the status of this challenge.
|
||||
Status string
|
||||
|
||||
// Error indicates the reason for an authorization failure
|
||||
// when this challenge was used.
|
||||
// The type of a non-nil value is *Error.
|
||||
Error error
|
||||
}
|
||||
|
||||
// Authorization encodes an authorization response.
|
||||
type Authorization struct {
|
||||
// URI uniquely identifies a authorization.
|
||||
URI string
|
||||
|
||||
// Status identifies the status of an authorization.
|
||||
Status string
|
||||
|
||||
// Identifier is what the account is authorized to represent.
|
||||
Identifier AuthzID
|
||||
|
||||
// Challenges that the client needs to fulfill in order to prove possession
|
||||
// of the identifier (for pending authorizations).
|
||||
// For final authorizations, the challenges that were used.
|
||||
Challenges []*Challenge
|
||||
|
||||
// A collection of sets of challenges, each of which would be sufficient
|
||||
// to prove possession of the identifier.
|
||||
// Clients must complete a set of challenges that covers at least one set.
|
||||
// Challenges are identified by their indices in the challenges array.
|
||||
// If this field is empty, the client needs to complete all challenges.
|
||||
Combinations [][]int
|
||||
}
|
||||
|
||||
// AuthzID is an identifier that an account is authorized to represent.
|
||||
type AuthzID struct {
|
||||
Type string // The type of identifier, e.g. "dns".
|
||||
Value string // The identifier itself, e.g. "example.org".
|
||||
}
|
||||
|
||||
// wireAuthz is ACME JSON representation of Authorization objects.
|
||||
type wireAuthz struct {
|
||||
Status string
|
||||
Challenges []wireChallenge
|
||||
Combinations [][]int
|
||||
Identifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
|
||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||
a := &Authorization{
|
||||
URI: uri,
|
||||
Status: z.Status,
|
||||
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
||||
Combinations: z.Combinations, // shallow copy
|
||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||
}
|
||||
for i, v := range z.Challenges {
|
||||
a.Challenges[i] = v.challenge()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
||||
err := &AuthorizationError{
|
||||
URI: uri,
|
||||
Identifier: z.Identifier.Value,
|
||||
}
|
||||
for _, raw := range z.Challenges {
|
||||
if raw.Error != nil {
|
||||
err.Errors = append(err.Errors, raw.Error.error(nil))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// wireChallenge is ACME JSON challenge representation.
|
||||
type wireChallenge struct {
|
||||
URI string `json:"uri"`
|
||||
Type string
|
||||
Token string
|
||||
Status string
|
||||
Error *wireError
|
||||
}
|
||||
|
||||
func (c *wireChallenge) challenge() *Challenge {
|
||||
v := &Challenge{
|
||||
URI: c.URI,
|
||||
Type: c.Type,
|
||||
Token: c.Token,
|
||||
Status: c.Status,
|
||||
}
|
||||
if v.Status == "" {
|
||||
v.Status = StatusPending
|
||||
}
|
||||
if c.Error != nil {
|
||||
v.Error = c.Error.error(nil)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// wireError is a subset of fields of the Problem Details object
|
||||
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
||||
type wireError struct {
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
}
|
||||
|
||||
func (e *wireError) error(h http.Header) *Error {
|
||||
return &Error{
|
||||
StatusCode: e.Status,
|
||||
ProblemType: e.Type,
|
||||
Detail: e.Detail,
|
||||
Header: h,
|
||||
}
|
||||
}
|
||||
|
||||
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
||||
// customizing a temporary certificate for TLS-based challenges.
|
||||
type CertOption interface {
|
||||
privateCertOpt()
|
||||
}
|
||||
|
||||
// WithKey creates an option holding a private/public key pair.
|
||||
// The private part signs a certificate, and the public part represents the signee.
|
||||
func WithKey(key crypto.Signer) CertOption {
|
||||
return &certOptKey{key}
|
||||
}
|
||||
|
||||
type certOptKey struct {
|
||||
key crypto.Signer
|
||||
}
|
||||
|
||||
func (*certOptKey) privateCertOpt() {}
|
||||
|
||||
// WithTemplate creates an option for specifying a certificate template.
|
||||
// See x509.CreateCertificate for template usage details.
|
||||
//
|
||||
// In TLS ChallengeCert methods, the template is also used as parent,
|
||||
// resulting in a self-signed certificate.
|
||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
|
||||
type certOptTemplate x509.Certificate
|
||||
|
||||
func (*certOptTemplate) privateCertOpt() {}
|
Loading…
Reference in New Issue
Block a user