From 69f766d41d530b0b398bf6d9c477124856bf6061 Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Mon, 27 Jul 2020 07:50:59 -0500 Subject: [PATCH] Extend certauthority to support loading an existing CA. I think we may still split this apart into multiple packages, but for now it works pretty well in both use cases. Signed-off-by: Matt Moyer --- internal/certauthority/certauthority.go | 113 ++++--- internal/certauthority/certauthority_test.go | 300 +++++++++++++------ internal/certauthority/testdata/empty | 0 internal/certauthority/testdata/invalid | 1 + internal/certauthority/testdata/multiple.crt | 34 +++ internal/certauthority/testdata/test.crt | 17 ++ internal/certauthority/testdata/test.key | 27 ++ internal/certauthority/testdata/test2.key | 27 ++ 8 files changed, 394 insertions(+), 125 deletions(-) create mode 100644 internal/certauthority/testdata/empty create mode 100644 internal/certauthority/testdata/invalid create mode 100644 internal/certauthority/testdata/multiple.crt create mode 100644 internal/certauthority/testdata/test.crt create mode 100644 internal/certauthority/testdata/test.key create mode 100644 internal/certauthority/testdata/test2.key diff --git a/internal/certauthority/certauthority.go b/internal/certauthority/certauthority.go index 8026d8e6..8c4d6e54 100644 --- a/internal/certauthority/certauthority.go +++ b/internal/certauthority/certauthority.go @@ -22,8 +22,7 @@ import ( "time" ) -// CA holds the state for a simple x509 certificate authority suitable for use in an aggregated API service. -type CA struct { +type env struct { // secure random number generators for various steps (usually crypto/rand.Reader, but broken out here for tests). serialRNG io.Reader keygenRNG io.Reader @@ -32,45 +31,70 @@ type CA struct { // clock tells the current time (usually time.Now(), but broken out here for tests). clock func() time.Time + // parse function to parse an ASN.1 byte slice into an x509 struct (normally x509.ParseCertificate) + parseCert func([]byte) (*x509.Certificate, error) +} + +// CA holds the state for a simple x509 certificate authority suitable for use in an aggregated API service. +type CA struct { + // caCert is the DER-encoded certificate for the current CA. + caCertBytes []byte + // signer is the private key for the current CA. signer crypto.Signer - // caCert is the DER-encoded certificate for the current CA. - caCertBytes []byte + // env is our reference to the outside world (clocks and random number generation). + env env } -// Option to pass when calling New. -type Option func(*CA) error - -func New(subject pkix.Name, opts ...Option) (*CA, error) { - // Initialize the result by starting with some defaults and applying any provided options. - ca := CA{ +// secureEnv is the "real" environment using secure RNGs and the real system clock. +func secureEnv() env { + return env{ serialRNG: rand.Reader, keygenRNG: rand.Reader, signingRNG: rand.Reader, clock: time.Now, + parseCert: x509.ParseCertificate, } - for _, opt := range opts { - if err := opt(&ca); err != nil { - return nil, err - } - } +} +// Load a certificate authority from an existing certificate and private key (in PEM format). +func Load(certPath string, keyPath string) (*CA, error) { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("could not load CA: %w", err) + } + if certCount := len(cert.Certificate); certCount != 1 { + return nil, fmt.Errorf("expected CA to be a single certificate, found %d certificates", certCount) + } + return &CA{ + caCertBytes: cert.Certificate[0], + signer: cert.PrivateKey.(crypto.Signer), + env: secureEnv(), + }, nil +} + +// New generates a fresh certificate authority with the given subject. +func New(subject pkix.Name) (*CA, error) { return newInternal(subject, secureEnv()) } + +// newInternal is the internal guts of New, broken out for easier testing. +func newInternal(subject pkix.Name, env env) (*CA, error) { + ca := CA{env: env} // Generate a random serial for the CA - serialNumber, err := randomSerial(ca.serialRNG) + serialNumber, err := randomSerial(env.serialRNG) if err != nil { return nil, fmt.Errorf("could not generate CA serial: %w", err) } // Generate a new P256 keypair. - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), ca.keygenRNG) + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), env.keygenRNG) if err != nil { return nil, fmt.Errorf("could not generate CA private key: %w", err) } ca.signer = privateKey // Make a CA certificate valid for 100 years and backdated by one minute. - now := ca.clock() + now := env.clock() notBefore := now.Add(-1 * time.Minute) notAfter := now.Add(24 * time.Hour * 365 * 100) @@ -87,7 +111,7 @@ func New(subject pkix.Name, opts ...Option) (*CA, error) { } // Self-sign the CA to get the DER certificate. - caCertBytes, err := x509.CreateCertificate(ca.signingRNG, &caTemplate, &caTemplate, &privateKey.PublicKey, privateKey) + caCertBytes, err := x509.CreateCertificate(env.signingRNG, &caTemplate, &caTemplate, &privateKey.PublicKey, privateKey) if err != nil { return nil, fmt.Errorf("could not issue CA certificate: %w", err) } @@ -95,37 +119,27 @@ func New(subject pkix.Name, opts ...Option) (*CA, error) { return &ca, nil } -// WriteBundle writes the current CA signing bundle in concatenated PEM format. -func (c *CA) WriteBundle(out io.Writer) error { - if err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: c.caCertBytes}); err != nil { - return fmt.Errorf("could not encode CA certificate to PEM: %w", err) - } - return nil -} - // Bundle returns the current CA signing bundle in concatenated PEM format. -func (c *CA) Bundle() ([]byte, error) { - var out bytes.Buffer - err := c.WriteBundle(&out) - return out.Bytes(), err +func (c *CA) Bundle() []byte { + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.caCertBytes}) } // Issue a new server certificate for the given identity and duration. func (c *CA) Issue(subject pkix.Name, dnsNames []string, ttl time.Duration) (*tls.Certificate, error) { // Choose a random 128 bit serial number. - serialNumber, err := randomSerial(c.serialRNG) + serialNumber, err := randomSerial(c.env.serialRNG) if err != nil { return nil, fmt.Errorf("could not generate serial number for certificate: %w", err) } // Generate a new P256 keypair. - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), c.keygenRNG) + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), c.env.keygenRNG) if err != nil { return nil, fmt.Errorf("could not generate private key: %w", err) } // Make a CA caCert valid for the requested TTL and backdated by one minute. - now := c.clock() + now := c.env.clock() notBefore := now.Add(-1 * time.Minute) notAfter := now.Add(ttl) @@ -153,7 +167,7 @@ func (c *CA) Issue(subject pkix.Name, dnsNames []string, ttl time.Duration) (*tl } // Parse the DER encoded certificate back out into an *x509.Certificate. - newCert, err := x509.ParseCertificate(certBytes) + newCert, err := c.env.parseCert(certBytes) if err != nil { return nil, fmt.Errorf("could not parse certificate: %w", err) } @@ -166,6 +180,35 @@ func (c *CA) Issue(subject pkix.Name, dnsNames []string, ttl time.Duration) (*tl }, nil } +// IssuePEM issues a new server certificate for the given identity and duration, returning it as a pair of +// PEM-formatted byte slices for the certificate and private key. +func (c *CA) IssuePEM(subject pkix.Name, dnsNames []string, ttl time.Duration) ([]byte, []byte, error) { + return toPEM(c.Issue(subject, dnsNames, ttl)) +} + +func toPEM(cert *tls.Certificate, err error) ([]byte, []byte, error) { + // If the wrapped Issue() returned an error, pass it back. + if err != nil { + return nil, nil, err + } + + // Encode the certificate(s) to PEM. + certPEMBlocks := make([][]byte, 0, len(cert.Certificate)) + for _, c := range cert.Certificate { + certPEMBlocks = append(certPEMBlocks, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c})) + } + certPEM := bytes.Join(certPEMBlocks, nil) + + // Encode the private key to PEM, which means we first need to convert to PKCS8 (DER). + privateKeyPKCS8, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal private key into PKCS8: %w", err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyPKCS8}) + + return certPEM, keyPEM, nil +} + // randomSerial generates a random 128 bit serial number. func randomSerial(rng io.Reader) (*big.Int, error) { return rand.Int(rng, new(big.Int).Lsh(big.NewInt(1), 128)) diff --git a/internal/certauthority/certauthority_test.go b/internal/certauthority/certauthority_test.go index 44d99303..f0ae2dd8 100644 --- a/internal/certauthority/certauthority_test.go +++ b/internal/certauthority/certauthority_test.go @@ -6,8 +6,8 @@ SPDX-License-Identifier: Apache-2.0 package certauthority import ( - "bytes" "crypto" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "fmt" @@ -19,65 +19,149 @@ import ( "github.com/stretchr/testify/require" ) -func TestNew(t *testing.T) { - now := time.Date(2020, 7, 10, 12, 41, 12, 1234, time.UTC) - +func TestLoad(t *testing.T) { tests := []struct { - name string - opts []Option - wantErr string + name string + certPath string + keyPath string + wantErr string }{ { - name: "error option", - opts: []Option{func(ca *CA) error { - return fmt.Errorf("some error") - }}, - wantErr: "some error", + name: "missing cert", + certPath: "./testdata/cert-does-not-exist", + keyPath: "./testdata/test.key", + wantErr: "could not load CA: open ./testdata/cert-does-not-exist: no such file or directory", }, { - name: "failed to generate CA serial", - opts: []Option{func(ca *CA) error { - ca.serialRNG = strings.NewReader("") - ca.keygenRNG = strings.NewReader("") - ca.signingRNG = strings.NewReader("") - return nil - }}, - wantErr: "could not generate CA serial: EOF", + name: "empty cert", + certPath: "./testdata/empty", + keyPath: "./testdata/test.key", + wantErr: "could not load CA: tls: failed to find any PEM data in certificate input", }, { - name: "failed to generate CA key", - opts: []Option{func(ca *CA) error { - ca.serialRNG = strings.NewReader(strings.Repeat("x", 64)) - ca.keygenRNG = strings.NewReader("") - return nil - }}, - wantErr: "could not generate CA private key: EOF", + name: "invalid cert", + certPath: "./testdata/invalid", + keyPath: "./testdata/test.key", + wantErr: "could not load CA: tls: failed to find any PEM data in certificate input", }, { - name: "failed to self-sign", - opts: []Option{func(ca *CA) error { - ca.serialRNG = strings.NewReader(strings.Repeat("x", 64)) - ca.keygenRNG = strings.NewReader(strings.Repeat("y", 64)) - ca.signingRNG = strings.NewReader("") - return nil - }}, - wantErr: "could not issue CA certificate: EOF", + name: "missing key", + certPath: "./testdata/test.crt", + keyPath: "./testdata/key-does-not-exist", + wantErr: "could not load CA: open ./testdata/key-does-not-exist: no such file or directory", }, { - name: "success", - opts: []Option{func(ca *CA) error { - ca.serialRNG = strings.NewReader(strings.Repeat("x", 64)) - ca.keygenRNG = strings.NewReader(strings.Repeat("y", 64)) - ca.signingRNG = strings.NewReader(strings.Repeat("z", 64)) - ca.clock = func() time.Time { return now } - return nil - }}, + name: "empty key", + certPath: "./testdata/test.crt", + keyPath: "./testdata/empty", + wantErr: "could not load CA: tls: failed to find any PEM data in key input", + }, + { + name: "invalid key", + certPath: "./testdata/test.crt", + keyPath: "./testdata/invalid", + wantErr: "could not load CA: tls: failed to find any PEM data in key input", + }, + { + name: "mismatched cert and key", + certPath: "./testdata/test.crt", + keyPath: "./testdata/test2.key", + wantErr: "could not load CA: tls: private key does not match public key", + }, + { + name: "multiple certs", + certPath: "./testdata/multiple.crt", + keyPath: "./testdata/test.key", + wantErr: "expected CA to be a single certificate, found 2 certificates", + }, + { + name: "success", + certPath: "./testdata/test.crt", + keyPath: "./testdata/test.key", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - got, err := New(pkix.Name{CommonName: "Test CA"}, tt.opts...) + ca, err := Load(tt.certPath, tt.keyPath) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + return + } + require.NoError(t, err) + require.NotEmpty(t, ca.caCertBytes) + require.NotNil(t, ca.signer) + }) + } +} + +func TestNew(t *testing.T) { + got, err := New(pkix.Name{CommonName: "Test CA"}) + require.NoError(t, err) + require.NotNil(t, got) + + // Make sure the CA certificate looks roughly like what we expect. + caCert, err := x509.ParseCertificate(got.caCertBytes) + require.NoError(t, err) + require.Equal(t, "Test CA", caCert.Subject.CommonName) +} + +func TestNewInternal(t *testing.T) { + now := time.Date(2020, 7, 10, 12, 41, 12, 1234, time.UTC) + + tests := []struct { + name string + env env + wantErr string + wantCommonName string + wantNotBefore time.Time + wantNotAfter time.Time + }{ + { + name: "failed to generate CA serial", + env: env{ + serialRNG: strings.NewReader(""), + keygenRNG: strings.NewReader(""), + signingRNG: strings.NewReader(""), + }, + wantErr: "could not generate CA serial: EOF", + }, + { + name: "failed to generate CA key", + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(""), + signingRNG: strings.NewReader(""), + }, + wantErr: "could not generate CA private key: EOF", + }, + { + name: "failed to self-sign", + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(strings.Repeat("y", 64)), + signingRNG: strings.NewReader(""), + clock: func() time.Time { return now }, + }, + wantErr: "could not issue CA certificate: EOF", + }, + { + name: "success", + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(strings.Repeat("y", 64)), + signingRNG: strings.NewReader(strings.Repeat("z", 64)), + clock: func() time.Time { return now }, + }, + wantCommonName: "Test CA", + wantNotAfter: now.Add(100 * 365 * 24 * time.Hour), + wantNotBefore: now.Add(-1 * time.Minute), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := newInternal(pkix.Name{CommonName: "Test CA"}, tt.env) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) @@ -89,46 +173,17 @@ func TestNew(t *testing.T) { // Make sure the CA certificate looks roughly like what we expect. caCert, err := x509.ParseCertificate(got.caCertBytes) require.NoError(t, err) - require.Equal(t, "Test CA", caCert.Subject.CommonName) - require.Equal(t, now.Add(100*365*24*time.Hour).Unix(), caCert.NotAfter.Unix()) - require.Equal(t, now.Add(-1*time.Minute).Unix(), caCert.NotBefore.Unix()) + require.Equal(t, tt.wantCommonName, caCert.Subject.CommonName) + require.Equal(t, tt.wantNotAfter.Unix(), caCert.NotAfter.Unix()) + require.Equal(t, tt.wantNotBefore.Unix(), caCert.NotBefore.Unix()) }) } } -type errWriter struct { - err error -} - -func (e *errWriter) Write(p []byte) (n int, err error) { return 0, e.err } - -func TestWriteBundle(t *testing.T) { - t.Run("error", func(t *testing.T) { - ca := CA{} - out := errWriter{fmt.Errorf("some error")} - require.EqualError(t, ca.WriteBundle(&out), "could not encode CA certificate to PEM: some error") - }) - - t.Run("empty", func(t *testing.T) { - ca := CA{} - var out bytes.Buffer - require.NoError(t, ca.WriteBundle(&out)) - require.Equal(t, "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n", out.String()) - }) - - t.Run("success", func(t *testing.T) { - ca := CA{caCertBytes: []byte{1, 2, 3, 4, 5, 6, 7, 8}} - var out bytes.Buffer - require.NoError(t, ca.WriteBundle(&out)) - require.Equal(t, "-----BEGIN CERTIFICATE-----\nAQIDBAUGBwg=\n-----END CERTIFICATE-----\n", out.String()) - }) -} - func TestBundle(t *testing.T) { t.Run("success", func(t *testing.T) { ca := CA{caCertBytes: []byte{1, 2, 3, 4, 5, 6, 7, 8}} - got, err := ca.Bundle() - require.NoError(t, err) + got := ca.Bundle() require.Equal(t, "-----BEGIN CERTIFICATE-----\nAQIDBAUGBwg=\n-----END CERTIFICATE-----\n", string(got)) }) } @@ -147,7 +202,7 @@ func (e *errSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, er func TestIssue(t *testing.T) { now := time.Date(2020, 7, 10, 12, 41, 12, 1234, time.UTC) - realCA, err := New(pkix.Name{CommonName: "Test CA"}) + realCA, err := Load("./testdata/test.crt", "./testdata/test.key") require.NoError(t, err) tests := []struct { @@ -158,33 +213,41 @@ func TestIssue(t *testing.T) { { name: "failed to generate serial", ca: CA{ - serialRNG: strings.NewReader(""), + env: env{ + serialRNG: strings.NewReader(""), + }, }, wantErr: "could not generate serial number for certificate: EOF", }, { name: "failed to generate keypair", ca: CA{ - serialRNG: strings.NewReader(strings.Repeat("x", 64)), - keygenRNG: strings.NewReader(""), + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(""), + }, }, wantErr: "could not generate private key: EOF", }, { name: "invalid CA certificate", ca: CA{ - serialRNG: strings.NewReader(strings.Repeat("x", 64)), - keygenRNG: strings.NewReader(strings.Repeat("x", 64)), - clock: func() time.Time { return now }, + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(strings.Repeat("x", 64)), + clock: func() time.Time { return now }, + }, }, wantErr: "could not parse CA certificate: asn1: syntax error: sequence truncated", }, { name: "signing error", ca: CA{ - serialRNG: strings.NewReader(strings.Repeat("x", 64)), - keygenRNG: strings.NewReader(strings.Repeat("x", 64)), - clock: func() time.Time { return now }, + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(strings.Repeat("x", 64)), + clock: func() time.Time { return now }, + }, caCertBytes: realCA.caCertBytes, signer: &errSigner{ pubkey: realCA.signer.Public(), @@ -196,9 +259,28 @@ func TestIssue(t *testing.T) { { name: "success", ca: CA{ - serialRNG: strings.NewReader(strings.Repeat("x", 64)), - keygenRNG: strings.NewReader(strings.Repeat("x", 64)), - clock: func() time.Time { return now }, + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(strings.Repeat("x", 64)), + clock: func() time.Time { return now }, + parseCert: func(_ []byte) (*x509.Certificate, error) { + return nil, fmt.Errorf("some parse certificate error") + }, + }, + caCertBytes: realCA.caCertBytes, + signer: realCA.signer, + }, + wantErr: "could not parse certificate: some parse certificate error", + }, + { + name: "success", + ca: CA{ + env: env{ + serialRNG: strings.NewReader(strings.Repeat("x", 64)), + keygenRNG: strings.NewReader(strings.Repeat("x", 64)), + clock: func() time.Time { return now }, + parseCert: x509.ParseCertificate, + }, caCertBytes: realCA.caCertBytes, signer: realCA.signer, }, @@ -218,3 +300,41 @@ func TestIssue(t *testing.T) { }) } } + +func TestIssuePEM(t *testing.T) { + realCA, err := Load("./testdata/test.crt", "./testdata/test.key") + require.NoError(t, err) + + certPEM, keyPEM, err := realCA.IssuePEM(pkix.Name{CommonName: "Test Server"}, []string{"example.com"}, 10*time.Minute) + require.NoError(t, err) + require.NotEmpty(t, certPEM) + require.NotEmpty(t, keyPEM) +} + +func TestToPEM(t *testing.T) { + realCert, err := tls.LoadX509KeyPair("./testdata/test.crt", "./testdata/test.key") + require.NoError(t, err) + + t.Run("error from input", func(t *testing.T) { + certPEM, keyPEM, err := toPEM(nil, fmt.Errorf("some error")) + require.EqualError(t, err, "some error") + require.Nil(t, certPEM) + require.Nil(t, keyPEM) + }) + + t.Run("invalid private key", func(t *testing.T) { + cert := realCert + cert.PrivateKey = nil + certPEM, keyPEM, err := toPEM(&cert, nil) + require.EqualError(t, err, "failed to marshal private key into PKCS8: x509: unknown key type while marshaling PKCS#8: ") + require.Nil(t, certPEM) + require.Nil(t, keyPEM) + }) + + t.Run("success", func(t *testing.T) { + certPEM, keyPEM, err := toPEM(&realCert, nil) + require.NoError(t, err) + require.NotEmpty(t, certPEM) + require.NotEmpty(t, keyPEM) + }) +} diff --git a/internal/certauthority/testdata/empty b/internal/certauthority/testdata/empty new file mode 100644 index 00000000..e69de29b diff --git a/internal/certauthority/testdata/invalid b/internal/certauthority/testdata/invalid new file mode 100644 index 00000000..f31839d5 --- /dev/null +++ b/internal/certauthority/testdata/invalid @@ -0,0 +1 @@ +Some invalid file contents diff --git a/internal/certauthority/testdata/multiple.crt b/internal/certauthority/testdata/multiple.crt new file mode 100644 index 00000000..b69be2ad --- /dev/null +++ b/internal/certauthority/testdata/multiple.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTIwMDcyNTIxMDQxOFoXDTMwMDcyMzIxMDQxOFowFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3K +hYv2gIQ1Dwzh2cWMid+ofAnvLIfV2Xv61vTLGprUI+XUqB4/gtf6X6UNn0Lett2n +d8p4wy7hw73hU/ggdvmWJvqBrSjc3JGfy+kj66fKXX+PTlbL7QbwiRvcSqIXIWlV +lHHxECWrED8jCulw/NVqfook/h5iNUCT9yswSJr/0fImiVnoTlIoEYG2eCNejZ5c +g39uD3ZTqd9ZxWwSLLnI+2kpJnZBPcd1ZQ8AQqzDgZtYRCqacn5gckQUKZWKQlxo +Eft6g1XHJouAWAZw7hEtk0v8rG0/eKF7wamxFi6BFVlbjWBsB4T9rApbdBWTKeCJ +Hv8fv5RMFSzpT3uzTO8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACh5RhbxqJe+Z/gc17cZhKNmdiwu +I2pLp3QBfwvN+Wbmajzw/7rYhY0d8JYVTJzXSCPWi6UAKxAtXOLF8WIIf9i39n6R +uKOBGW14FzzGyRJiD3qaG/JTvEW+SLhwl68Ndr5LHSnbugAqq31abcQy6Zl9v5A8 +JKC97Lj/Sn8rj7opKy4W3oq7NCQsAb0zh4IllRF6UvSnJySfsg7xdXHHpxYDHtOS +XcOu5ySUIZTgFe9RfeUZlGZ5xn0ckMlQ7qW2Wx1q0OVWw5us4NtkGqKrHG4Tn1X7 +uwo/Yytn5sDxrDv1/oii6AZOCsTPre4oD3wz4nmVzCVJcgrqH4Q24hT8WNg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICyzCCAbOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTIwMDcyMTIxMDcwMloXDTMwMDcxOTIxMTIwMlowFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALRz +y8TRjCTkqxTGpMoGa6nmZAZU76GqchVJh6x/T/utsfE822IgyLo5oNKKtLsNiXOT +pO1DdaPtwKJBlifmAIfPwPIOikcqYicLYMS6PM1f6pPGUM9zlYW198sQfTybVVQG +KBLEjyYbcFPtM98omXaILM+V8KXokMiSWgho36qH/abcdxKKTPJs44U+mby5gkew +Iee+8yXOiqCmCPc6a+UsU28eYt72XTiAMLKQsGu71Q99IMXYq6/kZbxxFk8zh2r+ +ZuaBbCbasclznutLnWB4hpfswLnTY3Ct9kv07mjzmIsLguOOQGzoVJ72xsLwxfoG +KS5jnTImXFVp28NFP6kCAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgKkMBIGA1UdEwEB +/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAF4E3IA8Wst9GOeFjjv/IPAp +LT89JHehMrTRMngw+Lvv4WG9rymtHFpvEiVgh8MhcP+MuNc95tIPLaonpTdKJ/dR +PgDlpOFL0dvaskuOVWQYZnD/ehf8zF+vlt9Mby3rJleruGETahdvQVd7DB61zSC6 +7g2rE2VqZ62Vj48RdEF9n+tYXWa4wRw3I1i8DUA/fNAS8P4/KXN6VvdVHe8y+nim +ASs8VX0QEWaGRIQZankFXv5p1/tXOFo/Wmo8vrVa9OsCXJm7iN5riwYiQn237iBJ +XmEs5jp04H7V7aDqZOX/N13uzQbGSPChAgWr/LxO1tIa6PmK+vfo60lRsBWeYrE= +-----END CERTIFICATE----- diff --git a/internal/certauthority/testdata/test.crt b/internal/certauthority/testdata/test.crt new file mode 100644 index 00000000..796a7690 --- /dev/null +++ b/internal/certauthority/testdata/test.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTIwMDcyNTIxMDQxOFoXDTMwMDcyMzIxMDQxOFowFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3K +hYv2gIQ1Dwzh2cWMid+ofAnvLIfV2Xv61vTLGprUI+XUqB4/gtf6X6UNn0Lett2n +d8p4wy7hw73hU/ggdvmWJvqBrSjc3JGfy+kj66fKXX+PTlbL7QbwiRvcSqIXIWlV +lHHxECWrED8jCulw/NVqfook/h5iNUCT9yswSJr/0fImiVnoTlIoEYG2eCNejZ5c +g39uD3ZTqd9ZxWwSLLnI+2kpJnZBPcd1ZQ8AQqzDgZtYRCqacn5gckQUKZWKQlxo +Eft6g1XHJouAWAZw7hEtk0v8rG0/eKF7wamxFi6BFVlbjWBsB4T9rApbdBWTKeCJ +Hv8fv5RMFSzpT3uzTO8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACh5RhbxqJe+Z/gc17cZhKNmdiwu +I2pLp3QBfwvN+Wbmajzw/7rYhY0d8JYVTJzXSCPWi6UAKxAtXOLF8WIIf9i39n6R +uKOBGW14FzzGyRJiD3qaG/JTvEW+SLhwl68Ndr5LHSnbugAqq31abcQy6Zl9v5A8 +JKC97Lj/Sn8rj7opKy4W3oq7NCQsAb0zh4IllRF6UvSnJySfsg7xdXHHpxYDHtOS +XcOu5ySUIZTgFe9RfeUZlGZ5xn0ckMlQ7qW2Wx1q0OVWw5us4NtkGqKrHG4Tn1X7 +uwo/Yytn5sDxrDv1/oii6AZOCsTPre4oD3wz4nmVzCVJcgrqH4Q24hT8WNg= +-----END CERTIFICATE----- diff --git a/internal/certauthority/testdata/test.key b/internal/certauthority/testdata/test.key new file mode 100644 index 00000000..7ad653ae --- /dev/null +++ b/internal/certauthority/testdata/test.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvcqFi/aAhDUPDOHZxYyJ36h8Ce8sh9XZe/rW9MsamtQj5dSo +Hj+C1/pfpQ2fQt623ad3ynjDLuHDveFT+CB2+ZYm+oGtKNzckZ/L6SPrp8pdf49O +VsvtBvCJG9xKohchaVWUcfEQJasQPyMK6XD81Wp+iiT+HmI1QJP3KzBImv/R8iaJ +WehOUigRgbZ4I16NnlyDf24PdlOp31nFbBIsucj7aSkmdkE9x3VlDwBCrMOBm1hE +KppyfmByRBQplYpCXGgR+3qDVccmi4BYBnDuES2TS/ysbT94oXvBqbEWLoEVWVuN +YGwHhP2sClt0FZMp4Ike/x+/lEwVLOlPe7NM7wIDAQABAoIBAFC1tUEmHNUcM0BJ +M3D9KQzB+63F1mwVlx1QOOV1EeVR3co5Ox1R6PSr9sycFGQ9jgqI0zp5TJe9Tp6L +GkhklfPh1MWnK9o6wlnzWKXWrrp2Jni+mpPyuOPAmq4Maniv2XeP+0bROwqpyojv +AA7yC7M+TH226ZJGNVs3EV9+cwHml0yuzBfIJn/rv/w2g+WRKM/MC0S7k2d8bRlA +NycKVGAGBhKTltjoVYOeh6aHEpSjK8zfaePjo5dYJvoVIli60YCgcJOU/8jXT+Np +1Fm7tRvAtj3pUp0Sqdaf2RUzh9jfJp2VFCHuSJ6TPqArOyQojtMcTHF0TiW7xrHP +xOCRIAECgYEAwGBPU7vdthMJBg+ORUoGQQaItTeJvQwIqJvbKD2osp4jhS1dGZBw +W30GKEc/gd8JNtOq9BBnMicPF7hktuy+bSPv41XPud67rSSO7Tsw20C10gFRq06B +zIJWFAUqK3IkvVc3VDmtSLSDox4QZ/BdqaMlQ5y5JCsC5kThmkZFlO8CgYEA/I9X +YHi6RioMJE1fqOHJL4DDjlezmcuRrD7fE5InKbtJZ2JhGYOX/C0KXnHTOWTCDxxN +FBvpvD6Xv5o3PhB9Z6k2fqvJ4GS8urkG/KU4xcC+bak+9ava8oaiSqG16zD9NH2P +jJ60NrbLl1J0pU9fiwuFVUKJ4hDZOfN9RqYdyAECgYAVwo8WhJiGgM6zfcz073OX +pVqPTPHqjVLpZ3+5pIfRdGvGI6R1QM5EuvaYVb7MPOM47WZX5wcVOC/P2g6iVlMP +21HGIC2384a9BfaYxOo40q/+SiHnw6CQ9mkwKIllkqqvNA9RGpkMMUb2i28For2l +c4vCgxa6DZdtXns6TRqPxwKBgCfY5cxOv/T6BVhk7MbUeM2J31DB/ZAyUhV/Bess +kAlBh19MYk2IOZ6L7KriApV3lDaWHIMjtEkDByYvyq98Io0MYZCywfMpca10K+oI +l2B7/I+IuGpCZxUEsO5dfTpSTGDPvqpND9niFVUWqVi7oTNq6ep9yQtl5SADjqxq +4SABAoGAIm0hUg1wtcS46cGLy6PIkPM5tocTSghtz4vFsuk/i4QA9GBoBO2gH6ty ++kJHmeaXt2dmgySp0QAWit5UlceEumB0NXnAdJZQxeGSFSyYkDWhwXd8wDceKo/1 +LfCU6Dk8IN/SsppVUWXQ2rlORvxlrHeCio8o0kS9Yiu55WMYg4g= +-----END RSA PRIVATE KEY----- diff --git a/internal/certauthority/testdata/test2.key b/internal/certauthority/testdata/test2.key new file mode 100644 index 00000000..19b7096c --- /dev/null +++ b/internal/certauthority/testdata/test2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqeddVCjzZNKp/hVprYRY4EKos0PkrpkA0cbQTIhKrJIU2JXW +G0/RgffNnfMEXGR/6xhGPGM9CU+jD5aZJtl/+7w0zhlZib1lQLeSIfOLGO4rqbsr +k4KXYosUaUD8qd6El1FXUB6OfpZFjmvE5qzFlpjOEbedDrgbzVeD6BsdeCrgT3G4 +Jly/79MWsOjGk8Oa+OS/4OLf6W0UnKCWbyENNDVrsihLByffv1YZZ+s4bOMFb1cA +Oc078TF/1M5ekTcicGdKKxr7CQT9RtR6iuPcVucuBhd9eDCsK4tH9PEWtyY/hISz +Pnrigt6LEW++IvHLJQ7mX+COzFMNramyK/F8aQIDAQABAoIBAFUd02OWIFkiMIdZ +std6tgujWWB1YtsVS5PMRg4ROVe61zap2dlU42B5BElctZKTxoHAZ29ZR/qiKs5k +Y9VSoQs7/jhB+tlGSLNjQ5I+sDCNINKnMe10PuLfShpwtCNlloc3+MXqiPhhz/bJ +hpsJcvM/Gf1GPyhgk40Lisl8zAamouLZ0qQSspmHmPo4ztpA2w81bG05GfTJTqVp +6pceOotVU4NNkiqq0SOF13ZajUaR3ifCnEbjkRnh8L8x0rqGUv7aAZMIc6x3kHRT +ZG2HfLvQW8QWsOWB6WIdZqAPMtrTJw0ozMfLJYfkfosGjLC99pjefMY7ENPnGwvN +FdOgwAECgYEA3WtNyi9bE5Licrv0Ys/A3oI11in2eKL8cmeJM/Hxvxz3EzQH8Kl8 +nYtfCP18kHSg7fddg4xbqW6NLPIkOtopDy0evTzPpZpxBc6mu1buqFm4EBeUAsIu +rK1Ol0dKxZ7EoFAQGHN0JpTd62diRHnxCiIvfQD55ClloyrR/Cd8SuECgYEAxHBk +YsDSFs9DFbhvFEWigfPPYjViHg9FsNBYodtuiyWXbkH8QXmjTpEaiLXk5Zc0VOYA +8OBYbxly1l6nAW9/v2LewPoxVQTyTnTnsLlarLo2ax8GT1NNu9gCPGzbEORvlHky +h/NCw0RBcmGnfTijoOVEC8RETbN1SK3x5as0qokCgYAM3myyAJiZhaL1qijlCVAb +XpQEc4HotwhXGd9mjnxPcD6H9jEz8pXUjkIiwqDXwH+N9R+RQrodGdjIsPYcGYvj +Xur3cq5a4KQLA1y7bK0ISdah0M0AcArIbHYx4qnc3IJvEtgso6EvkN1pDiQu+Kti +vGPoLwNXGHTYy+dScXUO4QKBgQCmgFFGNwOb28+T0IEuYJuOpJZKOs9QhUdfyCjo +ADMhdBp3lSx4Xt6h0HH6IJrEU7ZCo7V2deHfQWXJ9+58VAKmuOnwDeDUnF25THO5 +olIOB8PqZiCWChjgOAYlK2s/VTCSW2wOOY2ELw1+IvGxPNnMnadghdoTNiIaGX3o +WoZIaQKBgEDojPr84GMR+M5d2YrvLJmLSmDLwWR0j6soANUXsaLzZVZBifOSKIG8 +ySO4QOe4O2Nb1nWAfmEP964VwoQ0rHlkt0rJopwJTj4uYjhDRGIN+x2aJZ00bm4u +/1c7cowqxj/TkjTP4JASxBwBPUYpJxiajATTZiddxt4xRxaNVKlK +-----END RSA PRIVATE KEY-----