//go:build standalone package main import ( "crypto/x509" "encoding/pem" "errors" "fmt" "html/template" "io" "os" "sort" "time" "git.lukeshu.com/dashboard/bin-src/util" ) func rfc6962type(certX509 *x509.Certificate) string { if util.IsPrecertificate(certX509) { return "Precertificate" } return "Certificate" } //nolint:gochecknoglobals // would be const var tmpl = template.Must(template.New("pem2html"). Funcs(template.FuncMap{ "red": red, "green": green, "rfc6962type": rfc6962type, "date": util.Date2HTML, "datetime": util.DateTime2HTML, "colorDatetime": util.DateTime2ColorHTML, }).Parse(` {{range $cert := .certs}} {{end}}

CT log (Updated {{.now | colorDatetime}})

Logged NotBefore NotAfter Subject.CN Issuer.O SCTs
{{$cert.Updated | date}} {{(index $cert 0).X509.NotBefore | date}} {{(index $cert 0).X509.NotAfter | date}} {{(index $cert 0).X509.Subject.CommonName}} {{(index $cert 0).X509.Issuer.Organization}} {{range $i, $sct := $cert.Precerts}} [{{$i}}] {{end}}
`)) func getNow() time.Time { stat, err := os.Stdin.Stat() if err != nil { return time.Now() } return stat.ModTime() } //nolint:gochecknoglobals // FIXME var now = getNow() func green(t time.Time) string { maxgreen := byte(0xF3) // When did we get the cert? // - 30 days ago => 0 green // - just now => max green greenness := util.MapRange( util.TimeRange{A: now.AddDate(0, 0, -30), B: now}, util.ByteRange{A: 0, B: maxgreen}, t) return fmt.Sprintf("#%02X%02X%02X", maxgreen-greenness, maxgreen, maxgreen-greenness) } func red(t time.Time) string { maxred := byte(0xF3) // When with the cert expire? // - now => max red // - 30 days from now => 0 red redness := util.MapRange( util.TimeRange{A: now, B: now.AddDate(0, 0, 30)}, util.ByteRange{A: maxred, B: 0}, t) return fmt.Sprintf("#%02X%02X%02X", maxred, maxred-redness, maxred-redness) } type Cert struct { Url string Updated time.Time X509 *x509.Certificate } // A CertSet is a set of certificates all sharing the same // SerialNumber. Normally, this will be 1 regular certificate, and // any number of pre-certificates. type CertSet []Cert // Given a list of certificates all sharing the same serial number, // return a list of CertSets. If there are multiple regular // certificates, it returns a seprate CertSet for each. func NewCertSet(certs []Cert) []CertSet { if len(certs) == 0 { return nil } var retCerts []Cert var retPrecerts []Cert for _, cert := range certs { if util.IsPrecertificate(cert.X509) { retPrecerts = append(retPrecerts, cert) } else { retCerts = append(retCerts, cert) } } if len(retCerts) == 0 { return []CertSet{CertSet(retPrecerts)} } ret := make([]CertSet, len(retCerts)) for i := range ret { ret[i] = append(CertSet{retCerts[i]}, retPrecerts...) } return ret } func (certs CertSet) IsValid() bool { return !util.IsPrecertificate(certs[0].X509) } func (certs CertSet) Main() Cert { if util.IsPrecertificate(certs[0].X509) { return Cert{X509: new(x509.Certificate)} } return certs[0] } func (certs CertSet) Precerts() []Cert { if util.IsPrecertificate(certs[0].X509) { return certs } return certs[1:] } // Updated returns the most recent "Updated" timestamp of any of the // certificates in the CertSet. func (certs CertSet) Updated() time.Time { ret := certs[0].Updated for _, cert := range certs { if cert.Updated.After(ret) { ret = cert.Updated } } return ret } func main() { if err := mainWithError(); err != nil { _, _ = fmt.Fprintf(os.Stderr, "%s: error: %v", os.Args[0], err) os.Exit(1) } } func mainWithError() error { data, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("reading stdin: %w", err) } bySerial := make(map[string][]Cert) for len(data) > 0 { var certPem *pem.Block certPem, data = pem.Decode(data) var ok bool var cert Cert cert.Url, ok = certPem.Headers["X-Crt-Sh-Url"] if !ok { return errors.New("did not get X-Crt-Sh-Url") } str, ok := certPem.Headers["X-Crt-Sh-Updated"] if !ok { return errors.New("did not get X-Crt-Sh-Updated") } cert.Updated, err = time.Parse("2006-01-02T15:04:05Z", str) if err != nil { return fmt.Errorf("could not parse updated time: %w", err) } cert.X509, err = x509.ParseCertificate(certPem.Bytes) if err != nil { return fmt.Errorf("could not parse cert: %w", err) } serial := fmt.Sprintf("%036x", cert.X509.SerialNumber) bySerial[serial] = append(bySerial[serial], cert) } var certs []CertSet for _, set := range bySerial { certs = append(certs, NewCertSet(set)...) } sort.Slice(certs, func(i, j int) bool { return certs[i].Updated().After(certs[j].Updated()) }) if err := tmpl.Execute(os.Stdout, map[string]any{"certs": certs, "now": now}); err != nil { return fmt.Errorf("could not execute template: %w", err) } return nil }