2020-02-19 03:07:12 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/sha1"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/pem"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/smallstep/certificates/kms/apiv1"
|
|
|
|
"github.com/smallstep/certificates/kms/cloudkms"
|
|
|
|
"github.com/smallstep/cli/crypto/pemutil"
|
|
|
|
"github.com/smallstep/cli/ui"
|
|
|
|
"github.com/smallstep/cli/utils"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
var credentialsFile string
|
|
|
|
var project, location, ring string
|
|
|
|
var protectionLevelName string
|
|
|
|
var ssh bool
|
|
|
|
flag.StringVar(&credentialsFile, "credentials-file", "", "Path to the `file` containing the Google's Cloud KMS credentials.")
|
|
|
|
flag.StringVar(&project, "project", "", "Google Cloud Project ID.")
|
|
|
|
flag.StringVar(&location, "location", "global", "Cloud KMS location name.")
|
|
|
|
flag.StringVar(&ring, "ring", "pki", "Cloud KMS ring name.")
|
|
|
|
flag.StringVar(&protectionLevelName, "protection-level", "SOFTWARE", "Protection level to use, SOFTWARE or HSM.")
|
|
|
|
flag.BoolVar(&ssh, "ssh", false, "Create SSH keys.")
|
|
|
|
flag.Usage = usage
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case project == "":
|
|
|
|
usage()
|
|
|
|
case location == "":
|
|
|
|
fmt.Fprintln(os.Stderr, "flag `--location` is required")
|
|
|
|
os.Exit(1)
|
|
|
|
case ring == "":
|
|
|
|
fmt.Fprintln(os.Stderr, "flag `--ring` is required")
|
|
|
|
os.Exit(1)
|
|
|
|
case protectionLevelName == "":
|
|
|
|
fmt.Fprintln(os.Stderr, "flag `--protection-level` is required")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
var protectionLevel apiv1.ProtectionLevel
|
|
|
|
switch strings.ToUpper(protectionLevelName) {
|
|
|
|
case "SOFTWARE":
|
|
|
|
protectionLevel = apiv1.Software
|
|
|
|
case "HSM":
|
|
|
|
protectionLevel = apiv1.HSM
|
|
|
|
default:
|
|
|
|
fmt.Fprintf(os.Stderr, "invalid value `%s` for flag `--protection-level`; options are `SOFTWARE` or `HSM`\n", protectionLevelName)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := cloudkms.New(context.Background(), apiv1.Options{
|
|
|
|
Type: string(apiv1.CloudKMS),
|
|
|
|
CredentialsFile: credentialsFile,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := createPKI(c, project, location, ring, protectionLevel); err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ssh {
|
|
|
|
ui.Println()
|
|
|
|
if err := createSSH(c, project, location, ring, protectionLevel); err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func fatal(err error) {
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func usage() {
|
|
|
|
fmt.Fprintln(os.Stderr, "Usage: step-cloudkms-init --project <name>")
|
|
|
|
fmt.Fprintln(os.Stderr, `
|
|
|
|
The step-cloudkms-init command initializes a public key infrastructure (PKI)
|
|
|
|
to be used by step-ca.
|
|
|
|
|
|
|
|
This tool is experimental and in the future it will be integrated in step cli.
|
|
|
|
|
|
|
|
OPTIONS`)
|
|
|
|
fmt.Fprintln(os.Stderr)
|
|
|
|
flag.PrintDefaults()
|
|
|
|
fmt.Fprintln(os.Stderr, `
|
|
|
|
COPYRIGHT
|
|
|
|
|
|
|
|
(c) 2018-2020 Smallstep Labs, Inc.`)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func createPKI(c *cloudkms.CloudKMS, project, location, keyRing string, protectionLevel apiv1.ProtectionLevel) error {
|
|
|
|
ui.Println("Creating PKI ...")
|
|
|
|
|
|
|
|
parent := "projects/" + project + "/locations/" + location + "/keyRings/" + keyRing + "/cryptoKeys"
|
|
|
|
|
|
|
|
// Root Certificate
|
|
|
|
resp, err := c.CreateKey(&apiv1.CreateKeyRequest{
|
|
|
|
Name: parent + "/root",
|
|
|
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
|
|
|
ProtectionLevel: protectionLevel,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
signer, err := c.CreateSigner(&resp.CreateSignerRequest)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
root := &x509.Certificate{
|
|
|
|
IsCA: true,
|
|
|
|
NotBefore: now,
|
|
|
|
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
|
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
|
|
BasicConstraintsValid: true,
|
|
|
|
MaxPathLen: 1,
|
|
|
|
MaxPathLenZero: false,
|
|
|
|
Issuer: pkix.Name{CommonName: "Smallstep Root"},
|
|
|
|
Subject: pkix.Name{CommonName: "Smallstep Root"},
|
|
|
|
SerialNumber: mustSerialNumber(),
|
|
|
|
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := x509.CreateCertificate(rand.Reader, root, root, resp.PublicKey, signer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = utils.WriteFile("root_ca.crt", pem.EncodeToMemory(&pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: b,
|
|
|
|
}), 0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.PrintSelected("Root Key", resp.Name)
|
|
|
|
ui.PrintSelected("Root Certificate", "root_ca.crt")
|
|
|
|
|
|
|
|
root, err = pemutil.ReadCertificate("root_ca.crt")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Intermediate Certificate
|
|
|
|
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
|
|
|
|
Name: parent + "/intermediate",
|
|
|
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
|
|
|
ProtectionLevel: protectionLevel,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
intermediate := &x509.Certificate{
|
|
|
|
IsCA: true,
|
|
|
|
NotBefore: now,
|
|
|
|
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
|
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
|
|
BasicConstraintsValid: true,
|
|
|
|
MaxPathLen: 0,
|
|
|
|
MaxPathLenZero: true,
|
2020-02-21 19:14:11 +00:00
|
|
|
Issuer: root.Subject,
|
2020-02-19 03:07:12 +00:00
|
|
|
Subject: pkix.Name{CommonName: "Smallstep Intermediate"},
|
|
|
|
SerialNumber: mustSerialNumber(),
|
|
|
|
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err = x509.CreateCertificate(rand.Reader, intermediate, root, resp.PublicKey, signer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = utils.WriteFile("intermediate_ca.crt", pem.EncodeToMemory(&pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: b,
|
|
|
|
}), 0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.PrintSelected("Intermediate Key", resp.Name)
|
|
|
|
ui.PrintSelected("Intermediate Certificate", "intermediate_ca.crt")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createSSH(c *cloudkms.CloudKMS, project, location, keyRing string, protectionLevel apiv1.ProtectionLevel) error {
|
|
|
|
ui.Println("Creating SSH Keys ...")
|
|
|
|
|
|
|
|
parent := "projects/" + project + "/locations/" + location + "/keyRings/" + keyRing + "/cryptoKeys"
|
|
|
|
|
|
|
|
// User Key
|
|
|
|
resp, err := c.CreateKey(&apiv1.CreateKeyRequest{
|
|
|
|
Name: parent + "/ssh-user-key",
|
|
|
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
|
|
|
ProtectionLevel: protectionLevel,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := ssh.NewPublicKey(resp.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = utils.WriteFile("ssh_user_ca_key.pub", ssh.MarshalAuthorizedKey(key), 0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.PrintSelected("SSH User Public Key", "ssh_user_ca_key.pub")
|
|
|
|
ui.PrintSelected("SSH User Private Key", resp.Name)
|
|
|
|
|
|
|
|
// Host Key
|
|
|
|
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
|
|
|
|
Name: parent + "/ssh-host-key",
|
|
|
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
|
|
|
ProtectionLevel: apiv1.Software,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err = ssh.NewPublicKey(resp.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = utils.WriteFile("ssh_host_ca_key.pub", ssh.MarshalAuthorizedKey(key), 0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.PrintSelected("SSH Host Public Key", "ssh_host_ca_key.pub")
|
|
|
|
ui.PrintSelected("SSH Host Private Key", resp.Name)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustSerialNumber() *big.Int {
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
|
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return sn
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
|
|
|
b, err := x509.MarshalPKIXPublicKey(key)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
hash := sha1.Sum(b)
|
|
|
|
return hash[:]
|
|
|
|
}
|