2 tofuproxy -- HTTP proxy with TLS certificates management
3 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
32 "go.cypherpunks.ru/ucspi"
33 "go.stargrave.org/tofuproxy/fifos"
37 TLSNextProtoS = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
38 CACert *x509.Certificate
39 CAPrv crypto.PrivateKey
40 sessionCache = tls.NewLRUClientSessionCache(1024)
45 func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
46 if req.Method != http.MethodConnect {
50 hj, ok := w.(http.Hijacker)
52 log.Fatalln("no hijacking")
54 conn, _, err := hj.Hijack()
59 conn.Write([]byte(fmt.Sprintf(
62 http.StatusOK, http.StatusText(http.StatusOK),
64 host := strings.Split(req.Host, ":")[0]
66 keypair, ok := hostCerts[host]
67 if !ok || !keypair.cert.NotAfter.After(time.Now().Add(time.Hour)) {
68 keypair = newKeypair(host, CACert, CAPrv)
69 hostCerts[host] = keypair
72 tlsConn := tls.Server(conn, &tls.Config{
73 Certificates: []tls.Certificate{{
74 Certificate: [][]byte{keypair.cert.Raw},
75 PrivateKey: keypair.prv,
78 if err = tlsConn.Handshake(); err != nil {
79 log.Printf("TLS error %s: %+v\n", host, err)
83 Handler: &HTTPSHandler{host: req.Host},
84 TLSNextProto: TLSNextProtoS,
86 err = srv.Serve(&SingleListener{conn: tlsConn})
88 if _, ok := err.(AlreadyAccepted); !ok {
89 log.Printf("TLS serve error %s: %+v\n", host, err)
95 type HTTPSHandler struct {
99 func (h *HTTPSHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
100 req.URL.Scheme = "https"
101 req.URL.Host = h.host
105 func dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
106 host := strings.TrimSuffix(addr, ":443")
108 VerifyPeerCertificate: func(
110 verifiedChains [][]*x509.Certificate,
112 return verifyCert(host, nil, rawCerts, verifiedChains)
114 ClientSessionCache: sessionCache,
115 NextProtos: []string{"h2", "http/1.1"},
117 conn, dialErr := tls.Dial(network, addr, &cfg)
119 if _, ok := dialErr.(ErrRejected); ok {
122 cfg.InsecureSkipVerify = true
123 cfg.VerifyPeerCertificate = func(
125 verifiedChains [][]*x509.Certificate,
127 return verifyCert(host, dialErr, rawCerts, verifiedChains)
130 conn, err = tls.Dial(network, addr, &cfg)
132 fifos.SinkErr <- fmt.Sprintf("%s\t%s", addr, dialErr.Error())
136 connState := conn.ConnectionState()
137 if connState.DidResume {
138 fifos.SinkTLS <- fmt.Sprintf(
140 strings.TrimSuffix(addr, ":443"),
141 ucspi.TLSVersion(connState.Version),
142 tls.CipherSuiteName(connState.CipherSuite),
143 spkiHash(connState.PeerCertificates[0]),
144 connState.NegotiatedProtocol,