package main import ( "crypto/x509" "encoding/pem" "fmt" "io/ioutil" "html/template" "os" "sort" ) func handleErr(err error, str string, a ...interface{}) { a = append([]interface{}{err}, a...) if err != nil { fmt.Fprintf(os.Stderr, str, a...) os.Exit(1) } } func readTLS(filename string) (map[string]*x509.Certificate, error) { file, err := os.Open(filename) if err != nil { return nil, err } data, err := ioutil.ReadAll(file) if err != nil { return nil, err } ret := make(map[string]*x509.Certificate) for len(data) > 0 { var certPem *pem.Block certPem, data = pem.Decode(data) certX509, err := x509.ParseCertificate(certPem.Bytes) if err != nil { return nil, err } ret[certX509.Subject.CommonName] = certX509 } return ret, nil } func readCrtSh(filename string, hosts []string) (map[string]*x509.Certificate, error) { file, err := os.Open(filename) if err != nil { return nil, err } data, err := ioutil.ReadAll(file) if err != nil { return nil, err } ret := make(map[string]*x509.Certificate) for len(data) > 0 { var certPem *pem.Block certPem, data = pem.Decode(data) certX509, err := x509.ParseCertificate(certPem.Bytes) if err != nil { return nil, err } for _, host := range hosts { if certX509.VerifyHostname(host) == nil { if old, haveold := ret[host]; !haveold || certX509.NotBefore.After(old.NotBefore) { ret[host] = certX509 } } } } return ret, nil } func keys(m map[string]*x509.Certificate) []string { ret := make([]string, len(m)) i := 0 for k := range m { ret[i] = k i++ } sort.Strings(ret) return ret } func fmtCert(cert *x509.Certificate) string { return fmt.Sprintf("%s\t%s\t%s", cert.Subject.CommonName, cert.NotBefore.Format("2006-01-02 15:04:05 MST(-07)"), cert.NotAfter.Format("2006-01-02 15:04:05 MST(-07)")) } var tmpl = template.Must(template.New("2html"). Funcs(template.FuncMap{ "class": class, "link": link, "join": join, "isNil": isNil, }).Parse(` CT log accuracy {{define "Row"}} {{end}} {{range $cert := .certs}} {{if isNil $cert.CrtSh}} {{template "Row" join "-" $cert.TLS}} {{else if $cert.TLS.Equal $cert.CrtSh | not}} {{template "Row" join "-" $cert.TLS}} {{template "Row" join "+" $cert.CrtSh}} {{else}} {{template "Row" join " " $cert.TLS}} {{end}} {{end}}
--- {{.fileTLS}}
+++ {{.fileCrtSh}}
@@ -1,{{len .certsTLS}} +1,{{len .certsCrtSh}} @@
{{.pfix}} {{.cert.Subject.CommonName}} {{.cert.NotBefore.Local.Format "2006-01-02 15:04:05"}} {{.cert.NotAfter.Local.Format "2006-01-02 15:04:05"}}
`)) type Cert struct { TLS, CrtSh *x509.Certificate } func link(cert *x509.Certificate) string { return fmt.Sprintf("https://crt.sh/?serial=%036x", cert.SerialNumber) } func isNil(cert *x509.Certificate) bool { return cert == nil } func class(pfix string) string { return map[string]string { "+": "diff-add", "-": "diff-del", " ": "diff-ctx", }[pfix] } func join(pfix string, cert *x509.Certificate) map[string]interface{} { return map[string]interface{} { "pfix": pfix, "cert": cert, } } func main() { if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "Usage: %s TLS-file crt.sh-file\n", os.Args[0]) } certsTLS, err := readTLS(os.Args[1]) handleErr(err, "Could load TLS file: %v\n") hostsTLS := keys(certsTLS) certsCrtSh, err := readCrtSh(os.Args[2], hostsTLS) handleErr(err, "Could load crt.sh file: %v\n") certs := make([]Cert, len(certsTLS)) i := 0 for _, host := range hostsTLS { var cert Cert cert.TLS = certsTLS[host] cert.CrtSh = certsCrtSh[host] certs[i] = cert i++ } handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{ "certs": certs, "certsTLS": certsTLS, "certsCrtSh": certsCrtSh, "fileTLS": os.Args[1], "fileCrtSh": os.Args[2], }), "Could not execute template: %v\n") }