2 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, version 3 of the License.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
30 "go.cypherpunks.ru/ucspi"
34 CmdCerttool = "certtool"
38 func certInfo(certRaw []byte) string {
39 cmd := exec.Command(CmdCerttool, "--certificate-info", "--inder")
40 cmd.Stdin = bytes.NewReader(certRaw)
41 out, err := cmd.Output()
45 lines := make([]string, 0, 128)
46 for i, line := range strings.Split(string(out), "\n") {
47 if strings.Contains(line, "ASCII:") {
50 lines = append(lines, fmt.Sprintf(
51 "%03d %s", i, strings.ReplaceAll(line, `"`, `\"`),
54 return strings.Join(lines, "\n")
61 verifiedChains [][]*x509.Certificate,
63 var certTheir *x509.Certificate
65 if len(verifiedChains) > 0 {
66 certTheir = verifiedChains[0][0]
68 certTheir, err = x509.ParseCertificate(rawCerts[0])
73 certTheirHash := spkiHash(certTheir)
75 certOurHash := accepted[host]
77 if certTheirHash == certOurHash {
81 certOurHash = rejected[host]
83 if certTheirHash == certOurHash {
84 return ErrRejected{host}
86 daneExists, daneMatched := dane(host, certTheir)
89 sinkCert <- fmt.Sprintf("DANE\t%s\tmatched", host)
91 sinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", host)
94 fn := filepath.Join(*certs, host)
95 certsOur, _, err := ucspi.CertPoolFromFile(fn)
96 if err == nil || dialErr != nil || (daneExists && !daneMatched) {
97 if certsOur != nil && certTheirHash == spkiHash(certsOur[0]) {
98 acceptedAdd(host, certTheirHash)
99 if bytes.Compare(certsOur[0].Raw, rawCerts[0]) != 0 {
100 sinkCert <- fmt.Sprintf("Refresh\t%s\t%s", host, certTheirHash)
106 b.WriteString(fmt.Sprintf("wm title . \"%s\"\n", host))
109 b.WriteString(fmt.Sprintf(`set tErr [text .tErr]
110 $tErr insert end "%s"
111 $tErr configure -wrap word -height 5
113 b.WriteString("grid .tErr -columnspan 3\n")
118 b.WriteString("label .lDANE -bg green -text \"DANE matched\"\n")
120 b.WriteString("label .lDANE -bg red -text \"DANE not matched!\"\n")
122 b.WriteString("grid .lDANE\n")
125 var bCerts bytes.Buffer
126 for i, rawCert := range rawCerts {
127 bCerts.WriteString(fmt.Sprintf("Their %d:\n", i))
128 bCerts.WriteString(certInfo(rawCert))
130 b.WriteString(fmt.Sprintf(`set tTheir [text .tTheir]
131 $tTheir insert end "%s"
132 set sbTheir [scrollbar .sbTheir -command [list $tTheir yview]]
133 $tTheir configure -wrap word -yscrollcommand [list $sbTheir set]
135 b.WriteString("grid $tTheir $sbTheir -sticky nsew -columnspan 3\n")
139 for i, cert := range certsOur {
140 bCerts.WriteString(fmt.Sprintf("Our %d:\n", i))
141 bCerts.WriteString(certInfo(cert.Raw))
143 b.WriteString(fmt.Sprintf(`set tOur [text .tOur]
144 $tOur insert end "%s"
145 set sbOur [scrollbar .sbOur -command [list $tOur yview]]
146 $tOur configure -wrap word -yscrollcommand [list $sbOur set]
148 b.WriteString("grid $tOur $sbOur -sticky nsew -columnspan 3\n")
152 proc doAccept {} { exit 10 }
153 proc doOnce {} { exit 11 }
154 proc doReject {} { exit 12 }
155 button .bAccept -text "Accept" -bg green -command doAccept
156 button .bOnce -text "Once" -command doOnce
157 button .bReject -text "Reject" -bg red -command doReject
158 grid .bAccept .bOnce .bReject
159 grid rowconfigure . 0 -weight 1
160 grid columnconfigure . 0 -weight 1
163 cmd := exec.Command(CmdWish)
164 // ioutil.WriteFile("/tmp/w.tcl", b.Bytes(), 0666)
167 exitError, ok := err.(*exec.ExitError)
169 sinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
170 return ErrRejected{host}
172 switch exitError.ExitCode() {
174 sinkCert <- fmt.Sprintf("ADD\t%s\t%s", host, certTheirHash)
177 sinkCert <- fmt.Sprintf("ONCE\t%s\t%s", host, certTheirHash)
178 acceptedAdd(host, certTheirHash)
181 rejectedAdd(host, certTheirHash)
184 sinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash)
185 return ErrRejected{host}
188 if !os.IsNotExist(err) {
191 sinkCert <- fmt.Sprintf("TOFU\t%s\t%s", host, certTheirHash)
194 tmp, err := os.CreateTemp(*certs, "")
198 for _, rawCert := range rawCerts {
199 err = pem.Encode(tmp, &pem.Block{Type: "CERTIFICATE", Bytes: rawCert})
205 os.Rename(tmp.Name(), fn)
206 acceptedAdd(host, certTheirHash)