X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=verify.go;h=dc852a8b747a7b31ed67f7b5c28a1702bc851616;hb=0c0a261a6ef4fddfc34a9150005f7964cc69c420;hp=f6844cd997f7fd13a3198eb488ca6ff102ba6e01;hpb=e0874503d7bc16fb92ca0cd9bcd21a437fafd77e;p=tofuproxy.git diff --git a/verify.go b/verify.go index f6844cd..dc852a8 100644 --- a/verify.go +++ b/verify.go @@ -1,4 +1,5 @@ /* +tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management Copyright (C) 2021 Sergey Matveev This program is free software: you can redistribute it and/or modify @@ -14,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package tofuproxy import ( "bytes" @@ -31,17 +32,117 @@ import ( "sync" "go.cypherpunks.ru/ucspi" + "go.stargrave.org/tofuproxy/caches" + "go.stargrave.org/tofuproxy/fifos" ) +const VerifyDialog = ` +# host err daneStatus certsTheir certsOur + +if {[string length $err] > 0} { + set tErr [text .tErr] + $tErr insert end $err + $tErr configure -wrap word -height 5 + $tErr configure -state disabled + grid .tErr +} + +proc certsDecode {raws} { + set certs [list] + foreach raw [split $raws] { + set lines [list] + set lineN 0 + foreach line [split [binary decode hex $raw] "\n"] { + lappend lines [format "%03d %s" $lineN $line] + incr lineN + } + lappend certs [join $lines "\n"] + } + return $certs +} + +set certsTheir [certsDecode $certsTheir] +set certsOur [certsDecode $certsOur] + +tk_setPalette grey +wm title . $host + +proc paginator {i delta t l certs} { + incr i $delta + if {$i == [llength $certs]} { + set i 0 + } elseif {$i < 0} { + set i [expr {[llength $certs] - 1}] + } + $t configure -state normal + $t delete 1.0 end + $t insert end [lindex $certs $i] + $t configure -state disabled + $l configure -text "[expr {$i + 1}] / [llength $certs]" + return $i +} + +proc addCertsWindow {name} { + global certs$name t$name sb$name page$name l$name + set t [text .t$name] + set sb [scrollbar .sb$name -command [list $t yview]] + $t configure -wrap word -yscrollcommand [list $sb set] + $t configure -state disabled + grid $t $sb -sticky nsew + set t$name $t + set sb$name $t + + frame .fControl$name + set l$name [label .lPage$name] + button .bNext$name -text "Next" -command [subst { + set page$name \[paginator \$page$name +1 \$t$name \$l$name \$certs$name] + }] + button .bPrev$name -text "Prev" -command [subst { + set page$name \[paginator \$page$name -1 \$t$name \$l$name \$certs$name] + }] + grid .fControl$name + grid .lPage$name .bNext$name .bPrev$name -in .fControl$name + set page$name [paginator -1 +1 $t [set l$name] [set certs$name]] +} + +addCertsWindow Their +if {[llength $certsOur] > 0} { addCertsWindow Our } +frame .fButtons +set lDANE [label .lDANE] +if {$daneStatus ne ""} { + array set daneColour {ok green bad red} + $lDANE configure -bg $daneColour($daneStatus) + $lDANE configure -text "DANE-EE: $daneStatus" +} +proc doAccept {} { exit 10 } +proc doOnce {} { exit 11 } +proc doReject {} { exit 12 } +button .bAccept -text "Accept" -bg green -command doAccept +button .bOnce -text "Once" -bg green -command doOnce +button .bReject -text "Reject" -bg red -command doReject +grid .fButtons +grid .lDANE .bAccept .bOnce .bReject -in .fButtons +grid rowconfigure . 0 -weight 1 +grid columnconfigure . 0 -weight 1 + +bind . {switch -exact %K { + q {exit 0} ; # reject once + a doAccept + o doOnce + r doReject + n {.bNextTheir invoke} + p {.bPrevTheir invoke} + N {.bNextOur invoke} + P {.bPrevOur invoke} +}} +` + var ( CmdCerttool = "certtool" - CmdWish = "wish8.7" + CmdWish = "wish8.6" - certs *string - accepted = make(map[string]string) - acceptedM sync.RWMutex - rejected = make(map[string]string) - rejectedM sync.RWMutex + Certs string + VerifyM sync.Mutex ) func spkiHash(cert *x509.Certificate) string { @@ -49,18 +150,6 @@ func spkiHash(cert *x509.Certificate) string { 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 } @@ -74,16 +163,7 @@ func certInfo(certRaw []byte) string { if err != nil { return err.Error() } - lines := make([]string, 0, 128) - for i, line := range strings.Split(string(out), "\n") { - if strings.Contains(line, "ASCII:") { - continue - } - lines = append(lines, fmt.Sprintf( - "%03d %s", i, strings.ReplaceAll(line, `"`, `\"`), - )) - } - return strings.Join(lines, "\n") + return string(out) } func verifyCert( @@ -103,127 +183,108 @@ func verifyCert( } } certTheirHash := spkiHash(certTheir) - acceptedM.RLock() - certOurHash := accepted[host] - acceptedM.RUnlock() + VerifyM.Lock() + defer VerifyM.Unlock() + caches.AcceptedM.RLock() + certOurHash := caches.Accepted[host] + caches.AcceptedM.RUnlock() if certTheirHash == certOurHash { return nil } - rejectedM.RLock() - certOurHash = rejected[host] - rejectedM.RUnlock() + caches.RejectedM.RLock() + certOurHash = caches.Rejected[host] + caches.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) + fifos.LogDANE <- fmt.Sprintf("%s\tACK", host) } else { - sinkErr <- fmt.Sprintf("DANE\t%s\tnot matched", host) + fifos.LogDANE <- fmt.Sprintf("%s\tNAK", 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) + caches.AcceptedM.Lock() + caches.Accepted[host] = certTheirHash + caches.AcceptedM.Unlock() if bytes.Compare(certsOur[0].Raw, rawCerts[0]) != 0 { - sinkCert <- fmt.Sprintf("Refresh\t%s\t%s", host, certTheirHash) + fifos.LogCert <- fmt.Sprintf("Refresh\t%s\t%s", host, certTheirHash) goto CertUpdate } return nil } var b bytes.Buffer - b.WriteString(fmt.Sprintf("wm title . \"%s\"\n", host)) - - if dialErr != nil { - b.WriteString(fmt.Sprintf(`set tErr [text .tErr] -$tErr insert end "%s" -$tErr configure -wrap word -height 5 -`, dialErr.Error())) - b.WriteString("grid .tErr -columnspan 3\n") + b.WriteString(fmt.Sprintf("set host \"%s\"\n", host)) + if dialErr == nil { + b.WriteString(fmt.Sprintf("set err \"\"\n")) + } else { + b.WriteString(fmt.Sprintf("set err \"%s\"\n", dialErr.Error())) } - + var daneStatus string if daneExists { if daneMatched { - b.WriteString("label .lDANE -bg green -text \"DANE matched\"\n") + daneStatus = "ok" } else { - b.WriteString("label .lDANE -bg red -text \"DANE not matched!\"\n") + daneStatus = "bad" } - b.WriteString("grid .lDANE\n") } - - var bCerts bytes.Buffer - for i, rawCert := range rawCerts { - bCerts.WriteString(fmt.Sprintf("Their %d:\n", i)) - bCerts.WriteString(certInfo(rawCert)) + b.WriteString(fmt.Sprintf("set daneStatus \"%s\"\n", daneStatus)) + hexCerts := make([]string, 0, len(rawCerts)) + for _, rawCert := range rawCerts { + hexCerts = append(hexCerts, hex.EncodeToString([]byte(certInfo(rawCert)))) } - b.WriteString(fmt.Sprintf(`set tTheir [text .tTheir] -$tTheir insert end "%s" -set sbTheir [scrollbar .sbTheir -command [list $tTheir yview]] -$tTheir configure -wrap word -yscrollcommand [list $sbTheir set] -`, bCerts.String())) - b.WriteString("grid $tTheir $sbTheir -sticky nsew -columnspan 3\n") - - if certsOur != nil { - bCerts.Reset() - for i, cert := range certsOur { - bCerts.WriteString(fmt.Sprintf("Our %d:\n", i)) - bCerts.WriteString(certInfo(cert.Raw)) - } - b.WriteString(fmt.Sprintf(`set tOur [text .tOur] -$tOur insert end "%s" -set sbOur [scrollbar .sbOur -command [list $tOur yview]] -$tOur configure -wrap word -yscrollcommand [list $sbOur set] -`, bCerts.String())) - b.WriteString("grid $tOur $sbOur -sticky nsew -columnspan 3\n") + b.WriteString(fmt.Sprintf( + "set certsTheir \"%s\"\n", strings.Join(hexCerts, " "), + )) + hexCerts = make([]string, 0, len(certsOur)) + for _, cert := range certsOur { + hexCerts = append(hexCerts, hex.EncodeToString([]byte(certInfo(cert.Raw)))) } - - b.WriteString(` -proc doAccept {} { exit 10 } -proc doOnce {} { exit 11 } -proc doReject {} { exit 12 } -button .bAccept -text "Accept" -bg green -command doAccept -button .bOnce -text "Once" -command doOnce -button .bReject -text "Reject" -bg red -command doReject -grid .bAccept .bOnce .bReject -grid rowconfigure . 0 -weight 1 -grid columnconfigure . 0 -weight 1 -`) - + b.WriteString(fmt.Sprintf( + "set certsOur \"%s\"\n", strings.Join(hexCerts, " "), + )) + b.WriteString(VerifyDialog) cmd := exec.Command(CmdWish) - // ioutil.WriteFile("/tmp/w.tcl", b.Bytes(), 0666) + // ioutil.WriteFile("/tmp/verify-dialog.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.LogCert <- fmt.Sprintf("Reject\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.LogCert <- fmt.Sprintf("Accept\t%s\t%s", host, certTheirHash) goto CertUpdate case 11: - sinkCert <- fmt.Sprintf("ONCE\t%s\t%s", host, certTheirHash) - acceptedAdd(host, certTheirHash) + fifos.LogCert <- fmt.Sprintf("Once\t%s\t%s", host, certTheirHash) + caches.AcceptedM.Lock() + caches.Accepted[host] = certTheirHash + caches.AcceptedM.Unlock() return nil case 12: - rejectedAdd(host, certTheirHash) + caches.RejectedM.Lock() + caches.Rejected[host] = certTheirHash + caches.RejectedM.Unlock() fallthrough default: - sinkCert <- fmt.Sprintf("DENY\t%s\t%s", host, certTheirHash) + fifos.LogCert <- fmt.Sprintf("Reject\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.LogCert <- 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) } @@ -235,6 +296,8 @@ CertUpdate: } tmp.Close() os.Rename(tmp.Name(), fn) - acceptedAdd(host, certTheirHash) + caches.AcceptedM.Lock() + caches.Accepted[host] = certTheirHash + caches.AcceptedM.Unlock() return nil }