七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
85
internal/infrastructure/wecom/callback_crypto.go
Normal file
85
internal/infrastructure/wecom/callback_crypto.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package wecom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// CallbackCrypto 实现企微回调要求的 SHA-1 签名校验和 AES-256-CBC 解密。
|
||||
type CallbackCrypto struct {
|
||||
token string
|
||||
aesKey []byte
|
||||
receiveID string
|
||||
}
|
||||
|
||||
// NewCallbackCrypto 创建企业微信回调加密器。
|
||||
func NewCallbackCrypto(token, encodingAESKey, receiveID string) (*CallbackCrypto, error) {
|
||||
key, err := base64.StdEncoding.DecodeString(strings.TrimSpace(encodingAESKey) + "=")
|
||||
if err != nil || len(key) != 32 || strings.TrimSpace(token) == "" || strings.TrimSpace(receiveID) == "" {
|
||||
return nil, errors.New(errors.CodeWeComCredentialInvalid, "企业微信回调 Token、EncodingAESKey 或 receiveid 无效")
|
||||
}
|
||||
return &CallbackCrypto{token: token, aesKey: key, receiveID: receiveID}, nil
|
||||
}
|
||||
|
||||
// VerifyAndDecrypt 校验签名并解密企业微信回调密文。
|
||||
func (c *CallbackCrypto) VerifyAndDecrypt(signature, timestamp, nonce, encrypted string) ([]byte, error) {
|
||||
if c == nil || !c.validSignature(signature, timestamp, nonce, encrypted) {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "企业微信回调签名无效")
|
||||
}
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
|
||||
if err != nil || len(ciphertext) == 0 || len(ciphertext)%aes.BlockSize != 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "企业微信回调密文无效")
|
||||
}
|
||||
block, err := aes.NewCipher(c.aesKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "初始化企业微信回调解密器失败")
|
||||
}
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
cipher.NewCBCDecrypter(block, c.aesKey[:aes.BlockSize]).CryptBlocks(plaintext, ciphertext)
|
||||
plaintext, err = removePKCS7Padding(plaintext)
|
||||
if err != nil || len(plaintext) < 20 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "企业微信回调填充无效")
|
||||
}
|
||||
messageLength := int(binary.BigEndian.Uint32(plaintext[16:20]))
|
||||
if messageLength < 0 || 20+messageLength > len(plaintext) {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "企业微信回调消息长度无效")
|
||||
}
|
||||
message := plaintext[20 : 20+messageLength]
|
||||
receiveID := plaintext[20+messageLength:]
|
||||
if subtle.ConstantTimeCompare(receiveID, []byte(c.receiveID)) != 1 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "企业微信回调 receiveid 不匹配")
|
||||
}
|
||||
return append([]byte(nil), message...), nil
|
||||
}
|
||||
|
||||
func (c *CallbackCrypto) validSignature(signature, timestamp, nonce, encrypted string) bool {
|
||||
parts := []string{c.token, timestamp, nonce, encrypted}
|
||||
sort.Strings(parts)
|
||||
hash := sha1.Sum([]byte(strings.Join(parts, "")))
|
||||
expected := hex.EncodeToString(hash[:])
|
||||
return subtle.ConstantTimeCompare([]byte(strings.ToLower(signature)), []byte(expected)) == 1
|
||||
}
|
||||
|
||||
func removePKCS7Padding(value []byte) ([]byte, error) {
|
||||
if len(value) == 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
padding := int(value[len(value)-1])
|
||||
if padding <= 0 || padding > 32 || padding > len(value) {
|
||||
return nil, errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
if !bytes.Equal(value[len(value)-padding:], bytes.Repeat([]byte{byte(padding)}, padding)) {
|
||||
return nil, errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
return value[:len(value)-padding], nil
|
||||
}
|
||||
Reference in New Issue
Block a user