2022-10-17 10:51:59 +00:00
package commands
import (
"fmt"
"strings"
"syscall"
"github.com/go-crypt/crypt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/term"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
"github.com/authelia/authelia/v4/internal/utils"
)
func newHashPasswordCmd ( ) ( cmd * cobra . Command ) {
cmd = & cobra . Command {
Use : cmdUseHashPassword ,
Short : cmdAutheliaHashPasswordShort ,
Long : cmdAutheliaHashPasswordLong ,
Example : cmdAutheliaHashPasswordExample ,
Args : cobra . MaximumNArgs ( 1 ) ,
RunE : cmdHashPasswordRunE ,
DisableAutoGenTag : true ,
}
cmdFlagConfig ( cmd )
cmd . Flags ( ) . BoolP ( cmdFlagNameSHA512 , "z" , false , fmt . Sprintf ( "use sha512 as the algorithm (changes iterations to %d, change with -i)" , schema . DefaultPasswordConfig . SHA2Crypt . Iterations ) )
cmd . Flags ( ) . IntP ( cmdFlagNameIterations , "i" , schema . DefaultPasswordConfig . Argon2 . Iterations , "set the number of hashing iterations" )
cmd . Flags ( ) . IntP ( cmdFlagNameMemory , "m" , schema . DefaultPasswordConfig . Argon2 . Memory , "[argon2id] set the amount of memory param (in MB)" )
cmd . Flags ( ) . IntP ( cmdFlagNameParallelism , "p" , schema . DefaultPasswordConfig . Argon2 . Parallelism , "[argon2id] set the parallelism param" )
cmd . Flags ( ) . IntP ( "key-length" , "k" , schema . DefaultPasswordConfig . Argon2 . KeyLength , "[argon2id] set the key length param" )
cmd . Flags ( ) . IntP ( "salt-length" , "l" , schema . DefaultPasswordConfig . Argon2 . SaltLength , "set the auto-generated salt length" )
cmd . Flags ( ) . Bool ( cmdFlagNameNoConfirm , false , "skip the password confirmation prompt" )
return cmd
}
func cmdHashPasswordRunE ( cmd * cobra . Command , args [ ] string ) ( err error ) {
var (
flagsMap map [ string ] string
sha512 bool
)
if sha512 , err = cmd . Flags ( ) . GetBool ( cmdFlagNameSHA512 ) ; err != nil {
return err
}
switch {
case sha512 :
flagsMap = map [ string ] string {
cmdFlagNameIterations : prefixFilePassword + ".sha2crypt.iterations" ,
"salt-length" : prefixFilePassword + ".sha2crypt.salt_length" ,
}
default :
flagsMap = map [ string ] string {
cmdFlagNameIterations : prefixFilePassword + ".argon2.iterations" ,
"key-length" : prefixFilePassword + ".argon2.key_length" ,
"salt-length" : prefixFilePassword + ".argon2.salt_length" ,
cmdFlagNameParallelism : prefixFilePassword + ".argon2.parallelism" ,
cmdFlagNameMemory : prefixFilePassword + ".argon2.memory" ,
}
}
return cmdCryptoHashGenerateFinish ( cmd , args , flagsMap )
}
func newCryptoHashCmd ( ) ( cmd * cobra . Command ) {
cmd = & cobra . Command {
Use : cmdUseHash ,
Short : cmdAutheliaCryptoHashShort ,
Long : cmdAutheliaCryptoHashLong ,
Example : cmdAutheliaCryptoHashExample ,
Args : cobra . NoArgs ,
DisableAutoGenTag : true ,
}
cmd . AddCommand (
newCryptoHashValidateCmd ( ) ,
newCryptoHashGenerateCmd ( ) ,
)
return cmd
}
func newCryptoHashGenerateCmd ( ) ( cmd * cobra . Command ) {
cmd = & cobra . Command {
Use : cmdUseGenerate ,
Short : cmdAutheliaCryptoHashGenerateShort ,
Long : cmdAutheliaCryptoHashGenerateLong ,
Example : cmdAutheliaCryptoHashGenerateExample ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return cmdCryptoHashGenerateFinish ( cmd , args , map [ string ] string { } )
} ,
DisableAutoGenTag : true ,
}
cmdFlagConfig ( cmd )
cmdFlagPassword ( cmd , true )
for _ , use := range [ ] string { cmdUseHashArgon2 , cmdUseHashSHA2Crypt , cmdUseHashPBKDF2 , cmdUseHashBCrypt , cmdUseHashSCrypt } {
cmd . AddCommand ( newCryptoHashGenerateSubCmd ( use ) )
}
return cmd
}
func newCryptoHashGenerateSubCmd ( use string ) ( cmd * cobra . Command ) {
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 ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return nil
} ,
DisableAutoGenTag : true ,
}
cmdFlagConfig ( cmd )
cmdFlagPassword ( cmd , true )
cmdFlagRandomPassword ( cmd )
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" )
cmd . RunE = cryptoHashGenerateArgon2RunE
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 . RunE = cryptoHashGenerateSHA2CryptRunE
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'" )
cmd . RunE = cryptoHashGeneratePBKDF2RunE
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" )
cmd . RunE = cryptoHashGenerateBCryptRunE
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" )
cmd . RunE = cryptoHashGenerateSCryptRunE
}
return cmd
}
func cryptoHashGenerateArgon2RunE ( cmd * cobra . Command , args [ ] string ) ( err error ) {
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" ,
}
return cmdCryptoHashGenerateFinish ( cmd , args , flagsMap )
}
func cryptoHashGenerateSHA2CryptRunE ( cmd * cobra . Command , args [ ] string ) ( err error ) {
flagsMap := map [ string ] string {
cmdFlagNameVariant : prefixFilePassword + ".sha2crypt.variant" ,
cmdFlagNameIterations : prefixFilePassword + ".sha2crypt.iterations" ,
cmdFlagNameSaltSize : prefixFilePassword + ".sha2crypt.salt_length" ,
}
return cmdCryptoHashGenerateFinish ( cmd , args , flagsMap )
}
func cryptoHashGeneratePBKDF2RunE ( cmd * cobra . Command , args [ ] string ) ( err error ) {
flagsMap := map [ string ] string {
cmdFlagNameVariant : prefixFilePassword + ".pbkdf2.variant" ,
cmdFlagNameIterations : prefixFilePassword + ".pbkdf2.iterations" ,
cmdFlagNameKeySize : prefixFilePassword + ".pbkdf2.key_length" ,
cmdFlagNameSaltSize : prefixFilePassword + ".pbkdf2.salt_length" ,
}
return cmdCryptoHashGenerateFinish ( cmd , args , flagsMap )
}
func cryptoHashGenerateBCryptRunE ( cmd * cobra . Command , args [ ] string ) ( err error ) {
flagsMap := map [ string ] string {
cmdFlagNameVariant : prefixFilePassword + ".bcrypt.variant" ,
cmdFlagNameCost : prefixFilePassword + ".bcrypt.cost" ,
}
return cmdCryptoHashGenerateFinish ( cmd , args , flagsMap )
}
func cryptoHashGenerateSCryptRunE ( cmd * cobra . Command , args [ ] string ) ( err error ) {
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" ,
}
return cmdCryptoHashGenerateFinish ( cmd , args , flagsMap )
}
func newCryptoHashValidateCmd ( ) ( cmd * cobra . Command ) {
cmd = & cobra . Command {
Use : fmt . Sprintf ( cmdUseFmtValidate , cmdUseValidate ) ,
Short : cmdAutheliaCryptoHashValidateShort ,
Long : cmdAutheliaCryptoHashValidateLong ,
Example : cmdAutheliaCryptoHashValidateExample ,
Args : cobra . ExactArgs ( 1 ) ,
RunE : func ( 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
} ,
DisableAutoGenTag : true ,
}
cmdFlagPassword ( cmd , false )
return cmd
}
func cmdCryptoHashGenerateFinish ( cmd * cobra . Command , args [ ] string , flagsMap map [ string ] string ) ( err error ) {
var (
algorithm string
configs [ ] string
c schema . Password
)
if configs , err = cmd . Flags ( ) . GetStringSlice ( cmdFlagNameConfig ) ; err != nil {
return err
}
// Skip config if the flag wasn't set and the default is non-existent.
if ! cmd . Flags ( ) . Changed ( cmdFlagNameConfig ) {
configs = configFilterExisting ( configs )
}
legacy := cmd . Use == cmdUseHashPassword
switch {
case cmd . Use == cmdUseGenerate :
break
case legacy :
if sha512 , _ := cmd . Flags ( ) . GetBool ( cmdFlagNameSHA512 ) ; sha512 {
algorithm = cmdUseHashSHA2Crypt
} else {
algorithm = cmdUseHashArgon2
}
default :
algorithm = cmd . Use
}
if c , err = cmdCryptoHashGetConfig ( algorithm , configs , cmd . Flags ( ) , flagsMap ) ; err != nil {
return err
}
if legacy && algorithm == cmdUseHashArgon2 && cmd . Flags ( ) . Changed ( cmdFlagNameMemory ) {
c . Argon2 . Memory *= 1024
}
var (
hash crypt . Hash
digest crypt . Digest
password string
random bool
)
if password , random , err = cmdCryptoHashGetPassword ( cmd , args , legacy , ! legacy ) ; err != nil {
return err
}
if len ( password ) == 0 {
return fmt . Errorf ( "no password provided" )
}
if hash , err = authentication . NewFileCryptoHashFromConfig ( c ) ; 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 cmdCryptoHashGetConfig ( algorithm string , configs [ ] string , flags * pflag . FlagSet , flagsMap map [ string ] string ) ( c schema . Password , err error ) {
mapDefaults := map [ string ] interface { } {
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 ,
}
sources := configuration . NewDefaultSourcesWithDefaults ( configs ,
configuration . DefaultEnvPrefix , configuration . DefaultEnvDelimiter ,
configuration . NewMapSource ( mapDefaults ) ,
configuration . NewCommandLineSourceWithMapping ( flags , flagsMap , false , false ) ,
)
if algorithm != "" {
alg := map [ string ] interface { } { prefixFilePassword + ".algorithm" : algorithm }
sources = append ( sources , configuration . NewMapSource ( alg ) )
}
val := schema . NewStructValidator ( )
if _ , err = configuration . LoadAdvanced ( val , prefixFilePassword , & c , sources ... ) ; err != nil {
return schema . Password { } , fmt . Errorf ( "error occurred loading configuration: %w" , err )
}
validator . ValidatePasswordConfiguration ( & c , val )
errs := val . Errors ( )
if len ( errs ) != 0 {
for i , e := range errs {
if i == 0 {
err = e
continue
}
err = fmt . Errorf ( "%v, %w" , err , e )
}
return schema . Password { } , fmt . Errorf ( "errors occurred validating the password configuration: %w" , err )
}
return c , 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 :
var length int
if length , err = cmd . Flags ( ) . GetInt ( cmdFlagNameRandomLength ) ; err != nil {
return
}
password = utils . RandomString ( length , utils . CharSetAlphaNumeric , true )
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 (
data [ ] byte
noConfirm bool
)
if data , err = hashReadPasswordWithPrompt ( "Enter Password: " ) ; err != nil {
err = fmt . Errorf ( "failed to read the password from the terminal: %w" , err )
return
}
password = string ( data )
if cmd . Use == fmt . Sprintf ( cmdUseFmtValidate , cmdUseValidate ) {
fmt . Println ( "" )
return
}
if noConfirm , err = cmd . Flags ( ) . GetBool ( cmdFlagNameNoConfirm ) ; err == nil && ! noConfirm {
if data , err = hashReadPasswordWithPrompt ( "Confirm Password: " ) ; err != nil {
err = fmt . Errorf ( "failed to read the password from the terminal: %w" , err )
return
}
if password != string ( data ) {
fmt . Println ( "" )
err = fmt . Errorf ( "the password did not match the confirmation password" )
return
}
}
fmt . Println ( "" )
return
}
func hashReadPasswordWithPrompt ( prompt string ) ( data [ ] byte , err error ) {
fmt . Print ( prompt )
2022-10-19 03:09:22 +00:00
if data , err = term . ReadPassword ( int ( syscall . Stdin ) ) ; err != nil { //nolint:unconvert,nolintlint
2022-10-17 10:51:59 +00:00
if err . Error ( ) == "inappropriate ioctl for device" {
return nil , fmt . Errorf ( "the terminal doesn't appear to be interactive either use the '--password' flag or use an interactive terminal: %w" , err )
}
return nil , err
}
fmt . Println ( "" )
return data , nil
}
func cmdFlagConfig ( cmd * cobra . Command ) {
cmd . Flags ( ) . StringSliceP ( cmdFlagNameConfig , "c" , [ ] string { "configuration.yml" } , "configuration files to load" )
}
func cmdFlagPassword ( cmd * cobra . Command , noConfirm bool ) {
cmd . Flags ( ) . String ( cmdFlagNamePassword , "" , "manually supply the password rather than using the terminal prompt" )
if noConfirm {
cmd . Flags ( ) . Bool ( cmdFlagNameNoConfirm , false , "skip the password confirmation prompt" )
}
}
func cmdFlagRandomPassword ( cmd * cobra . Command ) {
cmd . Flags ( ) . Bool ( cmdFlagNameRandom , false , "uses a randomly generated password" )
cmd . Flags ( ) . Int ( cmdFlagNameRandomLength , 72 , "when using a randomly generated password it configures the length" )
}
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" )
}