//go:build standalone package main import ( "crypto/x509" "encoding/pem" "fmt" "io" "net/url" "os" "sort" "strings" "git.lukeshu.com/dashboard/bin-src/util" ) type Cert struct { Url string X509 *x509.Certificate } func (cert Cert) WriteTo(w io.Writer, action string) error { block := pem.Block{ Type: "CERTIFICATE", Headers: map[string]string{ "X-Crt-Sh-Url": cert.Url, "X-Diff-Action": action, }, Bytes: cert.X509.Raw, } return pem.Encode(w, &block) } func readTLS(filename string) (map[string]Cert, error) { //nolint:gosec // that's the point file, err := os.Open(filename) if err != nil { return nil, err } data, err := io.ReadAll(file) if err != nil { return nil, err } ret := make(map[string]Cert) for len(data) > 0 { var certPem *pem.Block certPem, data = pem.Decode(data) certX509, err := x509.ParseCertificate(certPem.Bytes) if err != nil { u, err2 := url.Parse(certPem.Headers["X-Socket"]) if err2 != nil { return nil, fmt.Errorf("could not get cert or even parse URL:\ncert: %w\nurl: %w", err, err2) } ret[strings.Split(u.Host, ":")[0]] = Cert{ X509: new(x509.Certificate), } } else { ret[certX509.Subject.CommonName] = Cert{ Url: fmt.Sprintf("https://crt.sh/?serial=%036x", certX509.SerialNumber), X509: certX509, } } } return ret, nil } func readCrtSh(filename string, hosts []string) (map[string]Cert, error) { //nolint:gosec // that's the point file, err := os.Open(filename) if err != nil { return nil, err } data, err := io.ReadAll(file) if err != nil { return nil, err } ret := make(map[string]Cert) 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 } if util.IsPrecertificate(certX509) { continue } for _, host := range hosts { if certX509.VerifyHostname(host) == nil { if old, haveold := ret[host]; !haveold || certX509.NotBefore.After(old.X509.NotBefore) { ret[host] = Cert{ Url: certPem.Headers["X-Crt-Sh-Url"], X509: certX509, } } } } } return ret, nil } func keys(m map[string]Cert) []string { ret := make([]string, len(m)) i := 0 for k := range m { ret[i] = k i++ } sort.Strings(ret) return ret } func main() { if len(os.Args) != 3 { _, _ = fmt.Fprintf(os.Stderr, "Usage: %s TLS-file crt.sh-file\n", os.Args[0]) os.Exit(2) } if err := mainWithError(os.Args[1], os.Args[2]); err != nil { _, _ = fmt.Fprintf(os.Stderr, "%s: error: %v", os.Args[0], err) os.Exit(1) } } func mainWithError(filenameTLS string, filenameCrtSh string) error { certsTLS, err := readTLS(filenameTLS) if err != nil { return fmt.Errorf("could load TLS file: %w", err) } hostsTLS := keys(certsTLS) certsCrtSh, err := readCrtSh(filenameCrtSh, hostsTLS) if err != nil { return fmt.Errorf("could load crt.sh file: %w", err) } for _, host := range hostsTLS { certTLS := certsTLS[host] certCrtSh, haveCrtSh := certsCrtSh[host] switch { case !haveCrtSh: if err := certTLS.WriteTo(os.Stdout, "del"); err != nil { return fmt.Errorf("could not encode PEM: %w", err) } case !certTLS.X509.Equal(certCrtSh.X509): if err := certTLS.WriteTo(os.Stdout, "del"); err != nil { return fmt.Errorf("could not encode PEM: %w", err) } if err := certCrtSh.WriteTo(os.Stdout, "add"); err != nil { return fmt.Errorf("could not encode PEM: %w", err) } default: if err := certCrtSh.WriteTo(os.Stdout, "ctx"); err != nil { return fmt.Errorf("could not encode PEM: %w", err) } } } return nil }