/* tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates manager, WARC/Gemini browser Copyright (C) 2021 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package tofuproxy import ( "bytes" "crypto/tls" "crypto/x509" "errors" "fmt" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "go.cypherpunks.ru/ucspi" "go.stargrave.org/tofuproxy/caches" "go.stargrave.org/tofuproxy/fifos" ) var CCerts string type ClientCertificateGetter struct { host string auth bool } func (g *ClientCertificateGetter) get( cri *tls.CertificateRequestInfo, ) (*tls.Certificate, error) { caches.TLSAuthCacheM.RLock() tlsCert := caches.TLSAuthCache[g.host] caches.TLSAuthCacheM.RUnlock() if tlsCert != nil { return tlsCert, nil } sigSchemes := make([]string, 0, len(cri.SignatureSchemes)) for _, ss := range cri.SignatureSchemes { sigSchemes = append(sigSchemes, ss.String()) } var b bytes.Buffer b.WriteString(fmt.Sprintf(` tk_setPalette grey wm title . "TLS client authentication: %s" set lb [listbox .lb] .lb insert end "" grid .lb proc login {} { global lb puts [$lb get active] exit } button .login -text "Use" -command login grid .login bind . {switch -exact %%K { q {exit 0} ; # reject once n {puts "0:NONE" ; exit} l login }} label .lTLSVersion -text "TLS version: %s" grid .lTLSVersion set sigSchemeRow 0 foreach sigScheme {%s} { label .lSignatureScheme$sigSchemeRow -text "Signature scheme: $sigScheme" grid .lSignatureScheme$sigSchemeRow incr sigSchemeRow } `, g.host, ucspi.TLSVersion(cri.Version), strings.Join(sigSchemes, " "), )) ents, err := os.ReadDir(CCerts) if err != nil { log.Fatalln(err) } certs := make([]*x509.Certificate, 0, len(ents)) tlsCerts := make([]*tls.Certificate, 0, len(ents)) b.WriteString(".lb insert end \"0: NONE\"\n") certs = append(certs, nil) tlsCerts = append(tlsCerts, nil) for i, ent := range ents { p := filepath.Join(CCerts, ent.Name()) _, cert, err := ucspi.CertificateFromFile(p) if err != nil { log.Fatalln(err) } prv, err := ucspi.PrivateKeyFromFile(p) if err != nil { log.Fatalln(err) } certs = append(certs, cert) tlsCerts = append(tlsCerts, &tls.Certificate{ Certificate: [][]byte{cert.Raw}, PrivateKey: prv, }) b.WriteString(fmt.Sprintf(".lb insert end \"%d: %s\"\n", i+1, cert.Subject)) } // ioutil.WriteFile("/tmp/tls-auth-dialog.tcl", b.Bytes(), 0666) cmd := exec.Command(CmdWish) cmd.Stdin = &b out, err := cmd.Output() if err != nil { return nil, err } lines := strings.Split(string(out), "\n") if len(lines) < 1 { return nil, errors.New("invalid output from authentication form") } t := strings.Split(lines[0], ":")[0] i, err := strconv.Atoi(t) if err != nil { return &tls.Certificate{}, nil } if i == 0 { dummy := tls.Certificate{} caches.TLSAuthCacheM.Lock() caches.TLSAuthCache[g.host] = &dummy caches.TLSAuthCacheM.Unlock() return &dummy, nil } fifos.LogTLSAuth <- fmt.Sprintf("%s\t%s", g.host, certs[i].Subject) caches.TLSAuthCacheM.Lock() caches.TLSAuthCache[g.host] = tlsCerts[i] caches.TLSAuthCacheM.Unlock() g.auth = true return tlsCerts[i], nil }