]> Sergey Matveev's repositories - tofuproxy.git/blob - tlsauth.go
bf87b2c499602d261584227d4c41d671f6b7d213
[tofuproxy.git] / tlsauth.go
1 /*
2 tofuproxy -- HTTP proxy with TLS certificates management
3 Copyright (C) 2021 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
18 package tofuproxy
19
20 import (
21         "bytes"
22         "crypto/tls"
23         "crypto/x509"
24         "errors"
25         "fmt"
26         "log"
27         "os"
28         "os/exec"
29         "path/filepath"
30         "strconv"
31         "strings"
32
33         "go.cypherpunks.ru/ucspi"
34         "go.stargrave.org/tofuproxy/caches"
35         "go.stargrave.org/tofuproxy/fifos"
36 )
37
38 var CCerts string
39
40 type ClientCertificateGetter struct {
41         host string
42         auth bool
43 }
44
45 func (g *ClientCertificateGetter) get(
46         cri *tls.CertificateRequestInfo,
47 ) (*tls.Certificate, error) {
48         caches.TLSAuthCacheM.RLock()
49         tlsCert := caches.TLSAuthCache[g.host]
50         caches.TLSAuthCacheM.RUnlock()
51         if tlsCert != nil {
52                 return tlsCert, nil
53         }
54         sigSchemes := make([]string, 0, len(cri.SignatureSchemes))
55         for _, ss := range cri.SignatureSchemes {
56                 sigSchemes = append(sigSchemes, ss.String())
57         }
58         var b bytes.Buffer
59         b.WriteString(fmt.Sprintf(`
60 tk_setPalette grey
61 wm title . "TLS client authentication: %s"
62
63 set lb [listbox .lb]
64 .lb insert end ""
65 grid .lb
66
67 proc login {} {
68     global lb
69     puts [$lb get active]
70     exit
71 }
72
73 button .login -text "Use" -command login
74 grid .login
75
76 bind . <KeyPress> {switch -exact %%K {
77     q {exit 0} ; # reject once
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         for i, ent := range ents {
104                 p := filepath.Join(CCerts, ent.Name())
105                 _, cert, err := ucspi.CertificateFromFile(p)
106                 if err != nil {
107                         log.Fatalln(err)
108                 }
109                 prv, err := ucspi.PrivateKeyFromFile(p)
110                 if err != nil {
111                         log.Fatalln(err)
112                 }
113                 certs = append(certs, cert)
114                 tlsCerts = append(tlsCerts, &tls.Certificate{
115                         Certificate: [][]byte{cert.Raw},
116                         PrivateKey:  prv,
117                 })
118                 b.WriteString(fmt.Sprintf(".lb insert end \"%d: %s\"\n", i, cert.Subject))
119         }
120         // ioutil.WriteFile("/tmp/tls-auth-dialog.tcl", b.Bytes(), 0666)
121         cmd := exec.Command(CmdWish)
122         cmd.Stdin = &b
123         out, err := cmd.Output()
124         if err != nil {
125                 return nil, err
126         }
127         lines := strings.Split(string(out), "\n")
128         if len(lines) < 1 {
129                 return nil, errors.New("invalid output from authentication form")
130         }
131         t := strings.Split(lines[0], ":")[0]
132         i, err := strconv.Atoi(t)
133         if err != nil {
134                 return &tls.Certificate{}, nil
135         }
136         fifos.LogTLSAuth <- fmt.Sprintf("%s\t%s", g.host, certs[i].Subject)
137         caches.TLSAuthCacheM.Lock()
138         caches.TLSAuthCache[g.host] = tlsCerts[i]
139         caches.TLSAuthCacheM.Unlock()
140         g.auth = true
141         return tlsCerts[i], nil
142 }