From: Dan Fuhry Date: Mon, 28 Jul 2025 17:27:10 +0000 (-0400) Subject: add x509_certificates table X-Git-Url: https://go.fuhry.dev/?a=commitdiff_plain;h=refs%2Fheads%2Fmain;p=osquery.git add x509_certificates table --- diff --git a/.gitignore b/.gitignore index b5b830b..d324763 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ bazel-bin bazel-osquery bazel-out bazel-testlogs +x509_certificates/testdata/*.key diff --git a/MODULE.bazel b/MODULE.bazel index 2420ab6..ce61d5b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -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 diff --git a/README.md b/README.md index 527ad87..6597529 100644 --- 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 diff --git a/cmd/flatpak/BUILD.bazel b/cmd/flatpak/BUILD.bazel index 74bec19..e84463d 100644 --- a/cmd/flatpak/BUILD.bazel +++ b/cmd/flatpak/BUILD.bazel @@ -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", diff --git a/cmd/pacman/BUILD.bazel b/cmd/pacman/BUILD.bazel index d5065b0..382bc2a 100644 --- a/cmd/pacman/BUILD.bazel +++ b/cmd/pacman/BUILD.bazel @@ -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 index 0000000..f1954a6 --- /dev/null +++ b/cmd/x509_certificates/BUILD.bazel @@ -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 index 0000000..0722d08 --- /dev/null +++ b/cmd/x509_certificates/main.go @@ -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 1dc801f..0b2eb9e 100644 --- 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 2484d84..4d6138b 100644 --- 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 index 0000000..6d126d3 --- /dev/null +++ b/x509_certificates/BUILD.bazel @@ -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 index 0000000..85335c2 --- /dev/null +++ b/x509_certificates/plugin.go @@ -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 index 0000000..d4df8e3 --- /dev/null +++ b/x509_certificates/plugin_test.go @@ -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 index 0000000..59bb961 --- /dev/null +++ b/x509_certificates/testdata/all-crts.crt @@ -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 index 0000000..faa9f2e --- /dev/null +++ b/x509_certificates/testdata/ecdsa-p256.crt @@ -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 index 0000000..c2b4f50 --- /dev/null +++ b/x509_certificates/testdata/ecdsa-p384.crt @@ -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 index 0000000..c4791ee --- /dev/null +++ b/x509_certificates/testdata/ecdsa-p521.crt @@ -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 index 0000000..c56d86d --- /dev/null +++ b/x509_certificates/testdata/generate.sh @@ -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 index 0000000..049184b --- /dev/null +++ b/x509_certificates/testdata/rsa-2048.crt @@ -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 index 0000000..fe37679 --- /dev/null +++ b/x509_certificates/testdata/rsa-4096.crt @@ -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-----