123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- // Copyright 2017 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package auth
-
- import (
- "crypto/md5"
- "crypto/sha256"
- "crypto/subtle"
- "encoding/base32"
- "encoding/base64"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/secret"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
-
- "github.com/pquerna/otp/totp"
- "golang.org/x/crypto/pbkdf2"
- )
-
- //
- // Two-factor authentication
- //
-
- // ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
- type ErrTwoFactorNotEnrolled struct {
- UID int64
- }
-
- // IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
- func IsErrTwoFactorNotEnrolled(err error) bool {
- _, ok := err.(ErrTwoFactorNotEnrolled)
- return ok
- }
-
- func (err ErrTwoFactorNotEnrolled) Error() string {
- return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
- }
-
- // Unwrap unwraps this as a ErrNotExist err
- func (err ErrTwoFactorNotEnrolled) Unwrap() error {
- return util.ErrNotExist
- }
-
- // TwoFactor represents a two-factor authentication token.
- type TwoFactor struct {
- ID int64 `xorm:"pk autoincr"`
- UID int64 `xorm:"UNIQUE"`
- Secret string
- ScratchSalt string
- ScratchHash string
- LastUsedPasscode string `xorm:"VARCHAR(10)"`
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
- UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
- }
-
- func init() {
- db.RegisterModel(new(TwoFactor))
- }
-
- // 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
- }
- // 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)
- t.ScratchSalt, _ = util.CryptoRandomString(10)
- t.ScratchHash = HashToken(token, t.ScratchSalt)
- return token, nil
- }
-
- // HashToken return the hashable salt
- func HashToken(token, salt string) string {
- tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
- return fmt.Sprintf("%x", tempHash)
- }
-
- // VerifyScratchToken verifies if the specified scratch token is valid.
- func (t *TwoFactor) VerifyScratchToken(token string) bool {
- if len(token) == 0 {
- return false
- }
- tempHash := HashToken(token, t.ScratchSalt)
- return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
- }
-
- func (t *TwoFactor) getEncryptionKey() []byte {
- k := md5.Sum([]byte(setting.SecretKey))
- return k[:]
- }
-
- // SetSecret sets the 2FA secret.
- func (t *TwoFactor) SetSecret(secretString string) error {
- secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString))
- if err != nil {
- return err
- }
- t.Secret = base64.StdEncoding.EncodeToString(secretBytes)
- return nil
- }
-
- // ValidateTOTP validates the provided passcode.
- func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
- decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
- if err != nil {
- return false, err
- }
- secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
- if err != nil {
- return false, err
- }
- secretStr := string(secretBytes)
- return totp.Validate(passcode, secretStr), nil
- }
-
- // NewTwoFactor creates a new two-factor authentication token.
- func NewTwoFactor(t *TwoFactor) error {
- _, err := db.GetEngine(db.DefaultContext).Insert(t)
- return err
- }
-
- // UpdateTwoFactor updates a two-factor authentication token.
- func UpdateTwoFactor(t *TwoFactor) error {
- _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t)
- return err
- }
-
- // GetTwoFactorByUID returns the two-factor authentication token associated with
- // the user, if any.
- func GetTwoFactorByUID(uid int64) (*TwoFactor, error) {
- twofa := &TwoFactor{}
- has, err := db.GetEngine(db.DefaultContext).Where("uid=?", uid).Get(twofa)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrTwoFactorNotEnrolled{uid}
- }
- return twofa, nil
- }
-
- // HasTwoFactorByUID returns the two-factor authentication token associated with
- // the user, if any.
- func HasTwoFactorByUID(uid int64) (bool, error) {
- return db.GetEngine(db.DefaultContext).Where("uid=?", uid).Exist(&TwoFactor{})
- }
-
- // DeleteTwoFactorByID deletes two-factor authentication token by given ID.
- func DeleteTwoFactorByID(id, userID int64) error {
- cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&TwoFactor{
- UID: userID,
- })
- if err != nil {
- return err
- } else if cnt != 1 {
- return ErrTwoFactorNotEnrolled{userID}
- }
- return nil
- }
|