- Introduced Weixin integration support with new configuration options for WeChat iLink Bot. - Updated existing adapters (DingTalk, Discord, Feishu, Telegram) to include sender_id and app_id in message metadata for improved context handling. - Enhanced dispatcher logic to accommodate the new Weixin adapter and ensure proper initialization and shutdown processes. - Improved message handling across integrations to support typing indicators, providing a more interactive user experience.
126 lines
3.4 KiB
Go
126 lines
3.4 KiB
Go
package weixin
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
func buildCDNDownloadURL(cdnBaseURL, encryptedQueryParam string) string {
|
|
return cdnBaseURL + "/download?encrypted_query_param=" + url.QueryEscape(encryptedQueryParam)
|
|
}
|
|
|
|
func parseAesKey(aesKeyBase64 string) ([]byte, error) {
|
|
decoded, err := base64.StdEncoding.DecodeString(aesKeyBase64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parseAesKey: base64 decode: %w", err)
|
|
}
|
|
if len(decoded) == 16 {
|
|
return decoded, nil
|
|
}
|
|
if len(decoded) == 32 {
|
|
hexStr := string(decoded)
|
|
raw := make([]byte, 16)
|
|
for i := 0; i < 16; i++ {
|
|
var b byte
|
|
_, err := fmt.Sscanf(hexStr[i*2:i*2+2], "%02x", &b)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parseAesKey: hex parse: %w", err)
|
|
}
|
|
raw[i] = b
|
|
}
|
|
return raw, nil
|
|
}
|
|
return nil, fmt.Errorf("parseAesKey: unexpected decoded len=%d", len(decoded))
|
|
}
|
|
|
|
func decryptAES128ECB(ciphertext, key []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bs := block.BlockSize()
|
|
if len(ciphertext)%bs != 0 {
|
|
return nil, fmt.Errorf("decryptAES128ECB: ciphertext len %d not multiple of block size", len(ciphertext))
|
|
}
|
|
dst := make([]byte, len(ciphertext))
|
|
for i := 0; i < len(ciphertext); i += bs {
|
|
block.Decrypt(dst[i:i+bs], ciphertext[i:i+bs])
|
|
}
|
|
if len(dst) == 0 {
|
|
return dst, nil
|
|
}
|
|
padLen := int(dst[len(dst)-1])
|
|
if padLen == 0 || padLen > bs {
|
|
return nil, fmt.Errorf("decryptAES128ECB: invalid PKCS7 padding %d", padLen)
|
|
}
|
|
return dst[:len(dst)-padLen], nil
|
|
}
|
|
|
|
func downloadCDNBytes(cdnBaseURL, encryptedQueryParam string) ([]byte, error) {
|
|
u := buildCDNDownloadURL(cdnBaseURL, encryptedQueryParam)
|
|
resp, err := http.Get(u) //nolint:gosec
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CDN download: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("CDN download HTTP %d", resp.StatusCode)
|
|
}
|
|
return io.ReadAll(resp.Body)
|
|
}
|
|
|
|
func DownloadAndDecrypt(cdnBaseURL, encryptedQueryParam, aesKeyBase64 string) ([]byte, error) {
|
|
key, err := parseAesKey(aesKeyBase64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data, err := downloadCDNBytes(cdnBaseURL, encryptedQueryParam)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decryptAES128ECB(data, key)
|
|
}
|
|
|
|
func DecryptFromRaw(cdnBaseURL, encryptedQueryParam string, rawKey []byte) ([]byte, error) {
|
|
data, err := downloadCDNBytes(cdnBaseURL, encryptedQueryParam)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decryptAES128ECB(data, rawKey)
|
|
}
|
|
|
|
func DownloadPlain(cdnBaseURL, encryptedQueryParam string) ([]byte, error) {
|
|
return downloadCDNBytes(cdnBaseURL, encryptedQueryParam)
|
|
}
|
|
|
|
func encryptAES128ECB(plaintext, key []byte) []byte {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
bs := block.BlockSize()
|
|
padLen := bs - (len(plaintext) % bs)
|
|
padded := make([]byte, len(plaintext)+padLen)
|
|
copy(padded, plaintext)
|
|
for i := len(plaintext); i < len(padded); i++ {
|
|
padded[i] = byte(padLen)
|
|
}
|
|
dst := make([]byte, len(padded))
|
|
for i := 0; i < len(padded); i += bs {
|
|
block.Encrypt(dst[i:i+bs], padded[i:i+bs])
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func aesEcbPaddedSize(plaintextSize int) int {
|
|
return ((plaintextSize + 1 + 15) / 16) * 16
|
|
}
|
|
|
|
func buildCDNUploadURL(cdnBaseURL, uploadParam, filekey string) string {
|
|
return cdnBaseURL + "/upload?encrypted_query_param=" +
|
|
url.QueryEscape(uploadParam) + "&filekey=" + url.QueryEscape(filekey)
|
|
}
|