diff options
| author | tjp <tjp@ctrl-c.club> | 2024-01-05 12:19:40 -0700 |
|---|---|---|
| committer | tjp <tjp@ctrl-c.club> | 2024-01-05 12:24:46 -0700 |
| commit | 230933ee0e4bce6ddf25e0816fff0bd30e3c8864 (patch) | |
| tree | b5e4818d05fa770c6316b41cf57cffb8eb952627 | |
| parent | 65218373fdc7e32ef175425c25ba9e90ac31fac6 (diff) | |
TOFU certificate validation
| -rw-r--r-- | files.go | 47 | ||||
| -rw-r--r-- | main.go | 7 | ||||
| -rw-r--r-- | tls.go | 47 |
3 files changed, 101 insertions, 0 deletions
@@ -189,6 +189,53 @@ func toursFilePath() (string, error) { return dataFilePath("tours") } +func getTofuStore() error { + tofuFilePath, err := dataFilePath("tofu") + if err != nil { + return err + } + + tofuStore = map[string]string{} + + f, err := os.Open(tofuFilePath) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + rdr := bufio.NewScanner(f) + for rdr.Scan() { + domain, certhash, _ := strings.Cut(rdr.Text(), ":") + tofuStore[domain] = certhash + } + if err := rdr.Err(); err != nil { + return err + } + + return nil +} + +func saveTofuStore(store map[string]string) error { + tofuFilePath, err := dataFilePath("tofu") + if err != nil { + return err + } + + f, err := os.OpenFile(tofuFilePath, os.O_WRONLY|os.O_TRUNC, 0o600) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + for domain, certhash := range store { + if _, err := fmt.Fprintf(f, "%s:%s\n", domain, certhash); err != nil { + return err + } + } + + return nil +} + func dataFilePath(filename string) (string, error) { home := os.Getenv("HOME") path := os.Getenv("XDG_DATA_HOME") @@ -8,6 +8,7 @@ import ( "strings" "github.com/chzyer/readline" + "tildegit.org/tjp/sliderule" ) func main() { @@ -16,6 +17,12 @@ func main() { log.Fatal(err) } + if err := getTofuStore(); err != nil { + log.Fatal(err) + } + + client = sliderule.NewClient(tlsConfig()) + state := NewBrowserState() state.Quiet = conf.Quiet state.Pager = conf.Pager @@ -0,0 +1,47 @@ +package main + +import ( + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "errors" +) + +func tlsConfig() *tls.Config { + return &tls.Config{ + InsecureSkipVerify: true, + VerifyConnection: tofuVerify, + } +} + +var tofuStore map[string]string + +var ErrTOFUViolation = errors.New("certificate for this domain has changed") + +func tofuVerify(connState tls.ConnectionState) error { + certhash, err := hashCert(connState.PeerCertificates[0]) + if err != nil { + return err + } + + expected, ok := tofuStore[connState.ServerName] + if !ok { + tofuStore[connState.ServerName] = certhash + return saveTofuStore(tofuStore) + } + + if certhash != expected { + return ErrTOFUViolation + } + return nil +} + +func hashCert(cert *x509.Certificate) (string, error) { + pubkeybytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + return "", err + } + hash := sha256.Sum256(pubkeybytes) + return hex.EncodeToString(hash[:]), nil +} |
