Add TLS-ALPN-01 support to challtestserv (#3758)
Adds support for TLS-ALPN-01 challenges to `test/challtestsrv`. Fixes #3757.
This commit is contained in:
parent
2dadd5e09a
commit
6692dd5628
|
|
@ -32,6 +32,10 @@ type ChallSrv struct {
|
|||
// dnsOne is a map of DNS host values to key authorizations used for DNS-01
|
||||
// responses
|
||||
dnsOne map[string][]string
|
||||
|
||||
// tlsALPNOne is a map of token values to key authorizations used for TLS-ALPN-01
|
||||
// responses
|
||||
tlsALPNOne map[string]string
|
||||
}
|
||||
|
||||
// Config holds challenge server configuration
|
||||
|
|
@ -41,16 +45,18 @@ type Config struct {
|
|||
HTTPOneAddrs []string
|
||||
// DNSOneAddrs are the DNS-01 challenge server bind addresses/ports
|
||||
DNSOneAddrs []string
|
||||
// TLSALPNOneAddrs are the TLS-ALPN-01 challenge server bind addresses/ports
|
||||
TLSALPNOneAddrs []string
|
||||
}
|
||||
|
||||
// validate checks that a challenge server Config is valid. To be valid it must
|
||||
// specify a bind address for at least one challenge type. If there is no
|
||||
// configured log in the config a default is provided.
|
||||
func (c *Config) validate() error {
|
||||
// There needs to be at least one challenge time with a bind address
|
||||
if len(c.HTTPOneAddrs) < 1 && len(c.DNSOneAddrs) < 1 {
|
||||
// There needs to be at least one challenge type with a bind address
|
||||
if len(c.HTTPOneAddrs) < 1 && len(c.DNSOneAddrs) < 1 && len(c.TLSALPNOneAddrs) < 1 {
|
||||
return fmt.Errorf(
|
||||
"config must specify at least one HTTPOneAddrs entry or one DNSOneAddrs entry")
|
||||
"config must specify at least one HTTPOneAddrs entry, one DNSOneAddrs entry, or one TLSALPNOneAddrs entry")
|
||||
}
|
||||
// If there is no configured log make a default with a prefix
|
||||
if c.Log == nil {
|
||||
|
|
@ -69,8 +75,9 @@ func New(config Config) (*ChallSrv, error) {
|
|||
challSrv := &ChallSrv{
|
||||
log: config.Log,
|
||||
|
||||
httpOne: make(map[string]string),
|
||||
dnsOne: make(map[string][]string),
|
||||
httpOne: make(map[string]string),
|
||||
dnsOne: make(map[string][]string),
|
||||
tlsALPNOne: make(map[string]string),
|
||||
}
|
||||
|
||||
// If there are HTTP-01 addresses configured, create HTTP-01 servers
|
||||
|
|
@ -86,6 +93,12 @@ func New(config Config) (*ChallSrv, error) {
|
|||
dnsOneServer(address, challSrv.dnsHandler)...)
|
||||
}
|
||||
|
||||
// If there are TLS-ALPN-01 addresses configured, create TLS-ALPN-01 servers
|
||||
for _, address := range config.TLSALPNOneAddrs {
|
||||
challSrv.log.Printf("Creating TLS-ALPN-01 challenge server on %s\n", address)
|
||||
challSrv.servers = append(challSrv.servers, tlsALPNOneServer(address, challSrv))
|
||||
}
|
||||
|
||||
return challSrv, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package challtestsrv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/va"
|
||||
)
|
||||
|
||||
// AddTLSALPNChallenge adds a new TLS-ALPN-01 key authorization for the given host
|
||||
func (s *ChallSrv) AddTLSALPNChallenge(host, content string) {
|
||||
s.challMu.Lock()
|
||||
defer s.challMu.Unlock()
|
||||
s.tlsALPNOne[host] = content
|
||||
}
|
||||
|
||||
// DeleteTLSALPNChallenge deletes the key authorization for a given host
|
||||
func (s *ChallSrv) DeleteTLSALPNChallenge(host string) {
|
||||
s.challMu.Lock()
|
||||
defer s.challMu.Unlock()
|
||||
if _, ok := s.tlsALPNOne[host]; ok {
|
||||
delete(s.tlsALPNOne, host)
|
||||
}
|
||||
}
|
||||
|
||||
// GetTLSALPNChallenge checks the s.tlsALPNOne map for the given host.
|
||||
// If it is present it returns the key authorization and true, if not
|
||||
// it returns an empty string and false.
|
||||
func (s *ChallSrv) GetTLSALPNChallenge(host string) (string, bool) {
|
||||
s.challMu.RLock()
|
||||
defer s.challMu.RUnlock()
|
||||
content, present := s.tlsALPNOne[host]
|
||||
return content, present
|
||||
}
|
||||
|
||||
func (s *ChallSrv) ServeChallengeCertFunc(k *ecdsa.PrivateKey) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if len(hello.SupportedProtos) != 1 || hello.SupportedProtos[0] != va.ACMETLS1Protocol {
|
||||
return nil, fmt.Errorf("ALPN failed, ClientHelloInfo.SupportedProtos: %s", hello.SupportedProtos)
|
||||
}
|
||||
|
||||
ka, found := s.GetTLSALPNChallenge(hello.ServerName)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unknown ClientHelloInfo.ServerName: %s", hello.ServerName)
|
||||
}
|
||||
|
||||
kaHash := sha256.Sum256([]byte(ka))
|
||||
extValue, err := asn1.Marshal(kaHash[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed marshalling hash OCTET STRING: %s", err)
|
||||
}
|
||||
certTmpl := x509.Certificate{
|
||||
DNSNames: []string{hello.ServerName},
|
||||
Extensions: []pkix.Extension{
|
||||
{
|
||||
Id: va.IdPeAcmeIdentifierV1,
|
||||
Critical: true,
|
||||
Value: extValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, &certTmpl, k.Public(), k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed creating challenge certificate: %s", err)
|
||||
}
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: k,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type challTLSServer struct {
|
||||
*http.Server
|
||||
}
|
||||
|
||||
func (c challTLSServer) Shutdown() error {
|
||||
return c.Server.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
func (c challTLSServer) ListenAndServe() error {
|
||||
// We never want to serve a plain cert so leave certFile and keyFile
|
||||
// empty. If we don't know the SNI name/ALPN fails the handshake will
|
||||
// fail anyway.
|
||||
return c.Server.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
||||
func tlsALPNOneServer(address string, challSrv *ChallSrv) challengeServer {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv := &http.Server{
|
||||
Addr: address,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: []string{va.ACMETLS1Protocol},
|
||||
GetCertificate: challSrv.ServeChallengeCertFunc(key),
|
||||
},
|
||||
}
|
||||
srv.SetKeepAlivesEnabled(false)
|
||||
return challTLSServer{srv}
|
||||
}
|
||||
Loading…
Reference in New Issue