diff --git a/example.yaml b/example.yaml index 32499ef..39ba9d2 100644 --- a/example.yaml +++ b/example.yaml @@ -2,7 +2,6 @@ SlackWebhookURL: "" #Slack Webhook URL SlackIconURL: "" #Slack Icon (Avatar) URL SlackUsername: "" #Slack Username -Regexp: ".*\\.fr$" #Regexp to match. Can't be empty. It uses Golang regexp format -DomainName: test +Regexp: ".*" #Regexp to match. Can't be empty. It uses Golang regexp format Workers: 20 #Number of workers for consuming stream from CertStream -DisplayErrors: false #Enable/Disable display of errors in logs \ No newline at end of file +DisplayErrors: true #Enable/Disable display of errors in logs \ No newline at end of file diff --git a/go.mod b/go.mod index a487656..54aa741 100644 --- a/go.mod +++ b/go.mod @@ -13,5 +13,6 @@ require ( github.com/sirupsen/logrus v1.2.0 github.com/spf13/viper v1.6.3 github.com/stretchr/testify v1.4.0 // indirect + golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) diff --git a/go.sum b/go.sum index e89ab6a..eb2b2d4 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/lib/config.go b/lib/config.go index ad5393e..0793808 100644 --- a/lib/config.go +++ b/lib/config.go @@ -20,14 +20,13 @@ type Configuration struct { RegIP string Regexp string DisplayErrors string + Homoglyph map[string]string } -const regStrIP = `^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$` - // GetConfig provides a Configuration func GetConfig() *Configuration { c := &Configuration{ - RegIP: regStrIP, + Homoglyph: getHomoglyphMap(), } configFile := kingpin.Flag("configfile", "config file").Short('c').ExistingFile() @@ -66,9 +65,7 @@ func GetConfig() *Configuration { if c.Regexp == "" { log.Fatal("Regexp can't be empty") } - if c.DomainName == "" { - log.Fatal("Specify the domain name to monitor for IDN homographs") - } + if _, err := regexp.Compile(c.Regexp); err != nil { log.Fatal("Bad regexp") } diff --git a/lib/homoglyph.go b/lib/homoglyph.go index 9a1a315..5a61333 100644 --- a/lib/homoglyph.go +++ b/lib/homoglyph.go @@ -4,6 +4,7 @@ import ( "github.com/picatz/homoglyphr" ) +// getHomoglyphMap generates a map of homoglyphs for replacement func getHomoglyphMap() map[string]string { alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"} homoglyph := map[string]string{} @@ -14,3 +15,20 @@ func getHomoglyphMap() map[string]string { } return homoglyph } + +// replaceHomoglyph replaces homoglyphs in a string by close latin letters +func replaceHomoglyph(idn string, homoglyphMap map[string]string) string { + var s string + for _, i := range idn { + if i > 127 { + if letter, present := homoglyphMap[string(i)]; present { + s += letter + } else { + s += string(i) + } + } else { + s += string(i) + } + } + return s +} diff --git a/lib/lib.go b/lib/lib.go index 8c5c241..cbb2760 100644 --- a/lib/lib.go +++ b/lib/lib.go @@ -3,44 +3,44 @@ package lib import ( "context" "encoding/json" - "fmt" "net" "regexp" "strings" "time" - log "github.com/sirupsen/logrus" - - _ "net/http/pprof" - - _ "expvar" - "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" + log "github.com/sirupsen/logrus" + "golang.org/x/net/idna" ) -type Result struct { +// result represents a catched certificate +type result struct { Domain string `json:"domain"` + IDN string `json:"IDN,omitempty"` SAN []string `json:"SAN"` Issuer string `json:"issuer"` Addresses []string `json:"Addresses"` } -type Certificate struct { +// certificate represents a certificate from CertStream +type certificate struct { MessageType string `json:"message_type"` - Data Data `json:"data"` + Data data `json:"data"` } -type Data struct { +// data represents data field for a certificate from CertStream +type data struct { UpdateType string `json:"update_type"` - LeafCert LeafCert `json:"leaf_cert"` - Chain []LeafCert `json:"chain"` + LeafCert leafCert `json:"leaf_cert"` + Chain []leafCert `json:"chain"` CertIndex float32 `json:"cert_index"` Seen float32 `json:"seen"` Source map[string]string `json:"source"` } -type LeafCert struct { +// leafCert represents leaf_cert field from CertStream +type leafCert struct { Subject map[string]string `json:"subject"` Extensions map[string]interface{} `json:"extensions"` NotBefore float32 `json:"not_before"` @@ -51,20 +51,20 @@ type LeafCert struct { AllDomains []string `json:"all_domains"` } -// MsgChan is the communication channel between certCheckWorkers and LoopCheckCerts +// MsgChan is the communication channel between certCheckWorkers and LoopCertStream var MsgChan chan []byte +// the websocket stream from calidog const certInput = "wss://certstream.calidog.io" // CertCheckWorker parses certificates and raises alert if matches config func CertCheckWorker(config *Configuration) { reg, _ := regexp.Compile(config.Regexp) - regIP, _ := regexp.Compile(config.RegIP) for { msg := <-MsgChan - detailedCert, err := ParseResultCertificate(msg, regIP) + detailedCert, err := parseResultCertificate(msg) if err != nil { log.Warnf("Error parsing message: %s", err) continue @@ -72,33 +72,40 @@ func CertCheckWorker(config *Configuration) { if detailedCert == nil { continue } - if !IsMatchingCert(detailedCert, reg) { + if !isMatchingCert(config, detailedCert, reg) { continue } - notify(config, *detailedCert) + slackPost(config, *detailedCert) } } -func ParseResultCertificate(msg []byte, regIP *regexp.Regexp) (*Result, error) { - var c Certificate - var r *Result +// parseResultCertificate parses certificate details +func parseResultCertificate(msg []byte) (*result, error) { + var c certificate + var r *result err := json.Unmarshal(msg, &c) if err != nil || c.MessageType == "heartbeat" { return nil, err } - r = &Result{ + r = &result{ Domain: c.Data.LeafCert.Subject["CN"], Issuer: c.Data.Chain[0].Subject["O"], SAN: c.Data.LeafCert.AllDomains, Addresses: []string{"N/A"}, } - r.Addresses = FetchIPAddresses(r.Domain, regIP) + r.Addresses = fetchIPAddresses(r.Domain) return r, nil } -func FetchIPAddresses(name string, regIP *regexp.Regexp) []string { +// isIPv4Net checks if IP is IPv4 +func isIPv4Net(host string) bool { + return net.ParseIP(host) != nil +} + +// fetchIPAddresses resolves domain to get IP +func fetchIPAddresses(name string) []string { var ipsList []string ips, err := net.LookupIP(name) @@ -107,19 +114,29 @@ func FetchIPAddresses(name string, regIP *regexp.Regexp) []string { return ipsList } for _, j := range ips { - if regIP.MatchString(j.String()) { + if isIPv4Net(j.String()) { ipsList = append(ipsList, j.String()) } } return ipsList } -func IsMatchingCert(cert *Result, reg *regexp.Regexp) bool { +// isIDN checks if domain is an IDN +func isIDN(domain string) bool { + return strings.HasPrefix(domain, "xn--") +} + +// isMatchingCert checks if certificate matches the regexp +func isMatchingCert(config *Configuration, cert *result, reg *regexp.Regexp) bool { domainList := append(cert.SAN, cert.Domain) for _, domain := range domainList { - // if isIDN(domain) { - // return true - // } + if isIDN(domain) { + unicodeDomain, _ := idna.ToUnicode(domain) + cert.IDN = unicodeDomain + if reg.MatchString(replaceHomoglyph(unicodeDomain, config.Homoglyph)) { + return true + } + } if reg.MatchString(domain) { return true } @@ -127,27 +144,24 @@ func IsMatchingCert(cert *Result, reg *regexp.Regexp) bool { return false } -func isIDN(domain string) bool { - return strings.HasPrefix(domain, "xn--") -} - -func notify(config *Configuration, detailedCert Result) { +// slackPost posts event to Slack +func slackPost(config *Configuration, detailedCert result) { b, _ := json.Marshal(detailedCert) if config.SlackWebHookURL != "" { - go newSlackPayload(detailedCert, config).Post(config) + go newSlackPayload(&detailedCert, config).post(config) } else { - fmt.Printf("A certificate for '%v' has been issued : %v\n", detailedCert.Domain, string(b)) + log.Infof("A certificate for '%v' has been issued : %v\n", detailedCert.Domain, string(b)) } } -// LoopCheckCerts Loops on messages from source -func LoopCheckCerts(config *Configuration) { +// LoopCertStream gathers messages from CertStream +func LoopCertStream(config *Configuration) { for { conn, _, _, err := ws.DefaultDialer.Dial(context.Background(), certInput) defer conn.Close() if err != nil { - log.Warn("Error connecting to certstream! Sleeping a few seconds and reconnecting...") + log.Warn("Error connecting to CertStream! Sleeping a few seconds and reconnecting...") time.Sleep(1 * time.Second) continue } diff --git a/lib/lib_suite_test.go b/lib/lib_suite_test.go index a556e7e..0c56288 100644 --- a/lib/lib_suite_test.go +++ b/lib/lib_suite_test.go @@ -1,4 +1,4 @@ -package lib_test +package lib import ( "testing" diff --git a/lib/lib_test.go b/lib/lib_test.go index 1b915a3..dac16d4 100644 --- a/lib/lib_test.go +++ b/lib/lib_test.go @@ -1,9 +1,7 @@ -package lib_test +package lib import ( - "cercat/lib" "io/ioutil" - "regexp" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -11,36 +9,36 @@ import ( var _ = Describe("Handler", func() { Describe("isMatchingCert", func() { - reg, _ := regexp.Compile(`.+\.com`) - regIDN, _ := regexp.Compile(lib.BuildIDNRegex("test")) Describe("If certificate matches", func() { - cert := &lib.Result{Domain: "www.test.com"} + cert := &result{Domain: "www.test.com"} + reg := ".*test.*" It("should return true", func() { - result := lib.IsMatchingCert(cert, reg, regIDN) + result := isMatchingCert(cert, reg) Expect(result).To(BeTrue()) }) }) Describe("If alternative subject matches", func() { - cert := &lib.Result{Domain: "www.test.net", SAN: []string{"www.test.com"}} + cert := &Result{Domain: "www.test.net", SAN: []string{"www.test.com"}} + reg := ".*test.*" It("should return true", func() { - result := lib.IsMatchingCert(cert, reg, regIDN) + result := isMatchingCert(cert, reg) Expect(result).To(BeTrue()) }) }) Describe("If domain is IDN", func() { - cert := &lib.Result{Domain: "xn--tst-rdd.com"} + cert := &Result{Domain: "xn--tst-rdd.com"} + reg := ".*test.*" It("should return true", func() { - result := lib.IsMatchingCert(cert, reg, regIDN) + result := isMatchingCert(cert, reg) Expect(result).To(BeTrue()) }) }) }) Describe("parseResultCertificate", func() { - regIP, _ := regexp.Compile(lib.RegStrIP) Describe("If cannot marshall message", func() { msg := []byte("") It("should return nil and error", func() { - result, err := lib.ParseResultCertificate(msg, regIP) + result, err := parseResultCertificate(msg) Expect(result).To(BeNil()) Expect(err).To(HaveOccurred()) }) @@ -48,7 +46,7 @@ var _ = Describe("Handler", func() { Describe("If message is heartbeat", func() { msg, _ := ioutil.ReadFile("../res/heartbeat.json") It("should return nil", func() { - result, err := lib.ParseResultCertificate(msg, regIP) + result, err := parseResultCertificate(msg) Expect(result).To(BeNil()) Expect(err).ToNot(HaveOccurred()) }) @@ -56,7 +54,7 @@ var _ = Describe("Handler", func() { Describe("If message is regular", func() { msg, _ := ioutil.ReadFile("../res/cert.json") It("should return valid infos", func() { - result, err := lib.ParseResultCertificate(msg, regIP) + result, err := parseResultCertificate(msg) Expect(result.Domain).Should(Equal("baden-mueller.de")) Expect(result.SAN).Should(Equal([]string{"baden-mueller.de", "www.baden-mueller.de"})) Expect(result.Issuer).Should(Equal("Let's Encrypt")) diff --git a/lib/slack.go b/lib/slack.go index 065c684..c5d0bcf 100644 --- a/lib/slack.go +++ b/lib/slack.go @@ -9,12 +9,14 @@ import ( log "github.com/sirupsen/logrus" ) +// slackAttachmentField type slackAttachmentField struct { Title string `json:"title"` Value string `json:"value"` Short bool `json:"short"` } +// slackAttachment type slackAttachment struct { Color string `json:"color"` Text string `json:"text,omitempty"` @@ -23,6 +25,7 @@ type slackAttachment struct { // FooterIcon string `json:"footer_icon,omitempty"` } +// slackPayload type slackPayload struct { Text string `json:"text,omitempty"` Username string `json:"username,omitempty"` @@ -30,7 +33,8 @@ type slackPayload struct { Attachments []slackAttachment `json:"attachments,omitempty"` } -func newSlackPayload(r Result, config *Configuration) slackPayload { +// newSlackPayload generates a new Slack Payload +func newSlackPayload(r *result, config *Configuration) slackPayload { var attachments []slackAttachment var attachment slackAttachment var fields []slackAttachmentField @@ -69,7 +73,8 @@ func newSlackPayload(r Result, config *Configuration) slackPayload { Attachments: attachments} } -func (s slackPayload) Post(config *Configuration) { +// post posts to Slack a Payload +func (s slackPayload) post(config *Configuration) { body, _ := json.Marshal(s) req, _ := http.NewRequest(http.MethodPost, config.SlackWebHookURL, bytes.NewBuffer(body)) req.Header.Add("Content-Type", "application/json") diff --git a/main.go b/main.go index bdccdb0..0416821 100644 --- a/main.go +++ b/main.go @@ -20,5 +20,5 @@ func main() { go lib.CertCheckWorker(config) } - lib.LoopCheckCerts(config) + lib.LoopCertStream(config) } diff --git a/res/cert.json b/res/cert.json deleted file mode 100644 index 7964f08..0000000 --- a/res/cert.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"cert_index":612101919,"cert_link":"http://ct.googleapis.com/logs/argon2020/ct/v1/get-entries?start=612101919&end=612101919","chain":[{"as_der":"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIGCCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNvbTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9kc3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwVAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcCARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwuY3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsFAAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJouM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwuX4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlGPfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==","extensions":{"authorityInfoAccess":"CA Issuers - URI:http://apps.identrust.com/roots/dstrootcax3.p7c\nOCSP - URI:http://isrg.trustid.ocsp.identrust.com\n","authorityKeyIdentifier":"keyid:C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10\n","basicConstraints":"CA:TRUE","certificatePolicies":"Policy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http://cps.root-x1.letsencrypt.org","crlDistributionPoints":"Full Name:\n URI:http://crl.identrust.com/DSTROOTCAX3CRL.crl","keyUsage":"Digital Signature, Key Cert Sign, C R L Sign","subjectKeyIdentifier":"A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1"},"fingerprint":"E6:A3:B4:5B:06:2D:50:9B:33:82:28:2D:19:6E:FE:97:D5:95:6C:CB","not_after":1615999246,"not_before":1458232846,"serial_number":"A0141420000015385736A0B85ECA708","subject":{"C":"US","CN":"Let's Encrypt Authority X3","L":null,"O":"Let's Encrypt","OU":null,"ST":null,"aggregated":"/C=US/CN=Let's Encrypt Authority X3/O=Let's Encrypt"}},{"as_der":"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ","extensions":{"basicConstraints":"CA:TRUE","keyUsage":"Key Cert Sign, C R L Sign","subjectKeyIdentifier":"C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10"},"fingerprint":"DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13","not_after":1633010475,"not_before":970348339,"serial_number":"44AFB080D6A327BA893039862EF8406B","subject":{"C":null,"CN":"DST Root CA X3","L":null,"O":"Digital Signature Trust Co.","OU":null,"ST":null,"aggregated":"/CN=DST Root CA X3/O=Digital Signature Trust Co."}}],"leaf_cert":{"all_domains":["baden-mueller.de","www.baden-mueller.de"],"as_der":"MIIEezCCA2OgAwIBAgISA1Gm0sew3z+8nJrpo5Jj1naBMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDA1MjQxODE3NDhaFw0yMDA4MjIxODE3NDhaMBsxGTAXBgNVBAMTEGJhZGVuLW11ZWxsZXIuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCSDtXT7WQQaNmnaH6+BxHwEz7eqHdTx/02HV7x/q9oozIKfWnfc4A3glkMdnJtZUjLlbV4sgAO4MBDNo65Qsq4L/GesRsVTczmYcAxnrfp8e/eK7wF08oqCvdHddXSHD82aXe/6Y6a3hiLEG+oBMDfG1Skwyt7NGNySlenz3EYEbc35IVoFKIkp2CyMV/nkKQPCgQBL10niEiQd9Q9bHDQJZsBtW59VVCy5K5kIPo6P5v295PCt0WTUppXagY2G/YGpQOmvsjl9MFjMZc4yOOd3RhGhcr2jgd9iF04TvownTxvQAU1EbKcXDHcoPVmhH5zDeiN1JbLNpW2wMf5Vr9VAgMBAAGjggGIMIIBhDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFE8R8swxvB64KS8VqcCaUcMFpEjAMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9yZy8wMQYDVR0RBCowKIIQYmFkZW4tbXVlbGxlci5kZYIUd3d3LmJhZGVuLW11ZWxsZXIuZGUwTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwEwYKKwYBBAHWeQIEAwEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAIUMEOxJNwvA/pWwFm0BR0HClGdzSC1vQprBaZ6cedUM6b/wfAiCPNJXCvJyrfhJp4T1GcnwU5VnMLPE1/nwJ6LY3My86/M+eQn/3HRuXu3p1GFpp5k2cXsHB7VRlw5X78XVvsnYKc6giwOan7L8fL136EcplQTZRc/5qu9hvazeBOBQQc/lCeWceWz0ZDDVbU2IGvY6aF/SAQREOSq8jLVpEoXB0zwq3dXeEi+PfC2Ea03eOpo1y11nmRYB5Usi/GjMi7oXuBxVQMolJXJj38ziJcp1TT1sv2Ha/00F+Pudo54w1NEo04DbDD9yB2H9wTlMM4YsArmD3K22OGA8wNE=","extensions":{"authorityInfoAccess":"CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\nOCSP - URI:http://ocsp.int-x3.letsencrypt.org\n","authorityKeyIdentifier":"keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1\n","basicConstraints":"CA:FALSE","certificatePolicies":"Policy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http://cps.letsencrypt.org","ctlPoisonByte":true,"extendedKeyUsage":"TLS Web server authentication, TLS Web client authentication","keyUsage":"Digital Signature, Key Encipherment","subjectAltName":"DNS:www.baden-mueller.de, DNS:baden-mueller.de","subjectKeyIdentifier":"4F:11:F2:CC:31:BC:1E:B8:29:2F:15:A9:C0:9A:51:C3:05:A4:48:C0"},"fingerprint":"64:BF:49:41:3B:7A:FD:5D:C1:04:D9:44:64:9D:1C:25:13:A2:49:86","not_after":1598120268,"not_before":1590344268,"serial_number":"351A6D2C7B0DF3FBC9C9AE9A39263D67681","subject":{"C":null,"CN":"baden-mueller.de","L":null,"O":null,"OU":null,"ST":null,"aggregated":"/CN=baden-mueller.de"}},"seen":1590347943.736608,"source":{"name":"Google 'Argon2020' log","url":"ct.googleapis.com/logs/argon2020/"},"update_type":"PrecertLogEntry"},"message_type":"certificate_update"} \ No newline at end of file diff --git a/res/heartbeat.json b/res/heartbeat.json deleted file mode 100644 index a1bcf74..0000000 --- a/res/heartbeat.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"cert_index":612101919,"cert_link":"http://ct.googleapis.com/logs/argon2020/ct/v1/get-entries?start=612101919&end=612101919","chain":[{"as_der":"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIGCCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNvbTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9kc3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwVAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcCARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwuY3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsFAAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJouM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwuX4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlGPfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==","extensions":{"authorityInfoAccess":"CA Issuers - URI:http://apps.identrust.com/roots/dstrootcax3.p7c\nOCSP - URI:http://isrg.trustid.ocsp.identrust.com\n","authorityKeyIdentifier":"keyid:C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10\n","basicConstraints":"CA:TRUE","certificatePolicies":"Policy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http://cps.root-x1.letsencrypt.org","crlDistributionPoints":"Full Name:\n URI:http://crl.identrust.com/DSTROOTCAX3CRL.crl","keyUsage":"Digital Signature, Key Cert Sign, C R L Sign","subjectKeyIdentifier":"A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1"},"fingerprint":"E6:A3:B4:5B:06:2D:50:9B:33:82:28:2D:19:6E:FE:97:D5:95:6C:CB","not_after":1615999246,"not_before":1458232846,"serial_number":"A0141420000015385736A0B85ECA708","subject":{"C":"US","CN":"Let's Encrypt Authority X3","L":null,"O":"Let's Encrypt","OU":null,"ST":null,"aggregated":"/C=US/CN=Let's Encrypt Authority X3/O=Let's Encrypt"}},{"as_der":"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ","extensions":{"basicConstraints":"CA:TRUE","keyUsage":"Key Cert Sign, C R L Sign","subjectKeyIdentifier":"C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10"},"fingerprint":"DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13","not_after":1633010475,"not_before":970348339,"serial_number":"44AFB080D6A327BA893039862EF8406B","subject":{"C":null,"CN":"DST Root CA X3","L":null,"O":"Digital Signature Trust Co.","OU":null,"ST":null,"aggregated":"/CN=DST Root CA X3/O=Digital Signature Trust Co."}}],"leaf_cert":{"all_domains":["baden-mueller.de","www.baden-mueller.de"],"as_der":"MIIEezCCA2OgAwIBAgISA1Gm0sew3z+8nJrpo5Jj1naBMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDA1MjQxODE3NDhaFw0yMDA4MjIxODE3NDhaMBsxGTAXBgNVBAMTEGJhZGVuLW11ZWxsZXIuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCSDtXT7WQQaNmnaH6+BxHwEz7eqHdTx/02HV7x/q9oozIKfWnfc4A3glkMdnJtZUjLlbV4sgAO4MBDNo65Qsq4L/GesRsVTczmYcAxnrfp8e/eK7wF08oqCvdHddXSHD82aXe/6Y6a3hiLEG+oBMDfG1Skwyt7NGNySlenz3EYEbc35IVoFKIkp2CyMV/nkKQPCgQBL10niEiQd9Q9bHDQJZsBtW59VVCy5K5kIPo6P5v295PCt0WTUppXagY2G/YGpQOmvsjl9MFjMZc4yOOd3RhGhcr2jgd9iF04TvownTxvQAU1EbKcXDHcoPVmhH5zDeiN1JbLNpW2wMf5Vr9VAgMBAAGjggGIMIIBhDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFE8R8swxvB64KS8VqcCaUcMFpEjAMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9yZy8wMQYDVR0RBCowKIIQYmFkZW4tbXVlbGxlci5kZYIUd3d3LmJhZGVuLW11ZWxsZXIuZGUwTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwEwYKKwYBBAHWeQIEAwEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAIUMEOxJNwvA/pWwFm0BR0HClGdzSC1vQprBaZ6cedUM6b/wfAiCPNJXCvJyrfhJp4T1GcnwU5VnMLPE1/nwJ6LY3My86/M+eQn/3HRuXu3p1GFpp5k2cXsHB7VRlw5X78XVvsnYKc6giwOan7L8fL136EcplQTZRc/5qu9hvazeBOBQQc/lCeWceWz0ZDDVbU2IGvY6aF/SAQREOSq8jLVpEoXB0zwq3dXeEi+PfC2Ea03eOpo1y11nmRYB5Usi/GjMi7oXuBxVQMolJXJj38ziJcp1TT1sv2Ha/00F+Pudo54w1NEo04DbDD9yB2H9wTlMM4YsArmD3K22OGA8wNE=","extensions":{"authorityInfoAccess":"CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\nOCSP - URI:http://ocsp.int-x3.letsencrypt.org\n","authorityKeyIdentifier":"keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1\n","basicConstraints":"CA:FALSE","certificatePolicies":"Policy: 1.3.6.1.4.1.44947.1.1.1\n CPS: http://cps.letsencrypt.org","ctlPoisonByte":true,"extendedKeyUsage":"TLS Web server authentication, TLS Web client authentication","keyUsage":"Digital Signature, Key Encipherment","subjectAltName":"DNS:www.baden-mueller.de, DNS:baden-mueller.de","subjectKeyIdentifier":"4F:11:F2:CC:31:BC:1E:B8:29:2F:15:A9:C0:9A:51:C3:05:A4:48:C0"},"fingerprint":"64:BF:49:41:3B:7A:FD:5D:C1:04:D9:44:64:9D:1C:25:13:A2:49:86","not_after":1598120268,"not_before":1590344268,"serial_number":"351A6D2C7B0DF3FBC9C9AE9A39263D67681","subject":{"C":null,"CN":"baden-mueller.de","L":null,"O":null,"OU":null,"ST":null,"aggregated":"/CN=baden-mueller.de"}},"seen":1590347943.736608,"source":{"name":"Google 'Argon2020' log","url":"ct.googleapis.com/logs/argon2020/"},"update_type":"PrecertLogEntry"},"message_type":"heartbeat"} \ No newline at end of file