forked from TrueCloudLab/certificates
commit
38fb92452f
19 changed files with 192 additions and 45 deletions
|
@ -312,6 +312,7 @@ func (a *Authority) init() error {
|
||||||
if id := a.config.AuthorityConfig.AuthorityID; id != "" && !strings.EqualFold(id, linkedcaClient.authorityID) {
|
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")
|
return errors.New("error initializing linkedca: token authority and configured authority do not match")
|
||||||
}
|
}
|
||||||
|
a.config.AuthorityConfig.AuthorityID = linkedcaClient.authorityID
|
||||||
linkedcaClient.Run()
|
linkedcaClient.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,6 +323,9 @@ func (a *Authority) init() error {
|
||||||
options = *a.config.AuthorityConfig.Options
|
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
|
// Configure linked RA
|
||||||
if linkedcaClient != nil && options.CertificateAuthority == "" {
|
if linkedcaClient != nil && options.CertificateAuthority == "" {
|
||||||
conf, err := linkedcaClient.GetConfiguration(ctx)
|
conf, err := linkedcaClient.GetConfiguration(ctx)
|
||||||
|
@ -357,7 +361,6 @@ func (a *Authority) init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.x509CAService, err = cas.New(ctx, options)
|
a.x509CAService, err = cas.New(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -277,6 +277,7 @@ func (c *linkedCaClient) StoreCertificateChain(p provisioner.Interface, fullchai
|
||||||
PemCertificate: serializeCertificateChain(fullchain[0]),
|
PemCertificate: serializeCertificateChain(fullchain[0]),
|
||||||
PemCertificateChain: serializeCertificateChain(fullchain[1:]...),
|
PemCertificateChain: serializeCertificateChain(fullchain[1:]...),
|
||||||
Provisioner: createProvisionerIdentity(p),
|
Provisioner: createProvisionerIdentity(p),
|
||||||
|
RaProvisioner: createRegistrationAuthorityProvisioner(p),
|
||||||
})
|
})
|
||||||
return errors.Wrap(err, "error posting certificate")
|
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 {
|
func serializeCertificate(crt *x509.Certificate) string {
|
||||||
if crt == nil {
|
if crt == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -24,6 +24,7 @@ type jwtPayload struct {
|
||||||
|
|
||||||
type stepPayload struct {
|
type stepPayload struct {
|
||||||
SSH *SignSSHOptions `json:"ssh,omitempty"`
|
SSH *SignSSHOptions `json:"ssh,omitempty"`
|
||||||
|
RA *RAInfo `json:"ra,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWK is the default provisioner, an entity that can sign tokens necessary for
|
// 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")
|
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{
|
return []SignOption{
|
||||||
p,
|
self,
|
||||||
templateOptions,
|
templateOptions,
|
||||||
// modifiers / withOptions
|
// modifiers / withOptions
|
||||||
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
|
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
|
||||||
|
|
|
@ -340,6 +340,26 @@ type Permissions struct {
|
||||||
CriticalOptions map[string]string `json:"criticalOptions"`
|
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
|
// MockProvisioner for testing
|
||||||
type MockProvisioner struct {
|
type MockProvisioner struct {
|
||||||
Mret1, Mret2, Mret3 interface{}
|
Mret1, Mret2, Mret3 interface{}
|
||||||
|
|
|
@ -221,8 +221,17 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
|
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{
|
return []SignOption{
|
||||||
p,
|
self,
|
||||||
templateOptions,
|
templateOptions,
|
||||||
// modifiers / withOptions
|
// modifiers / withOptions
|
||||||
newProvisionerExtensionOption(TypeX5C, p.Name, ""),
|
newProvisionerExtensionOption(TypeX5C, p.Name, ""),
|
||||||
|
|
|
@ -93,12 +93,17 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
|
||||||
signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration
|
signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration
|
||||||
|
|
||||||
var prov provisioner.Interface
|
var prov provisioner.Interface
|
||||||
|
var pInfo *casapi.ProvisionerInfo
|
||||||
for _, op := range extraOpts {
|
for _, op := range extraOpts {
|
||||||
switch k := op.(type) {
|
switch k := op.(type) {
|
||||||
// Capture current provisioner
|
// Capture current provisioner
|
||||||
case provisioner.Interface:
|
case provisioner.Interface:
|
||||||
prov = k
|
prov = k
|
||||||
|
pInfo = &casapi.ProvisionerInfo{
|
||||||
|
ProvisionerID: prov.GetID(),
|
||||||
|
ProvisionerType: prov.GetType().String(),
|
||||||
|
ProvisionerName: prov.GetName(),
|
||||||
|
}
|
||||||
// Adds new options to NewCertificate
|
// Adds new options to NewCertificate
|
||||||
case provisioner.CertificateOptions:
|
case provisioner.CertificateOptions:
|
||||||
certOptions = append(certOptions, k.Options(signOpts)...)
|
certOptions = append(certOptions, k.Options(signOpts)...)
|
||||||
|
@ -221,10 +226,11 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
|
||||||
// Sign certificate
|
// Sign certificate
|
||||||
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
|
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
|
||||||
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
|
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
|
||||||
Template: leaf,
|
Template: leaf,
|
||||||
CSR: csr,
|
CSR: csr,
|
||||||
Lifetime: lifetime,
|
Lifetime: lifetime,
|
||||||
Backdate: signOpts.Backdate,
|
Backdate: signOpts.Backdate,
|
||||||
|
Provisioner: pInfo,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
|
||||||
|
|
5
ca/ca.go
5
ca/ca.go
|
@ -1,6 +1,7 @@
|
||||||
package ca
|
package ca
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -342,10 +343,10 @@ func (ca *CA) Run() error {
|
||||||
log.Printf("X.509 Root Fingerprint: %s", x509util.Fingerprint(crt))
|
log.Printf("X.509 Root Fingerprint: %s", x509util.Fingerprint(crt))
|
||||||
}
|
}
|
||||||
if authorityInfo.SSHCAHostPublicKey != nil {
|
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 {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,10 @@ import (
|
||||||
// Options represents the configuration options used to select and configure the
|
// Options represents the configuration options used to select and configure the
|
||||||
// CertificateAuthorityService (CAS) to use.
|
// CertificateAuthorityService (CAS) to use.
|
||||||
type Options struct {
|
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.
|
// The type of the CAS to use.
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
|
|
@ -52,11 +52,20 @@ const (
|
||||||
|
|
||||||
// CreateCertificateRequest is the request used to sign a new certificate.
|
// CreateCertificateRequest is the request used to sign a new certificate.
|
||||||
type CreateCertificateRequest struct {
|
type CreateCertificateRequest struct {
|
||||||
Template *x509.Certificate
|
Template *x509.Certificate
|
||||||
CSR *x509.CertificateRequest
|
CSR *x509.CertificateRequest
|
||||||
Lifetime time.Duration
|
Lifetime time.Duration
|
||||||
Backdate time.Duration
|
Backdate time.Duration
|
||||||
RequestID string
|
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.
|
// CreateCertificateResponse is the response to a create certificate request.
|
||||||
|
|
|
@ -10,8 +10,15 @@ import (
|
||||||
"github.com/smallstep/certificates/cas/apiv1"
|
"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 {
|
type stepIssuer interface {
|
||||||
SignToken(subject string, sans []string) (string, error)
|
SignToken(subject string, sans []string, info *raInfo) (string, error)
|
||||||
RevokeToken(subject string) (string, error)
|
RevokeToken(subject string) (string, error)
|
||||||
Lifetime(d time.Duration) time.Duration
|
Lifetime(d time.Duration) time.Duration
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
type mockErrIssuer struct{}
|
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{}
|
return "", apiv1.ErrNotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,25 +53,25 @@ func newJWKIssuer(caURL *url.URL, client *ca.Client, cfg *apiv1.CertificateIssue
|
||||||
}, nil
|
}, 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{
|
aud := i.caURL.ResolveReference(&url.URL{
|
||||||
Path: "/1.0/sign",
|
Path: "/1.0/sign",
|
||||||
}).String()
|
}).String()
|
||||||
return i.createToken(aud, subject, sans)
|
return i.createToken(aud, subject, sans, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *jwkIssuer) RevokeToken(subject string) (string, error) {
|
func (i *jwkIssuer) RevokeToken(subject string) (string, error) {
|
||||||
aud := i.caURL.ResolveReference(&url.URL{
|
aud := i.caURL.ResolveReference(&url.URL{
|
||||||
Path: "/1.0/revoke",
|
Path: "/1.0/revoke",
|
||||||
}).String()
|
}).String()
|
||||||
return i.createToken(aud, subject, nil)
|
return i.createToken(aud, subject, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *jwkIssuer) Lifetime(d time.Duration) time.Duration {
|
func (i *jwkIssuer) Lifetime(d time.Duration) time.Duration {
|
||||||
return d
|
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
|
id, err := randutil.Hex(64) // 256 bits
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -84,6 +84,13 @@ func (i *jwkIssuer) createToken(aud, sub string, sans []string) (string, error)
|
||||||
"sans": sans,
|
"sans": sans,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if info != nil {
|
||||||
|
builder = builder.Claims(map[string]interface{}{
|
||||||
|
"step": map[string]interface{}{
|
||||||
|
"ra": info,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
tok, err := builder.CompactSerialize()
|
tok, err := builder.CompactSerialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -27,11 +27,16 @@ func Test_jwkIssuer_SignToken(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
subject string
|
subject string
|
||||||
sans []string
|
sans []string
|
||||||
|
info *raInfo
|
||||||
|
}
|
||||||
|
type stepClaims struct {
|
||||||
|
RA *raInfo `json:"ra"`
|
||||||
}
|
}
|
||||||
type claims struct {
|
type claims struct {
|
||||||
Aud []string `json:"aud"`
|
Aud []string `json:"aud"`
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
Sans []string `json:"sans"`
|
Sans []string `json:"sans"`
|
||||||
|
Step stepClaims `json:"step"`
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -39,8 +44,11 @@ func Test_jwkIssuer_SignToken(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}}, false},
|
{"ok", fields{caURL, "ra@doe.org", signer}, args{"doe", []string{"doe.org"}, nil}, false},
|
||||||
{"fail", fields{caURL, "ra@doe.org", &mockErrSigner{}}, args{"doe", []string{"doe.org"}}, true},
|
{"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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -49,7 +57,7 @@ func Test_jwkIssuer_SignToken(t *testing.T) {
|
||||||
issuer: tt.fields.issuer,
|
issuer: tt.fields.issuer,
|
||||||
signer: tt.fields.signer,
|
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 {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("jwkIssuer.SignToken() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("jwkIssuer.SignToken() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
@ -65,6 +73,9 @@ func Test_jwkIssuer_SignToken(t *testing.T) {
|
||||||
Sub: tt.args.subject,
|
Sub: tt.args.subject,
|
||||||
Sans: tt.args.sans,
|
Sans: tt.args.sans,
|
||||||
}
|
}
|
||||||
|
if tt.args.info != nil {
|
||||||
|
want.Step.RA = tt.args.info
|
||||||
|
}
|
||||||
if err := jwt.Claims(testX5CKey.Public(), &c); err != nil {
|
if err := jwt.Claims(testX5CKey.Public(), &c); err != nil {
|
||||||
t.Errorf("jwt.Claims() error = %v", err)
|
t.Errorf("jwt.Claims() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ func init() {
|
||||||
type StepCAS struct {
|
type StepCAS struct {
|
||||||
iss stepIssuer
|
iss stepIssuer
|
||||||
client *ca.Client
|
client *ca.Client
|
||||||
|
authorityID string
|
||||||
fingerprint string
|
fingerprint string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) {
|
||||||
return &StepCAS{
|
return &StepCAS{
|
||||||
iss: iss,
|
iss: iss,
|
||||||
client: client,
|
client: client,
|
||||||
|
authorityID: opts.AuthorityID,
|
||||||
fingerprint: opts.CertificateAuthorityFingerprint,
|
fingerprint: opts.CertificateAuthorityFingerprint,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,17 @@ func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1
|
||||||
return nil, errors.New("createCertificateRequest `lifetime` cannot be 0")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -135,7 +147,7 @@ func (s *StepCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequ
|
||||||
}, nil
|
}, 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 := make([]string, 0, len(cr.DNSNames)+len(cr.EmailAddresses)+len(cr.IPAddresses)+len(cr.URIs))
|
||||||
sans = append(sans, cr.DNSNames...)
|
sans = append(sans, cr.DNSNames...)
|
||||||
sans = append(sans, cr.EmailAddresses...)
|
sans = append(sans, cr.EmailAddresses...)
|
||||||
|
@ -151,7 +163,7 @@ func (s *StepCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.D
|
||||||
commonName = sans[0]
|
commonName = sans[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := s.iss.SignToken(commonName, sans)
|
token, err := s.iss.SignToken(commonName, sans, raInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -665,6 +665,14 @@ func TestStepCAS_CreateCertificate(t *testing.T) {
|
||||||
Certificate: testCrt,
|
Certificate: testCrt,
|
||||||
CertificateChain: []*x509.Certificate{testIssCrt},
|
CertificateChain: []*x509.Certificate{testIssCrt},
|
||||||
}, false},
|
}, 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{
|
{"fail CSR", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
|
||||||
CSR: nil,
|
CSR: nil,
|
||||||
Lifetime: time.Hour,
|
Lifetime: time.Hour,
|
||||||
|
@ -691,6 +699,7 @@ func TestStepCAS_CreateCertificate(t *testing.T) {
|
||||||
s := &StepCAS{
|
s := &StepCAS{
|
||||||
iss: tt.fields.iss,
|
iss: tt.fields.iss,
|
||||||
client: tt.fields.client,
|
client: tt.fields.client,
|
||||||
|
authorityID: "authority-id",
|
||||||
fingerprint: tt.fields.fingerprint,
|
fingerprint: tt.fields.fingerprint,
|
||||||
}
|
}
|
||||||
got, err := s.CreateCertificate(tt.args.req)
|
got, err := s.CreateCertificate(tt.args.req)
|
||||||
|
|
|
@ -46,13 +46,13 @@ func newX5CIssuer(caURL *url.URL, cfg *apiv1.CertificateIssuer) (*x5cIssuer, err
|
||||||
}, nil
|
}, 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{
|
aud := i.caURL.ResolveReference(&url.URL{
|
||||||
Path: "/1.0/sign",
|
Path: "/1.0/sign",
|
||||||
Fragment: "x5c/" + i.issuer,
|
Fragment: "x5c/" + i.issuer,
|
||||||
}).String()
|
}).String()
|
||||||
|
|
||||||
return i.createToken(aud, subject, sans)
|
return i.createToken(aud, subject, sans, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *x5cIssuer) RevokeToken(subject string) (string, error) {
|
func (i *x5cIssuer) RevokeToken(subject string) (string, error) {
|
||||||
|
@ -61,7 +61,7 @@ func (i *x5cIssuer) RevokeToken(subject string) (string, error) {
|
||||||
Fragment: "x5c/" + i.issuer,
|
Fragment: "x5c/" + i.issuer,
|
||||||
}).String()
|
}).String()
|
||||||
|
|
||||||
return i.createToken(aud, subject, nil)
|
return i.createToken(aud, subject, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *x5cIssuer) Lifetime(d time.Duration) time.Duration {
|
func (i *x5cIssuer) Lifetime(d time.Duration) time.Duration {
|
||||||
|
@ -76,7 +76,7 @@ func (i *x5cIssuer) Lifetime(d time.Duration) time.Duration {
|
||||||
return d
|
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)
|
signer, err := newX5CSigner(i.certFile, i.keyFile, i.password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -94,6 +94,13 @@ func (i *x5cIssuer) createToken(aud, sub string, sans []string) (string, error)
|
||||||
"sans": sans,
|
"sans": sans,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if info != nil {
|
||||||
|
builder = builder.Claims(map[string]interface{}{
|
||||||
|
"step": map[string]interface{}{
|
||||||
|
"ra": info,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
tok, err := builder.CompactSerialize()
|
tok, err := builder.CompactSerialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -51,11 +51,16 @@ func Test_x5cIssuer_SignToken(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
subject string
|
subject string
|
||||||
sans []string
|
sans []string
|
||||||
|
info *raInfo
|
||||||
|
}
|
||||||
|
type stepClaims struct {
|
||||||
|
RA *raInfo `json:"ra"`
|
||||||
}
|
}
|
||||||
type claims struct {
|
type claims struct {
|
||||||
Aud []string `json:"aud"`
|
Aud []string `json:"aud"`
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
Sans []string `json:"sans"`
|
Sans []string `json:"sans"`
|
||||||
|
Step stepClaims `json:"step"`
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -63,10 +68,13 @@ func Test_x5cIssuer_SignToken(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}}, false},
|
{"ok", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, nil}, false},
|
||||||
{"fail crt", fields{caURL, "", testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}}, true},
|
{"ok ra", fields{caURL, testX5CPath, testX5CKeyPath, "X5C"}, args{"doe", []string{"doe.org"}, &raInfo{
|
||||||
{"fail key", fields{caURL, testX5CPath, "", "X5C"}, args{"doe", []string{"doe.org"}}, true},
|
AuthorityID: "authority-id", ProvisionerID: "provisioner-id", ProvisionerType: "provisioner-type",
|
||||||
{"fail no signer", fields{caURL, testIssKeyPath, testIssPath, "X5C"}, args{"doe", []string{"doe.org"}}, true},
|
}}, 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -76,7 +84,7 @@ func Test_x5cIssuer_SignToken(t *testing.T) {
|
||||||
keyFile: tt.fields.keyFile,
|
keyFile: tt.fields.keyFile,
|
||||||
issuer: tt.fields.issuer,
|
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 {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("x5cIssuer.SignToken() error = %v, wantErr %v", err, 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,
|
Sub: tt.args.subject,
|
||||||
Sans: tt.args.sans,
|
Sans: tt.args.sans,
|
||||||
}
|
}
|
||||||
|
if tt.args.info != nil {
|
||||||
|
want.Step.RA = tt.args.info
|
||||||
|
}
|
||||||
if err := jwt.Claims(testX5CKey.Public(), &c); err != nil {
|
if err := jwt.Claims(testX5CKey.Public(), &c); err != nil {
|
||||||
t.Errorf("jwt.Claims() error = %v", err)
|
t.Errorf("jwt.Claims() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -48,7 +48,7 @@ require (
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||||
go.step.sm/cli-utils v0.7.0
|
go.step.sm/cli-utils v0.7.0
|
||||||
go.step.sm/crypto v0.16.2
|
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/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
||||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect
|
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect
|
||||||
|
|
4
go.sum
4
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.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||||
go.step.sm/crypto v0.16.2 h1:Pr9aazTwWBBZNogUsOqhOrPSdwAa9pPs+lMB602lnDA=
|
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/crypto v0.16.2/go.mod h1:1WkTOTY+fOX/RY4TnZREp6trQAsBHRQ7nu6QJBiNQF8=
|
||||||
go.step.sm/linkedca v0.16.1 h1:CdbMV5SjnlRsgeYTXaaZmQCkYIgJq8BOzpewri57M2k=
|
go.step.sm/linkedca v0.17.0 h1:90XYS0cPCVilsS1udTOph7TVnsNVVPK/gb66VIAP4RU=
|
||||||
go.step.sm/linkedca v0.16.1/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
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.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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
|
Loading…
Reference in a new issue