//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(`
`))
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
}