]> Sergey Matveev's repositories - tofuproxy.git/blob - tls/tlsauth.go
Download link for 0.6.0 release
[tofuproxy.git] / tls / tlsauth.go
1 // tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
2 //              manager, WARC/geminispace browser
3 // Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
4 //
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.
8 //
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.
13 //
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/>.
16
17 package tofuproxy
18
19 import (
20         "bytes"
21         "crypto/tls"
22         "crypto/x509"
23         "errors"
24         "fmt"
25         "log"
26         "os"
27         "os/exec"
28         "path/filepath"
29         "strconv"
30         "strings"
31
32         "go.cypherpunks.ru/ucspi"
33         "go.stargrave.org/tofuproxy/caches"
34         "go.stargrave.org/tofuproxy/fifos"
35 )
36
37 var CCerts string
38
39 type ClientCertificateGetter struct {
40         host string
41         auth bool
42 }
43
44 func (g *ClientCertificateGetter) get(
45         cri *tls.CertificateRequestInfo,
46 ) (*tls.Certificate, error) {
47         caches.TLSAuthCacheM.RLock()
48         tlsCert := caches.TLSAuthCache[g.host]
49         caches.TLSAuthCacheM.RUnlock()
50         if tlsCert != nil {
51                 return tlsCert, nil
52         }
53         sigSchemes := make([]string, 0, len(cri.SignatureSchemes))
54         for _, ss := range cri.SignatureSchemes {
55                 sigSchemes = append(sigSchemes, ss.String())
56         }
57         var b bytes.Buffer
58         fmt.Fprintf(&b, `
59 tk_setPalette grey
60 wm title . "TLS client authentication: %s"
61
62 set lb [listbox .lb]
63 .lb insert end ""
64 grid .lb
65
66 proc login {} {
67     global lb
68     puts [$lb get active]
69     exit
70 }
71
72 button .login -text "Use" -command login
73 grid .login
74
75 bind . <KeyPress> {switch -exact %%K {
76     q {exit 0} ; # reject once
77     n {puts "0:NONE" ; exit}
78     l login
79 }}
80
81 label .lTLSVersion -text "TLS version: %s"
82 grid .lTLSVersion
83
84 set sigSchemeRow 0
85 foreach sigScheme {%s} {
86     label .lSignatureScheme$sigSchemeRow -text "Signature scheme: $sigScheme"
87     grid .lSignatureScheme$sigSchemeRow
88     incr sigSchemeRow
89 }
90
91 `,
92                 g.host,
93                 ucspi.TLSVersion(cri.Version),
94                 strings.Join(sigSchemes, " "),
95         )
96
97         ents, err := os.ReadDir(CCerts)
98         if err != nil {
99                 log.Fatalln(err)
100         }
101         certs := make([]*x509.Certificate, 0, len(ents))
102         tlsCerts := make([]*tls.Certificate, 0, len(ents))
103         b.WriteString(".lb insert end \"0: NONE\"\n")
104         certs = append(certs, nil)
105         tlsCerts = append(tlsCerts, nil)
106         for i, ent := range ents {
107                 p := filepath.Join(CCerts, ent.Name())
108                 _, cert, err := ucspi.CertificateFromFile(p)
109                 if err != nil {
110                         log.Fatalln(err)
111                 }
112                 prv, err := ucspi.PrivateKeyFromFile(p)
113                 if err != nil {
114                         log.Fatalln(err)
115                 }
116                 certs = append(certs, cert)
117                 tlsCerts = append(tlsCerts, &tls.Certificate{
118                         Certificate: [][]byte{cert.Raw},
119                         PrivateKey:  prv,
120                 })
121                 fmt.Fprintf(&b, ".lb insert end \"%d: %s\"\n", i+1, cert.Subject)
122         }
123         // os.WriteFile("/tmp/tls-auth-dialog.tcl", b.Bytes(), 0666)
124         cmd := exec.Command(CmdWish)
125         cmd.Stdin = &b
126         out, err := cmd.Output()
127         if err != nil {
128                 return nil, err
129         }
130         lines := strings.Split(string(out), "\n")
131         if len(lines) < 1 {
132                 return nil, errors.New("invalid output from authentication form")
133         }
134         t := strings.Split(lines[0], ":")[0]
135         i, err := strconv.Atoi(t)
136         if err != nil {
137                 return &tls.Certificate{}, nil
138         }
139         if i == 0 {
140                 dummy := tls.Certificate{}
141                 caches.TLSAuthCacheM.Lock()
142                 caches.TLSAuthCache[g.host] = &dummy
143                 caches.TLSAuthCacheM.Unlock()
144                 return &dummy, nil
145         }
146         fifos.LogTLSAuth <- fmt.Sprintf("%s\t%s", g.host, certs[i].Subject)
147         caches.TLSAuthCacheM.Lock()
148         caches.TLSAuthCache[g.host] = tlsCerts[i]
149         caches.TLSAuthCacheM.Unlock()
150         g.auth = true
151         return tlsCerts[i], nil
152 }