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