Add identity client and move identity to a new package.

This commit is contained in:
Mariano Cano 2019-12-11 20:23:44 -08:00
parent 89b216c21e
commit 0d9a9e083e
19 changed files with 667 additions and 86 deletions

View file

@ -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}

105
ca/identity/client.go Normal file
View file

@ -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
}
}

133
ca/identity/client_test.go Normal file
View file

@ -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)
}
})
}
}

View file

@ -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
}

View file

@ -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)
}
})
}
}

View file

@ -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-----

10
ca/identity/testdata/certs/root_ca.crt vendored Normal file
View file

@ -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-----

25
ca/identity/testdata/certs/server.crt vendored Normal file
View file

@ -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-----

41
ca/identity/testdata/config/ca.json vendored Normal file
View file

@ -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
}
}

View file

@ -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"
}

1
ca/identity/testdata/config/fail.json vendored Normal file
View file

@ -0,0 +1 @@
This is not a json file

View file

@ -0,0 +1,5 @@
{
"type": "mTLS",
"crt": "testdata/identity/identity.crt",
"key": "testdata/identity/identity_key"
}

View file

@ -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-----

View file

@ -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-----

View file

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJ4A5QcJioS5I89uT/hkuWPy/nlW5qy8vM8Tm2sgUCDyoAoGCCqGSM49
AwEHoUQDQgAEjT41KODf68aEB5kavE+SytcWqrQAhQEPedA+YY+5RYgSZu3qqncR
UX+j4rDNaAO/l/MGYOUGR0FHP3FzdchxDQ==
-----END EC PRIVATE KEY-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIGgfuMfx7h1VaCYzzEPZhrbTLsAr6dtyuQ2RLl6jKqBoAoGCCqGSM49
AwEHoUQDQgAE5kETAs0T9YQXGIQF5W4skfULbEKLEHwmU0vnp4Ok7S/VY1rEF6nd
UkkrX4tIKz8ok0CyD4T20oaP1Sc0qX+fQw==
-----END EC PRIVATE KEY-----