Files
kopia/internal/user/user_profile.go
Julio López ca1962f6e4 refactor(general): user password hashing and key derivation helpers (#3821)
Code movement and simplification, no functional changes.

Objectives:
- Allow callers specifying the needed key (or hash) size, instead of
hard-coding it in the registered PBK derivers. Conceptually, the caller
needs to specify the key size, since that is a requirement of the
(encryption) algorithm being used in the caller. Now, the code changes
here do not result in any functional changes since the key size is
always 32 bytes.
- Remove a global definition for the default PB key deriver to use.
Instead, each of the 3 use case sets the default value.

Changes:
- `crypto.DeriveKeyFromPassword` now takes a key size.
- Adds new constants for the key sizes at the callers.
- Removes the global `crypto.MasterKeySize` const.
- Removes the global `crypto.DefaultKeyDerivationAlgorithm` const.
- Adds const for the default derivation algorithms for each use case.
- Adds a const for the salt length in the `internal/user` package, to ensure
  the same salt length is used in both hash versions.
- Unexports various functions, variables and constants in the `internal/crypto`
  & `internal/user` packages.
- Renames various constants for consistency.
- Removes unused functions and symbols.
- Renames files to be consistent and better reflect the structure of the code.
- Adds a couple of tests to ensure the const values are in sync and supported.
- Fixes a couple of typos

Followups to:
- #3725
- #3770
- #3779
- #3799
- #3816

The individual commits show the code transformations to simplify the
review of the changes.
2024-04-26 23:30:56 -07:00

93 lines
2.8 KiB
Go

package user
import (
"math/rand"
"github.com/kopia/kopia/repo/manifest"
"github.com/pkg/errors"
)
const (
// ScryptHashVersion is the version representation of the scrypt algorithm.
ScryptHashVersion = 1
// scryptHashAlgorithm is the scrypt password hashing algorithm. This must match crypto.ScryptAlgorithm.
scryptHashAlgorithm = "scrypt-65536-8-1"
// Pbkdf2HashVersion is the version representation of the pbkdf2 algorithm.
Pbkdf2HashVersion = 2
// pbkdf2HashAlgorithm is the pbkdf2 password hashing algorithm. This must match crypto.Pbkdf2Algorithm.
pbkdf2HashAlgorithm = "pbkdf2-sha256-600000"
passwordHashLength = 32
passwordHashSaltLength = 32
)
// Profile describes information about a single user.
type Profile struct {
ManifestID manifest.ID `json:"-"`
Username string `json:"username"`
PasswordHashVersion int `json:"passwordHashVersion,omitempty"`
PasswordHash []byte `json:"passwordHash"`
}
// SetPassword changes the password for a user profile.
func (p *Profile) SetPassword(password string) error {
return p.setPassword(password)
}
// IsValidPassword determines whether the password is valid for a given user.
func (p *Profile) IsValidPassword(password string) bool {
var invalidProfile bool
var passwordHashAlgorithm string
var err error
if p == nil {
invalidProfile = true
} else {
passwordHashAlgorithm, err = getPasswordHashAlgorithm(p.PasswordHashVersion)
if err != nil {
invalidProfile = true
}
}
if invalidProfile {
algorithms := PasswordHashingAlgorithms()
// if the user profile is invalid, either a non-existing user name or password
// hash version, then return false but use the same amount of time as when we
// compare against valid user to avoid revealing whether the user account exists.
isValidPassword(password, dummyHashThatNeverMatchesAnyPassword, algorithms[rand.Intn(len(algorithms))]) //nolint:gosec
return false
}
return isValidPassword(password, p.PasswordHash, passwordHashAlgorithm)
}
// getPasswordHashAlgorithm returns the password hash algorithm given a version.
func getPasswordHashAlgorithm(passwordHashVersion int) (string, error) {
switch passwordHashVersion {
case ScryptHashVersion:
return scryptHashAlgorithm, nil
case Pbkdf2HashVersion:
return pbkdf2HashAlgorithm, nil
default:
return "", errors.Errorf("unsupported hash version (%d)", passwordHashVersion)
}
}
// GetPasswordHashVersion returns the password hash version given an algorithm.
func GetPasswordHashVersion(passwordHashAlgorithm string) (int, error) {
switch passwordHashAlgorithm {
case scryptHashAlgorithm:
return ScryptHashVersion, nil
case pbkdf2HashAlgorithm:
return Pbkdf2HashVersion, nil
default:
return 0, errors.Errorf("unsupported hash algorithm (%s)", passwordHashAlgorithm)
}
}