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 (
2017-12-31 05:19:42 +03:00
"crypto/aes"
"crypto/cipher"
2017-01-16 05:14:29 +03:00
"crypto/md5"
2017-12-31 05:19:42 +03:00
"crypto/rand"
2018-07-27 15:54:50 +03:00
"crypto/sha256"
2017-01-16 05:14:29 +03:00
"crypto/subtle"
"encoding/base64"
2017-12-31 05:19:42 +03:00
"errors"
2018-07-27 15:54:50 +03:00
"fmt"
2017-12-31 05:19:42 +03:00
"io"
2017-01-16 05:14:29 +03:00
2018-02-18 21:14:37 +03:00
"code.gitea.io/gitea/modules/generate"
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.
func ( t * TwoFactor ) SetSecret ( secret string ) error {
2017-12-31 05:19:42 +03:00
secretBytes , err := aesEncrypt ( t . getEncryptionKey ( ) , [ ] byte ( secret ) )
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
}
2017-12-31 05:19:42 +03:00
secret , err := aesDecrypt ( t . getEncryptionKey ( ) , decodedStoredSecret )
2017-01-16 05:14:29 +03:00
if err != nil {
return false , err
}
secretStr := string ( secret )
return totp . Validate ( passcode , secretStr ) , nil
}
2017-12-31 05:19:42 +03:00
// aesEncrypt encrypts text and given key with AES.
func aesEncrypt ( key , text [ ] byte ) ( [ ] byte , error ) {
block , err := aes . NewCipher ( key )
if err != nil {
return nil , err
}
b := base64 . StdEncoding . EncodeToString ( text )
ciphertext := make ( [ ] byte , aes . BlockSize + len ( b ) )
iv := ciphertext [ : aes . BlockSize ]
if _ , err := io . ReadFull ( rand . Reader , iv ) ; err != nil {
return nil , err
}
cfb := cipher . NewCFBEncrypter ( block , iv )
cfb . XORKeyStream ( ciphertext [ aes . BlockSize : ] , [ ] byte ( b ) )
return ciphertext , nil
}
// aesDecrypt decrypts text and given key with AES.
func aesDecrypt ( key , text [ ] byte ) ( [ ] byte , error ) {
block , err := aes . NewCipher ( key )
if err != nil {
return nil , err
}
if len ( text ) < aes . BlockSize {
return nil , errors . New ( "ciphertext too short" )
}
iv := text [ : aes . BlockSize ]
text = text [ aes . BlockSize : ]
cfb := cipher . NewCFBDecrypter ( block , iv )
cfb . XORKeyStream ( text , text )
data , err := base64 . StdEncoding . DecodeString ( string ( text ) )
if err != nil {
return nil , err
}
return data , nil
}
2017-01-16 05:14:29 +03:00
// 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 ) {
twofa := & TwoFactor { UID : uid }
has , err := x . Get ( twofa )
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
}