From 03f83f1f27223d696106b2d3838099d9de743834 Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:55:18 -0500 Subject: [PATCH 1/5] net/http: add ListenAndServeTLS and ServeTLS Adds Server.ServeTLS, Server.ListenAndServeTLS and the package-level ListenAndServeTLS, mirroring the standard library without HTTP/2 negotiation. They use Go's software crypto/tls, which the host target needs because its netdev has no TLS offload. quic-go/http3 also references http.ListenAndServeTLS. --- http/server.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/http/server.go b/http/server.go index 4f6510c..83e3550 100644 --- a/http/server.go +++ b/http/server.go @@ -3260,6 +3260,55 @@ func ListenAndServe(addr string, handler Handler) error { return server.ListenAndServe() } +// ServeTLS accepts HTTPS connections on l, wrapping each with a TLS server +// using the given certificate and key files or srv.TLSConfig. +// +// TINYGO: uses software crypto/tls. No HTTP/2 negotiation. +func (srv *Server) ServeTLS(l net.Listener, certFile, keyFile string) error { + config := srv.TLSConfig + if config == nil { + config = &tls.Config{} + } else { + config = config.Clone() + } + + configHasCert := len(config.Certificates) > 0 || config.GetCertificate != nil + if !configHasCert || certFile != "" || keyFile != "" { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + config.Certificates = []tls.Certificate{cert} + } + + tlsListener := tls.NewListener(l, config) + return srv.Serve(tlsListener) +} + +// ListenAndServeTLS listens on srv.Addr and serves HTTPS requests, loading the +// server certificate and key from certFile and keyFile. +func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { + if srv.shuttingDown() { + return ErrServerClosed + } + addr := srv.Addr + if addr == "" { + addr = ":https" + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + defer ln.Close() + return srv.ServeTLS(ln, certFile, keyFile) +} + +// ListenAndServeTLS is the HTTPS counterpart of ListenAndServe. +func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error { + server := &Server{Addr: addr, Handler: handler} + return server.ListenAndServeTLS(certFile, keyFile) +} + // onceCloseListener wraps a net.Listener, protecting it from // multiple Close calls. type onceCloseListener struct { From 1b48fb1d62262031abcb878a28cdeabbdf096cd7 Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:41:43 -0500 Subject: [PATCH 2/5] net: resolve ":https" to port 443 ResolveTCPAddr mapped ":http" but passed ":https" to strconv.Atoi, so http.ListenAndServeTLS with an empty address failed with a port parse error. --- tcpsock.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tcpsock.go b/tcpsock.go index 4e3c864..ad983ca 100644 --- a/tcpsock.go +++ b/tcpsock.go @@ -102,6 +102,8 @@ func ResolveTCPAddr(network, address string) (*TCPAddr, error) { switch address { case ":http": address = ":80" + case ":https": + address = ":443" } // TINYGO: Use netdev resolver From 21d936e1299e97a881587bdb30bc8104314d3e2b Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:41:43 -0500 Subject: [PATCH 3/5] net/http: set Request.TLS and close the listener once conn.serve runs the TLS handshake again and sets Request.TLS, which the TinyGo copy had removed with the rest of the TLS conn check. ListenAndServeTLS no longer closes the listener a second time, since Serve already closes it. Also drops the stale "Removed ServeTLS" note. --- http/server.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/http/server.go b/http/server.go index 83e3550..0e39c2b 100644 --- a/http/server.go +++ b/http/server.go @@ -7,7 +7,6 @@ // TINYGO: Removed ALPN protocol support // TINYGO: Removed some HTTP/2 support // TINYGO: Removed TimeoutHandler -// TINYGO: Removed ServeTLS and ListenAndServeTLS // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -1907,7 +1906,15 @@ func (c *conn) serve(ctx context.Context) { } }() - // TINYGO: Removed TLS conn check + // TINYGO: Removed the ALPN and HTTP/2 parts of the TLS conn check + if tlsConn, ok := c.rwc.(*tls.Conn); ok { + if err := tlsConn.HandshakeContext(ctx); err != nil { + c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) + return + } + c.tlsState = new(tls.ConnectionState) + *c.tlsState = tlsConn.ConnectionState() + } // HTTP/1.x from here on. @@ -3299,7 +3306,6 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { if err != nil { return err } - defer ln.Close() return srv.ServeTLS(ln, certFile, keyFile) } From fe98048b6bb575e522f4b1727d969d0d7dc3691b Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Wed, 28 Oct 2026 08:53:37 -0500 Subject: [PATCH 4/5] net/http: bound the TLS handshake with the server timeouts The handshake in conn.serve had no deadline, so a stalled client could hold the connection forever. Set read and write deadlines from tlsHandshakeTimeout before the handshake and clear them after, as upstream net/http does. --- http/server.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/http/server.go b/http/server.go index 0e39c2b..1f7b718 100644 --- a/http/server.go +++ b/http/server.go @@ -1908,10 +1908,21 @@ func (c *conn) serve(ctx context.Context) { // TINYGO: Removed the ALPN and HTTP/2 parts of the TLS conn check if tlsConn, ok := c.rwc.(*tls.Conn); ok { + tlsTO := c.server.tlsHandshakeTimeout() + if tlsTO > 0 { + dl := time.Now().Add(tlsTO) + c.rwc.SetReadDeadline(dl) + c.rwc.SetWriteDeadline(dl) + } if err := tlsConn.HandshakeContext(ctx); err != nil { c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) return } + // Restore Conn-level deadlines. + if tlsTO > 0 { + c.rwc.SetReadDeadline(time.Time{}) + c.rwc.SetWriteDeadline(time.Time{}) + } c.tlsState = new(tls.ConnectionState) *c.tlsState = tlsConn.ConnectionState() } From cad00215381c172f2ee46fb1043b2c2ac3668e4c Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Wed, 28 Oct 2026 09:35:16 -0500 Subject: [PATCH 5/5] net/http: refuse ServeTLS when crypto/tls has no software handshake On targets where crypto/tls is the netdev stub, NewListener returns plain conns, so ServeTLS with TLSConfig.Certificates served unencrypted HTTP without an error. Probe crypto/tls once with a closed pipe. A real handshake fails on EOF while the stub returns nil, and in that case ServeTLS and ListenAndServeTLS now return an error. --- http/server.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/http/server.go b/http/server.go index 1f7b718..0f4b386 100644 --- a/http/server.go +++ b/http/server.go @@ -3283,6 +3283,9 @@ func ListenAndServe(addr string, handler Handler) error { // // TINYGO: uses software crypto/tls. No HTTP/2 negotiation. func (srv *Server) ServeTLS(l net.Listener, certFile, keyFile string) error { + if !softwareTLS() { + return errNoSoftwareTLS + } config := srv.TLSConfig if config == nil { config = &tls.Config{} @@ -3309,6 +3312,9 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { if srv.shuttingDown() { return ErrServerClosed } + if !softwareTLS() { + return errNoSoftwareTLS + } addr := srv.Addr if addr == "" { addr = ":https" @@ -3326,6 +3332,19 @@ func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error { return server.ListenAndServeTLS(certFile, keyFile) } +// errNoSoftwareTLS keeps ServeTLS from serving plain HTTP on targets whose +// crypto/tls is the netdev stub. +var errNoSoftwareTLS = errors.New("http: TLS server needs software crypto/tls, which this target lacks") + +// softwareTLS reports whether crypto/tls runs a real handshake. TinyGo's +// netdev stub returns from Handshake without reading the conn. +func softwareTLS() bool { + c, s := net.Pipe() + c.Close() + defer s.Close() + return tls.Server(s, &tls.Config{}).Handshake() != nil +} + // onceCloseListener wraps a net.Listener, protecting it from // multiple Close calls. type onceCloseListener struct {