package main import ( "crypto/x509" "encoding/pem" "fmt" "io" "io/ioutil" "net/url" "os" "sort" "strings" ) 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) } } 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) { file, err := os.Open(filename) if err != nil { return nil, err } data, err := ioutil.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 { url, err2 := url.Parse(certPem.Headers["X-Socket"]) if err2 != nil { fmt.Fprintf(os.Stderr, "Could not get cert or even parse URL:\ncert: %v\nurl: %v\n", err, err2) os.Exit(1) } ret[strings.Split(url.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) { file, err := os.Open(filename) if err != nil { return nil, err } data, err := ioutil.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 } 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]) } certsTLS, err := readTLS(os.Args[1]) handleErr(err, "Could load TLS file: %v\n") hostsTLS := keys(certsTLS) certsCrtSh, err := readCrtSh(os.Args[2], hostsTLS) handleErr(err, "Could load crt.sh file: %v\n") for _, host := range hostsTLS { certTLS := certsTLS[host] certCrtSh, haveCrtSh := certsCrtSh[host] if !haveCrtSh { handleErr(certTLS.WriteTo(os.Stdout, "del"), "Could not encode PEM: %v\n") } else if !certTLS.X509.Equal(certCrtSh.X509) { handleErr(certTLS.WriteTo(os.Stdout, "del"), "Could not encode PEM: %v\n") handleErr(certCrtSh.WriteTo(os.Stdout, "add"), "Could not encode PEM: %v\n") } else { handleErr(certCrtSh.WriteTo(os.Stdout, "ctx"), "Could not encode PEM: %v\n") } } }