1
0
mirror of https://github.com/smallstep/cli.git synced 2025-08-09 03:22:43 +03:00

Add command step ca init

This commit is contained in:
Mariano Cano
2018-10-02 16:35:51 -07:00
parent 9edc53eb00
commit 7d7749cbc6
4 changed files with 513 additions and 0 deletions

View File

@@ -20,6 +20,9 @@ import (
_ "github.com/smallstep/cli/command/crypto" _ "github.com/smallstep/cli/command/crypto"
_ "github.com/smallstep/cli/command/oauth" _ "github.com/smallstep/cli/command/oauth"
// Work in progress ...
_ "github.com/smallstep/cli/command/ca"
// Profiling and debugging // Profiling and debugging
_ "net/http/pprof" _ "net/http/pprof"
) )

21
command/ca/ca.go Normal file
View File

@@ -0,0 +1,21 @@
package ca
import (
"github.com/smallstep/cli/command"
"github.com/urfave/cli"
)
// init creates and registers the ca command
func init() {
cmd := cli.Command{
Name: "ca",
Usage: "TODO",
UsageText: "step ca <subcommand> [arguments] [global-flags] [subcommand-flags]",
Description: `**step ca** command group provides facilities ... TODO`,
Subcommands: cli.Commands{
initCommand(),
},
}
command.Register(cmd)
}

255
command/ca/init.go Normal file
View File

@@ -0,0 +1,255 @@
package ca
import (
"crypto/rand"
"fmt"
"io"
"os"
"github.com/smallstep/cli/config"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/utils"
"github.com/urfave/cli"
)
func initCommand() cli.Command {
return cli.Command{
Name: "init",
Action: cli.ActionFunc(initAction),
Usage: "initializes the CA PKI",
UsageText: `**step ca init**`,
Description: `**step ca init** command initializes a public key infrastructure (PKI) to be
used by the Certificate Authority`,
}
}
func initAction(ctx *cli.Context) error {
if err := assertCryptoRand(); err != nil {
return err
}
stepPath := config.StepPath()
defaultSecrets := stepPath + "/secrets"
defaultConfig := stepPath + "/config"
fmt.Fprintf(os.Stderr, "What would you like to name your new PKI? (e.g. Smallstep): ")
name, err := utils.ReadString(os.Stdin)
if err != nil {
return err
}
pass, err := utils.ReadPasswordGenerate("What do you want your password to be? [leave empty and we'll generate one]: ")
if err != nil {
return err
}
p, err := newPKI(defaultSecrets, defaultSecrets, defaultConfig)
if err != nil {
return err
}
// Generate ott and ssh key pairs
if err := p.GenerateKeyPairs(pass); err != nil {
return err
}
fmt.Println()
fmt.Print("Generating root certificate... \n")
rootCrt, rootKey, err := p.GenerateRootCertificate(name+" Root CA", pass)
if err != nil {
return err
}
fmt.Println("all done!")
fmt.Println()
fmt.Print("Generating intermediate certificate... \n")
err = p.GenerateIntermediateCertificate(name+" Intermediate CA", rootCrt, rootKey, pass)
if err != nil {
return err
}
fmt.Println("all done!")
if err = p.Save(); err != nil {
return err
}
return nil
}
// assertCrytoRand asserts that a cryptographically secure random number
// generator is available, it will return an error otherwise.
func assertCryptoRand() error {
buf := make([]byte, 64)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
return errs.NewError("crypto/rand is unavailable: Read() failed with %#v", err)
}
return nil
}
// type pki struct {
// root, rootKey string
// intermediate, intermediateKey string
// ottPublicKey, ottPrivateKey string
// sshUserKey, sshHostKey string
// country, locality, organization string
// config string
// }
// func validatePaths(public, private, config string) (*pki, error) {
// var err error
// if _, err = os.Stat(public); os.IsNotExist(err) {
// if err = os.MkdirAll(public, 0700); err != nil {
// return nil, errs.FileError(err, public)
// }
// }
// if _, err = os.Stat(private); os.IsNotExist(err) {
// if err = os.MkdirAll(private, 0700); err != nil {
// return nil, errs.FileError(err, private)
// }
// }
// if _, err = os.Stat(config); os.IsNotExist(err) {
// if err = os.MkdirAll(config, 0700); err != nil {
// return nil, errs.FileError(err, config)
// }
// }
// // get absolute path for dir/name
// getPath := func(dir string, name string) (string, error) {
// s, err := filepath.Abs(filepath.Join(dir, name))
// return s, errors.Wrapf(err, "error getting absolute path for %s", name)
// }
// p := new(pki)
// if p.root, err = getPath(public, "root_ca.crt"); err != nil {
// return nil, err
// }
// if p.rootKey, err = getPath(private, "root_ca_key"); err != nil {
// return nil, err
// }
// 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 {
// return nil, err
// }
// if p.ottPublicKey, err = getPath(public, "ott_key.public"); err != nil {
// return nil, err
// }
// if p.ottPrivateKey, err = getPath(private, "ott_key"); err != nil {
// return nil, err
// }
// if p.sshUserKey, err = getPath(private, "ssh_user_key"); err != nil {
// return nil, err
// }
// if p.sshHostKey, err = getPath(private, "ssh_host_key"); err != nil {
// return nil, err
// }
// if p.config, err = getPath(config, "ca.step"); err != nil {
// return nil, err
// }
// return p, nil
// }
// // generateOTTKeyPair generates a keypair using the default crypto algorithms.
// // This key pair will be used to sign/verify one-time-tokens.
// func generateOTTKeyPair(ottPublicKey, ottPrivateKey string, pass []byte) error {
// if len(pass) == 0 {
// return errors.New("password cannot be empty when initializing simple pki")
// }
// pub, priv, err := keys.GenerateDefaultKeyPair()
// if err != nil {
// return err
// }
// if _, err := pemutil.Serialize(pub, pemutil.ToFile(ottPublicKey, 0644)); err != nil {
// return err
// }
// _, err = pemutil.Serialize(priv, pemutil.WithEncryption(pass), pemutil.ToFile(ottPrivateKey, 0644))
// return err
// }
// // generateCASigningKeyPair generates a certificate signing public/private key
// // pair for signing ssh certificates.
// func generateCASigningKeyPair(keyFile string, pass []byte) error {
// if len(pass) == 0 {
// return errors.New("password cannot be empty when initializing simple pki")
// }
// pubFile := keyFile + ".pub"
// pub, priv, err := keys.GenerateDefaultKeyPair()
// if err != nil {
// return err
// }
// sshPub, err := ssh.NewPublicKey(pub)
// if err != nil {
// return errors.Wrap(err, "error creating SSH public key")
// }
// err = ioutil.WriteFile(pubFile, ssh.MarshalAuthorizedKey(sshPub), os.FileMode(0644))
// if err != nil {
// return errs.FileError(err, pubFile)
// }
// _, err = pemutil.Serialize(priv, pemutil.WithEncryption([]byte(pass)), pemutil.ToFile(keyFile, 0644))
// return err
// }
// func savePKI(p *pki) error {
// fmt.Println()
// fmt.Printf("Root certificate: %s\n", p.root)
// fmt.Printf("Root private key: %s\n", p.rootKey)
// fmt.Printf("Intermediate certificate: %s\n", p.intermediate)
// fmt.Printf("Intermediate private key: %s\n", p.intermediateKey)
// config := map[string]interface{}{
// "root": p.root,
// "crt": p.intermediate,
// "key": p.intermediateKey,
// "address": "127.0.0.1:9000",
// "dnsNames": []string{"127.0.0.1"},
// "logger": map[string]interface{}{"format": "text"},
// "tls": map[string]interface{}{
// "minVersion": x509util.DefaultTLSMinVersion,
// "maxVersion": x509util.DefaultTLSMaxVersion,
// "renegotiation": x509util.DefaultTLSRenegotiation,
// "cipherSuites": x509util.DefaultTLSCipherSuites,
// },
// "authority": map[string]interface{}{
// "type": "jwt",
// "key": p.ottPublicKey,
// "template": map[string]interface{}{
// "country": p.country,
// "locality": p.locality,
// "organization": p.organization,
// },
// },
// }
// b, err := json.MarshalIndent(config, "", " ")
// if err != nil {
// return errors.Wrapf(err, "error marshalling %s", p.config)
// }
// if err = ioutil.WriteFile(p.config, b, 0666); err != nil {
// return errs.FileError(err, p.config)
// }
// fmt.Println()
// fmt.Printf("Certificate Authority configuration: %s\n", p.config)
// fmt.Println()
// fmt.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.")
// return nil
// }

234
command/ca/pki.go Normal file
View File

@@ -0,0 +1,234 @@
package ca
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/pkg/x509"
"golang.org/x/crypto/ssh"
)
type pki struct {
root, rootKey string
intermediate, intermediateKey string
ottPublicKey, ottPrivateKey string
sshUserKey, sshHostKey string
country, locality, organization string
config string
}
func newPKI(public, private, config string) (*pki, error) {
var err error
if _, err = os.Stat(public); os.IsNotExist(err) {
if err = os.MkdirAll(public, 0700); err != nil {
return nil, errs.FileError(err, public)
}
}
if _, err = os.Stat(private); os.IsNotExist(err) {
if err = os.MkdirAll(private, 0700); err != nil {
return nil, errs.FileError(err, private)
}
}
if _, err = os.Stat(config); os.IsNotExist(err) {
if err = os.MkdirAll(config, 0700); err != nil {
return nil, errs.FileError(err, config)
}
}
// get absolute path for dir/name
getPath := func(dir string, name string) (string, error) {
s, err := filepath.Abs(filepath.Join(dir, name))
return s, errors.Wrapf(err, "error getting absolute path for %s", name)
}
p := new(pki)
if p.root, err = getPath(public, "root_ca.crt"); err != nil {
return nil, err
}
if p.rootKey, err = getPath(private, "root_ca_key"); err != nil {
return nil, err
}
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 {
return nil, err
}
if p.ottPublicKey, err = getPath(public, "ott_key.public"); err != nil {
return nil, err
}
if p.ottPrivateKey, err = getPath(private, "ott_key"); err != nil {
return nil, err
}
if p.sshUserKey, err = getPath(private, "ssh_user_key"); err != nil {
return nil, err
}
if p.sshHostKey, err = getPath(private, "ssh_host_key"); err != nil {
return nil, err
}
if p.config, err = getPath(config, "ca.step"); err != nil {
return nil, err
}
return p, nil
}
// GenerateKeyPairs generates the key pairs used by the certificate authority.
func (p *pki) GenerateKeyPairs(pass []byte) error {
// Create OTT key pair, the user doesn't need to know about this.
// Created in default secrets directory because it is required by `new-token`.
if err := generateOTTKeyPair(p.ottPublicKey, p.ottPrivateKey, pass); err != nil {
return err
}
// Create ssh user certificate signing key pair, the user doesn't need to know about this.
if err := generateCASigningKeyPair(p.sshUserKey, pass); err != nil {
return err
}
// Create ssh host certificate signing key pair, the user doesn't need to know about this.
if err := generateCASigningKeyPair(p.sshHostKey, pass); err != nil {
return err
}
return nil
}
// GenerateRootCertificate generates a root certificate with the given name.
func (p *pki) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) {
rootProfile, err := x509util.NewRootProfile(name)
if err != nil {
return nil, nil, err
}
rootBytes, err := rootProfile.CreateWriteCertificate(p.root, p.rootKey, string(pass))
if err != nil {
return nil, nil, err
}
rootCrt, err := x509.ParseCertificate(rootBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "error parsing root certificate")
}
return rootCrt, rootProfile.SubjectPrivateKey(), nil
}
// GenerateIntermediateCertificate generates an intermediate certificate with
// the given name.
func (p *pki) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
interProfile, err := x509util.NewIntermediateProfile(name, rootCrt, rootKey)
if err != nil {
return err
}
_, err = interProfile.CreateWriteCertificate(p.intermediate, p.intermediateKey, string(pass))
return err
}
// Save stores the pki on a json file that will be used as the certificate
// authority configuration.
func (p *pki) Save() error {
fmt.Println()
fmt.Printf("Root certificate: %s\n", p.root)
fmt.Printf("Root private key: %s\n", p.rootKey)
fmt.Printf("Intermediate certificate: %s\n", p.intermediate)
fmt.Printf("Intermediate private key: %s\n", p.intermediateKey)
config := map[string]interface{}{
"root": p.root,
"crt": p.intermediate,
"key": p.intermediateKey,
"address": "127.0.0.1:9000",
"dnsNames": []string{"127.0.0.1"},
"logger": map[string]interface{}{"format": "text"},
"tls": map[string]interface{}{
"minVersion": x509util.DefaultTLSMinVersion,
"maxVersion": x509util.DefaultTLSMaxVersion,
"renegotiation": x509util.DefaultTLSRenegotiation,
"cipherSuites": x509util.DefaultTLSCipherSuites,
},
"authority": map[string]interface{}{
"type": "jwt",
"key": p.ottPublicKey,
"template": map[string]interface{}{
"country": p.country,
"locality": p.locality,
"organization": p.organization,
},
},
}
b, err := json.MarshalIndent(config, "", " ")
if err != nil {
return errors.Wrapf(err, "error marshalling %s", p.config)
}
if err = ioutil.WriteFile(p.config, b, 0666); err != nil {
return errs.FileError(err, p.config)
}
fmt.Println()
fmt.Printf("Certificate Authority configuration: %s\n", p.config)
fmt.Println()
fmt.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.")
return nil
}
// generateOTTKeyPair generates a keypair using the default crypto algorithms.
// This key pair will be used to sign/verify one-time-tokens.
func generateOTTKeyPair(ottPublicKey, ottPrivateKey string, pass []byte) error {
if len(pass) == 0 {
return errors.New("password cannot be empty when initializing simple pki")
}
pub, priv, err := keys.GenerateDefaultKeyPair()
if err != nil {
return err
}
if _, err := pemutil.Serialize(pub, pemutil.ToFile(ottPublicKey, 0644)); err != nil {
return err
}
_, err = pemutil.Serialize(priv, pemutil.WithEncryption(pass), pemutil.ToFile(ottPrivateKey, 0644))
return err
}
// generateCASigningKeyPair generates a certificate signing public/private key
// pair for signing ssh certificates.
func generateCASigningKeyPair(keyFile string, pass []byte) error {
if len(pass) == 0 {
return errors.New("password cannot be empty when initializing simple pki")
}
pubFile := keyFile + ".pub"
pub, priv, err := keys.GenerateDefaultKeyPair()
if err != nil {
return err
}
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return errors.Wrap(err, "error creating SSH public key")
}
err = ioutil.WriteFile(pubFile, ssh.MarshalAuthorizedKey(sshPub), os.FileMode(0644))
if err != nil {
return errs.FileError(err, pubFile)
}
_, err = pemutil.Serialize(priv, pemutil.WithEncryption([]byte(pass)), pemutil.ToFile(keyFile, 0644))
return err
}