authelia/internal/commands/crypto_hash.go

390 lines
15 KiB
Go

package commands
import (
"fmt"
"strings"
"github.com/go-crypt/crypt"
"github.com/go-crypt/crypt/algorithm"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func newCryptoHashCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseHash,
Short: cmdAutheliaCryptoHashShort,
Long: cmdAutheliaCryptoHashLong,
Example: cmdAutheliaCryptoHashExample,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
}
cmd.AddCommand(
newCryptoHashValidateCmd(ctx),
newCryptoHashGenerateCmd(ctx),
)
return cmd
}
func newCryptoHashGenerateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
defaults := map[string]any{
prefixFilePassword + ".algorithm": schema.DefaultPasswordConfig.Algorithm,
prefixFilePassword + ".argon2.variant": schema.DefaultPasswordConfig.Argon2.Variant,
prefixFilePassword + ".argon2.iterations": schema.DefaultPasswordConfig.Argon2.Iterations,
prefixFilePassword + ".argon2.memory": schema.DefaultPasswordConfig.Argon2.Memory,
prefixFilePassword + ".argon2.parallelism": schema.DefaultPasswordConfig.Argon2.Parallelism,
prefixFilePassword + ".argon2.key_length": schema.DefaultPasswordConfig.Argon2.KeyLength,
prefixFilePassword + ".argon2.salt_length": schema.DefaultPasswordConfig.Argon2.SaltLength,
prefixFilePassword + ".sha2crypt.variant": schema.DefaultPasswordConfig.SHA2Crypt.Variant,
prefixFilePassword + ".sha2crypt.iterations": schema.DefaultPasswordConfig.SHA2Crypt.Iterations,
prefixFilePassword + ".sha2crypt.salt_length": schema.DefaultPasswordConfig.SHA2Crypt.SaltLength,
prefixFilePassword + ".pbkdf2.variant": schema.DefaultPasswordConfig.PBKDF2.Variant,
prefixFilePassword + ".pbkdf2.iterations": schema.DefaultPasswordConfig.PBKDF2.Iterations,
prefixFilePassword + ".pbkdf2.salt_length": schema.DefaultPasswordConfig.PBKDF2.SaltLength,
prefixFilePassword + ".bcrypt.variant": schema.DefaultPasswordConfig.BCrypt.Variant,
prefixFilePassword + ".bcrypt.cost": schema.DefaultPasswordConfig.BCrypt.Cost,
prefixFilePassword + ".scrypt.iterations": schema.DefaultPasswordConfig.SCrypt.Iterations,
prefixFilePassword + ".scrypt.block_size": schema.DefaultPasswordConfig.SCrypt.BlockSize,
prefixFilePassword + ".scrypt.parallelism": schema.DefaultPasswordConfig.SCrypt.Parallelism,
prefixFilePassword + ".scrypt.key_length": schema.DefaultPasswordConfig.SCrypt.KeyLength,
prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength,
}
cmd = &cobra.Command{
Use: cmdUseGenerate,
Short: cmdAutheliaCryptoHashGenerateShort,
Long: cmdAutheliaCryptoHashGenerateLong,
Example: cmdAutheliaCryptoHashGenerateExample,
PreRunE: ctx.ChainRunE(
ctx.ConfigSetDefaultsRunE(defaults),
ctx.CryptoHashGenerateMapFlagsRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateSectionPasswordRunE,
),
RunE: ctx.CryptoHashGenerateRunE,
DisableAutoGenTag: true,
}
cmdFlagPassword(cmd, true)
cmdFlagRandomPassword(cmd)
for _, use := range []string{cmdUseHashArgon2, cmdUseHashSHA2Crypt, cmdUseHashPBKDF2, cmdUseHashBCrypt, cmdUseHashSCrypt} {
cmd.AddCommand(newCryptoHashGenerateSubCmd(ctx, use))
}
return cmd
}
func newCryptoHashGenerateSubCmd(ctx *CmdCtx, use string) (cmd *cobra.Command) {
defaults := map[string]any{
prefixFilePassword + ".algorithm": schema.DefaultPasswordConfig.Algorithm,
prefixFilePassword + ".argon2.variant": schema.DefaultPasswordConfig.Argon2.Variant,
prefixFilePassword + ".argon2.iterations": schema.DefaultPasswordConfig.Argon2.Iterations,
prefixFilePassword + ".argon2.memory": schema.DefaultPasswordConfig.Argon2.Memory,
prefixFilePassword + ".argon2.parallelism": schema.DefaultPasswordConfig.Argon2.Parallelism,
prefixFilePassword + ".argon2.key_length": schema.DefaultPasswordConfig.Argon2.KeyLength,
prefixFilePassword + ".argon2.salt_length": schema.DefaultPasswordConfig.Argon2.SaltLength,
prefixFilePassword + ".sha2crypt.variant": schema.DefaultPasswordConfig.SHA2Crypt.Variant,
prefixFilePassword + ".sha2crypt.iterations": schema.DefaultPasswordConfig.SHA2Crypt.Iterations,
prefixFilePassword + ".sha2crypt.salt_length": schema.DefaultPasswordConfig.SHA2Crypt.SaltLength,
prefixFilePassword + ".pbkdf2.variant": schema.DefaultPasswordConfig.PBKDF2.Variant,
prefixFilePassword + ".pbkdf2.iterations": schema.DefaultPasswordConfig.PBKDF2.Iterations,
prefixFilePassword + ".pbkdf2.salt_length": schema.DefaultPasswordConfig.PBKDF2.SaltLength,
prefixFilePassword + ".bcrypt.variant": schema.DefaultPasswordConfig.BCrypt.Variant,
prefixFilePassword + ".bcrypt.cost": schema.DefaultPasswordConfig.BCrypt.Cost,
prefixFilePassword + ".scrypt.iterations": schema.DefaultPasswordConfig.SCrypt.Iterations,
prefixFilePassword + ".scrypt.block_size": schema.DefaultPasswordConfig.SCrypt.BlockSize,
prefixFilePassword + ".scrypt.parallelism": schema.DefaultPasswordConfig.SCrypt.Parallelism,
prefixFilePassword + ".scrypt.key_length": schema.DefaultPasswordConfig.SCrypt.KeyLength,
prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength,
}
useFmt := fmtCryptoHashUse(use)
cmd = &cobra.Command{
Use: use,
Short: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubShort, useFmt),
Long: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubLong, useFmt, useFmt),
Example: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubExample, use),
Args: cobra.NoArgs,
PersistentPreRunE: ctx.ChainRunE(
ctx.ConfigSetDefaultsRunE(defaults),
ctx.CryptoHashGenerateMapFlagsRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateSectionPasswordRunE,
),
RunE: ctx.CryptoHashGenerateRunE,
DisableAutoGenTag: true,
}
switch use {
case cmdUseHashArgon2:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.Argon2.Iterations)
cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.Argon2.Parallelism)
cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.Argon2.KeyLength)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.Argon2.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.Argon2.Variant, "variant, options are 'argon2id', 'argon2i', and 'argon2d'")
cmd.Flags().IntP(cmdFlagNameMemory, "m", schema.DefaultPasswordConfig.Argon2.Memory, "memory in kibibytes")
cmd.Flags().String(cmdFlagNameProfile, "", "profile to use, options are low-memory and recommended")
case cmdUseHashSHA2Crypt:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SHA2Crypt.Iterations)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SHA2Crypt.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.SHA2Crypt.Variant, "variant, options are sha256 and sha512")
cmd.PreRunE = ctx.ChainRunE()
case cmdUseHashPBKDF2:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.PBKDF2.Iterations)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.PBKDF2.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.PBKDF2.Variant, "variant, options are 'sha1', 'sha224', 'sha256', 'sha384', and 'sha512'")
case cmdUseHashBCrypt:
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.BCrypt.Variant, "variant, options are 'standard' and 'sha256'")
cmd.Flags().IntP(cmdFlagNameCost, "i", schema.DefaultPasswordConfig.BCrypt.Cost, "hashing cost")
case cmdUseHashSCrypt:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SCrypt.Iterations)
cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.SCrypt.KeyLength)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SCrypt.SaltLength)
cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.SCrypt.Parallelism)
cmd.Flags().IntP(cmdFlagNameBlockSize, "r", schema.DefaultPasswordConfig.SCrypt.BlockSize, "block size")
}
return cmd
}
func newCryptoHashValidateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate),
Short: cmdAutheliaCryptoHashValidateShort,
Long: cmdAutheliaCryptoHashValidateLong,
Example: cmdAutheliaCryptoHashValidateExample,
Args: cobra.ExactArgs(1),
RunE: ctx.CryptoHashValidateRunE,
DisableAutoGenTag: true,
}
cmdFlagPassword(cmd, false)
return cmd
}
// CryptoHashValidateRunE is the RunE for the authelia crypto hash validate command.
func (ctx *CmdCtx) CryptoHashValidateRunE(cmd *cobra.Command, args []string) (err error) {
var (
password string
valid bool
)
if password, _, err = cmdCryptoHashGetPassword(cmd, args, false, false); err != nil {
return fmt.Errorf("error occurred trying to obtain the password: %w", err)
}
if len(password) == 0 {
return fmt.Errorf("no password provided")
}
if valid, err = crypt.CheckPassword(password, args[0]); err != nil {
return fmt.Errorf("error occurred trying to validate the password against the digest: %w", err)
}
switch {
case valid:
fmt.Println("The password matches the digest.")
default:
fmt.Println("The password does not match the digest.")
}
return nil
}
// CryptoHashGenerateMapFlagsRunE is the RunE which configures the flags map configuration source for the
// authelia crypto hash generate commands.
func (ctx *CmdCtx) CryptoHashGenerateMapFlagsRunE(cmd *cobra.Command, args []string) (err error) {
var flagsMap map[string]string
switch cmd.Use {
case cmdUseHashArgon2:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".argon2.variant",
cmdFlagNameIterations: prefixFilePassword + ".argon2.iterations",
cmdFlagNameMemory: prefixFilePassword + ".argon2.memory",
cmdFlagNameParallelism: prefixFilePassword + ".argon2.parallelism",
cmdFlagNameKeySize: prefixFilePassword + ".argon2.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".argon2.salt_length",
}
case cmdUseHashSHA2Crypt:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".sha2crypt.variant",
cmdFlagNameIterations: prefixFilePassword + ".sha2crypt.iterations",
cmdFlagNameSaltSize: prefixFilePassword + ".sha2crypt.salt_length",
}
case cmdUseHashPBKDF2:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".pbkdf2.variant",
cmdFlagNameIterations: prefixFilePassword + ".pbkdf2.iterations",
cmdFlagNameKeySize: prefixFilePassword + ".pbkdf2.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".pbkdf2.salt_length",
}
case cmdUseHashBCrypt:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".bcrypt.variant",
cmdFlagNameCost: prefixFilePassword + ".bcrypt.cost",
}
case cmdUseHashSCrypt:
flagsMap = map[string]string{
cmdFlagNameIterations: prefixFilePassword + ".scrypt.iterations",
cmdFlagNameBlockSize: prefixFilePassword + ".scrypt.block_size",
cmdFlagNameParallelism: prefixFilePassword + ".scrypt.parallelism",
cmdFlagNameKeySize: prefixFilePassword + ".scrypt.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".scrypt.salt_length",
}
}
if flagsMap != nil {
ctx.cconfig.sources = append(ctx.cconfig.sources, configuration.NewCommandLineSourceWithMapping(cmd.Flags(), flagsMap, false, false))
}
return nil
}
// CryptoHashGenerateRunE is the RunE for the authelia crypto hash generate commands.
func (ctx *CmdCtx) CryptoHashGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var (
hash algorithm.Hash
digest algorithm.Digest
password string
random bool
)
if password, random, err = cmdCryptoHashGetPassword(cmd, args, false, true); err != nil {
return err
}
if len(password) == 0 {
return fmt.Errorf("no password provided")
}
switch cmd.Use {
case cmdUseGenerate:
break
default:
ctx.config.AuthenticationBackend.File.Password.Algorithm = cmd.Use
}
if hash, err = authentication.NewFileCryptoHashFromConfig(ctx.config.AuthenticationBackend.File.Password); err != nil {
return err
}
if digest, err = hash.Hash(password); err != nil {
return err
}
if random {
fmt.Printf("Random Password: %s\n", password)
}
fmt.Printf("Digest: %s\n", digest.Encode())
return nil
}
func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRandom bool) (password string, random bool, err error) {
if useRandom {
if random, err = cmd.Flags().GetBool(cmdFlagNameRandom); err != nil {
return
}
}
switch {
case random:
password, err = flagsGetRandomCharacters(cmd.Flags(), cmdFlagNameRandomLength, cmdFlagNameRandomCharSet, cmdFlagNameCharacters)
return
case cmd.Flags().Changed(cmdFlagNamePassword):
password, err = cmd.Flags().GetString(cmdFlagNamePassword)
return
case useArgs && len(args) != 0:
password, err = strings.Join(args, " "), nil
return
}
var (
noConfirm bool
)
if password, err = termReadPasswordWithPrompt("Enter Password: ", "password"); err != nil {
err = fmt.Errorf("failed to read the password from the terminal: %w", err)
return
}
if cmd.Use == fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate) {
fmt.Println("")
return
}
if noConfirm, err = cmd.Flags().GetBool(cmdFlagNameNoConfirm); err == nil && !noConfirm {
var confirm string
if confirm, err = termReadPasswordWithPrompt("Confirm Password: ", ""); err != nil {
return
}
if password != confirm {
fmt.Println("")
err = fmt.Errorf("the password did not match the confirmation password")
return
}
}
fmt.Println("")
return
}
func cmdFlagPassword(cmd *cobra.Command, noConfirm bool) {
cmd.PersistentFlags().String(cmdFlagNamePassword, "", "manually supply the password rather than using the terminal prompt")
if noConfirm {
cmd.PersistentFlags().Bool(cmdFlagNameNoConfirm, false, "skip the password confirmation prompt")
}
}
func cmdFlagRandomPassword(cmd *cobra.Command) {
cmd.PersistentFlags().Bool(cmdFlagNameRandom, false, "uses a randomly generated password")
cmd.PersistentFlags().String(cmdFlagNameRandomCharSet, cmdFlagValueCharSet, cmdFlagUsageCharset)
cmd.PersistentFlags().String(cmdFlagNameRandomCharacters, "", cmdFlagUsageCharacters)
cmd.PersistentFlags().Int(cmdFlagNameRandomLength, 72, cmdFlagUsageLength)
}
func cmdFlagIterations(cmd *cobra.Command, value int) {
cmd.Flags().IntP(cmdFlagNameIterations, "i", value, "number of iterations")
}
func cmdFlagKeySize(cmd *cobra.Command, value int) {
cmd.Flags().IntP(cmdFlagNameKeySize, "k", value, "key size in bytes")
}
func cmdFlagSaltSize(cmd *cobra.Command, value int) {
cmd.Flags().IntP(cmdFlagNameSaltSize, "s", value, "salt size in bytes")
}
func cmdFlagParallelism(cmd *cobra.Command, value int) {
cmd.Flags().IntP(cmdFlagNameParallelism, "p", value, "parallelism or threads")
}