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...) os.Exit(1) } } var tmpl = template.Must(template.New("pem2html"). Funcs(template.FuncMap{ "red": red, "green": green, "date": fDate, "datetime": fDateTime, }).Parse(` Live Certs {{range $cert := .certs}} {{end}}

Live Certs (Updated {{.now | datetime}})

NotBefore NotAfter Subject.CN Socket
{{$cert.X509.NotBefore | date}} {{$cert.X509.NotAfter | date}} {{$cert.X509.Subject.CommonName | html}} {{$cert.Socket | html}}
`)) func fDate(t time.Time) template.HTML { return template.HTML(t.Local().Format("")) } func fDateTime(t time.Time) template.HTML { return template.HTML(t.Local().Format("")) } func getNow() time.Time { stat, err := os.Stdin.Stat() if err == nil { return stat.ModTime() } else { return time.Now() } } var now = getNow() 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 Error string X509 *x509.Certificate } func (cert Cert) Url() string { return fmt.Sprintf("https://crt.sh/?serial=%036x", cert.X509.SerialNumber) } func (cert Cert) Class() string { if cert.Error == "" { return "" } else { return "invalid" } } 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.After(l[j].X509.NotAfter) } // 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.Error, ok = certPem.Headers["X-Error"] 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") }