Skip to content

Commit

Permalink
feat: Added password encryption for login functionality (#7764)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengkunwang223 authored Jan 23, 2025
1 parent 4f57dfc commit aaaa598
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 34 deletions.
7 changes: 7 additions & 0 deletions backend/app/repo/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type DBOption func(*gorm.DB) *gorm.DB
type ICommonRepo interface {
WithByID(id uint) DBOption
WithByName(name string) DBOption
WithByLowerName(name string) DBOption
WithByType(tp string) DBOption
WithOrderBy(orderStr string) DBOption
WithOrderRuleBy(orderBy, order string) DBOption
Expand Down Expand Up @@ -45,6 +46,12 @@ func (c *CommonRepo) WithByName(name string) DBOption {
}
}

func (c *CommonRepo) WithByLowerName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("LOWER(name) = LOWER(?)", name)
}
}

func (c *CommonRepo) WithByDate(startTime, endTime time.Time) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("start_time > ? AND start_time < ?", startTime, endTime)
Expand Down
5 changes: 5 additions & 0 deletions backend/app/repo/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type ISettingRepo interface {
Create(key, value string) error
Update(key, value string) error
WithByKey(key string) DBOption
UpdateOrCreate(key, value string) error

CreateMonitorBase(model model.MonitorBase) error
BatchCreateMonitorIO(ioList []model.MonitorIO) error
Expand Down Expand Up @@ -85,3 +86,7 @@ func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
}

func (u *SettingRepo) UpdateOrCreate(key, value string) error {
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Assign(model.Setting{Key: key, Value: value}).FirstOrCreate(&model.Setting{}).Error
}
3 changes: 2 additions & 1 deletion backend/app/service/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
return
}
if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 {

if list, _ := appInstallRepo.ListBy(commonRepo.WithByLowerName(req.Name)); len(list) > 0 {
err = buserr.New(constant.ErrAppNameExist)
return
}
Expand Down
50 changes: 32 additions & 18 deletions backend/app/service/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package service
import (
"crypto/hmac"
"encoding/base64"
"strconv"

"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
Expand All @@ -15,6 +13,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pkg/errors"
"strconv"
)

type AuthService struct{}
Expand All @@ -38,16 +37,11 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
if nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
if err = checkPassword(info.Password); err != nil {
return nil, err
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
Expand Down Expand Up @@ -83,17 +77,12 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
if nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
if err = checkPassword(info.Password); err != nil {
return nil, err
}
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
return nil, err
Expand Down Expand Up @@ -219,3 +208,28 @@ func (u *AuthService) IsLogin(c *gin.Context) bool {
}
return true
}

func checkPassword(password string) error {
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))

privateKey, err := encrypt.ParseRSAPrivateKey(priKey.Value)
if err != nil {
return err
}
loginPassword, err := encrypt.DecryptPassword(password, privateKey)
if err != nil {
return err
}
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return errors.WithMessage(constant.ErrRecordNotFound, err.Error())
}
existPassword, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
return err
}
if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) {
return constant.ErrAuth
}
return nil
}
47 changes: 47 additions & 0 deletions backend/app/service/setting.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package service

import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
Expand Down Expand Up @@ -42,6 +44,7 @@ type ISettingService interface {
HandlePasswordExpired(c *gin.Context, old, new string) error
GenerateApiKey() (string, error)
UpdateApiConfig(req dto.ApiInterfaceConfig) error
GenerateRSAKey() error
}

func NewISettingService() ISettingService {
Expand Down Expand Up @@ -516,3 +519,47 @@ func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error {
global.CONF.System.ApiKeyValidityTime = req.ApiKeyValidityTime
return nil
}

func exportPrivateKeyToPEM(privateKey *rsa.PrivateKey) string {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
})
return string(privateKeyPEM)
}

func exportPublicKeyToPEM(publicKey *rsa.PublicKey) (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", err
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})
return string(publicKeyPEM), nil
}

func (u *SettingService) GenerateRSAKey() error {
priKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PRIVATE_KEY"))
pubKey, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
if priKey.Value != "" && pubKey.Value != "" {
return nil
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
if err != nil {
return err
}
err = settingRepo.UpdateOrCreate("PASSWORD_PUBLIC_KEY", publicKeyPEM)
if err != nil {
return err
}
return nil
}
7 changes: 7 additions & 0 deletions backend/init/business/business.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func Init() {
go syncInstalledApp()
go syncRuntime()
go syncSSL()
generateKey()
}

func syncApp() {
Expand Down Expand Up @@ -38,3 +39,9 @@ func syncSSL() {
global.LOG.Errorf("sync ssl status error : %s", err.Error())
}
}

func generateKey() {
if err := service.NewISettingService().GenerateRSAKey(); err != nil {
global.LOG.Errorf("generate rsa key error : %s", err.Error())
}
}
5 changes: 2 additions & 3 deletions backend/init/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,7 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
rootRouter.StaticFS("/public", http.FS(web.Favicon))
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
rootRouter.Static("/api/v1/images", "./uploads")
rootRouter.Use(func(c *gin.Context) {
c.Next()
})

rootRouter.GET("/assets/*filepath", func(c *gin.Context) {
c.Writer.Header().Set("Cache-Control", fmt.Sprintf("private, max-age=%d", 3600))
staticServer := http.FileServer(http.FS(web.Assets))
Expand Down Expand Up @@ -158,6 +156,7 @@ func Routers() *gin.Engine {

Router.Use(middleware.WhiteAllow())
Router.Use(middleware.BindDomain())
Router.Use(middleware.SetPasswordPublicKey())

Router.NoRoute(func(c *gin.Context) {
if checkFrontendPath(c) {
Expand Down
22 changes: 22 additions & 0 deletions backend/middleware/password_rsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package middleware

import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/gin-gonic/gin"
)

func SetPasswordPublicKey() gin.HandlerFunc {
return func(c *gin.Context) {
cookieKey, _ := c.Cookie("panel_public_key")
settingRepo := repo.NewISettingRepo()
key, _ := settingRepo.Get(settingRepo.WithByKey("PASSWORD_PUBLIC_KEY"))
base64Key := base64.StdEncoding.EncodeToString([]byte(key.Value))
if base64Key == cookieKey {
c.Next()
return
}
c.SetCookie("panel_public_key", base64Key, 7*24*60*60, "/", "", false, false)
c.Next()
}
}
77 changes: 77 additions & 0 deletions backend/utils/encrypt/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"io"
"strings"

"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
Expand Down Expand Up @@ -102,3 +107,75 @@ func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) {
ciphertext = unPadding(ciphertext)
return ciphertext, nil
}

func ParseRSAPrivateKey(privateKeyPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil || block.Type != "RSA PRIVATE KEY" {
return nil, errors.New("failed to decode PEM block containing the private key")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return privateKey, nil
}

func aesDecrypt(ciphertext, key, iv []byte) ([]byte, error) {
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
return nil, errors.New("invalid AES key length: must be 16, 24, or 32 bytes")
}
if len(iv) != aes.BlockSize {
return nil, errors.New("invalid IV length: must be 16 bytes")
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(ciphertext, ciphertext)
ciphertext = pkcs7Unpad(ciphertext)
return ciphertext, nil
}

func pkcs7Unpad(data []byte) []byte {
length := len(data)
padLength := int(data[length-1])
return data[:length-padLength]
}

func DecryptPassword(encryptedData string, privateKey *rsa.PrivateKey) (string, error) {
parts := strings.Split(encryptedData, ":")
if len(parts) != 3 {
return "", errors.New("encrypted data format error")
}
keyCipher := parts[0]
ivBase64 := parts[1]
ciphertextBase64 := parts[2]

encryptedAESKey, err := base64.StdEncoding.DecodeString(keyCipher)
if err != nil {
return "", errors.New("failed to decode keyCipher")
}

aesKey, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedAESKey)
if err != nil {
return "", errors.New("failed to decode AES Key")
}

ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
if err != nil {
return "", errors.New("failed to decrypt the encrypted data")
}
iv, err := base64.StdEncoding.DecodeString(ivBase64)
if err != nil {
return "", errors.New("failed to decode the IV")
}

password, err := aesDecrypt(ciphertext, aesKey, iv)
if err != nil {
return "", err
}

return string(password), nil
}
Loading

0 comments on commit aaaa598

Please sign in to comment.