code.gitea.io/gitea@v1.21.7/models/auth/twofactor.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package auth 5 6 import ( 7 "context" 8 "crypto/md5" 9 "crypto/subtle" 10 "encoding/base32" 11 "encoding/base64" 12 "encoding/hex" 13 "fmt" 14 15 "code.gitea.io/gitea/models/db" 16 "code.gitea.io/gitea/modules/secret" 17 "code.gitea.io/gitea/modules/setting" 18 "code.gitea.io/gitea/modules/timeutil" 19 "code.gitea.io/gitea/modules/util" 20 21 "github.com/minio/sha256-simd" 22 "github.com/pquerna/otp/totp" 23 "golang.org/x/crypto/pbkdf2" 24 ) 25 26 // 27 // Two-factor authentication 28 // 29 30 // ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication. 31 type ErrTwoFactorNotEnrolled struct { 32 UID int64 33 } 34 35 // IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled. 36 func IsErrTwoFactorNotEnrolled(err error) bool { 37 _, ok := err.(ErrTwoFactorNotEnrolled) 38 return ok 39 } 40 41 func (err ErrTwoFactorNotEnrolled) Error() string { 42 return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID) 43 } 44 45 // Unwrap unwraps this as a ErrNotExist err 46 func (err ErrTwoFactorNotEnrolled) Unwrap() error { 47 return util.ErrNotExist 48 } 49 50 // TwoFactor represents a two-factor authentication token. 51 type TwoFactor struct { 52 ID int64 `xorm:"pk autoincr"` 53 UID int64 `xorm:"UNIQUE"` 54 Secret string 55 ScratchSalt string 56 ScratchHash string 57 LastUsedPasscode string `xorm:"VARCHAR(10)"` 58 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` 59 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` 60 } 61 62 func init() { 63 db.RegisterModel(new(TwoFactor)) 64 } 65 66 // GenerateScratchToken recreates the scratch token the user is using. 67 func (t *TwoFactor) GenerateScratchToken() (string, error) { 68 tokenBytes, err := util.CryptoRandomBytes(6) 69 if err != nil { 70 return "", err 71 } 72 // these chars are specially chosen, avoid ambiguous chars like `0`, `O`, `1`, `I`. 73 const base32Chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" 74 token := base32.NewEncoding(base32Chars).WithPadding(base32.NoPadding).EncodeToString(tokenBytes) 75 t.ScratchSalt, _ = util.CryptoRandomString(10) 76 t.ScratchHash = HashToken(token, t.ScratchSalt) 77 return token, nil 78 } 79 80 // HashToken return the hashable salt 81 func HashToken(token, salt string) string { 82 tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New) 83 return hex.EncodeToString(tempHash) 84 } 85 86 // VerifyScratchToken verifies if the specified scratch token is valid. 87 func (t *TwoFactor) VerifyScratchToken(token string) bool { 88 if len(token) == 0 { 89 return false 90 } 91 tempHash := HashToken(token, t.ScratchSalt) 92 return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1 93 } 94 95 func (t *TwoFactor) getEncryptionKey() []byte { 96 k := md5.Sum([]byte(setting.SecretKey)) 97 return k[:] 98 } 99 100 // SetSecret sets the 2FA secret. 101 func (t *TwoFactor) SetSecret(secretString string) error { 102 secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString)) 103 if err != nil { 104 return err 105 } 106 t.Secret = base64.StdEncoding.EncodeToString(secretBytes) 107 return nil 108 } 109 110 // ValidateTOTP validates the provided passcode. 111 func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { 112 decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret) 113 if err != nil { 114 return false, err 115 } 116 secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret) 117 if err != nil { 118 return false, err 119 } 120 secretStr := string(secretBytes) 121 return totp.Validate(passcode, secretStr), nil 122 } 123 124 // NewTwoFactor creates a new two-factor authentication token. 125 func NewTwoFactor(ctx context.Context, t *TwoFactor) error { 126 _, err := db.GetEngine(ctx).Insert(t) 127 return err 128 } 129 130 // UpdateTwoFactor updates a two-factor authentication token. 131 func UpdateTwoFactor(ctx context.Context, t *TwoFactor) error { 132 _, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t) 133 return err 134 } 135 136 // GetTwoFactorByUID returns the two-factor authentication token associated with 137 // the user, if any. 138 func GetTwoFactorByUID(ctx context.Context, uid int64) (*TwoFactor, error) { 139 twofa := &TwoFactor{} 140 has, err := db.GetEngine(ctx).Where("uid=?", uid).Get(twofa) 141 if err != nil { 142 return nil, err 143 } else if !has { 144 return nil, ErrTwoFactorNotEnrolled{uid} 145 } 146 return twofa, nil 147 } 148 149 // HasTwoFactorByUID returns the two-factor authentication token associated with 150 // the user, if any. 151 func HasTwoFactorByUID(ctx context.Context, uid int64) (bool, error) { 152 return db.GetEngine(ctx).Where("uid=?", uid).Exist(&TwoFactor{}) 153 } 154 155 // DeleteTwoFactorByID deletes two-factor authentication token by given ID. 156 func DeleteTwoFactorByID(ctx context.Context, id, userID int64) error { 157 cnt, err := db.GetEngine(ctx).ID(id).Delete(&TwoFactor{ 158 UID: userID, 159 }) 160 if err != nil { 161 return err 162 } else if cnt != 1 { 163 return ErrTwoFactorNotEnrolled{userID} 164 } 165 return nil 166 }