From b1dfc68a0ffe46081709a691a1fb6d06e900703d Mon Sep 17 00:00:00 2001 From: Sean Du Date: Mon, 13 Jul 2026 07:55:47 +0800 Subject: [PATCH] Handle malformed encrypted requests without panics Signed-off-by: Sean Du --- internal/handler/ghome/basic/handshake.go | 5 +- internal/handler/login/authkey.go | 5 +- internal/handler/login/login.go | 14 ++- internal/session/session.go | 10 +- pkg/encrypt/aes.go | 34 ++++-- pkg/encrypt/aes_test.go | 36 +++++++ pkg/encrypt/rsa.go | 122 ++++++++-------------- 7 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 pkg/encrypt/aes_test.go diff --git a/internal/handler/ghome/basic/handshake.go b/internal/handler/ghome/basic/handshake.go index 46fea29..fcb550b 100644 --- a/internal/handler/ghome/basic/handshake.go +++ b/internal/handler/ghome/basic/handshake.go @@ -33,7 +33,10 @@ func handshake(ctx *gin.Context) { return } - decryptedData := encrypt.RSADecrypt(data) + decryptedData, err := encrypt.RSADecrypt(data) + if ss.CheckErr(err) { + return + } params, err := url.ParseQuery(string(decryptedData)) if ss.CheckErr(err) { diff --git a/internal/handler/login/authkey.go b/internal/handler/login/authkey.go index 66da8b4..062a151 100644 --- a/internal/handler/login/authkey.go +++ b/internal/handler/login/authkey.go @@ -25,7 +25,10 @@ func authKey(ctx *gin.Context) { if ss.CheckErr(err) { return } - dummyTokenDecrypted := encrypt.RSADecrypt(dummyToken) + dummyTokenDecrypted, err := encrypt.RSADecrypt(dummyToken) + if ss.CheckErr(err) { + return + } // aesKey := dummyTokenDecrypted[0:16] // authData, err := base64.StdEncoding.DecodeString(reqData.Get("auth_data").String()) diff --git a/internal/handler/login/login.go b/internal/handler/login/login.go index a5a5497..786165a 100644 --- a/internal/handler/login/login.go +++ b/internal/handler/login/login.go @@ -41,6 +41,10 @@ func login(ctx *gin.Context) { } xmcKey := utils.XOR(clientToken, serverToken) + if len(xmcKey) < 16 { + ss.Abort(errors.New("invalid login token data")) + return + } aesKey := xmcKey[0:16] reqData := gjson.Parse(ctx.MustGet("request_data").(string)) @@ -48,7 +52,15 @@ func login(ctx *gin.Context) { if ss.CheckErr(err) { return } - loginKey := encrypt.AESCBCDecrypt(key, aesKey)[16:] + decryptedLoginKey, err := encrypt.AESCBCDecrypt(key, aesKey) + if ss.CheckErr(err) { + return + } + if len(decryptedLoginKey) < 16 { + ss.Abort(errors.New("invalid login key")) + return + } + loginKey := decryptedLoginKey[16:] // fmt.Println("loginKey", string(loginKey)) // password, err := base64.StdEncoding.DecodeString(reqData.Get("login_passwd").String()) diff --git a/internal/session/session.go b/internal/session/session.go index b6ed121..cd79b5a 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -186,13 +186,21 @@ func (ss *Session) CheckErr(err error) bool { } func (ss *Session) Respond(resp any) { + if ss.done { + return + } + data, err := json.Marshal(resp) if ss.CheckErr(err) { return } ss.resp.setHeader("Content-Type", "application/json") - ss.resp.setHeader("X-Message-Sign", base64.StdEncoding.EncodeToString(encrypt.RSASignSHA1(data))) + signature, err := encrypt.RSASignSHA1(data) + if ss.CheckErr(err) { + return + } + ss.resp.setHeader("X-Message-Sign", base64.StdEncoding.EncodeToString(signature)) ss.resp.writeBody(http.StatusOK, string(data)) } diff --git a/pkg/encrypt/aes.go b/pkg/encrypt/aes.go index 87bc996..59909e0 100644 --- a/pkg/encrypt/aes.go +++ b/pkg/encrypt/aes.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/aes" "crypto/cipher" + "fmt" ) var ( @@ -17,32 +18,45 @@ func padding(plainText []byte, blockSize int) []byte { return plainText } -func unPadding(cipherText []byte) []byte { +func unPadding(cipherText []byte) ([]byte, error) { + if len(cipherText) == 0 { + return nil, fmt.Errorf("AES plaintext is empty") + } + end := cipherText[len(cipherText)-1] - cipherText = cipherText[:len(cipherText)-int(end)] - return cipherText + if end == 0 || int(end) > len(cipherText) { + return nil, fmt.Errorf("invalid AES padding") + } + for _, value := range cipherText[len(cipherText)-int(end):] { + if value != end { + return nil, fmt.Errorf("invalid AES padding") + } + } + return cipherText[:len(cipherText)-int(end)], nil } -func AESCBCEncrypt(plainText []byte, key []byte) []byte { +func AESCBCEncrypt(plainText []byte, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - panic(err) + return nil, err } plainText = padding(plainText, block.BlockSize()) blockMode := cipher.NewCBCEncrypter(block, iv) cipherText := make([]byte, len(plainText)) blockMode.CryptBlocks(cipherText, plainText) - return cipherText + return cipherText, nil } -func AESCBCDecrypt(cipherText []byte, key []byte) []byte { +func AESCBCDecrypt(cipherText []byte, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - panic(err) + return nil, err + } + if len(cipherText) == 0 || len(cipherText)%block.BlockSize() != 0 { + return nil, fmt.Errorf("AES ciphertext must be a non-empty multiple of the block size") } plainText := make([]byte, len(cipherText)) blockMode := cipher.NewCBCDecrypter(block, iv) blockMode.CryptBlocks(plainText, cipherText) - plainText = unPadding(plainText) - return plainText + return unPadding(plainText) } diff --git a/pkg/encrypt/aes_test.go b/pkg/encrypt/aes_test.go new file mode 100644 index 0000000..d24c85d --- /dev/null +++ b/pkg/encrypt/aes_test.go @@ -0,0 +1,36 @@ +package encrypt + +import ( + "bytes" + "testing" +) + +func TestAESCBCDecryptRoundTrip(t *testing.T) { + key := []byte("1234567890abcdef") + plainText := []byte("login payload") + + cipherText, err := AESCBCEncrypt(plainText, key) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + decrypted, err := AESCBCDecrypt(cipherText, key) + if err != nil { + t.Fatalf("decrypt: %v", err) + } + if !bytes.Equal(decrypted, plainText) { + t.Fatalf("decrypted plaintext = %q, want %q", decrypted, plainText) + } +} + +func TestAESCBCDecryptRejectsMalformedCiphertext(t *testing.T) { + key := []byte("1234567890abcdef") + for _, cipherText := range [][]byte{nil, make([]byte, 15)} { + if _, err := AESCBCDecrypt(cipherText, key); err == nil { + t.Fatalf("AESCBCDecrypt(%d bytes) succeeded", len(cipherText)) + } + } + + if _, err := unPadding([]byte{1, 2, 3, 2}); err == nil { + t.Fatal("unPadding accepted invalid padding") + } +} diff --git a/pkg/encrypt/rsa.go b/pkg/encrypt/rsa.go index 18fdde0..7c20945 100644 --- a/pkg/encrypt/rsa.go +++ b/pkg/encrypt/rsa.go @@ -7,6 +7,7 @@ import ( "crypto/sha1" "crypto/x509" "encoding/pem" + "fmt" "honoka-chan/config" "os" "path/filepath" @@ -75,100 +76,67 @@ func RSAGen(bits int) { pem.Encode(publickeyfile, &publickeyblock) } -func RSAEncrypt(plainText []byte, publickeypath string) []byte { - //open publickeyfile - publickeyfile, err := os.Open(publickeypath) +func RSAEncrypt(plainText []byte, publickeypath string) ([]byte, error) { + data, err := os.ReadFile(publickeypath) if err != nil { - panic(err) - } - defer publickeyfile.Close() - - //get publickeyfile info - publickeyfileInfo, _ := publickeyfile.Stat() - - //read publickeyfile content - //1. make size - buf := make([]byte, publickeyfileInfo.Size()) - //2. read file to buf - publickeyfile.Read(buf) - //3. decode pem - publickeyDecodeBlock, _ := pem.Decode(buf) - //4. x509 decode - publicKeyInterface, err := x509.ParsePKIXPublicKey(publickeyDecodeBlock.Bytes) - if err != nil { - panic(err) - } - //assert - publicKey := publicKeyInterface.(*rsa.PublicKey) - - //encrypt plainText - cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) - if err != nil { - panic(err) + return nil, err } - return cipherText + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("invalid RSA public key PEM") + } + parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + publicKey, ok := parsedKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("PEM does not contain an RSA public key") + } + + return rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) } -func RSADecrypt(cipherText []byte) []byte { - //open privatekeyfile - privatekeyfile, err := os.Open(config.PrivateKeyPath) +func RSADecrypt(cipherText []byte) ([]byte, error) { + privateKey, err := loadPrivateKey() if err != nil { - panic(err) - } - defer privatekeyfile.Close() - //get privatekeyfile content - privatekeyinfo, _ := privatekeyfile.Stat() - buf := make([]byte, privatekeyinfo.Size()) - privatekeyfile.Read(buf) - //pem decode - privatekeyblock, _ := pem.Decode(buf) - //X509 decode - parseKey, err := x509.ParsePKCS8PrivateKey(privatekeyblock.Bytes) - if err != nil { - panic(err) - } - privateKey := parseKey.(*rsa.PrivateKey) - //decrypt the cipher - plainText, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText) - if err != nil { - panic(err) + return nil, err } - return plainText + return rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText) } -func RSASignSHA1(cipherText []byte) []byte { - //open privatekeyfile - privatekeyfile, err := os.Open(config.PrivateKeyPath) +func RSASignSHA1(cipherText []byte) ([]byte, error) { + privateKey, err := loadPrivateKey() if err != nil { - panic(err) + return nil, err } - defer privatekeyfile.Close() - //get privatekeyfile content - privatekeyinfo, _ := privatekeyfile.Stat() - buf := make([]byte, privatekeyinfo.Size()) - privatekeyfile.Read(buf) - //pem decode - privatekeyblock, _ := pem.Decode(buf) - //X509 decode - parseKey, err := x509.ParsePKCS8PrivateKey(privatekeyblock.Bytes) - if err != nil { - panic(err) - } - privateKey := parseKey.(*rsa.PrivateKey) msgHash := sha1.New() - _, err = msgHash.Write(cipherText) - if err != nil { - panic(err) - } + _, _ = msgHash.Write(cipherText) msgHashSum := msgHash.Sum(nil) - signature, err := rsa.SignPSS(rand.Reader, privateKey, crypto.SHA1, msgHashSum, nil) + return rsa.SignPSS(rand.Reader, privateKey, crypto.SHA1, msgHashSum, nil) +} + +func loadPrivateKey() (*rsa.PrivateKey, error) { + data, err := os.ReadFile(config.PrivateKeyPath) if err != nil { - panic(err) + return nil, err } - return signature + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("invalid RSA private key PEM") + } + parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + privateKey, ok := parsedKey.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("PEM does not contain an RSA private key") + } + return privateKey, nil }