2021-01-27 04:03:53 +00:00
package main
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"flag"
"fmt"
"math/big"
"os"
"runtime"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/uri"
"go.step.sm/cli-utils/fileutil"
"go.step.sm/cli-utils/ui"
"go.step.sm/crypto/pemutil"
// Enable pkcs11.
_ "github.com/smallstep/certificates/kms/pkcs11"
)
// Config is a mapping of the cli flags.
type Config struct {
KMS string
2021-11-12 18:00:32 +00:00
GenerateRoot bool
2021-01-27 04:03:53 +00:00
RootObject string
RootKeyObject string
2021-06-01 16:35:36 +00:00
RootSubject string
2021-06-01 16:46:00 +00:00
RootPath string
2021-01-27 04:03:53 +00:00
CrtObject string
2021-06-01 16:46:00 +00:00
CrtPath string
2021-01-27 04:03:53 +00:00
CrtKeyObject string
2021-06-01 16:35:36 +00:00
CrtSubject string
2021-06-01 16:46:00 +00:00
CrtKeyPath string
2021-01-27 04:03:53 +00:00
SSHHostKeyObject string
SSHUserKeyObject string
RootFile string
KeyFile string
Pin string
2021-01-29 04:13:28 +00:00
NoCerts bool
2021-01-27 04:03:53 +00:00
EnableSSH bool
Force bool
2021-06-17 13:06:35 +00:00
Extractable bool
2021-01-27 04:03:53 +00:00
}
// Validate checks the flags in the config.
func ( c * Config ) Validate ( ) error {
switch {
case c . KMS == "" :
2021-02-12 03:24:09 +00:00
return errors . New ( "flag `--kms` is required" )
2021-11-17 23:48:52 +00:00
case c . CrtPath == "" :
return errors . New ( "flag `--crt-cert-path` is required" )
2021-01-27 04:03:53 +00:00
case c . RootFile != "" && c . KeyFile == "" :
2021-11-17 23:48:52 +00:00
return errors . New ( "flag `--root-cert-file` requires flag `--root-key-file`" )
2021-01-27 04:03:53 +00:00
case c . KeyFile != "" && c . RootFile == "" :
2021-11-17 23:48:52 +00:00
return errors . New ( "flag `--root-key-file` requires flag `--root-cert-file`" )
2021-01-27 04:03:53 +00:00
case c . RootFile == "" && c . RootObject == "" :
2021-11-17 23:48:52 +00:00
return errors . New ( "one of flag `--root-cert-file` or `--root-cert-obj` is required" )
case c . KeyFile == "" && c . RootKeyObject == "" :
return errors . New ( "one of flag `--root-key-file` or `--root-key-obj` is required" )
case c . CrtKeyPath == "" && c . CrtKeyObject == "" :
return errors . New ( "one of flag `--crt-key-path` or `--crt-key-obj` is required" )
case c . RootFile == "" && c . GenerateRoot && c . RootKeyObject == "" :
return errors . New ( "flag `--root-gen` requires flag `--root-key-obj`" )
case c . RootFile == "" && c . GenerateRoot && c . RootPath == "" :
return errors . New ( "flag `--root-gen` requires `--root-cert-path`" )
2021-01-27 04:03:53 +00:00
default :
if c . RootFile != "" {
2021-11-17 23:48:52 +00:00
c . GenerateRoot = false
2021-01-27 04:03:53 +00:00
c . RootObject = ""
c . RootKeyObject = ""
}
2021-11-12 18:00:32 +00:00
if c . CrtKeyPath != "" {
2021-01-27 04:03:53 +00:00
c . CrtObject = ""
c . CrtKeyObject = ""
}
if ! c . EnableSSH {
c . SSHHostKeyObject = ""
c . SSHUserKeyObject = ""
}
return nil
}
}
func main ( ) {
var kmsuri string
switch runtime . GOOS {
case "darwin" :
kmsuri = "pkcs11:module-path=/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib;token=YubiHSM"
case "linux" :
kmsuri = "pkcs11:module-path=/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so;token=YubiHSM"
case "windows" :
if home , err := os . UserHomeDir ( ) ; err == nil {
kmsuri = "pkcs11:module-path=" + home + "\\yubihsm2-sdk\\bin\\yubihsm_pkcs11.dll" + ";token=YubiHSM"
}
}
var c Config
flag . StringVar ( & c . KMS , "kms" , kmsuri , "PKCS #11 URI with the module-path and token to connect to the module." )
2021-03-23 10:40:13 +00:00
flag . StringVar ( & c . Pin , "pin" , "" , "PKCS #11 PIN" )
2021-11-12 18:00:32 +00:00
// Option 1: Generate new root
flag . BoolVar ( & c . GenerateRoot , "root-gen" , true , "Enable the generation of a root key." )
2021-11-17 23:48:52 +00:00
flag . StringVar ( & c . RootSubject , "root-name" , "PKCS #11 Smallstep Root" , "Subject and Issuer of the root certificate." )
2021-11-12 18:00:32 +00:00
flag . StringVar ( & c . RootObject , "root-cert-obj" , "pkcs11:id=7330;object=root-cert" , "PKCS #11 URI with object id and label to store the root certificate." )
flag . StringVar ( & c . RootKeyObject , "root-key-obj" , "pkcs11:id=7330;object=root-key" , "PKCS #11 URI with object id and label to store the root key." )
// Option 2: Read root from disk and sign intermediate
2021-11-17 23:48:52 +00:00
flag . StringVar ( & c . RootFile , "root-cert-file" , "" , "Path to the root certificate to use." )
2021-11-12 18:00:32 +00:00
flag . StringVar ( & c . KeyFile , "root-key-file" , "" , "Path to the root key to use." )
// Option 3: Generate certificate signing request
2021-06-01 16:35:36 +00:00
flag . StringVar ( & c . CrtSubject , "crt-name" , "PKCS #11 Smallstep Intermediate" , "Subject of the intermediate certificate." )
2021-11-17 23:48:52 +00:00
flag . StringVar ( & c . CrtObject , "crt-cert-obj" , "pkcs11:id=7331;object=intermediate-cert" , "PKCS #11 URI with object id and label to store the intermediate certificate." )
flag . StringVar ( & c . CrtKeyObject , "crt-key-obj" , "pkcs11:id=7331;object=intermediate-key" , "PKCS #11 URI with object id and label to store the intermediate certificate." )
// SSH certificates
flag . BoolVar ( & c . EnableSSH , "ssh" , false , "Enable the creation of ssh keys." )
2021-01-27 04:03:53 +00:00
flag . StringVar ( & c . SSHHostKeyObject , "ssh-host-key" , "pkcs11:id=7332;object=ssh-host-key" , "PKCS #11 URI with object id and label to store the key used to sign SSH host certificates." )
flag . StringVar ( & c . SSHUserKeyObject , "ssh-user-key" , "pkcs11:id=7333;object=ssh-user-key" , "PKCS #11 URI with object id and label to store the key used to sign SSH user certificates." )
2021-11-17 23:48:52 +00:00
// Output files
flag . StringVar ( & c . RootPath , "root-cert-path" , "root_ca.crt" , "Location to write the root certificate." )
flag . StringVar ( & c . CrtPath , "crt-cert-path" , "intermediate_ca.crt" , "Location to write the intermediate certificate." )
flag . StringVar ( & c . CrtKeyPath , "crt-key-path" , "" , "Location to write the intermediate private key." )
// Others
2021-01-29 04:13:28 +00:00
flag . BoolVar ( & c . NoCerts , "no-certs" , false , "Do not store certificates in the module." )
2021-01-27 04:03:53 +00:00
flag . BoolVar ( & c . Force , "force" , false , "Force the delete of previous keys." )
2021-06-17 13:06:35 +00:00
flag . BoolVar ( & c . Extractable , "extractable" , false , "Allow export of private keys under wrap." )
2021-01-27 04:03:53 +00:00
flag . Usage = usage
flag . Parse ( )
if err := c . Validate ( ) ; err != nil {
fatal ( err )
}
u , err := uri . ParseWithScheme ( "pkcs11" , c . KMS )
if err != nil {
fatal ( err )
}
2021-10-07 18:09:32 +00:00
// Initialize windows terminal
ui . Init ( )
2021-06-15 17:19:42 +00:00
if u . Get ( "pin-value" ) == "" && u . Get ( "pin-source" ) == "" && c . Pin == "" {
2021-01-27 04:03:53 +00:00
pin , err := ui . PromptPassword ( "What is the PKCS#11 PIN?" )
if err != nil {
fatal ( err )
}
c . Pin = string ( pin )
}
k , err := kms . New ( context . Background ( ) , apiv1 . Options {
Type : string ( apiv1 . PKCS11 ) ,
URI : c . KMS ,
Pin : c . Pin ,
} )
if err != nil {
fatal ( err )
}
2021-02-01 23:28:09 +00:00
defer func ( ) {
_ = k . Close ( )
} ( )
2021-01-27 04:03:53 +00:00
// Check if the slots are empty, fail if they are not
certUris := [ ] string {
c . RootObject , c . CrtObject ,
}
keyUris := [ ] string {
c . RootKeyObject , c . CrtKeyObject ,
c . SSHHostKeyObject , c . SSHUserKeyObject ,
}
if ! c . Force {
for _ , u := range certUris {
2021-01-29 04:13:28 +00:00
if u != "" && ! c . NoCerts {
2021-01-27 04:03:53 +00:00
checkObject ( k , u )
2021-01-29 21:31:07 +00:00
checkCertificate ( k , u )
2021-01-27 04:03:53 +00:00
}
}
for _ , u := range keyUris {
if u != "" {
checkObject ( k , u )
}
}
} else {
deleter , ok := k . ( interface {
DeleteKey ( uri string ) error
DeleteCertificate ( uri string ) error
} )
if ok {
for _ , u := range certUris {
2021-01-29 04:13:28 +00:00
if u != "" && ! c . NoCerts {
2021-01-29 21:31:07 +00:00
// Some HSMs like Nitrokey will overwrite the key with the
// certificate label.
if err := deleter . DeleteKey ( u ) ; err != nil {
2021-02-01 23:28:09 +00:00
fatalClose ( err , k )
2021-01-29 21:31:07 +00:00
}
2021-01-27 04:03:53 +00:00
if err := deleter . DeleteCertificate ( u ) ; err != nil {
2021-02-01 23:28:09 +00:00
fatalClose ( err , k )
2021-01-27 04:03:53 +00:00
}
}
}
for _ , u := range keyUris {
if u != "" {
if err := deleter . DeleteKey ( u ) ; err != nil {
2021-02-01 23:28:09 +00:00
fatalClose ( err , k )
2021-01-27 04:03:53 +00:00
}
}
}
}
}
if err := createPKI ( k , c ) ; err != nil {
2021-02-01 23:28:09 +00:00
fatalClose ( err , k )
2021-01-27 04:03:53 +00:00
}
2021-10-07 19:43:24 +00:00
// Reset windows terminal
ui . Reset ( )
2021-01-27 04:03:53 +00:00
}
func fatal ( err error ) {
if os . Getenv ( "STEPDEBUG" ) == "1" {
fmt . Fprintf ( os . Stderr , "%+v\n" , err )
} else {
fmt . Fprintln ( os . Stderr , err )
}
2021-10-07 19:43:24 +00:00
ui . Reset ( )
2021-01-27 04:03:53 +00:00
os . Exit ( 1 )
}
2021-02-01 23:28:09 +00:00
func fatalClose ( err error , k kms . KeyManager ) {
_ = k . Close ( )
fatal ( err )
}
2021-01-27 04:03:53 +00:00
func usage ( ) {
fmt . Fprintln ( os . Stderr , "Usage: step-pkcs11-init" )
fmt . Fprintln ( os . Stderr , `
The step - pkcs11 - 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 ( )
2022-02-16 10:08:26 +00:00
fmt . Fprintf ( os . Stderr , `
2021-01-27 04:03:53 +00:00
COPYRIGHT
2022-02-16 10:08:26 +00:00
( c ) 2018 - % d Smallstep Labs , Inc .
` , time . Now ( ) . Year ( ) )
2021-01-27 04:03:53 +00:00
os . Exit ( 1 )
}
2021-01-29 21:31:07 +00:00
func checkCertificate ( k kms . KeyManager , rawuri string ) {
if cm , ok := k . ( kms . CertificateManager ) ; ok {
if _ , err := cm . LoadCertificate ( & apiv1 . LoadCertificateRequest {
Name : rawuri ,
} ) ; err == nil {
fmt . Fprintf ( os . Stderr , "⚠️ Your PKCS #11 module already has a certificate on %s.\n" , rawuri )
fmt . Fprintln ( os . Stderr , " If you want to delete it and start fresh, use `--force`." )
2021-02-01 23:28:09 +00:00
_ = k . Close ( )
2021-01-29 21:31:07 +00:00
os . Exit ( 1 )
}
}
}
2021-01-27 04:03:53 +00:00
func checkObject ( k kms . KeyManager , rawuri string ) {
if _ , err := k . GetPublicKey ( & apiv1 . GetPublicKeyRequest {
Name : rawuri ,
} ) ; err == nil {
fmt . Fprintf ( os . Stderr , "⚠️ Your PKCS #11 module already has a key on %s.\n" , rawuri )
fmt . Fprintln ( os . Stderr , " If you want to delete it and start fresh, use `--force`." )
2021-02-01 23:28:09 +00:00
_ = k . Close ( )
2021-01-27 04:03:53 +00:00
os . Exit ( 1 )
}
}
func createPKI ( k kms . KeyManager , c Config ) error {
var err error
ui . Println ( "Creating PKI ..." )
now := time . Now ( )
// Root Certificate
var signer crypto . Signer
var root * x509 . Certificate
2021-11-12 18:00:32 +00:00
switch {
case c . GenerateRoot :
2021-01-27 04:03:53 +00:00
resp , err := k . CreateKey ( & apiv1 . CreateKeyRequest {
Name : c . RootKeyObject ,
SignatureAlgorithm : apiv1 . ECDSAWithSHA256 ,
2021-06-17 13:06:35 +00:00
Extractable : c . Extractable ,
2021-01-27 04:03:53 +00:00
} )
if err != nil {
return err
}
signer , err = k . CreateSigner ( & resp . CreateSignerRequest )
if err != nil {
return err
}
template := & 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 ,
2021-06-01 16:35:36 +00:00
Issuer : pkix . Name { CommonName : c . RootSubject } ,
Subject : pkix . Name { CommonName : c . RootSubject } ,
2021-01-27 04:03:53 +00:00
SerialNumber : mustSerialNumber ( ) ,
SubjectKeyId : mustSubjectKeyID ( resp . PublicKey ) ,
AuthorityKeyId : mustSubjectKeyID ( resp . PublicKey ) ,
}
b , err := x509 . CreateCertificate ( rand . Reader , template , template , resp . PublicKey , signer )
if err != nil {
return err
}
root , err = x509 . ParseCertificate ( b )
if err != nil {
return errors . Wrap ( err , "error parsing root certificate" )
}
2021-11-17 23:48:52 +00:00
if cm , ok := k . ( kms . CertificateManager ) ; ok && c . RootObject != "" && ! c . NoCerts {
2021-10-08 18:59:57 +00:00
if err := cm . StoreCertificate ( & apiv1 . StoreCertificateRequest {
2021-01-27 04:03:53 +00:00
Name : c . RootObject ,
Certificate : root ,
2021-10-29 21:45:10 +00:00
Extractable : c . Extractable ,
2021-01-27 04:03:53 +00:00
} ) ; err != nil {
return err
}
2021-11-17 23:48:52 +00:00
} else {
c . RootObject = ""
2021-01-27 04:03:53 +00:00
}
2021-10-08 18:59:57 +00:00
if err := fileutil . WriteFile ( c . RootPath , pem . EncodeToMemory ( & pem . Block {
2021-01-27 04:03:53 +00:00
Type : "CERTIFICATE" ,
Bytes : b ,
} ) , 0600 ) ; err != nil {
return err
}
ui . PrintSelected ( "Root Key" , resp . Name )
2021-06-01 16:46:00 +00:00
ui . PrintSelected ( "Root Certificate" , c . RootPath )
2021-11-17 23:48:52 +00:00
if c . RootObject != "" {
ui . PrintSelected ( "Root Certificate Object" , c . RootObject )
}
2021-11-12 18:00:32 +00:00
case c . RootFile != "" && c . KeyFile != "" : // Read Root From File
root , err = pemutil . ReadCertificate ( c . RootFile )
if err != nil {
return err
}
key , err := pemutil . Read ( c . KeyFile )
if err != nil {
return err
}
var ok bool
if signer , ok = key . ( crypto . Signer ) ; ! ok {
return errors . Errorf ( "key type '%T' does not implement a signer" , key )
}
2021-01-27 04:03:53 +00:00
}
// Intermediate Certificate
var keyName string
var publicKey crypto . PublicKey
2021-11-12 19:09:17 +00:00
var intSigner crypto . Signer
2021-11-12 18:00:32 +00:00
if c . CrtKeyPath != "" {
2021-01-27 04:03:53 +00:00
priv , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
return errors . Wrap ( err , "error creating intermediate key" )
}
pass , err := ui . PromptPasswordGenerate ( "What do you want your password to be? [leave empty and we'll generate one]" ,
ui . WithRichPrompt ( ) )
if err != nil {
return err
}
2021-06-01 16:46:00 +00:00
_ , err = pemutil . Serialize ( priv , pemutil . WithPassword ( pass ) , pemutil . ToFile ( c . CrtKeyPath , 0600 ) )
2021-01-27 04:03:53 +00:00
if err != nil {
return err
}
publicKey = priv . Public ( )
2021-11-12 19:09:17 +00:00
intSigner = priv
2021-01-27 04:03:53 +00:00
} else {
resp , err := k . CreateKey ( & apiv1 . CreateKeyRequest {
Name : c . CrtKeyObject ,
SignatureAlgorithm : apiv1 . ECDSAWithSHA256 ,
2021-06-17 13:06:35 +00:00
Extractable : c . Extractable ,
2021-01-27 04:03:53 +00:00
} )
if err != nil {
return err
}
publicKey = resp . PublicKey
keyName = resp . Name
2021-11-12 19:09:17 +00:00
intSigner , err = k . CreateSigner ( & resp . CreateSignerRequest )
if err != nil {
return err
}
2021-01-27 04:03:53 +00:00
}
2021-11-12 19:09:17 +00:00
if root != nil {
template := & 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 ,
Issuer : root . Subject ,
Subject : pkix . Name { CommonName : c . CrtSubject } ,
SerialNumber : mustSerialNumber ( ) ,
SubjectKeyId : mustSubjectKeyID ( publicKey ) ,
}
2021-01-27 04:03:53 +00:00
2021-11-12 19:09:17 +00:00
b , err := x509 . CreateCertificate ( rand . Reader , template , root , publicKey , signer )
if err != nil {
2021-01-27 04:03:53 +00:00
return err
}
2021-11-12 19:09:17 +00:00
intermediate , err := x509 . ParseCertificate ( b )
if err != nil {
return errors . Wrap ( err , "error parsing intermediate certificate" )
}
2021-11-17 23:48:52 +00:00
if cm , ok := k . ( kms . CertificateManager ) ; ok && c . CrtObject != "" && ! c . NoCerts {
2021-11-12 19:09:17 +00:00
if err := cm . StoreCertificate ( & apiv1 . StoreCertificateRequest {
Name : c . CrtObject ,
Certificate : intermediate ,
Extractable : c . Extractable ,
} ) ; err != nil {
return err
}
2021-11-17 23:48:52 +00:00
} else {
c . CrtObject = ""
2021-11-12 19:09:17 +00:00
}
if err := fileutil . WriteFile ( c . CrtPath , pem . EncodeToMemory ( & pem . Block {
Type : "CERTIFICATE" ,
Bytes : b ,
} ) , 0600 ) ; err != nil {
return err
}
2021-11-17 23:48:52 +00:00
} else {
// No root available, generate CSR for external root.
2021-11-12 19:09:17 +00:00
csrTemplate := x509 . CertificateRequest {
Subject : pkix . Name { CommonName : c . CrtSubject } ,
SignatureAlgorithm : x509 . ECDSAWithSHA256 ,
}
// step: generate the csr request
csrCertificate , err := x509 . CreateCertificateRequest ( rand . Reader , & csrTemplate , intSigner )
if err != nil {
return err
}
if err := fileutil . WriteFile ( c . CrtPath , pem . EncodeToMemory ( & pem . Block {
Type : "CERTIFICATE REQUEST" ,
Bytes : csrCertificate ,
} ) , 0600 ) ; err != nil {
return err
}
2021-01-27 04:03:53 +00:00
}
2021-11-12 18:00:32 +00:00
if c . CrtKeyPath != "" {
2021-06-01 16:46:00 +00:00
ui . PrintSelected ( "Intermediate Key" , c . CrtKeyPath )
2021-01-27 04:03:53 +00:00
} else {
ui . PrintSelected ( "Intermediate Key" , keyName )
}
2021-11-17 23:48:52 +00:00
if root != nil {
ui . PrintSelected ( "Intermediate Certificate" , c . CrtPath )
if c . CrtObject != "" {
ui . PrintSelected ( "Intermediate Certificate Object" , c . CrtObject )
}
} else {
ui . PrintSelected ( "Intermediate Certificate Request" , c . CrtPath )
}
2021-01-27 04:03:53 +00:00
if c . SSHHostKeyObject != "" {
resp , err := k . CreateKey ( & apiv1 . CreateKeyRequest {
Name : c . SSHHostKeyObject ,
SignatureAlgorithm : apiv1 . ECDSAWithSHA256 ,
} )
if err != nil {
return err
}
ui . PrintSelected ( "SSH Host Key" , resp . Name )
}
if c . SSHUserKeyObject != "" {
resp , err := k . CreateKey ( & apiv1 . CreateKeyRequest {
Name : c . SSHUserKeyObject ,
SignatureAlgorithm : apiv1 . ECDSAWithSHA256 ,
} )
if err != nil {
return err
}
ui . PrintSelected ( "SSH User 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 [ : ]
}