Adding ability to serve TLS directly from Mattermost server (#4119)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5f8e5c401b
Коммит
8f91c77755
27
vendor/github.com/rsc/letsencrypt/LICENSE
сгенерированный
поставляемый
Обычный файл
27
vendor/github.com/rsc/letsencrypt/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
152
vendor/github.com/rsc/letsencrypt/README
сгенерированный
поставляемый
Обычный файл
152
vendor/github.com/rsc/letsencrypt/README
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,152 @@
|
||||
package letsencrypt // import "rsc.io/letsencrypt"
|
||||
|
||||
Package letsencrypt obtains TLS certificates from LetsEncrypt.org.
|
||||
|
||||
LetsEncrypt.org is a service that issues free SSL/TLS certificates to
|
||||
servers that can prove control over the given domain's DNS records or the
|
||||
servers pointed at by those records.
|
||||
|
||||
|
||||
Quick Start
|
||||
|
||||
A complete HTTP/HTTPS web server using TLS certificates from
|
||||
LetsEncrypt.org, redirecting all HTTP access to HTTPS, and maintaining TLS
|
||||
certificates in a file letsencrypt.cache across server restarts.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"rsc.io/letsencrypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, TLS!\n")
|
||||
})
|
||||
var m letsencrypt.Manager
|
||||
if err := m.CacheFile("letsencrypt.cache"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Fatal(m.Serve())
|
||||
}
|
||||
|
||||
|
||||
Overview
|
||||
|
||||
The fundamental type in this package is the Manager, which manages obtaining
|
||||
and refreshing a collection of TLS certificates, typically for use by an
|
||||
HTTPS server. The example above shows the most basic use of a Manager. The
|
||||
use can be customized by calling additional methods of the Manager.
|
||||
|
||||
|
||||
Registration
|
||||
|
||||
A Manager m registers anonymously with LetsEncrypt.org, including agreeing
|
||||
to the letsencrypt.org terms of service, the first time it needs to obtain a
|
||||
certificate. To register with a particular email address and with the option
|
||||
of a prompt for agreement with the terms of service, call m.Register.
|
||||
|
||||
|
||||
GetCertificate
|
||||
|
||||
The Manager's GetCertificate method returns certificates from the Manager's
|
||||
cache, filling the cache by requesting certificates from LetsEncrypt.org. In
|
||||
this way, a server with a tls.Config.GetCertificate set to m.GetCertificate
|
||||
will demand load a certificate for any host name it serves. To force loading
|
||||
of certificates ahead of time, install m.GetCertificate as before but then
|
||||
call m.Cert for each host name.
|
||||
|
||||
A Manager can only obtain a certificate for a given host name if it can
|
||||
prove control of that host name to LetsEncrypt.org. By default it proves
|
||||
control by answering an HTTPS-based challenge: when the LetsEncrypt.org
|
||||
servers connect to the named host on port 443 (HTTPS), the TLS SNI handshake
|
||||
must use m.GetCertificate to obtain a per-host certificate. The most common
|
||||
way to satisfy this requirement is for the host name to resolve to the IP
|
||||
address of a (single) computer running m.ServeHTTPS, or at least running a
|
||||
Go TLS server with tls.Config.GetCertificate set to m.GetCertificate.
|
||||
However, other configurations are possible. For example, a group of machines
|
||||
could use an implementation of tls.Config.GetCertificate that cached
|
||||
certificates but handled cache misses by making RPCs to a Manager m on an
|
||||
elected leader machine.
|
||||
|
||||
In typical usage, then, the setting of tls.Config.GetCertificate to
|
||||
m.GetCertificate serves two purposes: it provides certificates to the TLS
|
||||
server for ordinary serving, and it also answers challenges to prove
|
||||
ownership of the domains in order to obtain those certificates.
|
||||
|
||||
To force the loading of a certificate for a given host into the Manager's
|
||||
cache, use m.Cert.
|
||||
|
||||
|
||||
Persistent Storage
|
||||
|
||||
If a server always starts with a zero Manager m, the server effectively
|
||||
fetches a new certificate for each of its host name from LetsEncrypt.org on
|
||||
each restart. This is unfortunate both because the server cannot start if
|
||||
LetsEncrypt.org is unavailable and because LetsEncrypt.org limits how often
|
||||
it will issue a certificate for a given host name (at time of writing, the
|
||||
limit is 5 per week for a given host name). To save server state proactively
|
||||
to a cache file and to reload the server state from that same file when
|
||||
creating a new manager, call m.CacheFile with the name of the file to use.
|
||||
|
||||
For alternate storage uses, m.Marshal returns the current state of the
|
||||
Manager as an opaque string, m.Unmarshal sets the state of the Manager using
|
||||
a string previously returned by m.Marshal (usually a different m), and
|
||||
m.Watch returns a channel that receives notifications about state changes.
|
||||
|
||||
|
||||
Limits
|
||||
|
||||
To avoid hitting basic rate limits on LetsEncrypt.org, a given Manager
|
||||
limits all its interactions to at most one request every minute, with an
|
||||
initial allowed burst of 20 requests.
|
||||
|
||||
By default, if GetCertificate is asked for a certificate it does not have,
|
||||
it will in turn ask LetsEncrypt.org for that certificate. This opens a
|
||||
potential attack where attackers connect to a server by IP address and
|
||||
pretend to be asking for an incorrect host name. Then GetCertificate will
|
||||
attempt to obtain a certificate for that host, incorrectly, eventually
|
||||
hitting LetsEncrypt.org's rate limit for certificate requests and making it
|
||||
impossible to obtain actual certificates. Because servers hold certificates
|
||||
for months at a time, however, an attack would need to be sustained over a
|
||||
time period of at least a month in order to cause real problems.
|
||||
|
||||
To mitigate this kind of attack, a given Manager limits itself to an average
|
||||
of one certificate request for a new host every three hours, with an initial
|
||||
allowed burst of up to 20 requests. Long-running servers will therefore stay
|
||||
within the LetsEncrypt.org limit of 300 failed requests per month.
|
||||
Certificate refreshes are not subject to this limit.
|
||||
|
||||
To eliminate the attack entirely, call m.SetHosts to enumerate the exact set
|
||||
of hosts that are allowed in certificate requests.
|
||||
|
||||
|
||||
Web Servers
|
||||
|
||||
The basic requirement for use of a Manager is that there be an HTTPS server
|
||||
running on port 443 and calling m.GetCertificate to obtain TLS certificates.
|
||||
Using standard primitives, the way to do this is:
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":https",
|
||||
TLSConfig: &tls.Config{
|
||||
GetCertificate: m.GetCertificate,
|
||||
},
|
||||
}
|
||||
srv.ListenAndServeTLS("", "")
|
||||
|
||||
However, this pattern of serving HTTPS with demand-loaded TLS certificates
|
||||
comes up enough to wrap into a single method m.ServeHTTPS.
|
||||
|
||||
Similarly, many HTTPS servers prefer to redirect HTTP clients to the HTTPS
|
||||
URLs. That functionality is provided by RedirectHTTP.
|
||||
|
||||
The combination of serving HTTPS with demand-loaded TLS certificates and
|
||||
serving HTTPS redirects to HTTP clients is provided by m.Serve, as used in
|
||||
the original example above.
|
||||
|
||||
func RedirectHTTP(w http.ResponseWriter, r *http.Request)
|
||||
type Manager struct { ... }
|
||||
757
vendor/github.com/rsc/letsencrypt/lets.go
сгенерированный
поставляемый
Обычный файл
757
vendor/github.com/rsc/letsencrypt/lets.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,757 @@
|
||||
// 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 letsencrypt obtains TLS certificates from LetsEncrypt.org.
|
||||
//
|
||||
// LetsEncrypt.org is a service that issues free SSL/TLS certificates to servers
|
||||
// that can prove control over the given domain's DNS records or
|
||||
// the servers pointed at by those records.
|
||||
//
|
||||
// Quick Start
|
||||
//
|
||||
// A complete HTTP/HTTPS web server using TLS certificates from LetsEncrypt.org,
|
||||
// redirecting all HTTP access to HTTPS, and maintaining TLS certificates in a file
|
||||
// letsencrypt.cache across server restarts.
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "rsc.io/letsencrypt"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// fmt.Fprintf(w, "Hello, TLS!\n")
|
||||
// })
|
||||
// var m letsencrypt.Manager
|
||||
// if err := m.CacheFile("letsencrypt.cache"); err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// log.Fatal(m.Serve())
|
||||
// }
|
||||
//
|
||||
// Overview
|
||||
//
|
||||
// The fundamental type in this package is the Manager, which
|
||||
// manages obtaining and refreshing a collection of TLS certificates,
|
||||
// typically for use by an HTTPS server.
|
||||
// The example above shows the most basic use of a Manager.
|
||||
// The use can be customized by calling additional methods of the Manager.
|
||||
//
|
||||
// Registration
|
||||
//
|
||||
// A Manager m registers anonymously with LetsEncrypt.org, including agreeing to
|
||||
// the letsencrypt.org terms of service, the first time it needs to obtain a certificate.
|
||||
// To register with a particular email address and with the option of a
|
||||
// prompt for agreement with the terms of service, call m.Register.
|
||||
//
|
||||
// GetCertificate
|
||||
//
|
||||
// The Manager's GetCertificate method returns certificates
|
||||
// from the Manager's cache, filling the cache by requesting certificates
|
||||
// from LetsEncrypt.org. In this way, a server with a tls.Config.GetCertificate
|
||||
// set to m.GetCertificate will demand load a certificate for any host name
|
||||
// it serves. To force loading of certificates ahead of time, install m.GetCertificate
|
||||
// as before but then call m.Cert for each host name.
|
||||
//
|
||||
// A Manager can only obtain a certificate for a given host name if it can prove
|
||||
// control of that host name to LetsEncrypt.org. By default it proves control by
|
||||
// answering an HTTPS-based challenge: when
|
||||
// the LetsEncrypt.org servers connect to the named host on port 443 (HTTPS),
|
||||
// the TLS SNI handshake must use m.GetCertificate to obtain a per-host certificate.
|
||||
// The most common way to satisfy this requirement is for the host name to
|
||||
// resolve to the IP address of a (single) computer running m.ServeHTTPS,
|
||||
// or at least running a Go TLS server with tls.Config.GetCertificate set to m.GetCertificate.
|
||||
// However, other configurations are possible. For example, a group of machines
|
||||
// could use an implementation of tls.Config.GetCertificate that cached
|
||||
// certificates but handled cache misses by making RPCs to a Manager m
|
||||
// on an elected leader machine.
|
||||
//
|
||||
// In typical usage, then, the setting of tls.Config.GetCertificate to m.GetCertificate
|
||||
// serves two purposes: it provides certificates to the TLS server for ordinary serving,
|
||||
// and it also answers challenges to prove ownership of the domains in order to
|
||||
// obtain those certificates.
|
||||
//
|
||||
// To force the loading of a certificate for a given host into the Manager's cache,
|
||||
// use m.Cert.
|
||||
//
|
||||
// Persistent Storage
|
||||
//
|
||||
// If a server always starts with a zero Manager m, the server effectively fetches
|
||||
// a new certificate for each of its host name from LetsEncrypt.org on each restart.
|
||||
// This is unfortunate both because the server cannot start if LetsEncrypt.org is
|
||||
// unavailable and because LetsEncrypt.org limits how often it will issue a certificate
|
||||
// for a given host name (at time of writing, the limit is 5 per week for a given host name).
|
||||
// To save server state proactively to a cache file and to reload the server state from
|
||||
// that same file when creating a new manager, call m.CacheFile with the name of
|
||||
// the file to use.
|
||||
//
|
||||
// For alternate storage uses, m.Marshal returns the current state of the Manager
|
||||
// as an opaque string, m.Unmarshal sets the state of the Manager using a string
|
||||
// previously returned by m.Marshal (usually a different m), and m.Watch returns
|
||||
// a channel that receives notifications about state changes.
|
||||
//
|
||||
// Limits
|
||||
//
|
||||
// To avoid hitting basic rate limits on LetsEncrypt.org, a given Manager limits all its
|
||||
// interactions to at most one request every minute, with an initial allowed burst of
|
||||
// 20 requests.
|
||||
//
|
||||
// By default, if GetCertificate is asked for a certificate it does not have, it will in turn
|
||||
// ask LetsEncrypt.org for that certificate. This opens a potential attack where attackers
|
||||
// connect to a server by IP address and pretend to be asking for an incorrect host name.
|
||||
// Then GetCertificate will attempt to obtain a certificate for that host, incorrectly,
|
||||
// eventually hitting LetsEncrypt.org's rate limit for certificate requests and making it
|
||||
// impossible to obtain actual certificates. Because servers hold certificates for months
|
||||
// at a time, however, an attack would need to be sustained over a time period
|
||||
// of at least a month in order to cause real problems.
|
||||
//
|
||||
// To mitigate this kind of attack, a given Manager limits
|
||||
// itself to an average of one certificate request for a new host every three hours,
|
||||
// with an initial allowed burst of up to 20 requests.
|
||||
// Long-running servers will therefore stay
|
||||
// within the LetsEncrypt.org limit of 300 failed requests per month.
|
||||
// Certificate refreshes are not subject to this limit.
|
||||
//
|
||||
// To eliminate the attack entirely, call m.SetHosts to enumerate the exact set
|
||||
// of hosts that are allowed in certificate requests.
|
||||
//
|
||||
// Web Servers
|
||||
//
|
||||
// The basic requirement for use of a Manager is that there be an HTTPS server
|
||||
// running on port 443 and calling m.GetCertificate to obtain TLS certificates.
|
||||
// Using standard primitives, the way to do this is:
|
||||
//
|
||||
// srv := &http.Server{
|
||||
// Addr: ":https",
|
||||
// TLSConfig: &tls.Config{
|
||||
// GetCertificate: m.GetCertificate,
|
||||
// },
|
||||
// }
|
||||
// srv.ListenAndServeTLS("", "")
|
||||
//
|
||||
// However, this pattern of serving HTTPS with demand-loaded TLS certificates
|
||||
// comes up enough to wrap into a single method m.ServeHTTPS.
|
||||
//
|
||||
// Similarly, many HTTPS servers prefer to redirect HTTP clients to the HTTPS URLs.
|
||||
// That functionality is provided by RedirectHTTP.
|
||||
//
|
||||
// The combination of serving HTTPS with demand-loaded TLS certificates and
|
||||
// serving HTTPS redirects to HTTP clients is provided by m.Serve, as used in
|
||||
// the original example above.
|
||||
//
|
||||
package letsencrypt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
const letsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
const debug = false
|
||||
|
||||
// A Manager m takes care of obtaining and refreshing a collection of TLS certificates
|
||||
// obtained by LetsEncrypt.org.
|
||||
// The zero Manager is not yet registered with LetsEncrypt.org and has no TLS certificates
|
||||
// but is nonetheless ready for use.
|
||||
// See the package comment for an overview of how to use a Manager.
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
state state
|
||||
rateLimit *rate.Limiter
|
||||
newHostLimit *rate.Limiter
|
||||
certCache map[string]*cacheEntry
|
||||
certTokens map[string]*tls.Certificate
|
||||
watchChan chan struct{}
|
||||
}
|
||||
|
||||
// Serve runs an HTTP/HTTPS web server using TLS certificates obtained by the manager.
|
||||
// The HTTP server redirects all requests to the HTTPS server.
|
||||
// The HTTPS server obtains TLS certificates as needed and responds to requests
|
||||
// by invoking http.DefaultServeMux.
|
||||
//
|
||||
// Serve does not return unitil the HTTPS server fails to start or else stops.
|
||||
// Either way, Serve can only return a non-nil error, never nil.
|
||||
func (m *Manager) Serve() error {
|
||||
l, err := net.Listen("tcp", ":http")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer l.Close()
|
||||
go http.Serve(l, http.HandlerFunc(RedirectHTTP))
|
||||
|
||||
return m.ServeHTTPS()
|
||||
}
|
||||
|
||||
// ServeHTTPS runs an HTTPS web server using TLS certificates obtained by the manager.
|
||||
// The HTTPS server obtains TLS certificates as needed and responds to requests
|
||||
// by invoking http.DefaultServeMux.
|
||||
// ServeHTTPS does not return unitil the HTTPS server fails to start or else stops.
|
||||
// Either way, ServeHTTPS can only return a non-nil error, never nil.
|
||||
func (m *Manager) ServeHTTPS() error {
|
||||
srv := &http.Server{
|
||||
Addr: ":https",
|
||||
TLSConfig: &tls.Config{
|
||||
GetCertificate: m.GetCertificate,
|
||||
},
|
||||
}
|
||||
return srv.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
||||
// RedirectHTTP is an HTTP handler (suitable for use with http.HandleFunc)
|
||||
// that responds to all requests by redirecting to the same URL served over HTTPS.
|
||||
// It should only be invoked for requests received over HTTP.
|
||||
func RedirectHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS != nil || r.Host == "" {
|
||||
http.Error(w, "not found", 404)
|
||||
}
|
||||
|
||||
u := r.URL
|
||||
u.Host = r.Host
|
||||
u.Scheme = "https"
|
||||
http.Redirect(w, r, u.String(), 302)
|
||||
}
|
||||
|
||||
// state is the serializable state for the Manager.
|
||||
// It also implements acme.User.
|
||||
type state struct {
|
||||
Email string
|
||||
Reg *acme.RegistrationResource
|
||||
Key string
|
||||
key *ecdsa.PrivateKey
|
||||
Hosts []string
|
||||
Certs map[string]stateCert
|
||||
}
|
||||
|
||||
func (s *state) GetEmail() string { return s.Email }
|
||||
func (s *state) GetRegistration() *acme.RegistrationResource { return s.Reg }
|
||||
func (s *state) GetPrivateKey() crypto.PrivateKey { return s.key }
|
||||
|
||||
type stateCert struct {
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
|
||||
func (cert stateCert) toTLS() (*tls.Certificate, error) {
|
||||
c, err := tls.X509KeyPair([]byte(cert.Cert), []byte(cert.Key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, err
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
host string
|
||||
m *Manager
|
||||
|
||||
mu sync.Mutex
|
||||
cert *tls.Certificate
|
||||
timeout time.Time
|
||||
refreshing bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *Manager) init() {
|
||||
m.mu.Lock()
|
||||
if m.certCache == nil {
|
||||
m.rateLimit = rate.NewLimiter(rate.Every(1*time.Minute), 20)
|
||||
m.newHostLimit = rate.NewLimiter(rate.Every(3*time.Hour), 20)
|
||||
m.certCache = map[string]*cacheEntry{}
|
||||
m.certTokens = map[string]*tls.Certificate{}
|
||||
m.watchChan = make(chan struct{}, 1)
|
||||
m.watchChan <- struct{}{}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Watch returns the manager's watch channel,
|
||||
// which delivers a notification after every time the
|
||||
// manager's state (as exposed by Marshal and Unmarshal) changes.
|
||||
// All calls to Watch return the same watch channel.
|
||||
//
|
||||
// The watch channel includes notifications about changes
|
||||
// before the first call to Watch, so that in the pattern below,
|
||||
// the range loop executes once immediately, saving
|
||||
// the result of setup (along with any background updates that
|
||||
// may have raced in quickly).
|
||||
//
|
||||
// m := new(letsencrypt.Manager)
|
||||
// setup(m)
|
||||
// go backgroundUpdates(m)
|
||||
// for range m.Watch() {
|
||||
// save(m.Marshal())
|
||||
// }
|
||||
//
|
||||
func (m *Manager) Watch() <-chan struct{} {
|
||||
m.init()
|
||||
m.updated()
|
||||
return m.watchChan
|
||||
}
|
||||
|
||||
func (m *Manager) updated() {
|
||||
select {
|
||||
case m.watchChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) CacheFile(name string) error {
|
||||
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) > 0 {
|
||||
if err := m.Unmarshal(string(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
for range m.Watch() {
|
||||
err := ioutil.WriteFile(name, []byte(m.Marshal()), 0600)
|
||||
if err != nil {
|
||||
log.Printf("writing letsencrypt cache: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registered reports whether the manager has registered with letsencrypt.org yet.
|
||||
func (m *Manager) Registered() bool {
|
||||
m.init()
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.registered()
|
||||
}
|
||||
|
||||
func (m *Manager) registered() bool {
|
||||
return m.state.Reg != nil && m.state.Reg.Body.Agreement != ""
|
||||
}
|
||||
|
||||
// Register registers the manager with letsencrypt.org, using the given email address.
|
||||
// Registration may require agreeing to the letsencrypt.org terms of service.
|
||||
// If so, Register calls prompt(url) where url is the URL of the terms of service.
|
||||
// Prompt should report whether the caller agrees to the terms.
|
||||
// A nil prompt func is taken to mean that the user always agrees.
|
||||
// The email address is sent to LetsEncrypt.org but otherwise unchecked;
|
||||
// it can be omitted by passing the empty string.
|
||||
//
|
||||
// Calling Register is only required to make sure registration uses a
|
||||
// particular email address or to insert an explicit prompt into the
|
||||
// registration sequence. If the manager is not registered, it will
|
||||
// automatically register with no email address and automatic
|
||||
// agreement to the terms of service at the first call to Cert or GetCertificate.
|
||||
func (m *Manager) Register(email string, prompt func(string) bool) error {
|
||||
m.init()
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
return m.register(email, prompt)
|
||||
}
|
||||
|
||||
func (m *Manager) register(email string, prompt func(string) bool) error {
|
||||
if m.registered() {
|
||||
return fmt.Errorf("already registered")
|
||||
}
|
||||
m.state.Email = email
|
||||
if m.state.key == nil {
|
||||
key, err := newKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating key: %v", err)
|
||||
}
|
||||
Key, err := marshalKey(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating key: %v", err)
|
||||
}
|
||||
m.state.key = key
|
||||
m.state.Key = string(Key)
|
||||
}
|
||||
|
||||
c, err := acme.NewClient(letsEncryptURL, &m.state, acme.EC256)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create client: %v", err)
|
||||
}
|
||||
reg, err := c.Register()
|
||||
if err != nil {
|
||||
return fmt.Errorf("register: %v", err)
|
||||
}
|
||||
|
||||
m.state.Reg = reg
|
||||
if reg.Body.Agreement == "" {
|
||||
if prompt != nil && !prompt(reg.TosURL) {
|
||||
return fmt.Errorf("did not agree to TOS")
|
||||
}
|
||||
if err := c.AgreeToTOS(); err != nil {
|
||||
return fmt.Errorf("agreeing to TOS: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
m.updated()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal returns an encoding of the manager's state,
|
||||
// suitable for writing to disk and reloading by calling Unmarshal.
|
||||
// The state includes registration status, the configured host list
|
||||
// from SetHosts, and all known certificates, including their private
|
||||
// cryptographic keys.
|
||||
// Consequently, the state should be kept private.
|
||||
func (m *Manager) Marshal() string {
|
||||
m.init()
|
||||
m.mu.Lock()
|
||||
js, err := json.MarshalIndent(&m.state, "", "\t")
|
||||
m.mu.Unlock()
|
||||
if err != nil {
|
||||
panic("unexpected json.Marshal failure")
|
||||
}
|
||||
return string(js)
|
||||
}
|
||||
|
||||
// Unmarshal restores the state encoded by a previous call to Marshal
|
||||
// (perhaps on a different Manager in a different program).
|
||||
func (m *Manager) Unmarshal(enc string) error {
|
||||
m.init()
|
||||
var st state
|
||||
if err := json.Unmarshal([]byte(enc), &st); err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Key != "" {
|
||||
key, err := unmarshalKey(st.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.key = key
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.state = st
|
||||
m.mu.Unlock()
|
||||
for host, cert := range m.state.Certs {
|
||||
c, err := cert.toTLS()
|
||||
if err != nil {
|
||||
log.Printf("letsencrypt: ignoring entry for %s: %v", host, err)
|
||||
continue
|
||||
}
|
||||
m.certCache[host] = &cacheEntry{host: host, m: m, cert: c}
|
||||
}
|
||||
m.updated()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHosts sets the manager's list of known host names.
|
||||
// If the list is non-nil, the manager will only ever attempt to acquire
|
||||
// certificates for host names on the list.
|
||||
// If the list is nil, the manager does not restrict the hosts it will
|
||||
// ask for certificates for.
|
||||
func (m *Manager) SetHosts(hosts []string) {
|
||||
m.init()
|
||||
m.mu.Lock()
|
||||
m.state.Hosts = append(m.state.Hosts[:0], hosts...)
|
||||
m.mu.Unlock()
|
||||
m.updated()
|
||||
}
|
||||
|
||||
// GetCertificate can be placed a tls.Config's GetCertificate field to make
|
||||
// the TLS server use Let's Encrypt certificates.
|
||||
// Each time a client connects to the TLS server expecting a new host name,
|
||||
// the TLS server's call to GetCertificate will trigger an exchange with the
|
||||
// Let's Encrypt servers to obtain that certificate, subject to the manager rate limits.
|
||||
//
|
||||
// As noted in the Manager's documentation comment,
|
||||
// to obtain a certificate for a given host name, that name
|
||||
// must resolve to a computer running a TLS server on port 443
|
||||
// that obtains TLS SNI certificates by calling m.GetCertificate.
|
||||
// In the standard usage, then, installing m.GetCertificate in the tls.Config
|
||||
// both automatically provisions the TLS certificates needed for
|
||||
// ordinary HTTPS service and answers the challenges from LetsEncrypt.org.
|
||||
func (m *Manager) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
m.init()
|
||||
|
||||
host := clientHello.ServerName
|
||||
|
||||
if debug {
|
||||
log.Printf("GetCertificate %s", host)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(host, ".acme.invalid") {
|
||||
m.mu.Lock()
|
||||
cert := m.certTokens[host]
|
||||
m.mu.Unlock()
|
||||
if cert == nil {
|
||||
return nil, fmt.Errorf("unknown host")
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
return m.Cert(host)
|
||||
}
|
||||
|
||||
// Cert returns the certificate for the given host name, obtaining a new one if necessary.
|
||||
//
|
||||
// As noted in the documentation for Manager and for the GetCertificate method,
|
||||
// obtaining a certificate requires that m.GetCertificate be associated with host.
|
||||
// In most servers, simply starting a TLS server with a configuration referring
|
||||
// to m.GetCertificate is sufficient, and Cert need not be called.
|
||||
//
|
||||
// The main use of Cert is to force the manager to obtain a certificate
|
||||
// for a particular host name ahead of time.
|
||||
func (m *Manager) Cert(host string) (*tls.Certificate, error) {
|
||||
host = strings.ToLower(host)
|
||||
if debug {
|
||||
log.Printf("Cert %s", host)
|
||||
}
|
||||
|
||||
m.init()
|
||||
m.mu.Lock()
|
||||
if !m.registered() {
|
||||
m.register("", nil)
|
||||
}
|
||||
|
||||
ok := false
|
||||
if m.state.Hosts == nil {
|
||||
ok = true
|
||||
} else {
|
||||
for _, h := range m.state.Hosts {
|
||||
if host == h {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
m.mu.Unlock()
|
||||
return nil, fmt.Errorf("unknown host")
|
||||
}
|
||||
|
||||
// Otherwise look in our cert cache.
|
||||
entry, ok := m.certCache[host]
|
||||
if !ok {
|
||||
r := m.rateLimit.Reserve()
|
||||
ok := r.OK()
|
||||
if ok {
|
||||
ok = m.newHostLimit.Allow()
|
||||
if !ok {
|
||||
r.Cancel()
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
m.mu.Unlock()
|
||||
return nil, fmt.Errorf("rate limited")
|
||||
}
|
||||
entry = &cacheEntry{host: host, m: m}
|
||||
m.certCache[host] = entry
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
entry.init()
|
||||
if entry.err != nil {
|
||||
return nil, entry.err
|
||||
}
|
||||
return entry.cert, nil
|
||||
}
|
||||
|
||||
func (e *cacheEntry) init() {
|
||||
if e.err != nil && time.Now().Before(e.timeout) {
|
||||
return
|
||||
}
|
||||
if e.cert != nil {
|
||||
if e.timeout.IsZero() {
|
||||
t, err := certRefreshTime(e.cert)
|
||||
if err != nil {
|
||||
e.err = err
|
||||
e.timeout = time.Now().Add(1 * time.Minute)
|
||||
e.cert = nil
|
||||
return
|
||||
}
|
||||
e.timeout = t
|
||||
}
|
||||
if time.Now().After(e.timeout) && !e.refreshing {
|
||||
e.refreshing = true
|
||||
go e.refresh()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cert, refreshTime, err := e.m.verify(e.host)
|
||||
e.m.mu.Lock()
|
||||
e.m.certCache[e.host] = e
|
||||
e.m.mu.Unlock()
|
||||
e.install(cert, refreshTime, err)
|
||||
}
|
||||
|
||||
func (e *cacheEntry) install(cert *tls.Certificate, refreshTime time.Time, err error) {
|
||||
e.cert = nil
|
||||
e.timeout = time.Time{}
|
||||
e.err = nil
|
||||
|
||||
if err != nil {
|
||||
e.err = err
|
||||
e.timeout = time.Now().Add(1 * time.Minute)
|
||||
return
|
||||
}
|
||||
|
||||
e.cert = cert
|
||||
e.timeout = refreshTime
|
||||
}
|
||||
|
||||
func (e *cacheEntry) refresh() {
|
||||
e.m.rateLimit.Wait(context.Background())
|
||||
cert, refreshTime, err := e.m.verify(e.host)
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.refreshing = false
|
||||
if err == nil {
|
||||
e.install(cert, refreshTime, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) verify(host string) (cert *tls.Certificate, refreshTime time.Time, err error) {
|
||||
c, err := acme.NewClient(letsEncryptURL, &m.state, acme.EC256)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = c.SetChallengeProvider(acme.TLSSNI01, tlsProvider{m}); err != nil {
|
||||
return
|
||||
}
|
||||
c.SetChallengeProvider(acme.TLSSNI01, tlsProvider{m})
|
||||
c.ExcludeChallenges([]acme.Challenge{acme.HTTP01})
|
||||
acmeCert, errmap := c.ObtainCertificate([]string{host}, true, nil)
|
||||
if len(errmap) > 0 {
|
||||
if debug {
|
||||
log.Printf("ObtainCertificate %v => %v", host, errmap)
|
||||
}
|
||||
err = fmt.Errorf("%v", errmap)
|
||||
return
|
||||
}
|
||||
entryCert := stateCert{
|
||||
Cert: string(acmeCert.Certificate),
|
||||
Key: string(acmeCert.PrivateKey),
|
||||
}
|
||||
cert, err = entryCert.toTLS()
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Printf("ObtainCertificate %v toTLS failure: %v", host, err)
|
||||
}
|
||||
err = err
|
||||
return
|
||||
}
|
||||
if refreshTime, err = certRefreshTime(cert); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
if m.state.Certs == nil {
|
||||
m.state.Certs = make(map[string]stateCert)
|
||||
}
|
||||
m.state.Certs[host] = entryCert
|
||||
m.mu.Unlock()
|
||||
m.updated()
|
||||
|
||||
return cert, refreshTime, nil
|
||||
}
|
||||
|
||||
func certRefreshTime(cert *tls.Certificate) (time.Time, error) {
|
||||
xc, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Printf("ObtainCertificate to X.509 failure: %v", err)
|
||||
}
|
||||
return time.Time{}, err
|
||||
}
|
||||
t := xc.NotBefore.Add(xc.NotAfter.Sub(xc.NotBefore) / 2)
|
||||
monthEarly := xc.NotAfter.Add(-30 * 24 * time.Hour)
|
||||
if t.Before(monthEarly) {
|
||||
t = monthEarly
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// tlsProvider implements acme.ChallengeProvider for TLS handshake challenges.
|
||||
type tlsProvider struct {
|
||||
m *Manager
|
||||
}
|
||||
|
||||
func (p tlsProvider) Present(domain, token, keyAuth string) error {
|
||||
cert, dom, err := acme.TLSSNI01ChallengeCertDomain(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.m.mu.Lock()
|
||||
p.m.certTokens[dom] = &cert
|
||||
p.m.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p tlsProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, dom, err := acme.TLSSNI01ChallengeCertDomain(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.m.mu.Lock()
|
||||
delete(p.m.certTokens, dom)
|
||||
p.m.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalKey(key *ecdsa.PrivateKey) ([]byte, error) {
|
||||
data, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: data}), nil
|
||||
}
|
||||
|
||||
func unmarshalKey(text string) (*ecdsa.PrivateKey, error) {
|
||||
b, _ := pem.Decode([]byte(text))
|
||||
if b == nil {
|
||||
return nil, fmt.Errorf("unmarshalKey: missing key")
|
||||
}
|
||||
if b.Type != "EC PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("unmarshalKey: found %q, not %q", b.Type, "EC PRIVATE KEY")
|
||||
}
|
||||
k, err := x509.ParseECPrivateKey(b.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshalKey: %v", err)
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func newKey() (*ecdsa.PrivateKey, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
}
|
||||
21
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/LICENSE
сгенерированный
поставляемый
Обычный файл
21
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Sebastian Erhart
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/challenges.go
сгенерированный
поставляемый
Обычный файл
16
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/challenges.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,16 @@
|
||||
package acme
|
||||
|
||||
// Challenge is a string that identifies a particular type and version of ACME challenge.
|
||||
type Challenge string
|
||||
|
||||
const (
|
||||
// HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http
|
||||
// Note: HTTP01ChallengePath returns the URL path to fulfill this challenge
|
||||
HTTP01 = Challenge("http-01")
|
||||
// TLSSNI01 is the "tls-sni-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni
|
||||
// Note: TLSSNI01ChallengeCert returns a certificate to fulfill this challenge
|
||||
TLSSNI01 = Challenge("tls-sni-01")
|
||||
// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
|
||||
// Note: DNS01Record returns a DNS record which will fulfill this challenge
|
||||
DNS01 = Challenge("dns-01")
|
||||
)
|
||||
638
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client.go
сгенерированный
поставляемый
Обычный файл
638
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,638 @@
|
||||
// Package acme implements the ACME protocol for Let's Encrypt and other conforming providers.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logger is an optional custom logger.
|
||||
Logger *log.Logger
|
||||
)
|
||||
|
||||
// logf writes a log entry. It uses Logger if not
|
||||
// nil, otherwise it uses the default log.Logger.
|
||||
func logf(format string, args ...interface{}) {
|
||||
if Logger != nil {
|
||||
Logger.Printf(format, args...)
|
||||
} else {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// User interface is to be implemented by users of this library.
|
||||
// It is used by the client type to get user specific information.
|
||||
type User interface {
|
||||
GetEmail() string
|
||||
GetRegistration() *RegistrationResource
|
||||
GetPrivateKey() crypto.PrivateKey
|
||||
}
|
||||
|
||||
// Interface for all challenge solvers to implement.
|
||||
type solver interface {
|
||||
Solve(challenge challenge, domain string) error
|
||||
}
|
||||
|
||||
type validateFunc func(j *jws, domain, uri string, chlng challenge) error
|
||||
|
||||
// Client is the user-friendy way to ACME
|
||||
type Client struct {
|
||||
directory directory
|
||||
user User
|
||||
jws *jws
|
||||
keyType KeyType
|
||||
issuerCert []byte
|
||||
solvers map[Challenge]solver
|
||||
}
|
||||
|
||||
// NewClient creates a new ACME client on behalf of the user. The client will depend on
|
||||
// the ACME directory located at caDirURL for the rest of its actions. It will
|
||||
// generate private keys for certificates of size keyBits.
|
||||
func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
|
||||
privKey := user.GetPrivateKey()
|
||||
if privKey == nil {
|
||||
return nil, errors.New("private key was nil")
|
||||
}
|
||||
|
||||
var dir directory
|
||||
if _, err := getJSON(caDirURL, &dir); err != nil {
|
||||
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||
}
|
||||
|
||||
if dir.NewRegURL == "" {
|
||||
return nil, errors.New("directory missing new registration URL")
|
||||
}
|
||||
if dir.NewAuthzURL == "" {
|
||||
return nil, errors.New("directory missing new authz URL")
|
||||
}
|
||||
if dir.NewCertURL == "" {
|
||||
return nil, errors.New("directory missing new certificate URL")
|
||||
}
|
||||
if dir.RevokeCertURL == "" {
|
||||
return nil, errors.New("directory missing revoke certificate URL")
|
||||
}
|
||||
|
||||
jws := &jws{privKey: privKey, directoryURL: caDirURL}
|
||||
|
||||
// REVIEW: best possibility?
|
||||
// Add all available solvers with the right index as per ACME
|
||||
// spec to this map. Otherwise they won`t be found.
|
||||
solvers := make(map[Challenge]solver)
|
||||
solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
|
||||
solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
|
||||
|
||||
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
|
||||
}
|
||||
|
||||
// SetChallengeProvider specifies a custom provider that will make the solution available
|
||||
func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider) error {
|
||||
switch challenge {
|
||||
case HTTP01:
|
||||
c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
|
||||
case TLSSNI01:
|
||||
c.solvers[challenge] = &tlsSNIChallenge{jws: c.jws, validate: validate, provider: p}
|
||||
default:
|
||||
return fmt.Errorf("Unknown challenge %v", challenge)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHTTPAddress specifies a custom interface:port to be used for HTTP based challenges.
|
||||
// If this option is not used, the default port 80 and all interfaces will be used.
|
||||
// To only specify a port and no interface use the ":port" notation.
|
||||
func (c *Client) SetHTTPAddress(iface string) error {
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chlng, ok := c.solvers[HTTP01]; ok {
|
||||
chlng.(*httpChallenge).provider = NewHTTPProviderServer(host, port)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTLSAddress specifies a custom interface:port to be used for TLS based challenges.
|
||||
// If this option is not used, the default port 443 and all interfaces will be used.
|
||||
// To only specify a port and no interface use the ":port" notation.
|
||||
func (c *Client) SetTLSAddress(iface string) error {
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chlng, ok := c.solvers[TLSSNI01]; ok {
|
||||
chlng.(*tlsSNIChallenge).provider = NewTLSProviderServer(host, port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExcludeChallenges explicitly removes challenges from the pool for solving.
|
||||
func (c *Client) ExcludeChallenges(challenges []Challenge) {
|
||||
// Loop through all challenges and delete the requested one if found.
|
||||
for _, challenge := range challenges {
|
||||
delete(c.solvers, challenge)
|
||||
}
|
||||
}
|
||||
|
||||
// Register the current account to the ACME server.
|
||||
func (c *Client) Register() (*RegistrationResource, error) {
|
||||
if c == nil || c.user == nil {
|
||||
return nil, errors.New("acme: cannot register a nil client or user")
|
||||
}
|
||||
logf("[INFO] acme: Registering account for %s", c.user.GetEmail())
|
||||
|
||||
regMsg := registrationMessage{
|
||||
Resource: "new-reg",
|
||||
}
|
||||
if c.user.GetEmail() != "" {
|
||||
regMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
||||
} else {
|
||||
regMsg.Contact = []string{}
|
||||
}
|
||||
|
||||
var serverReg Registration
|
||||
hdr, err := postJSON(c.jws, c.directory.NewRegURL, regMsg, &serverReg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reg := &RegistrationResource{Body: serverReg}
|
||||
|
||||
links := parseLinks(hdr["Link"])
|
||||
reg.URI = hdr.Get("Location")
|
||||
if links["terms-of-service"] != "" {
|
||||
reg.TosURL = links["terms-of-service"]
|
||||
}
|
||||
|
||||
if links["next"] != "" {
|
||||
reg.NewAuthzURL = links["next"]
|
||||
} else {
|
||||
return nil, errors.New("acme: The server did not return 'next' link to proceed")
|
||||
}
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
// AgreeToTOS updates the Client registration and sends the agreement to
|
||||
// the server.
|
||||
func (c *Client) AgreeToTOS() error {
|
||||
reg := c.user.GetRegistration()
|
||||
|
||||
reg.Body.Agreement = c.user.GetRegistration().TosURL
|
||||
reg.Body.Resource = "reg"
|
||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, c.user.GetRegistration().Body, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
|
||||
// The first domain in domains is used for the CommonName field of the certificate, all other
|
||||
// domains are added using the Subject Alternate Names extension. A new private key is generated
|
||||
// for every invocation of this function. If you do not want that you can supply your own private key
|
||||
// in the privKey parameter. If this parameter is non-nil it will be used instead of generating a new one.
|
||||
// If bundle is true, the []byte contains both the issuer certificate and
|
||||
// your issued certificate as a bundle.
|
||||
// This function will never return a partial certificate. If one domain in the list fails,
|
||||
// the whole certificate will fail.
|
||||
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey) (CertificateResource, map[string]error) {
|
||||
if bundle {
|
||||
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
||||
} else {
|
||||
logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||
}
|
||||
|
||||
challenges, failures := c.getChallenges(domains)
|
||||
// If any challenge fails - return. Do not generate partial SAN certificates.
|
||||
if len(failures) > 0 {
|
||||
return CertificateResource{}, failures
|
||||
}
|
||||
|
||||
errs := c.solveChallenges(challenges)
|
||||
// If any challenge fails - return. Do not generate partial SAN certificates.
|
||||
if len(errs) > 0 {
|
||||
return CertificateResource{}, errs
|
||||
}
|
||||
|
||||
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
cert, err := c.requestCertificate(challenges, bundle, privKey)
|
||||
if err != nil {
|
||||
for _, chln := range challenges {
|
||||
failures[chln.Domain] = err
|
||||
}
|
||||
}
|
||||
|
||||
return cert, failures
|
||||
}
|
||||
|
||||
// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||
func (c *Client) RevokeCertificate(certificate []byte) error {
|
||||
certificates, err := parsePEMBundle(certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return fmt.Errorf("Certificate bundle starts with a CA certificate")
|
||||
}
|
||||
|
||||
encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
|
||||
|
||||
_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Resource: "revoke-cert", Certificate: encodedCert}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// RenewCertificate takes a CertificateResource and tries to renew the certificate.
|
||||
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
|
||||
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||
// this function will start a new-cert flow where a new certificate gets generated.
|
||||
// If bundle is true, the []byte contains both the issuer certificate and
|
||||
// your issued certificate as a bundle.
|
||||
// For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil.
|
||||
func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (CertificateResource, error) {
|
||||
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
||||
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
||||
certificates, err := parsePEMBundle(cert.Certificate)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return CertificateResource{}, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain)
|
||||
}
|
||||
|
||||
// This is just meant to be informal for the user.
|
||||
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
|
||||
logf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours()))
|
||||
|
||||
// The first step of renewal is to check if we get a renewed cert
|
||||
// directly from the cert URL.
|
||||
resp, err := httpGet(cert.CertURL)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
serverCertBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
serverCert, err := x509.ParseCertificate(serverCertBytes)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
// If the server responds with a different certificate we are effectively renewed.
|
||||
// TODO: Further test if we can actually use the new certificate (Our private key works)
|
||||
if !x509Cert.Equal(serverCert) {
|
||||
logf("[INFO][%s] acme: Server responded with renewed certificate", cert.Domain)
|
||||
issuedCert := pemEncode(derCertificateBytes(serverCertBytes))
|
||||
// If bundle is true, we want to return a certificate bundle.
|
||||
// To do this, we need the issuer certificate.
|
||||
if bundle {
|
||||
// The issuer certificate link is always supplied via an "up" link
|
||||
// in the response headers of a new certificate.
|
||||
links := parseLinks(resp.Header["Link"])
|
||||
issuerCert, err := c.getIssuerCertificate(links["up"])
|
||||
if err != nil {
|
||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||
logf("[ERROR][%s] acme: Could not bundle issuer certificate: %v", cert.Domain, err)
|
||||
} else {
|
||||
// Success - append the issuer cert to the issued cert.
|
||||
issuerCert = pemEncode(derCertificateBytes(issuerCert))
|
||||
issuedCert = append(issuedCert, issuerCert...)
|
||||
}
|
||||
}
|
||||
|
||||
cert.Certificate = issuedCert
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
var privKey crypto.PrivateKey
|
||||
if cert.PrivateKey != nil {
|
||||
privKey, err = parsePEMPrivateKey(cert.PrivateKey)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
}
|
||||
|
||||
var domains []string
|
||||
var failures map[string]error
|
||||
// check for SAN certificate
|
||||
if len(x509Cert.DNSNames) > 1 {
|
||||
domains = append(domains, x509Cert.Subject.CommonName)
|
||||
for _, sanDomain := range x509Cert.DNSNames {
|
||||
if sanDomain == x509Cert.Subject.CommonName {
|
||||
continue
|
||||
}
|
||||
domains = append(domains, sanDomain)
|
||||
}
|
||||
} else {
|
||||
domains = append(domains, x509Cert.Subject.CommonName)
|
||||
}
|
||||
|
||||
newCert, failures := c.ObtainCertificate(domains, bundle, privKey)
|
||||
return newCert, failures[cert.Domain]
|
||||
}
|
||||
|
||||
// Looks through the challenge combinations to find a solvable match.
|
||||
// Then solves the challenges in series and returns.
|
||||
func (c *Client) solveChallenges(challenges []authorizationResource) map[string]error {
|
||||
// loop through the resources, basically through the domains.
|
||||
failures := make(map[string]error)
|
||||
for _, authz := range challenges {
|
||||
// no solvers - no solving
|
||||
if solvers := c.chooseSolvers(authz.Body, authz.Domain); solvers != nil {
|
||||
for i, solver := range solvers {
|
||||
// TODO: do not immediately fail if one domain fails to validate.
|
||||
err := solver.Solve(authz.Body.Challenges[i], authz.Domain)
|
||||
if err != nil {
|
||||
failures[authz.Domain] = err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
failures[authz.Domain] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Domain)
|
||||
}
|
||||
}
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
// Checks all combinations from the server and returns an array of
|
||||
// solvers which should get executed in series.
|
||||
func (c *Client) chooseSolvers(auth authorization, domain string) map[int]solver {
|
||||
for _, combination := range auth.Combinations {
|
||||
solvers := make(map[int]solver)
|
||||
for _, idx := range combination {
|
||||
if solver, ok := c.solvers[auth.Challenges[idx].Type]; ok {
|
||||
solvers[idx] = solver
|
||||
} else {
|
||||
logf("[INFO][%s] acme: Could not find solver for: %s", domain, auth.Challenges[idx].Type)
|
||||
}
|
||||
}
|
||||
|
||||
// If we can solve the whole combination, return the solvers
|
||||
if len(solvers) == len(combination) {
|
||||
return solvers
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the challenges needed to proof our identifier to the ACME server.
|
||||
func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[string]error) {
|
||||
resc, errc := make(chan authorizationResource), make(chan domainError)
|
||||
|
||||
for _, domain := range domains {
|
||||
go func(domain string) {
|
||||
authMsg := authorization{Resource: "new-authz", Identifier: identifier{Type: "dns", Value: domain}}
|
||||
var authz authorization
|
||||
hdr, err := postJSON(c.jws, c.user.GetRegistration().NewAuthzURL, authMsg, &authz)
|
||||
if err != nil {
|
||||
errc <- domainError{Domain: domain, Error: err}
|
||||
return
|
||||
}
|
||||
|
||||
links := parseLinks(hdr["Link"])
|
||||
if links["next"] == "" {
|
||||
logf("[ERROR][%s] acme: Server did not provide next link to proceed", domain)
|
||||
return
|
||||
}
|
||||
|
||||
resc <- authorizationResource{Body: authz, NewCertURL: links["next"], AuthURL: hdr.Get("Location"), Domain: domain}
|
||||
}(domain)
|
||||
}
|
||||
|
||||
responses := make(map[string]authorizationResource)
|
||||
failures := make(map[string]error)
|
||||
for i := 0; i < len(domains); i++ {
|
||||
select {
|
||||
case res := <-resc:
|
||||
responses[res.Domain] = res
|
||||
case err := <-errc:
|
||||
failures[err.Domain] = err.Error
|
||||
}
|
||||
}
|
||||
|
||||
challenges := make([]authorizationResource, 0, len(responses))
|
||||
for _, domain := range domains {
|
||||
if challenge, ok := responses[domain]; ok {
|
||||
challenges = append(challenges, challenge)
|
||||
}
|
||||
}
|
||||
|
||||
close(resc)
|
||||
close(errc)
|
||||
|
||||
return challenges, failures
|
||||
}
|
||||
|
||||
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey) (CertificateResource, error) {
|
||||
if len(authz) == 0 {
|
||||
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
||||
}
|
||||
|
||||
commonName := authz[0]
|
||||
var err error
|
||||
if privKey == nil {
|
||||
privKey, err = generatePrivateKey(c.keyType)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
}
|
||||
|
||||
var san []string
|
||||
var authURLs []string
|
||||
for _, auth := range authz[1:] {
|
||||
san = append(san, auth.Domain)
|
||||
authURLs = append(authURLs, auth.AuthURL)
|
||||
}
|
||||
|
||||
// TODO: should the CSR be customizable?
|
||||
csr, err := generateCsr(privKey, commonName.Domain, san)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
csrString := base64.URLEncoding.EncodeToString(csr)
|
||||
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: authURLs})
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
resp, err := c.jws.post(commonName.NewCertURL, jsonBytes)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
privateKeyPem := pemEncode(privKey)
|
||||
cerRes := CertificateResource{
|
||||
Domain: commonName.Domain,
|
||||
CertURL: resp.Header.Get("Location"),
|
||||
PrivateKey: privateKeyPem}
|
||||
|
||||
for {
|
||||
switch resp.StatusCode {
|
||||
case 201, 202:
|
||||
cert, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
// The server returns a body with a length of zero if the
|
||||
// certificate was not ready at the time this request completed.
|
||||
// Otherwise the body is the certificate.
|
||||
if len(cert) > 0 {
|
||||
|
||||
cerRes.CertStableURL = resp.Header.Get("Content-Location")
|
||||
cerRes.AccountRef = c.user.GetRegistration().URI
|
||||
|
||||
issuedCert := pemEncode(derCertificateBytes(cert))
|
||||
// If bundle is true, we want to return a certificate bundle.
|
||||
// To do this, we need the issuer certificate.
|
||||
if bundle {
|
||||
// The issuer certificate link is always supplied via an "up" link
|
||||
// in the response headers of a new certificate.
|
||||
links := parseLinks(resp.Header["Link"])
|
||||
issuerCert, err := c.getIssuerCertificate(links["up"])
|
||||
if err != nil {
|
||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||
logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", commonName.Domain, err)
|
||||
} else {
|
||||
// Success - append the issuer cert to the issued cert.
|
||||
issuerCert = pemEncode(derCertificateBytes(issuerCert))
|
||||
issuedCert = append(issuedCert, issuerCert...)
|
||||
}
|
||||
}
|
||||
|
||||
cerRes.Certificate = issuedCert
|
||||
logf("[INFO][%s] Server responded with a certificate.", commonName.Domain)
|
||||
return cerRes, nil
|
||||
}
|
||||
|
||||
// The certificate was granted but is not yet issued.
|
||||
// Check retry-after and loop.
|
||||
ra := resp.Header.Get("Retry-After")
|
||||
retryAfter, err := strconv.Atoi(ra)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
|
||||
logf("[INFO][%s] acme: Server responded with status 202; retrying after %ds", commonName.Domain, retryAfter)
|
||||
time.Sleep(time.Duration(retryAfter) * time.Second)
|
||||
|
||||
break
|
||||
default:
|
||||
return CertificateResource{}, handleHTTPError(resp)
|
||||
}
|
||||
|
||||
resp, err = httpGet(cerRes.CertURL)
|
||||
if err != nil {
|
||||
return CertificateResource{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getIssuerCertificate requests the issuer certificate and caches it for
|
||||
// subsequent requests.
|
||||
func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
|
||||
logf("[INFO] acme: Requesting issuer cert from %s", url)
|
||||
if c.issuerCert != nil {
|
||||
return c.issuerCert, nil
|
||||
}
|
||||
|
||||
resp, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = x509.ParseCertificate(issuerBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.issuerCert = issuerBytes
|
||||
return issuerBytes, err
|
||||
}
|
||||
|
||||
func parseLinks(links []string) map[string]string {
|
||||
aBrkt := regexp.MustCompile("[<>]")
|
||||
slver := regexp.MustCompile("(.+) *= *\"(.+)\"")
|
||||
linkMap := make(map[string]string)
|
||||
|
||||
for _, link := range links {
|
||||
|
||||
link = aBrkt.ReplaceAllString(link, "")
|
||||
parts := strings.Split(link, ";")
|
||||
|
||||
matches := slver.FindStringSubmatch(parts[1])
|
||||
if len(matches) > 0 {
|
||||
linkMap[matches[2]] = parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
return linkMap
|
||||
}
|
||||
|
||||
// validate makes the ACME server start validating a
|
||||
// challenge response, only returning once it is done.
|
||||
func validate(j *jws, domain, uri string, chlng challenge) error {
|
||||
var challengeResponse challenge
|
||||
|
||||
hdr, err := postJSON(j, uri, chlng, &challengeResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// After the path is sent, the ACME server will access our server.
|
||||
// Repeatedly check the server for an updated status on our request.
|
||||
for {
|
||||
switch challengeResponse.Status {
|
||||
case "valid":
|
||||
logf("[INFO][%s] The server validated our request", domain)
|
||||
return nil
|
||||
case "pending":
|
||||
break
|
||||
case "invalid":
|
||||
return handleChallengeError(challengeResponse)
|
||||
default:
|
||||
return errors.New("The server returned an unexpected state.")
|
||||
}
|
||||
|
||||
ra, err := strconv.Atoi(hdr.Get("Retry-After"))
|
||||
if err != nil {
|
||||
// The ACME server MUST return a Retry-After.
|
||||
// If it doesn't, we'll just poll hard.
|
||||
ra = 1
|
||||
}
|
||||
time.Sleep(time.Duration(ra) * time.Second)
|
||||
|
||||
hdr, err = getJSON(uri, &challengeResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
198
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client_test.go
сгенерированный
поставляемый
Обычный файл
198
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,198 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
keyBits := 32 // small value keeps test fast
|
||||
keyType := RSA2048
|
||||
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
||||
if err != nil {
|
||||
t.Fatal("Could not generate test key:", err)
|
||||
}
|
||||
user := mockUser{
|
||||
email: "test@test.com",
|
||||
regres: new(RegistrationResource),
|
||||
privatekey: key,
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
|
||||
w.Write(data)
|
||||
}))
|
||||
|
||||
client, err := NewClient(ts.URL, user, keyType)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create client: %v", err)
|
||||
}
|
||||
|
||||
if client.jws == nil {
|
||||
t.Fatalf("Expected client.jws to not be nil")
|
||||
}
|
||||
if expected, actual := key, client.jws.privKey; actual != expected {
|
||||
t.Errorf("Expected jws.privKey to be %p but was %p", expected, actual)
|
||||
}
|
||||
|
||||
if client.keyType != keyType {
|
||||
t.Errorf("Expected keyType to be %s but was %s", keyType, client.keyType)
|
||||
}
|
||||
|
||||
if expected, actual := 2, len(client.solvers); actual != expected {
|
||||
t.Fatalf("Expected %d solver(s), got %d", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientOptPort(t *testing.T) {
|
||||
keyBits := 32 // small value keeps test fast
|
||||
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
||||
if err != nil {
|
||||
t.Fatal("Could not generate test key:", err)
|
||||
}
|
||||
user := mockUser{
|
||||
email: "test@test.com",
|
||||
regres: new(RegistrationResource),
|
||||
privatekey: key,
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
|
||||
w.Write(data)
|
||||
}))
|
||||
|
||||
optPort := "1234"
|
||||
optHost := ""
|
||||
client, err := NewClient(ts.URL, user, RSA2048)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create client: %v", err)
|
||||
}
|
||||
client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
|
||||
client.SetTLSAddress(net.JoinHostPort(optHost, optPort))
|
||||
|
||||
httpSolver, ok := client.solvers[HTTP01].(*httpChallenge)
|
||||
if !ok {
|
||||
t.Fatal("Expected http-01 solver to be httpChallenge type")
|
||||
}
|
||||
if httpSolver.jws != client.jws {
|
||||
t.Error("Expected http-01 to have same jws as client")
|
||||
}
|
||||
if got := httpSolver.provider.(*HTTPProviderServer).port; got != optPort {
|
||||
t.Errorf("Expected http-01 to have port %s but was %s", optPort, got)
|
||||
}
|
||||
if got := httpSolver.provider.(*HTTPProviderServer).iface; got != optHost {
|
||||
t.Errorf("Expected http-01 to have iface %s but was %s", optHost, got)
|
||||
}
|
||||
|
||||
httpsSolver, ok := client.solvers[TLSSNI01].(*tlsSNIChallenge)
|
||||
if !ok {
|
||||
t.Fatal("Expected tls-sni-01 solver to be httpChallenge type")
|
||||
}
|
||||
if httpsSolver.jws != client.jws {
|
||||
t.Error("Expected tls-sni-01 to have same jws as client")
|
||||
}
|
||||
if got := httpsSolver.provider.(*TLSProviderServer).port; got != optPort {
|
||||
t.Errorf("Expected tls-sni-01 to have port %s but was %s", optPort, got)
|
||||
}
|
||||
if got := httpsSolver.provider.(*TLSProviderServer).iface; got != optHost {
|
||||
t.Errorf("Expected tls-sni-01 to have port %s but was %s", optHost, got)
|
||||
}
|
||||
|
||||
// test setting different host
|
||||
optHost = "127.0.0.1"
|
||||
client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
|
||||
client.SetTLSAddress(net.JoinHostPort(optHost, optPort))
|
||||
|
||||
if got := httpSolver.provider.(*HTTPProviderServer).iface; got != optHost {
|
||||
t.Errorf("Expected http-01 to have iface %s but was %s", optHost, got)
|
||||
}
|
||||
if got := httpsSolver.provider.(*TLSProviderServer).port; got != optPort {
|
||||
t.Errorf("Expected tls-sni-01 to have port %s but was %s", optPort, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
var statuses []string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Minimal stub ACME server for validation.
|
||||
w.Header().Add("Replay-Nonce", "12345")
|
||||
w.Header().Add("Retry-After", "0")
|
||||
switch r.Method {
|
||||
case "HEAD":
|
||||
case "POST":
|
||||
st := statuses[0]
|
||||
statuses = statuses[1:]
|
||||
writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
|
||||
|
||||
case "GET":
|
||||
st := statuses[0]
|
||||
statuses = statuses[1:]
|
||||
writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
|
||||
|
||||
default:
|
||||
http.Error(w, r.Method, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||
j := &jws{privKey: privKey, directoryURL: ts.URL}
|
||||
|
||||
tsts := []struct {
|
||||
name string
|
||||
statuses []string
|
||||
want string
|
||||
}{
|
||||
{"POST-unexpected", []string{"weird"}, "unexpected"},
|
||||
{"POST-valid", []string{"valid"}, ""},
|
||||
{"POST-invalid", []string{"invalid"}, "Error Detail"},
|
||||
{"GET-unexpected", []string{"pending", "weird"}, "unexpected"},
|
||||
{"GET-valid", []string{"pending", "valid"}, ""},
|
||||
{"GET-invalid", []string{"pending", "invalid"}, "Error Detail"},
|
||||
}
|
||||
|
||||
for _, tst := range tsts {
|
||||
statuses = tst.statuses
|
||||
if err := validate(j, "example.com", ts.URL, challenge{Type: "http-01", Token: "token"}); err == nil && tst.want != "" {
|
||||
t.Errorf("[%s] validate: got error %v, want something with %q", tst.name, err, tst.want)
|
||||
} else if err != nil && !strings.Contains(err.Error(), tst.want) {
|
||||
t.Errorf("[%s] validate: got error %v, want something with %q", tst.name, err, tst.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeJSONResponse marshals the body as JSON and writes it to the response.
|
||||
func writeJSONResponse(w http.ResponseWriter, body interface{}) {
|
||||
bs, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err := w.Write(bs); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// stubValidate is like validate, except it does nothing.
|
||||
func stubValidate(j *jws, domain, uri string, chlng challenge) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockUser struct {
|
||||
email string
|
||||
regres *RegistrationResource
|
||||
privatekey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func (u mockUser) GetEmail() string { return u.email }
|
||||
func (u mockUser) GetRegistration() *RegistrationResource { return u.regres }
|
||||
func (u mockUser) GetPrivateKey() crypto.PrivateKey { return u.privatekey }
|
||||
323
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto.go
сгенерированный
поставляемый
Обычный файл
323
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,323 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
// KeyType represents the key algo as well as the key size or curve to use.
|
||||
type KeyType string
|
||||
type derCertificateBytes []byte
|
||||
|
||||
// Constants for all key types we support.
|
||||
const (
|
||||
EC256 = KeyType("P256")
|
||||
EC384 = KeyType("P384")
|
||||
RSA2048 = KeyType("2048")
|
||||
RSA4096 = KeyType("4096")
|
||||
RSA8192 = KeyType("8192")
|
||||
)
|
||||
|
||||
const (
|
||||
// OCSPGood means that the certificate is valid.
|
||||
OCSPGood = ocsp.Good
|
||||
// OCSPRevoked means that the certificate has been deliberately revoked.
|
||||
OCSPRevoked = ocsp.Revoked
|
||||
// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
|
||||
OCSPUnknown = ocsp.Unknown
|
||||
// OCSPServerFailed means that the OCSP responder failed to process the request.
|
||||
OCSPServerFailed = ocsp.ServerFailed
|
||||
)
|
||||
|
||||
// GetOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||
// the parsed response, and an error, if any. The returned []byte can be passed directly
|
||||
// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
|
||||
// issued certificate, this function will try to get the issuer certificate from the
|
||||
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
|
||||
// values are nil, the OCSP status may be assumed OCSPUnknown.
|
||||
func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||
certificates, err := parsePEMBundle(bundle)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// We expect the certificate slice to be ordered downwards the chain.
|
||||
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
|
||||
// which should always be the first two certificates. If there's no
|
||||
// OCSP server listed in the leaf cert, there's nothing to do. And if
|
||||
// we have only one certificate so far, we need to get the issuer cert.
|
||||
issuedCert := certificates[0]
|
||||
if len(issuedCert.OCSPServer) == 0 {
|
||||
return nil, nil, errors.New("no OCSP server specified in cert")
|
||||
}
|
||||
if len(certificates) == 1 {
|
||||
// TODO: build fallback. If this fails, check the remaining array entries.
|
||||
if len(issuedCert.IssuingCertificateURL) == 0 {
|
||||
return nil, nil, errors.New("no issuing certificate URL")
|
||||
}
|
||||
|
||||
resp, err := httpGet(issuedCert.IssuingCertificateURL[0])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
issuerCert, err := x509.ParseCertificate(issuerBytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Insert it into the slice on position 0
|
||||
// We want it ordered right SRV CRT -> CA
|
||||
certificates = append(certificates, issuerCert)
|
||||
}
|
||||
issuerCert := certificates[1]
|
||||
|
||||
// Finally kick off the OCSP request.
|
||||
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(ocspReq)
|
||||
req, err := httpPost(issuedCert.OCSPServer[0], "application/ocsp-request", reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024))
|
||||
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if ocspRes.Certificate == nil {
|
||||
err = ocspRes.CheckSignatureFrom(issuerCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ocspResBytes, ocspRes, nil
|
||||
}
|
||||
|
||||
func getKeyAuthorization(token string, key interface{}) (string, error) {
|
||||
var publicKey crypto.PublicKey
|
||||
switch k := key.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
publicKey = k.Public()
|
||||
case *rsa.PrivateKey:
|
||||
publicKey = k.Public()
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
jwk := keyAsJWK(publicKey)
|
||||
if jwk == nil {
|
||||
return "", errors.New("Could not generate JWK from key.")
|
||||
}
|
||||
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// unpad the base64URL
|
||||
keyThumb := base64.URLEncoding.EncodeToString(thumbBytes)
|
||||
index := strings.Index(keyThumb, "=")
|
||||
if index != -1 {
|
||||
keyThumb = keyThumb[:index]
|
||||
}
|
||||
|
||||
return token + "." + keyThumb, nil
|
||||
}
|
||||
|
||||
// parsePEMBundle parses a certificate bundle from top to bottom and returns
|
||||
// a slice of x509 certificates. This function will error if no certificates are found.
|
||||
func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||
var certificates []*x509.Certificate
|
||||
var certDERBlock *pem.Block
|
||||
|
||||
for {
|
||||
certDERBlock, bundle = pem.Decode(bundle)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
}
|
||||
|
||||
if len(certificates) == 0 {
|
||||
return nil, errors.New("No certificates were found while parsing the bundle.")
|
||||
}
|
||||
|
||||
return certificates, nil
|
||||
}
|
||||
|
||||
func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||
keyBlock, _ := pem.Decode(key)
|
||||
|
||||
switch keyBlock.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||
default:
|
||||
return nil, errors.New("Unknown PEM header value")
|
||||
}
|
||||
}
|
||||
|
||||
func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||
|
||||
switch keyType {
|
||||
case EC256:
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
case EC384:
|
||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
case RSA2048:
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
case RSA4096:
|
||||
return rsa.GenerateKey(rand.Reader, 4096)
|
||||
case RSA8192:
|
||||
return rsa.GenerateKey(rand.Reader, 8192)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Invalid KeyType: %s", keyType)
|
||||
}
|
||||
|
||||
func generateCsr(privateKey crypto.PrivateKey, domain string, san []string) ([]byte, error) {
|
||||
template := x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: domain,
|
||||
},
|
||||
}
|
||||
|
||||
if len(san) > 0 {
|
||||
template.DNSNames = san
|
||||
}
|
||||
|
||||
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||
}
|
||||
|
||||
func pemEncode(data interface{}) []byte {
|
||||
var pemBlock *pem.Block
|
||||
switch key := data.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||
case *rsa.PrivateKey:
|
||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||
break
|
||||
case derCertificateBytes:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))}
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(pemBlock)
|
||||
}
|
||||
|
||||
func pemDecode(data []byte) (*pem.Block, error) {
|
||||
pemBlock, _ := pem.Decode(data)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("Pem decode did not yield a valid block. Is the certificate in the right format?")
|
||||
}
|
||||
|
||||
return pemBlock, nil
|
||||
}
|
||||
|
||||
func pemDecodeTox509(pem []byte) (*x509.Certificate, error) {
|
||||
pemBlock, err := pemDecode(pem)
|
||||
if pemBlock == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
// GetPEMCertExpiration returns the "NotAfter" date of a PEM encoded certificate.
|
||||
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
|
||||
func GetPEMCertExpiration(cert []byte) (time.Time, error) {
|
||||
pemBlock, err := pemDecode(cert)
|
||||
if pemBlock == nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return getCertExpiration(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
// getCertExpiration returns the "NotAfter" date of a DER encoded certificate.
|
||||
func getCertExpiration(cert []byte) (time.Time, error) {
|
||||
pCert, err := x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return pCert.NotAfter, nil
|
||||
}
|
||||
|
||||
func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privKey, time.Time{}, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||
}
|
||||
|
||||
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiration.IsZero() {
|
||||
expiration = time.Now().Add(365)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "ACME Challenge TEMP",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
}
|
||||
|
||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||||
}
|
||||
|
||||
func limitReader(rd io.ReadCloser, numBytes int64) io.ReadCloser {
|
||||
return http.MaxBytesReader(nil, rd, numBytes)
|
||||
}
|
||||
93
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto_test.go
сгенерированный
поставляемый
Обычный файл
93
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,93 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGeneratePrivateKey(t *testing.T) {
|
||||
key, err := generatePrivateKey(RSA2048)
|
||||
if err != nil {
|
||||
t.Error("Error generating private key:", err)
|
||||
}
|
||||
if key == nil {
|
||||
t.Error("Expected key to not be nil, but it was")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateCSR(t *testing.T) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatal("Error generating private key:", err)
|
||||
}
|
||||
|
||||
csr, err := generateCsr(key, "fizz.buzz", nil)
|
||||
if err != nil {
|
||||
t.Error("Error generating CSR:", err)
|
||||
}
|
||||
if csr == nil || len(csr) == 0 {
|
||||
t.Error("Expected CSR with data, but it was nil or length 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPEMEncode(t *testing.T) {
|
||||
buf := bytes.NewBufferString("TestingRSAIsSoMuchFun")
|
||||
|
||||
reader := MockRandReader{b: buf}
|
||||
key, err := rsa.GenerateKey(reader, 32)
|
||||
if err != nil {
|
||||
t.Fatal("Error generating private key:", err)
|
||||
}
|
||||
|
||||
data := pemEncode(key)
|
||||
|
||||
if data == nil {
|
||||
t.Fatal("Expected result to not be nil, but it was")
|
||||
}
|
||||
if len(data) != 127 {
|
||||
t.Errorf("Expected PEM encoding to be length 127, but it was %d", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPEMCertExpiration(t *testing.T) {
|
||||
privKey, err := generatePrivateKey(RSA2048)
|
||||
if err != nil {
|
||||
t.Fatal("Error generating private key:", err)
|
||||
}
|
||||
|
||||
expiration := time.Now().Add(365)
|
||||
expiration = expiration.Round(time.Second)
|
||||
certBytes, err := generateDerCert(privKey.(*rsa.PrivateKey), expiration, "test.com")
|
||||
if err != nil {
|
||||
t.Fatal("Error generating cert:", err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString("TestingRSAIsSoMuchFun")
|
||||
|
||||
// Some random string should return an error.
|
||||
if ctime, err := GetPEMCertExpiration(buf.Bytes()); err == nil {
|
||||
t.Errorf("Expected getCertExpiration to return an error for garbage string but returned %v", ctime)
|
||||
}
|
||||
|
||||
// A DER encoded certificate should return an error.
|
||||
if _, err := GetPEMCertExpiration(certBytes); err == nil {
|
||||
t.Errorf("Expected getCertExpiration to return an error for DER certificates but returned none.")
|
||||
}
|
||||
|
||||
// A PEM encoded certificate should work ok.
|
||||
pemCert := pemEncode(derCertificateBytes(certBytes))
|
||||
if ctime, err := GetPEMCertExpiration(pemCert); err != nil || !ctime.Equal(expiration.UTC()) {
|
||||
t.Errorf("Expected getCertExpiration to return %v but returned %v. Error: %v", expiration, ctime, err)
|
||||
}
|
||||
}
|
||||
|
||||
type MockRandReader struct {
|
||||
b *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r MockRandReader) Read(p []byte) (int, error) {
|
||||
return r.b.Read(p)
|
||||
}
|
||||
73
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/error.go
сгенерированный
поставляемый
Обычный файл
73
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/error.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,73 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
tosAgreementError = "Must agree to subscriber agreement before any further actions"
|
||||
)
|
||||
|
||||
// RemoteError is the base type for all errors specific to the ACME protocol.
|
||||
type RemoteError struct {
|
||||
StatusCode int `json:"status,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
func (e RemoteError) Error() string {
|
||||
return fmt.Sprintf("acme: Error %d - %s - %s", e.StatusCode, e.Type, e.Detail)
|
||||
}
|
||||
|
||||
// TOSError represents the error which is returned if the user needs to
|
||||
// accept the TOS.
|
||||
// TODO: include the new TOS url if we can somehow obtain it.
|
||||
type TOSError struct {
|
||||
RemoteError
|
||||
}
|
||||
|
||||
type domainError struct {
|
||||
Domain string
|
||||
Error error
|
||||
}
|
||||
|
||||
type challengeError struct {
|
||||
RemoteError
|
||||
records []validationRecord
|
||||
}
|
||||
|
||||
func (c challengeError) Error() string {
|
||||
|
||||
var errStr string
|
||||
for _, validation := range c.records {
|
||||
errStr = errStr + fmt.Sprintf("\tValidation for %s:%s\n\tResolved to:\n\t\t%s\n\tUsed: %s\n\n",
|
||||
validation.Hostname, validation.Port, strings.Join(validation.ResolvedAddresses, "\n\t\t"), validation.UsedAddress)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\nError Detail:\n%s", c.RemoteError.Error(), errStr)
|
||||
}
|
||||
|
||||
func handleHTTPError(resp *http.Response) error {
|
||||
var errorDetail RemoteError
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
err := decoder.Decode(&errorDetail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errorDetail.StatusCode = resp.StatusCode
|
||||
|
||||
// Check for errors we handle specifically
|
||||
if errorDetail.StatusCode == http.StatusForbidden && errorDetail.Detail == tosAgreementError {
|
||||
return TOSError{errorDetail}
|
||||
}
|
||||
|
||||
return errorDetail
|
||||
}
|
||||
|
||||
func handleChallengeError(chlng challenge) error {
|
||||
return challengeError{chlng.Error, chlng.ValidationRecords}
|
||||
}
|
||||
117
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http.go
сгенерированный
поставляемый
Обычный файл
117
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,117 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserAgent (if non-empty) will be tacked onto the User-Agent string in requests.
|
||||
var UserAgent string
|
||||
|
||||
// defaultClient is an HTTP client with a reasonable timeout value.
|
||||
var defaultClient = http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
const (
|
||||
// defaultGoUserAgent is the Go HTTP package user agent string. Too
|
||||
// bad it isn't exported. If it changes, we should update it here, too.
|
||||
defaultGoUserAgent = "Go-http-client/1.1"
|
||||
|
||||
// ourUserAgent is the User-Agent of this underlying library package.
|
||||
ourUserAgent = "xenolf-acme"
|
||||
)
|
||||
|
||||
// httpHead performs a HEAD request with a proper User-Agent string.
|
||||
// The response body (resp.Body) is already closed when this function returns.
|
||||
func httpHead(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
resp, err = defaultClient.Do(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// httpPost performs a POST request with a proper User-Agent string.
|
||||
// Callers should close resp.Body when done reading from it.
|
||||
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", bodyType)
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
return defaultClient.Do(req)
|
||||
}
|
||||
|
||||
// httpGet performs a GET request with a proper User-Agent string.
|
||||
// Callers should close resp.Body when done reading from it.
|
||||
func httpGet(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
return defaultClient.Do(req)
|
||||
}
|
||||
|
||||
// getJSON performs an HTTP GET request and parses the response body
|
||||
// as JSON, into the provided respBody object.
|
||||
func getJSON(uri string, respBody interface{}) (http.Header, error) {
|
||||
resp, err := httpGet(uri)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get %q: %v", uri, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return resp.Header, handleHTTPError(resp)
|
||||
}
|
||||
|
||||
return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
|
||||
}
|
||||
|
||||
// postJSON performs an HTTP POST request and parses the response body
|
||||
// as JSON, into the provided respBody object.
|
||||
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
|
||||
jsonBytes, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to marshal network message...")
|
||||
}
|
||||
|
||||
resp, err := j.post(uri, jsonBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return resp.Header, handleHTTPError(resp)
|
||||
}
|
||||
|
||||
if respBody == nil {
|
||||
return resp.Header, nil
|
||||
}
|
||||
|
||||
return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
|
||||
}
|
||||
|
||||
// userAgent builds and returns the User-Agent string to use in requests.
|
||||
func userAgent() string {
|
||||
ua := fmt.Sprintf("%s (%s; %s) %s %s", defaultGoUserAgent, runtime.GOOS, runtime.GOARCH, ourUserAgent, UserAgent)
|
||||
return strings.TrimSpace(ua)
|
||||
}
|
||||
41
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge.go
сгенерированный
поставляемый
Обычный файл
41
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,41 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
type httpChallenge struct {
|
||||
jws *jws
|
||||
validate validateFunc
|
||||
provider ChallengeProvider
|
||||
}
|
||||
|
||||
// HTTP01ChallengePath returns the URL path for the `http-01` challenge
|
||||
func HTTP01ChallengePath(token string) string {
|
||||
return "/.well-known/acme-challenge/" + token
|
||||
}
|
||||
|
||||
func (s *httpChallenge) Solve(chlng challenge, domain string) error {
|
||||
|
||||
logf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := s.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Printf("[%s] error cleaning up: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return s.validate(s.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||
}
|
||||
79
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_server.go
сгенерированный
поставляемый
Обычный файл
79
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_server.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,79 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTTPProviderServer implements ChallengeProvider for `http-01` challenge
|
||||
// It may be instantiated without using the NewHTTPProviderServer function if
|
||||
// you want only to use the default values.
|
||||
type HTTPProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
done chan bool
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewHTTPProviderServer creates a new HTTPProviderServer on the selected interface and port.
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 80 respectively.
|
||||
func NewHTTPProviderServer(iface, port string) *HTTPProviderServer {
|
||||
return &HTTPProviderServer{iface: iface, port: port}
|
||||
}
|
||||
|
||||
// Present starts a web server and makes the token available at `HTTP01ChallengePath(token)` for web requests.
|
||||
func (s *HTTPProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if s.port == "" {
|
||||
s.port = "80"
|
||||
}
|
||||
|
||||
var err error
|
||||
s.listener, err = net.Listen("tcp", net.JoinHostPort(s.iface, s.port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not start HTTP server for challenge -> %v", err)
|
||||
}
|
||||
|
||||
s.done = make(chan bool)
|
||||
go s.serve(domain, token, keyAuth)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp closes the HTTP server and removes the token from `HTTP01ChallengePath(token)`
|
||||
func (s *HTTPProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
s.listener.Close()
|
||||
<-s.done
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
|
||||
path := HTTP01ChallengePath(token)
|
||||
|
||||
// The handler validates the HOST header and request type.
|
||||
// For validation it then writes the token the server returned with the challenge
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.Host, domain) && r.Method == "GET" {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
w.Write([]byte(keyAuth))
|
||||
logf("[INFO][%s] Served key authentication", domain)
|
||||
} else {
|
||||
logf("[INFO] Received request for domain %s with method %s", r.Host, r.Method)
|
||||
w.Write([]byte("TEST"))
|
||||
}
|
||||
})
|
||||
|
||||
httpServer := &http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
// Once httpServer is shut down we don't want any lingering
|
||||
// connections, so disable KeepAlives.
|
||||
httpServer.SetKeepAlivesEnabled(false)
|
||||
httpServer.Serve(s.listener)
|
||||
s.done <- true
|
||||
}
|
||||
57
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_test.go
сгенерированный
поставляемый
Обычный файл
57
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,57 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPChallenge(t *testing.T) {
|
||||
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||
j := &jws{privKey: privKey}
|
||||
clientChallenge := challenge{Type: HTTP01, Token: "http1"}
|
||||
mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
|
||||
uri := "http://localhost:23457/.well-known/acme-challenge/" + chlng.Token
|
||||
resp, err := httpGet(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if want := "text/plain"; resp.Header.Get("Content-Type") != want {
|
||||
t.Errorf("Get(%q) Content-Type: got %q, want %q", uri, resp.Header.Get("Content-Type"), want)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyStr := string(body)
|
||||
|
||||
if bodyStr != chlng.KeyAuthorization {
|
||||
t.Errorf("Get(%q) Body: got %q, want %q", uri, bodyStr, chlng.KeyAuthorization)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
solver := &httpChallenge{jws: j, validate: mockValidate, provider: &HTTPProviderServer{port: "23457"}}
|
||||
|
||||
if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil {
|
||||
t.Errorf("Solve error: got %v, want nil", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPChallengeInvalidPort(t *testing.T) {
|
||||
privKey, _ := rsa.GenerateKey(rand.Reader, 128)
|
||||
j := &jws{privKey: privKey}
|
||||
clientChallenge := challenge{Type: HTTP01, Token: "http2"}
|
||||
solver := &httpChallenge{jws: j, validate: stubValidate, provider: &HTTPProviderServer{port: "123456"}}
|
||||
|
||||
if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil {
|
||||
t.Errorf("Solve error: got %v, want error", err)
|
||||
} else if want := "invalid port 123456"; !strings.HasSuffix(err.Error(), want) {
|
||||
t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
100
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_test.go
сгенерированный
поставляемый
Обычный файл
100
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,100 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPHeadUserAgent(t *testing.T) {
|
||||
var ua, method string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ua = r.Header.Get("User-Agent")
|
||||
method = r.Method
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
_, err := httpHead(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if method != "HEAD" {
|
||||
t.Errorf("Expected method to be HEAD, got %s", method)
|
||||
}
|
||||
if !strings.Contains(ua, ourUserAgent) {
|
||||
t.Errorf("Expected User-Agent to contain '%s', got: '%s'", ourUserAgent, ua)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPGetUserAgent(t *testing.T) {
|
||||
var ua, method string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ua = r.Header.Get("User-Agent")
|
||||
method = r.Method
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
res, err := httpGet(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
|
||||
if method != "GET" {
|
||||
t.Errorf("Expected method to be GET, got %s", method)
|
||||
}
|
||||
if !strings.Contains(ua, ourUserAgent) {
|
||||
t.Errorf("Expected User-Agent to contain '%s', got: '%s'", ourUserAgent, ua)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPPostUserAgent(t *testing.T) {
|
||||
var ua, method string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ua = r.Header.Get("User-Agent")
|
||||
method = r.Method
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
res, err := httpPost(ts.URL, "text/plain", strings.NewReader("falalalala"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
|
||||
if method != "POST" {
|
||||
t.Errorf("Expected method to be POST, got %s", method)
|
||||
}
|
||||
if !strings.Contains(ua, ourUserAgent) {
|
||||
t.Errorf("Expected User-Agent to contain '%s', got: '%s'", ourUserAgent, ua)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgent(t *testing.T) {
|
||||
ua := userAgent()
|
||||
|
||||
if !strings.Contains(ua, defaultGoUserAgent) {
|
||||
t.Errorf("Expected UA to contain %s, got '%s'", defaultGoUserAgent, ua)
|
||||
}
|
||||
if !strings.Contains(ua, ourUserAgent) {
|
||||
t.Errorf("Expected UA to contain %s, got '%s'", ourUserAgent, ua)
|
||||
}
|
||||
if strings.HasSuffix(ua, " ") {
|
||||
t.Errorf("UA should not have trailing spaces; got '%s'", ua)
|
||||
}
|
||||
|
||||
// customize the UA by appending a value
|
||||
UserAgent = "MyApp/1.2.3"
|
||||
ua = userAgent()
|
||||
if !strings.Contains(ua, defaultGoUserAgent) {
|
||||
t.Errorf("Expected UA to contain %s, got '%s'", defaultGoUserAgent, ua)
|
||||
}
|
||||
if !strings.Contains(ua, ourUserAgent) {
|
||||
t.Errorf("Expected UA to contain %s, got '%s'", ourUserAgent, ua)
|
||||
}
|
||||
if !strings.Contains(ua, UserAgent) {
|
||||
t.Errorf("Expected custom UA to contain %s, got '%s'", UserAgent, ua)
|
||||
}
|
||||
}
|
||||
107
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/jws.go
сгенерированный
поставляемый
Обычный файл
107
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/jws.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,107 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/square/go-jose.v1"
|
||||
)
|
||||
|
||||
type jws struct {
|
||||
directoryURL string
|
||||
privKey crypto.PrivateKey
|
||||
nonces []string
|
||||
}
|
||||
|
||||
func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
||||
switch k := key.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
return &jose.JsonWebKey{Key: k, Algorithm: "EC"}
|
||||
case *rsa.PublicKey:
|
||||
return &jose.JsonWebKey{Key: k, Algorithm: "RSA"}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Posts a JWS signed message to the specified URL
|
||||
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||
signedContent, err := j.signContent(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize())))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
j.getNonceFromResponse(resp)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
||||
|
||||
var alg jose.SignatureAlgorithm
|
||||
switch k := j.privKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
alg = jose.RS256
|
||||
case *ecdsa.PrivateKey:
|
||||
if k.Curve == elliptic.P256() {
|
||||
alg = jose.ES256
|
||||
} else if k.Curve == elliptic.P384() {
|
||||
alg = jose.ES384
|
||||
}
|
||||
}
|
||||
|
||||
signer, err := jose.NewSigner(alg, j.privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signer.SetNonceSource(j)
|
||||
|
||||
signed, err := signer.Sign(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) getNonceFromResponse(resp *http.Response) error {
|
||||
nonce := resp.Header.Get("Replay-Nonce")
|
||||
if nonce == "" {
|
||||
return fmt.Errorf("Server did not respond with a proper nonce header.")
|
||||
}
|
||||
|
||||
j.nonces = append(j.nonces, nonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *jws) getNonce() error {
|
||||
resp, err := httpHead(j.directoryURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return j.getNonceFromResponse(resp)
|
||||
}
|
||||
|
||||
func (j *jws) Nonce() (string, error) {
|
||||
nonce := ""
|
||||
if len(j.nonces) == 0 {
|
||||
err := j.getNonce()
|
||||
if err != nil {
|
||||
return nonce, err
|
||||
}
|
||||
}
|
||||
|
||||
nonce, j.nonces = j.nonces[len(j.nonces)-1], j.nonces[:len(j.nonces)-1]
|
||||
return nonce, nil
|
||||
}
|
||||
115
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/messages.go
сгенерированный
поставляемый
Обычный файл
115
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/messages.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,115 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v1"
|
||||
)
|
||||
|
||||
type directory struct {
|
||||
NewAuthzURL string `json:"new-authz"`
|
||||
NewCertURL string `json:"new-cert"`
|
||||
NewRegURL string `json:"new-reg"`
|
||||
RevokeCertURL string `json:"revoke-cert"`
|
||||
}
|
||||
|
||||
type recoveryKeyMessage struct {
|
||||
Length int `json:"length,omitempty"`
|
||||
Client jose.JsonWebKey `json:"client,omitempty"`
|
||||
Server jose.JsonWebKey `json:"client,omitempty"`
|
||||
}
|
||||
|
||||
type registrationMessage struct {
|
||||
Resource string `json:"resource"`
|
||||
Contact []string `json:"contact"`
|
||||
// RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
|
||||
}
|
||||
|
||||
// Registration is returned by the ACME server after the registration
|
||||
// The client implementation should save this registration somewhere.
|
||||
type Registration struct {
|
||||
Resource string `json:"resource,omitempty"`
|
||||
ID int `json:"id"`
|
||||
Key jose.JsonWebKey `json:"key"`
|
||||
Contact []string `json:"contact"`
|
||||
Agreement string `json:"agreement,omitempty"`
|
||||
Authorizations string `json:"authorizations,omitempty"`
|
||||
Certificates string `json:"certificates,omitempty"`
|
||||
// RecoveryKey recoveryKeyMessage `json:"recoveryKey,omitempty"`
|
||||
}
|
||||
|
||||
// RegistrationResource represents all important informations about a registration
|
||||
// of which the client needs to keep track itself.
|
||||
type RegistrationResource struct {
|
||||
Body Registration `json:"body,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
NewAuthzURL string `json:"new_authzr_uri,omitempty"`
|
||||
TosURL string `json:"terms_of_service,omitempty"`
|
||||
}
|
||||
|
||||
type authorizationResource struct {
|
||||
Body authorization
|
||||
Domain string
|
||||
NewCertURL string
|
||||
AuthURL string
|
||||
}
|
||||
|
||||
type authorization struct {
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Identifier identifier `json:"identifier"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Expires time.Time `json:"expires,omitempty"`
|
||||
Challenges []challenge `json:"challenges,omitempty"`
|
||||
Combinations [][]int `json:"combinations,omitempty"`
|
||||
}
|
||||
|
||||
type identifier struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type validationRecord struct {
|
||||
URI string `json:"url,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
ResolvedAddresses []string `json:"addressesResolved,omitempty"`
|
||||
UsedAddress string `json:"addressUsed,omitempty"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Type Challenge `json:"type,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
KeyAuthorization string `json:"keyAuthorization,omitempty"`
|
||||
TLS bool `json:"tls,omitempty"`
|
||||
Iterations int `json:"n,omitempty"`
|
||||
Error RemoteError `json:"error,omitempty"`
|
||||
ValidationRecords []validationRecord `json:"validationRecord,omitempty"`
|
||||
}
|
||||
|
||||
type csrMessage struct {
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Csr string `json:"csr"`
|
||||
Authorizations []string `json:"authorizations"`
|
||||
}
|
||||
|
||||
type revokeCertMessage struct {
|
||||
Resource string `json:"resource"`
|
||||
Certificate string `json:"certificate"`
|
||||
}
|
||||
|
||||
// CertificateResource represents a CA issued certificate.
|
||||
// PrivateKey and Certificate are both already PEM encoded
|
||||
// and can be directly written to disk. Certificate may
|
||||
// be a certificate bundle, depending on the options supplied
|
||||
// to create it.
|
||||
type CertificateResource struct {
|
||||
Domain string `json:"domain"`
|
||||
CertURL string `json:"certUrl"`
|
||||
CertStableURL string `json:"certStableUrl"`
|
||||
AccountRef string `json:"accountRef,omitempty"`
|
||||
PrivateKey []byte `json:"-"`
|
||||
Certificate []byte `json:"-"`
|
||||
}
|
||||
28
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/provider.go
сгенерированный
поставляемый
Обычный файл
28
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/provider.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,28 @@
|
||||
package acme
|
||||
|
||||
import "time"
|
||||
|
||||
// ChallengeProvider enables implementing a custom challenge
|
||||
// provider. Present presents the solution to a challenge available to
|
||||
// be solved. CleanUp will be called by the challenge if Present ends
|
||||
// in a non-error state.
|
||||
type ChallengeProvider interface {
|
||||
Present(domain, token, keyAuth string) error
|
||||
CleanUp(domain, token, keyAuth string) error
|
||||
}
|
||||
|
||||
// ChallengeProviderTimeout allows for implementing a
|
||||
// ChallengeProvider where an unusually long timeout is required when
|
||||
// waiting for an ACME challenge to be satisfied, such as when
|
||||
// checking for DNS record progagation. If an implementor of a
|
||||
// ChallengeProvider provides a Timeout method, then the return values
|
||||
// of the Timeout method will be used when appropriate by the acme
|
||||
// package. The interval value is the time between checks.
|
||||
//
|
||||
// The default values used for timeout and interval are 60 seconds and
|
||||
// 2 seconds respectively. These are used when no Timeout method is
|
||||
// defined for the ChallengeProvider.
|
||||
type ChallengeProviderTimeout interface {
|
||||
ChallengeProvider
|
||||
Timeout() (timeout, interval time.Duration)
|
||||
}
|
||||
73
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go
сгенерированный
поставляемый
Обычный файл
73
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,73 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
type tlsSNIChallenge struct {
|
||||
jws *jws
|
||||
validate validateFunc
|
||||
provider ChallengeProvider
|
||||
}
|
||||
|
||||
func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
|
||||
// FIXME: https://github.com/ietf-wg-acme/acme/pull/22
|
||||
// Currently we implement this challenge to track boulder, not the current spec!
|
||||
|
||||
logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain)
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, t.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := t.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Printf("[%s] error cleaning up: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
return t.validate(t.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
|
||||
func TLSSNI01ChallengeCertDomain(keyAuth string) (tls.Certificate, string, error) {
|
||||
// generate a new RSA key for the certificates
|
||||
tempPrivKey, err := generatePrivateKey(RSA2048)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
||||
rsaPrivPEM := pemEncode(rsaPrivKey)
|
||||
|
||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
|
||||
return certificate, domain, nil
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert returns a certificate for the `tls-sni-01` challenge
|
||||
func TLSSNI01ChallengeCert(keyAuth string) (tls.Certificate, error) {
|
||||
cert, _, err := TLSSNI01ChallengeCertDomain(keyAuth)
|
||||
return cert, err
|
||||
}
|
||||
62
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go
сгенерированный
поставляемый
Обычный файл
62
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,62 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// TLSProviderServer implements ChallengeProvider for `TLS-SNI-01` challenge
|
||||
// It may be instantiated without using the NewTLSProviderServer function if
|
||||
// you want only to use the default values.
|
||||
type TLSProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
done chan bool
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewTLSProviderServer creates a new TLSProviderServer on the selected interface and port.
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 443 respectively.
|
||||
func NewTLSProviderServer(iface, port string) *TLSProviderServer {
|
||||
return &TLSProviderServer{iface: iface, port: port}
|
||||
}
|
||||
|
||||
// Present makes the keyAuth available as a cert
|
||||
func (s *TLSProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if s.port == "" {
|
||||
s.port = "443"
|
||||
}
|
||||
|
||||
cert, err := TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConf := new(tls.Config)
|
||||
tlsConf.Certificates = []tls.Certificate{cert}
|
||||
|
||||
s.listener, err = tls.Listen("tcp", net.JoinHostPort(s.iface, s.port), tlsConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not start HTTPS server for challenge -> %v", err)
|
||||
}
|
||||
|
||||
s.done = make(chan bool)
|
||||
go func() {
|
||||
http.Serve(s.listener, nil)
|
||||
s.done <- true
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp closes the HTTP server.
|
||||
func (s *TLSProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
s.listener.Close()
|
||||
<-s.done
|
||||
return nil
|
||||
}
|
||||
65
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_test.go
сгенерированный
поставляемый
Обычный файл
65
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,65 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTLSSNIChallenge(t *testing.T) {
|
||||
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||
j := &jws{privKey: privKey}
|
||||
clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni1"}
|
||||
mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
|
||||
conn, err := tls.Dial("tcp", "localhost:23457", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Expected to connect to challenge server without an error. %s", err.Error())
|
||||
}
|
||||
|
||||
// Expect the server to only return one certificate
|
||||
connState := conn.ConnectionState()
|
||||
if count := len(connState.PeerCertificates); count != 1 {
|
||||
t.Errorf("Expected the challenge server to return exactly one certificate but got %d", count)
|
||||
}
|
||||
|
||||
remoteCert := connState.PeerCertificates[0]
|
||||
if count := len(remoteCert.DNSNames); count != 1 {
|
||||
t.Errorf("Expected the challenge certificate to have exactly one DNSNames entry but had %d", count)
|
||||
}
|
||||
|
||||
zBytes := sha256.Sum256([]byte(chlng.KeyAuthorization))
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
|
||||
|
||||
if remoteCert.DNSNames[0] != domain {
|
||||
t.Errorf("Expected the challenge certificate DNSName to match %s but was %s", domain, remoteCert.DNSNames[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
solver := &tlsSNIChallenge{jws: j, validate: mockValidate, provider: &TLSProviderServer{port: "23457"}}
|
||||
|
||||
if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil {
|
||||
t.Errorf("Solve error: got %v, want nil", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSSNIChallengeInvalidPort(t *testing.T) {
|
||||
privKey, _ := rsa.GenerateKey(rand.Reader, 128)
|
||||
j := &jws{privKey: privKey}
|
||||
clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni2"}
|
||||
solver := &tlsSNIChallenge{jws: j, validate: stubValidate, provider: &TLSProviderServer{port: "123456"}}
|
||||
|
||||
if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil {
|
||||
t.Errorf("Solve error: got %v, want error", err)
|
||||
} else if want := "invalid port 123456"; !strings.HasSuffix(err.Error(), want) {
|
||||
t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils.go
сгенерированный
поставляемый
Обычный файл
29
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,29 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WaitFor polls the given function 'f', once every 'interval', up to 'timeout'.
|
||||
func WaitFor(timeout, interval time.Duration, f func() (bool, error)) error {
|
||||
var lastErr string
|
||||
timeup := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeup:
|
||||
return fmt.Errorf("Time limit exceeded. Last error: %s", lastErr)
|
||||
default:
|
||||
}
|
||||
|
||||
stop, err := f()
|
||||
if stop {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
lastErr = err.Error()
|
||||
}
|
||||
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils_test.go
сгенерированный
поставляемый
Обычный файл
26
vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,26 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWaitForTimeout(t *testing.T) {
|
||||
c := make(chan error)
|
||||
go func() {
|
||||
err := WaitFor(3*time.Second, 1*time.Second, func() (bool, error) {
|
||||
return false, nil
|
||||
})
|
||||
c <- err
|
||||
}()
|
||||
|
||||
timeout := time.After(4 * time.Second)
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("timeout exceeded")
|
||||
case err := <-c:
|
||||
if err == nil {
|
||||
t.Errorf("expected timeout error; got %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
31
vendor/github.com/rsc/letsencrypt/vendor/vendor.json
сгенерированный
поставляемый
Обычный файл
31
vendor/github.com/rsc/letsencrypt/vendor/vendor.json
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"comment": "",
|
||||
"ignore": "",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "CHmdoMriAboKW2nHYSXo0yBizaE=",
|
||||
"path": "github.com/xenolf/lego/acme",
|
||||
"revision": "ca19a90028e242e878585941c2a27c8f3b3efc25",
|
||||
"revisionTime": "2016-03-28T16:28:34Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "jrheBzltbBE1frmNXQiu911T7dE=",
|
||||
"path": "gopkg.in/square/go-jose.v1",
|
||||
"revision": "40d457b439244b546f023d056628e5184136899b",
|
||||
"revisionTime": "2016-03-29T20:33:11Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "fX4KSC9E1oX9yRx20Zjb3rVJHn4=",
|
||||
"path": "gopkg.in/square/go-jose.v1/cipher",
|
||||
"revision": "40d457b439244b546f023d056628e5184136899b",
|
||||
"revisionTime": "2016-03-29T20:33:11Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "NxdXsIcLGuuX654ygsaOhoLsg6s=",
|
||||
"path": "gopkg.in/square/go-jose.v1/json",
|
||||
"revision": "40d457b439244b546f023d056628e5184136899b",
|
||||
"revisionTime": "2016-03-29T20:33:11Z"
|
||||
}
|
||||
],
|
||||
"rootPath": "rsc.io/letsencrypt"
|
||||
}
|
||||
Ссылка в новой задаче
Block a user