diff --git a/ca/client.go b/ca/client.go index 95b383e5..0bfe386f 100644 --- a/ca/client.go +++ b/ca/client.go @@ -25,13 +25,18 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca/identity" "github.com/smallstep/cli/config" "github.com/smallstep/cli/crypto/keys" + "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/x509util" "golang.org/x/net/http2" "gopkg.in/square/go-jose.v2/jwt" ) +// DisableIdentity is a global variable to disable the identity. +var DisableIdentity = false + // UserAgent will set the User-Agent header in the client requests. var UserAgent = "step-http-client/1.0" @@ -120,26 +125,18 @@ func (o *clientOptions) applyDefaultIdentity() error { } // Do not load an identity if something fails - b, err := ioutil.ReadFile(IdentityFile) + i, err := identity.LoadDefaultIdentity() if err != nil { return nil } - var identity Identity - if err := json.Unmarshal(b, &identity); err != nil { + if err := i.Validate(); err != nil { return nil } - if err := identity.Validate(); err != nil { - return nil - } - opts, err := identity.Options() + crt, err := i.TLSCertificate() if err != nil { return nil } - for _, fn := range opts { - if err := fn(o); err != nil { - return err - } - } + o.certificate = crt return nil } @@ -1111,6 +1108,21 @@ func CreateCertificateRequest(commonName string, sans ...string) (*api.Certifica return createCertificateRequest(commonName, sans, key) } +// CreateIdentityRequest returns a new CSR to create the identity. If an +// identity was already present it reuses the private key. +func CreateIdentityRequest(commonName string, sans ...string) (*api.CertificateRequest, crypto.PrivateKey, error) { + var identityKey crypto.PrivateKey + if i, err := identity.LoadDefaultIdentity(); err == nil && i.Key != "" { + if k, err := pemutil.Read(i.Key); err == nil { + identityKey = k + } + } + if identityKey == nil { + return CreateCertificateRequest(commonName, sans...) + } + return createCertificateRequest(commonName, sans, identityKey) +} + func createCertificateRequest(commonName string, sans []string, key crypto.PrivateKey) (*api.CertificateRequest, crypto.PrivateKey, error) { if len(sans) == 0 { sans = []string{commonName} diff --git a/ca/identity/client.go b/ca/identity/client.go new file mode 100644 index 00000000..d615a019 --- /dev/null +++ b/ca/identity/client.go @@ -0,0 +1,105 @@ +package identity + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + + "github.com/pkg/errors" +) + +// Client wraps http.Client with a transport using the step root and identity. +type Client struct { + CaURL *url.URL + *http.Client +} + +// ResolveReference resolves the given reference from the CaURL. +func (c *Client) ResolveReference(ref *url.URL) *url.URL { + return c.CaURL.ResolveReference(ref) +} + +// LoadStepClient configures an http.Client with the root in +// $STEPPATH/config/defaults.json and the identity defined in +// $STEPPATH/config/identity.json +func LoadClient() (*Client, error) { + b, err := ioutil.ReadFile(DefaultsFile) + if err != nil { + return nil, errors.Wrapf(err, "error reading %s", DefaultsFile) + } + + var defaults defaultsConfig + if err := json.Unmarshal(b, &defaults); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling %s", DefaultsFile) + } + if err := defaults.Validate(); err != nil { + return nil, errors.Wrapf(err, "error validating %s", DefaultsFile) + } + caURL, err := url.Parse(defaults.CaURL) + if err != nil { + return nil, errors.Wrapf(err, "error validating %s", DefaultsFile) + } + if caURL.Scheme == "" { + caURL.Scheme = "https" + } + + identity, err := LoadDefaultIdentity() + if err != nil { + return nil, err + } + if err := identity.Validate(); err != nil { + return nil, errors.Wrapf(err, "error validating %s", IdentityFile) + } + if kind := identity.Kind(); kind != MutualTLS { + return nil, errors.Errorf("unsupported identity %s: only mTLS is currently supported", kind) + } + + // Prepare transport with information in defaults.json and identity.json + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{} + + // RootCAs + b, err = ioutil.ReadFile(defaults.Root) + if err != nil { + return nil, errors.Wrapf(err, "error loading %s", defaults.Root) + } + pool := x509.NewCertPool() + if pool.AppendCertsFromPEM(b) { + tr.TLSClientConfig.RootCAs = pool + } + + // Certificate + crt, err := tls.LoadX509KeyPair(identity.Certificate, identity.Key) + if err != nil { + return nil, fmt.Errorf("error loading certificate: %v", err) + } + tr.TLSClientConfig.Certificates = []tls.Certificate{crt} + + return &Client{ + CaURL: caURL, + Client: &http.Client{ + Transport: tr, + }, + }, nil + +} + +type defaultsConfig struct { + CaURL string `json:"ca-url"` + Root string `json:"root"` +} + +func (c *defaultsConfig) Validate() error { + switch { + case c.CaURL == "": + return fmt.Errorf("missing or invalid `ca-url` property") + case c.Root == "": + return fmt.Errorf("missing or invalid `root` property") + default: + return nil + } +} diff --git a/ca/identity/client_test.go b/ca/identity/client_test.go new file mode 100644 index 00000000..4cbcc3a2 --- /dev/null +++ b/ca/identity/client_test.go @@ -0,0 +1,133 @@ +package identity + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "testing" +) + +func TestClient_ResolveReference(t *testing.T) { + type fields struct { + CaURL *url.URL + } + type args struct { + ref *url.URL + } + tests := []struct { + name string + fields fields + args args + want *url.URL + }{ + {"ok", fields{&url.URL{Scheme: "https", Host: "localhost"}}, args{&url.URL{Path: "/foo"}}, &url.URL{Scheme: "https", Host: "localhost", Path: "/foo"}}, + {"ok", fields{&url.URL{Scheme: "https", Host: "localhost", Path: "/bar"}}, args{&url.URL{Path: "/foo"}}, &url.URL{Scheme: "https", Host: "localhost", Path: "/foo"}}, + {"ok", fields{&url.URL{Scheme: "https", Host: "localhost"}}, args{&url.URL{Path: "/foo", RawQuery: "foo=bar"}}, &url.URL{Scheme: "https", Host: "localhost", Path: "/foo", RawQuery: "foo=bar"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + CaURL: tt.fields.CaURL, + } + if got := c.ResolveReference(tt.args.ref); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.ResolveReference() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLoadClient(t *testing.T) { + oldIdentityFile := IdentityFile + oldDefaultsFile := DefaultsFile + defer func() { + IdentityFile = oldIdentityFile + DefaultsFile = oldDefaultsFile + }() + + crt, err := tls.LoadX509KeyPair("testdata/identity/identity.crt", "testdata/identity/identity_key") + if err != nil { + t.Fatal(err) + } + b, err := ioutil.ReadFile("testdata/certs/root_ca.crt") + if err != nil { + t.Fatal(err) + } + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(b) + + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{ + Certificates: []tls.Certificate{crt}, + RootCAs: pool, + } + expected := &Client{ + CaURL: &url.URL{Scheme: "https", Host: "127.0.0.1"}, + Client: &http.Client{ + Transport: tr, + }, + } + + tests := []struct { + name string + prepare func() + want *Client + wantErr bool + }{ + {"ok", func() { IdentityFile = "testdata/config/identity.json"; DefaultsFile = "testdata/config/defaults.json" }, expected, false}, + {"fail identity", func() { IdentityFile = "testdata/config/missing.json"; DefaultsFile = "testdata/config/defaults.json" }, nil, true}, + {"fail identity", func() { IdentityFile = "testdata/config/fail.json"; DefaultsFile = "testdata/config/defaults.json" }, nil, true}, + {"fail defaults", func() { IdentityFile = "testdata/config/identity.json"; DefaultsFile = "testdata/config/missing.json" }, nil, true}, + {"fail defaults", func() { IdentityFile = "testdata/config/identity.json"; DefaultsFile = "testdata/config/fail.json" }, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.prepare() + got, err := LoadClient() + if (err != nil) != tt.wantErr { + t.Errorf("LoadClient() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.want == nil { + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("LoadClient() = %#v, want %#v", got, tt.want) + } + } else { + if !reflect.DeepEqual(got.CaURL, tt.want.CaURL) || + !reflect.DeepEqual(got.Client.Transport.(*http.Transport).TLSClientConfig.RootCAs, tt.want.Client.Transport.(*http.Transport).TLSClientConfig.RootCAs) || + !reflect.DeepEqual(got.Client.Transport.(*http.Transport).TLSClientConfig.Certificates, tt.want.Client.Transport.(*http.Transport).TLSClientConfig.Certificates) { + t.Errorf("LoadClient() = %#v, want %#v", got, tt.want) + } + } + }) + } +} + +func Test_defaultsConfig_Validate(t *testing.T) { + type fields struct { + CaURL string + Root string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + {"ok", fields{"https://127.0.0.1", "root_ca.crt"}, false}, + {"fail ca-url", fields{"", "root_ca.crt"}, true}, + {"fail root", fields{"https://127.0.0.1", ""}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &defaultsConfig{ + CaURL: tt.fields.CaURL, + Root: tt.fields.Root, + } + if err := c.Validate(); (err != nil) != tt.wantErr { + t.Errorf("defaultsConfig.Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/ca/identity.go b/ca/identity/identity.go similarity index 60% rename from ca/identity.go rename to ca/identity/identity.go index d7ad7042..35736236 100644 --- a/ca/identity.go +++ b/ca/identity/identity.go @@ -1,4 +1,4 @@ -package ca +package identity import ( "bytes" @@ -8,7 +8,6 @@ import ( "encoding/json" "encoding/pem" "io/ioutil" - "net/http" "os" "path/filepath" "strings" @@ -20,17 +19,14 @@ import ( "github.com/smallstep/cli/crypto/pemutil" ) -// IdentityType represents the different types of identity files. -type IdentityType string - -// DisableIdentity is a global variable to disable the identity. -var DisableIdentity = false +// Type represents the different types of identity files. +type Type string // Disabled represents a disabled identity type -const Disabled IdentityType = "" +const Disabled Type = "" // MutualTLS represents the identity using mTLS -const MutualTLS IdentityType = "mTLS" +const MutualTLS Type = "mTLS" // DefaultLeeway is the duration for matching not before claims. const DefaultLeeway = 1 * time.Minute @@ -38,6 +34,9 @@ const DefaultLeeway = 1 * time.Minute // IdentityFile contains the location of the identity file. var IdentityFile = filepath.Join(config.StepPath(), "config", "identity.json") +// DefaultsFile contains the location of the defaults file. +var DefaultsFile = filepath.Join(config.StepPath(), "config", "defaults.json") + // Identity represents the identity file that can be used to authenticate with // the CA. type Identity struct { @@ -46,26 +45,11 @@ type Identity struct { Key string `json:"key"` } -// NewIdentityRequest returns a new CSR to create the identity. If an identity -// was already present it reuses the private key. -func NewIdentityRequest(commonName string, sans ...string) (*api.CertificateRequest, crypto.PrivateKey, error) { - var identityKey crypto.PrivateKey - if i, err := LoadDefaultIdentity(); err == nil && i.Key != "" { - if k, err := pemutil.Read(i.Key); err == nil { - identityKey = k - } - } - if identityKey == nil { - return CreateCertificateRequest(commonName, sans...) - } - return createCertificateRequest(commonName, sans, identityKey) -} - // LoadDefaultIdentity loads the default identity. func LoadDefaultIdentity() (*Identity, error) { b, err := ioutil.ReadFile(IdentityFile) if err != nil { - return nil, errors.Wrap(err, "error reading identity json") + return nil, errors.Wrapf(err, "error reading %s", IdentityFile) } identity := new(Identity) if err := json.Unmarshal(b, &identity); err != nil { @@ -137,14 +121,14 @@ func WriteDefaultIdentity(certChain []api.Certificate, key crypto.PrivateKey) er } // Kind returns the type for the given identity. -func (i *Identity) Kind() IdentityType { +func (i *Identity) Kind() Type { switch strings.ToLower(i.Type) { case "": return Disabled case "mtls": return MutualTLS default: - return IdentityType(i.Type) + return Type(i.Type) } } @@ -160,74 +144,55 @@ func (i *Identity) Validate() error { if i.Key == "" { return errors.New("identity.key cannot be empty") } + if err := fileExists(i.Certificate); err != nil { + return err + } + if err := fileExists(i.Key); err != nil { + return err + } return nil default: return errors.Errorf("unsupported identity type %s", i.Type) } } -// Options returns the ClientOptions used for the given identity. -func (i *Identity) Options() ([]ClientOption, error) { +// TLSCertificate returns a tls.Certificate for the identity. +func (i *Identity) TLSCertificate() (tls.Certificate, error) { + fail := func(err error) (tls.Certificate, error) { return tls.Certificate{}, err } switch i.Kind() { case Disabled: - return nil, nil + return tls.Certificate{}, nil case MutualTLS: crt, err := tls.LoadX509KeyPair(i.Certificate, i.Key) if err != nil { - return nil, errors.Wrap(err, "error creating identity certificate") + return fail(errors.Wrap(err, "error creating identity certificate")) } + // Check if certificate is expired. - // Do not return any options if expired. x509Cert, err := x509.ParseCertificate(crt.Certificate[0]) if err != nil { - return nil, errors.Wrap(err, "error creating identity certificate") + return fail(errors.Wrap(err, "error creating identity certificate")) } now := time.Now().Truncate(time.Second) - if now.Add(DefaultLeeway).Before(x509Cert.NotBefore) || now.After(x509Cert.NotAfter) { - return nil, nil + if now.Add(DefaultLeeway).Before(x509Cert.NotBefore) { + return fail(errors.New("certificate is not yet valid")) } - return []ClientOption{WithCertificate(crt)}, nil + if now.After(x509Cert.NotAfter) { + return fail(errors.New("certificate is already expired")) + } + return crt, nil default: - return nil, errors.Errorf("unsupported identity type %s", i.Type) + return fail(errors.Errorf("unsupported identity type %s", i.Type)) } } -// Renew renews the identity certificate using the given client. -func (i *Identity) Renew(client *Client) error { - switch i.Kind() { - case Disabled: - return nil - case MutualTLS: - cert, err := tls.LoadX509KeyPair(i.Certificate, i.Key) - if err != nil { - return errors.Wrap(err, "error creating identity certificate") - } - tr := &http.Transport{ - TLSClientConfig: &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: client.GetRootCAs(), - PreferServerCipherSuites: true, - }, - } - resp, err := client.Renew(tr) - if err != nil { - return err - } - buf := new(bytes.Buffer) - for _, crt := range resp.CertChainPEM { - block := &pem.Block{ - Type: "CERTIFICATE", - Bytes: crt.Raw, - } - if err := pem.Encode(buf, block); err != nil { - return errors.Wrap(err, "error encoding identity certificate") - } - } - if err := ioutil.WriteFile(i.Certificate, buf.Bytes(), 0600); err != nil { - return errors.Wrap(err, "error writing identity certificate") - } - return nil - default: - return errors.Errorf("unsupported identity type %s", i.Type) +func fileExists(filename string) error { + info, err := os.Stat(filename) + if err != nil { + return errors.Wrapf(err, "error reading %s", filename) } + if info.IsDir() { + return errors.Errorf("error reading %s: file is a directory", filename) + } + return nil } diff --git a/ca/identity/identity_test.go b/ca/identity/identity_test.go new file mode 100644 index 00000000..58f5db71 --- /dev/null +++ b/ca/identity/identity_test.go @@ -0,0 +1,166 @@ +package identity + +import ( + "crypto/tls" + "reflect" + "testing" +) + +func TestLoadDefaultIdentity(t *testing.T) { + oldFile := IdentityFile + defer func() { + IdentityFile = oldFile + }() + + expected := &Identity{ + Type: "mTLS", + Certificate: "testdata/identity/identity.crt", + Key: "testdata/identity/identity_key", + } + tests := []struct { + name string + prepare func() + want *Identity + wantErr bool + }{ + {"ok", func() { IdentityFile = "testdata/config/identity.json" }, expected, false}, + {"fail read", func() { IdentityFile = "testdata/config/missing.json" }, nil, true}, + {"fail unmarshal", func() { IdentityFile = "testdata/config/fail.json" }, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.prepare() + got, err := LoadDefaultIdentity() + if (err != nil) != tt.wantErr { + t.Errorf("LoadDefaultIdentity() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("LoadDefaultIdentity() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIdentity_Kind(t *testing.T) { + type fields struct { + Type string + } + tests := []struct { + name string + fields fields + want Type + }{ + {"disabled", fields{""}, Disabled}, + {"mutualTLS", fields{"mTLS"}, MutualTLS}, + {"unknown", fields{"unknown"}, Type("unknown")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &Identity{ + Type: tt.fields.Type, + } + if got := i.Kind(); got != tt.want { + t.Errorf("Identity.Kind() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIdentity_Validate(t *testing.T) { + type fields struct { + Type string + Certificate string + Key string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + {"ok", fields{"mTLS", "testdata/identity/identity.crt", "testdata/identity/identity_key"}, false}, + {"ok disabled", fields{}, false}, + {"fail type", fields{"foo", "testdata/identity/identity.crt", "testdata/identity/identity_key"}, true}, + {"fail certificate", fields{"mTLS", "", "testdata/identity/identity_key"}, true}, + {"fail key", fields{"mTLS", "testdata/identity/identity.crt", ""}, true}, + {"fail missing certificate", fields{"mTLS", "testdata/identity/missing.crt", "testdata/identity/identity_key"}, true}, + {"fail missing key", fields{"mTLS", "testdata/identity/identity.crt", "testdata/identity/missing_key"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &Identity{ + Type: tt.fields.Type, + Certificate: tt.fields.Certificate, + Key: tt.fields.Key, + } + if err := i.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Identity.Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestIdentity_TLSCertificate(t *testing.T) { + expected, err := tls.LoadX509KeyPair("testdata/identity/identity.crt", "testdata/identity/identity_key") + if err != nil { + t.Fatal(err) + } + + type fields struct { + Type string + Certificate string + Key string + } + tests := []struct { + name string + fields fields + want tls.Certificate + wantErr bool + }{ + {"ok", fields{"mTLS", "testdata/identity/identity.crt", "testdata/identity/identity_key"}, expected, false}, + {"ok disabled", fields{}, tls.Certificate{}, false}, + {"fail type", fields{"foo", "testdata/identity/identity.crt", "testdata/identity/identity_key"}, tls.Certificate{}, true}, + {"fail certificate", fields{"mTLS", "testdata/certs/server.crt", "testdata/identity/identity_key"}, tls.Certificate{}, true}, + {"fail not after", fields{"mTLS", "testdata/identity/expired.crt", "testdata/identity/identity_key"}, tls.Certificate{}, true}, + {"fail not before", fields{"mTLS", "testdata/identity/not_before.crt", "testdata/identity/identity_key"}, tls.Certificate{}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &Identity{ + Type: tt.fields.Type, + Certificate: tt.fields.Certificate, + Key: tt.fields.Key, + } + got, err := i.TLSCertificate() + if (err != nil) != tt.wantErr { + t.Errorf("Identity.TLSCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Identity.TLSCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_fileExists(t *testing.T) { + type args struct { + filename string + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"ok", args{"testdata/identity/identity.crt"}, false}, + {"missing", args{"testdata/identity/missing.crt"}, true}, + {"directory", args{"testdata/identity"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := fileExists(tt.args.filename); (err != nil) != tt.wantErr { + t.Errorf("fileExists() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/ca/identity/testdata/certs/intermediate_ca.crt b/ca/identity/testdata/certs/intermediate_ca.crt new file mode 100644 index 00000000..8d7a1b87 --- /dev/null +++ b/ca/identity/testdata/certs/intermediate_ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUqgAwIBAgIQF4UYp5uEiuq/BO0cOWTq9DAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xOTEyMTIwMjQ1MThaFw0yOTEy +MDkwMjQ1MThaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGECLvDj+ZSqW78DRmUaugh0EU4NQ5 +PoZxsLpB0gUsvNDGE0V5/2Q85GmsYzlBjBuoM+RfvF2fSP+dDTs3Hwjgo2YwZDAO +BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU95Au +B82vrt2UJyDTNBQH3B8sePUwHwYDVR0jBBgwFoAUgwZucvb+H/1chTPLQ1GYTJwK +CXQwCgYIKoZIzj0EAwIDRwAwRAIgSaHuI61rNsFf1ke5WSUyuqy51DIE/ONCSWKT +VQgTVJMCIAMsE+Eibk43hL4qQi5vBJiFLfGQDDN/9HUi6w4w5EZ7 +-----END CERTIFICATE----- diff --git a/ca/identity/testdata/certs/root_ca.crt b/ca/identity/testdata/certs/root_ca.crt new file mode 100644 index 00000000..3488f0bc --- /dev/null +++ b/ca/identity/testdata/certs/root_ca.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBfDCCASGgAwIBAgIQE8W0gyMruWxRDfegdPHrdDAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xOTEyMTIwMjQ1MThaFw0yOTEy +MDkwMjQ1MThaMBwxGjAYBgNVBAMTEVNtYWxsc3RlcCBSb290IENBMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEgd74QbUDcEj3aV5Oxv5eAMzwnejj7S/iDFAp89t9 +kEb+Ux4NZC3Pay+92yRL//dBUI5WOopLXBniYomH4SFJg6NFMEMwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFIMGbnL2/h/9XIUz +y0NRmEycCgl0MAoGCCqGSM49BAMCA0kAMEYCIQD3/IUBL5/9Hpdp2+t4XnA42cwQ +j5WkGY5hJIhdQ5P8qgIhAMf19nAIUlSbXKPf21Gv6eYEoNuuLfpcqnfBt5NJX64M +-----END CERTIFICATE----- diff --git a/ca/identity/testdata/certs/server.crt b/ca/identity/testdata/certs/server.crt new file mode 100644 index 00000000..05f02310 --- /dev/null +++ b/ca/identity/testdata/certs/server.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIICHDCCAcKgAwIBAgIQQ4n25nGGKm6uGyVQ4cDNCTAKBggqhkjOPQQDAjAkMSIw +IAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENBMB4XDTE5MTIxMjAyNTAz +OVoXDTI5MTIwOTAyNTAzOVowFjEUMBIGA1UEAxMLdGVzdCBzZXJ2ZXIwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAATmQRMCzRP1hBcYhAXlbiyR9QtsQosQfCZTS+en +g6TtL9VjWsQXqd1SSStfi0grPyiTQLIPhPbSho/VJzSpf59Do4HjMIHgMA4GA1Ud +DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0O +BBYEFBvz34jDFrb3G4qiGkZZj99BnabAMB8GA1UdIwQYMBaAFPeQLgfNr67dlCcg +0zQUB9wfLHj1MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATBTBgwrBgEEAYKk +ZMYoQAEEQzBBAgEBBA9qb2VAZXhhbXBsZS5jb20EKzJ3U05fQ21leFhXaWdfRG5w +VlpzWUZkTUgxU3RjODZCSUJ6TjBydDVpcEUwCgYIKoZIzj0EAwIDSAAwRQIhAOt6 +/x9LWQyBtx3RcyyALF2//OCfGjAx0zLGmUsXIHGIAiAZGVwTxbhxiYU95AXncS3F +3tXNaaIJyyO7atiVPhCR1A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBozCCAUqgAwIBAgIQF4UYp5uEiuq/BO0cOWTq9DAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xOTEyMTIwMjQ1MThaFw0yOTEy +MDkwMjQ1MThaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGECLvDj+ZSqW78DRmUaugh0EU4NQ5 +PoZxsLpB0gUsvNDGE0V5/2Q85GmsYzlBjBuoM+RfvF2fSP+dDTs3Hwjgo2YwZDAO +BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU95Au +B82vrt2UJyDTNBQH3B8sePUwHwYDVR0jBBgwFoAUgwZucvb+H/1chTPLQ1GYTJwK +CXQwCgYIKoZIzj0EAwIDRwAwRAIgSaHuI61rNsFf1ke5WSUyuqy51DIE/ONCSWKT +VQgTVJMCIAMsE+Eibk43hL4qQi5vBJiFLfGQDDN/9HUi6w4w5EZ7 +-----END CERTIFICATE----- diff --git a/ca/identity/testdata/config/ca.json b/ca/identity/testdata/config/ca.json new file mode 100644 index 00000000..4201b3e1 --- /dev/null +++ b/ca/identity/testdata/config/ca.json @@ -0,0 +1,41 @@ +{ + "root": "testdata/certs/root_ca.crt", + "federatedRoots": [], + "crt": "testdata/certs/intermediate_ca.crt", + "key": "testdata/secrets/intermediate_ca_key", + "address": ":443", + "dnsNames": [ + "127.0.0.1", + "localhost" + ], + "logger": { + "format": "text" + }, + "authority": { + "provisioners": [ + { + "type": "jwk", + "name": "joe@example.com", + "key": { + "use": "sig", + "kty": "EC", + "kid": "2wSN_CmexXWig_DnpVZsYFdMH1Stc86BIBzN0rt5ipE", + "crv": "P-256", + "alg": "ES256", + "x": "QqYaIULUQqP0EOmogorCcQIxEtI7-zCRcUVFxyNwq4Q", + "y": "YeIMipM7uMHjlxpFIUbfCBC1xEXczXNYRzJCMyrGcH0" + }, + "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiSVQ3MVNUMTNNMTd1S3Y4VHRDczYyUSJ9.TXShNLPcITS0bFvQeMjjCDhQLICQs1ShECkgUkUsAm9ZWpSq6Yu03w.SWxtxscivS3L5Yo5.O-XY9YKK8wEJgVs7X1-FxiM_6w4s7iJQNXRD2JrZRsXtDqUz7diPfXuBOFPUFsNzykvob1qCsU4B23Ek2nbaS2HqPrIOGbOvOsR8Pt6kNoraH1QDp3Hyzkv0S-VGM0MCGYDDmmH33PZmsdS36Aw8v9xBnDHlwlMg4NjTskxpqggfQl01433B0lCJqJdrmeBeGL1ZCKixvc-wAQxU8GH5iiD925ViLY7RlVo-tmIBXpxRgheLgKiuMxmgPvf15qCdgU5TRqeuJbYJLzvPpoai0W4WHjpM1zLjjmp5OYRFW4m4ZRZf5g1Cm4lstFPUlTn85fkMZFdBh4_bFbjAv7k.epXp8DZKHj_dxP9EohwDIg" + } + ] + }, + "tls": { + "cipherSuites": [ + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + ], + "minVersion": 1.2, + "maxVersion": 1.2, + "renegotiation": false + } +} \ No newline at end of file diff --git a/ca/identity/testdata/config/defaults.json b/ca/identity/testdata/config/defaults.json new file mode 100644 index 00000000..7d26ed19 --- /dev/null +++ b/ca/identity/testdata/config/defaults.json @@ -0,0 +1,6 @@ +{ + "ca-url": "https://127.0.0.1", + "ca-config": "testdata/config/ca.json", + "fingerprint": "9dc35eef23a234b2520516a3169090d7ec2fc61323bdd6e4fde08bcfec5d0931", + "root": "testdata/certs/root_ca.crt" +} \ No newline at end of file diff --git a/ca/identity/testdata/config/fail.json b/ca/identity/testdata/config/fail.json new file mode 100644 index 00000000..5ce77559 --- /dev/null +++ b/ca/identity/testdata/config/fail.json @@ -0,0 +1 @@ +This is not a json file \ No newline at end of file diff --git a/ca/identity/testdata/config/identity.json b/ca/identity/testdata/config/identity.json new file mode 100644 index 00000000..f3d5e8f1 --- /dev/null +++ b/ca/identity/testdata/config/identity.json @@ -0,0 +1,5 @@ +{ + "type": "mTLS", + "crt": "testdata/identity/identity.crt", + "key": "testdata/identity/identity_key" +} \ No newline at end of file diff --git a/ca/identity/testdata/identity/expired.crt b/ca/identity/testdata/identity/expired.crt new file mode 100644 index 00000000..5d723ddd --- /dev/null +++ b/ca/identity/testdata/identity/expired.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIICIDCCAcegAwIBAgIRAM1GK1TLmvWLVOjP0dqVCiEwCgYIKoZIzj0EAwIwJDEi +MCAGA1UEAxMZU21hbGxzdGVwIEludGVybWVkaWF0ZSBDQTAeFw0xODEyMTIwMzI2 +MzZaFw0xODEyMTMwMzI2MzZaMBoxGDAWBgNVBAMMD2pvZUBleGFtcGxlLmNvbTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABI0+NSjg3+vGhAeZGrxPksrXFqq0AIUB +D3nQPmGPuUWIEmbt6qp3EVF/o+KwzWgDv5fzBmDlBkdBRz9xc3XIcQ2jgeMwgeAw +HwYDVR0jBBgwFoAU95AuB82vrt2UJyDTNBQH3B8sePUwDgYDVR0PAQH/BAQDAgWg +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU1Ht6zX2M +eVXcnxhM4hxU0RCblNowGgYDVR0RBBMwEYEPam9lQGV4YW1wbGUuY29tMFMGDCsG +AQQBgqRkxihAAQRDMEECAQEED2pvZUBleGFtcGxlLmNvbQQrMndTTl9DbWV4WFdp +Z19EbnBWWnNZRmRNSDFTdGM4NkJJQnpOMHJ0NWlwRTAKBggqhkjOPQQDAgNHADBE +AiBgoPACCRJ6s+C5Yz3BWeyM6VnWewctnaMsVJKyPdb98AIgV/7HRZsc5Xgi8iVt +D4XxVOZDu/y1V4VIH5W4INfg6JA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBozCCAUqgAwIBAgIQF4UYp5uEiuq/BO0cOWTq9DAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xOTEyMTIwMjQ1MThaFw0yOTEy +MDkwMjQ1MThaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGECLvDj+ZSqW78DRmUaugh0EU4NQ5 +PoZxsLpB0gUsvNDGE0V5/2Q85GmsYzlBjBuoM+RfvF2fSP+dDTs3Hwjgo2YwZDAO +BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU95Au +B82vrt2UJyDTNBQH3B8sePUwHwYDVR0jBBgwFoAUgwZucvb+H/1chTPLQ1GYTJwK +CXQwCgYIKoZIzj0EAwIDRwAwRAIgSaHuI61rNsFf1ke5WSUyuqy51DIE/ONCSWKT +VQgTVJMCIAMsE+Eibk43hL4qQi5vBJiFLfGQDDN/9HUi6w4w5EZ7 +-----END CERTIFICATE----- diff --git a/ca/identity/testdata/identity/identity.crt b/ca/identity/testdata/identity/identity.crt new file mode 100644 index 00000000..b8824136 --- /dev/null +++ b/ca/identity/testdata/identity/identity.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIICHzCCAcagAwIBAgIQfVgJ4dZ2AhS88uthvlIzyjAKBggqhkjOPQQDAjAkMSIw +IAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENBMB4XDTE5MTIxMjAyNDgy +MVoXDTI5MTIwOTAyNDgyMVowGjEYMBYGA1UEAwwPam9lQGV4YW1wbGUuY29tMFkw +EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjT41KODf68aEB5kavE+SytcWqrQAhQEP +edA+YY+5RYgSZu3qqncRUX+j4rDNaAO/l/MGYOUGR0FHP3FzdchxDaOB4zCB4DAO +BgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0G +A1UdDgQWBBTUe3rNfYx5VdyfGEziHFTREJuU2jAfBgNVHSMEGDAWgBT3kC4Hza+u +3ZQnINM0FAfcHyx49TAaBgNVHREEEzARgQ9qb2VAZXhhbXBsZS5jb20wUwYMKwYB +BAGCpGTGKEABBEMwQQIBAQQPam9lQGV4YW1wbGUuY29tBCsyd1NOX0NtZXhYV2ln +X0RucFZac1lGZE1IMVN0Yzg2QklCek4wcnQ1aXBFMAoGCCqGSM49BAMCA0cAMEQC +IHkYnKUBrXc/GIosKgnhHqVeRMi2O1JhnZdTE1uoy2C0AiA9ZrmGqPvpQ86f5yq5 +llsieqBTzIum6A45q0/4XeN3QA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBozCCAUqgAwIBAgIQF4UYp5uEiuq/BO0cOWTq9DAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xOTEyMTIwMjQ1MThaFw0yOTEy +MDkwMjQ1MThaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGECLvDj+ZSqW78DRmUaugh0EU4NQ5 +PoZxsLpB0gUsvNDGE0V5/2Q85GmsYzlBjBuoM+RfvF2fSP+dDTs3Hwjgo2YwZDAO +BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU95Au +B82vrt2UJyDTNBQH3B8sePUwHwYDVR0jBBgwFoAUgwZucvb+H/1chTPLQ1GYTJwK +CXQwCgYIKoZIzj0EAwIDRwAwRAIgSaHuI61rNsFf1ke5WSUyuqy51DIE/ONCSWKT +VQgTVJMCIAMsE+Eibk43hL4qQi5vBJiFLfGQDDN/9HUi6w4w5EZ7 +-----END CERTIFICATE----- diff --git a/ca/identity/testdata/identity/identity_key b/ca/identity/testdata/identity/identity_key new file mode 100644 index 00000000..332f0ebb --- /dev/null +++ b/ca/identity/testdata/identity/identity_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJ4A5QcJioS5I89uT/hkuWPy/nlW5qy8vM8Tm2sgUCDyoAoGCCqGSM49 +AwEHoUQDQgAEjT41KODf68aEB5kavE+SytcWqrQAhQEPedA+YY+5RYgSZu3qqncR +UX+j4rDNaAO/l/MGYOUGR0FHP3FzdchxDQ== +-----END EC PRIVATE KEY----- diff --git a/ca/identity/testdata/identity/not_before.crt b/ca/identity/testdata/identity/not_before.crt new file mode 100644 index 00000000..71cbc8d8 --- /dev/null +++ b/ca/identity/testdata/identity/not_before.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIICIDCCAcagAwIBAgIQHRUI8eJv55I9/5IHi1mpmjAKBggqhkjOPQQDAjAkMSIw +IAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENBMB4XDTI5MTIwOTAzMzAx +NFoXDTI5MTIxMDAzMzAxNFowGjEYMBYGA1UEAwwPam9lQGV4YW1wbGUuY29tMFkw +EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjT41KODf68aEB5kavE+SytcWqrQAhQEP +edA+YY+5RYgSZu3qqncRUX+j4rDNaAO/l/MGYOUGR0FHP3FzdchxDaOB4zCB4DAf +BgNVHSMEGDAWgBT3kC4Hza+u3ZQnINM0FAfcHyx49TAOBgNVHQ8BAf8EBAMCBaAw +HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTUe3rNfYx5 +VdyfGEziHFTREJuU2jAaBgNVHREEEzARgQ9qb2VAZXhhbXBsZS5jb20wUwYMKwYB +BAGCpGTGKEABBEMwQQIBAQQPam9lQGV4YW1wbGUuY29tBCsyd1NOX0NtZXhYV2ln +X0RucFZac1lGZE1IMVN0Yzg2QklCek4wcnQ1aXBFMAoGCCqGSM49BAMCA0gAMEUC +IQDJVzxQ0lY9+haZLs5qxhbaWoTmXwCbYdkwhThDfM/izwIgRZCmshc1flfimIPO +eblT85Gk16ND/diV6pmtUaMT73I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBozCCAUqgAwIBAgIQF4UYp5uEiuq/BO0cOWTq9DAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xOTEyMTIwMjQ1MThaFw0yOTEy +MDkwMjQ1MThaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGECLvDj+ZSqW78DRmUaugh0EU4NQ5 +PoZxsLpB0gUsvNDGE0V5/2Q85GmsYzlBjBuoM+RfvF2fSP+dDTs3Hwjgo2YwZDAO +BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU95Au +B82vrt2UJyDTNBQH3B8sePUwHwYDVR0jBBgwFoAUgwZucvb+H/1chTPLQ1GYTJwK +CXQwCgYIKoZIzj0EAwIDRwAwRAIgSaHuI61rNsFf1ke5WSUyuqy51DIE/ONCSWKT +VQgTVJMCIAMsE+Eibk43hL4qQi5vBJiFLfGQDDN/9HUi6w4w5EZ7 +-----END CERTIFICATE----- diff --git a/ca/identity/testdata/secrets/intermediate_ca_key b/ca/identity/testdata/secrets/intermediate_ca_key new file mode 100644 index 00000000..107a6c9d --- /dev/null +++ b/ca/identity/testdata/secrets/intermediate_ca_key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,37e3019a1aa420225bbd4f342a3ce330 + +3SNIIXzE11cGKTPnErv8S1HIrd2lbQo+lsMT9GrU33GAi/MTvp0hx0txy7E3CsrU +DbuPXs3zLCjgoNLOeyAWLqGjPLRt4YNnZGVDi3F/dFUAWxgXH8gZQ2d9ZqAXwxdd +bhT4ZcRFgFzCPlHExtxBrJe+Tmeuq1HqD+8gpOSYbt0= +-----END EC PRIVATE KEY----- diff --git a/ca/identity/testdata/secrets/root_ca_key b/ca/identity/testdata/secrets/root_ca_key new file mode 100644 index 00000000..c11f2909 --- /dev/null +++ b/ca/identity/testdata/secrets/root_ca_key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,48fc92ab6885b2377d8bbac5b035bde2 + +BE07EXlLmJbAfjt2c9GwQoTT07DzjLWgiGWqxMKC0bOLQdmHe2pFudeQldDhTOme +xnr9rRj9h+GRWV+sIzp+ilGd4/F6lfzWMl44GA5y7uBNWKhnI1uB9m9oo69hBNRg +dQuDmAx5EWXvg7Mgg1MQZIPY8539RXWJdAs+uRSI12g= +-----END EC PRIVATE KEY----- diff --git a/ca/identity/testdata/secrets/server_key b/ca/identity/testdata/secrets/server_key new file mode 100644 index 00000000..4ae87816 --- /dev/null +++ b/ca/identity/testdata/secrets/server_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIGgfuMfx7h1VaCYzzEPZhrbTLsAr6dtyuQ2RLl6jKqBoAoGCCqGSM49 +AwEHoUQDQgAE5kETAs0T9YQXGIQF5W4skfULbEKLEHwmU0vnp4Ok7S/VY1rEF6nd +UkkrX4tIKz8ok0CyD4T20oaP1Sc0qX+fQw== +-----END EC PRIVATE KEY-----