From 71ce388992aee6925e775ae3c3a08a7a50abbb5d Mon Sep 17 00:00:00 2001 From: Marcos Yacob Date: Mon, 2 Mar 2020 15:08:23 -0300 Subject: [PATCH] Add merge intermediate certificates mode (#19) * add option to merge intermediate certificates into Bundles file Signed-off-by: Marcos Yacob --- .go-version | 2 +- README.md | 19 ++-- cmd/spiffe-helper/config_test.go | 1 + examples/mysql/helper.conf | 4 + go.sum | 2 + helper.conf | 4 + pkg/sidecar/sidecar.go | 59 +++++++---- pkg/sidecar/sidecar_test.go | 174 ++++++++++++++++++++++--------- test/fixture/certs/ca.pem | 12 --- test/fixture/certs/svid.pem | 10 -- test/fixture/certs/svid_key.pem | 5 - test/fixture/config/helper.conf | 1 + test/util/cert_fixtures.go | 88 ---------------- test/util/load.go | 59 +++++++++++ 14 files changed, 244 insertions(+), 196 deletions(-) delete mode 100644 test/fixture/certs/ca.pem delete mode 100644 test/fixture/certs/svid.pem delete mode 100644 test/fixture/certs/svid_key.pem delete mode 100644 test/util/cert_fixtures.go create mode 100644 test/util/load.go diff --git a/.go-version b/.go-version index b0f139ea..237a2564 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.13.7 +1.13.8 diff --git a/README.md b/README.md index 9147d3a3..47865e25 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,16 @@ If `-config` is not specified, the default value `helper.conf` is assumed. The configuration file is an [HCL](https://github.com/hashicorp/hcl) formatted file that defines the following configurations: |Configuration | Description | Example Value | - |---------------------|------------------------------------------------------------------------------------------------| ------------- | - |`agentAddress` | Socket address of SPIRE Agent. | `"/tmp/agent.sock"` | - |`cmd` | The path to the process to launch. | `"ghostunnel"` | - |`cmdArgs` | The arguments of the process to launch. | `"server --listen localhost:8002 --target localhost:8001--keystore certs/svid_key.pem --cacert certs/svid_bundle.pem --allow-uri-san spiffe://example.org/Database"` | - |`certDir` | Directory name to store the fetched certificates. This directory must be created previously. | `"certs"` | - |`renewSignal` | The signal that the process to be launched expects to reload the certificates. | `"SIGUSR1"` | - |`svidFileName` | File name to be used to store the X.509 SVID public certificate in PEM format. | `"svid.pem"` | - |`svidKeyFileName` | File name to be used to store the X.509 SVID private key and public certificate in PEM format. | `"svid_key.pem"` | - |`svidBundleFileName` | File name to be used to store the X.509 SVID Bundle in PEM format. | `"svid_bundle.pem"` | + |--------------------------|------------------------------------------------------------------------------------------------| ------------- | + |`agentAddress` | Socket address of SPIRE Agent. | `"/tmp/agent.sock"` | + |`cmd` | The path to the process to launch. | `"ghostunnel"` | + |`cmdArgs` | The arguments of the process to launch. | `"server --listen localhost:8002 --target localhost:8001--keystore certs/svid_key.pem --cacert certs/svid_bundle.pem --allow-uri-san spiffe://example.org/Database"` | + |`certDir` | Directory name to store the fetched certificates. This directory must be created previously. | `"certs"` | + |`addIntermediatesToBundle`| Add intermediate certificates into Bundle file instead of SVID file. | `true` | + |`renewSignal` | The signal that the process to be launched expects to reload the certificates. | `"SIGUSR1"` | + |`svidFileName` | File name to be used to store the X.509 SVID public certificate in PEM format. | `"svid.pem"` | + |`svidKeyFileName` | File name to be used to store the X.509 SVID private key and public certificate in PEM format. | `"svid_key.pem"` | + |`svidBundleFileName` | File name to be used to store the X.509 SVID Bundle in PEM format. | `"svid_bundle.pem"` | #### Configuration example ``` diff --git a/cmd/spiffe-helper/config_test.go b/cmd/spiffe-helper/config_test.go index e6d2f650..7d24d045 100644 --- a/cmd/spiffe-helper/config_test.go +++ b/cmd/spiffe-helper/config_test.go @@ -30,4 +30,5 @@ func TestParseConfig(t *testing.T) { assert.Equal(t, expectedKeyFileName, c.SvidKeyFileName) assert.Equal(t, expectedSvidBundleFileName, c.SvidBundleFileName) assert.Equal(t, expectedTimeOut, c.Timeout) + assert.True(t, c.AddIntermediatesToBundle) } diff --git a/examples/mysql/helper.conf b/examples/mysql/helper.conf index 0118340b..33959946 100644 --- a/examples/mysql/helper.conf +++ b/examples/mysql/helper.conf @@ -17,3 +17,7 @@ certDir = "/var/lib/mysql" svidFileName = "server-cert.pem" svidKeyFileName = "server-key.pem" svidBundleFileName = "ca.pem" + +# MySQL expect intermediate certificates inside `svidBundleFile` file +# instead of svidFile +addIntermediatesToBundle = true \ No newline at end of file diff --git a/go.sum b/go.sum index 89d22bd1..f8d11a7f 100644 --- a/go.sum +++ b/go.sum @@ -199,6 +199,7 @@ github.com/spiffe/go-spiffe v0.0.0-20190922191205-018e7197ed1c h1:wpwh25WjvKF8/+ github.com/spiffe/go-spiffe v0.0.0-20190922191205-018e7197ed1c/go.mod h1:HyNeJnVYkDyQgB2qcSPxVYkAA2F3lQu51bDxNpFcKxY= github.com/spiffe/spire v0.0.0-20191007205405-52bcf313f4f6 h1:2lLO9rR2rXlfWjeOC5YR5sHMyw5LGwF/mFJpmiZJdK4= github.com/spiffe/spire v0.0.0-20191007205405-52bcf313f4f6/go.mod h1:Pnjj4CO6q5pS/vsXyqVHs/HNoDXWmn8+jZekriyWPmM= +github.com/spiffe/spire/proto/spire v0.0.0-20190723205943-8d4a2538e330 h1:n5uIC5TcJhMmGGTPcsV9rvZvuncc49xeWxullDtx4fg= github.com/spiffe/spire/proto/spire v0.0.0-20190723205943-8d4a2538e330/go.mod h1:qNEFMfiHxU6h7JTH8Op7b5aHGRsSwfQXYnPd/F0Hm8c= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -210,6 +211,7 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/uber-go/tally v3.3.12+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/zaffka/mongodb-boltdb-mock v0.0.0-20180816124423-49954d88fa3e/go.mod h1:GsDD1qsG+86MeeCG7ndi6Ei3iGthKL3wQ7PTFigDfNY= +github.com/zeebo/errs v1.2.0 h1:Tk8UszIOLEjtx6DWnvfmMJe6N8q7vu03Bj95HMWDUkc= github.com/zeebo/errs v1.2.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= diff --git a/helper.conf b/helper.conf index f445b9d8..692fcc00 100644 --- a/helper.conf +++ b/helper.conf @@ -6,3 +6,7 @@ renewSignal = "SIGUSR1" svidFileName = "svid.pem" svidKeyFileName = "svid_key.pem" svidBundleFileName = "svid_bundle.pem" +# Add CA with intermediates into Bundle file instead of SVID file, +# it is the expected behavior in some scenarios like MySQL. +# Default: false +# addIntermediatesToBundle = false \ No newline at end of file diff --git a/pkg/sidecar/sidecar.go b/pkg/sidecar/sidecar.go index ce5a287b..1c30a126 100644 --- a/pkg/sidecar/sidecar.go +++ b/pkg/sidecar/sidecar.go @@ -24,16 +24,20 @@ import ( // Config contains config variables when creating a SPIFFE Sidecar. type Config struct { - AgentAddress string `hcl:"agentAddress"` - Cmd string `hcl:"cmd"` - CmdArgs string `hcl:"cmdArgs"` - CertDir string `hcl:"certDir"` - SvidFileName string `hcl:"svidFileName"` - SvidKeyFileName string `hcl:"svidKeyFileName"` - SvidBundleFileName string `hcl:"svidBundleFileName"` - RenewSignal string `hcl:"renewSignal"` - Timeout string `hcl:"timeout"` - ReloadExternalProcess func() error + AgentAddress string `hcl:"agentAddress"` + Cmd string `hcl:"cmd"` + CmdArgs string `hcl:"cmdArgs"` + CertDir string `hcl:"certDir"` + // Merge intermediate certificates into Bundle file instead of SVID file, + // it is useful is some scenarios like MySQL, + // where this is the expected format for presented certificates and bundles + AddIntermediatesToBundle bool `hcl:"addIntermediatesToBundle"` + SvidFileName string `hcl:"svidFileName"` + SvidKeyFileName string `hcl:"svidKeyFileName"` + SvidBundleFileName string `hcl:"svidBundleFileName"` + RenewSignal string `hcl:"renewSignal"` + Timeout string `hcl:"timeout"` + ReloadExternalProcess func() error } // Sidecar is the component that consumes the Workload API and renews certs @@ -229,7 +233,8 @@ func (s *Sidecar) checkProcessExit() { // dumpBundles takes a X509SVIDResponse, representing a svid message from // the Workload API, and calls writeCerts and writeKey to write to disk -// the svid, key and bundle of certificates +// the svid, key and bundle of certificates. +// It is possible to change output setting `addIntermediatesToBundle` as true. func (s *Sidecar) dumpBundles(svidResponse *proto.X509SVIDResponse) error { // There may be more than one certificate, but we are interested in the first one only svid := svidResponse.Svids[0] @@ -238,33 +243,41 @@ func (s *Sidecar) dumpBundles(svidResponse *proto.X509SVIDResponse) error { svidKeyFile := path.Join(s.config.CertDir, s.config.SvidKeyFileName) svidBundleFile := path.Join(s.config.CertDir, s.config.SvidBundleFileName) - err := s.writeCerts(svidFile, svid.X509Svid) + certs, err := x509.ParseCertificates(svid.X509Svid) if err != nil { return err } - err = s.writeKey(svidKeyFile, svid.X509SvidKey) + bundles, err := x509.ParseCertificates(svid.Bundle) if err != nil { return err } - err = s.writeCerts(svidBundleFile, svid.Bundle) - if err != nil { + // Add intermediates into bundles, and remove them from certs + if s.config.AddIntermediatesToBundle { + bundles = append(bundles, certs[1:]...) + certs = []*x509.Certificate{certs[0]} + } + + if err := s.writeCerts(svidFile, certs); err != nil { return err } - return nil -} + if err := s.writeKey(svidKeyFile, svid.X509SvidKey); err != nil { + return err + } -// writeCerts takes a slice of bytes, which may contain multiple certificates, -// and encodes them as PEM blocks, writing them to file -func (s *Sidecar) writeCerts(file string, data []byte) error { - certs, err := x509.ParseCertificates(data) - if err != nil { + if err := s.writeCerts(svidBundleFile, bundles); err != nil { return err } - pemData := []byte{} + return nil +} + +// writeCerts takes an array of certificates, +// and encodes them as PEM blocks, writing them to file +func (s *Sidecar) writeCerts(file string, certs []*x509.Certificate) error { + var pemData []byte for _, cert := range certs { b := &pem.Block{ Type: "CERTIFICATE", diff --git a/pkg/sidecar/sidecar_test.go b/pkg/sidecar/sidecar_test.go index a65d040e..fed3feef 100644 --- a/pkg/sidecar/sidecar_test.go +++ b/pkg/sidecar/sidecar_test.go @@ -2,6 +2,7 @@ package sidecar import ( "context" + "crypto" "crypto/x509" "errors" "io/ioutil" @@ -14,6 +15,7 @@ import ( "github.com/spiffe/spiffe-helper/test/util" "github.com/spiffe/go-spiffe/proto/spiffe/workload" + "github.com/spiffe/go-spiffe/spiffetest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,7 +24,37 @@ import ( //running the Sidecar Daemon, when a SVID Response is sent to the //UpdateChan on the WorkloadAPI client, the PEM files are stored on disk func TestSidecar_RunDaemon(t *testing.T) { - var wg sync.WaitGroup + // Create root CA + domain1CA := spiffetest.NewCA(t) + // Create an intermediate certificate + domain1Inter := domain1CA.CreateCA() + domain1Bundle := domain1CA.Roots() + + // Svid with intermediate + svidChainWithIntermediate, svidKeyWithIntermediate := domain1Inter.CreateX509SVID("spiffe://example.test/workloadWithIntermediate") + require.Len(t, svidChainWithIntermediate, 2) + + // Add cert with intermediate into an svid + svidWithIntermediate := []spiffetest.X509SVID{ + { + CertChain: svidChainWithIntermediate, + Key: svidKeyWithIntermediate, + }, + } + + // Concat bundles with intermediate certificate + bundleWithIntermediate := domain1CA.Roots() + bundleWithIntermediate = append(bundleWithIntermediate, svidChainWithIntermediate[1:]...) + + // Create a single svid without intermediate + svidChain, svidKey := domain1CA.CreateX509SVID("spiffe://example.test/workload") + require.Len(t, svidChain, 1) + svid := []spiffetest.X509SVID{ + { + CertChain: svidChain, + Key: svidKey, + }, + } tmpdir, err := ioutil.TempDir("", "sidecar-run-daemon") require.NoError(t, err) @@ -46,39 +78,113 @@ func TestSidecar_RunDaemon(t *testing.T) { sidecar := Sidecar{ config: config, workloadAPIClient: workloadClient, + certReadyChan: make(chan struct{}, 1), } + defer close(sidecar.certReadyChan) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - wg.Add(1) + errCh := make(chan error, 1) go func() { - defer wg.Done() err = sidecar.RunDaemon(ctx) - require.NoError(t, err) + errCh <- err }() - x509SvidTestResponse := x509SvidResponse(t) - - //send a X509SVIDResponse to Updates channel - updateMockChan <- x509SvidTestResponse + testCases := []struct { + name string + response *spiffetest.X509SVIDResponse + certs []*x509.Certificate + key crypto.Signer + bundle []*x509.Certificate - //send signal to stop the Daemon - cancel() - wg.Wait() + intermediateInBundle bool + }{ + { + name: "svid with intermediate", + response: &spiffetest.X509SVIDResponse{ + Bundle: domain1Bundle, + SVIDs: svidWithIntermediate, + }, + certs: svidChainWithIntermediate, + key: svidKeyWithIntermediate, + bundle: domain1Bundle, + }, + { + name: "intermediate in bundle", + response: &spiffetest.X509SVIDResponse{ + Bundle: domain1Bundle, + SVIDs: svidWithIntermediate, + }, + // Only first certificate is expected + certs: []*x509.Certificate{svidChainWithIntermediate[0]}, + key: svidKeyWithIntermediate, + // A concatenation between bundle and intermediate is expected + bundle: bundleWithIntermediate, + + intermediateInBundle: true, + }, + { + name: "single svid with intermediate in bundle", + response: &spiffetest.X509SVIDResponse{ + Bundle: domain1CA.Roots(), + SVIDs: svid, + }, + certs: svidChain, + key: svidKey, + bundle: domain1Bundle, + intermediateInBundle: true, + }, + { + name: "single svid", + response: &spiffetest.X509SVIDResponse{ + Bundle: domain1CA.Roots(), + SVIDs: svid, + }, + certs: svidChain, + key: svidKey, + bundle: domain1Bundle, + }, + } - //The expected files svidFile := path.Join(tmpdir, config.SvidFileName) svidKeyFile := path.Join(tmpdir, config.SvidKeyFileName) svidBundleFile := path.Join(tmpdir, config.SvidBundleFileName) - if _, err := os.Stat(svidFile); err != nil { - t.Errorf("error %v with file: %v", err, svidFile) - } - if _, err := os.Stat(svidKeyFile); err != nil { - t.Errorf("error %v with file: %v", err, svidKeyFile) - } - if _, err := os.Stat(svidBundleFile); err != nil { - t.Errorf("error %v with file: %v", err, svidBundleFile) + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + sidecar.config.AddIntermediatesToBundle = testCase.intermediateInBundle + + // Push response to start updating process + updateMockChan <- testCase.response.ToProto(t) + + // Wait until response is processed + select { + case <-sidecar.CertReadyChan(): + //continue + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + } + + // Load certificates from disk and validate it is expected + certs, err := util.LoadCertificates(svidFile) + require.NoError(t, err) + require.Equal(t, testCase.certs, certs) + + // Load key from disk and validate it is expected + key, err := util.LoadPrivateKey(svidKeyFile) + require.NoError(t, err) + require.Equal(t, testCase.key, key) + + // Load bundle from disk and validate it is expected + bundles, err := util.LoadCertificates(svidBundleFile) + require.NoError(t, err) + require.Equal(t, testCase.bundle, bundles) + }) } + + cancel() + err = <-errCh + require.NoError(t, err) } //Tests that when there is no defaultTimeout in the config, it uses @@ -192,31 +298,3 @@ func (m MockWorkloadClient) CurrentSVID() (*workload.X509SVIDResponse, error) { } return m.current, nil } - -// creates a X509SVIDResponse reading test certs from files -func x509SvidResponse(t *testing.T) *workload.X509SVIDResponse { - // TODO: refactor to generate certificates instead reading from disk - svid, key, err := util.LoadSVIDFixture() - if err != nil { - t.Errorf("could not load svid fixture: %v", err) - } - ca, err := util.LoadCA() - if err != nil { - t.Errorf("could not load ca: %v", err) - } - - keyData, err := x509.MarshalPKCS8PrivateKey(key) - if err != nil { - t.Errorf("could not marshal private key: %v", err) - } - - svidMsg := &workload.X509SVID{ - SpiffeId: "spiffe://example.org/foo", - X509Svid: svid.Raw, - X509SvidKey: keyData, - Bundle: ca.Raw, - } - return &workload.X509SVIDResponse{ - Svids: []*workload.X509SVID{svidMsg}, - } -} diff --git a/test/fixture/certs/ca.pem b/test/fixture/certs/ca.pem deleted file mode 100644 index 7609b5c2..00000000 --- a/test/fixture/certs/ca.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBzjCCAVWgAwIBAgIBADAKBggqhkjOPQQDAzAdMQswCQYDVQQGEwJVUzEOMAwG -A1UEChMFTXlPcmcwHhcNMTkxMDA4MTM1MjI5WhcNMzkxMDAzMTM1MjM5WjAdMQsw -CQYDVQQGEwJVUzEOMAwGA1UEChMFTXlPcmcwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AAQEJ1I42DjoPBwO9y/aPyZonuFDXx1KwZRk29XN4NsiN3TDwus0Bas7kukQrD3g -+BACPrdAGvB0S+7pWNoutOhW1gFHMQAV6TpEwnY4Ptr+9xQy1Gbusj14oCnxl5AM -wjujaTBnMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRaGXg8RBw79Qp2t9Z6UjiR6mZDGzAlBgNVHREEHjAchhpzcGlmZmU6Ly92YXVs -dC5leGFtcGxlLmNvbTAKBggqhkjOPQQDAwNnADBkAjB3+d6C9WeXlRd16d9nQsGY -53P+5pg4C9C1SQRsdMdquG8ph3Rk8gsHlsKlCDEZrNwCMFxGiEm0A/lfdvTnlvEm -QpvOkM2QdG4VexaDY177vYVXnRFBZESpi4+GPbbywTcj+g== ------END CERTIFICATE----- diff --git a/test/fixture/certs/svid.pem b/test/fixture/certs/svid.pem deleted file mode 100644 index d9410adf..00000000 --- a/test/fixture/certs/svid.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBXzCB6gIJANXCDoURTF5MMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNVBAMMDFBF -TVVUSUxURVNUMTAeFw0xODA3MTYyMzU5NTZaFw00NTEyMDEyMzU5NTZaMBcxFTAT -BgNVBAMMDFBFTVVUSUxURVNUMTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQDMfDxC -DcBTMAjrmo+yNBuYjavI47dPGPrqIXzfAx7L6M2Bg1ZYDaO8xXgc0+7aZZRg7Fe1 -Gt0EJEourKA6qN0z4gTU5KWZrPLPwPHU75F90jgThdkmHdO7j3lr2MPjsvUCAwEA -ATANBgkqhkiG9w0BAQsFAANhAEsa1QiHgPwW0V4VLtRk7xyKIyCo+D0rgQA1qLmW -69aMW12GE+sxGo7INDP2bdQGB/udG5V6FnWNTP89VwakKjU4l6LoqtUtncwoGNgT -U2aPnxQpNXW7pWdBVSIBhSnptw== ------END CERTIFICATE----- diff --git a/test/fixture/certs/svid_key.pem b/test/fixture/certs/svid_key.pem deleted file mode 100644 index 2c29c20c..00000000 --- a/test/fixture/certs/svid_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEILfziMm/DrLM/+WzZF7Z8xXtU9ndA/arl/S6ItTueW/MoAoGCCqGSM49 -AwEHoUQDQgAEw3v9nes/j9PeSN3SJKHCq+G98wMvtakA7qF6mvIt2Dj5OXy4m+Dk -c6g/TSRXKyoyPiy7YqUHvcrv3mNStMoQWg== ------END EC PRIVATE KEY----- diff --git a/test/fixture/config/helper.conf b/test/fixture/config/helper.conf index f79de0d9..930c480d 100644 --- a/test/fixture/config/helper.conf +++ b/test/fixture/config/helper.conf @@ -7,3 +7,4 @@ svidFileName = "svid.pem" svidKeyFileName = "svid_key.pem" svidBundleFileName = "svid_bundle.pem" timeout = "10s" +addIntermediatesToBundle = true diff --git a/test/util/cert_fixtures.go b/test/util/cert_fixtures.go deleted file mode 100644 index a729241a..00000000 --- a/test/util/cert_fixtures.go +++ /dev/null @@ -1,88 +0,0 @@ -package util - -import ( - "crypto/ecdsa" - "crypto/x509" - "encoding/pem" - "fmt" - "io/ioutil" - "path" - "runtime" -) - -var ( - svidPath = path.Join(ProjectRoot(), "test/fixture/certs/svid.pem") - svidKeyPath = path.Join(ProjectRoot(), "test/fixture/certs/svid_key.pem") - caPath = path.Join(ProjectRoot(), "test/fixture/certs/ca.pem") -) - -// LoadCA reads, parses, and returns the pre-defined CA fixture -func LoadCA() (ca *x509.Certificate, err error) { - return LoadCert(caPath) -} - -// LoadSVIDFixture reads, parses, and returns the pre-defined SVID fixture and key -func LoadSVIDFixture() (svid *x509.Certificate, key *ecdsa.PrivateKey, err error) { - return LoadCertAndKey(svidPath, svidKeyPath) -} - -// LoadCertAndKey reads and parses both a certificate and a private key at once -func LoadCertAndKey(crtPath, keyPath string) (*x509.Certificate, *ecdsa.PrivateKey, error) { - crt, err := LoadCert(crtPath) - if err != nil { - return crt, nil, err - } - - key, err := LoadKey(keyPath) - return crt, key, err -} - -// LoadCert reads and parses an X.509 certificate at the specified path -func LoadCert(path string) (*x509.Certificate, error) { - block, err := LoadPEM(path) - if err != nil { - return nil, err - } - - crt, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err - } - - return crt, nil -} - -// LoadKey reads and parses the ECDSA private key at the specified path -func LoadKey(path string) (*ecdsa.PrivateKey, error) { - block, err := LoadPEM(path) - if err != nil { - return nil, err - } - - key, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return nil, err - } - - return key, nil -} - -// LoadPEM reads and parses the PEM structure at the specified path -func LoadPEM(path string) (*pem.Block, error) { - dat, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - blk, rest := pem.Decode(dat) - if len(rest) > 0 { - return nil, fmt.Errorf("error decoding certificate at %s", path) - } - - return blk, nil -} - -func ProjectRoot() string { - _, p, _, _ := runtime.Caller(0) - return path.Join(p, "../../../") -} diff --git a/test/util/load.go b/test/util/load.go new file mode 100644 index 00000000..933a3ea9 --- /dev/null +++ b/test/util/load.go @@ -0,0 +1,59 @@ +package util + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" +) + +func LoadPrivateKey(path string) (crypto.Signer, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + block, _ := pem.Decode(data) + + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return key.(crypto.Signer), nil +} + +// LoadCertificates loads one or more certificates into an []*x509.Certificate from +// a PEM file on disk. +func LoadCertificates(path string) ([]*x509.Certificate, error) { + rest, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + var certs []*x509.Certificate + for blockno := 0; ; blockno++ { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type != "CERTIFICATE" { + continue + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse certificate in block %d: %v", blockno, err) + } + certs = append(certs, cert) + } + + if len(certs) == 0 { + return nil, errors.New("no certificates found in file") + } + + return certs, nil +}