| @@ -5,11 +5,15 @@ | |||||
| package models | package models | ||||
| import ( | import ( | ||||
| "crypto/aes" | |||||
| "crypto/cipher" | |||||
| "crypto/md5" | "crypto/md5" | ||||
| "crypto/rand" | |||||
| "crypto/subtle" | "crypto/subtle" | ||||
| "encoding/base64" | "encoding/base64" | ||||
| "errors" | |||||
| "io" | |||||
| "github.com/Unknwon/com" | |||||
| "github.com/pquerna/otp/totp" | "github.com/pquerna/otp/totp" | ||||
| "code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
| @@ -52,7 +56,7 @@ func (t *TwoFactor) getEncryptionKey() []byte { | |||||
| // SetSecret sets the 2FA secret. | // SetSecret sets the 2FA secret. | ||||
| func (t *TwoFactor) SetSecret(secret string) error { | func (t *TwoFactor) SetSecret(secret string) error { | ||||
| secretBytes, err := com.AESGCMEncrypt(t.getEncryptionKey(), []byte(secret)) | |||||
| secretBytes, err := aesEncrypt(t.getEncryptionKey(), []byte(secret)) | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -66,7 +70,7 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { | |||||
| if err != nil { | if err != nil { | ||||
| return false, err | return false, err | ||||
| } | } | ||||
| secret, err := com.AESGCMDecrypt(t.getEncryptionKey(), decodedStoredSecret) | |||||
| secret, err := aesDecrypt(t.getEncryptionKey(), decodedStoredSecret) | |||||
| if err != nil { | if err != nil { | ||||
| return false, err | return false, err | ||||
| } | } | ||||
| @@ -74,6 +78,43 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { | |||||
| return totp.Validate(passcode, secretStr), nil | return totp.Validate(passcode, secretStr), nil | ||||
| } | } | ||||
| // 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 | |||||
| } | |||||
| // NewTwoFactor creates a new two-factor authentication token. | // NewTwoFactor creates a new two-factor authentication token. | ||||
| func NewTwoFactor(t *TwoFactor) error { | func NewTwoFactor(t *TwoFactor) error { | ||||
| err := t.GenerateScratchToken() | err := t.GenerateScratchToken() | ||||