Skip to content

Commit

Permalink
Use base32 for 2FA scratch token
Browse files Browse the repository at this point in the history
  • Loading branch information
wxiaoguang committed Jan 24, 2022
1 parent 4bfd749 commit 1dc2860
Show file tree
Hide file tree
Showing 11 changed files with 40 additions and 37 deletions.
7 changes: 5 additions & 2 deletions models/auth/twofactor.go
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/md5"
"crypto/sha256"
"crypto/subtle"
"encoding/base32"
"encoding/base64"
"fmt"

Expand Down Expand Up @@ -58,11 +59,13 @@ func init() {

// GenerateScratchToken recreates the scratch token the user is using.
func (t *TwoFactor) GenerateScratchToken() (string, error) {
token, err := util.RandomString(8)
tokenBytes, err := util.SecureRandomBytes(6)
if err != nil {
return "", err
}
t.ScratchSalt, _ = util.RandomString(10)
const base32Chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
token := base32.NewEncoding(base32Chars).WithPadding(base32.NoPadding).EncodeToString(tokenBytes)
t.ScratchSalt, _ = util.SecureRandomString(10)
t.ScratchHash = HashToken(token, t.ScratchSalt)
return token, nil
}
Expand Down
2 changes: 1 addition & 1 deletion models/migrations/v71.go
Expand Up @@ -53,7 +53,7 @@ func addScratchHash(x *xorm.Engine) error {

for _, tfa := range tfas {
// generate salt
salt, err := util.RandomString(10)
salt, err := util.SecureRandomString(10)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion models/migrations/v85.go
Expand Up @@ -65,7 +65,7 @@ func hashAppToken(x *xorm.Engine) error {

for _, token := range tokens {
// generate salt
salt, err := util.RandomString(10)
salt, err := util.SecureRandomString(10)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion models/token.go
Expand Up @@ -62,7 +62,7 @@ func init() {

// NewAccessToken creates new access token.
func NewAccessToken(t *AccessToken) error {
salt, err := util.RandomString(10)
salt, err := util.SecureRandomString(10)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion models/user/user.go
Expand Up @@ -533,7 +533,7 @@ const SaltByteLength = 16

// GetUserSalt returns a random user salt token.
func GetUserSalt() (string, error) {
rBytes, err := util.RandomBytes(SaltByteLength)
rBytes, err := util.SecureRandomBytes(SaltByteLength)
if err != nil {
return "", err
}
Expand Down
2 changes: 1 addition & 1 deletion modules/generate/generate.go
Expand Up @@ -60,7 +60,7 @@ func NewJwtSecretBase64() (string, error) {

// NewSecretKey generate a new value intended to be used by SECRET_KEY.
func NewSecretKey() (string, error) {
secretKey, err := util.RandomString(64)
secretKey, err := util.SecureRandomString(64)
if err != nil {
return "", err
}
Expand Down
2 changes: 1 addition & 1 deletion modules/secret/secret.go
Expand Up @@ -24,7 +24,7 @@ func New() (string, error) {

// NewWithLength creates a new secret for a given length
func NewWithLength(length int64) (string, error) {
return util.RandomString(length)
return util.SecureRandomString(length)
}

// AesEncrypt encrypts text and given key with AES.
Expand Down
36 changes: 18 additions & 18 deletions modules/util/util.go
Expand Up @@ -137,36 +137,36 @@ func MergeInto(dict map[string]interface{}, values ...interface{}) (map[string]i
return dict, nil
}

// RandomInt returns a random integer between 0 and limit, inclusive
func RandomInt(limit int64) (int64, error) {
// SecureRandomInt returns a secure random integer between 0 and limit, inclusive
func SecureRandomInt(limit int64) (int64, error) {
rInt, err := rand.Int(rand.Reader, big.NewInt(limit))
if err != nil {
return 0, err
}
return rInt.Int64(), nil
}

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const randomLetters = "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)
// SecureRandomString generates a secure random alphanumerical string, each byte is generated by [0,61] range
func SecureRandomString(length int64) (string, error) {
buf := make([]byte, length)
limit := int64(len(randomLetters))
for i := range buf {
num, err := SecureRandomInt(limit)
if err != nil {
return "", err
}
bytes[i] = letters[num]
buf[i] = randomLetters[num]
}
return string(bytes), nil
return string(buf), nil
}

// RandomBytes generates `length` bytes
// This differs from RandomString, as RandomString is limits each byte to have
// a maximum value of 63 instead of 255(max byte size)
func RandomBytes(length int64) ([]byte, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
return bytes, err
// SecureRandomBytes generates `length` bytes
// This differs from SecureRandomString, as each byte in SecureRandomString is generated by [0,61] range
// This function generates totally random bytes, each byte is generated by [0,255] range
func SecureRandomBytes(length int64) ([]byte, error) {
buf := make([]byte, length)
_, err := rand.Read(buf)
return buf, err
}
18 changes: 9 additions & 9 deletions modules/util/util_test.go
Expand Up @@ -120,34 +120,34 @@ func Test_NormalizeEOL(t *testing.T) {
}

func Test_RandomInt(t *testing.T) {
int, err := RandomInt(255)
int, err := SecureRandomInt(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)
str1, err := SecureRandomString(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)
str2, err := SecureRandomString(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)
str3, err := SecureRandomString(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)
str4, err := SecureRandomString(256)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{256}$`, str4)
assert.NoError(t, err)
Expand All @@ -157,18 +157,18 @@ func Test_RandomString(t *testing.T) {
}

func Test_RandomBytes(t *testing.T) {
bytes1, err := RandomBytes(32)
bytes1, err := SecureRandomBytes(32)
assert.NoError(t, err)

bytes2, err := RandomBytes(32)
bytes2, err := SecureRandomBytes(32)
assert.NoError(t, err)

assert.NotEqual(t, bytes1, bytes2)

bytes3, err := RandomBytes(256)
bytes3, err := SecureRandomBytes(256)
assert.NoError(t, err)

bytes4, err := RandomBytes(256)
bytes4, err := SecureRandomBytes(256)
assert.NoError(t, err)

assert.NotEqual(t, bytes3, bytes4)
Expand Down
2 changes: 1 addition & 1 deletion routers/web/auth/openid.go
Expand Up @@ -416,7 +416,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
if length < 256 {
length = 256
}
password, err := util.RandomString(int64(length))
password, err := util.SecureRandomString(int64(length))
if err != nil {
ctx.RenderWithErr(err.Error(), tplSignUpOID, form)
return
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/setting.go
Expand Up @@ -337,7 +337,7 @@ func SettingsPost(ctx *context.Context) {
return
}

remoteSuffix, err := util.RandomString(10)
remoteSuffix, err := util.SecureRandomString(10)
if err != nil {
ctx.ServerError("RandomString", err)
return
Expand Down

0 comments on commit 1dc2860

Please sign in to comment.