Use single shared random string generation function (#15741)

* Use single shared random string generation function

- Replace 3 functions that do the same with 1 shared one
- Use crypto/rand over math/rand for a stronger RNG
- Output only alphanumerical for URL compatibilty

Fixes: #15536

* use const string method

* Update modules/avatar/avatar.go

Co-authored-by: a1012112796 <1012112796@qq.com>

Co-authored-by: a1012112796 <1012112796@qq.com>
This commit is contained in:
silverwind 2021-05-10 08:45:17 +02:00 committed by GitHub
parent 270aab429e
commit 1e6fa57acb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 100 additions and 192 deletions

View file

@ -8,8 +8,8 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"xorm.io/xorm" "xorm.io/xorm"
@ -53,7 +53,7 @@ func addScratchHash(x *xorm.Engine) error {
for _, tfa := range tfas { for _, tfa := range tfas {
// generate salt // generate salt
salt, err := generate.GetRandomString(10) salt, err := util.RandomString(10)
if err != nil { if err != nil {
return err return err
} }

View file

@ -7,9 +7,9 @@ package migrations
import ( import (
"fmt" "fmt"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -65,7 +65,7 @@ func hashAppToken(x *xorm.Engine) error {
for _, token := range tokens { for _, token := range tokens {
// generate salt // generate salt
salt, err := generate.GetRandomString(10) salt, err := util.RandomString(10)
if err != nil { if err != nil {
return err return err
} }

View file

@ -10,8 +10,8 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
gouuid "github.com/google/uuid" gouuid "github.com/google/uuid"
) )
@ -40,7 +40,7 @@ func (t *AccessToken) AfterLoad() {
// NewAccessToken creates new access token. // NewAccessToken creates new access token.
func NewAccessToken(t *AccessToken) error { func NewAccessToken(t *AccessToken) error {
salt, err := generate.GetRandomString(10) salt, err := util.RandomString(10)
if err != nil { if err != nil {
return err return err
} }

View file

@ -11,10 +11,10 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
@ -34,11 +34,11 @@ type TwoFactor struct {
// GenerateScratchToken recreates the scratch token the user is using. // GenerateScratchToken recreates the scratch token the user is using.
func (t *TwoFactor) GenerateScratchToken() (string, error) { func (t *TwoFactor) GenerateScratchToken() (string, error) {
token, err := generate.GetRandomString(8) token, err := util.RandomString(8)
if err != nil { if err != nil {
return "", err return "", err
} }
t.ScratchSalt, _ = generate.GetRandomString(10) t.ScratchSalt, _ = util.RandomString(10)
t.ScratchHash = hashToken(token, t.ScratchSalt) t.ScratchHash = hashToken(token, t.ScratchSalt)
return token, nil return token, nil
} }

View file

@ -22,7 +22,6 @@ import (
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -746,7 +745,7 @@ func IsUserExist(uid int64, name string) (bool, error) {
// GetUserSalt returns a random user salt token. // GetUserSalt returns a random user salt token.
func GetUserSalt() (string, error) { func GetUserSalt() (string, error) {
return generate.GetRandomString(10) return util.RandomString(10)
} }
// NewGhostUser creates and returns a fake user for someone has deleted his/her account. // NewGhostUser creates and returns a fake user for someone has deleted his/her account.

View file

@ -12,10 +12,9 @@ import (
// Enable PNG support: // Enable PNG support:
_ "image/png" _ "image/png"
"math/rand"
"time"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/issue9/identicon" "github.com/issue9/identicon"
"github.com/nfnt/resize" "github.com/nfnt/resize"
@ -29,8 +28,11 @@ const AvatarSize = 290
// in custom size (height and width). // in custom size (height and width).
func RandomImageSize(size int, data []byte) (image.Image, error) { func RandomImageSize(size int, data []byte) (image.Image, error) {
randExtent := len(palette.WebSafe) - 32 randExtent := len(palette.WebSafe) - 32
rand.Seed(time.Now().UnixNano()) integer, err := util.RandomInt(int64(randExtent))
colorIndex := rand.Intn(randExtent) if err != nil {
return nil, fmt.Errorf("util.RandomInt: %v", err)
}
colorIndex := int(integer)
backColorIndex := colorIndex - 1 backColorIndex := colorIndex - 1
if backColorIndex < 0 { if backColorIndex < 0 {
backColorIndex = randExtent - 1 backColorIndex = randExtent - 1

View file

@ -13,12 +13,17 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_RandomImage(t *testing.T) { func Test_RandomImageSize(t *testing.T) {
_, err := RandomImage([]byte("gogs@local")) _, err := RandomImageSize(0, []byte("gitea@local"))
assert.NoError(t, err)
_, err = RandomImageSize(0, []byte("gogs@local"))
assert.Error(t, err) assert.Error(t, err)
_, err = RandomImageSize(64, []byte("gitea@local"))
assert.NoError(t, err)
}
func Test_RandomImage(t *testing.T) {
_, err := RandomImage([]byte("gitea@local"))
assert.NoError(t, err)
} }
func Test_PrepareWithPNG(t *testing.T) { func Test_PrepareWithPNG(t *testing.T) {

View file

@ -1,100 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"io"
)
// NewSecret creates a new secret
func NewSecret() (string, error) {
return NewSecretWithLength(32)
}
// NewSecretWithLength creates a new secret for a given length
func NewSecretWithLength(length int64) (string, error) {
return randomString(length)
}
func randomBytes(len int64) ([]byte, error) {
b := make([]byte, len)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
func randomString(len int64) (string, error) {
b, err := randomBytes(len)
return base64.URLEncoding.EncodeToString(b), err
}
// AesEncrypt encrypts text and given key with AES.
func AesEncrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
}
// AesDecrypt decrypts text and given key with AES.
func AesDecrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(text) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return nil, err
}
return data, nil
}
// EncryptSecret encrypts a string with given key into a hex string
func EncryptSecret(key string, str string) (string, error) {
keyHash := sha256.Sum256([]byte(key))
plaintext := []byte(str)
ciphertext, err := AesEncrypt(keyHash[:], plaintext)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// DecryptSecret decrypts a previously encrypted hex string
func DecryptSecret(key string, cipherhex string) (string, error) {
keyHash := sha256.Sum256([]byte(key))
ciphertext, err := base64.StdEncoding.DecodeString(cipherhex)
if err != nil {
return "", err
}
plaintext, err := AesDecrypt(keyHash[:], ciphertext)
if err != nil {
return "", err
}
return string(plaintext), nil
}

View file

@ -9,31 +9,12 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"io" "io"
"math/big"
"time" "time"
"code.gitea.io/gitea/modules/util"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
) )
// GetRandomString generate random string by specify chars.
func GetRandomString(n int) (string, error) {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
buffer := make([]byte, n)
max := big.NewInt(int64(len(alphanum)))
for i := 0; i < n; i++ {
index, err := randomInt(max)
if err != nil {
return "", err
}
buffer[i] = alphanum[index]
}
return string(buffer), nil
}
// NewInternalToken generate a new value intended to be used by INTERNAL_TOKEN. // NewInternalToken generate a new value intended to be used by INTERNAL_TOKEN.
func NewInternalToken() (string, error) { func NewInternalToken() (string, error) {
secretBytes := make([]byte, 32) secretBytes := make([]byte, 32)
@ -69,19 +50,10 @@ func NewJwtSecret() (string, error) {
// NewSecretKey generate a new value intended to be used by SECRET_KEY. // NewSecretKey generate a new value intended to be used by SECRET_KEY.
func NewSecretKey() (string, error) { func NewSecretKey() (string, error) {
secretKey, err := GetRandomString(64) secretKey, err := util.RandomString(64)
if err != nil { if err != nil {
return "", err return "", err
} }
return secretKey, nil return secretKey, nil
} }
func randomInt(max *big.Int) (int, error) {
rand, err := rand.Int(rand.Reader, max)
if err != nil {
return 0, err
}
return int(rand.Int64()), nil
}

View file

@ -1,24 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package generate
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
retVal := m.Run()
os.Exit(retVal)
}
func TestGetRandomString(t *testing.T) {
randomString, err := GetRandomString(4)
assert.NoError(t, err)
assert.Len(t, randomString, 4)
}

View file

@ -13,29 +13,18 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"io" "io"
"code.gitea.io/gitea/modules/util"
) )
// New creats a new secret // New creats a new secret
func New() (string, error) { func New() (string, error) {
return NewWithLength(32) return NewWithLength(44)
} }
// NewWithLength creates a new secret for a given length // NewWithLength creates a new secret for a given length
func NewWithLength(length int64) (string, error) { func NewWithLength(length int64) (string, error) {
return randomString(length) return util.RandomString(length)
}
func randomBytes(len int64) ([]byte, error) {
b := make([]byte, len)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
func randomString(len int64) (string, error) {
b, err := randomBytes(len)
return base64.URLEncoding.EncodeToString(b), err
} }
// AesEncrypt encrypts text and given key with AES. // AesEncrypt encrypts text and given key with AES.

View file

@ -13,7 +13,7 @@ import (
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
result, err := New() result, err := New()
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(result) > 32) assert.True(t, len(result) == 44)
result2, err := New() result2, err := New()
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -6,7 +6,9 @@ package util
import ( import (
"bytes" "bytes"
"crypto/rand"
"errors" "errors"
"math/big"
"strings" "strings"
) )
@ -124,3 +126,28 @@ func MergeInto(dict map[string]interface{}, values ...interface{}) (map[string]i
return dict, nil return dict, nil
} }
// RandomInt returns a random integer between 0 and limit, inclusive
func RandomInt(limit int64) (int64, error) {
int, err := rand.Int(rand.Reader, big.NewInt(limit))
if err != nil {
return 0, err
}
return int.Int64(), nil
}
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// RandomString generates a random alphanumerical string
func RandomString(length int64) (string, error) {
bytes := make([]byte, length)
limit := int64(len(letters))
for i := range bytes {
num, err := RandomInt(limit)
if err != nil {
return "", err
}
bytes[i] = letters[num]
}
return string(bytes), nil
}

View file

@ -5,6 +5,7 @@
package util package util
import ( import (
"regexp"
"strings" "strings"
"testing" "testing"
@ -118,3 +119,40 @@ func Test_NormalizeEOL(t *testing.T) {
assert.Equal(t, []byte("mix\nand\nmatch\n."), NormalizeEOL([]byte("mix\r\nand\rmatch\n."))) assert.Equal(t, []byte("mix\nand\nmatch\n."), NormalizeEOL([]byte("mix\r\nand\rmatch\n.")))
} }
func Test_RandomInt(t *testing.T) {
int, err := RandomInt(255)
assert.True(t, int >= 0)
assert.True(t, int <= 255)
assert.NoError(t, err)
}
func Test_RandomString(t *testing.T) {
str1, err := RandomString(32)
assert.NoError(t, err)
matches, err := regexp.MatchString(`^[a-zA-Z0-9]{32}$`, str1)
assert.NoError(t, err)
assert.True(t, matches)
str2, err := RandomString(32)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{32}$`, str1)
assert.NoError(t, err)
assert.True(t, matches)
assert.NotEqual(t, str1, str2)
str3, err := RandomString(256)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{256}$`, str3)
assert.NoError(t, err)
assert.True(t, matches)
str4, err := RandomString(256)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{256}$`, str4)
assert.NoError(t, err)
assert.True(t, matches)
assert.NotEqual(t, str3, str4)
}

View file

@ -13,11 +13,11 @@ import (
"code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/hcaptcha" "code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
@ -411,7 +411,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
if length < 256 { if length < 256 {
length = 256 length = 256
} }
password, err := generate.GetRandomString(length) password, err := util.RandomString(int64(length))
if err != nil { if err != nil {
ctx.RenderWithErr(err.Error(), tplSignUpOID, form) ctx.RenderWithErr(err.Error(), tplSignUpOID, form)
return return