diff --git a/authority/authority.go b/authority/authority.go index 933ceb14..3c74c037 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -312,6 +312,7 @@ func (a *Authority) init() error { if id := a.config.AuthorityConfig.AuthorityID; id != "" && !strings.EqualFold(id, linkedcaClient.authorityID) { return errors.New("error initializing linkedca: token authority and configured authority do not match") } + a.config.AuthorityConfig.AuthorityID = linkedcaClient.authorityID linkedcaClient.Run() } @@ -322,6 +323,9 @@ func (a *Authority) init() error { options = *a.config.AuthorityConfig.Options } + // AuthorityID might be empty. It's always available linked CAs/RAs. + options.AuthorityID = a.config.AuthorityConfig.AuthorityID + // Configure linked RA if linkedcaClient != nil && options.CertificateAuthority == "" { conf, err := linkedcaClient.GetConfiguration(ctx) @@ -357,7 +361,6 @@ func (a *Authority) init() error { return err } } - a.x509CAService, err = cas.New(ctx, options) if err != nil { return err diff --git a/authority/linkedca.go b/authority/linkedca.go index 0b98f877..0380cbaf 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -277,6 +277,7 @@ func (c *linkedCaClient) StoreCertificateChain(p provisioner.Interface, fullchai PemCertificate: serializeCertificateChain(fullchain[0]), PemCertificateChain: serializeCertificateChain(fullchain[1:]...), Provisioner: createProvisionerIdentity(p), + RaProvisioner: createRegistrationAuthorityProvisioner(p), }) return errors.Wrap(err, "error posting certificate") } @@ -392,6 +393,26 @@ func createProvisionerIdentity(p provisioner.Interface) *linkedca.ProvisionerIde } } +type raProvisioner interface { + RAInfo() *provisioner.RAInfo +} + +func createRegistrationAuthorityProvisioner(p provisioner.Interface) *linkedca.RegistrationAuthorityProvisioner { + if rap, ok := p.(raProvisioner); ok { + info := rap.RAInfo() + typ := linkedca.Provisioner_Type_value[strings.ToUpper(info.ProvisionerType)] + return &linkedca.RegistrationAuthorityProvisioner{ + AuthorityId: info.AuthorityID, + Provisioner: &linkedca.ProvisionerIdentity{ + Id: info.ProvisionerID, + Type: linkedca.Provisioner_Type(typ), + Name: info.ProvisionerName, + }, + } + } + return nil +} + func serializeCertificate(crt *x509.Certificate) string { if crt == nil { return "" diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index de592941..5cfb0409 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -24,6 +24,7 @@ type jwtPayload struct { type stepPayload struct { SSH *SignSSHOptions `json:"ssh,omitempty"` + RA *RAInfo `json:"ra,omitempty"` } // JWK is the default provisioner, an entity that can sign tokens necessary for @@ -172,8 +173,17 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") } + // Wrap provisioner if the token is an RA token. + var self Interface = p + if claims.Step != nil && claims.Step.RA != nil { + self = &raProvisioner{ + Interface: p, + raInfo: claims.Step.RA, + } + } + return []SignOption{ - p, + self, templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID), diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 0d5cd41a..bdfc7da7 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -340,6 +340,26 @@ type Permissions struct { CriticalOptions map[string]string `json:"criticalOptions"` } +// 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"` +} + +// raProvisioner wraps a provisioner with RA data. +type raProvisioner struct { + Interface + raInfo *RAInfo +} + +// RAInfo returns the RAInfo in the wrapped provisioner. +func (p *raProvisioner) RAInfo() *RAInfo { + return p.raInfo +} + // MockProvisioner for testing type MockProvisioner struct { Mret1, Mret2, Mret3 interface{} diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index b9ae24c5..9f9a0e4e 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -221,8 +221,17 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") } + // Wrap provisioner if the token is an RA token. + var self Interface = p + if claims.Step != nil && claims.Step.RA != nil { + self = &raProvisioner{ + Interface: p, + raInfo: claims.Step.RA, + } + } + return []SignOption{ - p, + self, templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeX5C, p.Name, ""), diff --git a/authority/tls.go b/authority/tls.go index 4c29ca15..24cd86c5 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -93,12 +93,17 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration var prov provisioner.Interface + var pInfo *casapi.ProvisionerInfo for _, op := range extraOpts { switch k := op.(type) { // Capture current provisioner case provisioner.Interface: prov = k - + pInfo = &casapi.ProvisionerInfo{ + ProvisionerID: prov.GetID(), + ProvisionerType: prov.GetType().String(), + ProvisionerName: prov.GetName(), + } // Adds new options to NewCertificate case provisioner.CertificateOptions: certOptions = append(certOptions, k.Options(signOpts)...) @@ -221,10 +226,11 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Sign certificate lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate)) resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ - Template: leaf, - CSR: csr, - Lifetime: lifetime, - Backdate: signOpts.Backdate, + Template: leaf, + CSR: csr, + Lifetime: lifetime, + Backdate: signOpts.Backdate, + Provisioner: pInfo, }) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) diff --git a/ca/ca.go b/ca/ca.go index 7c00bb6b..741593d2 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -1,6 +1,7 @@ package ca import ( + "bytes" "context" "crypto/tls" "crypto/x509" @@ -342,10 +343,10 @@ func (ca *CA) Run() error { log.Printf("X.509 Root Fingerprint: %s", x509util.Fingerprint(crt)) } if authorityInfo.SSHCAHostPublicKey != nil { - log.Printf("SSH Host CA Key: %s\n", authorityInfo.SSHCAHostPublicKey) + log.Printf("SSH Host CA Key: %s\n", bytes.TrimSpace(authorityInfo.SSHCAHostPublicKey)) } if authorityInfo.SSHCAUserPublicKey != nil { - log.Printf("SSH User CA Key: %s\n", authorityInfo.SSHCAUserPublicKey) + log.Printf("SSH User CA Key: %s\n", bytes.TrimSpace(authorityInfo.SSHCAUserPublicKey)) } } diff --git a/cas/apiv1/options.go b/cas/apiv1/options.go index f69f933b..01c38efd 100644 --- a/cas/apiv1/options.go +++ b/cas/apiv1/options.go @@ -12,6 +12,10 @@ import ( // Options represents the configuration options used to select and configure the // CertificateAuthorityService (CAS) to use. type Options struct { + // AuthorityID is the the id oc the current authority. This is used on + // StepCAS to add information about the origin of a certificate. + AuthorityID string `json:"-"` + // The type of the CAS to use. Type string `json:"type"` diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index bf745c17..33a879ce 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -52,11 +52,20 @@ 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 + Template *x509.Certificate + CSR *x509.CertificateRequest + Lifetime time.Duration + Backdate time.Duration + RequestID string + Provisioner *ProvisionerInfo +} + +// ProvisionerInfo contains information of the provisioner used to authorize a +// certificate. +type ProvisionerInfo struct { + ProvisionerID string + ProvisionerType string + ProvisionerName string } // CreateCertificateResponse is the response to a create certificate request. diff --git a/cas/stepcas/issuer.go b/cas/stepcas/issuer.go index be395e33..57421ffe 100644 --- a/cas/stepcas/issuer.go +++ b/cas/stepcas/issuer.go @@ -10,8 +10,15 @@ import ( "github.com/smallstep/certificates/cas/apiv1" ) +type raInfo struct { + AuthorityID string `json:"authorityId,omitempty"` + ProvisionerID string `json:"provisionerId"` + ProvisionerType string `json:"provisionerType"` + ProvisionerName string `json:"provisionerName"` +} + type stepIssuer interface { - SignToken(subject string, sans []string) (string, error) + SignToken(subject string, sans []string, info *raInfo) (string, error) RevokeToken(subject string) (string, error) Lifetime(d time.Duration) time.Duration } diff --git a/cas/stepcas/issuer_test.go b/cas/stepcas/issuer_test.go index 6fffd729..726dedbf 100644 --- a/cas/stepcas/issuer_test.go +++ b/cas/stepcas/issuer_test.go @@ -13,7 +13,7 @@ import ( type mockErrIssuer struct{} -func (m mockErrIssuer) SignToken(subject string, sans []string) (string, error) { +func (m mockErrIssuer) SignToken(subject string, sans []string, info *raInfo) (string, error) { return "", apiv1.ErrNotImplemented{} } diff --git a/cas/stepcas/jwk_issuer.go b/cas/stepcas/jwk_issuer.go index db45ef48..4ef4f541 100644 --- a/cas/stepcas/jwk_issuer.go +++ b/cas/stepcas/jwk_issuer.go @@ -53,25 +53,25 @@ func newJWKIssuer(caURL *url.URL, client *ca.Client, cfg *apiv1.CertificateIssue }, nil } -func (i *jwkIssuer) SignToken(subject string, sans []string) (string, error) { +func (i *jwkIssuer) SignToken(subject string, sans []string, info *raInfo) (string, error) { aud := i.caURL.ResolveReference(&url.URL{ Path: "/1.0/sign", }).String() - return i.createToken(aud, subject, sans) + return i.createToken(aud, subject, sans, info) } func (i *jwkIssuer) RevokeToken(subject string) (string, error) { aud := i.caURL.ResolveReference(&url.URL{ Path: "/1.0/revoke", }).String() - return i.createToken(aud, subject, nil) + return i.createToken(aud, subject, nil, nil) } func (i *jwkIssuer) Lifetime(d time.Duration) time.Duration { return d } -func (i *jwkIssuer) createToken(aud, sub string, sans []string) (string, error) { +func (i *jwkIssuer) createToken(aud, sub string, sans []string, info *raInfo) (string, error) { id, err := randutil.Hex(64) // 256 bits if err != nil { return "", err @@ -84,6 +84,13 @@ func (i *jwkIssuer) createToken(aud, sub string, sans []string) (string, error) "sans": sans, }) } + if info != nil { + builder = builder.Claims(map[string]interface{}{ + "step": map[string]interface{}{ + "ra": info, + }, + }) + } tok, err := builder.CompactSerialize() if err != nil { diff --git a/cas/stepcas/jwk_issuer_test.go b/cas/stepcas/jwk_issuer_test.go index 7ebfcb3f..81a6d900 100644 --- a/cas/stepcas/jwk_issuer_test.go +++ b/cas/stepcas/jwk_issuer_test.go @@ -27,11 +27,16 @@ func Test_jwkIssuer_SignToken(t *testing.T) { type args struct { subject string sans []string + info *raInfo + } + type stepClaims struct { + RA *raInfo `json:"ra"` } type claims struct { - Aud []string `json:"aud"` - Sub string `json:"sub"` - Sans []string `json:"sans"` + Aud []string `json:"aud"` + Sub string `json:"sub"` + Sans []string `json:"sans"` + Step stepClaims `json:"step"` } tests := []struct { name string @@ -39,8 +44,11 @@ func Test_jwkIssuer_SignToken(t *testing.T) { args args wantErr bool }{ - {"ok", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}}, false}, - {"fail", fields{caURL, "ra@doe.org", &mockErrSigner{}}, args{"doe", []string{"doe.org"}}, true}, + {"ok", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}, nil}, false}, + {"ok ra", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}, &raInfo{ + AuthorityID: "authority-id", ProvisionerID: "provisioner-id", ProvisionerType: "provisioner-type", + }}, false}, + {"fail", fields{caURL, "ra@doe.org", &mockErrSigner{}}, args{"doe", []string{"doe.org"}, nil}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -49,7 +57,7 @@ func Test_jwkIssuer_SignToken(t *testing.T) { issuer: tt.fields.issuer, signer: tt.fields.signer, } - got, err := i.SignToken(tt.args.subject, tt.args.sans) + got, err := i.SignToken(tt.args.subject, tt.args.sans, tt.args.info) if (err != nil) != tt.wantErr { t.Errorf("jwkIssuer.SignToken() error = %v, wantErr %v", err, tt.wantErr) return @@ -65,6 +73,9 @@ func Test_jwkIssuer_SignToken(t *testing.T) { Sub: tt.args.subject, Sans: tt.args.sans, } + if tt.args.info != nil { + want.Step.RA = tt.args.info + } if err := jwt.Claims(testX5CKey.Public(), &c); err != nil { t.Errorf("jwt.Claims() error = %v", err) } diff --git a/cas/stepcas/stepcas.go b/cas/stepcas/stepcas.go index 9fcbd36c..ffcbeab9 100644 --- a/cas/stepcas/stepcas.go +++ b/cas/stepcas/stepcas.go @@ -23,6 +23,7 @@ func init() { type StepCAS struct { iss stepIssuer client *ca.Client + authorityID string fingerprint string } @@ -59,6 +60,7 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) { return &StepCAS{ iss: iss, client: client, + authorityID: opts.AuthorityID, fingerprint: opts.CertificateAuthorityFingerprint, }, nil } @@ -73,7 +75,17 @@ func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 return nil, errors.New("createCertificateRequest `lifetime` cannot be 0") } - cert, chain, err := s.createCertificate(req.CSR, req.Lifetime) + var info *raInfo + if p := req.Provisioner; p != nil { + info = &raInfo{ + AuthorityID: s.authorityID, + ProvisionerID: p.ProvisionerID, + ProvisionerType: p.ProvisionerType, + ProvisionerName: p.ProvisionerName, + } + } + + cert, chain, err := s.createCertificate(req.CSR, req.Lifetime, info) if err != nil { return nil, err } @@ -135,7 +147,7 @@ func (s *StepCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequ }, nil } -func (s *StepCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.Duration) (*x509.Certificate, []*x509.Certificate, error) { +func (s *StepCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.Duration, raInfo *raInfo) (*x509.Certificate, []*x509.Certificate, error) { sans := make([]string, 0, len(cr.DNSNames)+len(cr.EmailAddresses)+len(cr.IPAddresses)+len(cr.URIs)) sans = append(sans, cr.DNSNames...) sans = append(sans, cr.EmailAddresses...) @@ -151,7 +163,7 @@ func (s *StepCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.D commonName = sans[0] } - token, err := s.iss.SignToken(commonName, sans) + token, err := s.iss.SignToken(commonName, sans, raInfo) if err != nil { return nil, nil, err } diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go index ad7851bf..4654292d 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -665,6 +665,14 @@ func TestStepCAS_CreateCertificate(t *testing.T) { Certificate: testCrt, CertificateChain: []*x509.Certificate{testIssCrt}, }, false}, + {"ok with provisioner", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ + CSR: testCR, + Lifetime: time.Hour, + Provisioner: &apiv1.ProvisionerInfo{ProvisionerID: "provisioner-id", ProvisionerType: "ACME"}, + }}, &apiv1.CreateCertificateResponse{ + Certificate: testCrt, + CertificateChain: []*x509.Certificate{testIssCrt}, + }, false}, {"fail CSR", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{ CSR: nil, Lifetime: time.Hour, @@ -691,6 +699,7 @@ func TestStepCAS_CreateCertificate(t *testing.T) { s := &StepCAS{ iss: tt.fields.iss, client: tt.fields.client, + authorityID: "authority-id", fingerprint: tt.fields.fingerprint, } got, err := s.CreateCertificate(tt.args.req) diff --git a/cas/stepcas/x5c_issuer.go b/cas/stepcas/x5c_issuer.go index 76ed9c3c..a005e501 100644 --- a/cas/stepcas/x5c_issuer.go +++ b/cas/stepcas/x5c_issuer.go @@ -46,13 +46,13 @@ func newX5CIssuer(caURL *url.URL, cfg *apiv1.CertificateIssuer) (*x5cIssuer, err }, nil } -func (i *x5cIssuer) SignToken(subject string, sans []string) (string, error) { +func (i *x5cIssuer) SignToken(subject string, sans []string, info *raInfo) (string, error) { aud := i.caURL.ResolveReference(&url.URL{ Path: "/1.0/sign", Fragment: "x5c/" + i.issuer, }).String() - return i.createToken(aud, subject, sans) + return i.createToken(aud, subject, sans, info) } func (i *x5cIssuer) RevokeToken(subject string) (string, error) { @@ -61,7 +61,7 @@ func (i *x5cIssuer) RevokeToken(subject string) (string, error) { Fragment: "x5c/" + i.issuer, }).String() - return i.createToken(aud, subject, nil) + return i.createToken(aud, subject, nil, nil) } func (i *x5cIssuer) Lifetime(d time.Duration) time.Duration { @@ -76,7 +76,7 @@ func (i *x5cIssuer) Lifetime(d time.Duration) time.Duration { return d } -func (i *x5cIssuer) createToken(aud, sub string, sans []string) (string, error) { +func (i *x5cIssuer) createToken(aud, sub string, sans []string, info *raInfo) (string, error) { signer, err := newX5CSigner(i.certFile, i.keyFile, i.password) if err != nil { return "", err @@ -94,6 +94,13 @@ func (i *x5cIssuer) createToken(aud, sub string, sans []string) (string, error) "sans": sans, }) } + if info != nil { + builder = builder.Claims(map[string]interface{}{ + "step": map[string]interface{}{ + "ra": info, + }, + }) + } tok, err := builder.CompactSerialize() if err != nil { diff --git a/cas/stepcas/x5c_issuer_test.go b/cas/stepcas/x5c_issuer_test.go index b1bc653d..5b260dda 100644 --- a/cas/stepcas/x5c_issuer_test.go +++ b/cas/stepcas/x5c_issuer_test.go @@ -51,11 +51,16 @@ func Test_x5cIssuer_SignToken(t *testing.T) { type args struct { subject string sans []string + info *raInfo + } + type stepClaims struct { + RA *raInfo `json:"ra"` } type claims struct { - Aud []string `json:"aud"` - Sub string `json:"sub"` - Sans []string `json:"sans"` + Aud []string `json:"aud"` + Sub string `json:"sub"` + Sans []string `json:"sans"` + Step stepClaims `json:"step"` } tests := []struct { name string @@ -63,10 +68,13 @@ func Test_x5cIssuer_SignToken(t *testing.T) { args args wantErr bool }{ - {"ok", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}}, false}, - {"fail crt", fields{caURL, "", testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}}, true}, - {"fail key", fields{caURL, testX5CPath, "", "X5C"}, args{"doe", []string{"doe.org"}}, true}, - {"fail no signer", fields{caURL, testIssKeyPath, testIssPath, "X5C"}, args{"doe", []string{"doe.org"}}, true}, + {"ok", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, nil}, false}, + {"ok ra", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, &raInfo{ + AuthorityID: "authority-id", ProvisionerID: "provisioner-id", ProvisionerType: "provisioner-type", + }}, 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}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -76,7 +84,7 @@ func Test_x5cIssuer_SignToken(t *testing.T) { keyFile: tt.fields.keyFile, issuer: tt.fields.issuer, } - got, err := i.SignToken(tt.args.subject, tt.args.sans) + got, err := i.SignToken(tt.args.subject, tt.args.sans, tt.args.info) if (err != nil) != tt.wantErr { t.Errorf("x5cIssuer.SignToken() error = %v, wantErr %v", err, tt.wantErr) } @@ -91,6 +99,9 @@ func Test_x5cIssuer_SignToken(t *testing.T) { Sub: tt.args.subject, Sans: tt.args.sans, } + if tt.args.info != nil { + want.Step.RA = tt.args.info + } if err := jwt.Claims(testX5CKey.Public(), &c); err != nil { t.Errorf("jwt.Claims() error = %v", err) } diff --git a/go.mod b/go.mod index a9cbc3a0..77015abe 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.step.sm/cli-utils v0.7.0 go.step.sm/crypto v0.16.2 - go.step.sm/linkedca v0.16.1 + go.step.sm/linkedca v0.17.0 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/net v0.0.0-20220403103023-749bd193bc2b golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect diff --git a/go.sum b/go.sum index b7fecfa4..09da2f9d 100644 --- a/go.sum +++ b/go.sum @@ -816,8 +816,8 @@ go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/ go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= go.step.sm/crypto v0.16.2 h1:Pr9aazTwWBBZNogUsOqhOrPSdwAa9pPs+lMB602lnDA= go.step.sm/crypto v0.16.2/go.mod h1:1WkTOTY+fOX/RY4TnZREp6trQAsBHRQ7nu6QJBiNQF8= -go.step.sm/linkedca v0.16.1 h1:CdbMV5SjnlRsgeYTXaaZmQCkYIgJq8BOzpewri57M2k= -go.step.sm/linkedca v0.16.1/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM= +go.step.sm/linkedca v0.17.0 h1:90XYS0cPCVilsS1udTOph7TVnsNVVPK/gb66VIAP4RU= +go.step.sm/linkedca v0.17.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM= 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=