]> Sergey Matveev's repositories - tofuproxy.git/blob - tls.go
Compatibility with raw IPv6 addresses as hostname
[tofuproxy.git] / tls.go
1 /*
2 tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
3              manager, WARC/geminispace browser
4 Copyright (C) 2021-2022 Sergey Matveev <stargrave@stargrave.org>
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, version 3 of the License.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package tofuproxy
20
21 import (
22         "crypto"
23         "crypto/tls"
24         "crypto/x509"
25         "fmt"
26         "log"
27         "net/http"
28         "time"
29
30         ttls "go.stargrave.org/tofuproxy/tls"
31 )
32
33 var (
34         TLSNextProtoS = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
35         CACert        *x509.Certificate
36         CAPrv         crypto.PrivateKey
37         sessionCache  = tls.NewLRUClientSessionCache(1024)
38 )
39
40 type Handler struct{}
41
42 func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
43         if req.Method != http.MethodConnect {
44                 roundTrip(w, req)
45                 return
46         }
47         hj, ok := w.(http.Hijacker)
48         if !ok {
49                 log.Fatalln("no hijacking")
50         }
51         conn, _, err := hj.Hijack()
52         if err != nil {
53                 log.Fatalln(err)
54         }
55         defer conn.Close()
56         conn.Write([]byte(fmt.Sprintf(
57                 "%s %d %s\r\n\r\n",
58                 req.Proto,
59                 http.StatusOK, http.StatusText(http.StatusOK),
60         )))
61         host, _, _ := ttls.SplitHostPort(req.Host)
62         hostCertsM.Lock()
63         keypair, ok := hostCerts[host]
64         if !ok || !keypair.cert.NotAfter.After(time.Now().Add(time.Hour)) {
65                 keypair = newKeypair(host, CACert, CAPrv)
66                 hostCerts[host] = keypair
67         }
68         hostCertsM.Unlock()
69         tlsConn := tls.Server(conn, &tls.Config{
70                 Certificates: []tls.Certificate{{
71                         Certificate: [][]byte{keypair.cert.Raw},
72                         PrivateKey:  keypair.prv,
73                 }},
74         })
75         if err = tlsConn.Handshake(); err != nil {
76                 log.Printf("TLS error %s: %+v\n", host, err)
77                 return
78         }
79         srv := http.Server{
80                 Handler:      &HTTPSHandler{host: req.Host},
81                 TLSNextProto: TLSNextProtoS,
82         }
83         err = srv.Serve(&SingleListener{conn: tlsConn})
84         if err != nil {
85                 if _, ok := err.(AlreadyAccepted); !ok {
86                         log.Printf("TLS serve error %s: %+v\n", host, err)
87                         return
88                 }
89         }
90 }
91
92 type HTTPSHandler struct {
93         host string
94 }
95
96 func (h *HTTPSHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
97         req.URL.Scheme = "https"
98         req.URL.Host = h.host
99         roundTrip(w, req)
100 }