use byteutil.S2B to avoid allocations when comparing + generating password hashes

This commit is contained in:
tobi 2025-04-07 14:48:48 +02:00
commit 9d10fb59b5
8 changed files with 53 additions and 18 deletions

View file

@ -25,6 +25,7 @@ import (
"slices" "slices"
"strings" "strings"
"codeberg.org/gruf/go-byteutil"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
@ -169,8 +170,8 @@ func (m *Module) validatePassword(
} }
if err := bcrypt.CompareHashAndPassword( if err := bcrypt.CompareHashAndPassword(
[]byte(user.EncryptedPassword), byteutil.S2B(user.EncryptedPassword),
[]byte(password), byteutil.S2B(password),
); err != nil { ); err != nil {
err := fmt.Errorf("password hash didn't match for user %s during sign in attempt: %s", user.Email, err) err := fmt.Errorf("password hash didn't match for user %s during sign in attempt: %s", user.Email, err)
return incorrectPassword(err) return incorrectPassword(err)
@ -278,8 +279,8 @@ func (m *Module) validate2FACode(c *gin.Context, user *gtsmodel.User, code strin
// Check against the user's stored codes. // Check against the user's stored codes.
for i := 0; i < len(user.TwoFactorBackups); i++ { for i := 0; i < len(user.TwoFactorBackups); i++ {
err := bcrypt.CompareHashAndPassword( err := bcrypt.CompareHashAndPassword(
[]byte(user.TwoFactorBackups[i]), byteutil.S2B(user.TwoFactorBackups[i]),
[]byte(code), byteutil.S2B(code),
) )
if err != nil { if err != nil {
// Doesn't match, // Doesn't match,

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"net/http" "net/http"
"codeberg.org/gruf/go-byteutil"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
@ -87,7 +88,10 @@ func (m *Module) AccountDeletePOSTHandler(c *gin.Context) {
return return
} }
if err := bcrypt.CompareHashAndPassword([]byte(authed.User.EncryptedPassword), []byte(form.Password)); err != nil { if err := bcrypt.CompareHashAndPassword(
byteutil.S2B(authed.User.EncryptedPassword),
byteutil.S2B(form.Password),
); err != nil {
err = errors.New("invalid password provided in account delete request") err = errors.New("invalid password provided in account delete request")
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1) apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
return return

View file

@ -23,6 +23,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"codeberg.org/gruf/go-byteutil"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/user" "github.com/superseriousbusiness/gotosocial/internal/api/client/user"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -50,11 +51,17 @@ func (suite *PasswordChangeTestSuite) TestPasswordChangePOST() {
} }
// new password should pass // new password should pass
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("peepeepoopoopassword")) err = bcrypt.CompareHashAndPassword(
byteutil.S2B(dbUser.EncryptedPassword),
byteutil.S2B("peepeepoopoopassword"),
)
suite.NoError(err) suite.NoError(err)
// old password should fail // old password should fail
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("password")) err = bcrypt.CompareHashAndPassword(
byteutil.S2B(dbUser.EncryptedPassword),
byteutil.S2B("password"),
)
suite.EqualError(err, "crypto/bcrypt: hashedPassword is not the hash of the given password") suite.EqualError(err, "crypto/bcrypt: hashedPassword is not the hash of the given password")
} }

View file

@ -25,6 +25,7 @@ import (
"slices" "slices"
"time" "time"
"codeberg.org/gruf/go-byteutil"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
@ -70,8 +71,8 @@ func (p *Processor) MoveSelf(
} }
if err := bcrypt.CompareHashAndPassword( if err := bcrypt.CompareHashAndPassword(
[]byte(authed.User.EncryptedPassword), byteutil.S2B(authed.User.EncryptedPassword),
[]byte(form.Password), byteutil.S2B(form.Password),
); err != nil { ); err != nil {
const text = "invalid password provided in Move request" const text = "invalid password provided in Move request"
return gtserror.NewErrorBadRequest(errors.New(text), text) return gtserror.NewErrorBadRequest(errors.New(text), text)

View file

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"time" "time"
"codeberg.org/gruf/go-byteutil"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
@ -41,7 +42,10 @@ func (p *Processor) EmailChange(
newEmail string, newEmail string,
) (*apimodel.User, gtserror.WithCode) { ) (*apimodel.User, gtserror.WithCode) {
// Ensure provided password is correct. // Ensure provided password is correct.
if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(password)); err != nil { if err := bcrypt.CompareHashAndPassword(
byteutil.S2B(user.EncryptedPassword),
byteutil.S2B(password),
); err != nil {
err := gtserror.Newf("%w", err) err := gtserror.Newf("%w", err)
return nil, gtserror.NewErrorUnauthorized(err, "password was incorrect") return nil, gtserror.NewErrorUnauthorized(err, "password was incorrect")
} }

View file

@ -20,6 +20,7 @@ package user
import ( import (
"context" "context"
"codeberg.org/gruf/go-byteutil"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/validate" "github.com/superseriousbusiness/gotosocial/internal/validate"
@ -29,7 +30,10 @@ import (
// PasswordChange processes a password change request for the given user. // PasswordChange processes a password change request for the given user.
func (p *Processor) PasswordChange(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode { func (p *Processor) PasswordChange(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode {
// Ensure provided oldPassword is the correct current password. // Ensure provided oldPassword is the correct current password.
if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(oldPassword)); err != nil { if err := bcrypt.CompareHashAndPassword(
byteutil.S2B(user.EncryptedPassword),
byteutil.S2B(oldPassword),
); err != nil {
err := gtserror.Newf("%w", err) err := gtserror.Newf("%w", err)
return gtserror.NewErrorUnauthorized(err, "old password was incorrect") return gtserror.NewErrorUnauthorized(err, "old password was incorrect")
} }
@ -48,7 +52,7 @@ func (p *Processor) PasswordChange(ctx context.Context, user *gtsmodel.User, old
// Hash the new password. // Hash the new password.
encryptedPassword, err := bcrypt.GenerateFromPassword( encryptedPassword, err := bcrypt.GenerateFromPassword(
[]byte(newPassword), byteutil.S2B(newPassword),
bcrypt.DefaultCost, bcrypt.DefaultCost,
) )
if err != nil { if err != nil {

View file

@ -22,6 +22,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"codeberg.org/gruf/go-byteutil"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -37,7 +38,10 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordOK() {
errWithCode := suite.user.PasswordChange(context.Background(), user, "password", "verygoodnewpassword") errWithCode := suite.user.PasswordChange(context.Background(), user, "password", "verygoodnewpassword")
suite.NoError(errWithCode) suite.NoError(errWithCode)
err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte("verygoodnewpassword")) err := bcrypt.CompareHashAndPassword(
byteutil.S2B(user.EncryptedPassword),
byteutil.S2B("verygoodnewpassword"),
)
suite.NoError(err) suite.NoError(err)
// get user from the db again // get user from the db again
@ -46,7 +50,10 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordOK() {
suite.NoError(err) suite.NoError(err)
// check the password has changed // check the password has changed
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("verygoodnewpassword")) err = bcrypt.CompareHashAndPassword(
byteutil.S2B(dbUser.EncryptedPassword),
byteutil.S2B("verygoodnewpassword"),
)
suite.NoError(err) suite.NoError(err)
} }
@ -64,7 +71,10 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordIncorrectOld() {
suite.NoError(err) suite.NoError(err)
// check the password has not changed // check the password has not changed
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("password")) err = bcrypt.CompareHashAndPassword(
byteutil.S2B(dbUser.EncryptedPassword),
byteutil.S2B("password"),
)
suite.NoError(err) suite.NoError(err)
} }
@ -82,7 +92,10 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordWeakNew() {
suite.NoError(err) suite.NoError(err)
// check the password has not changed // check the password has not changed
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("password")) err = bcrypt.CompareHashAndPassword(
byteutil.S2B(dbUser.EncryptedPassword),
byteutil.S2B("password"),
)
suite.NoError(err) suite.NoError(err)
} }

View file

@ -31,6 +31,7 @@ import (
"strings" "strings"
"time" "time"
"codeberg.org/gruf/go-byteutil"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pquerna/otp" "github.com/pquerna/otp"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
@ -251,8 +252,8 @@ func (p *Processor) TwoFactorDisable(
// Ensure provided password is correct. // Ensure provided password is correct.
if err := bcrypt.CompareHashAndPassword( if err := bcrypt.CompareHashAndPassword(
[]byte(user.EncryptedPassword), byteutil.S2B(user.EncryptedPassword),
[]byte(password), byteutil.S2B(password),
); err != nil { ); err != nil {
const errText = "incorrect password" const errText = "incorrect password"
return gtserror.NewErrorUnauthorized(errors.New(errText), errText) return gtserror.NewErrorUnauthorized(errors.New(errText), errText)