authelia/internal/commands/crypto.go
James Elliott fcac438637
feat(commands): enhance crypto generation capabilities (#2842)
This expands the functionality of the certificates and rsa commands and merges them into one command called cypto which can either use the cert or pair subcommands to generate certificates or key-pairs respectively. The rsa, ecdsa, and ed25519 subcommands exist for both the cert and pair commands. A new --ca-path argument for the cert subcommand allows Authelia to sign other certs with CA certs.

Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
2022-06-27 18:27:57 +10:00

452 lines
12 KiB
Go

package commands
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/utils"
)
func newCryptoCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "crypto",
Short: cmdAutheliaCryptoShort,
Long: cmdAutheliaCryptoLong,
Example: cmdAutheliaCryptoExample,
Args: cobra.NoArgs,
}
cmd.AddCommand(
newCryptoCertificateCmd(),
newCryptoPairCmd(),
)
return cmd
}
func newCryptoCertificateCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseCertificate,
Short: cmdAutheliaCryptoCertificateShort,
Long: cmdAutheliaCryptoCertificateLong,
Example: cmdAutheliaCryptoCertificateExample,
Args: cobra.NoArgs,
}
cmd.AddCommand(
newCryptoCertificateSubCmd(cmdUseRSA),
newCryptoCertificateSubCmd(cmdUseECDSA),
newCryptoCertificateSubCmd(cmdUseEd25519),
)
return cmd
}
func newCryptoCertificateSubCmd(use string) (cmd *cobra.Command) {
var (
example, useFmt string
)
useFmt = fmtCryptoUse(use)
switch use {
case cmdUseRSA:
example = cmdAutheliaCryptoCertificateRSAExample
case cmdUseECDSA:
example = cmdAutheliaCryptoCertificateECDSAExample
case cmdUseEd25519:
example = cmdAutheliaCryptoCertificateEd25519Example
}
cmd = &cobra.Command{
Use: use,
Short: fmt.Sprintf(fmtCmdAutheliaCryptoCertificateSubShort, useFmt),
Long: fmt.Sprintf(fmtCmdAutheliaCryptoCertificateSubLong, useFmt, useFmt),
Example: example,
Args: cobra.NoArgs,
}
cmd.AddCommand(newCryptoGenerateCmd(cmdUseCertificate, use), newCryptoCertificateRequestCmd(use))
return cmd
}
func newCryptoCertificateRequestCmd(algorithm string) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseRequest,
Args: cobra.NoArgs,
RunE: cryptoCertificateRequestRunE,
}
cmdFlagsCryptoPrivateKey(cmd)
cmdFlagsCryptoCertificateCommon(cmd)
cmdFlagsCryptoCertificateRequest(cmd)
algorithmFmt := fmtCryptoUse(algorithm)
cmd.Short = fmt.Sprintf(fmtCmdAutheliaCryptoCertificateGenerateRequestShort, algorithmFmt, cryptoCertCSROut)
cmd.Long = fmt.Sprintf(fmtCmdAutheliaCryptoCertificateGenerateRequestLong, algorithmFmt, cryptoCertCSROut, algorithmFmt, cryptoCertCSROut)
switch algorithm {
case cmdUseRSA:
cmd.Example = cmdAutheliaCryptoCertificateRSARequestExample
cmdFlagsCryptoPrivateKeyRSA(cmd)
case cmdUseECDSA:
cmd.Example = cmdAutheliaCryptoCertificateECDSARequestExample
cmdFlagsCryptoPrivateKeyECDSA(cmd)
case cmdUseEd25519:
cmd.Example = cmdAutheliaCryptoCertificateEd25519RequestExample
cmdFlagsCryptoPrivateKeyEd25519(cmd)
}
return cmd
}
func newCryptoPairCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUsePair,
Short: cmdAutheliaCryptoPairShort,
Long: cmdAutheliaCryptoPairLong,
Example: cmdAutheliaCryptoPairExample,
Args: cobra.NoArgs,
}
cmd.AddCommand(
newCryptoPairSubCmd(cmdUseRSA),
newCryptoPairSubCmd(cmdUseECDSA),
newCryptoPairSubCmd(cmdUseEd25519),
)
return cmd
}
func newCryptoPairSubCmd(use string) (cmd *cobra.Command) {
var (
example, useFmt string
)
useFmt = fmtCryptoUse(use)
switch use {
case cmdUseRSA:
example = cmdAutheliaCryptoPairRSAExample
case cmdUseECDSA:
example = cmdAutheliaCryptoPairECDSAExample
case cmdUseEd25519:
example = cmdAutheliaCryptoPairEd25519Example
}
cmd = &cobra.Command{
Use: use,
Short: fmt.Sprintf(cmdAutheliaCryptoPairSubShort, useFmt),
Long: fmt.Sprintf(cmdAutheliaCryptoPairSubLong, useFmt, useFmt),
Example: example,
Args: cobra.NoArgs,
RunE: cryptoGenerateRunE,
}
cmd.AddCommand(newCryptoGenerateCmd(cmdUsePair, use))
return cmd
}
func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseGenerate,
Args: cobra.NoArgs,
RunE: cryptoGenerateRunE,
}
cmdFlagsCryptoPrivateKey(cmd)
algorithmFmt := fmtCryptoUse(algorithm)
switch category {
case cmdUseCertificate:
cmdFlagsCryptoCertificateCommon(cmd)
cmdFlagsCryptoCertificateGenerate(cmd)
cmd.Short = fmt.Sprintf(fmtCmdAutheliaCryptoCertificateGenerateRequestShort, algorithmFmt, cryptoCertPubCertOut)
cmd.Long = fmt.Sprintf(fmtCmdAutheliaCryptoCertificateGenerateRequestLong, algorithmFmt, cryptoCertPubCertOut, algorithmFmt, cryptoCertPubCertOut)
switch algorithm {
case cmdUseRSA:
cmd.Example = cmdAutheliaCryptoCertificateRSAGenerateExample
cmdFlagsCryptoPrivateKeyRSA(cmd)
case cmdUseECDSA:
cmd.Example = cmdAutheliaCryptoCertificateECDSAGenerateExample
cmdFlagsCryptoPrivateKeyECDSA(cmd)
case cmdUseEd25519:
cmd.Example = cmdAutheliaCryptoCertificateEd25519GenerateExample
cmdFlagsCryptoPrivateKeyEd25519(cmd)
}
case cmdUsePair:
cmdFlagsCryptoPairGenerate(cmd)
cmd.Short = fmt.Sprintf(fmtCmdAutheliaCryptoPairGenerateShort, algorithmFmt)
cmd.Long = fmt.Sprintf(fmtCmdAutheliaCryptoPairGenerateLong, algorithmFmt, algorithmFmt)
switch algorithm {
case cmdUseRSA:
cmd.Example = cmdAutheliaCryptoPairRSAGenerateExample
cmdFlagsCryptoPrivateKeyRSA(cmd)
case cmdUseECDSA:
cmd.Example = cmdAutheliaCryptoPairECDSAGenerateExample
cmdFlagsCryptoPrivateKeyECDSA(cmd)
case cmdUseEd25519:
cmd.Example = cmdAutheliaCryptoPairEd25519GenerateExample
cmdFlagsCryptoPrivateKeyEd25519(cmd)
}
}
return cmd
}
func cryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var (
privateKey interface{}
)
if privateKey, err = cryptoGenPrivateKeyFromCmd(cmd); err != nil {
return err
}
if cmd.Parent().Parent().Use == cmdUseCertificate {
return cryptoCertificateGenerateRunE(cmd, args, privateKey)
}
return cryptoPairGenerateRunE(cmd, args, privateKey)
}
func cryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) {
var (
privateKey interface{}
)
if privateKey, err = cryptoGenPrivateKeyFromCmd(cmd); err != nil {
return err
}
var (
template *x509.CertificateRequest
csr []byte
privateKeyPath, csrPath string
)
if template, err = cryptoGetCSRFromCmd(cmd); err != nil {
return err
}
b := strings.Builder{}
b.WriteString("Generating Certificate Request\n\n")
b.WriteString("Subject:\n")
b.WriteString(fmt.Sprintf("\tCommon Name: %s, Organization: %s, Organizational Unit: %s\n", template.Subject.CommonName, template.Subject.Organization, template.Subject.OrganizationalUnit))
b.WriteString(fmt.Sprintf("\tCountry: %v, Province: %v, Street Address: %v, Postal Code: %v, Locality: %v\n\n", template.Subject.Country, template.Subject.Province, template.Subject.StreetAddress, template.Subject.PostalCode, template.Subject.Locality))
b.WriteString("Properties:\n")
b.WriteString(fmt.Sprintf("\tSignature Algorithm: %s, Public Key Algorithm: %s", template.SignatureAlgorithm, template.PublicKeyAlgorithm))
switch k := privateKey.(type) {
case *rsa.PrivateKey:
b.WriteString(fmt.Sprintf(", Bits: %d", k.N.BitLen()))
case *ecdsa.PrivateKey:
b.WriteString(fmt.Sprintf(", Elliptic Curve: %s", k.Curve.Params().Name))
}
b.WriteString(fmt.Sprintf("\n\tSubject Alternative Names: %s\n\n", strings.Join(cryptoSANsToString(template.DNSNames, template.IPAddresses), ", ")))
if privateKeyPath, csrPath, err = cryptoGetWritePathsFromCmd(cmd); err != nil {
return err
}
b.WriteString("Output Paths:\n")
b.WriteString(fmt.Sprintf("\tPrivate Key: %s\n", privateKeyPath))
b.WriteString(fmt.Sprintf("\tCertificate Request: %s\n\n", csrPath))
fmt.Print(b.String())
b.Reset()
if csr, err = x509.CreateCertificateRequest(rand.Reader, template, privateKey); err != nil {
return fmt.Errorf("failed to create certificate request: %w", err)
}
if privateKeyPath, csrPath, err = cryptoGetWritePathsFromCmd(cmd); err != nil {
return err
}
if err = utils.WriteKeyToPEM(privateKey, privateKeyPath, false); err != nil {
return err
}
if err = utils.WriteCertificateBytesToPEM(csr, csrPath, true); err != nil {
return err
}
return nil
}
func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey interface{}) (err error) {
var (
template, caCertificate, parent *x509.Certificate
publicKey, caPrivateKey, signatureKey interface{}
)
if publicKey = utils.PublicKeyFromPrivateKey(privateKey); publicKey == nil {
return fmt.Errorf("failed to obtain public key from private key")
}
if caPrivateKey, caCertificate, err = cryptoGetCAFromCmd(cmd); err != nil {
return err
}
signatureKey = privateKey
if caPrivateKey != nil {
signatureKey = caPrivateKey
}
if template, err = cryptoGetCertificateFromCmd(cmd); err != nil {
return err
}
b := strings.Builder{}
b.WriteString("Generating Certificate\n\n")
b.WriteString(fmt.Sprintf("\tSerial: %x\n\n", template.SerialNumber))
switch caCertificate {
case nil:
parent = template
b.WriteString("Signed By:\n\tSelf-Signed\n")
default:
parent = caCertificate
b.WriteString(fmt.Sprintf("Signed By:\n\t%s\n", caCertificate.Subject.CommonName))
b.WriteString(fmt.Sprintf("\tSerial: %x, Expires: %s\n", caCertificate.SerialNumber, caCertificate.NotAfter.Format(time.RFC3339)))
}
b.WriteString("\nSubject:\n")
b.WriteString(fmt.Sprintf("\tCommon Name: %s, Organization: %s, Organizational Unit: %s\n", template.Subject.CommonName, template.Subject.Organization, template.Subject.OrganizationalUnit))
b.WriteString(fmt.Sprintf("\tCountry: %v, Province: %v, Street Address: %v, Postal Code: %v, Locality: %v\n\n", template.Subject.Country, template.Subject.Province, template.Subject.StreetAddress, template.Subject.PostalCode, template.Subject.Locality))
b.WriteString("Properties:\n")
b.WriteString(fmt.Sprintf("\tNot Before: %s, Not After: %s\n", template.NotBefore.Format(time.RFC3339), template.NotAfter.Format(time.RFC3339)))
b.WriteString(fmt.Sprintf("\tCA: %v, CSR: %v, Signature Algorithm: %s, Public Key Algorithm: %s", template.IsCA, false, template.SignatureAlgorithm, template.PublicKeyAlgorithm))
switch k := privateKey.(type) {
case *rsa.PrivateKey:
b.WriteString(fmt.Sprintf(", Bits: %d", k.N.BitLen()))
case *ecdsa.PrivateKey:
b.WriteString(fmt.Sprintf(", Elliptic Curve: %s", k.Curve.Params().Name))
}
b.WriteString(fmt.Sprintf("\n\tSubject Alternative Names: %s\n\n", strings.Join(cryptoSANsToString(template.DNSNames, template.IPAddresses), ", ")))
var (
privateKeyPath, certificatePath string
certificate []byte
)
if privateKeyPath, certificatePath, err = cryptoGetWritePathsFromCmd(cmd); err != nil {
return err
}
b.WriteString("Output Paths:\n")
b.WriteString(fmt.Sprintf("\tPrivate Key: %s\n", privateKeyPath))
b.WriteString(fmt.Sprintf("\tCertificate: %s\n\n", certificatePath))
fmt.Print(b.String())
b.Reset()
if certificate, err = x509.CreateCertificate(rand.Reader, template, parent, publicKey, signatureKey); err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
if err = utils.WriteKeyToPEM(privateKey, privateKeyPath, false); err != nil {
return err
}
if err = utils.WriteCertificateBytesToPEM(certificate, certificatePath, false); err != nil {
return err
}
return nil
}
func cryptoPairGenerateRunE(cmd *cobra.Command, _ []string, privateKey interface{}) (err error) {
var (
privateKeyPath, publicKeyPath string
pkcs8 bool
)
if pkcs8, err = cmd.Flags().GetBool(cmdFlagNamePKCS8); err != nil {
return err
}
if privateKeyPath, publicKeyPath, err = cryptoGetWritePathsFromCmd(cmd); err != nil {
return err
}
b := strings.Builder{}
b.WriteString("Generating key pair\n\n")
switch k := privateKey.(type) {
case *rsa.PrivateKey:
b.WriteString(fmt.Sprintf("\tAlgorithm: RSA-%d %d bits\n\n", k.Size(), k.N.BitLen()))
case *ecdsa.PrivateKey:
b.WriteString(fmt.Sprintf("\tAlgorithm: ECDSA Curve %s\n\n", k.Curve.Params().Name))
case ed25519.PrivateKey:
b.WriteString("\tAlgorithm: Ed25519\n\n")
}
b.WriteString("Output Paths:\n")
b.WriteString(fmt.Sprintf("\tPrivate Key: %s\n", privateKeyPath))
b.WriteString(fmt.Sprintf("\tPublic Key: %s\n\n", publicKeyPath))
fmt.Print(b.String())
b.Reset()
if err = utils.WriteKeyToPEM(privateKey, privateKeyPath, pkcs8); err != nil {
return err
}
var publicKey interface{}
if publicKey = utils.PublicKeyFromPrivateKey(privateKey); publicKey == nil {
return fmt.Errorf("failed to obtain public key from private key")
}
if err = utils.WriteKeyToPEM(publicKey, publicKeyPath, pkcs8); err != nil {
return err
}
return nil
}