diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | diff.go | 15 | ||||
-rw-r--r-- | tls-getcerts.go | 19 | ||||
-rw-r--r-- | tls-pem2html.go | 202 |
5 files changed, 228 insertions, 16 deletions
@@ -5,4 +5,5 @@ NET-* crtsh-pem2html tls-getcerts +tls-pem2html diff @@ -12,8 +12,11 @@ crtsh.pem: crtsh-getcerts config-domains.txt NET-crtsh crtsh.html: %.html: %.pem crtsh-pem2html ./crtsh-pem2html < $< > $@ -tls.pem: tls-getcerts config-servers.txt NET-tls - ./tls-getcerts $$(cat config-servers.txt) > $@ +tls.pem: tls-getcerts config-sockets.txt NET-tls + ./tls-getcerts $$(cat config-sockets.txt) > $@ + +tls.html: %.html: %.pem tls-pem2html + ./tls-pem2html < $< > $@ diff.txt: diff tls.pem crtsh.pem ./diff tls.pem crtsh.pem > $@ @@ -80,6 +80,13 @@ func keys(m map[string]*x509.Certificate) []string { return ret } +func fmtCert(cert *x509.Certificate) string { + return fmt.Sprintf("%s %s %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)")) +} + func main() { if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "Usage: %s TLS-file crt.sh-file\n", os.Args[0]) @@ -97,12 +104,12 @@ func main() { certTLS := certsTLS[host] certCrtSh, okCrtSh := certsCrtSh[host] if !okCrtSh { - fmt.Printf("-%s %s\n", host, certTLS.NotBefore.Format("2006-01-02 15:04:05 MST(-07)")) + fmt.Printf("-%s %s\n", host, fmtCert(certTLS)) } else if !certTLS.Equal(certCrtSh) { - fmt.Printf("-%s %s\n", host, certTLS.NotBefore.Format("2006-01-02 15:04:05 MST(-07)")) - fmt.Printf("+%s %s\n", host, certCrtSh.NotBefore.Format("2006-01-02 15:04:05 MST(-07)")) + fmt.Printf("-%s %s\n", host, fmtCert(certTLS)) + fmt.Printf("+%s %s\n", host, fmtCert(certCrtSh)) } else { - fmt.Printf(" %s %s\n", host, certTLS.NotBefore.Format("2006-01-02 15:04:05 MST(-07)")) + fmt.Printf(" %s %s\n", host, fmtCert(certTLS)) } } } diff --git a/tls-getcerts.go b/tls-getcerts.go index b0d4533..ba951c9 100644 --- a/tls-getcerts.go +++ b/tls-getcerts.go @@ -3,31 +3,30 @@ package main import ( "crypto/tls" "crypto/x509" + "encoding/pem" "fmt" "os" - "encoding/pem" ) -func getcert(server string) (*x509.Certificate, error){ - conn, err := tls.Dial("tcp", fmt.Sprintf("%s:443", server), &tls.Config{ServerName: server}) +func getcert(socket string) (*x509.Certificate, error){ + conn, err := tls.Dial("tcp", socket, &tls.Config{InsecureSkipVerify: true}) if err != nil { return nil, err } defer conn.Close() - chain := conn.ConnectionState().PeerCertificates - return chain[len(chain)-2], nil + return conn.ConnectionState().PeerCertificates[0], nil } func main() { - for _, server := range os.Args[1:] { - cert, err := getcert(server) - if err != nil { - fmt.Fprintf(os.Stderr, "Could not get certificate from server %q: %q\n", server, err) + for _, socket := range os.Args[1:] { + cert, err := getcert(socket) + if cert == nil { + fmt.Fprintf(os.Stderr, "Could not get certificate for socket %q: %q\n", socket, err) os.Exit(1) } block := pem.Block{ Type: "CERTIFICATE", - Headers: nil, + Headers: map[string]string{"X-Socket": socket}, Bytes: cert.Raw, } pem.Encode(os.Stdout, &block) diff --git a/tls-pem2html.go b/tls-pem2html.go new file mode 100644 index 0000000..efefd68 --- /dev/null +++ b/tls-pem2html.go @@ -0,0 +1,202 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "html/template" + "io/ioutil" + "os" + "sort" + "time" +) + +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 handleBool(ok bool, str string, a ...interface{}) { + if !ok { + fmt.Fprintf(os.Stderr, str, a...) + } +} + +var tmpl = template.Must(template.New("pem2html"). + Funcs(template.FuncMap{ + "red": red, + "green": green, + }).Parse(`<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>CT log</title> + <style> + html { + height: 100%; + } + body { + font-size: 10px; + font-family: monospace; + height: 100%; + margin: 0; + display: flex; + align-items: center; + } + body > * { + margin: auto; + } + table { + border-collapse: collapse; + } + caption p { + margin: 0; + } + td, th { + padding: 0; + border: solid 1px black; + white-space: nowrap; + } + td { + background-color: #F3F3F3; + } + tr:hover td { + background-color: #AAAAF3 !important; + } + td a { + padding: 0.1em 0.25em; + display: block; + width: 100%; + height: 100%; + color: black; + } + tr.expired td { + background-color: #F30000; + } + </style> + <script src="sorttable.js"></script> +</head> +<body> +<table class=sortable> + <caption> + <p>Updated {{.now.Local.Format "2006-01-02 15:04:05"}}</p> + </caption> + <tr> + <th>NotBefore</th> + <th>NotAfter</th> + <th>Subject.CN</th> + <th>Socket</th> + </tr> +{{range $cert := .certs}} + <tr> + <td style="background-color: ${$cert.X509.NotBefore | green}}">{{$cert.X509.NotBefore.Local.Format "2006-01-02"}}</td> + <td style="background-color: {{$cert.X509.NotAfter | red}}" >{{$cert.X509.NotAfter.Local.Format "2006-01-02"}}</td> + <td>{{$cert.X509.Subject.CommonName | html}}</td> + <td>{{$cert.Socket | html}}</td> + </tr> +{{end}} +</table> +</body> +</html> +`)) + +var now = time.Now() + +type interpolation struct { + ta, tb time.Time + ba, bb byte +} + +func (i interpolation) interpolate(tc time.Time) byte { + db := i.tb.Sub(i.ta) + dc := tc.Sub(i.ta) + + pct := float64(dc) / float64(db) + if pct < 0 { + pct = 0 + } else if pct > 1 { + pct = 1 + } + + sb := int16(i.bb) - int16(i.ba) + sc := int16(pct * float64(sb)) + + return byte(int16(i.ba) + sc) +} + +var daysago = interpolation{ + ta: now.AddDate(0, 0, -30), + tb: now, + ba: 0xF3, + bb: 0x00, +} + +var daysuntil = interpolation{ + ta: now, + tb: now.AddDate(0, 0, 30), + ba: 0x00, + bb: 0xF3, +} + +func green(t time.Time) string { + b := daysago.interpolate(t) + return fmt.Sprintf("#%02X%02X%02X", b, 0xF3, b) +} + +func red(t time.Time) string { + b := daysuntil.interpolate(t) + return fmt.Sprintf("#%02X%02X%02X", 0xF3, b, b) +} + +type Cert struct { + Socket string + X509 *x509.Certificate +} + +type Certs []Cert + +// Len is the number of elements in the collection. +func (l Certs) Len() int { + return len(l) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (l Certs) Less(i, j int) bool { + return l[i].X509.NotAfter.UTC().After(l[j].X509.NotAfter.UTC()) +} + +// Swap swaps the elements with indexes i and j. +func (l Certs) Swap(i, j int) { + tmp := l[i] + l[i] = l[j] + l[j] = tmp +} + +func main() { + data, err := ioutil.ReadAll(os.Stdin) + handleErr(err, "Error reading stdin: %v\n") + + var certs Certs + for len(data) > 0 { + var certPem *pem.Block + certPem, data = pem.Decode(data) + + var ok bool + var cert Cert + + cert.Socket, ok = certPem.Headers["X-Socket"] + handleBool(ok, "Did not get X-Socket\n") + + cert.X509, err = x509.ParseCertificate(certPem.Bytes) + handleErr(err, "Error parsing cert: %v\n") + + certs = append(certs, cert) + } + + sort.Sort(certs) + handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{"certs":certs, "now": now}), "Could not execute template: %v\n") +} |