From 5df169425070a91bc89cd1ce57be25db6ab37bbb Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 11 Aug 2022 14:47:11 -0700 Subject: [PATCH] Add endpoint id for the RA certificate In a linked RA mode, send an endpoint id to group the server certificates. --- authority/linkedca.go | 10 +++++--- authority/provisioner/provisioner.go | 9 ++++--- authority/tls.go | 9 ++++--- cas/apiv1/requests.go | 13 +++++----- cas/stepcas/issuer.go | 19 +++++++++++--- cas/stepcas/issuer_test.go | 37 ++++++++++++++++++++++++++++ cas/stepcas/jwk_issuer_test.go | 3 +++ cas/stepcas/stepcas.go | 16 ++++++------ cas/stepcas/stepcas_test.go | 8 ++++++ cas/stepcas/x5c_issuer_test.go | 3 +++ go.mod | 20 +++++++-------- go.sum | 8 +++--- 12 files changed, 113 insertions(+), 42 deletions(-) diff --git a/authority/linkedca.go b/authority/linkedca.go index 0380cbaf..01a3630d 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -273,11 +273,13 @@ func (c *linkedCaClient) GetCertificateData(serial string) (*db.CertificateData, func (c *linkedCaClient) StoreCertificateChain(p provisioner.Interface, fullchain ...*x509.Certificate) error { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() + raProvisioner, endpointID := createRegistrationAuthorityProvisioner(p) _, err := c.client.PostCertificate(ctx, &linkedca.CertificateRequest{ PemCertificate: serializeCertificateChain(fullchain[0]), PemCertificateChain: serializeCertificateChain(fullchain[1:]...), Provisioner: createProvisionerIdentity(p), - RaProvisioner: createRegistrationAuthorityProvisioner(p), + RaProvisioner: raProvisioner, + EndpointId: endpointID, }) return errors.Wrap(err, "error posting certificate") } @@ -397,7 +399,7 @@ type raProvisioner interface { RAInfo() *provisioner.RAInfo } -func createRegistrationAuthorityProvisioner(p provisioner.Interface) *linkedca.RegistrationAuthorityProvisioner { +func createRegistrationAuthorityProvisioner(p provisioner.Interface) (*linkedca.RegistrationAuthorityProvisioner, string) { if rap, ok := p.(raProvisioner); ok { info := rap.RAInfo() typ := linkedca.Provisioner_Type_value[strings.ToUpper(info.ProvisionerType)] @@ -408,9 +410,9 @@ func createRegistrationAuthorityProvisioner(p provisioner.Interface) *linkedca.R Type: linkedca.Provisioner_Type(typ), Name: info.ProvisionerName, }, - } + }, info.EndpointID } - return nil + return nil, "" } func serializeCertificate(crt *x509.Certificate) string { diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index bdfc7da7..29d44c1c 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -343,10 +343,11 @@ type Permissions struct { // RAInfo is the information about a provisioner present in RA tokens generated // by StepCAS. type RAInfo struct { - AuthorityID string `json:"authorityId"` - ProvisionerID string `json:"provisionerId"` - ProvisionerType string `json:"provisionerType"` - ProvisionerName string `json:"provisionerName"` + AuthorityID string `json:"authorityId,omitempty"` + EndpointID string `json:"endpointId,omitempty"` + ProvisionerID string `json:"provisionerId,omitempty"` + ProvisionerType string `json:"provisionerType,omitempty"` + ProvisionerName string `json:"provisionerName,omitempty"` } // raProvisioner wraps a provisioner with RA data. diff --git a/authority/tls.go b/authority/tls.go index 2238f849..25f14bf7 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -615,10 +615,11 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { certTpl.NotAfter = now.Add(24 * time.Hour) resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ - Template: certTpl, - CSR: cr, - Lifetime: 24 * time.Hour, - Backdate: 1 * time.Minute, + Template: certTpl, + CSR: cr, + Lifetime: 24 * time.Hour, + Backdate: 1 * time.Minute, + IsServerCert: true, }) if err != nil { return fatal(err) diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index 6124f3ba..49cb5cc3 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -52,12 +52,13 @@ const ( // CreateCertificateRequest is the request used to sign a new certificate. type CreateCertificateRequest struct { - Template *x509.Certificate - CSR *x509.CertificateRequest - Lifetime time.Duration - Backdate time.Duration - RequestID string - Provisioner *ProvisionerInfo + Template *x509.Certificate + CSR *x509.CertificateRequest + Lifetime time.Duration + Backdate time.Duration + RequestID string + Provisioner *ProvisionerInfo + IsServerCert bool } // ProvisionerInfo contains information of the provisioner used to authorize a diff --git a/cas/stepcas/issuer.go b/cas/stepcas/issuer.go index 57421ffe..07607caa 100644 --- a/cas/stepcas/issuer.go +++ b/cas/stepcas/issuer.go @@ -5,16 +5,29 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/pkg/errors" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/cas/apiv1" ) +// raAuthorityNS is a custom namespace used to generate endpoint ids based on +// the authority id. +var raAuthorityNS = uuid.MustParse("d6f14c1f-2f92-47bf-a04f-7b2c11382edd") + +// newServerEndpointID returns a uuid v5 using raAuthorityNS as the namespace. +// The return uuid will be used as the server endpoint id, it will be unique per +// authority. +func newServerEndpointID(data string) uuid.UUID { + return uuid.NewSHA1(raAuthorityNS, []byte(data)) +} + type raInfo struct { AuthorityID string `json:"authorityId,omitempty"` - ProvisionerID string `json:"provisionerId"` - ProvisionerType string `json:"provisionerType"` - ProvisionerName string `json:"provisionerName"` + EndpointID string `json:"endpointId,omitempty"` + ProvisionerID string `json:"provisionerId,omitempty"` + ProvisionerType string `json:"provisionerType,omitempty"` + ProvisionerName string `json:"provisionerName,omitempty"` } type stepIssuer interface { diff --git a/cas/stepcas/issuer_test.go b/cas/stepcas/issuer_test.go index 726dedbf..c968237a 100644 --- a/cas/stepcas/issuer_test.go +++ b/cas/stepcas/issuer_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/cas/apiv1" "go.step.sm/crypto/jose" @@ -35,6 +36,42 @@ func (s *mockErrSigner) Options() jose.SignerOptions { return jose.SignerOptions{} } +func Test_newServerEndpointID(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want []byte + }{ + {"ok", args{"foo"}, []byte{ + 0x8f, 0x63, 0x69, 0x20, 0x8a, 0x7a, 0x57, 0x0c, 0xbe, 0x4c, 0x46, 0x66, 0x77, 0xf8, 0x54, 0xe7, + }}, + {"ok uuid", args{"e4fa6d2d-fa9c-4fdc-913e-7484cc9516e4"}, []byte{ + 0x8d, 0x8d, 0x7f, 0x04, 0x73, 0xd4, 0x5f, 0x2f, 0xa8, 0xe1, 0x28, 0x9a, 0xd1, 0xa8, 0xcf, 0x7e, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var want uuid.UUID + copy(want[:], tt.want) + got := newServerEndpointID(tt.args.name) + if !reflect.DeepEqual(got, want) { + t.Errorf("newServerEndpointID() = %v, want %v", got, tt.want) + } + // Check version + if v := (got[6] & 0xf0) >> 4; v != 5 { + t.Errorf("newServerEndpointID() version = %d, want 5", v) + } + // Check variant + if v := (got[8] & 0x80) >> 6; v != 2 { + t.Errorf("newServerEndpointID() variant = %d, want 2", v) + } + }) + } +} + func Test_newStepIssuer(t *testing.T) { caURL, client := testCAHelper(t) signer, err := newJWKSignerFromEncryptedKey(testKeyID, testEncryptedJWKKey, testPassword) diff --git a/cas/stepcas/jwk_issuer_test.go b/cas/stepcas/jwk_issuer_test.go index 81a6d900..0924414b 100644 --- a/cas/stepcas/jwk_issuer_test.go +++ b/cas/stepcas/jwk_issuer_test.go @@ -48,6 +48,9 @@ func Test_jwkIssuer_SignToken(t *testing.T) { {"ok ra", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}, &raInfo{ AuthorityID: "authority-id", ProvisionerID: "provisioner-id", ProvisionerType: "provisioner-type", }}, false}, + {"ok ra endpoint id", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}, &raInfo{ + AuthorityID: "authority-id", EndpointID: "endpoint-id", + }}, false}, {"fail", fields{caURL, "ra@doe.org", &mockErrSigner{}}, args{"doe", []string{"doe.org"}, nil}, true}, } for _, tt := range tests { diff --git a/cas/stepcas/stepcas.go b/cas/stepcas/stepcas.go index 5cedebe3..fb7a4941 100644 --- a/cas/stepcas/stepcas.go +++ b/cas/stepcas/stepcas.go @@ -75,14 +75,16 @@ func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 return nil, errors.New("createCertificateRequest `lifetime` cannot be 0") } - var info *raInfo + info := &raInfo{ + AuthorityID: s.authorityID, + } + if req.IsServerCert { + info.EndpointID = newServerEndpointID(s.authorityID).String() + } if p := req.Provisioner; p != nil { - info = &raInfo{ - AuthorityID: s.authorityID, - ProvisionerID: p.ID, - ProvisionerType: p.Type, - ProvisionerName: p.Name, - } + info.ProvisionerID = p.ID + info.ProvisionerType = p.Type + info.ProvisionerName = p.Name } cert, chain, err := s.createCertificate(req.CSR, req.Lifetime, info) diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go index 9999e9c1..97750c81 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -672,6 +672,14 @@ func TestStepCAS_CreateCertificate(t *testing.T) { Certificate: testCrt, CertificateChain: []*x509.Certificate{testIssCrt}, }, false}, + {"ok with server cert", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: testCR, + Lifetime: time.Hour, + IsServerCert: true, + }}, &apiv1.CreateCertificateResponse{ + Certificate: testCrt, + CertificateChain: []*x509.Certificate{testIssCrt}, + }, false}, {"fail CSR", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ CSR: nil, Lifetime: time.Hour, diff --git a/cas/stepcas/x5c_issuer_test.go b/cas/stepcas/x5c_issuer_test.go index 5b260dda..3f7f372f 100644 --- a/cas/stepcas/x5c_issuer_test.go +++ b/cas/stepcas/x5c_issuer_test.go @@ -72,6 +72,9 @@ func Test_x5cIssuer_SignToken(t *testing.T) { {"ok ra", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, &raInfo{ AuthorityID: "authority-id", ProvisionerID: "provisioner-id", ProvisionerType: "provisioner-type", }}, false}, + {"ok ra endpoint id", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, &raInfo{ + AuthorityID: "authority-id", EndpointID: "endpoint-id", + }}, false}, {"fail crt", fields{caURL, "", testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, nil}, true}, {"fail key", fields{caURL, testX5CPath, "", "X5C"}, args{"doe", []string{"doe.org"}, nil}, true}, {"fail no signer", fields{caURL, testIssKeyPath, testIssPath, "X5C"}, args{"doe", []string{"doe.org"}, nil}, true}, diff --git a/go.mod b/go.mod index 14e78d60..02770fbb 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,9 @@ go 1.18 require ( cloud.google.com/go v0.100.2 - cloud.google.com/go/kms v1.4.0 cloud.google.com/go/security v1.3.0 - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible - github.com/Azure/go-autorest/autorest v0.11.27 - github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 - github.com/Azure/go-autorest/autorest/date v0.3.0 github.com/Masterminds/sprig/v3 v3.2.2 - github.com/ThalesIgnite/crypto11 v1.2.5 - github.com/aws/aws-sdk-go v1.44.37 github.com/go-chi/chi v4.0.2+incompatible - github.com/go-piv/piv-go v1.10.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.8 github.com/google/uuid v1.3.0 @@ -34,8 +26,8 @@ require ( github.com/urfave/cli v1.22.4 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.step.sm/cli-utils v0.7.3 - go.step.sm/crypto v0.17.0 - go.step.sm/linkedca v0.17.0 + go.step.sm/crypto v0.17.1 + go.step.sm/linkedca v0.17.1 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/net v0.0.0-20220607020251-c690dde0001d google.golang.org/api v0.84.0 @@ -48,19 +40,26 @@ require ( require ( cloud.google.com/go/compute v1.6.1 // indirect cloud.google.com/go/iam v0.1.0 // indirect + cloud.google.com/go/kms v1.4.0 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/armon/go-metrics v0.3.9 // indirect github.com/armon/go-radix v1.0.0 // indirect + github.com/aws/aws-sdk-go v1.44.37 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect @@ -75,6 +74,7 @@ require ( github.com/fatih/color v1.9.0 // indirect github.com/go-kit/kit v0.10.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect + github.com/go-piv/piv-go v1.10.0 // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index f9a03646..43387360 100644 --- a/go.sum +++ b/go.sum @@ -767,10 +767,10 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.step.sm/cli-utils v0.7.3 h1:IA12IaiXVCI18yOFVQuvMpyvjL8wuwUn1yO+KhAVAr0= go.step.sm/cli-utils v0.7.3/go.mod h1:RJRwbBLqzs5nrepQLAV9FuT3fVpWz66tKzLIB7Izpfk= go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= -go.step.sm/crypto v0.17.0 h1:qaLUbWygcMRMxrsz91jL5ytHIsIMABFYX6TkU+V8Pq8= -go.step.sm/crypto v0.17.0/go.mod h1:2oZdJ4ZUqPv5q8wz6yN4Qfsdcu2+eRaob4q1E5Azavs= -go.step.sm/linkedca v0.17.0 h1:90XYS0cPCVilsS1udTOph7TVnsNVVPK/gb66VIAP4RU= -go.step.sm/linkedca v0.17.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM= +go.step.sm/crypto v0.17.1 h1:uKpJNvzVy/GKR28hJbW8VCbfcKKBDnGNBYCKhAp2TSg= +go.step.sm/crypto v0.17.1/go.mod h1:FXFiLBUsoE0OGz8JTjxhYU1rwKKNgVIb5izZTUMdc/8= +go.step.sm/linkedca v0.17.1 h1:LSP3kGGeVkOAoDWoqg89tko6mpvJKTRcOHfrEOnPsNc= +go.step.sm/linkedca v0.17.1/go.mod h1:qSuYlIIhvPmA2+DSSS03E2IXhbXWTLW61Xh9zDQJ3VM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=