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