@@ -35,7 +35,7 @@ type PasswordVerifier interface {
3535// PasswordHashAlgorithms are named PasswordSaltHashers with a default verifier and hash function
3636type PasswordHashAlgorithm struct {
3737 PasswordSaltHasher
38- Name string
38+ Specification string // The specification that is used to create the internal PasswordSaltHasher
3939}
4040
4141// Hash the provided password with the salt and return the hash
@@ -61,16 +61,16 @@ func (algorithm *PasswordHashAlgorithm) Hash(password, salt string) (string, err
6161
6262// Verify the provided password matches the hashPassword when hashed with the salt
6363func (algorithm * PasswordHashAlgorithm ) VerifyPassword (providedPassword , hashedPassword , salt string ) bool {
64- // The bcrypt package has its own specialized compare function that takes into
65- // account the stored password's bcrypt parameters.
64+ // Some PasswordSaltHashers have their own specialised compare function that takes into
65+ // account the stored parameters within the hash. e.g. bcrypt
6666 if verifier , ok := algorithm .PasswordSaltHasher .(PasswordVerifier ); ok {
6767 return verifier .VerifyPassword (providedPassword , hashedPassword , salt )
6868 }
6969
7070 // Compute the hash of the password.
7171 providedPasswordHash , err := algorithm .Hash (providedPassword , salt )
7272 if err != nil {
73- log .Error ("passwordhash: %v.Hash(): %v" , algorithm .Name , err )
73+ log .Error ("passwordhash: %v.Hash(): %v" , algorithm .Specification , err )
7474 return false
7575 }
7676
8484)
8585
8686// Register registers a PasswordSaltHasher with the availableHasherFactories
87- // This is not thread safe.
87+ // Caution: This is not thread safe.
8888func Register [T PasswordSaltHasher ](name string , newFn func (config string ) T ) {
8989 if _ , has := availableHasherFactories [name ]; has {
9090 panic (fmt .Errorf ("duplicate registration of password salt hasher: %s" , name ))
@@ -96,49 +96,82 @@ func Register[T PasswordSaltHasher](name string, newFn func(config string) T) {
9696 }
9797}
9898
99- // In early versions of gitea the password hash algorithm field could be empty
100- // At that point the default was `pbkdf2` without configuration values
101- // Please note this is not the same as the DefaultAlgorithm
102- const defaultEmptyHashAlgorithmName = "pbkdf2"
103-
104- func Parse (algorithm string ) * PasswordHashAlgorithm {
105- if algorithm == "" {
106- algorithm = defaultEmptyHashAlgorithmName
99+ // In early versions of gitea the password hash algorithm field of a user could be
100+ // empty. At that point the default was `pbkdf2` without configuration values
101+ //
102+ // Please note this is not the same as the DefaultAlgorithm which is used
103+ // to determine what an empty PASSWORD_HASH_ALGO setting in the app.ini means.
104+ // These are not the same even if they have the same apparent value and they mean different things.
105+ //
106+ // DO NOT COALESCE THESE VALUES
107+ const defaultEmptyHashAlgorithmSpecification = "pbkdf2"
108+
109+ // Parse will convert the provided algorithm specification in to a PasswordHashAlgorithm
110+ // If the provided specification matches the DefaultHashAlgorithm Specification it will be
111+ // used.
112+ // In addition the last non-default hasher will be cached to help reduce the load from
113+ // parsing specifications.
114+ //
115+ // NOTE: No de-aliasing is done in this function, thus any specification which does not
116+ // contain a configuration will use the default values for that hasher. These are not
117+ // necessarily the same values as those obtained by dealiasing. This allows for
118+ // seamless backwards compatibility with the original configuration.
119+ //
120+ // To further labour this point, running `Parse("pbkdf2")` does not obtain the
121+ // same algorithm as setting `PASSWORD_HASH_ALGO=pbkdf2` in app.ini, nor is it intended to.
122+ // A user that has `password_hash_algo='pbkdf2'` in the db means get the original, unconfigured algorithm
123+ // Users will be migrated automatically as they log-in to have the complete specification stored
124+ // in their `password_hash_algo` fields by other code.
125+ func Parse (algorithmSpec string ) * PasswordHashAlgorithm {
126+ if algorithmSpec == "" {
127+ algorithmSpec = defaultEmptyHashAlgorithmSpecification
107128 }
108129
109- if DefaultHashAlgorithm != nil && algorithm == DefaultHashAlgorithm .Name {
130+ if DefaultHashAlgorithm != nil && algorithmSpec == DefaultHashAlgorithm .Specification {
110131 return DefaultHashAlgorithm
111132 }
112133
113134 ptr := lastNonDefaultAlgorithm .Load ()
114135 if ptr != nil {
115136 hashAlgorithm , ok := ptr .(* PasswordHashAlgorithm )
116- if ok && hashAlgorithm .Name == algorithm {
137+ if ok && hashAlgorithm .Specification == algorithmSpec {
117138 return hashAlgorithm
118139 }
119140 }
120141
121- vals := strings .SplitN (algorithm , "$" , 2 )
122- var name string
142+ // Now convert the provided specification in to a hasherType +/- some configuration parameters
143+ vals := strings .SplitN (algorithmSpec , "$" , 2 )
144+ var hasherType string
123145 var config string
146+
124147 if len (vals ) == 0 {
148+ // This should not happen as algorithmSpec should not be empty
149+ // due to it being assigned to defaultEmptyHashAlgorithmSpecification above
150+ // but we should be absolutely cautious here
125151 return nil
126152 }
127- name = vals [0 ]
153+
154+ hasherType = vals [0 ]
128155 if len (vals ) > 1 {
129156 config = vals [1 ]
130157 }
131- newFn , has := availableHasherFactories [name ]
158+
159+ newFn , has := availableHasherFactories [hasherType ]
132160 if ! has {
161+ // unknown hasher type
133162 return nil
134163 }
164+
135165 ph := newFn (config )
136166 if ph == nil {
167+ // The provided configuration is likely invalid - it will have been logged already
168+ // but we cannot hash safely
137169 return nil
138170 }
171+
139172 hashAlgorithm := & PasswordHashAlgorithm {
140173 PasswordSaltHasher : ph ,
141- Name : algorithm ,
174+ Specification : algorithmSpec ,
142175 }
143176
144177 lastNonDefaultAlgorithm .Store (hashAlgorithm )
0 commit comments