forked from TrueCloudLab/certificates
parent
89b6aa924a
commit
debe565e42
4 changed files with 154 additions and 13 deletions
|
@ -1,6 +1,7 @@
|
||||||
package authority
|
package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
@ -24,6 +25,7 @@ import (
|
||||||
adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql"
|
adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql"
|
||||||
"github.com/smallstep/certificates/authority/administrator"
|
"github.com/smallstep/certificates/authority/administrator"
|
||||||
"github.com/smallstep/certificates/authority/config"
|
"github.com/smallstep/certificates/authority/config"
|
||||||
|
"github.com/smallstep/certificates/authority/internal/constraints"
|
||||||
"github.com/smallstep/certificates/authority/policy"
|
"github.com/smallstep/certificates/authority/policy"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/cas"
|
"github.com/smallstep/certificates/cas"
|
||||||
|
@ -46,14 +48,15 @@ type Authority struct {
|
||||||
linkedCAToken string
|
linkedCAToken string
|
||||||
|
|
||||||
// X509 CA
|
// X509 CA
|
||||||
password []byte
|
password []byte
|
||||||
issuerPassword []byte
|
issuerPassword []byte
|
||||||
x509CAService cas.CertificateAuthorityService
|
x509CAService cas.CertificateAuthorityService
|
||||||
rootX509Certs []*x509.Certificate
|
rootX509Certs []*x509.Certificate
|
||||||
rootX509CertPool *x509.CertPool
|
rootX509CertPool *x509.CertPool
|
||||||
federatedX509Certs []*x509.Certificate
|
federatedX509Certs []*x509.Certificate
|
||||||
certificates *sync.Map
|
intermediateX509Certs []*x509.Certificate
|
||||||
x509Enforcers []provisioner.CertificateEnforcer
|
certificates *sync.Map
|
||||||
|
x509Enforcers []provisioner.CertificateEnforcer
|
||||||
|
|
||||||
// SCEP CA
|
// SCEP CA
|
||||||
scepService *scep.Service
|
scepService *scep.Service
|
||||||
|
@ -80,10 +83,9 @@ type Authority struct {
|
||||||
authorizeRenewFunc provisioner.AuthorizeRenewFunc
|
authorizeRenewFunc provisioner.AuthorizeRenewFunc
|
||||||
authorizeSSHRenewFunc provisioner.AuthorizeSSHRenewFunc
|
authorizeSSHRenewFunc provisioner.AuthorizeSSHRenewFunc
|
||||||
|
|
||||||
// Constraint engine
|
// Constraints and Policy engines
|
||||||
|
constraintsEngine *constraints.Engine
|
||||||
// Policy engines
|
policyEngine *policy.Engine
|
||||||
policyEngine *policy.Engine
|
|
||||||
|
|
||||||
adminMutex sync.RWMutex
|
adminMutex sync.RWMutex
|
||||||
|
|
||||||
|
@ -375,6 +377,11 @@ func (a *Authority) init() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// If not defined with an option, add intermediates to the the list
|
||||||
|
// of used for constraints purposes.
|
||||||
|
if len(a.intermediateX509Certs) == 0 {
|
||||||
|
a.intermediateX509Certs = append(a.intermediateX509Certs, options.CertificateChain...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a.x509CAService, err = cas.New(ctx, options)
|
a.x509CAService, err = cas.New(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -612,6 +619,21 @@ func (a *Authority) init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load X509 constraints engine.
|
||||||
|
//
|
||||||
|
// This is currently only available in CA mode.
|
||||||
|
if size := len(a.intermediateX509Certs); size > 0 {
|
||||||
|
last := a.intermediateX509Certs[size-1]
|
||||||
|
constraintCerts := make([]*x509.Certificate, 0, size+1)
|
||||||
|
constraintCerts = append(constraintCerts, a.intermediateX509Certs...)
|
||||||
|
for _, root := range a.rootX509Certs {
|
||||||
|
if bytes.Equal(last.RawIssuer, root.RawSubject) && bytes.Equal(last.AuthorityKeyId, root.SubjectKeyId) {
|
||||||
|
constraintCerts = append(constraintCerts, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.constraintsEngine = constraints.New(constraintCerts...)
|
||||||
|
}
|
||||||
|
|
||||||
// Load x509 and SSH Policy Engines
|
// Load x509 and SSH Policy Engines
|
||||||
if err := a.reloadPolicyEngines(ctx); err != nil {
|
if err := a.reloadPolicyEngines(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -151,16 +151,23 @@ func WithKeyManager(k kms.KeyManager) Option {
|
||||||
|
|
||||||
// WithX509Signer defines the signer used to sign X509 certificates.
|
// WithX509Signer defines the signer used to sign X509 certificates.
|
||||||
func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option {
|
func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option {
|
||||||
|
return WithX509SignerChain([]*x509.Certificate{crt}, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithX509SignerChain defines the signer used to sign X509 certificates. This
|
||||||
|
// option is similar to WithX509Signer but it supports a chain of intermediates.
|
||||||
|
func WithX509SignerChain(issuerChain []*x509.Certificate, s crypto.Signer) Option {
|
||||||
return func(a *Authority) error {
|
return func(a *Authority) error {
|
||||||
srv, err := cas.New(context.Background(), casapi.Options{
|
srv, err := cas.New(context.Background(), casapi.Options{
|
||||||
Type: casapi.SoftCAS,
|
Type: casapi.SoftCAS,
|
||||||
Signer: s,
|
Signer: s,
|
||||||
CertificateChain: []*x509.Certificate{crt},
|
CertificateChain: issuerChain,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.x509CAService = srv
|
a.x509CAService = srv
|
||||||
|
a.intermediateX509Certs = append(a.intermediateX509Certs, issuerChain...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,6 +240,25 @@ func WithX509FederatedCerts(certs ...*x509.Certificate) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithX509RootCerts is an option that allows to define the list of intermediate
|
||||||
|
// certificates that the CA will be using. This option will replace any
|
||||||
|
// intermediate certificate defined before.
|
||||||
|
//
|
||||||
|
// Note that these certificates will not be bundled with the certificates signed
|
||||||
|
// by the CA, the CAS service will take care of that, although they should
|
||||||
|
// match, this is not guaranteed. These certificates will be mainly used for
|
||||||
|
// constraint purposes.
|
||||||
|
//
|
||||||
|
// This option should only used on specific configurations, for example when
|
||||||
|
// WithX509SignerFunc is used, as we don't know the list of intermediates on
|
||||||
|
// advance.
|
||||||
|
func WithX509IntermediateCerts(intermediateCerts ...*x509.Certificate) Option {
|
||||||
|
return func(a *Authority) error {
|
||||||
|
a.intermediateX509Certs = intermediateCerts
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithX509RootBundle is an option that allows to define the list of root
|
// WithX509RootBundle is an option that allows to define the list of root
|
||||||
// certificates. This option will replace any root certificate defined before.
|
// certificates. This option will replace any root certificate defined before.
|
||||||
func WithX509RootBundle(pemCerts []byte) Option {
|
func WithX509RootBundle(pemCerts []byte) Option {
|
||||||
|
|
|
@ -256,6 +256,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
|
||||||
// isAllowedToSignX509Certificate checks if the Authority is allowed
|
// isAllowedToSignX509Certificate checks if the Authority is allowed
|
||||||
// to sign the X.509 certificate.
|
// to sign the X.509 certificate.
|
||||||
func (a *Authority) isAllowedToSignX509Certificate(cert *x509.Certificate) error {
|
func (a *Authority) isAllowedToSignX509Certificate(cert *x509.Certificate) error {
|
||||||
|
if err := a.constraintsEngine.ValidateCertificate(cert); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return a.policyEngine.IsX509CertificateAllowed(cert)
|
return a.policyEngine.IsX509CertificateAllowed(cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,6 +354,12 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
|
||||||
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
|
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the certificate is allowed to be renewed, policies or
|
||||||
|
// constraints might change over time.
|
||||||
|
if err := a.isAllowedToSignX509Certificate(newCert); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := a.x509CAService.RenewCertificate(&casapi.RenewCertificateRequest{
|
resp, err := a.x509CAService.RenewCertificate(&casapi.RenewCertificateRequest{
|
||||||
Template: newCert,
|
Template: newCert,
|
||||||
Lifetime: lifetime,
|
Lifetime: lifetime,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/keyutil"
|
"go.step.sm/crypto/keyutil"
|
||||||
|
"go.step.sm/crypto/minica"
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
"go.step.sm/crypto/x509util"
|
"go.step.sm/crypto/x509util"
|
||||||
|
|
||||||
|
@ -1589,3 +1590,86 @@ func TestAuthority_Revoke(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthority_constraints(t *testing.T) {
|
||||||
|
ca, err := minica.New(
|
||||||
|
minica.WithIntermediateTemplate(`{
|
||||||
|
"subject": {{ toJson .Subject }},
|
||||||
|
"keyUsage": ["certSign", "crlSign"],
|
||||||
|
"basicConstraints": {
|
||||||
|
"isCA": true,
|
||||||
|
"maxPathLen": 0
|
||||||
|
},
|
||||||
|
"nameConstraints": {
|
||||||
|
"critical": true,
|
||||||
|
"permittedDNSDomains": ["internal.example.org"],
|
||||||
|
"excludedDNSDomains": ["internal.example.com"],
|
||||||
|
"permittedIPRanges": ["192.168.1.0/24", "192.168.2.1/32"],
|
||||||
|
"excludedIPRanges": ["192.168.3.0/24", "192.168.4.0/28"],
|
||||||
|
"permittedEmailAddresses": ["root@example.org", "example.org", ".acme.org"],
|
||||||
|
"excludedEmailAddresses": ["root@example.com", "example.com", ".acme.com"],
|
||||||
|
"permittedURIDomains": ["uuid.example.org", ".acme.org"],
|
||||||
|
"excludedURIDomains": ["uuid.example.com", ".acme.com"]
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := NewEmbedded(WithX509RootCerts(ca.Root), WithX509Signer(ca.Intermediate, ca.Signer))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
signer, err := keyutil.GenerateDefaultSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sans []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok dns", []string{"internal.example.org", "host.internal.example.org"}, false},
|
||||||
|
{"ok ip", []string{"192.168.1.10", "192.168.2.1"}, false},
|
||||||
|
{"ok email", []string{"root@example.org", "info@example.org", "info@www.acme.org"}, false},
|
||||||
|
{"ok uri", []string{"https://uuid.example.org/b908d973-5167-4a62-abe3-6beda358d82a", "https://uuid.acme.org/1724aae1-1bb3-44fb-83c3-9a1a18df67c8"}, false},
|
||||||
|
{"fail permitted dns", []string{"internal.acme.org"}, true},
|
||||||
|
{"fail excluded dns", []string{"internal.example.com"}, true},
|
||||||
|
{"fail permitted ips", []string{"192.168.2.10"}, true},
|
||||||
|
{"fail excluded ips", []string{"192.168.3.1"}, true},
|
||||||
|
{"fail permitted emails", []string{"root@acme.org"}, true},
|
||||||
|
{"fail excluded emails", []string{"root@example.com"}, true},
|
||||||
|
{"fail permitted uris", []string{"https://acme.org/uuid/7848819c-9d0b-4e12-bbff-cd66079a3444"}, true},
|
||||||
|
{"fail excluded uris", []string{"https://uuid.example.com/d325eda7-6356-4d60-b8f6-3d64724afeb3"}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
csr, err := x509util.CreateCertificateRequest(tt.sans[0], tt.sans, signer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cert, err := ca.SignCSR(csr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := x509util.CreateTemplateData(tt.sans[0], tt.sans)
|
||||||
|
templateOption, err := provisioner.TemplateOptions(nil, data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = auth.Sign(csr, provisioner.SignOptions{}, templateOption)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Authority.Sign() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = auth.Renew(cert)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Authority.Renew() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue