Add initial support for `step ca init` with cloud cas.

Fixes smallstep/cli#363
pull/409/head
Mariano Cano 4 years ago
parent 5a1e44a399
commit 2b4b902975

@ -5,6 +5,7 @@ import (
"crypto/x509" "crypto/x509"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/kms"
) )
// Options represents the configuration options used to select and configure the // Options represents the configuration options used to select and configure the
@ -24,6 +25,18 @@ type Options struct {
// They are configured in ca.json crt and key properties. // They are configured in ca.json crt and key properties.
Issuer *x509.Certificate `json:"-"` Issuer *x509.Certificate `json:"-"`
Signer crypto.Signer `json:"-"` Signer crypto.Signer `json:"-"`
// IsCreator is set to true when we're creating a certificate authority. Is
// used to skip some validations when initializing a CertificateAuthority.
IsCreator bool `json:"-"`
// KeyManager is the KMS used to generate keys in SoftCAS.
KeyManager kms.KeyManager `json:"-"`
// Project and Location are parameters used in CloudCAS to create a new
// certificate authority.
Project string `json:"-"`
Location string `json:"-"`
} }
// Validate checks the fields in Options. // Validate checks the fields in Options.

@ -1,8 +1,53 @@
package apiv1 package apiv1
import ( import (
"crypto"
"crypto/x509" "crypto/x509"
"time" "time"
"github.com/smallstep/certificates/kms/apiv1"
)
// CertificateAuthorityType indicates the type of Certificate Authority to
// create.
type CertificateAuthorityType int
const (
// RootCA is the type used to create a self-signed certificate suitable for
// use as a root CA.
RootCA CertificateAuthorityType = iota + 1
// IntermediateCA is the type used to create a subordinated certificate that
// can be used to sign additional leaf certificates.
IntermediateCA
)
// SignatureAlgorithm used for cryptographic signing.
type SignatureAlgorithm int
const (
// Not specified.
UnspecifiedSignAlgorithm SignatureAlgorithm = iota
// RSASSA-PKCS1-v1_5 key and a SHA256 digest.
SHA256WithRSA
// RSASSA-PKCS1-v1_5 key and a SHA384 digest.
SHA384WithRSA
// RSASSA-PKCS1-v1_5 key and a SHA512 digest.
SHA512WithRSA
// RSASSA-PSS key with a SHA256 digest.
SHA256WithRSAPSS
// RSASSA-PSS key with a SHA384 digest.
SHA384WithRSAPSS
// RSASSA-PSS key with a SHA512 digest.
SHA512WithRSAPSS
// ECDSA on the NIST P-256 curve with a SHA256 digest.
ECDSAWithSHA256
// ECDSA on the NIST P-384 curve with a SHA384 digest.
ECDSAWithSHA384
// ECDSA on the NIST P-521 curve with a SHA512 digest.
ECDSAWithSHA512
// EdDSA on Curve25519 with a SHA512 digest.
PureEd25519
) )
// CreateCertificateRequest is the request used to sign a new certificate. // CreateCertificateRequest is the request used to sign a new certificate.
@ -58,3 +103,35 @@ type GetCertificateAuthorityRequest struct {
type GetCertificateAuthorityResponse struct { type GetCertificateAuthorityResponse struct {
RootCertificate *x509.Certificate RootCertificate *x509.Certificate
} }
// CreateCertificateAuthorityRequest ...
// This is a work in progress
type CreateCertificateAuthorityRequest struct {
Name string
Type CertificateAuthorityType
Template *x509.Certificate
Lifetime time.Duration
Backdate time.Duration
RequestID string
Project string
Location string
// Parent is the signer of the new CertificateAuthority.
Parent *CreateCertificateAuthorityResponse
// CreateKey defines the KMS CreateKeyRequest to use when creating a new
// CertificateAuthority. If CreateKey is nil, a default algorithm will be
// used.
CreateKey *apiv1.CreateKeyRequest
}
// CreateCertificateAuthorityResponse ...
// This is a work in progress
type CreateCertificateAuthorityResponse struct {
Name string
Certificate *x509.Certificate
CertificateChain []*x509.Certificate
PublicKey crypto.PublicKey
PrivateKey crypto.PrivateKey
Signer crypto.Signer
}

@ -18,6 +18,13 @@ type CertificateAuthorityGetter interface {
GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error) GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error)
} }
// CertificateAuthorityCreator is an interface implamented by a
// CertificateAuthorityService that has a method to create a new certificate
// authority.
type CertificateAuthorityCreator interface {
CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error)
}
// Type represents the CAS type used. // Type represents the CAS type used.
type Type string type Type string

@ -6,14 +6,16 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/cas/softcas"
// Enable default implementation
_ "github.com/smallstep/certificates/cas/softcas"
) )
// CertificateAuthorityService is the interface implemented by all the CAS. // CertificateAuthorityService is the interface implemented by all the CAS.
type CertificateAuthorityService = apiv1.CertificateAuthorityService type CertificateAuthorityService = apiv1.CertificateAuthorityService
// CertificateAuthorityCreator is the interface implemented by all CAS that can create a new authority.
type CertificateAuthorityCreator = apiv1.CertificateAuthorityCreator
// New creates a new CertificateAuthorityService using the given options.
func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService, error) { func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService, error) {
if err := opts.Validate(); err != nil { if err := opts.Validate(); err != nil {
return nil, err return nil, err
@ -26,7 +28,33 @@ func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService,
fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(t) fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(t)
if !ok { if !ok {
return nil, errors.Errorf("unsupported kms type '%s'", t) return nil, errors.Errorf("unsupported cas type '%s'", t)
} }
return fn(ctx, opts) return fn(ctx, opts)
} }
// NewCreator creates a new CertificateAuthorityCreator using the given options.
func NewCreator(ctx context.Context, opts apiv1.Options) (CertificateAuthorityCreator, error) {
t := apiv1.Type(strings.ToLower(opts.Type))
if t == apiv1.DefaultCAS {
t = apiv1.SoftCAS
}
if t == apiv1.SoftCAS {
return &softcas.SoftCAS{
KeyManager: opts.KeyManager,
}, nil
}
svc, err := New(ctx, opts)
if err != nil {
return nil, err
}
creator, ok := svc.(CertificateAuthorityCreator)
if !ok {
return nil, errors.Errorf("cas type '%s' does not implements CertificateAuthorityCreator", t)
}
return creator, nil
}

@ -8,8 +8,10 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
) )
@ -326,3 +328,45 @@ func findExtraExtension(cert *x509.Certificate, oid asn1.ObjectIdentifier) (pkix
} }
return pkix.Extension{}, false return pkix.Extension{}, false
} }
func createKeyVersionSpec(alg kmsapi.SignatureAlgorithm, bits int) (*pb.CertificateAuthority_KeyVersionSpec, error) {
switch alg {
case kmsapi.UnspecifiedSignAlgorithm, kmsapi.ECDSAWithSHA256:
return &pb.CertificateAuthority_KeyVersionSpec{
KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{
Algorithm: pb.CertificateAuthority_EC_P256_SHA256,
},
}, nil
case kmsapi.ECDSAWithSHA384:
return &pb.CertificateAuthority_KeyVersionSpec{
KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{
Algorithm: pb.CertificateAuthority_EC_P384_SHA384,
},
}, nil
case kmsapi.SHA256WithRSAPSS:
algo, err := getRSAPSSAlgorithm(bits)
if err != nil {
return nil, err
}
return &pb.CertificateAuthority_KeyVersionSpec{
KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{
Algorithm: algo,
},
}, nil
default:
return nil, fmt.Errorf("unknown or unsupported signature algorithm '%s'", bits)
}
}
func getRSAPSSAlgorithm(bits int) (pb.CertificateAuthority_SignHashAlgorithm, error) {
switch bits {
case 0, 3072:
return pb.CertificateAuthority_RSA_PSS_3072_SHA_256, nil
case 2048:
return pb.CertificateAuthority_RSA_PSS_2048_SHA_256, nil
case 4096:
return pb.CertificateAuthority_RSA_PSS_4096_SHA_256, nil
default:
return 0, fmt.Errorf("unsupported RSA-PSS key size '%d'", bits)
}
}

@ -6,6 +6,8 @@ import (
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"regexp"
"strings"
"time" "time"
privateca "cloud.google.com/go/security/privateca/apiv1beta1" privateca "cloud.google.com/go/security/privateca/apiv1beta1"
@ -24,12 +26,20 @@ func init() {
}) })
} }
// The actual regular expression that matches a certificate authority is:
// ^projects/[a-z][a-z0-9-]{4,28}[a-z0-9]/locations/[a-z0-9-]+/certificateAuthorities/[a-zA-Z0-9-_]+$
// But we will allow a more flexible one to fail if this changes.
var caRegexp = regexp.MustCompile("^projects/[^/]+/locations/[^/]+/certificateAuthorities/[^/]+$")
// CertificateAuthorityClient is the interface implemented by the Google CAS // CertificateAuthorityClient is the interface implemented by the Google CAS
// client. // client.
type CertificateAuthorityClient interface { type CertificateAuthorityClient interface {
CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error)
RevokeCertificate(ctx context.Context, req *pb.RevokeCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) RevokeCertificate(ctx context.Context, req *pb.RevokeCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error)
GetCertificateAuthority(ctx context.Context, req *pb.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*pb.CertificateAuthority, error) GetCertificateAuthority(ctx context.Context, req *pb.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*pb.CertificateAuthority, error)
CreateCertificateAuthority(ctx context.Context, req *pb.CreateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error)
FetchCertificateAuthorityCsr(ctx context.Context, req *pb.FetchCertificateAuthorityCsrRequest, opts ...gax.CallOption) (*pb.FetchCertificateAuthorityCsrResponse, error)
ActivateCertificateAuthority(ctx context.Context, req *pb.ActivateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error)
} }
// recocationCodeMap maps revocation reason codes from RFC 5280, to Google CAS // recocationCodeMap maps revocation reason codes from RFC 5280, to Google CAS
@ -51,6 +61,8 @@ var revocationCodeMap = map[int]pb.RevocationReason{
type CloudCAS struct { type CloudCAS struct {
client CertificateAuthorityClient client CertificateAuthorityClient
certificateAuthority string certificateAuthority string
project string
location string
} }
// newCertificateAuthorityClient creates the certificate authority client. This // newCertificateAuthorityClient creates the certificate authority client. This
@ -70,8 +82,29 @@ var newCertificateAuthorityClient = func(ctx context.Context, credentialsFile st
// New creates a new CertificateAuthorityService implementation using Google // New creates a new CertificateAuthorityService implementation using Google
// Cloud CAS. // Cloud CAS.
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
if opts.CertificateAuthority == "" { if opts.IsCreator {
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty") switch {
case opts.Project == "":
return nil, errors.New("cloudCAS 'project' cannot be empty")
case opts.Location == "":
return nil, errors.New("cloudCAS 'location' cannot be empty")
}
} else {
if opts.CertificateAuthority == "" {
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty")
}
if !caRegexp.MatchString(opts.CertificateAuthority) {
return nil, errors.New("cloudCAS 'certificateAuthority' is not valid certificate authority resource")
}
// Extract project and location from CertificateAuthority
if parts := strings.Split(opts.CertificateAuthority, "/"); len(parts) == 6 {
if opts.Project == "" {
opts.Project = parts[1]
}
if opts.Location == "" {
opts.Location = parts[3]
}
}
} }
client, err := newCertificateAuthorityClient(ctx, opts.CredentialsFile) client, err := newCertificateAuthorityClient(ctx, opts.CredentialsFile)
@ -82,6 +115,8 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
return &CloudCAS{ return &CloudCAS{
client: client, client: client,
certificateAuthority: opts.CertificateAuthority, certificateAuthority: opts.CertificateAuthority,
project: opts.Project,
location: opts.Location,
}, nil }, nil
} }
@ -101,6 +136,7 @@ func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq
Name: name, Name: name,
}) })
if err != nil { if err != nil {
println(name)
return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed") return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed")
} }
if len(resp.PemCaCertificates) == 0 { if len(resp.PemCaCertificates) == 0 {
@ -160,7 +196,7 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.
}, nil }, nil
} }
// RevokeCertificate a certificate using Google Cloud CAS. // RevokeCertificate revokes a certificate using Google Cloud CAS.
func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
reason, ok := revocationCodeMap[req.ReasonCode] reason, ok := revocationCodeMap[req.ReasonCode]
switch { switch {
@ -203,6 +239,140 @@ func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv
}, nil }, nil
} }
// CreateCertificateAuthority creates a new root or intermediate certificate
// using Google Cloud CAS.
func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) {
switch {
case c.project == "":
return nil, errors.New("cloudCAS `project` cannot be empty")
case c.location == "":
return nil, errors.New("cloudCAS `location` cannot be empty")
case req.Template == nil:
return nil, errors.New("createCertificateAuthorityRequest `template` cannot be nil")
case req.Lifetime == 0:
return nil, errors.New("createCertificateAuthorityRequest `lifetime` cannot be 0")
case req.Type == apiv1.IntermediateCA && req.Parent == nil:
return nil, errors.New("createCertificateAuthorityRequest `parent` cannot be nil")
case req.Type == apiv1.IntermediateCA && req.Parent.Name == "":
return nil, errors.New("createCertificateAuthorityRequest `parent.name` cannot be empty")
}
// Select key and signature algorithm to use
var err error
var keySpec *pb.CertificateAuthority_KeyVersionSpec
if req.CreateKey == nil {
if keySpec, err = createKeyVersionSpec(0, 0); err != nil {
return nil, errors.Wrap(err, "createCertificateAuthorityRequest `createKey` is not valid")
}
} else {
if keySpec, err = createKeyVersionSpec(req.CreateKey.SignatureAlgorithm, req.CreateKey.Bits); err != nil {
return nil, errors.Wrap(err, "createCertificateAuthorityRequest `createKey` is not valid")
}
}
// Normalize or generate id.
certificateAuthorityID := normalizeCertificateAuthorityName(req.Name)
if certificateAuthorityID == "" {
id, err := createCertificateID()
if err != nil {
return nil, err
}
certificateAuthorityID = id
}
// Add CertificateAuthority extension
casExtension, err := apiv1.CreateCertificateAuthorityExtension(apiv1.CloudCAS, certificateAuthorityID)
if err != nil {
return nil, err
}
req.Template.ExtraExtensions = append(req.Template.ExtraExtensions, casExtension)
// Prepare CreateCertificateAuthorityRequest
pbReq := &pb.CreateCertificateAuthorityRequest{
Parent: "projects/" + c.project + "/locations/" + c.location,
CertificateAuthorityId: certificateAuthorityID,
RequestId: req.RequestID,
CertificateAuthority: &pb.CertificateAuthority{
Type: pb.CertificateAuthority_TYPE_UNSPECIFIED,
Tier: pb.CertificateAuthority_ENTERPRISE,
Config: &pb.CertificateConfig{
SubjectConfig: &pb.CertificateConfig_SubjectConfig{
Subject: createSubject(req.Template),
CommonName: req.Template.Subject.CommonName,
},
ReusableConfig: createReusableConfig(req.Template),
},
Lifetime: durationpb.New(req.Lifetime),
KeySpec: keySpec,
IssuingOptions: &pb.CertificateAuthority_IssuingOptions{
IncludeCaCertUrl: true,
IncludeCrlAccessUrl: true,
},
Labels: map[string]string{},
},
}
switch req.Type {
case apiv1.RootCA:
pbReq.CertificateAuthority.Type = pb.CertificateAuthority_SELF_SIGNED
case apiv1.IntermediateCA:
pbReq.CertificateAuthority.Type = pb.CertificateAuthority_SUBORDINATE
default:
return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type)
}
// Create certificate authority.
ctx, cancel := defaultContext()
defer cancel()
resp, err := c.client.CreateCertificateAuthority(ctx, pbReq)
if err != nil {
return nil, errors.Wrap(err, "cloudCAS CreateCertificateAuthority failed")
}
// Wait for the long-running operation.
ctx, cancel = defaultInitiatorContext()
defer cancel()
ca, err := resp.Wait(ctx)
if err != nil {
return nil, errors.Wrap(err, "cloudCAS CreateCertificateAuthority failed")
}
// Sign Intermediate CAs with the parent.
if req.Type == apiv1.IntermediateCA {
ca, err = c.signIntermediateCA(ca.Name, req)
if err != nil {
return nil, err
}
}
if len(ca.PemCaCertificates) == 0 {
return nil, errors.New("cloudCAS CreateCertificateAuthority failed: PemCaCertificates is empty")
}
cert, err := parseCertificate(ca.PemCaCertificates[0])
if err != nil {
return nil, err
}
var chain []*x509.Certificate
if pemChain := ca.PemCaCertificates[1:]; len(pemChain) > 0 {
chain = make([]*x509.Certificate, len(pemChain))
for i, s := range pemChain {
if chain[i], err = parseCertificate(s); err != nil {
return nil, err
}
}
}
return &apiv1.CreateCertificateAuthorityResponse{
Name: ca.Name,
Certificate: cert,
CertificateChain: chain,
}, nil
}
func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Duration, requestID string) (*x509.Certificate, []*x509.Certificate, error) { func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Duration, requestID string) (*x509.Certificate, []*x509.Certificate, error) {
// Removes the CAS extension if it exists. // Removes the CAS extension if it exists.
apiv1.RemoveCertificateAuthorityExtension(tpl) apiv1.RemoveCertificateAuthorityExtension(tpl)
@ -245,10 +415,82 @@ func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Durati
return getCertificateAndChain(cert) return getCertificateAndChain(cert)
} }
func (c *CloudCAS) signIntermediateCA(name string, req *apiv1.CreateCertificateAuthorityRequest) (*pb.CertificateAuthority, error) {
id, err := createCertificateID()
if err != nil {
return nil, err
}
// Fetch intermediate CSR
ctx, cancel := defaultInitiatorContext()
defer cancel()
csr, err := c.client.FetchCertificateAuthorityCsr(ctx, &pb.FetchCertificateAuthorityCsrRequest{
Name: name,
})
if err != nil {
return nil, errors.Wrap(err, "cloudCAS FetchCertificateAuthorityCsr failed")
}
// Sign the CSR with the ca.
ctx, cancel = defaultInitiatorContext()
defer cancel()
sign, err := c.client.CreateCertificate(ctx, &pb.CreateCertificateRequest{
Parent: req.Parent.Name,
CertificateId: id,
Certificate: &pb.Certificate{
// Name: "projects/" + c.project + "/locations/" + c.location + "/certificates/" + id,
CertificateConfig: &pb.Certificate_PemCsr{
PemCsr: csr.PemCsr,
},
Lifetime: durationpb.New(req.Lifetime),
Labels: map[string]string{},
},
RequestId: req.RequestID,
})
if err != nil {
return nil, errors.Wrap(err, "cloudCAS CreateCertificate failed")
}
ctx, cancel = defaultInitiatorContext()
defer cancel()
resp, err := c.client.ActivateCertificateAuthority(ctx, &pb.ActivateCertificateAuthorityRequest{
Name: name,
PemCaCertificate: sign.PemCertificate,
SubordinateConfig: &pb.SubordinateConfig{
SubordinateConfig: &pb.SubordinateConfig_PemIssuerChain{
PemIssuerChain: &pb.SubordinateConfig_SubordinateConfigChain{
PemCertificates: sign.PemCertificateChain,
},
},
},
RequestId: req.RequestID,
})
if err != nil {
return nil, errors.Wrap(err, "cloudCAS ActivateCertificateAuthority1 failed")
}
// Wait for the long-running operation.
ctx, cancel = defaultInitiatorContext()
defer cancel()
ca, err := resp.Wait(ctx)
if err != nil {
return nil, errors.Wrap(err, "cloudCAS ActivateCertificateAuthority failed")
}
return ca, nil
}
func defaultContext() (context.Context, context.CancelFunc) { func defaultContext() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), 15*time.Second) return context.WithTimeout(context.Background(), 15*time.Second)
} }
func defaultInitiatorContext() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), 60*time.Second)
}
func createCertificateID() (string, error) { func createCertificateID() (string, error) {
id, err := uuid.NewRandomFromReader(rand.Reader) id, err := uuid.NewRandomFromReader(rand.Reader)
if err != nil { if err != nil {
@ -287,3 +529,23 @@ func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.
return cert, chain, nil return cert, chain, nil
} }
// Normalize a certificate authority name to comply with [a-zA-Z0-9-_].
func normalizeCertificateAuthorityName(name string) string {
return strings.Map(func(r rune) rune {
switch {
case r >= 'a' && r <= 'z':
return r
case r >= 'A' && r <= 'Z':
return r
case r >= '0' && r <= '9':
return r
case r == '-':
return r
case r == '_':
return r
default:
return '-'
}
}, name)
}

@ -5,6 +5,7 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"io" "io"
"os" "os"
@ -673,3 +674,62 @@ func Test_getCertificateAndChain(t *testing.T) {
}) })
} }
} }
func TestCloudCAS(t *testing.T) {
cas, err := New(context.Background(), apiv1.Options{
Type: "cloudCAS",
CertificateAuthority: "projects/smallstep-cas-test/locations/us-west1",
CredentialsFile: "/Users/mariano/smallstep-cas-test-8a068f3e4540.json",
})
if err != nil {
t.Fatal(err)
}
// resp, err := cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
// Type: apiv1.RootCA,
// Template: &x509.Certificate{
// Subject: pkix.Name{
// CommonName: "Test Mariano Root CA",
// },
// BasicConstraintsValid: true,
// IsCA: true,
// MaxPathLen: 1,
// MaxPathLenZero: false,
// KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
// },
// Lifetime: time.Duration(30 * 24 * time.Hour),
// Project: "smallstep-cas-test",
// Location: "us-west1",
// })
// if err != nil {
// t.Fatal(err)
// }
// debug(resp)
resp := &apiv1.CreateCertificateAuthorityResponse{
Name: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/9a593da4-61af-4426-a2f8-0650373b9c8e",
}
resp, err = cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Type: apiv1.IntermediateCA,
Template: &x509.Certificate{
Subject: pkix.Name{
Country: []string{"US"},
CommonName: "Test Mariano Intermediate CA",
},
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
MaxPathLenZero: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
},
Lifetime: time.Duration(24 * time.Hour),
Parent: resp,
Project: "smallstep-cas-test",
Location: "us-west1",
})
if err != nil {
t.Fatal(err)
}
// debug(resp)
t.Error("foo")
}

@ -4,10 +4,12 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"errors"
"time" "time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -24,22 +26,26 @@ var now = func() time.Time {
// SoftCAS implements a Certificate Authority Service using Golang or KMS // SoftCAS implements a Certificate Authority Service using Golang or KMS
// crypto. This is the default CAS used in step-ca. // crypto. This is the default CAS used in step-ca.
type SoftCAS struct { type SoftCAS struct {
Issuer *x509.Certificate Issuer *x509.Certificate
Signer crypto.Signer Signer crypto.Signer
KeyManager kms.KeyManager
} }
// New creates a new CertificateAuthorityService implementation using Golang or KMS // New creates a new CertificateAuthorityService implementation using Golang or KMS
// crypto. // crypto.
func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) { func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) {
switch { if !opts.IsCreator {
case opts.Issuer == nil: switch {
return nil, errors.New("softCAS 'issuer' cannot be nil") case opts.Issuer == nil:
case opts.Signer == nil: return nil, errors.New("softCAS 'issuer' cannot be nil")
return nil, errors.New("softCAS 'signer' cannot be nil") case opts.Signer == nil:
return nil, errors.New("softCAS 'signer' cannot be nil")
}
} }
return &SoftCAS{ return &SoftCAS{
Issuer: opts.Issuer, Issuer: opts.Issuer,
Signer: opts.Signer, Signer: opts.Signer,
KeyManager: opts.KeyManager,
}, nil }, nil
} }
@ -113,3 +119,102 @@ func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1
}, },
}, nil }, nil
} }
// CreateCertificateAuthority creates a root or an intermediate certificate.
func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) {
switch {
case req.Template == nil:
return nil, errors.New("createCertificateAuthorityRequest `template` cannot be nil")
case req.Lifetime == 0:
return nil, errors.New("createCertificateAuthorityRequest `lifetime` cannot be 0")
case req.Type == apiv1.IntermediateCA && req.Parent == nil:
return nil, errors.New("createCertificateAuthorityRequest `parent` cannot be nil")
case req.Type == apiv1.IntermediateCA && req.Parent.Certificate == nil:
return nil, errors.New("createCertificateAuthorityRequest `parent.template` cannot be nil")
case req.Type == apiv1.IntermediateCA && req.Parent.Signer == nil:
return nil, errors.New("createCertificateAuthorityRequest `parent.signer` cannot be nil")
}
key, err := c.createKey(req.CreateKey)
if err != nil {
return nil, err
}
signer, err := c.createSigner(&key.CreateSignerRequest)
if err != nil {
return nil, err
}
t := now()
if req.Template.NotBefore.IsZero() {
req.Template.NotBefore = t.Add(-1 * req.Backdate)
}
if req.Template.NotAfter.IsZero() {
req.Template.NotAfter = t.Add(req.Lifetime)
}
var cert *x509.Certificate
switch req.Type {
case apiv1.RootCA:
cert, err = x509util.CreateCertificate(req.Template, req.Template, signer.Public(), signer)
if err != nil {
return nil, err
}
case apiv1.IntermediateCA:
cert, err = x509util.CreateCertificate(req.Template, req.Parent.Certificate, signer.Public(), req.Parent.Signer)
if err != nil {
return nil, err
}
default:
return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type)
}
// Add the parent
var chain []*x509.Certificate
if req.Parent != nil {
chain = append(chain, req.Parent.Certificate)
for _, crt := range req.Parent.CertificateChain {
chain = append(chain, crt)
}
}
return &apiv1.CreateCertificateAuthorityResponse{
Name: cert.Subject.CommonName,
Certificate: cert,
CertificateChain: chain,
PublicKey: key.PublicKey,
PrivateKey: key.PrivateKey,
Signer: signer,
}, nil
}
// initializeKeyManager initiazes the default key manager if was not given.
func (c *SoftCAS) initializeKeyManager() (err error) {
if c.KeyManager == nil {
c.KeyManager, err = kms.New(context.Background(), kmsapi.Options{
Type: string(kmsapi.DefaultKMS),
})
}
return
}
// createKey uses the configured kms to create a key.
func (c *SoftCAS) createKey(req *kmsapi.CreateKeyRequest) (*kmsapi.CreateKeyResponse, error) {
if err := c.initializeKeyManager(); err != nil {
return nil, err
}
if req == nil {
req = &kmsapi.CreateKeyRequest{
SignatureAlgorithm: kmsapi.ECDSAWithSHA256,
}
}
return c.KeyManager.CreateKey(req)
}
// createSigner uses the configured kms to create a singer
func (c *SoftCAS) createSigner(req *kmsapi.CreateSignerRequest) (crypto.Signer, error) {
if err := c.initializeKeyManager(); err != nil {
return nil, err
}
return c.KeyManager.CreateSigner(req)
}

@ -11,6 +11,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/pki" "github.com/smallstep/certificates/pki"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.step.sm/cli-utils/command" "go.step.sm/cli-utils/command"
@ -162,7 +163,10 @@ func onboardAction(ctx *cli.Context) error {
} }
func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) {
p, err := pki.New() p, err := pki.New(apiv1.Options{
Type: apiv1.SoftCAS,
IsCreator: true,
})
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -171,13 +175,13 @@ func onboardPKI(config onboardingConfiguration) (*authority.Config, string, erro
p.SetDNSNames([]string{config.DNS}) p.SetDNSNames([]string{config.DNS})
ui.Println("Generating root certificate...") ui.Println("Generating root certificate...")
rootCrt, rootKey, err := p.GenerateRootCertificate(config.Name+" Root CA", config.password) root, err := p.GenerateRootCertificate(config.Name, config.Name, config.Name, config.password)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
ui.Println("Generating intermediate certificate...") ui.Println("Generating intermediate certificate...")
err = p.GenerateIntermediateCertificate(config.Name+" Intermediate CA", rootCrt, rootKey, config.password) err = p.GenerateIntermediateCertificate(config.Name, config.Name, config.Name, root, config.password)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }

@ -20,19 +20,15 @@ require (
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.2
go.step.sm/cli-utils v0.1.0 go.step.sm/cli-utils v0.1.0
go.step.sm/crypto v0.6.1 go.step.sm/crypto v0.6.1
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/net v0.0.0-20201021035429-f5854403a974
google.golang.org/api v0.31.0 google.golang.org/api v0.31.0
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d
google.golang.org/grpc v1.32.0 google.golang.org/grpc v1.32.0
google.golang.org/protobuf v1.25.0 google.golang.org/protobuf v1.25.0
gopkg.in/square/go-jose.v2 v2.5.1 gopkg.in/square/go-jose.v2 v2.5.1
// cloud.google.com/go/security/privateca/apiv1alpha1 v0.0.0
// google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 v0.0.0
) )
// replace github.com/smallstep/cli => ../cli
// replace github.com/smallstep/nosql => ../nosql // replace github.com/smallstep/nosql => ../nosql
// replace go.step.sm/crypto => ../crypto // replace go.step.sm/crypto => ../crypto
// replace cloud.google.com/go/security/privateca/apiv1alpha1 => ./pkg/cloud.google.com/go/security/privateca/apiv1alpha1
// replace google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 => ./pkg/google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1

@ -293,6 +293,8 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -353,6 +355,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -401,6 +405,8 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E= golang.org/x/sys v0.0.0-20200828194041-157a740278f4 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

@ -5,6 +5,7 @@ import (
"crypto" "crypto"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
@ -31,7 +32,6 @@ 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/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -148,6 +148,8 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
// PKI represents the Public Key Infrastructure used by a certificate authority. // PKI represents the Public Key Infrastructure used by a certificate authority.
type PKI struct { type PKI struct {
casOptions apiv1.Options
caCreator apiv1.CertificateAuthorityCreator
root, rootKey, rootFingerprint string root, rootKey, rootFingerprint string
intermediate, intermediateKey string intermediate, intermediateKey string
sshHostPubKey, sshHostKey string sshHostPubKey, sshHostKey string
@ -160,11 +162,15 @@ type PKI struct {
dnsNames []string dnsNames []string
caURL string caURL string
enableSSH bool enableSSH bool
authorityOptions *apiv1.Options
} }
// New creates a new PKI configuration. // New creates a new PKI configuration.
func New() (*PKI, error) { func New(opts apiv1.Options) (*PKI, error) {
caCreator, err := cas.NewCreator(context.Background(), opts)
if err != nil {
return nil, err
}
public := GetPublicPath() public := GetPublicPath()
private := GetSecretsPath() private := GetSecretsPath()
config := GetConfigPath() config := GetConfigPath()
@ -185,8 +191,9 @@ func New() (*PKI, error) {
return s, errors.Wrapf(err, "error getting absolute path for %s", name) return s, errors.Wrapf(err, "error getting absolute path for %s", name)
} }
var err error
p := &PKI{ p := &PKI{
casOptions: opts,
caCreator: caCreator,
provisioner: "step-cli", provisioner: "step-cli",
address: "127.0.0.1:9000", address: "127.0.0.1:9000",
dnsNames: []string{"127.0.0.1"}, dnsNames: []string{"127.0.0.1"},
@ -237,12 +244,6 @@ func (p *PKI) GetRootFingerprint() string {
return p.rootFingerprint return p.rootFingerprint
} }
// SetAuthorityOptions sets the authority options object, these options are used
// to configure a registration authority.
func (p *PKI) SetAuthorityOptions(opts *apiv1.Options) {
p.authorityOptions = opts
}
// SetProvisioner sets the provisioner name of the OTT keys. // SetProvisioner sets the provisioner name of the OTT keys.
func (p *PKI) SetProvisioner(s string) { func (p *PKI) SetProvisioner(s string) {
p.provisioner = s p.provisioner = s
@ -275,37 +276,65 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
return nil return nil
} }
// GenerateRootCertificate generates a root certificate with the given name. // GenerateRootCertificate generates a root certificate with the given name
func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) { // and using the default key type.
signer, err := generateDefaultKey() func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) {
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Name: resource + "-Root-CA",
Type: apiv1.RootCA,
Lifetime: 10 * 365 * 24 * time.Hour,
CreateKey: nil, // use default
Template: &x509.Certificate{
Subject: pkix.Name{
CommonName: name + " Root CA",
Organization: []string{org},
},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1,
MaxPathLenZero: false,
},
})
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
cr, err := x509util.CreateCertificateRequest(name, []string{}, signer) // PrivateKey will only be set if we have access to it (SoftCAS).
if err != nil { if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
return nil, nil, err return nil, err
} }
data := x509util.CreateTemplateData(name, []string{}) return resp, nil
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) }
if err != nil {
return nil, nil, err
}
template := cert.GetCertificate() // GenerateIntermediateCertificate generates an intermediate certificate with
template.NotBefore = time.Now() // the given name and using the default key type.
template.NotAfter = template.NotBefore.AddDate(10, 0, 0) func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error {
rootCrt, err := x509util.CreateCertificate(template, template, signer.Public(), signer) resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Name: resource + "-Intermediate-CA",
Type: apiv1.IntermediateCA,
Lifetime: 10 * 365 * 24 * time.Hour,
CreateKey: nil, // use default
Template: &x509.Certificate{
Subject: pkix.Name{
CommonName: name + " Intermediate CA",
Organization: []string{org},
},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
MaxPathLenZero: true,
},
Parent: parent,
})
if err != nil { if err != nil {
return nil, nil, err return err
}
if err := p.WriteRootCertificate(rootCrt, signer, pass); err != nil {
return nil, nil, err
} }
return rootCrt, signer, nil p.casOptions.CertificateAuthority = resp.Name
return p.WriteIntermediateCertificate(resp.Certificate, resp.PrivateKey, pass)
} }
// WriteRootCertificate writes to disk the given certificate and key. // WriteRootCertificate writes to disk the given certificate and key.
@ -330,21 +359,33 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
return nil return nil
} }
// GetCertificateAuthority attempts to load the certificate authority from the // WriteIntermediateCertificate writes to disk the given certificate and key.
// RA. func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{}, pass []byte) error {
func (p *PKI) GetCertificateAuthority() error { if err := fileutil.WriteFile(p.intermediate, pem.EncodeToMemory(&pem.Block{
ca, err := cas.New(context.Background(), *p.authorityOptions) Type: "CERTIFICATE",
if err != nil { Bytes: crt.Raw,
}), 0600); err != nil {
return err return err
} }
if key != nil {
_, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600))
if err != nil {
return err
}
}
return nil
}
srv, ok := ca.(apiv1.CertificateAuthorityGetter) // GetCertificateAuthority attempts to load the certificate authority from the
// RA.
func (p *PKI) GetCertificateAuthority() error {
srv, ok := p.caCreator.(apiv1.CertificateAuthorityGetter)
if !ok { if !ok {
return nil return nil
} }
resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{ resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{
Name: p.authorityOptions.CertificateAuthority, Name: p.casOptions.CertificateAuthority,
}) })
if err != nil { if err != nil {
return err return err
@ -361,51 +402,6 @@ func (p *PKI) GetCertificateAuthority() error {
return nil return nil
} }
// GenerateIntermediateCertificate generates an intermediate certificate with
// the given name.
func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
key, err := generateDefaultKey()
if err != nil {
return err
}
cr, err := x509util.CreateCertificateRequest(name, []string{}, key)
if err != nil {
return err
}
data := x509util.CreateTemplateData(name, []string{})
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultIntermediateTemplate, data))
if err != nil {
return err
}
template := cert.GetCertificate()
template.NotBefore = rootCrt.NotBefore
template.NotAfter = rootCrt.NotAfter
intermediateCrt, err := x509util.CreateCertificate(template, rootCrt, key.Public(), rootKey.(crypto.Signer))
if err != nil {
return err
}
return p.WriteIntermediateCertificate(intermediateCrt, key, pass)
}
// WriteIntermediateCertificate writes to disk the given certificate and key.
func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{}, pass []byte) error {
if err := fileutil.WriteFile(p.intermediate, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crt.Raw,
}), 0600); err != nil {
return err
}
_, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600))
if err != nil {
return err
}
return nil
}
// GenerateSSHSigningKeys generates and encrypts a private key used for signing // GenerateSSHSigningKeys generates and encrypts a private key used for signing
// SSH user certificates and a private key used for signing host certificates. // SSH user certificates and a private key used for signing host certificates.
func (p *PKI) GenerateSSHSigningKeys(password []byte) error { func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
@ -457,7 +453,7 @@ func (p *PKI) TellPKI() {
func (p *PKI) tellPKI() { func (p *PKI) tellPKI() {
ui.Println() ui.Println()
if p.authorityOptions == nil || p.authorityOptions.Is(apiv1.SoftCAS) { if p.casOptions.Is(apiv1.SoftCAS) {
ui.PrintSelected("Root certificate", p.root) ui.PrintSelected("Root certificate", p.root)
ui.PrintSelected("Root private key", p.rootKey) ui.PrintSelected("Root private key", p.rootKey)
ui.PrintSelected("Root fingerprint", p.rootFingerprint) ui.PrintSelected("Root fingerprint", p.rootFingerprint)
@ -522,6 +518,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
EncryptedKey: key, EncryptedKey: key,
} }
var authorityOptions *apiv1.Options
if !p.casOptions.Is(apiv1.SoftCAS) {
authorityOptions = &p.casOptions
}
config := &authority.Config{ config := &authority.Config{
Root: []string{p.root}, Root: []string{p.root},
FederatedRoots: []string{}, FederatedRoots: []string{},
@ -535,7 +536,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
DataSource: GetDBPath(), DataSource: GetDBPath(),
}, },
AuthorityConfig: &authority.AuthConfig{ AuthorityConfig: &authority.AuthConfig{
Options: p.authorityOptions, Options: authorityOptions,
DisableIssuedAtCheck: false, DisableIssuedAtCheck: false,
Provisioners: provisioner.List{prov}, Provisioners: provisioner.List{prov},
}, },
@ -642,7 +643,7 @@ func (p *PKI) Save(opt ...Option) error {
ui.PrintSelected("Default configuration", p.defaults) ui.PrintSelected("Default configuration", p.defaults)
ui.PrintSelected("Certificate Authority configuration", p.config) ui.PrintSelected("Certificate Authority configuration", p.config)
ui.Println() ui.Println()
if p.authorityOptions == nil || p.authorityOptions.Is(apiv1.SoftCAS) { if p.casOptions.Is(apiv1.SoftCAS) {
ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.") ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.")
} else { } else {
ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.") ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.")

Loading…
Cancel
Save