Add initial support for step ca init
with cloud cas.
Fixes smallstep/cli#363
This commit is contained in:
parent
3e6137110b
commit
5deca85b14
12 changed files with 720 additions and 117 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/x509"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
)
|
||||
|
||||
// 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.
|
||||
Issuer *x509.Certificate `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.
|
||||
|
|
|
@ -1,8 +1,53 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"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.
|
||||
|
@ -58,3 +103,35 @@ type GetCertificateAuthorityRequest struct {
|
|||
type GetCertificateAuthorityResponse struct {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 Type string
|
||||
|
||||
|
|
36
cas/cas.go
36
cas/cas.go
|
@ -6,14 +6,16 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
|
||||
// Enable default implementation
|
||||
_ "github.com/smallstep/certificates/cas/softcas"
|
||||
"github.com/smallstep/certificates/cas/softcas"
|
||||
)
|
||||
|
||||
// CertificateAuthorityService is the interface implemented by all the CAS.
|
||||
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) {
|
||||
if err := opts.Validate(); err != nil {
|
||||
return nil, err
|
||||
|
@ -26,7 +28,33 @@ func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService,
|
|||
|
||||
fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(t)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("unsupported kms type '%s'", t)
|
||||
return nil, errors.Errorf("unsupported cas type '%s'", t)
|
||||
}
|
||||
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"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
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
|
||||
// client.
|
||||
type CertificateAuthorityClient interface {
|
||||
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)
|
||||
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
|
||||
|
@ -51,6 +61,8 @@ var revocationCodeMap = map[int]pb.RevocationReason{
|
|||
type CloudCAS struct {
|
||||
client CertificateAuthorityClient
|
||||
certificateAuthority string
|
||||
project string
|
||||
location string
|
||||
}
|
||||
|
||||
// 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
|
||||
// Cloud CAS.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
|
||||
if opts.CertificateAuthority == "" {
|
||||
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty")
|
||||
if opts.IsCreator {
|
||||
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)
|
||||
|
@ -82,6 +115,8 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
|
|||
return &CloudCAS{
|
||||
client: client,
|
||||
certificateAuthority: opts.CertificateAuthority,
|
||||
project: opts.Project,
|
||||
location: opts.Location,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -101,6 +136,7 @@ func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq
|
|||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
println(name)
|
||||
return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed")
|
||||
}
|
||||
if len(resp.PemCaCertificates) == 0 {
|
||||
|
@ -160,7 +196,7 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.
|
|||
}, 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) {
|
||||
reason, ok := revocationCodeMap[req.ReasonCode]
|
||||
switch {
|
||||
|
@ -203,6 +239,140 @@ func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv
|
|||
}, 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) {
|
||||
// Removes the CAS extension if it exists.
|
||||
apiv1.RemoveCertificateAuthorityExtension(tpl)
|
||||
|
@ -245,10 +415,82 @@ func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Durati
|
|||
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) {
|
||||
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) {
|
||||
id, err := uuid.NewRandomFromReader(rand.Reader)
|
||||
if err != nil {
|
||||
|
@ -287,3 +529,23 @@ func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.
|
|||
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"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"io"
|
||||
"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"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -24,22 +26,26 @@ var now = func() time.Time {
|
|||
// SoftCAS implements a Certificate Authority Service using Golang or KMS
|
||||
// crypto. This is the default CAS used in step-ca.
|
||||
type SoftCAS struct {
|
||||
Issuer *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
Issuer *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
KeyManager kms.KeyManager
|
||||
}
|
||||
|
||||
// New creates a new CertificateAuthorityService implementation using Golang or KMS
|
||||
// crypto.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) {
|
||||
switch {
|
||||
case opts.Issuer == nil:
|
||||
return nil, errors.New("softCAS 'issuer' cannot be nil")
|
||||
case opts.Signer == nil:
|
||||
return nil, errors.New("softCAS 'signer' cannot be nil")
|
||||
if !opts.IsCreator {
|
||||
switch {
|
||||
case opts.Issuer == nil:
|
||||
return nil, errors.New("softCAS 'issuer' cannot be nil")
|
||||
case opts.Signer == nil:
|
||||
return nil, errors.New("softCAS 'signer' cannot be nil")
|
||||
}
|
||||
}
|
||||
return &SoftCAS{
|
||||
Issuer: opts.Issuer,
|
||||
Signer: opts.Signer,
|
||||
Issuer: opts.Issuer,
|
||||
Signer: opts.Signer,
|
||||
KeyManager: opts.KeyManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -113,3 +119,102 @@ func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1
|
|||
},
|
||||
}, 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/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/pki"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/command"
|
||||
|
@ -162,7 +163,10 @@ func onboardAction(ctx *cli.Context) 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 {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -171,13 +175,13 @@ func onboardPKI(config onboardingConfiguration) (*authority.Config, string, erro
|
|||
p.SetDNSNames([]string{config.DNS})
|
||||
|
||||
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 {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
10
go.mod
10
go.mod
|
@ -20,19 +20,15 @@ require (
|
|||
github.com/urfave/cli v1.22.2
|
||||
go.step.sm/cli-utils v0.1.0
|
||||
go.step.sm/crypto v0.6.1
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974
|
||||
google.golang.org/api v0.31.0
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d
|
||||
google.golang.org/grpc v1.32.0
|
||||
google.golang.org/protobuf v1.25.0
|
||||
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 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
|
||||
|
|
6
go.sum
6
go.sum
|
@ -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-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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-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-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-20190226205417-e64efc72b421/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-20200828194041-157a740278f4 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E=
|
||||
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
181
pki/pki.go
181
pki/pki.go
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
|
@ -31,7 +32,6 @@ import (
|
|||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"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.
|
||||
type PKI struct {
|
||||
casOptions apiv1.Options
|
||||
caCreator apiv1.CertificateAuthorityCreator
|
||||
root, rootKey, rootFingerprint string
|
||||
intermediate, intermediateKey string
|
||||
sshHostPubKey, sshHostKey string
|
||||
|
@ -160,11 +162,15 @@ type PKI struct {
|
|||
dnsNames []string
|
||||
caURL string
|
||||
enableSSH bool
|
||||
authorityOptions *apiv1.Options
|
||||
}
|
||||
|
||||
// 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()
|
||||
private := GetSecretsPath()
|
||||
config := GetConfigPath()
|
||||
|
@ -185,8 +191,9 @@ func New() (*PKI, error) {
|
|||
return s, errors.Wrapf(err, "error getting absolute path for %s", name)
|
||||
}
|
||||
|
||||
var err error
|
||||
p := &PKI{
|
||||
casOptions: opts,
|
||||
caCreator: caCreator,
|
||||
provisioner: "step-cli",
|
||||
address: "127.0.0.1:9000",
|
||||
dnsNames: []string{"127.0.0.1"},
|
||||
|
@ -237,12 +244,6 @@ func (p *PKI) GetRootFingerprint() string {
|
|||
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.
|
||||
func (p *PKI) SetProvisioner(s string) {
|
||||
p.provisioner = s
|
||||
|
@ -275,37 +276,65 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GenerateRootCertificate generates a root certificate with the given name.
|
||||
func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) {
|
||||
signer, err := generateDefaultKey()
|
||||
// GenerateRootCertificate generates a root certificate with the given name
|
||||
// and using the default key type.
|
||||
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 {
|
||||
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 := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediateCertificate generates an intermediate certificate with
|
||||
// the given name and using the default key type.
|
||||
func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
data := x509util.CreateTemplateData(name, []string{})
|
||||
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
template := cert.GetCertificate()
|
||||
template.NotBefore = time.Now()
|
||||
template.NotAfter = template.NotBefore.AddDate(10, 0, 0)
|
||||
rootCrt, err := x509util.CreateCertificate(template, template, signer.Public(), signer)
|
||||
if err != nil {
|
||||
return nil, nil, 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.
|
||||
|
@ -330,21 +359,33 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
|
|||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if key != nil {
|
||||
_, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificateAuthority attempts to load the certificate authority from the
|
||||
// RA.
|
||||
func (p *PKI) GetCertificateAuthority() error {
|
||||
ca, err := cas.New(context.Background(), *p.authorityOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv, ok := ca.(apiv1.CertificateAuthorityGetter)
|
||||
srv, ok := p.caCreator.(apiv1.CertificateAuthorityGetter)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{
|
||||
Name: p.authorityOptions.CertificateAuthority,
|
||||
Name: p.casOptions.CertificateAuthority,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -361,51 +402,6 @@ func (p *PKI) GetCertificateAuthority() error {
|
|||
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
|
||||
// SSH user certificates and a private key used for signing host certificates.
|
||||
func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
|
||||
|
@ -457,7 +453,7 @@ func (p *PKI) TellPKI() {
|
|||
|
||||
func (p *PKI) tellPKI() {
|
||||
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 private key", p.rootKey)
|
||||
ui.PrintSelected("Root fingerprint", p.rootFingerprint)
|
||||
|
@ -522,6 +518,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
|
|||
EncryptedKey: key,
|
||||
}
|
||||
|
||||
var authorityOptions *apiv1.Options
|
||||
if !p.casOptions.Is(apiv1.SoftCAS) {
|
||||
authorityOptions = &p.casOptions
|
||||
}
|
||||
|
||||
config := &authority.Config{
|
||||
Root: []string{p.root},
|
||||
FederatedRoots: []string{},
|
||||
|
@ -535,7 +536,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
|
|||
DataSource: GetDBPath(),
|
||||
},
|
||||
AuthorityConfig: &authority.AuthConfig{
|
||||
Options: p.authorityOptions,
|
||||
Options: authorityOptions,
|
||||
DisableIssuedAtCheck: false,
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
|
@ -642,7 +643,7 @@ func (p *PKI) Save(opt ...Option) error {
|
|||
ui.PrintSelected("Default configuration", p.defaults)
|
||||
ui.PrintSelected("Certificate Authority configuration", p.config)
|
||||
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'.")
|
||||
} else {
|
||||
ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.")
|
||||
|
|
Loading…
Reference in a new issue