chore(sec): unify usage of crypto/rand.Read (#7453)

- Unify the usage of [`crypto/rand.Read`](https://pkg.go.dev/crypto/rand#Read) to `util.CryptoRandomBytes`.
- Refactor `util.CryptoRandomBytes` to never return an error. It is documented by Go, https://go.dev/issue/66821, to always succeed. So if we still receive a error or if the returned bytes read is not equal to the expected bytes to be read we panic (just to be on the safe side).
- This simplifies a lot of code to no longer care about error handling.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7453
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
This commit is contained in:
Gusted 2025-04-04 03:31:37 +00:00 committed by Earl Warren
parent 99fc04b763
commit 53df0bf9a4
25 changed files with 61 additions and 163 deletions

View file

@ -168,12 +168,7 @@ func (r *ActionRunner) GenerateToken() (err error) {
// UpdateSecret updates the hash based on the specified token. It does not
// ensure that the runner's UUID matches the first 16 bytes of the token.
func (r *ActionRunner) UpdateSecret(token string) error {
saltBytes, err := util.CryptoRandomBytes(16)
if err != nil {
return fmt.Errorf("CryptoRandomBytes %v", err)
}
salt := hex.EncodeToString(saltBytes)
salt := hex.EncodeToString(util.CryptoRandomBytes(16))
r.Token = token
r.TokenSalt = salt

View file

@ -22,11 +22,7 @@ func generateSaltedToken() (string, string, string, string, error) {
if err != nil {
return "", "", "", "", err
}
buf, err := util.CryptoRandomBytes(20)
if err != nil {
return "", "", "", "", err
}
token := hex.EncodeToString(buf)
token := hex.EncodeToString(util.CryptoRandomBytes(20))
hash := auth_model.HashToken(token, salt)
return token, salt, hash, token[len(token)-8:], nil
}

View file

@ -111,12 +111,8 @@ func generateAccessToken(t *AccessToken) error {
if err != nil {
return err
}
token, err := util.CryptoRandomBytes(20)
if err != nil {
return err
}
t.TokenSalt = salt
t.Token = hex.EncodeToString(token)
t.Token = hex.EncodeToString(util.CryptoRandomBytes(20))
t.TokenHash = HashToken(t.Token, t.TokenSalt)
t.TokenLastEight = t.Token[len(t.Token)-8:]
return nil

View file

@ -63,10 +63,7 @@ func (authToken *AuthorizationToken) IsExpired() bool {
func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp, purpose AuthorizationPurpose) (lookupKey, validator string, err error) {
// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
// and the other 32 bytes will be used for the validator.
rBytes, err := util.CryptoRandomBytes(64)
if err != nil {
return "", "", err
}
rBytes := util.CryptoRandomBytes(64)
hexEncoded := hex.EncodeToString(rBytes)
validator, lookupKey = hexEncoded[64:], hexEncoded[:64]

View file

@ -184,13 +184,9 @@ var base32Lower = base32.NewEncoding(lowerBase32Chars).WithPadding(base32.NoPadd
// GenerateClientSecret will generate the client secret and returns the plaintext and saves the hash at the database
func (app *OAuth2Application) GenerateClientSecret(ctx context.Context) (string, error) {
rBytes, err := util.CryptoRandomBytes(32)
if err != nil {
return "", err
}
// Add a prefix to the base32, this is in order to make it easier
// for code scanners to grab sensitive tokens.
clientSecret := "gto_" + base32Lower.EncodeToString(rBytes)
clientSecret := "gto_" + base32Lower.EncodeToString(util.CryptoRandomBytes(32))
hashedSecret, err := bcrypt.GenerateFromPassword([]byte(clientSecret), bcrypt.DefaultCost)
if err != nil {
@ -475,13 +471,9 @@ func (grant *OAuth2Grant) TableName() string {
// GenerateNewAuthorizationCode generates a new authorization code for a grant and saves it to the database
func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redirectURI, codeChallenge, codeChallengeMethod string) (code *OAuth2AuthorizationCode, err error) {
rBytes, err := util.CryptoRandomBytes(32)
if err != nil {
return &OAuth2AuthorizationCode{}, err
}
// Add a prefix to the base32, this is in order to make it easier
// for code scanners to grab sensitive tokens.
codeSecret := "gta_" + base32Lower.EncodeToString(rBytes)
codeSecret := "gta_" + base32Lower.EncodeToString(util.CryptoRandomBytes(32))
code = &OAuth2AuthorizationCode{
Grant: grant,

View file

@ -61,17 +61,13 @@ func init() {
}
// GenerateScratchToken recreates the scratch token the user is using.
func (t *TwoFactor) GenerateScratchToken() (string, error) {
tokenBytes, err := util.CryptoRandomBytes(6)
if err != nil {
return "", err
}
func (t *TwoFactor) GenerateScratchToken() string {
// these chars are specially chosen, avoid ambiguous chars like `0`, `O`, `1`, `I`.
const base32Chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
token := base32.NewEncoding(base32Chars).WithPadding(base32.NoPadding).EncodeToString(tokenBytes)
token := base32.NewEncoding(base32Chars).WithPadding(base32.NoPadding).EncodeToString(util.CryptoRandomBytes(6))
t.ScratchSalt, _ = util.CryptoRandomString(10)
t.ScratchHash = HashToken(token, t.ScratchSalt)
return token, nil
return token
}
// HashToken return the hashable salt

View file

@ -289,12 +289,8 @@ func CreateOrganization(ctx context.Context, org *Organization, owner *user_mode
}
org.LowerName = strings.ToLower(org.Name)
if org.Rands, err = user_model.GetUserSalt(); err != nil {
return err
}
if org.Salt, err = user_model.GetUserSalt(); err != nil {
return err
}
org.Rands = user_model.GetUserSalt()
org.Salt = user_model.GetUserSalt()
org.UseCustomAvatar = true
org.MaxRepoCreation = -1
org.NumTeams = 1

View file

@ -266,9 +266,7 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
if err != nil {
return err
}
if user.Rands, err = GetUserSalt(); err != nil {
return err
}
user.Rands = GetUserSalt()
email.IsActivated = activate
if _, err := db.GetEngine(ctx).ID(email.ID).Cols("is_activated").Update(email); err != nil {
return err
@ -403,9 +401,7 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
// The user's activation state should be synchronized with the primary email
if user.IsActive != activate {
user.IsActive = activate
if user.Rands, err = GetUserSalt(); err != nil {
return fmt.Errorf("unable to generate salt: %w", err)
}
user.Rands = GetUserSalt()
if err = UpdateUserCols(ctx, user, "is_active", "rands"); err != nil {
return fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err)
}

View file

@ -387,9 +387,7 @@ func (u *User) SetPassword(passwd string) (err error) {
return err
}
if u.Salt, err = GetUserSalt(); err != nil {
return err
}
u.Salt = GetUserSalt()
if u.Passwd, err = hash.Parse(setting.PasswordHashAlgo).Hash(passwd, u.Salt); err != nil {
return err
}
@ -559,13 +557,9 @@ func IsUserExist(ctx context.Context, uid int64, name string) (bool, error) {
const SaltByteLength = 16
// GetUserSalt returns a random user salt token.
func GetUserSalt() (string, error) {
rBytes, err := util.CryptoRandomBytes(SaltByteLength)
if err != nil {
return "", err
}
func GetUserSalt() string {
// Returns a 32 bytes long string.
return hex.EncodeToString(rBytes), nil
return hex.EncodeToString(util.CryptoRandomBytes(SaltByteLength))
}
// Note: The set of characters here can safely expand without a breaking change,
@ -772,9 +766,7 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
u.LowerName = strings.ToLower(u.Name)
u.AvatarEmail = u.Email
if u.Rands, err = GetUserSalt(); err != nil {
return err
}
u.Rands = GetUserSalt()
if u.Passwd != "" {
if err = u.SetPassword(u.Passwd); err != nil {
return err