mirror of
https://github.com/ribbybibby/ssl_exporter.git
synced 2026-02-20 12:39:51 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bb799094c | ||
|
|
39c4ef7937 | ||
|
|
f17849f9b2 | ||
|
|
7e59584659 | ||
|
|
645d7a6e42 | ||
|
|
1af3b2a3b2 | ||
|
|
7e95d03166 |
11
README.md
11
README.md
@@ -19,6 +19,10 @@ Similarly to the blackbox_exporter, visiting [http://localhost:9219/probe?target
|
||||
## Flags
|
||||
./ssl_exporter --help
|
||||
* __`--tls.insecure`:__ Skip certificate verification (default false). This is insecure but does allow you to collect metrics in the case where a certificate has expired. That being said, I feel that it's more important to catch verification failures than it is to identify an expired certificate, especially as the former includes the latter.
|
||||
* __`--tls.cacert`:__ Provide the path to an alternative bundle of root CA certificates. By default the exporter will use the host's root CA set.
|
||||
* __`--tls.client-auth`:__ Enable client authentication (default false). When enabled the exporter will present the certificate and key configured by `--tls.cert` and `tls.key` to the other side of the connection.
|
||||
* __`--tls.cert`:__ The path to a local certificate for client authentication (default "cert.pem"). Only used when `--tls.client-auth` is toggled on.
|
||||
* __`--tls.key`:__ The path to a local key for client authentication (default "key.pem"). Only used when `--tls.client-auth` is toggled on.
|
||||
* __`--web.listen-address`:__ The port (default ":9219").
|
||||
* __`--web.metrics-path`:__ The path metrics are exposed under (default "/metrics")
|
||||
* __`--web.probe-path`:__ The path the probe endpoint is exposed under (default "/probe")
|
||||
@@ -77,6 +81,13 @@ Identify instances that have failed to create a valid SSL connection:
|
||||
|
||||
ssl_https_connect_success == 0
|
||||
|
||||
## Client authentication
|
||||
The exporter optionally supports client authentication, which can be toggled on by providing the `--tls.client-auth` flag. By default, it will use the host system's root CA bundle and attempt to use `./cert.pem` and `./key.pem` as the client certificate and key, respectively. You can override these defaults with `--tls.cacert`, `--tls.cert` and `--tls.key`.
|
||||
|
||||
If you do enable client authentication, keep in mind that the certificate will be passed to all targets, even those that don't necessarily require client authentication. I'm not sure what the implications of that are but I think you'd probably want to avoid passing a certificate to an unrelated server.
|
||||
|
||||
Also, if you want to scrape targets with different client certificate requirements, you'll need to run different instances of the exporter for each. This seemed like a better approach than overloading the exporter with the ability to pass different certificates per-target.
|
||||
|
||||
## Limitations
|
||||
I've only exported a subset of the information you could extract from a certificate. It would be simple to add more, for instance organisational information, if there's a need.
|
||||
|
||||
|
||||
102
ssl_exporter.go
102
ssl_exporter.go
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -48,12 +49,12 @@ var (
|
||||
)
|
||||
subjectAlernativeIPs = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "cert_subject_alternative_ips"),
|
||||
"Subject Alternative DNS Names",
|
||||
"Subject Alternative IPs",
|
||||
[]string{"serial_no", "issuer_cn", "ips"}, nil,
|
||||
)
|
||||
subjectAlernativeEmailAddresses = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "cert_subject_alternative_emails"),
|
||||
"Subject Alternative DNS Names",
|
||||
"Subject Alternative Email Addresses",
|
||||
[]string{"serial_no", "issuer_cn", "emails"}, nil,
|
||||
)
|
||||
subjectOrganizationUnits = prometheus.NewDesc(
|
||||
@@ -63,12 +64,14 @@ var (
|
||||
)
|
||||
)
|
||||
|
||||
// Exporter is the exporter type...
|
||||
type Exporter struct {
|
||||
target string
|
||||
timeout time.Duration
|
||||
insecure bool
|
||||
target string
|
||||
timeout time.Duration
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// Describe metrics
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- httpsConnectSuccess
|
||||
ch <- notAfter
|
||||
@@ -79,11 +82,12 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- subjectOrganizationUnits
|
||||
}
|
||||
|
||||
// Collect metrics
|
||||
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
|
||||
|
||||
// Create the HTTP client and make a get request of the target
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: e.insecure},
|
||||
TLSClientConfig: e.tlsConfig,
|
||||
}
|
||||
client := &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
@@ -114,68 +118,69 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
|
||||
httpsConnectSuccess, prometheus.GaugeValue, 1,
|
||||
)
|
||||
|
||||
peer_certificates := uniq(resp.TLS.PeerCertificates)
|
||||
// Remove duplicate certificates from the response
|
||||
peerCertificates := uniq(resp.TLS.PeerCertificates)
|
||||
|
||||
// Loop through returned certificates and create metrics
|
||||
for _, cert := range peer_certificates {
|
||||
for _, cert := range peerCertificates {
|
||||
|
||||
subject_cn := cert.Subject.CommonName
|
||||
issuer_cn := cert.Issuer.CommonName
|
||||
subject_dnsn := cert.DNSNames
|
||||
subject_emails := cert.EmailAddresses
|
||||
subject_ips := cert.IPAddresses
|
||||
serial_no := cert.SerialNumber.String()
|
||||
subject_ous := cert.Subject.OrganizationalUnit
|
||||
subjectCN := cert.Subject.CommonName
|
||||
issuerCN := cert.Issuer.CommonName
|
||||
subjectDNSNames := cert.DNSNames
|
||||
subjectEmails := cert.EmailAddresses
|
||||
subjectIPs := cert.IPAddresses
|
||||
serialNum := cert.SerialNumber.String()
|
||||
subjectOUs := cert.Subject.OrganizationalUnit
|
||||
|
||||
if !cert.NotAfter.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
notAfter, prometheus.GaugeValue, float64(cert.NotAfter.UnixNano()/1e9), serial_no, issuer_cn,
|
||||
notAfter, prometheus.GaugeValue, float64(cert.NotAfter.UnixNano()/1e9), serialNum, issuerCN,
|
||||
)
|
||||
}
|
||||
|
||||
if !cert.NotBefore.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
notBefore, prometheus.GaugeValue, float64(cert.NotBefore.UnixNano()/1e9), serial_no, issuer_cn,
|
||||
notBefore, prometheus.GaugeValue, float64(cert.NotBefore.UnixNano()/1e9), serialNum, issuerCN,
|
||||
)
|
||||
}
|
||||
|
||||
if subject_cn != "" {
|
||||
if subjectCN != "" {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
commonName, prometheus.GaugeValue, 1, serial_no, issuer_cn, subject_cn,
|
||||
commonName, prometheus.GaugeValue, 1, serialNum, issuerCN, subjectCN,
|
||||
)
|
||||
}
|
||||
|
||||
if len(subject_dnsn) > 0 {
|
||||
if len(subjectDNSNames) > 0 {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
subjectAlernativeDNSNames, prometheus.GaugeValue, 1, serial_no, issuer_cn, ","+strings.Join(subject_dnsn, ",")+",",
|
||||
subjectAlernativeDNSNames, prometheus.GaugeValue, 1, serialNum, issuerCN, ","+strings.Join(subjectDNSNames, ",")+",",
|
||||
)
|
||||
}
|
||||
|
||||
if len(subject_emails) > 0 {
|
||||
if len(subjectEmails) > 0 {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
subjectAlernativeEmailAddresses, prometheus.GaugeValue, 1, serial_no, issuer_cn, ","+strings.Join(subject_emails, ",")+",",
|
||||
subjectAlernativeEmailAddresses, prometheus.GaugeValue, 1, serialNum, issuerCN, ","+strings.Join(subjectEmails, ",")+",",
|
||||
)
|
||||
}
|
||||
|
||||
if len(subject_ips) > 0 {
|
||||
if len(subjectIPs) > 0 {
|
||||
i := ","
|
||||
for _, ip := range subject_ips {
|
||||
for _, ip := range subjectIPs {
|
||||
i = i + ip.String() + ","
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
subjectAlernativeIPs, prometheus.GaugeValue, 1, serial_no, issuer_cn, i,
|
||||
subjectAlernativeIPs, prometheus.GaugeValue, 1, serialNum, issuerCN, i,
|
||||
)
|
||||
}
|
||||
|
||||
if len(subject_ous) > 0 {
|
||||
if len(subjectIPs) > 0 {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
subjectOrganizationUnits, prometheus.GaugeValue, 1, serial_no, issuer_cn, ","+strings.Join(subject_ous, ",")+",",
|
||||
subjectOrganizationUnits, prometheus.GaugeValue, 1, serialNum, issuerCN, ","+strings.Join(subjectOUs, ",")+",",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func probeHandler(w http.ResponseWriter, r *http.Request, insecure bool) {
|
||||
func probeHandler(w http.ResponseWriter, r *http.Request, tlsConfig *tls.Config) {
|
||||
|
||||
target := r.URL.Query().Get("target")
|
||||
|
||||
@@ -199,9 +204,9 @@ func probeHandler(w http.ResponseWriter, r *http.Request, insecure bool) {
|
||||
timeout := time.Duration((timeoutSeconds) * 1e9)
|
||||
|
||||
exporter := &Exporter{
|
||||
target: target,
|
||||
timeout: timeout,
|
||||
insecure: insecure,
|
||||
target: target,
|
||||
timeout: timeout,
|
||||
tlsConfig: tlsConfig,
|
||||
}
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
@@ -239,10 +244,17 @@ func init() {
|
||||
|
||||
func main() {
|
||||
var (
|
||||
tlsConfig *tls.Config
|
||||
certificates []tls.Certificate
|
||||
rootCAs *x509.CertPool
|
||||
listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9219").String()
|
||||
metricsPath = kingpin.Flag("web.metrics-path", "Path under which to expose metrics").Default("/metrics").String()
|
||||
probePath = kingpin.Flag("web.probe-path", "Path under which to expose the probe endpoint").Default("/probe").String()
|
||||
insecure = kingpin.Flag("tls.insecure", "Skip certificate verification").Default("false").Bool()
|
||||
clientAuth = kingpin.Flag("tls.client-auth", "Enable client authentication").Default("false").Bool()
|
||||
caFile = kingpin.Flag("tls.cacert", "Local path to an alternative CA cert bundle").String()
|
||||
certFile = kingpin.Flag("tls.cert", "Local path to a client certificate file (for client authentication)").Default("cert.pem").String()
|
||||
keyFile = kingpin.Flag("tls.key", "Local path to a private key file (for client authentication)").Default("key.pem").String()
|
||||
)
|
||||
|
||||
log.AddFlags(kingpin.CommandLine)
|
||||
@@ -250,12 +262,36 @@ func main() {
|
||||
kingpin.HelpFlag.Short('h')
|
||||
kingpin.Parse()
|
||||
|
||||
if *caFile != "" {
|
||||
caCert, err := ioutil.ReadFile(*caFile)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
rootCAs = x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
}
|
||||
|
||||
if *clientAuth {
|
||||
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: *insecure,
|
||||
Certificates: certificates,
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
|
||||
log.Infoln("Starting "+namespace+"_exporter", version.Info())
|
||||
log.Infoln("Build context", version.BuildContext())
|
||||
|
||||
http.Handle(*metricsPath, prometheus.Handler())
|
||||
http.HandleFunc(*probePath, func(w http.ResponseWriter, r *http.Request) {
|
||||
probeHandler(w, r, *insecure)
|
||||
probeHandler(w, r, tlsConfig)
|
||||
})
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`<html>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
@@ -10,29 +14,52 @@ import (
|
||||
)
|
||||
|
||||
func TestProbeHandler(t *testing.T) {
|
||||
certContent, err := ioutil.ReadFile("test/badssl.com-client.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("Can't read test client certificate from disk")
|
||||
}
|
||||
|
||||
keyContent, err := ioutil.ReadFile("test/badssl.com-client-key.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("Can't read test client certificate key from disk")
|
||||
}
|
||||
|
||||
keyBlock, _ := pem.Decode(keyContent)
|
||||
|
||||
keyBlockDecrypted, err := x509.DecryptPEMBlock(keyBlock, []byte("badssl.com"))
|
||||
if err != nil {
|
||||
t.Fatalf("Issue decrypting test client key")
|
||||
}
|
||||
|
||||
emptyRootCAs := x509.NewCertPool()
|
||||
|
||||
certificate, err := tls.X509KeyPair(certContent, keyBlockDecrypted)
|
||||
|
||||
// Test the behaviour of various target URIs
|
||||
// 'ok' denotes whether we expect a succesful https connection
|
||||
// 'insecure' denotes whether we ignore invalid certs
|
||||
cases := []struct {
|
||||
uri string
|
||||
ok bool
|
||||
insecure bool
|
||||
uri string
|
||||
ok bool
|
||||
tlsConfig *tls.Config
|
||||
}{
|
||||
// Test against an assumed valid, reachable and functioning HTTPS address
|
||||
{uri: "https://google.com", ok: true, insecure: false},
|
||||
{uri: "https://google.com", ok: true, tlsConfig: &tls.Config{}},
|
||||
// Test against a HTTP address
|
||||
{uri: "http://google.com", ok: false, insecure: false},
|
||||
{uri: "http://google.com", ok: false, tlsConfig: &tls.Config{}},
|
||||
// Test against an expired certificate when we're rejecting invalid certs
|
||||
{uri: "https://expired.badssl.com", ok: false, insecure: false},
|
||||
{uri: "https://expired.badssl.com", ok: false, tlsConfig: &tls.Config{}},
|
||||
// Test against an expired certificate when we're accepting invalid certs
|
||||
{uri: "https://expired.badssl.com", ok: true, insecure: true},
|
||||
{uri: "https://expired.badssl.com", ok: true, tlsConfig: &tls.Config{InsecureSkipVerify: true}},
|
||||
// Test against a target with no protocol
|
||||
{uri: "google.com", ok: false, insecure: false},
|
||||
{uri: "google.com", ok: false, tlsConfig: &tls.Config{}},
|
||||
// Test against a string with spaces
|
||||
{uri: "with spaces", ok: false, insecure: false},
|
||||
{uri: "with spaces", ok: false, tlsConfig: &tls.Config{}},
|
||||
// Test against nothing
|
||||
{uri: "", ok: false, insecure: false},
|
||||
{uri: "", ok: false, tlsConfig: &tls.Config{}},
|
||||
// Test with client authentication
|
||||
{uri: "https://client.badssl.com", ok: true, tlsConfig: &tls.Config{Certificates: []tls.Certificate{certificate}}},
|
||||
// Test with an empty root CA bundle
|
||||
{uri: "https://google.com", ok: false, tlsConfig: &tls.Config{RootCAs: emptyRootCAs}},
|
||||
}
|
||||
|
||||
fmt.Println("Note: The error logs in these tests are expected. One of the important tests is that we return the expected body, even in the face of errors.")
|
||||
@@ -47,7 +74,7 @@ func TestProbeHandler(t *testing.T) {
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
probeHandler(w, r, test.insecure)
|
||||
probeHandler(w, r, test.tlsConfig)
|
||||
})
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
30
test/badssl.com-client-key.pem
Normal file
30
test/badssl.com-client-key.pem
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,8906E5B87ECB8682
|
||||
|
||||
cY/EOwDILOiCPzyqZanlrhrb5h+fCnS/mHhsNme0Zyrk9udaBZKwxkPtUC6rJZ9/
|
||||
rX4HQch9tuVy992CDe1bl4l+3EfqshUhxtivG8GHiFj7TNo5Ia9LaE+KwugB2rdX
|
||||
J8odwWmwE8Y9TX09WyPKHikjg2nAOgN3Gf+GeJW6fL9xcEVmIgDyX6kBusQYWIQ3
|
||||
PMYqyaBhL8UCZVRpiUINTrJXCnNAI4t6K0i4J+hjyekRnA5PQ+A/0+C9J+kQW+wj
|
||||
uDWkQtyyBNsscGhXrL9ita7/UtJa+/aS99FLTnxD+fqyU4svzNIQWmZBcIxb9Lxq
|
||||
LHcxNaQCtFycD1iU8Tsq9HY1apKrdPBjaaYne6qCg8nIPWpo3gWoffu+R3EfVcdZ
|
||||
lNgKL3Cd1oCJy/Vcz+u0iayCPT0zfP1dLlGpL2U7L7+8pJ88FbXt/F1th9QEUp5z
|
||||
dISg/6ukHyIkfXwoxYXkin9fW2clo99+fuXkJaVPEOnRmr0+kf+1S0Uw8fkete4v
|
||||
f0IUxUwydxCXFN2ZFZ6o/LFLMLRfNAqGSGjqGeY1L0ILkJPo3KBqlmUhl2FIROOW
|
||||
4rSrfpujjsOqlPncJ9apW04RnqhY7t0YOtp0rMK+gIDteKD+utCyh+UAetiiqTiB
|
||||
ZDup/kzPNDClSDU6cgRbZUf/Nt8RiBcVeX4TfDb1eEhqV0BTHrLOBo8yp6ETDlJM
|
||||
CKO5i9kb5fUaFPFD5VTc5rnY4qV/hUj/uyAVK757A+m9nn7fYOMeaRAgOEvChYSf
|
||||
Qam0VPgpJxfjsaw0BFOMyfsHNJqvTbPXA+hIKD3fhP6jNcgpKTLHJp89J4XUOeIR
|
||||
tvh/u7vljhVygP7ZQOpCoX1xSMdJMO7Qhrh3O/2fq/EJertJD2PQ0Zgqb8wosHn/
|
||||
Aw9VWT6849jL55Xd5l3zXmI9vU0Le4HP3NstV9jjpcp91dU9yfQpcuIo8U26u+4r
|
||||
LR1VmhZJjo7FBOpyJZ1Jb3vyp+nPI+tH214DhM9LuXvMbf3ORhXTMOlqSABUmo/+
|
||||
t+QjVfcEuhFR9CTVWZUIXflJk/euvzqTQdm8iz7JuFzQOhoXjiPIq0GqtCk10SeL
|
||||
zqHz1s0TZNcrZyzkmiHuWjGVwHN/XZA3dW67uj522hD7EzucKE9CoCdJ3f4JEWmS
|
||||
CQwEbba6SKAR6iBlouIYLVdkOBgimGiF13rCwvdN234hQeJT8Wc87iG+uDw73PJL
|
||||
+amDglATH4wIpBk4xmjh/GTRDK0yH9jp7Dv9iwShbjk0yOuoz5yDn8VPqRIREn8d
|
||||
9sAaiFUUQ/9XrdlA5F+49OznClDWLKHK8sSAAFyrzvoCcqseSKbvLyrlHGT0fiot
|
||||
obgDu/W+K2xEOjQeaIyVI5J1qOi6k78fyv1vutjEs6mcTRtDAIxi+V5y5lXUEj0v
|
||||
OWYsbp9yb8Yq602vV8UYSROd+1xdE+7Td3ENLYE7MnVqju7a5NRfnZYgAU03NgIf
|
||||
nHGFZC6/tMz/PXS+D0dqzXxwEjH5JQzGBjvSQHK09gHtCfcyshMQQWtXZGQViZX8
|
||||
QdYXiaq67nJex0DjWTt56a4EgsdYC1J28bJ3GAkrWNkDFRmlx49zvA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
27
test/badssl.com-client.pem
Normal file
27
test/badssl.com-client.pem
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEnTCCAoWgAwIBAgIJAPC7KMFjfslXMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
|
||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
|
||||
c2NvMQ8wDQYDVQQKDAZCYWRTU0wxMTAvBgNVBAMMKEJhZFNTTCBDbGllbnQgUm9v
|
||||
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTcxMTE2MDUzNjMzWhcNMTkxMTE2
|
||||
MDUzNjMzWjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
|
||||
A1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NMMSIwIAYDVQQDDBlC
|
||||
YWRTU0wgQ2xpZW50IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPoen08
|
||||
utVhjPmlxIUdkiMG4+ffe7N+JtDLG75CaxZp9CxytX7kywooRBJsRnQhmQPca8MR
|
||||
WAJBIz+w/L+3AFkTIqWBfyT+1VO8TVKPkEpGdLDovZOmzZAASi9/sj+j6gM7AaCi
|
||||
DeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct4MG8
|
||||
w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrjQ7i/
|
||||
s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABoy0wKzAJBgNVHRMEAjAAMBEG
|
||||
CWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIB
|
||||
AKpzk1ZTunWuof3DIer2Abq7IV3STGeFaoH4TuHdSbmXwC0KuPkv7wVPgPekyRaH
|
||||
b9CBnsreRF7eleD1M63kakhdnA1XIbdJw8sfSDlKdI4emmb4fzdaaPxbrkQ5IxOB
|
||||
QDw5rTUFVPPqFWw1bGP2zrKD1/i1pxUtGM0xem1jR7UZYpsSPs0JCOHKZOmk8OEW
|
||||
Uy+Jp4gRzbMLZ0TrvajGEZXRepjOkXObR81xZGtvTNP2wl1zm13ffwIYdqJUrf1H
|
||||
H4miU9lVX+3/Z+2mVHBWhzBgbTmo06s3uwUE6JsxUGm2/w4NNblRit0uQcGw7ba8
|
||||
kl2d5rZQscFsqNFz2vRjj1G0dO8S3owmuF0izZO9Fqvq0jB6oaUkxcAcTKFSjs2z
|
||||
wy1oy+cu8iO3GRbfAW7U0xzGp9MnkdPS5dHzvhod3/DK0YVskfxZF7M8GhkjT7Qm
|
||||
2EUBQNNMNXC3g/GXTdXOgqqjW5GXahI8Z6Q4OYN6xZwuEhizwKkgojwaww2YgYT9
|
||||
MJXciJZWr3QXvFdBH7m0zwpKgQ1wm6j3yeyuRphq2lEtU3OQl55A3tXtvqyMXsxk
|
||||
xMCCNQdmKQt0WYmMS3Xj/AfAY2sjCWziDflvW5mGCUjSYdZ+r3JIIF4m/FNCIO1d
|
||||
Ioacp9qb0qL9duFlVHtFiPgoKrEdJaNVUL7NG9ppF8pR
|
||||
-----END CERTIFICATE-----
|
||||
Reference in New Issue
Block a user