Created
June 12, 2026 16:52
-
-
Save uplime/3f0703590f1e5192047c8f8e1a160675 to your computer and use it in GitHub Desktop.
Find missing links in an X.509 certificate chain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "crypto/x509" | |
| _ "embed" | |
| "encoding/base64" | |
| "errors" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "io/ioutil" | |
| "net/http" | |
| "os" | |
| "regexp" | |
| "runtime" | |
| "strings" | |
| ) | |
| type Certificate struct { | |
| Store *x509.Certificate | |
| } | |
| func LoadCertFromDER(der []byte) (Certificate, error) { | |
| cert := Certificate { Store: nil } | |
| store, err := x509.ParseCertificate(der) | |
| if err != nil { | |
| return cert, err | |
| } | |
| cert.Store = store | |
| return cert, nil | |
| } | |
| func LoadCertFromPEM(pem string) (Certificate, error) { | |
| cert := Certificate { Store: nil } | |
| if ! strings.HasPrefix(pem, "-----BEGIN CERTIFICATE-----") { | |
| return cert, errors.New("invalid certificate header") | |
| } else if ! strings.HasSuffix(pem, "-----END CERTIFICATE-----") { | |
| return cert, errors.New("invalid certificate footer") | |
| } | |
| encoded := strings.Split(pem, "\n") | |
| data := strings.Join(encoded[1:len(encoded) - 1], "\n") | |
| bytes, err := base64.StdEncoding.DecodeString(data) | |
| if err != nil { | |
| return cert, err | |
| } | |
| store, err := x509.ParseCertificate(bytes) | |
| if err != nil { | |
| return cert, err | |
| } | |
| cert.Store = store | |
| return cert, nil | |
| } | |
| func (cert Certificate) ToPEM() string { | |
| pem_export := "-----BEGIN CERTIFICATE-----\n" | |
| encoded := base64.StdEncoding.EncodeToString(cert.Store.Raw) | |
| idx := 0 | |
| for idx < len(encoded) { | |
| if idx + 64 < len(encoded) { | |
| pem_export += encoded[idx:idx + 64] + "\n" | |
| } else { | |
| pem_export += encoded[idx:] + "\n" | |
| } | |
| idx += 64 | |
| } | |
| pem_export += "-----END CERTIFICATE-----" | |
| return pem_export | |
| } | |
| func (cert Certificate) IsSelfSigned() bool { | |
| err := cert.Store.CheckSignatureFrom(cert.Store) | |
| return err == nil | |
| } | |
| func (cert Certificate) IsRoot() bool { | |
| return cert.IsSelfSigned() && cert.Store.IsCA | |
| } | |
| func (cert Certificate) IsIntermediate() bool { | |
| err := cert.Store.CheckSignatureFrom(cert.Store) | |
| return err == nil && cert.Store.IsCA | |
| } | |
| func (cert Certificate) IsLeaf() bool { | |
| if cert.Store.IsCA { | |
| return false | |
| } | |
| allowed_uses := []x509.ExtKeyUsage { | |
| x509.ExtKeyUsageAny, x509.ExtKeyUsageServerAuth, | |
| x509.ExtKeyUsageClientAuth, | |
| } | |
| for _, use := range cert.Store.ExtKeyUsage { | |
| good_use := false | |
| for _, allowed := range allowed_uses { | |
| if use == allowed { | |
| good_use = true | |
| } | |
| } | |
| if ! good_use { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func (cert Certificate) GetHTTPAuthorityURL() (string, bool) { | |
| for _, url := range cert.Store.IssuingCertificateURL { | |
| if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { | |
| return url, true | |
| } | |
| } | |
| return "", false | |
| } | |
| func (cert Certificate) Parent(parent Certificate) bool { | |
| return cert.Store.CheckSignatureFrom(parent.Store) == nil | |
| } | |
| type CertificateChain struct { | |
| Certificates []Certificate | |
| Leaf, Root *Certificate | |
| } | |
| func LoadCertsFromPEM(pem string) (CertificateChain, error) { | |
| chain := CertificateChain { | |
| Certificates: []Certificate { }, Leaf: nil, Root: nil, | |
| } | |
| parser, err := regexp.Compile("-----BEGIN CERTIFICATE-----\r?\n([A-Za-z0-9/+=\r\n]+)\r?\n-----END CERTIFICATE-----") | |
| if err != nil { | |
| return chain, err | |
| } | |
| matches := parser.FindAllStringSubmatch(string(pem), -1) | |
| for _, match := range matches { | |
| cert, err := LoadCertFromPEM(match[0]) | |
| if err != nil { | |
| return chain, err | |
| } | |
| chain.Certificates = append(chain.Certificates, cert) | |
| chain.Root = &chain.Certificates[len(chain.Certificates) - 1] | |
| } | |
| if len(chain.Certificates) == 0 { | |
| return chain, errors.New("no certificates found") | |
| } | |
| chain.Leaf = &chain.Certificates[0] | |
| return chain, nil | |
| } | |
| //go:embed roots.pem | |
| var BAKED_IN_ROOTS string | |
| func LoadSystemStore() (CertificateChain, error) { | |
| cert_path := "" | |
| if path, has := os.LookupEnv("SSL_CERT_FILE"); has { | |
| cert_path = path | |
| } else if runtime.GOOS == "linux" { | |
| cert_path = "/etc/ssl/certs/ca-certificates.crt" | |
| } else if runtime.GOOS == "darwin" { | |
| cert_path = "/etc/ssl/cert.pem" | |
| } | |
| if cert_path == "" { | |
| return LoadCertsFromPEM(BAKED_IN_ROOTS) | |
| } | |
| pem, err := os.ReadFile(cert_path) | |
| if err != nil { | |
| return CertificateChain { }, err | |
| } | |
| return LoadCertsFromPEM(string(pem)) | |
| } | |
| func (chain CertificateChain) ToPEM() string { | |
| pem_export := "" | |
| for _, link := range chain.Certificates { | |
| pem_export += link.ToPEM() + "\n" | |
| } | |
| return pem_export | |
| } | |
| func (chain *CertificateChain) Prepend(cert Certificate) { | |
| chain.Certificates = append([]Certificate { cert }, chain.Certificates...) | |
| chain.Leaf = &chain.Certificates[0] | |
| } | |
| func (chain *CertificateChain) Append(cert Certificate) { | |
| chain.Certificates = append(chain.Certificates, cert) | |
| chain.Root = &chain.Certificates[len(chain.Certificates) - 1] | |
| } | |
| func (chain CertificateChain) IsLinked() bool { | |
| for idx, link := range chain.Certificates[1:] { | |
| err := chain.Certificates[idx].Store.CheckSignatureFrom(link.Store) | |
| if err != nil { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func (chain CertificateChain) IsComplete(test_links ...bool) bool { | |
| if len(test_links) > 0 && test_links[0] { | |
| return chain.Root.IsRoot() && chain.Leaf.IsLeaf() && chain.IsLinked() | |
| } else { | |
| return chain.Root.IsRoot() && chain.Leaf.IsLeaf() | |
| } | |
| } | |
| func (chain CertificateChain) Links() int { | |
| return len(chain.Certificates) | |
| } | |
| func main() { | |
| has_sys_pool := true | |
| sys_pool, err := LoadSystemStore() | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "error: unable to load system pool: %s\n", err) | |
| has_sys_pool = false | |
| } | |
| flag.Parse() | |
| args := flag.Args() | |
| for _, arg := range args { | |
| pem, err := os.ReadFile(arg) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| continue | |
| } | |
| chain, err := LoadCertsFromPEM(string(pem)) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| continue | |
| } else if chain.Leaf.IsSelfSigned() { | |
| fmt.Fprintf(os.Stderr, "%s: certificate is self-signed\n", arg) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| continue | |
| } else if ! chain.IsLinked() { | |
| fmt.Fprintf(os.Stderr, "%s: error: certificates do not form a chain\n", arg) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| continue | |
| } | |
| for ! chain.Root.IsRoot() { | |
| found_link := false | |
| aia_url, found_url := chain.Root.GetHTTPAuthorityURL() | |
| if found_url { | |
| res, err := http.Get(aia_url) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| if ! has_sys_pool { | |
| fmt.Fprintf(os.Stderr, "%s: attempting alternate chain discovery methods\n", arg) | |
| } | |
| } else { | |
| cert_data, err := io.ReadAll(res.Body) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| if ! has_sys_pool { | |
| fmt.Fprintf(os.Stderr, "%s: attempting alternate chain discovery methods\n", arg) | |
| } | |
| } else { | |
| cert := Certificate { Store: nil } | |
| mime := "der" | |
| switch res.Header.Get("content-type") { | |
| case "application/pkix-cer": mime = "der" | |
| case "application/pkix-cert": mime = "der" | |
| case "application/x-x509-ca-cert": mime = "der" | |
| case "application/x-pem-file": mime = "pem" | |
| } | |
| if strings.HasSuffix(aia_url, ".der") || mime == "der" { | |
| link, err := LoadCertFromDER(cert_data) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| if ! has_sys_pool { | |
| fmt.Fprintf(os.Stderr, "%s: attempting alternate chain discovery methods\n", arg) | |
| } | |
| } else { | |
| cert = link | |
| found_link = true | |
| } | |
| } else if strings.HasSuffix(aia_url, ".pem") || mime == "pem" { | |
| link, err := LoadCertFromPEM(string(cert_data)) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| if ! has_sys_pool { | |
| fmt.Fprintf(os.Stderr, "%s: attempting alternate chain discovery methods\n", arg) | |
| } | |
| } else { | |
| cert = link | |
| found_link = true | |
| } | |
| } | |
| if found_link { | |
| chain.Append(cert) | |
| } else { | |
| fmt.Fprintf(os.Stderr, "%s: unable to determine link file type\n", arg) | |
| if ! has_sys_pool { | |
| fmt.Fprintf(os.Stderr, "%s: attempting alternate chain discovery methods\n", arg) | |
| } | |
| } | |
| } | |
| res.Body.Close() | |
| } | |
| } | |
| if ! found_link && has_sys_pool { | |
| for _, sys_cert := range sys_pool.Certificates { | |
| if chain.Root.Parent(sys_cert) { | |
| chain.Append(sys_cert) | |
| found_link = true | |
| break | |
| } | |
| } | |
| } | |
| if ! found_link { | |
| break | |
| } | |
| } | |
| if chain.IsComplete(true) { | |
| file, err := ioutil.TempFile("", "*-chain.pem") | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| } else { | |
| if _, err := file.Write([]byte(chain.ToPEM())); err != nil { | |
| fmt.Fprintf(os.Stderr, "%s: error: %s\n", arg, err) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| } else { | |
| fmt.Printf("%s: complete chain has been saved to %s\n", arg, file.Name()) | |
| } | |
| file.Close() | |
| } | |
| } else { | |
| fmt.Fprintf(os.Stderr, "%s: unable to determine next link in chain\n", arg) | |
| fmt.Fprintf(os.Stderr, "%s: skipping file...\n", arg) | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -----BEGIN CERTIFICATE----- | |
| MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx | |
| EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT | |
| EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp | |
| ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz | |
| NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH | |
| EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE | |
| AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw | |
| DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD | |
| E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH | |
| /PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy | |
| DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh | |
| GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR | |
| tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA | |
| AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE | |
| FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX | |
| WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu | |
| 9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr | |
| gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo | |
| 2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO | |
| LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI | |
| 4uJEvlz36hz1 | |
| -----END CERTIFICATE----- | |
| -----BEGIN CERTIFICATE----- | |
| MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs | |
| MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 | |
| d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j | |
| ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL | |
| MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 | |
| LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug | |
| RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm | |
| +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW | |
| PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM | |
| xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB | |
| Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 | |
| hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg | |
| EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF | |
| MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA | |
| FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec | |
| nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z | |
| eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF | |
| hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 | |
| Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe | |
| vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep | |
| +OkuE6N36B9K | |
| -----END CERTIFICATE----- | |
| -----BEGIN CERTIFICATE----- | |
| MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw | |
| TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh | |
| cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 | |
| WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu | |
| ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY | |
| MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc | |
| h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ | |
| 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U | |
| A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW | |
| T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH | |
| B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC | |
| B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv | |
| KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn | |
| OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn | |
| jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw | |
| qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI | |
| rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV | |
| HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq | |
| hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL | |
| ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ | |
| 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK | |
| NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 | |
| ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur | |
| TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC | |
| jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc | |
| oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq | |
| 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA | |
| mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d | |
| emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= | |
| -----END CERTIFICATE----- | |
| -----BEGIN CERTIFICATE----- | |
| MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH | |
| MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM | |
| QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy | |
| MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl | |
| cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB | |
| AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM | |
| f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX | |
| mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 | |
| zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P | |
| fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc | |
| vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 | |
| Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp | |
| zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO | |
| Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW | |
| k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ | |
| DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF | |
| lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV | |
| HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW | |
| Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 | |
| d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z | |
| XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR | |
| gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 | |
| d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv | |
| J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg | |
| DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM | |
| +SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy | |
| F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 | |
| SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws | |
| E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl | |
| -----END CERTIFICATE----- | |
| -----BEGIN CERTIFICATE----- | |
| MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF | |
| ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 | |
| b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL | |
| MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv | |
| b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj | |
| ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM | |
| 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw | |
| IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 | |
| VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L | |
| 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm | |
| jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC | |
| AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA | |
| A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI | |
| U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs | |
| N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv | |
| o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU | |
| 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy | |
| rqXRfboQnoZsG4q5WTP468SQvvG5 | |
| -----END CERTIFICATE----- | |
| -----BEGIN CERTIFICATE----- | |
| MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ | |
| RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD | |
| VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX | |
| DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y | |
| ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy | |
| VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr | |
| mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr | |
| IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK | |
| mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu | |
| XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy | |
| dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye | |
| jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 | |
| BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 | |
| DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 | |
| 9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx | |
| jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 | |
| Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz | |
| ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS | |
| R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp | |
| -----END CERTIFICATE----- | |
| -----BEGIN CERTIFICATE----- | |
| MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC | |
| VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T | |
| U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp | |
| Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx | |
| NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv | |
| dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv | |
| bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 | |
| AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA | |
| VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku | |
| WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP | |
| MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX | |
| 5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ | |
| ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg | |
| h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== | |
| -----END CERTIFICATE----- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment