From 8f91c777559748fa6e857d9fc1f4ae079a532813 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 3 Oct 2016 16:03:15 -0400 Subject: Adding ability to serve TLS directly from Mattermost server (#4119) --- vendor/github.com/rsc/letsencrypt/LICENSE | 27 + vendor/github.com/rsc/letsencrypt/README | 152 +++++ vendor/github.com/rsc/letsencrypt/lets.go | 757 +++++++++++++++++++++ .../vendor/github.com/xenolf/lego/LICENSE | 21 + .../github.com/xenolf/lego/acme/challenges.go | 16 + .../vendor/github.com/xenolf/lego/acme/client.go | 638 +++++++++++++++++ .../github.com/xenolf/lego/acme/client_test.go | 198 ++++++ .../vendor/github.com/xenolf/lego/acme/crypto.go | 323 +++++++++ .../github.com/xenolf/lego/acme/crypto_test.go | 93 +++ .../vendor/github.com/xenolf/lego/acme/error.go | 73 ++ .../vendor/github.com/xenolf/lego/acme/http.go | 117 ++++ .../github.com/xenolf/lego/acme/http_challenge.go | 41 ++ .../xenolf/lego/acme/http_challenge_server.go | 79 +++ .../xenolf/lego/acme/http_challenge_test.go | 57 ++ .../github.com/xenolf/lego/acme/http_test.go | 100 +++ .../vendor/github.com/xenolf/lego/acme/jws.go | 107 +++ .../vendor/github.com/xenolf/lego/acme/messages.go | 115 ++++ .../vendor/github.com/xenolf/lego/acme/provider.go | 28 + .../xenolf/lego/acme/tls_sni_challenge.go | 73 ++ .../xenolf/lego/acme/tls_sni_challenge_server.go | 62 ++ .../xenolf/lego/acme/tls_sni_challenge_test.go | 65 ++ .../vendor/github.com/xenolf/lego/acme/utils.go | 29 + .../github.com/xenolf/lego/acme/utils_test.go | 26 + .../github.com/rsc/letsencrypt/vendor/vendor.json | 31 + 24 files changed, 3228 insertions(+) create mode 100644 vendor/github.com/rsc/letsencrypt/LICENSE create mode 100644 vendor/github.com/rsc/letsencrypt/README create mode 100644 vendor/github.com/rsc/letsencrypt/lets.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/LICENSE create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/challenges.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client_test.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto_test.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/error.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_server.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_test.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_test.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/jws.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/messages.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/provider.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_test.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils_test.go create mode 100644 vendor/github.com/rsc/letsencrypt/vendor/vendor.json (limited to 'vendor/github.com/rsc') diff --git a/vendor/github.com/rsc/letsencrypt/LICENSE b/vendor/github.com/rsc/letsencrypt/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/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. diff --git a/vendor/github.com/rsc/letsencrypt/README b/vendor/github.com/rsc/letsencrypt/README new file mode 100644 index 000000000..98a875f37 --- /dev/null +++ b/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 { ... } diff --git a/vendor/github.com/rsc/letsencrypt/lets.go b/vendor/github.com/rsc/letsencrypt/lets.go new file mode 100644 index 000000000..c0168b56a --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/LICENSE b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/LICENSE new file mode 100644 index 000000000..17460b716 --- /dev/null +++ b/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. diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/challenges.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/challenges.go new file mode 100644 index 000000000..857900507 --- /dev/null +++ b/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") +) diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client.go new file mode 100644 index 000000000..16e4cbe00 --- /dev/null +++ b/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 + } + } +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client_test.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/client_test.go new file mode 100644 index 000000000..e309554f3 --- /dev/null +++ b/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 } diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto.go new file mode 100644 index 000000000..fc20442f7 --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto_test.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/crypto_test.go new file mode 100644 index 000000000..d2fc5088b --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/error.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/error.go new file mode 100644 index 000000000..b32561a3a --- /dev/null +++ b/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} +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http.go new file mode 100644 index 000000000..410aead6d --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge.go new file mode 100644 index 000000000..95cb1fd81 --- /dev/null +++ b/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}) +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_server.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_server.go new file mode 100644 index 000000000..42541380c --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_test.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_challenge_test.go new file mode 100644 index 000000000..fdd8f4d27 --- /dev/null +++ b/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) + } +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_test.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/http_test.go new file mode 100644 index 000000000..33a48a331 --- /dev/null +++ b/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) + } +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/jws.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/jws.go new file mode 100644 index 000000000..8435d0cfc --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/messages.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/messages.go new file mode 100644 index 000000000..d1fac9200 --- /dev/null +++ b/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:"-"` +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/provider.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/provider.go new file mode 100644 index 000000000..d177ff07a --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go new file mode 100644 index 000000000..f184b17a5 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go new file mode 100644 index 000000000..faaf16f6b --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_test.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/tls_sni_challenge_test.go new file mode 100644 index 000000000..3aec74565 --- /dev/null +++ b/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) + } +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils.go new file mode 100644 index 000000000..2fa0db304 --- /dev/null +++ b/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) + } +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils_test.go b/vendor/github.com/rsc/letsencrypt/vendor/github.com/xenolf/lego/acme/utils_test.go new file mode 100644 index 000000000..158af4116 --- /dev/null +++ b/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) + } + } +} diff --git a/vendor/github.com/rsc/letsencrypt/vendor/vendor.json b/vendor/github.com/rsc/letsencrypt/vendor/vendor.json new file mode 100644 index 000000000..8a4241102 --- /dev/null +++ b/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" +} -- cgit v1.2.3-1-g7c22