|
|
|
@ -10,21 +10,23 @@ import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"encoding/pem"
|
|
|
|
|
"fmt"
|
|
|
|
|
"html"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"github.com/smallstep/certificates/authority"
|
|
|
|
|
"github.com/smallstep/certificates/authority/admin"
|
|
|
|
|
admindb "github.com/smallstep/certificates/authority/admin/db/nosql"
|
|
|
|
|
authconfig "github.com/smallstep/certificates/authority/config"
|
|
|
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
|
|
|
|
"github.com/smallstep/certificates/ca"
|
|
|
|
|
"github.com/smallstep/certificates/cas"
|
|
|
|
|
"github.com/smallstep/certificates/cas/apiv1"
|
|
|
|
|
"github.com/smallstep/certificates/db"
|
|
|
|
|
"github.com/smallstep/nosql"
|
|
|
|
|
"go.step.sm/cli-utils/config"
|
|
|
|
|
"go.step.sm/cli-utils/errs"
|
|
|
|
|
"go.step.sm/cli-utils/fileutil"
|
|
|
|
@ -32,9 +34,40 @@ import (
|
|
|
|
|
"go.step.sm/crypto/jose"
|
|
|
|
|
"go.step.sm/crypto/keyutil"
|
|
|
|
|
"go.step.sm/crypto/pemutil"
|
|
|
|
|
"go.step.sm/linkedca"
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// DeploymentType defines witch type of deployment a user is initializing
|
|
|
|
|
type DeploymentType int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// StandaloneDeployment is a deployment where all the components like keys,
|
|
|
|
|
// provisioners, admins, certificates and others are managed by the user.
|
|
|
|
|
StandaloneDeployment DeploymentType = iota
|
|
|
|
|
// LinkedDeployment is a deployment where the keys are managed by the user,
|
|
|
|
|
// but provisioners, admins and the record of certificates are managed in
|
|
|
|
|
// the cloud.
|
|
|
|
|
LinkedDeployment
|
|
|
|
|
// HostedDeployment is a deployment where all the components are managed in
|
|
|
|
|
// the cloud by smallstep.com/certificate-manager.
|
|
|
|
|
HostedDeployment
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// String returns the string version of the deployment type.
|
|
|
|
|
func (d DeploymentType) String() string {
|
|
|
|
|
switch d {
|
|
|
|
|
case StandaloneDeployment:
|
|
|
|
|
return "standalone"
|
|
|
|
|
case LinkedDeployment:
|
|
|
|
|
return "linked"
|
|
|
|
|
case HostedDeployment:
|
|
|
|
|
return "hosted"
|
|
|
|
|
default:
|
|
|
|
|
return "unknown"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// ConfigPath is the directory name under the step path where the configuration
|
|
|
|
|
// files will be stored.
|
|
|
|
@ -134,43 +167,125 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
|
|
|
|
|
return resp.Key, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type options struct {
|
|
|
|
|
provisioner string
|
|
|
|
|
pkiOnly bool
|
|
|
|
|
enableACME bool
|
|
|
|
|
enableSSH bool
|
|
|
|
|
enableAdmin bool
|
|
|
|
|
noDB bool
|
|
|
|
|
isHelm bool
|
|
|
|
|
deploymentType DeploymentType
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Option is the type of a configuration option on the pki constructor.
|
|
|
|
|
type Option func(p *PKI)
|
|
|
|
|
|
|
|
|
|
// WithAddress sets the listen address of step-ca.
|
|
|
|
|
func WithAddress(s string) Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.Address = s
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithCaURL sets the default ca-url of step-ca.
|
|
|
|
|
func WithCaURL(s string) Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.Defaults.CaUrl = s
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithDNSNames sets the SANs of step-ca.
|
|
|
|
|
func WithDNSNames(s []string) Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.DnsNames = s
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithProvisioner defines the name of the default provisioner.
|
|
|
|
|
func WithProvisioner(s string) Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.provisioner = s
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithPKIOnly will only generate the PKI without the step-ca config files.
|
|
|
|
|
func WithPKIOnly() Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.pkiOnly = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithACME enables acme provisioner in step-ca.
|
|
|
|
|
func WithACME() Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.enableACME = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithSSH enables ssh in step-ca.
|
|
|
|
|
func WithSSH() Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.enableSSH = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithAdmin enables the admin api in step-ca.
|
|
|
|
|
func WithAdmin() Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.enableAdmin = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithNoDB disables the db in step-ca.
|
|
|
|
|
func WithNoDB() Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.noDB = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithHelm configures the pki to create a helm values.yaml.
|
|
|
|
|
func WithHelm() Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.isHelm = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithDeploymentType defines the deployment type of step-ca.
|
|
|
|
|
func WithDeploymentType(dt DeploymentType) Option {
|
|
|
|
|
return func(p *PKI) {
|
|
|
|
|
p.options.deploymentType = dt
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
sshUserPubKey, sshUserKey string
|
|
|
|
|
config, defaults string
|
|
|
|
|
ottPublicKey *jose.JSONWebKey
|
|
|
|
|
ottPrivateKey *jose.JSONWebEncryption
|
|
|
|
|
provisioner string
|
|
|
|
|
address string
|
|
|
|
|
dnsNames []string
|
|
|
|
|
caURL string
|
|
|
|
|
enableSSH bool
|
|
|
|
|
linkedca.Configuration
|
|
|
|
|
Defaults linkedca.Defaults
|
|
|
|
|
casOptions apiv1.Options
|
|
|
|
|
caService apiv1.CertificateAuthorityService
|
|
|
|
|
caCreator apiv1.CertificateAuthorityCreator
|
|
|
|
|
config string
|
|
|
|
|
defaults string
|
|
|
|
|
ottPublicKey *jose.JSONWebKey
|
|
|
|
|
ottPrivateKey *jose.JSONWebEncryption
|
|
|
|
|
options *options
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New creates a new PKI configuration.
|
|
|
|
|
func New(opts apiv1.Options) (*PKI, error) {
|
|
|
|
|
caCreator, err := cas.NewCreator(context.Background(), opts)
|
|
|
|
|
func New(o apiv1.Options, opts ...Option) (*PKI, error) {
|
|
|
|
|
caService, err := cas.New(context.Background(), o)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public := GetPublicPath()
|
|
|
|
|
private := GetSecretsPath()
|
|
|
|
|
config := GetConfigPath()
|
|
|
|
|
|
|
|
|
|
// Create directories
|
|
|
|
|
dirs := []string{public, private, config, GetTemplatesPath()}
|
|
|
|
|
for _, name := range dirs {
|
|
|
|
|
if _, err := os.Stat(name); os.IsNotExist(err) {
|
|
|
|
|
if err = os.MkdirAll(name, 0700); err != nil {
|
|
|
|
|
return nil, errs.FileError(err, name)
|
|
|
|
|
}
|
|
|
|
|
var caCreator apiv1.CertificateAuthorityCreator
|
|
|
|
|
if o.IsCreator {
|
|
|
|
|
creator, ok := caService.(apiv1.CertificateAuthorityCreator)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, errors.Errorf("cas type '%s' does not implements CertificateAuthorityCreator", o.Type)
|
|
|
|
|
}
|
|
|
|
|
caCreator = creator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get absolute path for dir/name
|
|
|
|
@ -180,44 +295,96 @@ func New(opts apiv1.Options) (*PKI, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p := &PKI{
|
|
|
|
|
casOptions: opts,
|
|
|
|
|
caCreator: caCreator,
|
|
|
|
|
provisioner: "step-cli",
|
|
|
|
|
address: "127.0.0.1:9000",
|
|
|
|
|
dnsNames: []string{"127.0.0.1"},
|
|
|
|
|
Configuration: linkedca.Configuration{
|
|
|
|
|
Address: "127.0.0.1:9000",
|
|
|
|
|
DnsNames: []string{"127.0.0.1"},
|
|
|
|
|
Ssh: &linkedca.SSH{},
|
|
|
|
|
Authority: &linkedca.Authority{},
|
|
|
|
|
Files: make(map[string][]byte),
|
|
|
|
|
},
|
|
|
|
|
casOptions: o,
|
|
|
|
|
caCreator: caCreator,
|
|
|
|
|
caService: caService,
|
|
|
|
|
options: &options{
|
|
|
|
|
provisioner: "step-cli",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, fn := range opts {
|
|
|
|
|
fn(p)
|
|
|
|
|
}
|
|
|
|
|
if p.root, err = getPath(public, "root_ca.crt"); err != nil {
|
|
|
|
|
|
|
|
|
|
// Use /home/step as the step path in helm configurations.
|
|
|
|
|
// Use the current step path when creating pki in files.
|
|
|
|
|
var public, private, config string
|
|
|
|
|
if p.options.isHelm {
|
|
|
|
|
public = "/home/step/certs"
|
|
|
|
|
private = "/home/step/secrets"
|
|
|
|
|
config = "/home/step/config"
|
|
|
|
|
} else {
|
|
|
|
|
public = GetPublicPath()
|
|
|
|
|
private = GetSecretsPath()
|
|
|
|
|
config = GetConfigPath()
|
|
|
|
|
// Create directories
|
|
|
|
|
dirs := []string{public, private, config, GetTemplatesPath()}
|
|
|
|
|
for _, name := range dirs {
|
|
|
|
|
if _, err := os.Stat(name); os.IsNotExist(err) {
|
|
|
|
|
if err = os.MkdirAll(name, 0700); err != nil {
|
|
|
|
|
return nil, errs.FileError(err, name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if p.Defaults.CaUrl == "" {
|
|
|
|
|
p.Defaults.CaUrl = p.DnsNames[0]
|
|
|
|
|
_, port, err := net.SplitHostPort(p.Address)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "error parsing %s", p.Address)
|
|
|
|
|
}
|
|
|
|
|
if port == "443" {
|
|
|
|
|
p.Defaults.CaUrl = fmt.Sprintf("https://%s", p.Defaults.CaUrl)
|
|
|
|
|
} else {
|
|
|
|
|
p.Defaults.CaUrl = fmt.Sprintf("https://%s:%s", p.Defaults.CaUrl, port)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
root, err := getPath(public, "root_ca.crt")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.rootKey, err = getPath(private, "root_ca_key"); err != nil {
|
|
|
|
|
rootKey, err := getPath(private, "root_ca_key")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.intermediate, err = getPath(public, "intermediate_ca.crt"); err != nil {
|
|
|
|
|
p.Root = []string{root}
|
|
|
|
|
p.RootKey = []string{rootKey}
|
|
|
|
|
p.Defaults.Root = root
|
|
|
|
|
|
|
|
|
|
if p.Intermediate, err = getPath(public, "intermediate_ca.crt"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.intermediateKey, err = getPath(private, "intermediate_ca_key"); err != nil {
|
|
|
|
|
if p.IntermediateKey, err = getPath(private, "intermediate_ca_key"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.sshHostPubKey, err = getPath(public, "ssh_host_ca_key.pub"); err != nil {
|
|
|
|
|
if p.Ssh.HostPublicKey, err = getPath(public, "ssh_host_ca_key.pub"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.sshUserPubKey, err = getPath(public, "ssh_user_ca_key.pub"); err != nil {
|
|
|
|
|
if p.Ssh.UserPublicKey, err = getPath(public, "ssh_user_ca_key.pub"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.sshHostKey, err = getPath(private, "ssh_host_ca_key"); err != nil {
|
|
|
|
|
if p.Ssh.HostKey, err = getPath(private, "ssh_host_ca_key"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.sshUserKey, err = getPath(private, "ssh_user_ca_key"); err != nil {
|
|
|
|
|
if p.Ssh.UserKey, err = getPath(private, "ssh_user_ca_key"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if len(config) > 0 {
|
|
|
|
|
if p.config, err = getPath(config, "ca.json"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.defaults, err = getPath(config, "defaults.json"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.defaults, err = getPath(config, "defaults.json"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if p.config, err = getPath(config, "ca.json"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.Defaults.CaConfig = p.config
|
|
|
|
|
|
|
|
|
|
return p, nil
|
|
|
|
|
}
|
|
|
|
@ -229,27 +396,7 @@ func (p *PKI) GetCAConfigPath() string {
|
|
|
|
|
|
|
|
|
|
// GetRootFingerprint returns the root fingerprint.
|
|
|
|
|
func (p *PKI) GetRootFingerprint() string {
|
|
|
|
|
return p.rootFingerprint
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetProvisioner sets the provisioner name of the OTT keys.
|
|
|
|
|
func (p *PKI) SetProvisioner(s string) {
|
|
|
|
|
p.provisioner = s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetAddress sets the listening address of the CA.
|
|
|
|
|
func (p *PKI) SetAddress(s string) {
|
|
|
|
|
p.address = s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetDNSNames sets the dns names of the CA.
|
|
|
|
|
func (p *PKI) SetDNSNames(s []string) {
|
|
|
|
|
p.dnsNames = s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetCAURL sets the ca-url to use in the defaults.json.
|
|
|
|
|
func (p *PKI) SetCAURL(s string) {
|
|
|
|
|
p.caURL = s
|
|
|
|
|
return p.Defaults.Fingerprint
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GenerateKeyPairs generates the key pairs used by the certificate authority.
|
|
|
|
@ -261,6 +408,28 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add JWK provisioner to the configuration.
|
|
|
|
|
publicKey, err := json.Marshal(p.ottPublicKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "error marshaling public key")
|
|
|
|
|
}
|
|
|
|
|
encryptedKey, err := p.ottPrivateKey.CompactSerialize()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "error serializing private key")
|
|
|
|
|
}
|
|
|
|
|
p.Authority.Provisioners = append(p.Authority.Provisioners, &linkedca.Provisioner{
|
|
|
|
|
Type: linkedca.Provisioner_JWK,
|
|
|
|
|
Name: p.options.provisioner,
|
|
|
|
|
Details: &linkedca.ProvisionerDetails{
|
|
|
|
|
Data: &linkedca.ProvisionerDetails_JWK{
|
|
|
|
|
JWK: &linkedca.JWKProvisioner{
|
|
|
|
|
PublicKey: publicKey,
|
|
|
|
|
EncryptedPrivateKey: []byte(encryptedKey),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -296,6 +465,21 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WriteRootCertificate writes to the buffer the given certificate and key if given.
|
|
|
|
|
func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
|
|
|
|
|
p.Files[p.Root[0]] = encodeCertificate(rootCrt)
|
|
|
|
|
if rootKey != nil {
|
|
|
|
|
var err error
|
|
|
|
|
p.Files[p.RootKey[0]], err = encodePrivateKey(rootKey, pass)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sum := sha256.Sum256(rootCrt.Raw)
|
|
|
|
|
p.Defaults.Fingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
|
|
|
|
|
return 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 {
|
|
|
|
@ -322,46 +506,9 @@ func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.casOptions.CertificateAuthority = resp.Name
|
|
|
|
|
return p.WriteIntermediateCertificate(resp.Certificate, resp.PrivateKey, pass)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WriteRootCertificate writes to disk the given certificate and key.
|
|
|
|
|
func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
|
|
|
|
|
if err := fileutil.WriteFile(p.root, pem.EncodeToMemory(&pem.Block{
|
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
|
Bytes: rootCrt.Raw,
|
|
|
|
|
}), 0600); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if rootKey != nil {
|
|
|
|
|
_, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sum := sha256.Sum256(rootCrt.Raw)
|
|
|
|
|
p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
|
|
|
|
|
p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateCertificateAuthorityResponse returns a
|
|
|
|
@ -379,7 +526,7 @@ func (p *PKI) CreateCertificateAuthorityResponse(cert *x509.Certificate, key cry
|
|
|
|
|
// GetCertificateAuthority attempts to load the certificate authority from the
|
|
|
|
|
// RA.
|
|
|
|
|
func (p *PKI) GetCertificateAuthority() error {
|
|
|
|
|
srv, ok := p.caCreator.(apiv1.CertificateAuthorityGetter)
|
|
|
|
|
srv, ok := p.caService.(apiv1.CertificateAuthorityGetter)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
@ -396,8 +543,8 @@ func (p *PKI) GetCertificateAuthority() error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Issuer is in the RA
|
|
|
|
|
p.intermediate = ""
|
|
|
|
|
p.intermediateKey = ""
|
|
|
|
|
p.Intermediate = ""
|
|
|
|
|
p.IntermediateKey = ""
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
@ -405,8 +552,8 @@ func (p *PKI) GetCertificateAuthority() error {
|
|
|
|
|
// 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 {
|
|
|
|
|
var pubNames = []string{p.sshHostPubKey, p.sshUserPubKey}
|
|
|
|
|
var privNames = []string{p.sshHostKey, p.sshUserKey}
|
|
|
|
|
var pubNames = []string{p.Ssh.HostPublicKey, p.Ssh.UserPublicKey}
|
|
|
|
|
var privNames = []string{p.Ssh.HostKey, p.Ssh.UserKey}
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
|
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
@ -419,57 +566,65 @@ func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(err, "error converting public key")
|
|
|
|
|
}
|
|
|
|
|
_, err = pemutil.Serialize(priv, pemutil.WithFilename(privNames[i]), pemutil.WithPassword(password))
|
|
|
|
|
p.Files[pubNames[i]] = ssh.MarshalAuthorizedKey(sshKey)
|
|
|
|
|
p.Files[privNames[i]], err = encodePrivateKey(priv, password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err = fileutil.WriteFile(pubNames[i], ssh.MarshalAuthorizedKey(sshKey), 0600); err != nil {
|
|
|
|
|
}
|
|
|
|
|
p.options.enableSSH = true
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WriteFiles writes on disk the previously generated files.
|
|
|
|
|
func (p *PKI) WriteFiles() error {
|
|
|
|
|
for fn, b := range p.Files {
|
|
|
|
|
if err := fileutil.WriteFile(fn, b, 0600); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
p.enableSSH = true
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PKI) askFeedback() {
|
|
|
|
|
ui.Println()
|
|
|
|
|
ui.Printf("\033[1mFEEDBACK\033[0m %s %s\n",
|
|
|
|
|
html.UnescapeString("&#"+strconv.Itoa(128525)+";"),
|
|
|
|
|
html.UnescapeString("&#"+strconv.Itoa(127867)+";"))
|
|
|
|
|
ui.Println(" The \033[1mstep\033[0m utility is not instrumented for usage statistics. It does not")
|
|
|
|
|
ui.Println(" phone home. But your feedback is extremely valuable. Any information you")
|
|
|
|
|
ui.Println(" can provide regarding how you’re using `step` helps. Please send us a")
|
|
|
|
|
ui.Println(" sentence or two, good or bad: \033[1mfeedback@smallstep.com\033[0m or join")
|
|
|
|
|
ui.Println(" \033[1mhttps://github.com/smallstep/certificates/discussions\033[0m.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TellPKI outputs the locations of public and private keys generated
|
|
|
|
|
// generated for a new PKI. Generally this will consist of a root certificate
|
|
|
|
|
// and key and an intermediate certificate and key.
|
|
|
|
|
func (p *PKI) TellPKI() {
|
|
|
|
|
p.tellPKI()
|
|
|
|
|
p.askFeedback()
|
|
|
|
|
ui.Println("\033[1mFEEDBACK\033[0m 😍 🍻")
|
|
|
|
|
ui.Println(" The \033[1mstep\033[0m utility is not instrumented for usage statistics. It does not phone")
|
|
|
|
|
ui.Println(" home. But your feedback is extremely valuable. Any information you can provide")
|
|
|
|
|
ui.Println(" regarding how you’re using `step` helps. Please send us a sentence or two,")
|
|
|
|
|
ui.Println(" good or bad at \033[1mfeedback@smallstep.com\033[0m or join GitHub Discussions")
|
|
|
|
|
ui.Println(" \033[1mhttps://github.com/smallstep/certificates/discussions\033[0m and our Discord ")
|
|
|
|
|
ui.Println(" \033[1mhttps://u.step.sm/discord\033[0m.")
|
|
|
|
|
|
|
|
|
|
if p.options.deploymentType == LinkedDeployment {
|
|
|
|
|
ui.Println()
|
|
|
|
|
ui.Println("\033[1mNEXT STEPS\033[0m")
|
|
|
|
|
ui.Println(" 1. Log in or create a Certificate Manager account at \033[1mhttps://u.step.sm/linked\033[0m")
|
|
|
|
|
ui.Println(" 2. Add a new authority and select \"Link a step-ca instance\"")
|
|
|
|
|
ui.Println(" 3. Follow instructions in browser to start `step-ca` using the `--token` flag")
|
|
|
|
|
ui.Println()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PKI) tellPKI() {
|
|
|
|
|
ui.Println()
|
|
|
|
|
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)
|
|
|
|
|
ui.PrintSelected("Intermediate certificate", p.intermediate)
|
|
|
|
|
ui.PrintSelected("Intermediate private key", p.intermediateKey)
|
|
|
|
|
} else if p.rootFingerprint != "" {
|
|
|
|
|
ui.PrintSelected("Root certificate", p.root)
|
|
|
|
|
ui.PrintSelected("Root fingerprint", p.rootFingerprint)
|
|
|
|
|
ui.PrintSelected("Root certificate", p.Root[0])
|
|
|
|
|
ui.PrintSelected("Root private key", p.RootKey[0])
|
|
|
|
|
ui.PrintSelected("Root fingerprint", p.Defaults.Fingerprint)
|
|
|
|
|
ui.PrintSelected("Intermediate certificate", p.Intermediate)
|
|
|
|
|
ui.PrintSelected("Intermediate private key", p.IntermediateKey)
|
|
|
|
|
} else if p.Defaults.Fingerprint != "" {
|
|
|
|
|
ui.PrintSelected("Root certificate", p.Root[0])
|
|
|
|
|
ui.PrintSelected("Root fingerprint", p.Defaults.Fingerprint)
|
|
|
|
|
} else {
|
|
|
|
|
ui.Printf(`{{ "%s" | red }} {{ "Root certificate:" | bold }} failed to retrieve it from RA`+"\n", ui.IconBad)
|
|
|
|
|
}
|
|
|
|
|
if p.enableSSH {
|
|
|
|
|
ui.PrintSelected("SSH user root certificate", p.sshUserPubKey)
|
|
|
|
|
ui.PrintSelected("SSH user root private key", p.sshUserKey)
|
|
|
|
|
ui.PrintSelected("SSH host root certificate", p.sshHostPubKey)
|
|
|
|
|
ui.PrintSelected("SSH host root private key", p.sshHostKey)
|
|
|
|
|
if p.options.enableSSH {
|
|
|
|
|
ui.PrintSelected("SSH user public key", p.Ssh.UserPublicKey)
|
|
|
|
|
ui.PrintSelected("SSH user private key", p.Ssh.UserKey)
|
|
|
|
|
ui.PrintSelected("SSH host public key", p.Ssh.HostPublicKey)
|
|
|
|
|
ui.PrintSelected("SSH host private key", p.Ssh.HostKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -480,176 +635,230 @@ type caDefaults struct {
|
|
|
|
|
Root string `json:"root"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Option is the type for modifiers over the auth config object.
|
|
|
|
|
type Option func(c *authconfig.Config) error
|
|
|
|
|
|
|
|
|
|
// WithDefaultDB is a configuration modifier that adds a default DB stanza to
|
|
|
|
|
// the authority config.
|
|
|
|
|
func WithDefaultDB() Option {
|
|
|
|
|
return func(c *authconfig.Config) error {
|
|
|
|
|
c.DB = &db.Config{
|
|
|
|
|
Type: "badger",
|
|
|
|
|
DataSource: GetDBPath(),
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithoutDB is a configuration modifier that adds a default DB stanza to
|
|
|
|
|
// the authority config.
|
|
|
|
|
func WithoutDB() Option {
|
|
|
|
|
return func(c *authconfig.Config) error {
|
|
|
|
|
c.DB = nil
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// ConfigOption is the type for modifiers over the auth config object.
|
|
|
|
|
type ConfigOption func(c *authconfig.Config) error
|
|
|
|
|
|
|
|
|
|
// GenerateConfig returns the step certificates configuration.
|
|
|
|
|
func (p *PKI) GenerateConfig(opt ...Option) (*authconfig.Config, error) {
|
|
|
|
|
key, err := p.ottPrivateKey.CompactSerialize()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "error serializing private key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prov := &provisioner.JWK{
|
|
|
|
|
Name: p.provisioner,
|
|
|
|
|
Type: "JWK",
|
|
|
|
|
Key: p.ottPublicKey,
|
|
|
|
|
EncryptedKey: key,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
|
|
|
|
|
var authorityOptions *apiv1.Options
|
|
|
|
|
if !p.casOptions.Is(apiv1.SoftCAS) {
|
|
|
|
|
authorityOptions = &p.casOptions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config := &authconfig.Config{
|
|
|
|
|
Root: []string{p.root},
|
|
|
|
|
FederatedRoots: []string{},
|
|
|
|
|
IntermediateCert: p.intermediate,
|
|
|
|
|
IntermediateKey: p.intermediateKey,
|
|
|
|
|
Address: p.address,
|
|
|
|
|
DNSNames: p.dnsNames,
|
|
|
|
|
Root: p.Root,
|
|
|
|
|
FederatedRoots: p.FederatedRoots,
|
|
|
|
|
IntermediateCert: p.Intermediate,
|
|
|
|
|
IntermediateKey: p.IntermediateKey,
|
|
|
|
|
Address: p.Address,
|
|
|
|
|
DNSNames: p.DnsNames,
|
|
|
|
|
Logger: []byte(`{"format": "text"}`),
|
|
|
|
|
DB: &db.Config{
|
|
|
|
|
Type: "badger",
|
|
|
|
|
Type: "badgerv2",
|
|
|
|
|
DataSource: GetDBPath(),
|
|
|
|
|
},
|
|
|
|
|
AuthorityConfig: &authconfig.AuthConfig{
|
|
|
|
|
Options: authorityOptions,
|
|
|
|
|
DisableIssuedAtCheck: false,
|
|
|
|
|
Provisioners: provisioner.List{prov},
|
|
|
|
|
},
|
|
|
|
|
TLS: &authconfig.TLSOptions{
|
|
|
|
|
MinVersion: authconfig.DefaultTLSMinVersion,
|
|
|
|
|
MaxVersion: authconfig.DefaultTLSMaxVersion,
|
|
|
|
|
Renegotiation: authconfig.DefaultTLSRenegotiation,
|
|
|
|
|
CipherSuites: authconfig.DefaultTLSCipherSuites,
|
|
|
|
|
EnableAdmin: false,
|
|
|
|
|
},
|
|
|
|
|
TLS: &authconfig.DefaultTLSOptions,
|
|
|
|
|
Templates: p.getTemplates(),
|
|
|
|
|
}
|
|
|
|
|
if p.enableSSH {
|
|
|
|
|
enableSSHCA := true
|
|
|
|
|
config.SSH = &authconfig.SSHConfig{
|
|
|
|
|
HostKey: p.sshHostKey,
|
|
|
|
|
UserKey: p.sshUserKey,
|
|
|
|
|
|
|
|
|
|
// Add linked as a deployment type to detect it on start and provide a
|
|
|
|
|
// message if the token is not given.
|
|
|
|
|
if p.options.deploymentType == LinkedDeployment {
|
|
|
|
|
config.AuthorityConfig.DeploymentType = LinkedDeployment.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// On standalone deployments add the provisioners to either the ca.json or
|
|
|
|
|
// the database.
|
|
|
|
|
var provisioners []provisioner.Interface
|
|
|
|
|
if p.options.deploymentType == StandaloneDeployment {
|
|
|
|
|
key, err := p.ottPrivateKey.CompactSerialize()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "error serializing private key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prov := &provisioner.JWK{
|
|
|
|
|
Name: p.options.provisioner,
|
|
|
|
|
Type: "JWK",
|
|
|
|
|
Key: p.ottPublicKey,
|
|
|
|
|
EncryptedKey: key,
|
|
|
|
|
}
|
|
|
|
|
// Enable SSH authorization for default JWK provisioner
|
|
|
|
|
prov.Claims = &provisioner.Claims{
|
|
|
|
|
EnableSSHCA: &enableSSHCA,
|
|
|
|
|
provisioners = append(provisioners, prov)
|
|
|
|
|
|
|
|
|
|
// Add default ACME provisioner if enabled
|
|
|
|
|
if p.options.enableACME {
|
|
|
|
|
provisioners = append(provisioners, &provisioner.ACME{
|
|
|
|
|
Type: "ACME",
|
|
|
|
|
Name: "acme",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
// Add default SSHPOP provisioner
|
|
|
|
|
sshpop := &provisioner.SSHPOP{
|
|
|
|
|
Type: "SSHPOP",
|
|
|
|
|
Name: "sshpop",
|
|
|
|
|
Claims: &provisioner.Claims{
|
|
|
|
|
|
|
|
|
|
if p.options.enableSSH {
|
|
|
|
|
enableSSHCA := true
|
|
|
|
|
config.SSH = &authconfig.SSHConfig{
|
|
|
|
|
HostKey: p.Ssh.HostKey,
|
|
|
|
|
UserKey: p.Ssh.UserKey,
|
|
|
|
|
}
|
|
|
|
|
// Enable SSH authorization for default JWK provisioner
|
|
|
|
|
prov.Claims = &provisioner.Claims{
|
|
|
|
|
EnableSSHCA: &enableSSHCA,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add default SSHPOP provisioner
|
|
|
|
|
provisioners = append(provisioners, &provisioner.SSHPOP{
|
|
|
|
|
Type: "SSHPOP",
|
|
|
|
|
Name: "sshpop",
|
|
|
|
|
Claims: &provisioner.Claims{
|
|
|
|
|
EnableSSHCA: &enableSSHCA,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
config.AuthorityConfig.Provisioners = append(config.AuthorityConfig.Provisioners, sshpop)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply configuration modifiers
|
|
|
|
|
for _, o := range opt {
|
|
|
|
|
if err = o(config); err != nil {
|
|
|
|
|
if err := o(config); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set authority.enableAdmin to true
|
|
|
|
|
if p.options.enableAdmin {
|
|
|
|
|
config.AuthorityConfig.EnableAdmin = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if p.options.deploymentType == StandaloneDeployment {
|
|
|
|
|
if !config.AuthorityConfig.EnableAdmin {
|
|
|
|
|
config.AuthorityConfig.Provisioners = provisioners
|
|
|
|
|
} else {
|
|
|
|
|
// At this moment this code path is never used because `step ca
|
|
|
|
|
// init` will always set enableAdmin to false for a standalone
|
|
|
|
|
// deployment. Once we move `step beta` commands out of the beta we
|
|
|
|
|
// should probably default to this route.
|
|
|
|
|
//
|
|
|
|
|
// Note that we might want to be able to define the database as a
|
|
|
|
|
// flag in `step ca init` so we can write to the proper place.
|
|
|
|
|
db, err := db.New(config.DB)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
adminDB, err := admindb.New(db.(nosql.DB), admin.DefaultAuthorityID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
// Add all the provisioners to the db.
|
|
|
|
|
var adminID string
|
|
|
|
|
for i, p := range provisioners {
|
|
|
|
|
prov, err := authority.ProvisionerToLinkedca(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := adminDB.CreateProvisioner(context.Background(), prov); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if i == 0 {
|
|
|
|
|
adminID = prov.Id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Add the first provisioner as an admin.
|
|
|
|
|
if err := adminDB.CreateAdmin(context.Background(), &linkedca.Admin{
|
|
|
|
|
AuthorityId: admin.DefaultAuthorityID,
|
|
|
|
|
Subject: "step",
|
|
|
|
|
Type: linkedca.Admin_SUPER_ADMIN,
|
|
|
|
|
ProvisionerId: adminID,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save stores the pki on a json file that will be used as the certificate
|
|
|
|
|
// authority configuration.
|
|
|
|
|
func (p *PKI) Save(opt ...Option) error {
|
|
|
|
|
p.tellPKI()
|
|
|
|
|
|
|
|
|
|
// Generate and write ca.json
|
|
|
|
|
config, err := p.GenerateConfig(opt...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
func (p *PKI) Save(opt ...ConfigOption) error {
|
|
|
|
|
// Write generated files
|
|
|
|
|
if err := p.WriteFiles(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b, err := json.MarshalIndent(config, "", "\t")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(err, "error marshaling %s", p.config)
|
|
|
|
|
}
|
|
|
|
|
if err = fileutil.WriteFile(p.config, b, 0644); err != nil {
|
|
|
|
|
return errs.FileError(err, p.config)
|
|
|
|
|
}
|
|
|
|
|
// Display the files written
|
|
|
|
|
p.tellPKI()
|
|
|
|
|
|
|
|
|
|
// Generate the CA URL.
|
|
|
|
|
if p.caURL == "" {
|
|
|
|
|
p.caURL = p.dnsNames[0]
|
|
|
|
|
var port string
|
|
|
|
|
_, port, err = net.SplitHostPort(p.address)
|
|
|
|
|
// Generate and write ca.json
|
|
|
|
|
if !p.options.pkiOnly {
|
|
|
|
|
config, err := p.GenerateConfig(opt...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(err, "error parsing %s", p.address)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if port == "443" {
|
|
|
|
|
p.caURL = fmt.Sprintf("https://%s", p.caURL)
|
|
|
|
|
} else {
|
|
|
|
|
p.caURL = fmt.Sprintf("https://%s:%s", p.caURL, port)
|
|
|
|
|
|
|
|
|
|
b, err := json.MarshalIndent(config, "", "\t")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(err, "error marshaling %s", p.config)
|
|
|
|
|
}
|
|
|
|
|
if err = fileutil.WriteFile(p.config, b, 0644); err != nil {
|
|
|
|
|
return errs.FileError(err, p.config)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate and write defaults.json
|
|
|
|
|
defaults := &caDefaults{
|
|
|
|
|
Root: p.root,
|
|
|
|
|
CAConfig: p.config,
|
|
|
|
|
CAUrl: p.caURL,
|
|
|
|
|
Fingerprint: p.rootFingerprint,
|
|
|
|
|
}
|
|
|
|
|
b, err = json.MarshalIndent(defaults, "", "\t")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(err, "error marshaling %s", p.defaults)
|
|
|
|
|
}
|
|
|
|
|
if err = fileutil.WriteFile(p.defaults, b, 0644); err != nil {
|
|
|
|
|
return errs.FileError(err, p.defaults)
|
|
|
|
|
}
|
|
|
|
|
// Generate and write defaults.json
|
|
|
|
|
defaults := &caDefaults{
|
|
|
|
|
Root: p.Defaults.Root,
|
|
|
|
|
CAConfig: p.Defaults.CaConfig,
|
|
|
|
|
CAUrl: p.Defaults.CaUrl,
|
|
|
|
|
Fingerprint: p.Defaults.Fingerprint,
|
|
|
|
|
}
|
|
|
|
|
b, err = json.MarshalIndent(defaults, "", "\t")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(err, "error marshaling %s", p.defaults)
|
|
|
|
|
}
|
|
|
|
|
if err = fileutil.WriteFile(p.defaults, b, 0644); err != nil {
|
|
|
|
|
return errs.FileError(err, p.defaults)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate and write templates
|
|
|
|
|
if err := generateTemplates(config.Templates); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
// Generate and write templates
|
|
|
|
|
if err := generateTemplates(config.Templates); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if config.DB != nil {
|
|
|
|
|
ui.PrintSelected("Database folder", config.DB.DataSource)
|
|
|
|
|
}
|
|
|
|
|
if config.Templates != nil {
|
|
|
|
|
ui.PrintSelected("Templates folder", GetTemplatesPath())
|
|
|
|
|
}
|
|
|
|
|
if config.DB != nil {
|
|
|
|
|
ui.PrintSelected("Database folder", config.DB.DataSource)
|
|
|
|
|
}
|
|
|
|
|
if config.Templates != nil {
|
|
|
|
|
ui.PrintSelected("Templates folder", GetTemplatesPath())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ui.PrintSelected("Default configuration", p.defaults)
|
|
|
|
|
ui.PrintSelected("Certificate Authority configuration", p.config)
|
|
|
|
|
ui.Println()
|
|
|
|
|
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'.")
|
|
|
|
|
ui.PrintSelected("Default configuration", p.defaults)
|
|
|
|
|
ui.PrintSelected("Certificate Authority configuration", p.config)
|
|
|
|
|
if p.options.deploymentType != LinkedDeployment {
|
|
|
|
|
ui.Println()
|
|
|
|
|
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'.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.askFeedback()
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodeCertificate(c *x509.Certificate) []byte {
|
|
|
|
|
return pem.EncodeToMemory(&pem.Block{
|
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
|
Bytes: c.Raw,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodePrivateKey(key crypto.PrivateKey, pass []byte) ([]byte, error) {
|
|
|
|
|
block, err := pemutil.Serialize(key, pemutil.WithPassword(pass))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return pem.EncodeToMemory(block), nil
|
|
|
|
|
}
|
|
|
|
|