Merge pull request #1226 from openziti/add.pki.ec.support.65

fixed #65 adds EC generation support to the PKI sub command
This commit is contained in:
Andrew
2023-08-07 15:56:14 -04:00
committed by GitHub
16 changed files with 343 additions and 138 deletions
+1 -1
View File
@@ -13,7 +13,6 @@ require (
github.com/go-openapi/runtime v0.26.0
github.com/go-openapi/strfmt v0.21.7
github.com/go-resty/resty/v2 v2.7.0
github.com/golang/protobuf v1.5.3
github.com/google/go-cmp v0.5.9
github.com/gorilla/websocket v1.5.0
github.com/jedib0t/go-pretty/v6 v6.4.0
@@ -89,6 +88,7 @@ require (
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
+20 -2
View File
@@ -21,6 +21,7 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
@@ -200,10 +201,27 @@ func generateCert(options *verifyCaOptions, token string) ([]byte, crypto.Signer
}
var caKey crypto.Signer
if caPrivateKeyBlock.Type == "EC PRIVATE KEY" {
switch caPrivateKeyBlock.Type {
case "EC PRIVATE KEY":
caKey, err = x509.ParseECPrivateKey(caPrivateKeyBlock.Bytes)
} else {
case "RSA PRIVATE KEY":
caKey, err = x509.ParsePKCS1PrivateKey(caPrivateKeyBlock.Bytes)
case "PRIVATE KEY":
key, err := x509.ParsePKCS8PrivateKey(caPrivateKeyBlock.Bytes)
if err != nil {
return nil, nil, fmt.Errorf("could not parse PKCS8 key file (%s)", err)
}
switch typedKey := key.(type) {
case *rsa.PrivateKey:
caKey = typedKey
case *ecdsa.PrivateKey:
caKey = typedKey
default:
return nil, nil, fmt.Errorf("unsupported private key parsed from PKCS8 %T", typedKey)
}
}
if err != nil {
+3 -1
View File
@@ -181,7 +181,7 @@ func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.Priva
privateKey, err := loadPrivateKey(accKeyPath)
if err != nil {
log.Fatalf("Could not load RSA private key from file %s: %v", accKeyPath, err)
log.Fatalf("Could not load private key from file %s: %v", accKeyPath, err)
}
return privateKey
@@ -227,6 +227,8 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(keyBlock.Bytes)
case "PRIVATE KEY":
return x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
}
return nil, errors.New("unknown private key type")
+2 -1
View File
@@ -43,7 +43,7 @@ type PKIFlags struct {
CAName string
CommonName string
CAExpire int
CAMaxpath int
CAMaxPath int
CAPrivateKeySize int
IntermediateFile string
IntermediateName string
@@ -61,6 +61,7 @@ type PKIFlags struct {
PKI *pki.ZitiPKI
SpiffeID string
AllowOverwrite bool
EcCurve string
}
var (
+108 -59
View File
@@ -17,12 +17,14 @@
package cmd
import (
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
"github.com/openziti/ziti/ziti/pki/pki"
"github.com/openziti/ziti/ziti/util"
"io"
"net"
@@ -119,111 +121,111 @@ func (o *PKICreateOptions) Run() error {
// ObtainPKIRoot returns the value for pki-root
func (o *PKICreateOptions) ObtainPKIRoot() (string, error) {
pkiroot := o.Flags.PKIRoot
if pkiroot == "" {
pkiroot = viper.GetString("pki-root")
if pkiroot == "" {
pkiRoot := o.Flags.PKIRoot
if pkiRoot == "" {
pkiRoot = viper.GetString("pki-root")
if pkiRoot == "" {
pkiRootDir, err := util.PKIRootDir()
if err != nil {
return "", err
}
pkiroot, err = util.PickValue("Required flag 'pki-root' not specified; Enter PKI Root now:", pkiRootDir, true)
pkiRoot, err = util.PickValue("Required flag 'pki-root' not specified; Enter PKI Root now:", pkiRootDir, true)
if err != nil {
return "", err
}
}
}
return pkiroot, nil
return pkiRoot, nil
}
// ObtainCAFile returns the value for ca-file
func (o *PKICreateOptions) ObtainCAFile() (string, error) {
cafile := o.Flags.CAFile
if cafile == "" {
cafile = viper.GetString("ca-file")
if cafile == "" {
caFile := o.Flags.CAFile
if caFile == "" {
caFile = viper.GetString("ca-file")
if caFile == "" {
var err error
cafile, err = util.PickValue("Required flag 'ca-file' not specified; Enter CA name now:", "ca", true)
caFile, err = util.PickValue("Required flag 'ca-file' not specified; Enter CA name now:", "ca", true)
if err != nil {
return "", err
}
}
}
return cafile, nil
return caFile, nil
}
// ObtainIntermediateCAFile returns the value for intermediate-file
func (o *PKICreateOptions) ObtainIntermediateCAFile() (string, error) {
intermediatefile := o.Flags.IntermediateFile
if intermediatefile == "" {
intermediatefile = viper.GetString("intermediate-file")
if intermediatefile == "" {
intermediateFile := o.Flags.IntermediateFile
if intermediateFile == "" {
intermediateFile = viper.GetString("intermediate-file")
if intermediateFile == "" {
var err error
intermediatefile, err = util.PickValue("Required flag 'intermediate-file' not specified; Enter Intermediate CA name now:", "intermediate", true)
intermediateFile, err = util.PickValue("Required flag 'intermediate-file' not specified; Enter Intermediate CA name now:", "intermediate", true)
if err != nil {
return "", err
}
}
}
return intermediatefile, nil
return intermediateFile, nil
}
// ObtainIntermediateCSRFile returns the value for intermediate-file
func (o *PKICreateOptions) ObtainIntermediateCSRFile() (string, error) {
intermediatecsrfile := viper.GetString("intermediate-csr-file")
if intermediatecsrfile == "" {
intermediateCsrFile := viper.GetString("intermediate-csr-file")
if intermediateCsrFile == "" {
var err error
intermediatecsrfile, err = util.PickValue("Required flag 'intermediate--csr-file' not specified; Enter Intermediate CSR file name now:", "intermediate-csr", true)
intermediateCsrFile, err = util.PickValue("Required flag 'intermediate--csr-file' not specified; Enter Intermediate CSR file name now:", "intermediate-csr", true)
if err != nil {
return "", err
}
}
return intermediatecsrfile, nil
return intermediateCsrFile, nil
}
// ObtainCSRFile returns the value for csr-file
func (o *PKICreateOptions) ObtainCSRFile() (string, error) {
csrfile := viper.GetString("csr-file")
if csrfile == "" {
csrFile := viper.GetString("csr-file")
if csrFile == "" {
var err error
csrfile, err = util.PickValue("Required flag 'csr-file' not specified; Enter CSR name now:", "csr", true)
csrFile, err = util.PickValue("Required flag 'csr-file' not specified; Enter CSR name now:", "csr", true)
if err != nil {
return "", err
}
}
return csrfile, nil
return csrFile, nil
}
// ObtainServerCertFile returns the value for server-file
func (o *PKICreateOptions) ObtainServerCertFile() (string, error) {
serverfile := o.Flags.ServerFile
if serverfile == "" {
serverfile = viper.GetString("server-file")
if serverfile == "" {
serverFile := o.Flags.ServerFile
if serverFile == "" {
serverFile = viper.GetString("server-file")
if serverFile == "" {
var err error
serverfile, err = util.PickValue("Required flag 'server-file' not specified; Enter name now:", "server", true)
serverFile, err = util.PickValue("Required flag 'server-file' not specified; Enter name now:", "server", true)
if err != nil {
return "", err
}
}
}
return serverfile, nil
return serverFile, nil
}
// ObtainClientCertFile returns the value for client-file
func (o *PKICreateOptions) ObtainClientCertFile() (string, error) {
clientfile := o.Flags.ClientFile
if clientfile == "" {
clientfile = viper.GetString("client-file")
if clientfile == "" {
clientFile := o.Flags.ClientFile
if clientFile == "" {
clientFile = viper.GetString("client-file")
if clientFile == "" {
var err error
clientfile, err = util.PickValue("Required flag 'client-file' not specified; Enter name now:", "client", true)
clientFile, err = util.PickValue("Required flag 'client-file' not specified; Enter name now:", "client", true)
if err != nil {
return "", err
}
}
}
return clientfile, nil
return clientFile, nil
}
// ObtainKeyFile returns the value for key-file
@@ -245,13 +247,13 @@ func (o *PKICreateOptions) ObtainKeyFile(required bool) (string, error) {
}
// ObtainCAName returns the value for ca-name
func (o *PKICreateOptions) ObtainCAName(pkiroot string) (string, error) {
caname := o.Flags.CAName
if caname == "" {
caname = viper.GetString("ca-name")
if caname == "" {
func (o *PKICreateOptions) ObtainCAName(pkiRoot string) (string, error) {
caName := o.Flags.CAName
if caName == "" {
caName = viper.GetString("ca-name")
if caName == "" {
var err error
files, err := os.ReadDir(pkiroot)
files, err := os.ReadDir(pkiRoot)
if err != nil {
return "", err
}
@@ -263,14 +265,14 @@ func (o *PKICreateOptions) ObtainCAName(pkiroot string) (string, error) {
}
}
}
caname, err = util.PickName(names, "Required flag 'ca-name' not specified; choose from below (dirs seen in your ZITI_PKI_ROOT):")
caName, err = util.PickName(names, "Required flag 'ca-name' not specified; choose from below (dirs seen in your ZITI_PKI_ROOT):")
if err != nil {
return "", err
}
}
}
fmt.Println("Using CA name: ", caname)
return caname, nil
fmt.Println("Using CA name: ", caName)
return caName, nil
}
// ObtainCommonName returns the value for CN
@@ -290,9 +292,9 @@ func (o *PKICreateOptions) ObtainCommonName() (string, error) {
}
// ObtainFileName returns the value for the 'name' used in the PKI request
func (o *PKICreateOptions) ObtainFileName(cafile string, commonName string) string {
func (o *PKICreateOptions) ObtainFileName(caFile string, commonName string) string {
var filename string
if filename = cafile; len(cafile) == 0 {
if filename = caFile; len(caFile) == 0 {
filename = strings.Replace(commonName, " ", "_", -1)
filename = strings.Replace(filename, "*", "wildcard", -1)
}
@@ -322,18 +324,18 @@ func (o *PKICreateOptions) ObtainPKIRequestTemplate(commonName string) *x509.Cer
template := &x509.Certificate{
Subject: subject,
NotAfter: time.Now().AddDate(0, 0, o.Flags.CAExpire),
MaxPathLen: o.Flags.CAMaxpath,
MaxPathLen: o.Flags.CAMaxPath,
}
return template
}
// ObtainKeyName returns the private key from the key-file
func (o *PKICreateOptions) ObtainKeyName(pkiroot string) (string, error) {
keyname := viper.GetString("key-name")
if keyname == "" {
func (o *PKICreateOptions) ObtainKeyName(pkiRoot string) (string, error) {
keyName := viper.GetString("key-name")
if keyName == "" {
var err error
files, err := os.ReadDir(pkiroot)
files, err := os.ReadDir(pkiRoot)
if err != nil {
return "", err
}
@@ -343,13 +345,13 @@ func (o *PKICreateOptions) ObtainKeyName(pkiroot string) (string, error) {
names = append(names, f.Name())
}
}
keyname, err = util.PickName(names, "Required flag 'key-name' not specified; choose from below (dirs seen in your ZITI_PKI_ROOT):")
keyName, err = util.PickName(names, "Required flag 'key-name' not specified; choose from below (dirs seen in your ZITI_PKI_ROOT):")
if err != nil {
return "", err
}
}
return keyname, nil
return keyName, nil
}
// ObtainPKICSRRequestTemplate returns the CSR 'template' used in the PKI request
@@ -380,8 +382,7 @@ func (o *PKICreateOptions) ObtainPKICSRRequestTemplate(commonName string) *x509.
val, _ := asn1.Marshal(basicConstraints{true, 0})
csrTemplate := &x509.CertificateRequest{
Subject: subject,
SignatureAlgorithm: x509.SHA512WithRSA,
Subject: subject,
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{2, 5, 29, 19},
@@ -394,7 +395,7 @@ func (o *PKICreateOptions) ObtainPKICSRRequestTemplate(commonName string) *x509.
return csrTemplate
}
// ObtainIPsAndDNSNames returns the IP addrs and/or DNS names used in the PKI request template
// ObtainIPsAndDNSNames returns the IP address and/or DNS names used in the PKI request template
func (o *PKICreateOptions) ObtainIPsAndDNSNames() ([]net.IP, []string, error) {
if (len(o.Flags.IP) == 0) && (len(o.Flags.DNSName) == 0) {
@@ -410,3 +411,51 @@ func (o *PKICreateOptions) ObtainIPsAndDNSNames() ([]net.IP, []string, error) {
return IPs, o.Flags.DNSName, nil
}
// ObtainPrivateKeyOptions returns the private key options necessary to generate a private key
func (o *PKICreateOptions) ObtainPrivateKeyOptions() (pki.PrivateKeyOptions, error) {
isEc := o.Flags.EcCurve != ""
if isEc {
return o.obtainEcOptions()
}
return o.obtainRsaOptions()
}
func (o *PKICreateOptions) obtainEcOptions() (pki.PrivateKeyOptions, error) {
target := strings.Replace(strings.ToLower(o.Flags.EcCurve), "-", "", -1)
validCurves := []elliptic.Curve{
elliptic.P224(),
elliptic.P256(),
elliptic.P384(),
elliptic.P521(),
}
for _, validCurve := range validCurves {
curCurveName := strings.Replace(strings.ToLower(validCurve.Params().Name), "-", "", -1)
if curCurveName == target {
return &pki.EcPrivateKeyOptions{
Curve: validCurve,
}, nil
}
}
validCurveString := ""
for _, validCurve := range validCurves {
if validCurveString != "" {
validCurveString = validCurveString + ", "
}
validCurveString = validCurveString + strings.Replace(validCurve.Params().Name, "-", "", -1)
}
return nil, fmt.Errorf("unknown curve name '%s', valid curves (case and dash insensitive) %s", o.Flags.EcCurve, validCurveString)
}
func (o *PKICreateOptions) obtainRsaOptions() (pki.PrivateKeyOptions, error) {
return &pki.RsaPrivateKeyOptions{
Size: o.Flags.CAPrivateKeySize,
}, nil
}
+15 -7
View File
@@ -17,6 +17,7 @@
package cmd
import (
"fmt"
"github.com/openziti/fabric/controller/idgen"
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
"github.com/openziti/ziti/ziti/internal/log"
@@ -70,23 +71,24 @@ func (o *PKICreateCAOptions) addPKICreateCAFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.Flags.CAFile, "ca-file", "", "", "Dir/File name (within PKI_ROOT) in which to store new CA")
cmd.Flags().StringVarP(&o.Flags.CAName, FlagCaName, "", "NetFoundry Inc. Certificate Authority", "Name of CA")
cmd.Flags().IntVarP(&o.Flags.CAExpire, "expire-limit", "", 3650, "Expiration limit in days")
cmd.Flags().IntVarP(&o.Flags.CAMaxpath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the private key")
cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the RSA private key, ignored if -curve is set")
cmd.Flags().StringVarP(&o.Flags.EcCurve, "curve", "", "", "If set an EC private key is generated and -private-key-size is ignored, options: P224, P256, P384, P521")
cmd.Flags().StringVar(&o.Flags.SpiffeID, "trust-domain", "", "An optional spiffe trust domain. spiffe:// will be automatically prefixed, if not provided")
}
// Run implements this command
func (o *PKICreateCAOptions) Run() error {
pkiroot, err := o.ObtainPKIRoot()
pkiRoot, err := o.ObtainPKIRoot()
if err != nil {
return err
}
o.Flags.PKI = &pki.ZitiPKI{Store: &store.Local{}}
local := o.Flags.PKI.Store.(*store.Local)
local.Root = pkiroot
local.Root = pkiRoot
cafile, err := o.ObtainCAFile()
caFile, err := o.ObtainCAFile()
if err != nil {
return err
}
@@ -98,7 +100,7 @@ func (o *PKICreateCAOptions) Run() error {
commonName := o.Flags.CAName
filename := o.ObtainFileName(cafile, commonName)
filename := o.ObtainFileName(caFile, commonName)
template := o.ObtainPKIRequestTemplate(commonName)
template.IsCA = true
@@ -114,13 +116,19 @@ func (o *PKICreateCAOptions) Run() error {
template.URIs = append(template.URIs, spiffeId)
}
privateKeyOptions, err := o.ObtainPrivateKeyOptions()
if err != nil {
return fmt.Errorf("could not resolve private key options: %w", err)
}
var signer *certificate.Bundle
req := &pki.Request{
Name: filename,
Template: template,
IsClientCertificate: false,
PrivateKeySize: o.Flags.CAPrivateKeySize,
PrivateKeyOptions: privateKeyOptions,
}
if err = o.Flags.PKI.Sign(signer, req); err != nil {
+15 -7
View File
@@ -17,6 +17,7 @@
package cmd
import (
"fmt"
"github.com/openziti/fabric/controller/idgen"
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
"github.com/openziti/ziti/ziti/internal/log"
@@ -73,21 +74,22 @@ func (o *PKICreateClientOptions) addPKICreateClientFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.Flags.ClientName, FlagCaClientName, "", "NetFoundry Inc. Client", "Common Name (CN) to use for new Client certificate")
cmd.Flags().StringSliceVar(&o.Flags.Email, "email", []string{}, "Email addr(s) to add to Subject Alternate Name (SAN) for new Client certificate")
cmd.Flags().IntVarP(&o.Flags.CAExpire, "expire-limit", "", 365, "Expiration limit in days")
cmd.Flags().IntVarP(&o.Flags.CAMaxpath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 2048, "Size of the private key")
cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the RSA private key, ignored if -curve is set")
cmd.Flags().StringVarP(&o.Flags.EcCurve, "curve", "", "", "If set an EC private key is generated and -private-key-size is ignored, options: P224, P256, P384, P521")
cmd.Flags().StringVar(&o.Flags.SpiffeID, "spiffe-id", "", "Optionally provide the path portion of a SPIFFE id. The trust domain will be taken from the signing certificate.")
}
// Run implements this command
func (o *PKICreateClientOptions) Run() error {
pkiroot, err := o.ObtainPKIRoot()
pkiRoot, err := o.ObtainPKIRoot()
if err != nil {
return err
}
o.Flags.PKI = &pki.ZitiPKI{Store: &store.Local{}}
local := o.Flags.PKI.Store.(*store.Local)
local.Root = pkiroot
local.Root = pkiRoot
if !o.Cmd.Flags().Changed(FlagCaClientName) {
o.Flags.ClientName = o.Flags.ClientName + " " + idgen.New()
@@ -108,7 +110,7 @@ func (o *PKICreateClientOptions) Run() error {
var signer *certificate.Bundle
caname, err := o.ObtainCAName(pkiroot)
caName, err := o.ObtainCAName(pkiRoot)
if err != nil {
return err
}
@@ -118,7 +120,7 @@ func (o *PKICreateClientOptions) Run() error {
return err
}
signer, err = o.Flags.PKI.GetCA(caname)
signer, err = o.Flags.PKI.GetCA(caName)
if err != nil {
return errors.Wrap(err, "cannot locate signer")
}
@@ -143,12 +145,18 @@ func (o *PKICreateClientOptions) Run() error {
template.URIs = append(template.URIs, &spiffId)
}
privateKeyOptions, err := o.ObtainPrivateKeyOptions()
if err != nil {
return fmt.Errorf("could not resolve private key options: %w", err)
}
req := &pki.Request{
Name: filename,
KeyName: keyFile,
Template: template,
IsClientCertificate: true,
PrivateKeySize: o.Flags.CAPrivateKeySize,
PrivateKeyOptions: privateKeyOptions,
}
if err := o.Flags.PKI.Sign(signer, req); err != nil {
+9 -10
View File
@@ -64,28 +64,27 @@ func (o *PKICreateCSROptions) addPKICreateCSRFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.Flags.CSRName, "csr-name", "", "NetFoundry Inc. CSR", "Name of CSR")
cmd.Flags().StringVarP(&o.Flags.KeyName, "key-name", "", "", "Name of file that contains private key for CSR")
cmd.Flags().IntVarP(&o.Flags.CAExpire, "expire-limit", "", 365, "Expiration limit in days")
cmd.Flags().IntVarP(&o.Flags.CAMaxpath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the private key")
cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", -1, "Intermediate maximum path length")
}
// Run implements this command
func (o *PKICreateCSROptions) Run() error {
pkiroot, err := o.ObtainPKIRoot()
pkiRoot, err := o.ObtainPKIRoot()
if err != nil {
return fmt.Errorf("%s", err)
}
o.Flags.PKI = &pki.ZitiPKI{Store: &store.Local{}}
local := o.Flags.PKI.Store.(*store.Local)
local.Root = pkiroot
local.Root = pkiRoot
csrfile, err := o.ObtainCSRFile()
csrFile, err := o.ObtainCSRFile()
if err != nil {
return fmt.Errorf("%s", err)
}
keyname, err := o.ObtainKeyName(pkiroot)
keyName, err := o.ObtainKeyName(pkiRoot)
if err != nil {
return fmt.Errorf("%s", err)
}
@@ -93,13 +92,13 @@ func (o *PKICreateCSROptions) Run() error {
commonName := o.Flags.CSRName
template := o.ObtainPKICSRRequestTemplate(commonName)
key, err := o.Flags.PKI.GetPrivateKey(keyname, keyname)
key, err := o.Flags.PKI.GetPrivateKey(keyName, keyName)
if err != nil {
return fmt.Errorf("Cannot locate private key: %v", err)
return fmt.Errorf("cannot locate private key: %v", err)
}
if err := o.Flags.PKI.CSR(keyname, csrfile, *template, key); err != nil {
return fmt.Errorf("Cannot create CSR: %v", err)
if err := o.Flags.PKI.CSR(keyName, csrFile, *template, key); err != nil {
return fmt.Errorf("cannot create CSR: %v", err)
}
log.Infoln("Success")
+17 -9
View File
@@ -17,6 +17,7 @@
package cmd
import (
"fmt"
"github.com/openziti/fabric/controller/idgen"
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
"github.com/openziti/ziti/ziti/internal/log"
@@ -70,22 +71,23 @@ func (o *PKICreateIntermediateOptions) addPKICreateIntermediateFlags(cmd *cobra.
cmd.Flags().StringVarP(&o.Flags.IntermediateFile, "intermediate-file", "", "intermediate", "Dir/File name (within PKI_ROOT) in which to store new Intermediate CA")
cmd.Flags().StringVarP(&o.Flags.IntermediateName, FlagCaIntermediateName, "", "NetFoundry Inc. Intermediate CA", "Common Name (CN) to use for new Intermediate CA")
cmd.Flags().IntVarP(&o.Flags.CAExpire, "expire-limit", "", 3650, "Expiration limit in days")
cmd.Flags().IntVarP(&o.Flags.CAMaxpath, "max-path-len", "", 0, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the private key")
cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", 0, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the RSA private key, ignored if -curve is set")
cmd.Flags().StringVarP(&o.Flags.EcCurve, "curve", "", "", "If set an EC private key is generated and -private-key-size is ignored, options: P224, P256, P384, P521")
}
// Run implements this command
func (o *PKICreateIntermediateOptions) Run() error {
pkiroot, err := o.ObtainPKIRoot()
pkiRoot, err := o.ObtainPKIRoot()
if err != nil {
return err
}
o.Flags.PKI = &pki.ZitiPKI{Store: &store.Local{}}
local := o.Flags.PKI.Store.(*store.Local)
local.Root = pkiroot
local.Root = pkiRoot
intermediatefile, err := o.ObtainIntermediateCAFile()
intermediateFile, err := o.ObtainIntermediateCAFile()
if err != nil {
return err
}
@@ -96,19 +98,19 @@ func (o *PKICreateIntermediateOptions) Run() error {
commonName := o.Flags.IntermediateName
filename := o.ObtainFileName(intermediatefile, commonName)
filename := o.ObtainFileName(intermediateFile, commonName)
template := o.ObtainPKIRequestTemplate(commonName)
template.IsCA = true
var signer *certificate.Bundle
caname, err := o.ObtainCAName(pkiroot)
caName, err := o.ObtainCAName(pkiRoot)
if err != nil {
return err
}
signer, err = o.Flags.PKI.GetCA(caname)
signer, err = o.Flags.PKI.GetCA(caName)
if err != nil {
return errors.Wrap(err, "cannot locate signer")
}
@@ -119,11 +121,17 @@ func (o *PKICreateIntermediateOptions) Run() error {
}
}
privateKeyOptions, err := o.ObtainPrivateKeyOptions()
if err != nil {
return fmt.Errorf("could not resolve private key options: %w", err)
}
req := &pki.Request{
Name: filename,
Template: template,
IsClientCertificate: false,
PrivateKeySize: o.Flags.CAPrivateKeySize,
PrivateKeyOptions: privateKeyOptions,
}
if err := o.Flags.PKI.Sign(signer, req); err != nil {
+15 -8
View File
@@ -65,27 +65,28 @@ func (o *PKICreateKeyOptions) addPKICreateKeyFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.Flags.PKIRoot, "pki-root", "", "", "Directory in which PKI resides")
cmd.Flags().StringVarP(&o.Flags.CAName, "ca-name", "", "intermediate", "Name of Intermediate CA (within PKI_ROOT) to use to sign the new Client certificate")
cmd.Flags().StringVarP(&o.Flags.KeyFile, "key-file", "", "key", "Name of file (under chosen CA) in which to store new private key")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the private key")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the RSA private key, ignored if -curve is set")
cmd.Flags().StringVarP(&o.Flags.EcCurve, "curve", "", "", "If set an EC private key is generated and -private-key-size is ignored, options: P224, P256, P384, P521")
}
// Run implements this command
func (o *PKICreateKeyOptions) Run() error {
pkiroot, err := o.ObtainPKIRoot()
pkiRoot, err := o.ObtainPKIRoot()
if err != nil {
return fmt.Errorf("%s", err)
}
o.Flags.PKI = &pki.ZitiPKI{Store: &store.Local{}}
local := o.Flags.PKI.Store.(*store.Local)
local.Root = pkiroot
local.Root = pkiRoot
keyFile, err := o.ObtainKeyFile(true)
if err != nil {
return fmt.Errorf("%s", err)
}
caname, err := o.ObtainCAName(pkiroot)
caName, err := o.ObtainCAName(pkiRoot)
if err != nil {
return fmt.Errorf("%s", err)
}
@@ -93,20 +94,26 @@ func (o *PKICreateKeyOptions) Run() error {
template := o.ObtainPKIRequestTemplate("")
var signer *certificate.Bundle
signer, err = o.Flags.PKI.GetCA(caname)
signer, err = o.Flags.PKI.GetCA(caName)
if err != nil {
return fmt.Errorf("Cannot locate signer: %v", err)
return fmt.Errorf("cannot locate signer: %v", err)
}
privateKeyOptions, err := o.ObtainPrivateKeyOptions()
if err != nil {
return fmt.Errorf("could not resolve private key options: %w", err)
}
req := &pki.Request{
KeyName: keyFile,
Template: template,
IsClientCertificate: false,
PrivateKeySize: o.Flags.CAPrivateKeySize,
PrivateKeyOptions: privateKeyOptions,
}
if err := o.Flags.PKI.GeneratePrivateKey(signer, req); err != nil {
return fmt.Errorf("Cannot Generate Private key: %v", err)
return fmt.Errorf("cannot Generate Private key: %v", err)
}
log.Infoln("Success")
+11 -3
View File
@@ -17,6 +17,7 @@
package cmd
import (
"fmt"
"github.com/openziti/fabric/controller/idgen"
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
"github.com/openziti/ziti/ziti/internal/log"
@@ -74,8 +75,9 @@ func (o *PKICreateServerOptions) addPKICreateServerFlags(cmd *cobra.Command) {
cmd.Flags().StringSliceVar(&o.Flags.DNSName, "dns", []string{}, "DNS name(s) to add to Subject Alternate Name (SAN) for new Server certificate")
cmd.Flags().StringSliceVar(&o.Flags.IP, "ip", []string{}, "IP addr(s) to add to Subject Alternate Name (SAN) for new Server certificate")
cmd.Flags().IntVarP(&o.Flags.CAExpire, "expire-limit", "", 365, "Expiration limit in days")
cmd.Flags().IntVarP(&o.Flags.CAMaxpath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the private key")
cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", -1, "Intermediate maximum path length")
cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the RSA private key, ignored if -curve is set")
cmd.Flags().StringVarP(&o.Flags.EcCurve, "curve", "", "", "If set an EC private key is generated and -private-key-size is ignored, options: P224, P256, P384, P521")
cmd.Flags().StringVar(&o.Flags.SpiffeID, "spiffe-id", "", "Optionally provide the path portion of a SPIFFE id. The trust domain will be taken from the signing certificate.")
cmd.Flags().BoolVar(&o.Flags.AllowOverwrite, "allow-overwrite", false, "Allow overwrite existing certs")
}
@@ -151,12 +153,18 @@ func (o *PKICreateServerOptions) Run() error {
template.URIs = append(template.URIs, &spiffId)
}
privateKeyOptions, err := o.ObtainPrivateKeyOptions()
if err != nil {
return fmt.Errorf("could not resolve private key options: %w", err)
}
req := &pki.Request{
Name: filename,
KeyName: keyFile,
Template: template,
IsClientCertificate: false,
PrivateKeySize: o.Flags.CAPrivateKeySize,
PrivateKeyOptions: privateKeyOptions,
AllowOverwrite: o.Flags.AllowOverwrite,
}
+13 -5
View File
@@ -17,7 +17,7 @@
package certificate
import (
"crypto/rsa"
"crypto"
"crypto/x509"
"fmt"
)
@@ -25,21 +25,29 @@ import (
// Bundle represents a pair of private key and certificate.
type Bundle struct {
Name string
Key *rsa.PrivateKey
Key crypto.PrivateKey
Cert *x509.Certificate
}
// Raw returns the raw bytes for the private key and certificate.
func (b *Bundle) Raw() ([]byte, []byte) {
return x509.MarshalPKCS1PrivateKey(b.Key), b.Cert.Raw
keyBytes, _ := x509.MarshalPKCS8PrivateKey(b.Key)
return keyBytes, b.Cert.Raw
}
// RawToBundle creates a bundle from the name and bytes given for a private key
// and a certificate.
func RawToBundle(name string, key []byte, cert []byte) (*Bundle, error) {
k, err := x509.ParsePKCS1PrivateKey(key)
k, err := x509.ParsePKCS8PrivateKey(key)
if err != nil {
return nil, fmt.Errorf("failed parsing private key: %v", err)
//previous versions may have stored private keys as PKCS1 (aka RSA only) try as a secondary action
var pkcs1Err error
k, pkcs1Err = x509.ParsePKCS1PrivateKey(key)
if pkcs1Err != nil {
return nil, fmt.Errorf("failed parsing private key PKCS8 error: %w, PKCS1 error: %w", err, pkcs1Err)
}
}
c, err := x509.ParseCertificate(cert)
if err != nil {
+82 -15
View File
@@ -20,6 +20,7 @@ package pki
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@@ -42,13 +43,40 @@ var (
ErrMaxPathLenReached = errors.New("max path len reached")
)
type PrivateKeyOptions interface {
GenerateKey() (crypto.PrivateKey, error)
}
var _ PrivateKeyOptions = &RsaPrivateKeyOptions{}
type RsaPrivateKeyOptions struct {
Size int
}
func (r *RsaPrivateKeyOptions) GenerateKey() (crypto.PrivateKey, error) {
if r.Size == 0 {
r.Size = defaultPrivateKeySize
}
return rsa.GenerateKey(rand.Reader, r.Size)
}
var _ PrivateKeyOptions = &EcPrivateKeyOptions{}
type EcPrivateKeyOptions struct {
Curve elliptic.Curve
}
func (e *EcPrivateKeyOptions) GenerateKey() (crypto.PrivateKey, error) {
return ecdsa.GenerateKey(e.Curve, rand.Reader)
}
// Request is a struct for providing configuration to
// GenerateCertificate when actioning a certification generation request.
type Request struct {
Name string
KeyName string
IsClientCertificate bool
PrivateKeySize int
PrivateKeyOptions PrivateKeyOptions
Template *x509.Certificate
AllowOverwrite bool
}
@@ -101,13 +129,11 @@ func (e *ZitiPKI) Sign(signer *certificate.Bundle, req *Request) error {
}
var err error
var privateKey *rsa.PrivateKey
var privateKey crypto.PrivateKey
if req.KeyName == "" {
if req.PrivateKeySize == 0 {
req.PrivateKeySize = defaultPrivateKeySize
}
privateKey, err = rsa.GenerateKey(rand.Reader, req.PrivateKeySize)
privateKey, err = req.PrivateKeyOptions.GenerateKey()
if err != nil {
return fmt.Errorf("failed generating private key: %v", err)
}
@@ -118,7 +144,12 @@ func (e *ZitiPKI) Sign(signer *certificate.Bundle, req *Request) error {
}
privateKey = pk.(*rsa.PrivateKey)
}
publicKey := privateKey.Public()
publicKey, err := publicKeyFromPrivate(privateKey)
if err != nil {
return err
}
if err := defaultTemplate(req, publicKey); err != nil {
return fmt.Errorf("failed updating generation request: %v", err)
@@ -148,12 +179,29 @@ func (e *ZitiPKI) Sign(signer *certificate.Bundle, req *Request) error {
return fmt.Errorf("failed creating and signing certificate: %v", err)
}
if err := e.Store.Add(signer.Name, req.Name, req.Template.IsCA, x509.MarshalPKCS1PrivateKey(privateKey), rawCert, req.AllowOverwrite); err != nil {
keyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil
}
if err := e.Store.Add(signer.Name, req.Name, req.Template.IsCA, keyBytes, rawCert, req.AllowOverwrite); err != nil {
return fmt.Errorf("failed saving generated bundle: %v", err)
}
return nil
}
func publicKeyFromPrivate(key crypto.PrivateKey) (crypto.PublicKey, error) {
switch pk := key.(type) {
case *rsa.PrivateKey:
return &pk.PublicKey, nil
case *ecdsa.PrivateKey:
return &pk.PublicKey, nil
}
return nil, errors.New("unsupported key type")
}
// Chain will...
func (e *ZitiPKI) Chain(signer *certificate.Bundle, req *Request) error {
destCA := signer.Name
@@ -166,16 +214,22 @@ func (e *ZitiPKI) Chain(signer *certificate.Bundle, req *Request) error {
return nil
}
// Generate and store a private key
// GeneratePrivateKey generates and stores a private key
func (e *ZitiPKI) GeneratePrivateKey(signer *certificate.Bundle, req *Request) error {
if req.PrivateKeySize == 0 {
req.PrivateKeySize = defaultPrivateKeySize
}
privateKey, err := rsa.GenerateKey(rand.Reader, req.PrivateKeySize)
privateKey, err := req.PrivateKeyOptions.GenerateKey()
if err != nil {
return fmt.Errorf("failed generating private key: %v", err)
}
if err := e.Store.AddKey(signer.Name, req.KeyName, x509.MarshalPKCS1PrivateKey(privateKey)); err != nil {
keyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return fmt.Errorf("failed marshalling private key: %v", err)
}
if err := e.Store.AddKey(signer.Name, req.KeyName, keyBytes); err != nil {
return fmt.Errorf("failed saving generated key: %v", err)
}
return nil
@@ -200,7 +254,20 @@ func (e *ZitiPKI) CRL(caName string, expire time.Time) ([]byte, error) {
return nil, fmt.Errorf("failed retrieving CA bundle %v: %v", caName, err)
}
crl, err := ca.Cert.CreateCRL(rand.Reader, ca.Key, revoked, time.Now(), expire)
signer, ok := ca.Key.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("could not convert %T to %T", ca.Key, signer)
}
template := &x509.RevocationList{
RevokedCertificates: revoked,
ThisUpdate: time.Now(),
NextUpdate: expire,
}
crl, err := x509.CreateRevocationList(rand.Reader, template, ca.Cert, signer)
if err != nil {
return nil, fmt.Errorf("failed creating crl for %v: %v", caName, err)
}
+2 -4
View File
@@ -19,17 +19,16 @@ package pki
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/asn1"
"fmt"
"math/big"
"time"
)
func defaultTemplate(genReq *Request, publicKey crypto.PublicKey) error {
publicKeyBytes, err := asn1.Marshal(*publicKey.(*rsa.PublicKey))
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return fmt.Errorf("failed marshaling public key: %v", err)
}
@@ -45,7 +44,6 @@ func defaultTemplate(genReq *Request, publicKey crypto.PublicKey) error {
genReq.Template.SerialNumber = sn
genReq.Template.NotBefore = time.Now().Add(-time.Minute)
genReq.Template.SignatureAlgorithm = x509.SHA256WithRSA
return nil
}
+29 -5
View File
@@ -156,19 +156,22 @@ func (l *Local) AddCSR(caName, name string, isCa bool, key, cert []byte) error {
return nil
}
// AddKey adds the given key to the local filesystem.
// AddKey adds the given private key to the local filesystem.
func (l *Local) AddKey(caName string, name string, key []byte) error {
if l.Exists(caName, name) {
return fmt.Errorf("a key already exists for the key name %v within CA %v", name, caName)
}
if err := l.writeKey(caName, name, key); err != nil {
pemType := getPkPemType(key)
if err := l.writeKey(caName, name, pemType, key); err != nil {
return fmt.Errorf("failed writing key %v within CA %v to the local filesystem: %v", name, caName, err)
}
return nil
}
// writeKey encodes in PEM format the bundle private key and stores it on the local filesystem.
func (l *Local) writeKey(caName string, name string, key []byte) error {
func (l *Local) writeKey(caName, name, pemType string, key []byte) error {
caDir := filepath.Join(l.Root, caName)
if _, err := os.Stat(caDir); err != nil {
if err := InitCADir(caDir); err != nil {
@@ -176,12 +179,30 @@ func (l *Local) writeKey(caName string, name string, key []byte) error {
}
}
keyPath, _ := l.path(caName, name)
if err := encodeAndWrite(keyPath, "RSA PRIVATE KEY", key); err != nil {
if err := encodeAndWrite(keyPath, pemType, key); err != nil {
return fmt.Errorf("failed encoding and writing private key file: %v", err)
}
return nil
}
func getPkPemType(key []byte) string {
var pkcs1Err error
_, pkcs1Err = x509.ParsePKCS1PrivateKey(key)
if pkcs1Err == nil {
return "RSA PRIVATE KEY"
}
var pkcsEcErr error
_, pkcsEcErr = x509.ParseECPrivateKey(key)
if pkcsEcErr == nil {
return "EC PRIVATE KEY"
}
return "PRIVATE KEY"
}
// writeBundle encodes in PEM format the bundle private key and
// certificate and stores them on the local filesystem.
func (l *Local) writeBundle(caName, name string, isCa bool, key, cert []byte) error {
@@ -191,8 +212,11 @@ func (l *Local) writeBundle(caName, name string, isCa bool, key, cert []byte) er
return fmt.Errorf("root directory for CA %v does not exist and cannot be created: %v", caDir, err)
}
}
keyPemType := getPkPemType(key)
keyPath, certPath := l.path(caName, name)
if err := encodeAndWrite(keyPath, "RSA PRIVATE KEY", key); err != nil {
if err := encodeAndWrite(keyPath, keyPemType, key); err != nil {
return fmt.Errorf("failed encoding and writing private key file: %v", err)
}
if err := encodeAndWrite(certPath, "CERTIFICATE", cert); err != nil {
+1 -1
View File
@@ -65,7 +65,7 @@ type Store interface {
// Args:
// The intermediate CA name
// The Key name
// The raw private key.
// The private key
//
// Returns an error if it failed to store the bundle.
AddKey(string, string, []byte) error