/* 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 main import ( "bytes" "crypto/x509" "encoding/pem" "fmt" "log" "os" "os/exec" "path/filepath" "strings" "go.cypherpunks.ru/ucspi" ) func verifyCert( host string, dialErr error, rawCerts [][]byte, verifiedChains [][]*x509.Certificate, ) error { var certTheir *x509.Certificate var err error if len(verifiedChains) > 0 { certTheir = verifiedChains[0][0] } else { certTheir, err = x509.ParseCertificate(rawCerts[0]) if err != nil { return err } } certTheirHash := spkiHash(certTheir) acceptedM.RLock() certOurHash := accepted[host] acceptedM.RUnlock() if certTheirHash == certOurHash { return nil } rejectedM.RLock() certOurHash = rejected[host] rejectedM.RUnlock() if certTheirHash == certOurHash { return ErrRejected{host} } daneExists, daneMatched := dane(host, certTheir) if daneExists { if daneMatched { sinkCert <- fmt.Sprintf("DANE\t%s\tmatched", host) } else { sinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", 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) goto CertUpdate } return nil } cmd := exec.Command( "xmessage", "-buttons", "Accept:10,Once:11,Reject:12", "-file", "-", ) var b bytes.Buffer b.WriteString("Host: " + host) if dialErr != nil { b.WriteString("\nError: " + dialErr.Error()) } if daneExists { if daneMatched { b.WriteString("\nDANE matched") } else { b.WriteString("\nDANE no match!") } } for i, rawCert := range rawCerts { b.WriteString(fmt.Sprintf("\nTheir %d:\n", i)) b.WriteString(certInfo(rawCert)) } if certsOur != nil { b.WriteString(strings.Repeat("-", 80)) for i, cert := range certsOur { b.WriteString(fmt.Sprintf("\nOur %d:\n", i)) b.WriteString(certInfo(cert.Raw)) } } cmd.Stdin = &b switch cmd.Run().(*exec.ExitError).ExitCode() { case 10: sinkCert <- fmt.Sprintf("ADD\t%s\t%s", host, certTheirHash) goto CertUpdate case 11: 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) return ErrRejected{host} } } else { if !os.IsNotExist(err) { return err } sinkCert <- fmt.Sprintf("TOFU\t%s\t%s", host, certTheirHash) } CertUpdate: tmp, err := os.CreateTemp(*certs, "") if err != nil { log.Fatalln(err) } for _, rawCert := range rawCerts { err = pem.Encode(tmp, &pem.Block{Type: "CERTIFICATE", Bytes: rawCert}) if err != nil { log.Fatalln(err) } } tmp.Close() os.Rename(tmp.Name(), fn) acceptedAdd(host, certTheirHash) return nil }