]> go.fuhry.dev Git - osquery.git/commitdiff
add x509_certificates table main
authorDan Fuhry <dan@fuhry.com>
Mon, 28 Jul 2025 17:27:10 +0000 (13:27 -0400)
committerDan Fuhry <dan@fuhry.com>
Mon, 28 Jul 2025 17:27:10 +0000 (13:27 -0400)
19 files changed:
.gitignore
MODULE.bazel
README.md
cmd/flatpak/BUILD.bazel
cmd/pacman/BUILD.bazel
cmd/x509_certificates/BUILD.bazel [new file with mode: 0644]
cmd/x509_certificates/main.go [new file with mode: 0644]
go.mod
go.sum
x509_certificates/BUILD.bazel [new file with mode: 0644]
x509_certificates/plugin.go [new file with mode: 0644]
x509_certificates/plugin_test.go [new file with mode: 0644]
x509_certificates/testdata/all-crts.crt [new file with mode: 0644]
x509_certificates/testdata/ecdsa-p256.crt [new file with mode: 0644]
x509_certificates/testdata/ecdsa-p384.crt [new file with mode: 0644]
x509_certificates/testdata/ecdsa-p521.crt [new file with mode: 0644]
x509_certificates/testdata/generate.sh [new file with mode: 0755]
x509_certificates/testdata/rsa-2048.crt [new file with mode: 0644]
x509_certificates/testdata/rsa-4096.crt [new file with mode: 0644]

index b5b830bb3808ef3b4b5a2bdc86bdae2f29810a6b..d3247633bc8caaf5ba0b57dbb60be976b4868310 100644 (file)
@@ -2,3 +2,4 @@ bazel-bin
 bazel-osquery
 bazel-out
 bazel-testlogs
+x509_certificates/testdata/*.key
index 2420ab601bdd5f1a4aed0ee89235478260b21ddd..ce61d5bb51361e4fa2e3a4e5524f788cbc72c56a 100644 (file)
@@ -22,4 +22,5 @@ use_repo(
        "com_github_hashicorp_golang_lru_v2",
        "com_github_linuxdeepin_go_lib",
        "com_github_jguer_go_alpm_v2",
+       "com_github_stretchr_testify",
 )
\ No newline at end of file
index 527ad87c15b04b81c831025dd65fc6141b8edb71..6597529885ba1a5ba842e9d8b484810f8d698183 100644 (file)
--- a/README.md
+++ b/README.md
@@ -62,6 +62,41 @@ CREATE TABLE flatpak_packages(
 );
 ```
 
+### `x509_certificates`
+
+Provides the `x509_certificates` table.
+
+Queries against this table must include a `WHERE path = 'absolute path'` constraint.
+
+This table will discover multiple certificates in a single file, distinguished by the `index` column.
+
+Schema:
+
+```
+osquery> .schema x509_certificates
+CREATE TABLE x509_certificates(
+    `path` TEXT,                   -- Absolute path of the certificate file
+    `index` INT,                   -- Indicates the certificate's position in the file
+    `error` TEXT,                  -- Set to non-empty string if there was an error parsing the certificate
+    `encoding` TEXT,               -- "DER" or "PEM"
+    `subject` TEXT,                -- X.500 string-encoded subject name
+    `issuer` TEXT,                 -- X.500 string-encoded issuer name
+    `serial` TEXT,                 -- Hex-encoded serial number of the certificate
+    `is_ca` INTEGER,               -- Set to "1" if the certificate has the "CA" flag set, "0" otherwise
+    `public_key` TEXT,             -- PKCS#8-encoded public key of the certificate
+    `public_key_algorithm` TEXT,   -- "RSA" and "ECDSA" are currently supported; empty string otherwise
+    `public_key_size` INTEGER,     -- Normalized length of the public key, in bits
+    `alt_names` TEXT,              -- Comma-separated list of subject alternative names. Each will have one of the following prefixes: "DNS:", "MAIL:", "IP:" or "URI:". Order is not guaranteed.
+    `not_before` BIGINT,           -- Seconds since epoch of when the certificate becomes valid
+    `not_after` BIGINT,            -- Seconds since epoch of when the certificate expires
+    `remaining_ttl` INTEGER,       -- Seconds until the certificate expires. Set to 0 if the certificate is expired or not valid yet.
+    `remaining_pct` INTEGER,       -- Percentage of time remaining in the certificate's validity period. Set to 0 if the certificate is expired or not valid yet.
+    `valid_now` INTEGER,           -- Set to 1 if the current time is between the certificate's NotBefore and NotAfter timestamps; 0 otherwise.
+    `sha1_thumbprint` TEXT,        -- Hex-encoded SHA-1 digest of the certificate's DER-encoded form.
+    `sha256_thumbprint` TEXT,      -- Hex-encoded SHA-256 digest of the certificate's DER-encoded form.
+);
+```
+
 ## Author/License
 
 Written by Dan Fuhry <dan@fuhry.com>
index 74bec192e3499b48bb87e01a4b17d8b5ffbf9141..e84463d017da3547a85a4dff62b2cef0b8529624 100644 (file)
@@ -3,7 +3,7 @@ load("@rules_go//go:def.bzl", "go_binary", "go_library")
 go_library(
     name = "flatpak_lib",
     srcs = ["main.go"],
-    importpath = "go.fuhry.dev/osquery/flatpak/cmd/flatpak",
+    importpath = "go.fuhry.dev/osquery/cmd/flatpak",
     visibility = ["//visibility:private"],
     deps = [
         "//extcommon",
index d5065b04be4ee8d51c77679f178fe17702dd65c6..382bc2ab039f5e02db15915c1919559769e75555 100644 (file)
@@ -3,7 +3,7 @@ load("@rules_go//go:def.bzl", "go_binary", "go_library")
 go_library(
     name = "pacman_lib",
     srcs = ["main.go"],
-    importpath = "go.fuhry.dev/osquery/pacman/cmd/pacman",
+    importpath = "go.fuhry.dev/osquery/cmd/pacman",
     visibility = ["//visibility:private"],
     deps = [
         "//extcommon",
diff --git a/cmd/x509_certificates/BUILD.bazel b/cmd/x509_certificates/BUILD.bazel
new file mode 100644 (file)
index 0000000..f1954a6
--- /dev/null
@@ -0,0 +1,18 @@
+load("@rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "x509_certificates_lib",
+    srcs = ["main.go"],
+    importpath = "go.fuhry.dev/osquery/cmd/x509_certificates",
+    visibility = ["//visibility:private"],
+    deps = [
+        "//extcommon",
+        "//x509_certificates",
+    ],
+)
+
+go_binary(
+    name = "x509_certificates",
+    embed = [":x509_certificates_lib"],
+    visibility = ["//visibility:public"],
+)
diff --git a/cmd/x509_certificates/main.go b/cmd/x509_certificates/main.go
new file mode 100644 (file)
index 0000000..0722d08
--- /dev/null
@@ -0,0 +1,10 @@
+package main
+
+import (
+       "go.fuhry.dev/osquery/extcommon"
+       "go.fuhry.dev/osquery/x509_certificates"
+)
+
+func main() {
+       extcommon.Main("x509_certificates", x509_certificates.Schema, x509_certificates.Generate)
+}
diff --git a/go.mod b/go.mod
index 1dc801fcbbb3d68b585992f8c81045fd9d9f1359..0b2eb9e368ac3d5f04bdebc1018f87db6957d6d4 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -9,16 +9,20 @@ require (
        github.com/hashicorp/golang-lru/v2 v2.0.7
        github.com/linuxdeepin/go-lib v0.0.0-20250616085703-5b1a742a7af7
        github.com/osquery/osquery-go v0.0.0-20250131154556-629f995b6947
+       github.com/stretchr/testify v1.10.0
 )
 
 require (
        github.com/Microsoft/go-winio v0.6.2 // indirect
        github.com/apache/thrift v0.20.0 // indirect
+       github.com/davecgh/go-spew v1.1.1 // indirect
        github.com/go-logr/logr v1.2.4 // indirect
        github.com/go-logr/stdr v1.2.2 // indirect
        github.com/pkg/errors v0.9.1 // indirect
+       github.com/pmezard/go-difflib v1.0.0 // indirect
        go.opentelemetry.io/otel v1.16.0 // indirect
        go.opentelemetry.io/otel/metric v1.16.0 // indirect
        go.opentelemetry.io/otel/trace v1.16.0 // indirect
        golang.org/x/sys v0.25.0 // indirect
+       gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 2484d849ece65037191a697ac682c9b754e86ba3..4d6138bb22536a6272a36455ffb009b82157281d 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -57,8 +57,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/youpy/go-riff v0.1.0/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ=
 github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -108,6 +108,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/x509_certificates/BUILD.bazel b/x509_certificates/BUILD.bazel
new file mode 100644 (file)
index 0000000..6d126d3
--- /dev/null
@@ -0,0 +1,17 @@
+load("@rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "x509_certificates",
+    srcs = ["plugin.go"],
+    importpath = "go.fuhry.dev/osquery/x509_certificates",
+    visibility = ["//visibility:public"],
+    deps = ["@com_github_osquery_osquery_go//plugin/table"],
+)
+
+go_test(
+    name = "x509_certificates_test",
+    srcs = ["plugin_test.go"],
+    data = glob(["testdata/**"]),
+    embed = [":x509_certificates"],
+    deps = ["@com_github_stretchr_testify//assert"],
+)
diff --git a/x509_certificates/plugin.go b/x509_certificates/plugin.go
new file mode 100644 (file)
index 0000000..85335c2
--- /dev/null
@@ -0,0 +1,250 @@
+package x509_certificates
+
+import (
+       "bytes"
+       "context"
+       "crypto/ecdsa"
+       "crypto/rsa"
+       "crypto/sha1"
+       "crypto/sha256"
+       "crypto/x509"
+       "encoding/hex"
+       "encoding/pem"
+       "errors"
+       "fmt"
+       "io"
+       "os"
+       "strconv"
+       "strings"
+       "time"
+
+       "github.com/osquery/osquery-go/plugin/table"
+)
+
+const (
+       ColumnPath             = "path"
+       ColumnIndex            = "index"
+       ColumnError            = "error"
+       ColumnEncoding         = "encoding"
+       ColumnSubject          = "subject"
+       ColumnIssuer           = "issuer"
+       ColumnSerial           = "serial"
+       ColumnIsCA             = "is_ca"
+       ColumnPublicKey        = "public_key"
+       ColumnPublicKeyAlg     = "public_key_algorithm"
+       ColumnPublicKeyBits    = "public_key_size"
+       ColumnAltNames         = "alt_names"
+       ColumnNotBefore        = "not_before"
+       ColumnNotAfter         = "not_after"
+       ColumnSecondsRemaining = "remaining_ttl"
+       ColumnPercentRemaining = "remaining_pct"
+       ColumnValidNow         = "valid_now"
+       ColumnThumbprintSHA1   = "sha1_thumbprint"
+       ColumnThumbprintSHA256 = "sha256_thumbprint"
+)
+
+func Schema() (out []table.ColumnDefinition) {
+       return []table.ColumnDefinition{
+               table.TextColumn(ColumnPath),
+               table.BigIntColumn(ColumnIndex),
+               table.TextColumn(ColumnError),
+               table.TextColumn(ColumnEncoding),
+               table.TextColumn(ColumnSubject),
+               table.TextColumn(ColumnIssuer),
+               table.TextColumn(ColumnSerial),
+               table.IntegerColumn(ColumnIsCA),
+               table.TextColumn(ColumnPublicKey),
+               table.TextColumn(ColumnPublicKeyAlg),
+               table.IntegerColumn(ColumnPublicKeyBits),
+               table.TextColumn(ColumnAltNames),
+               table.BigIntColumn(ColumnNotBefore),
+               table.BigIntColumn(ColumnNotAfter),
+               table.IntegerColumn(ColumnSecondsRemaining),
+               table.IntegerColumn(ColumnPercentRemaining),
+               table.IntegerColumn(ColumnValidNow),
+               table.TextColumn(ColumnThumbprintSHA1),
+               table.TextColumn(ColumnThumbprintSHA256),
+       }
+}
+
+var ErrMissingRequiredColumn = errors.New("missing required column in WHERE clause \"path\"")
+var ErrUnsupportedColumnOperator = errors.New("unsupported operator in WHERE clause")
+
+func Generate(ctx context.Context, q table.QueryContext) (out []map[string]string, err error) {
+       if _, ok := q.Constraints[ColumnPath]; !ok {
+               err = ErrMissingRequiredColumn
+               return
+       }
+
+       for _, c := range q.Constraints[ColumnPath].Constraints {
+               if c.Operator != table.OperatorEquals {
+                       err = ErrUnsupportedColumnOperator
+                       return
+               }
+
+               out = append(out, generateRows(c.Expression)...)
+       }
+
+       return out, err
+}
+
+func newRow(certPath string, index int) map[string]string {
+       return map[string]string{
+               ColumnPath:             certPath,
+               ColumnIndex:            fmt.Sprintf("%d", index),
+               ColumnError:            "",
+               ColumnEncoding:         "DER",
+               ColumnSubject:          "",
+               ColumnIssuer:           "",
+               ColumnSerial:           "",
+               ColumnIsCA:             "0",
+               ColumnPublicKey:        "",
+               ColumnPublicKeyAlg:     "",
+               ColumnPublicKeyBits:    "0",
+               ColumnAltNames:         "",
+               ColumnNotBefore:        "",
+               ColumnNotAfter:         "",
+               ColumnSecondsRemaining: "0",
+               ColumnValidNow:         "0",
+               ColumnThumbprintSHA1:   "",
+               ColumnThumbprintSHA256: "",
+       }
+}
+
+func generateRows(certPath string) (out []map[string]string) {
+       row := newRow(certPath, 0)
+
+       stat, err := os.Stat(certPath)
+       if err != nil {
+               row[ColumnError] = err.Error()
+               out = append(out, row)
+               return
+       }
+
+       if !stat.Mode().IsRegular() {
+               row[ColumnError] = "not a regular file"
+               out = append(out, row)
+               return
+       }
+
+       contents, err := os.ReadFile(certPath)
+       if err != nil {
+               row[ColumnError] = err.Error()
+               out = append(out, row)
+               return
+       }
+
+       if strings.TrimSpace(string(contents)) == "" {
+               row[ColumnError] = "file is empty"
+               out = append(out, row)
+               return
+       }
+
+outer:
+       for i := 0; ; i++ {
+               if strings.TrimSpace(string(contents)) == "" {
+                       // reached EOF, return current results
+                       return
+               }
+
+               row = newRow(certPath, i)
+
+               cert, err := x509.ParseCertificate(contents)
+               if err == nil {
+                       // decoded as DER, seek forward in the file so we can grab the next cert
+                       contents = contents[len(cert.Raw):]
+               } else {
+                       // couldn't decode as DER, try PEM
+                       for {
+                               block, remainder := pem.Decode(contents)
+                               if block == nil && bytes.Equal(remainder, contents) {
+                                       if i == 0 {
+                                               row[ColumnError] = "unable to decode contents as PEM or DER"
+                                               out = append(out, row)
+                                       }
+                                       return
+                               }
+                               contents = remainder
+                               if block.Type != "CERTIFICATE" {
+                                       continue
+                               }
+
+                               cert, err = x509.ParseCertificate(block.Bytes)
+                               if err != nil {
+                                       row[ColumnError] = "PEM certificate block found, but unable to decode " +
+                                               "contents as X.509 certificate"
+                                       out = append(out, row)
+                                       continue outer
+                               }
+
+                               row[ColumnEncoding] = "PEM"
+                               break
+                       }
+               }
+
+               if cert == nil {
+                       row[ColumnError] = "certificate is nil after successful decode, should not reach this point"
+                       return
+               }
+
+               row[ColumnSubject] = cert.Subject.String()
+               row[ColumnIssuer] = cert.Issuer.String()
+               row[ColumnSerial] = hex.EncodeToString(cert.SerialNumber.Bytes())
+               row[ColumnNotBefore] = fmt.Sprintf("%d", cert.NotBefore.Unix())
+               row[ColumnNotAfter] = fmt.Sprintf("%d", cert.NotAfter.Unix())
+
+               var altNames []string
+               for _, name := range cert.DNSNames {
+                       altNames = append(altNames, fmt.Sprintf("DNS:%s", name))
+               }
+               for _, name := range cert.EmailAddresses {
+                       altNames = append(altNames, fmt.Sprintf("MAIL:%s", name))
+               }
+               for _, name := range cert.IPAddresses {
+                       altNames = append(altNames, fmt.Sprintf("IP:%s", name.String()))
+               }
+               for _, name := range cert.URIs {
+                       altNames = append(altNames, fmt.Sprintf("URI:%s", name.String()))
+               }
+               row[ColumnAltNames] = strings.Join(altNames, ",")
+
+               if pubkey, err := x509.MarshalPKIXPublicKey(cert.PublicKey); err == nil {
+                       row[ColumnPublicKey] = string(pem.EncodeToMemory(&pem.Block{
+                               Type:  "PUBLIC KEY",
+                               Bytes: pubkey,
+                       }))
+                       switch k := cert.PublicKey.(type) {
+                       case *rsa.PublicKey:
+                               row[ColumnPublicKeyAlg] = "RSA"
+                               row[ColumnPublicKeyBits] = strconv.Itoa(8 * k.Size())
+                       case *ecdsa.PublicKey:
+                               row[ColumnPublicKeyAlg] = "ECDSA"
+                               row[ColumnPublicKeyBits] = strconv.Itoa(k.Params().BitSize)
+                       }
+               }
+
+               if cert.IsCA {
+                       row[ColumnIsCA] = "1"
+               }
+
+               now := time.Now()
+               if now.After(cert.NotBefore) && now.Before(cert.NotAfter) {
+                       row[ColumnValidNow] = "1"
+                       ttl := cert.NotAfter.Unix() - cert.NotBefore.Unix()
+                       remain := cert.NotAfter.Unix() - now.Unix()
+                       row[ColumnSecondsRemaining] = fmt.Sprintf("%d", remain)
+                       row[ColumnPercentRemaining] = fmt.Sprintf("%d", (remain*100)/ttl)
+               }
+
+               sha := sha1.New()
+               _, _ = io.Copy(sha, bytes.NewBuffer(cert.Raw))
+               var buf []byte
+               row[ColumnThumbprintSHA1] = hex.EncodeToString(sha.Sum(buf))
+
+               sha = sha256.New()
+               _, _ = io.Copy(sha, bytes.NewBuffer(cert.Raw))
+               buf = nil
+               row[ColumnThumbprintSHA256] = hex.EncodeToString(sha.Sum(buf))
+               out = append(out, row)
+       }
+}
diff --git a/x509_certificates/plugin_test.go b/x509_certificates/plugin_test.go
new file mode 100644 (file)
index 0000000..d4df8e3
--- /dev/null
@@ -0,0 +1,96 @@
+package x509_certificates
+
+import (
+       "fmt"
+       "os"
+       "path"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestGenerateRows(t *testing.T) {
+       type testCase struct {
+               filename, expectError, expectAlg, expectBits string
+               index                                        int
+       }
+
+       wd, err := os.Getwd()
+       assert.NoError(t, err)
+       dataDir := path.Join(wd, "testdata")
+
+       var testCases = []*testCase{
+               {
+                       filename:    "ecdsa-p256.crt",
+                       expectError: "",
+                       expectAlg:   "ECDSA",
+                       expectBits:  "256",
+               },
+               {
+                       filename:    "ecdsa-p384.crt",
+                       expectError: "",
+                       expectAlg:   "ECDSA",
+                       expectBits:  "384",
+               },
+               {
+                       filename:    "ecdsa-p521.crt",
+                       expectError: "",
+                       expectAlg:   "ECDSA",
+                       expectBits:  "521",
+               },
+               {
+                       filename:    "rsa-2048.crt",
+                       expectError: "",
+                       expectAlg:   "RSA",
+                       expectBits:  "2048",
+               },
+               {
+                       filename:    "rsa-4096.crt",
+                       expectError: "",
+                       expectAlg:   "RSA",
+                       expectBits:  "4096",
+               },
+               {
+                       filename:    "all-crts.crt",
+                       expectError: "",
+                       expectAlg:   "ECDSA",
+                       expectBits:  "256",
+               },
+               {
+                       filename:    "all-crts.crt",
+                       expectError: "",
+                       expectAlg:   "RSA",
+                       expectBits:  "4096",
+                       index:       4,
+               },
+               {
+                       filename:    "does-not-exist.crt",
+                       expectError: "stat .+: no such file or directory",
+                       expectAlg:   "",
+                       expectBits:  "0",
+               },
+       }
+
+       for _, tc := range testCases {
+               name := fmt.Sprintf("%s-%d", tc.filename, tc.index)
+               t.Run(name, func(t *testing.T) {
+                       fullPath := path.Join(dataDir, tc.filename)
+                       out := generateRows(fullPath)
+
+                       if len(out) < tc.index+1 {
+                               t.Errorf("index %d not found in output", tc.index)
+                               t.FailNow()
+                       }
+
+                       row := out[tc.index]
+                       assert.Regexp(t, tc.expectError, row[ColumnError])
+                       if tc.expectError != "" {
+                               return
+                       }
+
+                       assert.Equal(t, "CN=Test certificate for osquery x509_certificates table", row[ColumnSubject])
+                       assert.Equal(t, tc.expectAlg, row[ColumnPublicKeyAlg])
+                       assert.Equal(t, tc.expectBits, row[ColumnPublicKeyBits])
+               })
+       }
+}
diff --git a/x509_certificates/testdata/all-crts.crt b/x509_certificates/testdata/all-crts.crt
new file mode 100644 (file)
index 0000000..59bb961
--- /dev/null
@@ -0,0 +1,93 @@
+-----BEGIN CERTIFICATE-----
+MIIB1TCCAXugAwIBAgIUZ9Q2PxNcVDhQEmb2orm6KgYcSxwwCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEYH
+Kf6phvEAh5uH0U3UAos4gRrPwdg+/X39UDHYqtA1fdVTOnurSUmgZCQ404hWo//6
+Ba0Y7vgfu5bVIGEDNFKjUzBRMB0GA1UdDgQWBBT2uO21QITQMdKolADS+IrZHqDr
+aTAfBgNVHSMEGDAWgBT2uO21QITQMdKolADS+IrZHqDraTAPBgNVHRMBAf8EBTAD
+AQH/MAoGCCqGSM49BAMCA0gAMEUCIQC1ZTinmpxm4cxf70FKd+cuRuljxyG1CWgP
+b1ZSvdSRKgIgFqRK/i6hG/BD/MHWkDlkz6wlTRC1NTLMnmEzX0rDbcM=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICETCCAZigAwIBAgIUSUCiCqqjzq0s/BjdYE5iMZxi9dowCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABA5IOT98
+utCAMSmep4XihIo+RemJ8FIVfK20ZaDoHW7Kvb7rsKHb7696nzDXUzFTV6Z0yZuo
+QvsX3Oi8wH15YgXQZv2BZA5p10Av4qkKJNI2Zf8wJpLdOFI/jB9TpICLYKNTMFEw
+HQYDVR0OBBYEFECUfMhewhq4WHZH5h/s9YunHKbJMB8GA1UdIwQYMBaAFECUfMhe
+whq4WHZH5h/s9YunHKbJMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDZwAw
+ZAIwTCtxp6OiwBP90RQwQX4UsW6p+OdiIdWxu4SXqsh2vMmJdiyPv8H5FdspSLmz
+BnlHAjBsWGFI4hBAQUDUBMDIcRBr5QFUJmjCAm1JOhEhtSpci+HicTV/aQXFLFKZ
+jMxDIcE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICXTCCAb6gAwIBAgIUJZXwsOk46485PcpXVo5i6/ThnIswCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAWEZ
+DpFAZ79nemFi9ifdhGL1PfjEPscw2R4bQDfuI/FozhtyP/7rl8MVOW1wz7qVQp14
+QzdEGbbJddAMJ0BYFWJWAMV74eo+AplSxNJGQeVM+7dh4pZM3Y4F0YnjFviwUh61
+ExL+Fbp2BCVYX7RYUKfxCG1vJ6Xm7l8YlqQbI6p4LO+Ro1MwUTAdBgNVHQ4EFgQU
+xMk27Lv0gjP5mtQm1mOy/+1T/e0wHwYDVR0jBBgwFoAUxMk27Lv0gjP5mtQm1mOy
+/+1T/e0wDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgOBjAAwgYgCQgCoRzGs
+ptIwR9mPePx6XMhg70P0fkZevdg4/EbNN4Mosfka5+19yb55luudnXV2KCY0tbSu
+yNCtx24I9ooBwfIAAAJCAcoRaowRWo5hdvLPswV8mW15+w3uN+8I8lsIpYiEs58a
+GILX/yl4iWVmfytwBKbhy/Eu2E1Y9hxZFkgr1UUvRzbS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIUa7fzO4n3UTqRwbvfnVVfMnkvA+QwDQYJKoZIhvcNAQEL
+BQAwPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2
+MjkzNVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4
+NTA5X2NlcnRpZmljYXRlcyB0YWJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAM5OXSsLWBIxTlAScBoglelLVEShB2XhjhWDoJO8KSliVmooTMxwiTpo
+/P/xQGvDWot8aDzIs8VH3HsbptbIkOgHCcTJSrytck3Qu2BbMyEiIefNAI4HxBfw
+xJMhZ/d1nyHQ1BpJ09fLMMt4Zeiu3GREoN/rUwxDOB9HtcHjA50MNspUFls/AkaP
+QTJSKdcJD3tpgcTrDZr9i5/Owtg3wwQEAcZIap1RjSvJ6JEtZmf0yVGiOz7S8ekb
+JJ+KgPqPz7z0UVX0Vclte5cY2aFY/yzVua7E0hen5UJxv7Whe9Lx2Pt9ENrxvSLx
+2y19mC08ldwVPlBoc1H2frp1Olv5Q3ECAwEAAaNTMFEwHQYDVR0OBBYEFDHg/RFp
+7vEO7Ci5FT5n8zEKS4RNMB8GA1UdIwQYMBaAFDHg/RFp7vEO7Ci5FT5n8zEKS4RN
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACxdeYyaXeHh+7XW
+L5XUrqQo/z5Ll1FbWF7PRTptVQpEF91YRg+pZyitsUwGE1dtRc5mm89pkXSDbJC3
+NY0LJYbAiKWh0yYtXYOw5y0UeBtWFzGj64lkBPKlN0ua0wbV9S7lDUH08KcRGC6i
+G2S+/WAH/KWBuQK9jTHtwyj0hI69Sr3m0fvul5oIgQOGcaNDA2a3i3/UadLketBs
+NcnCHNKDNzXAKhS7sfiLKxYbh7X6q23wgCgRv4Q1asavGGOxego6aj887XQ9CCMg
+nySKGZfSWVPiSEMqb9rbEjMtdKqCEISSkKZWE0WiPc3kWhZ28Ci97Qlt97qNTGs0
+Ih+AaNM=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFYTCCA0mgAwIBAgIUZCWEc/7M9ujBzXd61J8dp2ST13swDQYJKoZIhvcNAQEL
+BQAwPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2
+MjkzNVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4
+NTA5X2NlcnRpZmljYXRlcyB0YWJsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAOzpPngQX9qwFi0TR3xZ2qDZc1rkBJeMcBNOs3wKi18c+QZ+AcEgjMLx
+KTn2ktEccEFmdscaAr5FSnhdO25GUVCkcvMsaoWDsjx0r9h9wML6vH0iW7ro1bn+
+mpKN8CwFvBawUUsc3uwVyQ9rgs3ktWcsnjkXX5CDm91suXMyX0zPltnPH3KZgOcp
+yL5zj0sSrr2JyWWWFby38qXSoDw7KIoH8ZvXqmjEVoQubFuYtOAzuiSgJHuWKt9i
+TL9R9UPQiRawxT8x25EO+OrFDRt/kC/2ZjJTo9UPclEhD4lJgqoGbjzalWO8sETv
+1Dhq/DrP0huCFB+PC4iUsSqjBJd2B88gxrQt5D8qGnCqjNUs5fu+0NVfNew1iXov
+fMeNBvMc1cvRU57UMafQwMyO83FENwBqBquRVApWzMflnm8+xysHrlW9xxaVpDAe
+DW0J1ouDv1WiJVL98bdcR/EyIlM++dQTPLJLb5bTssMP2Qylw2tzIuofZ+l2PQ5I
+Zqq1tuXPnDGu4e/3ptTGGhsPzOo/fi3Dz5QcGC3I5FFxkqiHi7YmMGkxCkKrLzXk
+YKBMk5a8EV8p00OYMym4vWJ9fpumLK9GDcXOC/dEh/ZimcqyFIuQdWPYFwk2aeF+
+ZJQp4VkG1NxvES8Ae5wH+OPt0cUPnEMrJwtUFVw6Nc45AgpzSerHAgMBAAGjUzBR
+MB0GA1UdDgQWBBT3uMoDluXWmbVAl+8Zw6eObr1hNjAfBgNVHSMEGDAWgBT3uMoD
+luXWmbVAl+8Zw6eObr1hNjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
+A4ICAQB7WEjjJYPX5ICZTXsjQ6qSV6dpTQhg+hrm9H1anaGhJTiepvpOrPzj7Id/
+oBwuI0KrzLdYvrjS2zgdjhpkQ5hX2osRYO3BSPCR7vgbKea7k/JDELRsQA7D1XE+
+YC18yLGkKdMVNna/jCVsyWbJPKBFoGZHg+q5PkmVjAzGtjxD9pQNkMbavPTZ/6cA
+5TgV9xSBbfoTsewOt1I5ae8Idy8GrL6FMvAVXh7JYeo53aJheBCnt/cw94BSG2DN
+OfHgQ8QWvhRnSG3KuvGBqH2Ql/aemw1kdIOCWIskbG/0hqBEuy7xq6IGVUDa9GPu
+9uLTfR8ICobHyNt1QBW0zCBDoOEjmwd4GEnZZ6lpxST36E0D3w0asSSN6nnoTb5N
+UD2F7Lk+bo2xd5wTfVQyp/Wy4v+RUV627G2kvbP7cErWY90Z98klN8FEKVYcozE8
+z1OSSpuyFIkgi1PCFB1/NXVdJX+xra2hmYyz6pXYVa011ogPDDfXBxFyolT+W2qy
+5YQnC4QkAILtyuhWSbDNMktA4nnTgZ/jg9jrsYhWoliJLHbPWrSlpPqEhFjMpwRX
+n/lyZFZN3l5Y5yIBnMJOoB80Vz/k+2anBOhoAes1sFLgIzzMsslx6aVQ2ytPNo/b
+USYor8DSP2WO5SvU6xN1zU3ZsFiDkhd6eqOIn1i8HECu6YNo6w==
+-----END CERTIFICATE-----
diff --git a/x509_certificates/testdata/ecdsa-p256.crt b/x509_certificates/testdata/ecdsa-p256.crt
new file mode 100644 (file)
index 0000000..faa9f2e
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIB1TCCAXugAwIBAgIUZ9Q2PxNcVDhQEmb2orm6KgYcSxwwCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEYH
+Kf6phvEAh5uH0U3UAos4gRrPwdg+/X39UDHYqtA1fdVTOnurSUmgZCQ404hWo//6
+Ba0Y7vgfu5bVIGEDNFKjUzBRMB0GA1UdDgQWBBT2uO21QITQMdKolADS+IrZHqDr
+aTAfBgNVHSMEGDAWgBT2uO21QITQMdKolADS+IrZHqDraTAPBgNVHRMBAf8EBTAD
+AQH/MAoGCCqGSM49BAMCA0gAMEUCIQC1ZTinmpxm4cxf70FKd+cuRuljxyG1CWgP
+b1ZSvdSRKgIgFqRK/i6hG/BD/MHWkDlkz6wlTRC1NTLMnmEzX0rDbcM=
+-----END CERTIFICATE-----
diff --git a/x509_certificates/testdata/ecdsa-p384.crt b/x509_certificates/testdata/ecdsa-p384.crt
new file mode 100644 (file)
index 0000000..c2b4f50
--- /dev/null
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICETCCAZigAwIBAgIUSUCiCqqjzq0s/BjdYE5iMZxi9dowCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABA5IOT98
+utCAMSmep4XihIo+RemJ8FIVfK20ZaDoHW7Kvb7rsKHb7696nzDXUzFTV6Z0yZuo
+QvsX3Oi8wH15YgXQZv2BZA5p10Av4qkKJNI2Zf8wJpLdOFI/jB9TpICLYKNTMFEw
+HQYDVR0OBBYEFECUfMhewhq4WHZH5h/s9YunHKbJMB8GA1UdIwQYMBaAFECUfMhe
+whq4WHZH5h/s9YunHKbJMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDZwAw
+ZAIwTCtxp6OiwBP90RQwQX4UsW6p+OdiIdWxu4SXqsh2vMmJdiyPv8H5FdspSLmz
+BnlHAjBsWGFI4hBAQUDUBMDIcRBr5QFUJmjCAm1JOhEhtSpci+HicTV/aQXFLFKZ
+jMxDIcE=
+-----END CERTIFICATE-----
diff --git a/x509_certificates/testdata/ecdsa-p521.crt b/x509_certificates/testdata/ecdsa-p521.crt
new file mode 100644 (file)
index 0000000..c4791ee
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICXTCCAb6gAwIBAgIUJZXwsOk46485PcpXVo5i6/ThnIswCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAWEZ
+DpFAZ79nemFi9ifdhGL1PfjEPscw2R4bQDfuI/FozhtyP/7rl8MVOW1wz7qVQp14
+QzdEGbbJddAMJ0BYFWJWAMV74eo+AplSxNJGQeVM+7dh4pZM3Y4F0YnjFviwUh61
+ExL+Fbp2BCVYX7RYUKfxCG1vJ6Xm7l8YlqQbI6p4LO+Ro1MwUTAdBgNVHQ4EFgQU
+xMk27Lv0gjP5mtQm1mOy/+1T/e0wHwYDVR0jBBgwFoAUxMk27Lv0gjP5mtQm1mOy
+/+1T/e0wDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgOBjAAwgYgCQgCoRzGs
+ptIwR9mPePx6XMhg70P0fkZevdg4/EbNN4Mosfka5+19yb55luudnXV2KCY0tbSu
+yNCtx24I9ooBwfIAAAJCAcoRaowRWo5hdvLPswV8mW15+w3uN+8I8lsIpYiEs58a
+GILX/yl4iWVmfytwBKbhy/Eu2E1Y9hxZFkgr1UUvRzbS
+-----END CERTIFICATE-----
diff --git a/x509_certificates/testdata/generate.sh b/x509_certificates/testdata/generate.sh
new file mode 100755 (executable)
index 0000000..c56d86d
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+private_keys=()
+for bits in 256 384 521; do
+       test -f ecdsa-p$bits.key || openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-$bits -out ecdsa-p$bits.key
+       private_keys+=("ecdsa-p$bits.key")
+done
+
+for bits in 2048 4096; do
+       test -f rsa-$bits.key || openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:$bits -out rsa-$bits.key
+       private_keys+=("rsa-$bits.key")
+done
+
+crts=()
+for k in "${private_keys[@]}"; do
+       crt="${k%.key}.crt"
+       openssl req -new -key "$k" -subj "/CN=Test certificate for osquery x509_certificates table/" -days 36524 -x509 -out "$crt"
+       crts+=("$crt")
+done
+
+cat "${crts[@]}" > all-crts.crt
+
diff --git a/x509_certificates/testdata/rsa-2048.crt b/x509_certificates/testdata/rsa-2048.crt
new file mode 100644 (file)
index 0000000..049184b
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIUa7fzO4n3UTqRwbvfnVVfMnkvA+QwDQYJKoZIhvcNAQEL
+BQAwPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2
+MjkzNVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4
+NTA5X2NlcnRpZmljYXRlcyB0YWJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAM5OXSsLWBIxTlAScBoglelLVEShB2XhjhWDoJO8KSliVmooTMxwiTpo
+/P/xQGvDWot8aDzIs8VH3HsbptbIkOgHCcTJSrytck3Qu2BbMyEiIefNAI4HxBfw
+xJMhZ/d1nyHQ1BpJ09fLMMt4Zeiu3GREoN/rUwxDOB9HtcHjA50MNspUFls/AkaP
+QTJSKdcJD3tpgcTrDZr9i5/Owtg3wwQEAcZIap1RjSvJ6JEtZmf0yVGiOz7S8ekb
+JJ+KgPqPz7z0UVX0Vclte5cY2aFY/yzVua7E0hen5UJxv7Whe9Lx2Pt9ENrxvSLx
+2y19mC08ldwVPlBoc1H2frp1Olv5Q3ECAwEAAaNTMFEwHQYDVR0OBBYEFDHg/RFp
+7vEO7Ci5FT5n8zEKS4RNMB8GA1UdIwQYMBaAFDHg/RFp7vEO7Ci5FT5n8zEKS4RN
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACxdeYyaXeHh+7XW
+L5XUrqQo/z5Ll1FbWF7PRTptVQpEF91YRg+pZyitsUwGE1dtRc5mm89pkXSDbJC3
+NY0LJYbAiKWh0yYtXYOw5y0UeBtWFzGj64lkBPKlN0ua0wbV9S7lDUH08KcRGC6i
+G2S+/WAH/KWBuQK9jTHtwyj0hI69Sr3m0fvul5oIgQOGcaNDA2a3i3/UadLketBs
+NcnCHNKDNzXAKhS7sfiLKxYbh7X6q23wgCgRv4Q1asavGGOxego6aj887XQ9CCMg
+nySKGZfSWVPiSEMqb9rbEjMtdKqCEISSkKZWE0WiPc3kWhZ28Ci97Qlt97qNTGs0
+Ih+AaNM=
+-----END CERTIFICATE-----
diff --git a/x509_certificates/testdata/rsa-4096.crt b/x509_certificates/testdata/rsa-4096.crt
new file mode 100644 (file)
index 0000000..fe37679
--- /dev/null
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFYTCCA0mgAwIBAgIUZCWEc/7M9ujBzXd61J8dp2ST13swDQYJKoZIhvcNAQEL
+BQAwPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2
+MjkzNVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4
+NTA5X2NlcnRpZmljYXRlcyB0YWJsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAOzpPngQX9qwFi0TR3xZ2qDZc1rkBJeMcBNOs3wKi18c+QZ+AcEgjMLx
+KTn2ktEccEFmdscaAr5FSnhdO25GUVCkcvMsaoWDsjx0r9h9wML6vH0iW7ro1bn+
+mpKN8CwFvBawUUsc3uwVyQ9rgs3ktWcsnjkXX5CDm91suXMyX0zPltnPH3KZgOcp
+yL5zj0sSrr2JyWWWFby38qXSoDw7KIoH8ZvXqmjEVoQubFuYtOAzuiSgJHuWKt9i
+TL9R9UPQiRawxT8x25EO+OrFDRt/kC/2ZjJTo9UPclEhD4lJgqoGbjzalWO8sETv
+1Dhq/DrP0huCFB+PC4iUsSqjBJd2B88gxrQt5D8qGnCqjNUs5fu+0NVfNew1iXov
+fMeNBvMc1cvRU57UMafQwMyO83FENwBqBquRVApWzMflnm8+xysHrlW9xxaVpDAe
+DW0J1ouDv1WiJVL98bdcR/EyIlM++dQTPLJLb5bTssMP2Qylw2tzIuofZ+l2PQ5I
+Zqq1tuXPnDGu4e/3ptTGGhsPzOo/fi3Dz5QcGC3I5FFxkqiHi7YmMGkxCkKrLzXk
+YKBMk5a8EV8p00OYMym4vWJ9fpumLK9GDcXOC/dEh/ZimcqyFIuQdWPYFwk2aeF+
+ZJQp4VkG1NxvES8Ae5wH+OPt0cUPnEMrJwtUFVw6Nc45AgpzSerHAgMBAAGjUzBR
+MB0GA1UdDgQWBBT3uMoDluXWmbVAl+8Zw6eObr1hNjAfBgNVHSMEGDAWgBT3uMoD
+luXWmbVAl+8Zw6eObr1hNjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
+A4ICAQB7WEjjJYPX5ICZTXsjQ6qSV6dpTQhg+hrm9H1anaGhJTiepvpOrPzj7Id/
+oBwuI0KrzLdYvrjS2zgdjhpkQ5hX2osRYO3BSPCR7vgbKea7k/JDELRsQA7D1XE+
+YC18yLGkKdMVNna/jCVsyWbJPKBFoGZHg+q5PkmVjAzGtjxD9pQNkMbavPTZ/6cA
+5TgV9xSBbfoTsewOt1I5ae8Idy8GrL6FMvAVXh7JYeo53aJheBCnt/cw94BSG2DN
+OfHgQ8QWvhRnSG3KuvGBqH2Ql/aemw1kdIOCWIskbG/0hqBEuy7xq6IGVUDa9GPu
+9uLTfR8ICobHyNt1QBW0zCBDoOEjmwd4GEnZZ6lpxST36E0D3w0asSSN6nnoTb5N
+UD2F7Lk+bo2xd5wTfVQyp/Wy4v+RUV627G2kvbP7cErWY90Z98klN8FEKVYcozE8
+z1OSSpuyFIkgi1PCFB1/NXVdJX+xra2hmYyz6pXYVa011ogPDDfXBxFyolT+W2qy
+5YQnC4QkAILtyuhWSbDNMktA4nnTgZ/jg9jrsYhWoliJLHbPWrSlpPqEhFjMpwRX
+n/lyZFZN3l5Y5yIBnMJOoB80Vz/k+2anBOhoAes1sFLgIzzMsslx6aVQ2ytPNo/b
+USYor8DSP2WO5SvU6xN1zU3ZsFiDkhd6eqOIn1i8HECu6YNo6w==
+-----END CERTIFICATE-----