123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package hash
-
- import (
- "crypto/subtle"
- "encoding/hex"
- "fmt"
- "strings"
- "sync/atomic"
-
- "code.gitea.io/gitea/modules/log"
- )
-
- // This package takes care of hashing passwords, verifying passwords, defining
- // available password algorithms, defining recommended password algorithms and
- // choosing the default password algorithm.
-
- // PasswordSaltHasher will hash a provided password with the provided saltBytes
- type PasswordSaltHasher interface {
- HashWithSaltBytes(password string, saltBytes []byte) string
- }
-
- // PasswordHasher will hash a provided password with the salt
- type PasswordHasher interface {
- Hash(password, salt string) (string, error)
- }
-
- // PasswordVerifier will ensure that a providedPassword matches the hashPassword when hashed with the salt
- type PasswordVerifier interface {
- VerifyPassword(providedPassword, hashedPassword, salt string) bool
- }
-
- // PasswordHashAlgorithms are named PasswordSaltHashers with a default verifier and hash function
- type PasswordHashAlgorithm struct {
- PasswordSaltHasher
- Specification string // The specification that is used to create the internal PasswordSaltHasher
- }
-
- // Hash the provided password with the salt and return the hash
- func (algorithm *PasswordHashAlgorithm) Hash(password, salt string) (string, error) {
- var saltBytes []byte
-
- // There are two formats for the salt value:
- // * The new format is a (32+)-byte hex-encoded string
- // * The old format was a 10-byte binary format
- // We have to tolerate both here.
- if len(salt) == 10 {
- saltBytes = []byte(salt)
- } else {
- var err error
- saltBytes, err = hex.DecodeString(salt)
- if err != nil {
- return "", err
- }
- }
-
- return algorithm.HashWithSaltBytes(password, saltBytes), nil
- }
-
- // Verify the provided password matches the hashPassword when hashed with the salt
- func (algorithm *PasswordHashAlgorithm) VerifyPassword(providedPassword, hashedPassword, salt string) bool {
- // Some PasswordSaltHashers have their own specialised compare function that takes into
- // account the stored parameters within the hash. e.g. bcrypt
- if verifier, ok := algorithm.PasswordSaltHasher.(PasswordVerifier); ok {
- return verifier.VerifyPassword(providedPassword, hashedPassword, salt)
- }
-
- // Compute the hash of the password.
- providedPasswordHash, err := algorithm.Hash(providedPassword, salt)
- if err != nil {
- log.Error("passwordhash: %v.Hash(): %v", algorithm.Specification, err)
- return false
- }
-
- // Compare it against the hashed password in constant-time.
- return subtle.ConstantTimeCompare([]byte(hashedPassword), []byte(providedPasswordHash)) == 1
- }
-
- var (
- lastNonDefaultAlgorithm atomic.Value
- availableHasherFactories = map[string]func(string) PasswordSaltHasher{}
- )
-
- // MustRegister registers a PasswordSaltHasher with the availableHasherFactories
- // Caution: This is not thread safe.
- func MustRegister[T PasswordSaltHasher](name string, newFn func(config string) T) {
- if err := Register(name, newFn); err != nil {
- panic(err)
- }
- }
-
- // Register registers a PasswordSaltHasher with the availableHasherFactories
- // Caution: This is not thread safe.
- func Register[T PasswordSaltHasher](name string, newFn func(config string) T) error {
- if _, has := availableHasherFactories[name]; has {
- return fmt.Errorf("duplicate registration of password salt hasher: %s", name)
- }
-
- availableHasherFactories[name] = func(config string) PasswordSaltHasher {
- n := newFn(config)
- return n
- }
- return nil
- }
-
- // In early versions of gitea the password hash algorithm field of a user could be
- // empty. At that point the default was `pbkdf2` without configuration values
- //
- // Please note this is not the same as the DefaultAlgorithm which is used
- // to determine what an empty PASSWORD_HASH_ALGO setting in the app.ini means.
- // These are not the same even if they have the same apparent value and they mean different things.
- //
- // DO NOT COALESCE THESE VALUES
- const defaultEmptyHashAlgorithmSpecification = "pbkdf2"
-
- // Parse will convert the provided algorithm specification in to a PasswordHashAlgorithm
- // If the provided specification matches the DefaultHashAlgorithm Specification it will be
- // used.
- // In addition the last non-default hasher will be cached to help reduce the load from
- // parsing specifications.
- //
- // NOTE: No de-aliasing is done in this function, thus any specification which does not
- // contain a configuration will use the default values for that hasher. These are not
- // necessarily the same values as those obtained by dealiasing. This allows for
- // seamless backwards compatibility with the original configuration.
- //
- // To further labour this point, running `Parse("pbkdf2")` does not obtain the
- // same algorithm as setting `PASSWORD_HASH_ALGO=pbkdf2` in app.ini, nor is it intended to.
- // A user that has `password_hash_algo='pbkdf2'` in the db means get the original, unconfigured algorithm
- // Users will be migrated automatically as they log-in to have the complete specification stored
- // in their `password_hash_algo` fields by other code.
- func Parse(algorithmSpec string) *PasswordHashAlgorithm {
- if algorithmSpec == "" {
- algorithmSpec = defaultEmptyHashAlgorithmSpecification
- }
-
- if DefaultHashAlgorithm != nil && algorithmSpec == DefaultHashAlgorithm.Specification {
- return DefaultHashAlgorithm
- }
-
- ptr := lastNonDefaultAlgorithm.Load()
- if ptr != nil {
- hashAlgorithm, ok := ptr.(*PasswordHashAlgorithm)
- if ok && hashAlgorithm.Specification == algorithmSpec {
- return hashAlgorithm
- }
- }
-
- // Now convert the provided specification in to a hasherType +/- some configuration parameters
- vals := strings.SplitN(algorithmSpec, "$", 2)
- var hasherType string
- var config string
-
- if len(vals) == 0 {
- // This should not happen as algorithmSpec should not be empty
- // due to it being assigned to defaultEmptyHashAlgorithmSpecification above
- // but we should be absolutely cautious here
- return nil
- }
-
- hasherType = vals[0]
- if len(vals) > 1 {
- config = vals[1]
- }
-
- newFn, has := availableHasherFactories[hasherType]
- if !has {
- // unknown hasher type
- return nil
- }
-
- ph := newFn(config)
- if ph == nil {
- // The provided configuration is likely invalid - it will have been logged already
- // but we cannot hash safely
- return nil
- }
-
- hashAlgorithm := &PasswordHashAlgorithm{
- PasswordSaltHasher: ph,
- Specification: algorithmSpec,
- }
-
- lastNonDefaultAlgorithm.Store(hashAlgorithm)
-
- return hashAlgorithm
- }
|