bazel-osquery
bazel-out
bazel-testlogs
+x509_certificates/testdata/*.key
"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
);
```
+### `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>
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",
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",
--- /dev/null
+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"],
+)
--- /dev/null
+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)
+}
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
)
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=
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=
--- /dev/null
+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"],
+)
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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])
+ })
+ }
+}
--- /dev/null
+-----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-----
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIB1TCCAXugAwIBAgIUZ9Q2PxNcVDhQEmb2orm6KgYcSxwwCgYIKoZIzj0EAwIw
+PzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5X2Nl
+cnRpZmljYXRlcyB0YWJsZTAgFw0yNTA3MjgxNjI5MzVaGA8yMTI1MDcyODE2Mjkz
+NVowPzE9MDsGA1UEAww0VGVzdCBjZXJ0aWZpY2F0ZSBmb3Igb3NxdWVyeSB4NTA5
+X2NlcnRpZmljYXRlcyB0YWJsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEYH
+Kf6phvEAh5uH0U3UAos4gRrPwdg+/X39UDHYqtA1fdVTOnurSUmgZCQ404hWo//6
+Ba0Y7vgfu5bVIGEDNFKjUzBRMB0GA1UdDgQWBBT2uO21QITQMdKolADS+IrZHqDr
+aTAfBgNVHSMEGDAWgBT2uO21QITQMdKolADS+IrZHqDraTAPBgNVHRMBAf8EBTAD
+AQH/MAoGCCqGSM49BAMCA0gAMEUCIQC1ZTinmpxm4cxf70FKd+cuRuljxyG1CWgP
+b1ZSvdSRKgIgFqRK/i6hG/BD/MHWkDlkz6wlTRC1NTLMnmEzX0rDbcM=
+-----END CERTIFICATE-----
--- /dev/null
+-----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-----
--- /dev/null
+-----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-----
--- /dev/null
+#!/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
+
--- /dev/null
+-----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-----
--- /dev/null
+-----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-----