105 lines
3.7 KiB
Go
105 lines
3.7 KiB
Go
package gateway
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/password"
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/securestring"
|
|
)
|
|
|
|
// VerifyPasswordUseCase verifies a password against a hash
|
|
type VerifyPasswordUseCase struct {
|
|
passwordProvider password.PasswordProvider
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// ProvideVerifyPasswordUseCase creates a new VerifyPasswordUseCase
|
|
func ProvideVerifyPasswordUseCase(
|
|
passwordProvider password.PasswordProvider,
|
|
logger *zap.Logger,
|
|
) *VerifyPasswordUseCase {
|
|
return &VerifyPasswordUseCase{
|
|
passwordProvider: passwordProvider,
|
|
logger: logger.Named("verify-password-usecase"),
|
|
}
|
|
}
|
|
|
|
// Execute verifies a plain password against a hashed password
|
|
// Returns ErrInvalidCredentials if password doesn't match (for security)
|
|
func (uc *VerifyPasswordUseCase) Execute(plainPassword, passwordHash string) error {
|
|
// Create secure string from password
|
|
securePassword, err := securestring.NewSecureString(plainPassword)
|
|
if err != nil {
|
|
uc.logger.Error("failed to create secure password", zap.Error(err))
|
|
return fmt.Errorf("failed to process password: %w", err)
|
|
}
|
|
defer securePassword.Wipe() // Clean up password from memory
|
|
|
|
// Verify password
|
|
match, err := uc.passwordProvider.ComparePasswordAndHash(securePassword, passwordHash)
|
|
if err != nil {
|
|
uc.logger.Error("failed to compare password and hash", zap.Error(err))
|
|
return fmt.Errorf("failed to verify password: %w", err)
|
|
}
|
|
|
|
if !match {
|
|
uc.logger.Debug("password verification failed")
|
|
return ErrInvalidCredentials
|
|
}
|
|
|
|
uc.logger.Debug("password verified successfully")
|
|
return nil
|
|
}
|
|
|
|
// ExecuteTimingSafe verifies a password in a timing-safe manner
|
|
// CWE-208: Observable Timing Discrepancy - Prevents user enumeration via timing attacks
|
|
//
|
|
// This method ALWAYS performs password hashing, even when the user doesn't exist,
|
|
// to ensure constant-time behavior regardless of whether the email exists in the system.
|
|
//
|
|
// Parameters:
|
|
// - plainPassword: The password to verify
|
|
// - passwordHash: The hash to compare against (empty string if user doesn't exist)
|
|
// - userExists: Whether the user exists in the system
|
|
//
|
|
// Returns ErrInvalidCredentials if verification fails for any reason
|
|
func (uc *VerifyPasswordUseCase) ExecuteTimingSafe(plainPassword, passwordHash string, userExists bool) error {
|
|
// Create secure string from password
|
|
securePassword, err := securestring.NewSecureString(plainPassword)
|
|
if err != nil {
|
|
uc.logger.Error("failed to create secure password", zap.Error(err))
|
|
return fmt.Errorf("failed to process password: %w", err)
|
|
}
|
|
defer securePassword.Wipe() // Clean up password from memory
|
|
|
|
if !userExists || passwordHash == "" {
|
|
// User doesn't exist or no password hash available
|
|
// Perform dummy password hashing to maintain constant time
|
|
uc.logger.Debug("performing timing-safe dummy password verification")
|
|
match, err := uc.passwordProvider.ComparePasswordAndHash(securePassword, password.DummyPasswordHash)
|
|
if err != nil {
|
|
// Even if dummy verification fails, we don't care about the error
|
|
// The important part is that we spent the same amount of time
|
|
uc.logger.Debug("dummy password verification completed", zap.Error(err))
|
|
}
|
|
_ = match // Explicitly ignore the result
|
|
return ErrInvalidCredentials
|
|
}
|
|
|
|
// User exists - perform real password verification
|
|
match, err := uc.passwordProvider.ComparePasswordAndHash(securePassword, passwordHash)
|
|
if err != nil {
|
|
uc.logger.Error("failed to compare password and hash", zap.Error(err))
|
|
return ErrInvalidCredentials
|
|
}
|
|
|
|
if !match {
|
|
uc.logger.Debug("password verification failed")
|
|
return ErrInvalidCredentials
|
|
}
|
|
|
|
uc.logger.Debug("password verified successfully")
|
|
return nil
|
|
}
|