+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 . <KeyPress> {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.6"
+
+ Certs string
+ VerifyM sync.Mutex
+)
+
+func spkiHash(cert *x509.Certificate) string {
+ hsh := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
+ return hex.EncodeToString(hsh[:])
+}
+
+type ErrRejected struct {
+ addr string
+}
+
+func (err ErrRejected) Error() string { return err.addr + " was rejected" }
+
+func certInfo(certRaw []byte) string {
+ cmd := exec.Command(CmdCerttool, "--certificate-info", "--inder")
+ cmd.Stdin = bytes.NewReader(certRaw)
+ out, err := cmd.Output()
+ if err != nil {
+ return err.Error()
+ }
+ return string(out)
+}
+