1package tlsalpn01
2
3import (
4	"crypto/tls"
5	"errors"
6	"fmt"
7	"net"
8	"net/http"
9	"strings"
10
11	"github.com/go-acme/lego/v4/log"
12)
13
14const (
15	// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
16	ACMETLS1Protocol = "acme-tls/1"
17
18	// defaultTLSPort is the port that the ProviderServer will default to
19	// when no other port is provided.
20	defaultTLSPort = "443"
21)
22
23// ProviderServer implements ChallengeProvider for `TLS-ALPN-01` challenge.
24// It may be instantiated without using the NewProviderServer
25// if you want only to use the default values.
26type ProviderServer struct {
27	iface    string
28	port     string
29	listener net.Listener
30}
31
32// NewProviderServer creates a new ProviderServer on the selected interface and port.
33// Setting iface and / or port to an empty string will make the server fall back to
34// the "any" interface and port 443 respectively.
35func NewProviderServer(iface, port string) *ProviderServer {
36	return &ProviderServer{iface: iface, port: port}
37}
38
39func (s *ProviderServer) GetAddress() string {
40	return net.JoinHostPort(s.iface, s.port)
41}
42
43// Present generates a certificate with a SHA-256 digest of the keyAuth provided
44// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
45func (s *ProviderServer) Present(domain, token, keyAuth string) error {
46	if s.port == "" {
47		// Fallback to port 443 if the port was not provided.
48		s.port = defaultTLSPort
49	}
50
51	// Generate the challenge certificate using the provided keyAuth and domain.
52	cert, err := ChallengeCert(domain, keyAuth)
53	if err != nil {
54		return err
55	}
56
57	// Place the generated certificate with the extension into the TLS config
58	// so that it can serve the correct details.
59	tlsConf := new(tls.Config)
60	tlsConf.Certificates = []tls.Certificate{*cert}
61
62	// We must set that the `acme-tls/1` application level protocol is supported
63	// so that the protocol negotiation can succeed. Reference:
64	// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
65	tlsConf.NextProtos = []string{ACMETLS1Protocol}
66
67	// Create the listener with the created tls.Config.
68	s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
69	if err != nil {
70		return fmt.Errorf("could not start HTTPS server for challenge: %w", err)
71	}
72
73	// Shut the server down when we're finished.
74	go func() {
75		err := http.Serve(s.listener, nil)
76		if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
77			log.Println(err)
78		}
79	}()
80
81	return nil
82}
83
84// CleanUp closes the HTTPS server.
85func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
86	if s.listener == nil {
87		return nil
88	}
89
90	// Server was created, close it.
91	if err := s.listener.Close(); err != nil && errors.Is(err, http.ErrServerClosed) {
92		return err
93	}
94
95	return nil
96}
97