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"
2017-01-16 05:14:29 +03:00
"crypto/subtle"
"encoding/base64"
2017-12-31 05:19:42 +03:00
"errors"
"io"
2017-01-16 05:14:29 +03:00
"github.com/pquerna/otp/totp"
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"
2017-12-11 07:37:04 +03:00
"code.gitea.io/gitea/modules/util"
2017-01-16 05:14:29 +03:00
)
// TwoFactor represents a two-factor authentication token.
type TwoFactor struct {
ID int64 ` xorm:"pk autoincr" `
UID int64 ` xorm:"UNIQUE" `
Secret string
ScratchToken string
2017-12-11 07:37:04 +03:00
CreatedUnix util . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix util . TimeStamp ` xorm:"INDEX updated" `
2017-01-16 05:14:29 +03:00
}
// GenerateScratchToken recreates the scratch token the user is using.
func ( t * TwoFactor ) GenerateScratchToken ( ) 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 {
return err
}
t . ScratchToken = token
return nil
}
// VerifyScratchToken verifies if the specified scratch token is valid.
func ( t * TwoFactor ) VerifyScratchToken ( token string ) bool {
if len ( token ) == 0 {
return false
}
return subtle . ConstantTimeCompare ( [ ] byte ( token ) , [ ] byte ( t . ScratchToken ) ) == 1
}
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 {
err := t . GenerateScratchToken ( )
if err != nil {
return err
}
_ , err = x . Insert ( t )
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
}