2017-01-16 05:14:29 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2017-01-16 05:14:29 +03:00
2022-01-02 16:12:35 +03:00
package auth
2017-01-16 05:14:29 +03:00
import (
2023-09-15 09:13:19 +03:00
"context"
2017-01-16 05:14:29 +03:00
"crypto/md5"
2024-02-25 16:32:13 +03:00
"crypto/sha256"
2017-01-16 05:14:29 +03:00
"crypto/subtle"
2022-01-26 07:10:10 +03:00
"encoding/base32"
2017-01-16 05:14:29 +03:00
"encoding/base64"
2022-11-28 14:19:18 +03:00
"encoding/hex"
2018-07-27 15:54:50 +03:00
"fmt"
2017-01-16 05:14:29 +03:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2020-10-05 08:49:33 +03:00
"code.gitea.io/gitea/modules/secret"
2017-01-16 05:14:29 +03:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2021-05-10 09:45:17 +03:00
"code.gitea.io/gitea/modules/util"
2019-08-15 17:46:21 +03:00
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
2017-01-16 05:14:29 +03:00
)
2021-09-25 16:00:12 +03:00
//
// 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 )
}
2022-10-18 08:50:37 +03:00
// Unwrap unwraps this as a ErrNotExist err
func ( err ErrTwoFactorNotEnrolled ) Unwrap ( ) error {
return util . ErrNotExist
}
2017-01-16 05:14:29 +03:00
// TwoFactor represents a two-factor authentication token.
type TwoFactor struct {
2018-05-02 18:02:02 +03:00
ID int64 ` xorm:"pk autoincr" `
UID int64 ` xorm:"UNIQUE" `
Secret string
2018-07-27 15:54:50 +03:00
ScratchSalt string
ScratchHash string
2019-08-15 17:46:21 +03:00
LastUsedPasscode string ` xorm:"VARCHAR(10)" `
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2017-01-16 05:14:29 +03:00
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( TwoFactor ) )
}
2017-01-16 05:14:29 +03:00
// GenerateScratchToken recreates the scratch token the user is using.
2018-07-27 15:54:50 +03:00
func ( t * TwoFactor ) GenerateScratchToken ( ) ( string , error ) {
2022-01-26 07:10:10 +03:00
tokenBytes , err := util . CryptoRandomBytes ( 6 )
2017-01-16 05:14:29 +03:00
if err != nil {
2018-07-27 15:54:50 +03:00
return "" , err
2017-01-16 05:14:29 +03:00
}
2022-01-26 07:10:10 +03:00
// 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 )
2021-09-25 16:00:12 +03:00
t . ScratchHash = HashToken ( token , t . ScratchSalt )
2018-07-27 15:54:50 +03:00
return token , nil
}
2021-09-25 16:00:12 +03:00
// HashToken return the hashable salt
func HashToken ( token , salt string ) string {
2018-07-27 15:54:50 +03:00
tempHash := pbkdf2 . Key ( [ ] byte ( token ) , [ ] byte ( salt ) , 10000 , 50 , sha256 . New )
2022-11-28 14:19:18 +03:00
return hex . EncodeToString ( tempHash )
2017-01-16 05:14:29 +03:00
}
// VerifyScratchToken verifies if the specified scratch token is valid.
func ( t * TwoFactor ) VerifyScratchToken ( token string ) bool {
if len ( token ) == 0 {
return false
}
2021-09-25 16:00:12 +03:00
tempHash := HashToken ( token , t . ScratchSalt )
2018-07-27 15:54:50 +03:00
return subtle . ConstantTimeCompare ( [ ] byte ( t . ScratchHash ) , [ ] byte ( tempHash ) ) == 1
2017-01-16 05:14:29 +03:00
}
func ( t * TwoFactor ) getEncryptionKey ( ) [ ] byte {
k := md5 . Sum ( [ ] byte ( setting . SecretKey ) )
return k [ : ]
}
// SetSecret sets the 2FA secret.
2020-10-05 08:49:33 +03:00
func ( t * TwoFactor ) SetSecret ( secretString string ) error {
secretBytes , err := secret . AesEncrypt ( t . getEncryptionKey ( ) , [ ] byte ( secretString ) )
2017-01-16 05:14:29 +03:00
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
}
2020-10-05 08:49:33 +03:00
secretBytes , err := secret . AesDecrypt ( t . getEncryptionKey ( ) , decodedStoredSecret )
2017-01-16 05:14:29 +03:00
if err != nil {
return false , err
}
2020-10-05 08:49:33 +03:00
secretStr := string ( secretBytes )
2017-01-16 05:14:29 +03:00
return totp . Validate ( passcode , secretStr ) , nil
}
// NewTwoFactor creates a new two-factor authentication token.
2023-09-15 09:13:19 +03:00
func NewTwoFactor ( ctx context . Context , t * TwoFactor ) error {
_ , err := db . GetEngine ( ctx ) . Insert ( t )
2017-01-16 05:14:29 +03:00
return err
}
// UpdateTwoFactor updates a two-factor authentication token.
2023-09-15 09:13:19 +03:00
func UpdateTwoFactor ( ctx context . Context , t * TwoFactor ) error {
_ , err := db . GetEngine ( ctx ) . ID ( t . ID ) . AllCols ( ) . Update ( t )
2017-01-16 05:14:29 +03:00
return err
}
// GetTwoFactorByUID returns the two-factor authentication token associated with
// the user, if any.
2023-09-15 09:13:19 +03:00
func GetTwoFactorByUID ( ctx context . Context , uid int64 ) ( * TwoFactor , error ) {
2020-06-17 20:50:11 +03:00
twofa := & TwoFactor { }
2023-09-15 09:13:19 +03:00
has , err := db . GetEngine ( ctx ) . Where ( "uid=?" , uid ) . Get ( twofa )
2017-01-16 05:14:29 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrTwoFactorNotEnrolled { uid }
}
return twofa , nil
}
2021-11-09 01:47:19 +03:00
// HasTwoFactorByUID returns the two-factor authentication token associated with
// the user, if any.
2023-09-15 09:13:19 +03:00
func HasTwoFactorByUID ( ctx context . Context , uid int64 ) ( bool , error ) {
return db . GetEngine ( ctx ) . Where ( "uid=?" , uid ) . Exist ( & TwoFactor { } )
2021-11-09 01:47:19 +03:00
}
2017-01-16 05:14:29 +03:00
// DeleteTwoFactorByID deletes two-factor authentication token by given ID.
2023-09-15 09:13:19 +03:00
func DeleteTwoFactorByID ( ctx context . Context , id , userID int64 ) error {
cnt , err := db . GetEngine ( ctx ) . ID ( id ) . Delete ( & TwoFactor {
2017-01-16 05:14:29 +03:00
UID : userID ,
} )
if err != nil {
return err
} else if cnt != 1 {
return ErrTwoFactorNotEnrolled { userID }
}
return nil
}