2017-01-16 05:14:29 +03:00
// Copyright 2017 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 models
import (
"crypto/md5"
2018-07-27 15:54:50 +03:00
"crypto/sha256"
2017-01-16 05:14:29 +03:00
"crypto/subtle"
"encoding/base64"
2018-07-27 15:54:50 +03:00
"fmt"
2017-01-16 05:14:29 +03:00
2018-02-18 21:14:37 +03:00
"code.gitea.io/gitea/modules/generate"
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"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
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
}
// GenerateScratchToken recreates the scratch token the user is using.
2018-07-27 15:54:50 +03:00
func ( t * TwoFactor ) GenerateScratchToken ( ) ( string , error ) {
2018-02-18 21:14:37 +03:00
token , err := generate . GetRandomString ( 8 )
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
}
2018-07-27 15:54:50 +03:00
t . ScratchSalt , _ = generate . GetRandomString ( 10 )
t . ScratchHash = hashToken ( token , t . ScratchSalt )
return token , nil
}
func hashToken ( token , salt string ) string {
tempHash := pbkdf2 . Key ( [ ] byte ( token ) , [ ] byte ( salt ) , 10000 , 50 , sha256 . New )
return fmt . Sprintf ( "%x" , 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
}
2018-07-27 15:54:50 +03:00
tempHash := hashToken ( token , t . ScratchSalt )
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.
func NewTwoFactor ( t * TwoFactor ) error {
2019-05-02 23:06:01 +03:00
_ , err := x . Insert ( t )
2017-01-16 05:14:29 +03:00
return err
}
// UpdateTwoFactor updates a two-factor authentication token.
func UpdateTwoFactor ( t * TwoFactor ) error {
2017-10-05 07:43:04 +03:00
_ , err := x . 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.
func GetTwoFactorByUID ( uid int64 ) ( * TwoFactor , error ) {
2020-06-17 20:50:11 +03:00
twofa := & TwoFactor { }
has , err := x . 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
}
// DeleteTwoFactorByID deletes two-factor authentication token by given ID.
func DeleteTwoFactorByID ( id , userID int64 ) error {
2017-10-05 07:43:04 +03:00
cnt , err := x . 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
}