/*
+tofuproxy -- HTTP proxy with TLS certificates management
Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package main
+package tofuproxy
import (
"bytes"
+ "crypto/sha256"
"crypto/x509"
+ "encoding/hex"
"encoding/pem"
"fmt"
- "io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
+ "sync"
"go.cypherpunks.ru/ucspi"
+ "go.stargrave.org/tofuproxy/fifos"
)
+var (
+ CmdCerttool = "certtool"
+ CmdWish = "wish8.6"
+
+ Certs string
+ accepted = make(map[string]string)
+ acceptedM sync.RWMutex
+ rejected = make(map[string]string)
+ rejectedM sync.RWMutex
+ VerifyM sync.Mutex
+)
+
+func spkiHash(cert *x509.Certificate) string {
+ hsh := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
+ return hex.EncodeToString(hsh[:])
+}
+
+func acceptedAdd(addr, h string) {
+ acceptedM.Lock()
+ accepted[addr] = h
+ acceptedM.Unlock()
+}
+
+func rejectedAdd(addr, h string) {
+ rejectedM.Lock()
+ rejected[addr] = h
+ rejectedM.Unlock()
+}
+
+type ErrRejected struct {
+ addr string
+}
+
+func (err ErrRejected) Error() string { return err.addr + " was rejected" }
+
func certInfo(certRaw []byte) string {
- cmd := exec.Command("certtool", "--certificate-info", "--inder")
+ cmd := exec.Command(CmdCerttool, "--certificate-info", "--inder")
cmd.Stdin = bytes.NewReader(certRaw)
out, err := cmd.Output()
if err != nil {
}
}
certTheirHash := spkiHash(certTheir)
+ VerifyM.Lock()
+ defer VerifyM.Unlock()
acceptedM.RLock()
certOurHash := accepted[host]
acceptedM.RUnlock()
daneExists, daneMatched := dane(host, certTheir)
if daneExists {
if daneMatched {
- sinkCert <- fmt.Sprintf("DANE\t%s\tmatched", host)
+ fifos.SinkCert <- fmt.Sprintf("DANE\t%s\tmatched", host)
} else {
- sinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", host)
+ fifos.SinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", host)
}
}
- fn := filepath.Join(*certs, host)
+ fn := filepath.Join(Certs, host)
certsOur, _, err := ucspi.CertPoolFromFile(fn)
if err == nil || dialErr != nil || (daneExists && !daneMatched) {
if certsOur != nil && certTheirHash == spkiHash(certsOur[0]) {
acceptedAdd(host, certTheirHash)
if bytes.Compare(certsOur[0].Raw, rawCerts[0]) != 0 {
- sinkCert <- fmt.Sprintf("Refresh\t%s\t%s", host, certTheirHash)
+ fifos.SinkCert <- fmt.Sprintf("Refresh\t%s\t%s", host, certTheirHash)
goto CertUpdate
}
return nil
grid columnconfigure . 0 -weight 1
`)
- cmd := exec.Command("wish8.7")
- ioutil.WriteFile("/tmp/w.tcl", b.Bytes(), 0666)
+ cmd := exec.Command(CmdWish)
+ // ioutil.WriteFile("/tmp/w.tcl", b.Bytes(), 0666)
cmd.Stdin = &b
err = cmd.Run()
exitError, ok := err.(*exec.ExitError)
if !ok {
- sinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
+ fifos.SinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
return ErrRejected{host}
}
switch exitError.ExitCode() {
case 10:
- sinkCert <- fmt.Sprintf("ADD\t%s\t%s", host, certTheirHash)
+ fifos.SinkCert <- fmt.Sprintf("ADD\t%s\t%s", host, certTheirHash)
goto CertUpdate
case 11:
- sinkCert <- fmt.Sprintf("ONCE\t%s\t%s", host, certTheirHash)
+ fifos.SinkCert <- fmt.Sprintf("ONCE\t%s\t%s", host, certTheirHash)
acceptedAdd(host, certTheirHash)
return nil
case 12:
rejectedAdd(host, certTheirHash)
fallthrough
default:
- sinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
+ fifos.SinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
return ErrRejected{host}
}
} else {
if !os.IsNotExist(err) {
return err
}
- sinkCert <- fmt.Sprintf("TOFU\t%s\t%s", host, certTheirHash)
+ fifos.SinkCert <- fmt.Sprintf("TOFU\t%s\t%s", host, certTheirHash)
}
CertUpdate:
- tmp, err := os.CreateTemp(*certs, "")
+ tmp, err := os.CreateTemp(Certs, "")
if err != nil {
log.Fatalln(err)
}