diff --git a/apirouter/entry.go b/apirouter/entry.go index 7a4a6d0..c1cd388 100644 --- a/apirouter/entry.go +++ b/apirouter/entry.go @@ -10,6 +10,7 @@ import ( "key_performance_indicators/apirouter/v1/postseting" "key_performance_indicators/apirouter/v1/systempower" "key_performance_indicators/apirouter/verifyLogin" + "key_performance_indicators/apirouter/wechaturl" // "key_performance_indicators/v1" ) @@ -24,6 +25,7 @@ type RouterGroup struct { BookImg bookimg.ApiRouter SystemPowerRouter systempower.ApiRouter Empowerouter empowerrouter.ApiRouter + WechatRouter wechaturl.ApiRouter } var RouterGroupEntry = new(RouterGroup) diff --git a/apirouter/wechaturl/entry.go b/apirouter/wechaturl/entry.go new file mode 100644 index 0000000..010652b --- /dev/null +++ b/apirouter/wechaturl/entry.go @@ -0,0 +1,5 @@ +package wechaturl + +//企业微信回调 + +type ApiRouter struct{} diff --git a/apirouter/wechaturl/wechatrouter.go b/apirouter/wechaturl/wechatrouter.go new file mode 100644 index 0000000..a16fe76 --- /dev/null +++ b/apirouter/wechaturl/wechatrouter.go @@ -0,0 +1,19 @@ +package wechaturl + +import ( + "key_performance_indicators/middleware/wechatapp" + + "github.com/gin-gonic/gin" +) + +// 企业微信回调路由 +func (a *ApiRouter) RouterGroup(router *gin.RouterGroup) { + apiRouter := router.Group("wechatcallback") + + var methodBinding = wechatapp.WechatAppApi.WechatCallBackApi + { + apiRouter.GET("", methodBinding.Index) //入口 + apiRouter.POST("", methodBinding.Index) //入口 + apiRouter.GET("callback_message_api", methodBinding.CallbackMessageApi) //回调入口 + } +} diff --git a/config/configApp/server.go b/config/configApp/server.go index d5513f0..eebafa1 100644 --- a/config/configApp/server.go +++ b/config/configApp/server.go @@ -6,6 +6,9 @@ type Server struct { Logsetup logsetup `mapstructure:"logconfig" json:"logconfig" yaml:"logconfig"` Captcha captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"` RedisPrefixStr redisPrefixStr `mapstructure:"redisprefix" json:"redisprefix" yaml:"redisprefix"` + WechatCompany wechatCompany `mapstructure:"wechatcompany" json:"wechatcompany" yaml:"wechatcompany"` //企业ID + WechatSchool wechatConfig `mapstructure:"wechatschool" json:"wechatschool" yaml:"wechatschool"` //知行学院 + WechatKpi wechatConfig `mapstructure:"wechatkpi" json:"wechatkpi" yaml:"wechatkpi"` //绩效考核 } //服务配置详情 @@ -34,3 +37,15 @@ type redisPrefixStr struct { PreFix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // redis键前缀 Alias string `mapstructure:"alias" json:"alias" yaml:"alias"` // redis键前缀 } + +//企业微信基础配置 +type wechatCompany struct { + CompanyId string `mapstructure:"companyid" json:"companyid" yaml:"companyid"` // 企业ID +} + +type wechatConfig struct { + Agentid string `mapstructure:"agentid" json:"agentid" yaml:"agentid"` // Agentid + Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // Secret + Token string `mapstructure:"token" json:"token" yaml:"token"` // Token + Encodingaeskey string `mapstructure:"encodingaeskey" json:"encodingaeskey" yaml:"encodingaeskey"` // EncodingAESKey +} diff --git a/initialization/app/run.go b/initialization/app/run.go index e7dd1e3..f63b14a 100644 --- a/initialization/app/run.go +++ b/initialization/app/run.go @@ -16,7 +16,7 @@ func RunItem() { //加载基础配置 // var appConfig configApp.Server initviper.RunViper(&overall.CONSTANT_CONFIG) - // fmt.Printf("----------->%v", overall.CONSTANT_CONFIG) + // fmt.Printf("CONSTANT_CONFIG----------->%v", overall.CONSTANT_CONFIG) routers := route.InitialRouter() portStr := fmt.Sprintf(":%d", overall.CONSTANT_CONFIG.Appsetup.Port) startUp := InitServer(portStr, routers) diff --git a/initialization/route/initRoute.go b/initialization/route/initRoute.go index 82ec503..96837f6 100644 --- a/initialization/route/initRoute.go +++ b/initialization/route/initRoute.go @@ -46,6 +46,11 @@ func InitialRouter() *gin.Engine { // { // mytestapi.RouterGroup(appLoadRouterGroup) // } + //企业微信回调 + wechaturlApi := apirouter.RouterGroupEntry.WechatRouter + { + wechaturlApi.RouterGroup(appLoadRouterGroup) + } } //验证身份接口 鉴权Url(主要应用端使用) diff --git a/middleware/wechatapp/entry.go b/middleware/wechatapp/entry.go new file mode 100644 index 0000000..f1944bd --- /dev/null +++ b/middleware/wechatapp/entry.go @@ -0,0 +1,13 @@ +package wechatapp + +import ( + "key_performance_indicators/middleware/wechatapp/wechatcallback" + "key_performance_indicators/middleware/wechatapp/wechatsendmsg" +) + +type WechatApp struct { + WechatCallBackApi wechatcallback.ApiRouter + WechatSendMsgApi wechatsendmsg.ApiRouter +} + +var WechatAppApi = new(WechatApp) diff --git a/middleware/wechatapp/wechatcallback/response.go b/middleware/wechatapp/wechatcallback/response.go new file mode 100644 index 0000000..a160bff --- /dev/null +++ b/middleware/wechatapp/wechatcallback/response.go @@ -0,0 +1,125 @@ +package wechatcallback + +import ( + "fmt" + "key_performance_indicators/middleware/wechatapp/wechatstatice" + "key_performance_indicators/models/wechatcallback" + "key_performance_indicators/overall" + "key_performance_indicators/overall/publicmethod" + "strconv" + "time" + + "github.com/gin-gonic/gin" +) + +// 企业微信回调入口 +func (a *ApiRouter) Index(c *gin.Context) { + outputCont := publicmethod.MapOut[string]() + outputCont["index"] = "企业微信回调入口" + publicmethod.Result(0, outputCont, c) +} + +/* +* +@ 作者: 秦东 +@ 时间: 2022-09-27 11:33:29 +@ 功能: 回调入口 +@ 参数 + + #MsgSignature 企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。 + #Timestamp 时间戳。与nonce结合使用,用于防止请求重放攻击。 + #Nonce 随机数。与timestamp结合使用,用于防止请求重放攻击。 + #Echostr 加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文 + +@ 返回值 + + # +*/ +func (a *ApiRouter) CallbackMessageApi(c *gin.Context) { + MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。 + Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。 + Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。 + Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文 + EchostrType := c.Query("type") + SystemApp := c.Query("systemapp") + + if EchostrType == "" { + EchostrType = "json" + } + if SystemApp == "" { + SystemApp = "kpi" + } + // fmt.Printf("(1)SystemApp---------->%v--->EchostrType---------->%v--->MsgSignature---------->%v--->Timestamp---------->%v--->Nonce---------->%v--->Echostr---------->%v\n", SystemApp, EchostrType, MsgSignature, Timestamp, Nonce, Echostr) + + var basicValueCallback CallBackData //企业微信回调基础参数 + basicValueCallback.MsgSignature = MsgSignature + basicValueCallback.Timestamp = Timestamp + basicValueCallback.Nonce = Nonce + basicValueCallback.DataType = EchostrType + basicValueCallback.SystemApp = SystemApp + var msgStr string + if Echostr != "" { + //Api地址验证 + basicValueCallback.Echostr = Echostr + msgStr = basicValueCallback.VerificationUrl() + c.String(200, msgStr) + } else { + //回调事件 + fmt.Printf("回调事件") + } + // fmt.Printf("(3)CallbackMessageApi---------->%v------------------->%v\n", msgStr, basicValueCallback) + + // publicmethod.Result(1, basicValueCallback, c, msgStr) +} + +// 验证URL +func (c *CallBackData) VerificationUrl() (msg string) { + // wecahtCpt := WechatVerification() + // timestampStr := strconv.FormatInt(c.Timestamp, 10) + // echoStr, cryptErr := wecahtCpt.VerifyURL(c.MsgSignature, timestampStr, c.Nonce, c.Echostr) + + // if nil != cryptErr { + // fmt.Println("verifyUrl fail", cryptErr) + // } + // msg = string(echoStr) + // jsonk, _ := json.Marshal(c) + // fmt.Printf("json--------->%v\n", string(jsonk)) + switch c.DataType { + case "json": + wxcptJson := wechatstatice.WechatDecryptJson(c.SystemApp) + echoStr, cryptErr := wxcptJson.VerifyURL(c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) + fmt.Printf("(2)wxcptJson---------->%v------------echoStr------->%v----cryptErr---->%v-----MsgSignature--->%v----Timestamp---->%v-----Nonce--->%v-----Echostr--->%v\n", wxcptJson, string(echoStr), cryptErr, c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) + msg = string(echoStr) + + var callbackLog wechatcallback.CallbackLog + callbackLog.MsgSignature = c.MsgSignature + TimestampInt, _ := strconv.ParseInt(c.Timestamp, 10, 64) + callbackLog.TimeStamp = TimestampInt + callbackLog.Nonce = c.Nonce + callbackLog.Echostr = c.Echostr + callbackLog.Xmlstr = string(echoStr) + // callbackLog.Reqdata = string(reqData) + callbackLog.AddTime = time.Now().Unix() + overall.CONSTANT_DB_WECHAT_LOG.Create(&callbackLog) + default: + wxcptXml := wechatstatice.WechatDecryptXml(c.SystemApp) + echoStr, cryptErr := wxcptXml.VerifyURL(c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) + fmt.Printf("wxcptXml---------->%v------------echoStr------->%v----cryptErr---->%v-----MsgSignature--->%v----Timestamp---->%v-----Nonce--->%v-----Echostr--->%v\n", wxcptXml, string(echoStr), cryptErr, c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) + msg = string(echoStr) + var callbackLog wechatcallback.CallbackLog + callbackLog.MsgSignature = c.MsgSignature + TimestampInt, _ := strconv.ParseInt(c.Timestamp, 10, 64) + callbackLog.TimeStamp = TimestampInt + callbackLog.Nonce = c.Nonce + callbackLog.Echostr = c.Echostr + callbackLog.Xmlstr = string(echoStr) + // callbackLog.Reqdata = string(reqData) + callbackLog.AddTime = time.Now().Unix() + overall.CONSTANT_DB_WECHAT_LOG.Create(&callbackLog) + } + + return + // fmt.Println(string(echoStr)) + // fmt.Print(string(echoStr)) + +} diff --git a/middleware/wechatapp/wechatcallback/type.go b/middleware/wechatapp/wechatcallback/type.go new file mode 100644 index 0000000..2e07a5d --- /dev/null +++ b/middleware/wechatapp/wechatcallback/type.go @@ -0,0 +1,27 @@ +package wechatcallback + +//企业微信回调 +type ApiRouter struct{} + +//企业微信回调基础参数 +type CallBackData struct { + MsgSignature string `json:"msg_signature"` + Timestamp string `json:"timestamp"` + Nonce string `json:"nonce"` + Echostr string `json:"echostr"` + ToUserName string `json:"tousername"` + AgentID string `json:"agentid"` + Encrypt string `json:"encrypt"` + DataType string `json:"datatype"` + SystemApp string `json:"systemapp"` +} + +type MsgContent struct { + ToUsername string `json:"ToUserName"` + FromUsername string `json:"FromUserName"` + CreateTime uint32 `json:"CreateTime"` + MsgType string `json:"MsgType"` + Content string `json:"Content"` + Msgid uint64 `json:"MsgId"` + Agentid uint32 `json:"AgentId"` +} diff --git a/middleware/wechatapp/wechatsendmsg/type.go b/middleware/wechatapp/wechatsendmsg/type.go new file mode 100644 index 0000000..40f7e22 --- /dev/null +++ b/middleware/wechatapp/wechatsendmsg/type.go @@ -0,0 +1,4 @@ +package wechatsendmsg + +//企业微信发送消息 +type ApiRouter struct{} diff --git a/middleware/wechatapp/wechatstatice/method.go b/middleware/wechatapp/wechatstatice/method.go new file mode 100644 index 0000000..d3cebb8 --- /dev/null +++ b/middleware/wechatapp/wechatstatice/method.go @@ -0,0 +1,140 @@ +package wechatstatice + +import ( + "encoding/json" + "fmt" + "key_performance_indicators/middleware/grocerystore" + "key_performance_indicators/middleware/wechatapp/wxbizjsonmsgcrypt" + "key_performance_indicators/middleware/wechatapp/wxbizmsgcrypt" + "key_performance_indicators/overall" + "key_performance_indicators/overall/publicmethod" +) + +/* +获取企业微信token +@systemApp 系统 +*/ +func GetWechatToken(systemApp string) (token string, err error) { + redisClient := grocerystore.RunRedis(overall.CONSTANT_REDIS3) + companyId := overall.CONSTANT_CONFIG.WechatCompany.CompanyId + redisFileKey := fmt.Sprintf("Wechat:GainToken:%v_%v", overall.CONSTANT_CONFIG.RedisPrefixStr.Alias, companyId) + var secretStr string + switch systemApp { + case "kpi": + redisFileKey = fmt.Sprintf("%v_%v_%v", redisFileKey, systemApp, overall.CONSTANT_CONFIG.WechatKpi.Agentid) + secretStr = overall.CONSTANT_CONFIG.WechatKpi.Secret + case "school": + redisFileKey = fmt.Sprintf("%v_%v_%v", redisFileKey, systemApp, overall.CONSTANT_CONFIG.WechatSchool.Agentid) + secretStr = overall.CONSTANT_CONFIG.WechatSchool.Secret + default: + redisFileKey = fmt.Sprintf("%v_%v", redisFileKey, systemApp) + } + isTrue, token := redisClient.Get(redisFileKey) + if isTrue == true { + return + } + //重新获取token + getTokenUrl := "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + companyId + "&corpsecret=" + secretStr + tokenByte := publicmethod.CurlGet(getTokenUrl) + var callBackCont weChatCallBack + err = json.Unmarshal(tokenByte, &callBackCont) + if err != nil { + return + } + if callBackCont.Errcode != 0 { + return + } + token = callBackCont.Accesstoken + redisClient.SetRedisTime(7200) + redisClient.Set(redisFileKey, token) + return +} + +/* +企业微信方式应用消息URL组装 +@systemApp 系统 +*/ +/** +@ 作者: 秦东 +@ 时间: 2022-09-27 10:41:33 +@ 功能: 企业微信发送应用消息URL组装 +@ 参数 + #systemApp 系统 + #class 类型 +@ 返回值 + #sendUrl 发送应用消息URL + #token token + #err 系统信息 +*/ +func GetSendMsgTokenUrl(systemApp, class string) (sendUrl string, token string, err error) { + token, err = GetWechatToken(systemApp) + if err != nil { + return + } + switch class { + case "update": + sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/update_template_card?access_token=%v", token) + case "recall": + sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=%v", token) + default: + sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%v", token) + } + return +} + +//企业微信解密 +/** +@ 作者: 秦东 +@ 时间: 2022-09-27 11:57:56 +@ 功能: 企业微信加解密-json +@ 参数 + #token 应用Token + #encodingAesKey 应用Encodingaeskey +@ 返回值 + #wxcpt 初始化加解密类 +*/ +func WechatDecryptJson(systemApp string) (wxcpt *wxbizjsonmsgcrypt.WXBizMsgCrypt) { + var token string + var encodingAesKey string + switch systemApp { + case "kpi": + token = overall.CONSTANT_CONFIG.WechatKpi.Token + encodingAesKey = overall.CONSTANT_CONFIG.WechatKpi.Encodingaeskey + default: + token = overall.CONSTANT_CONFIG.WechatSchool.Token + encodingAesKey = overall.CONSTANT_CONFIG.WechatSchool.Encodingaeskey + } + fmt.Printf("WechatDecryptJson------->%v------->%v------->%v\n", token, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, encodingAesKey) + wxcpt = wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, wxbizjsonmsgcrypt.JsonType) + return +} + +/* +* +@ 作者: 秦东 +@ 时间: 2022-09-27 13:21:07 +@ 功能: 企业微信加解密-XML +@ 参数 + + #token 应用Token + #encodingAesKey 应用Encodingaeskey + +@ 返回值 + + #wxcpt 初始化加解密类 +*/ +func WechatDecryptXml(systemApp string) (wxcpt *wxbizmsgcrypt.WXBizMsgCrypt) { + var token string + var encodingAesKey string + switch systemApp { + case "kpi": + token = overall.CONSTANT_CONFIG.WechatKpi.Token + encodingAesKey = overall.CONSTANT_CONFIG.WechatKpi.Encodingaeskey + default: + token = overall.CONSTANT_CONFIG.WechatSchool.Token + encodingAesKey = overall.CONSTANT_CONFIG.WechatSchool.Encodingaeskey + } + fmt.Printf("WechatDecryptXml------->%v------->%v------->%v\n", token, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, encodingAesKey) + wxcpt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, wxbizmsgcrypt.XmlType) + return +} diff --git a/middleware/wechatapp/wechatstatice/type.go b/middleware/wechatapp/wechatstatice/type.go new file mode 100644 index 0000000..4ac4ced --- /dev/null +++ b/middleware/wechatapp/wechatstatice/type.go @@ -0,0 +1,10 @@ +package wechatstatice + +//组织架构返回统类 +type weChatCallBack struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` + Accesstoken string `json:"access_token"` + Expiresin int64 `json:"expires_in"` + Ticket string `json:"ticket"` +} diff --git a/middleware/wechatapp/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go b/middleware/wechatapp/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go new file mode 100644 index 0000000..1cddd5d --- /dev/null +++ b/middleware/wechatapp/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go @@ -0,0 +1,310 @@ +package wxbizjsonmsgcrypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "math/rand" + "sort" + "strings" +) + +const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +const ( + ValidateSignatureError int = -40001 + ParseJsonError int = -40002 + ComputeSignatureError int = -40003 + IllegalAesKey int = -40004 + ValidateCorpidError int = -40005 + EncryptAESError int = -40006 + DecryptAESError int = -40007 + IllegalBuffer int = -40008 + EncodeBase64Error int = -40009 + DecodeBase64Error int = -40010 + GenJsonError int = -40011 + IllegalProtocolType int = -40012 +) + +type ProtocolType int + +const ( + JsonType ProtocolType = 1 +) + +type CryptError struct { + ErrCode int + ErrMsg string +} + +func NewCryptError(err_code int, err_msg string) *CryptError { + return &CryptError{ErrCode: err_code, ErrMsg: err_msg} +} + +type WXBizJsonMsg4Recv struct { + Tousername string `json:"tousername"` + Encrypt string `json:"encrypt"` + Agentid string `json:"agentid"` +} + +type WXBizJsonMsg4Send struct { + Encrypt string `json:"encrypt"` + Signature string `json:"msgsignature"` + Timestamp string `json:"timestamp"` + Nonce string `json:"nonce"` +} + +func NewWXBizJsonMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizJsonMsg4Send { + return &WXBizJsonMsg4Send{Encrypt: encrypt, Signature: signature, Timestamp: timestamp, Nonce: nonce} +} + +type ProtocolProcessor interface { + parse(src_data []byte) (*WXBizJsonMsg4Recv, *CryptError) + serialize(msg_send *WXBizJsonMsg4Send) ([]byte, *CryptError) +} + +type WXBizMsgCrypt struct { + token string + encoding_aeskey string + receiver_id string + protocol_processor ProtocolProcessor +} + +type JsonProcessor struct { +} + +func (self *JsonProcessor) parse(src_data []byte) (*WXBizJsonMsg4Recv, *CryptError) { + var msg4_recv WXBizJsonMsg4Recv + err := json.Unmarshal(src_data, &msg4_recv) + if nil != err { + fmt.Println("Unmarshal fail", err) + return nil, NewCryptError(ParseJsonError, "json to msg fail") + } + return &msg4_recv, nil +} + +func (self *JsonProcessor) serialize(msg4_send *WXBizJsonMsg4Send) ([]byte, *CryptError) { + json_msg, err := json.Marshal(msg4_send) + if nil != err { + return nil, NewCryptError(GenJsonError, err.Error()) + } + + return json_msg, nil +} + +func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt { + var protocol_processor ProtocolProcessor + if protocol_type != JsonType { + panic("unsupport protocal") + } else { + protocol_processor = new(JsonProcessor) + } + + return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor} +} + +func (self *WXBizMsgCrypt) randString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] + } + return string(b) +} + +func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte { + padding := block_size - (len(plaintext) % block_size) + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + var buffer bytes.Buffer + buffer.WriteString(plaintext) + buffer.Write(padtext) + return buffer.Bytes() +} + +func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) { + plaintext_len := len(plaintext) + if nil == plaintext || plaintext_len == 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero") + } + if plaintext_len%block_size != 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size") + } + padding_len := int(plaintext[plaintext_len-1]) + return plaintext[:plaintext_len-padding_len], nil +} + +func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + const block_size = 32 + pad_msg := self.pKCS7Padding(plaintext, block_size) + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(EncryptAESError, err.Error()) + } + + ciphertext := make([]byte, len(pad_msg)) + iv := aeskey[:aes.BlockSize] + + mode := cipher.NewCBCEncrypter(block, iv) + + mode.CryptBlocks(ciphertext, pad_msg) + base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) + base64.StdEncoding.Encode(base64_msg, ciphertext) + + return base64_msg, nil +} + +func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(DecryptAESError, err.Error()) + } + + if len(encrypt_msg) < aes.BlockSize { + return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid") + } + + iv := aeskey[:aes.BlockSize] + + if len(encrypt_msg)%aes.BlockSize != 0 { + return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + mode.CryptBlocks(encrypt_msg, encrypt_msg) + + return encrypt_msg, nil +} + +func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string { + sort_arr := []string{self.token, timestamp, nonce, data} + sort.Strings(sort_arr) + var buffer bytes.Buffer + for _, value := range sort_arr { + buffer.WriteString(value) + } + + sha := sha1.New() + sha.Write(buffer.Bytes()) + signature := fmt.Sprintf("%x", sha.Sum(nil)) + return string(signature) +} + +func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) { + const block_size = 32 + plaintext, err := self.pKCS7Unpadding(plaintext, block_size) + if nil != err { + return nil, 0, nil, nil, err + } + + text_len := uint32(len(plaintext)) + if text_len < 20 { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1") + } + random := plaintext[:16] + msg_len := binary.BigEndian.Uint32(plaintext[16:20]) + if text_len < (20 + msg_len) { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2") + } + + msg := plaintext[20 : 20+msg_len] + receiver_id := plaintext[20+msg_len:] + + return random, msg_len, msg, receiver_id, nil +} + +func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) { + signature := self.calSignature(timestamp, nonce, echostr) + + if strings.Compare(signature, msg_signature) != 0 { + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, err := self.cbcDecrypter(echostr) + if nil != err { + return nil, err + } + + _, _, msg, receiver_id, err := self.ParsePlainText(plaintext) + if nil != err { + return nil, err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id)) + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} + +func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) { + rand_str := self.randString(16) + var buffer bytes.Buffer + buffer.WriteString(rand_str) + + msg_len_buf := make([]byte, 4) + binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg))) + buffer.Write(msg_len_buf) + buffer.WriteString(reply_msg) + buffer.WriteString(self.receiver_id) + + tmp_ciphertext, err := self.cbcEncrypter(buffer.String()) + if nil != err { + return nil, err + } + ciphertext := string(tmp_ciphertext) + + signature := self.calSignature(timestamp, nonce, ciphertext) + + msg4_send := NewWXBizJsonMsg4Send(ciphertext, signature, timestamp, nonce) + return self.protocol_processor.serialize(msg4_send) +} + +func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) { + msg4_recv, crypt_err := self.protocol_processor.parse(post_data) + if nil != crypt_err { + return nil, crypt_err + } + + signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt) + + if strings.Compare(signature, msg_signature) != 0 { + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt) + if nil != crypt_err { + return nil, crypt_err + } + + _, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext) + if nil != crypt_err { + return nil, crypt_err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} diff --git a/middleware/wechatapp/wxbizmsgcrypt/wxbizmsgcrypt.go b/middleware/wechatapp/wxbizmsgcrypt/wxbizmsgcrypt.go new file mode 100644 index 0000000..9f30dca --- /dev/null +++ b/middleware/wechatapp/wxbizmsgcrypt/wxbizmsgcrypt.go @@ -0,0 +1,315 @@ +package wxbizmsgcrypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "encoding/xml" + "fmt" + "math/rand" + "sort" + "strings" +) + +const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +const ( + ValidateSignatureError int = -40001 + ParseXmlError int = -40002 + ComputeSignatureError int = -40003 + IllegalAesKey int = -40004 + ValidateCorpidError int = -40005 + EncryptAESError int = -40006 + DecryptAESError int = -40007 + IllegalBuffer int = -40008 + EncodeBase64Error int = -40009 + DecodeBase64Error int = -40010 + GenXmlError int = -40010 + ParseJsonError int = -40012 + GenJsonError int = -40013 + IllegalProtocolType int = -40014 +) + +type ProtocolType int + +const ( + XmlType ProtocolType = 1 +) + +type CryptError struct { + ErrCode int + ErrMsg string +} + +func NewCryptError(err_code int, err_msg string) *CryptError { + return &CryptError{ErrCode: err_code, ErrMsg: err_msg} +} + +type WXBizMsg4Recv struct { + Tousername string `xml:"ToUserName"` + Encrypt string `xml:"Encrypt"` + Agentid string `xml:"AgentID"` +} + +type CDATA struct { + Value string `xml:",cdata"` +} + +type WXBizMsg4Send struct { + XMLName xml.Name `xml:"xml"` + Encrypt CDATA `xml:"Encrypt"` + Signature CDATA `xml:"MsgSignature"` + Timestamp string `xml:"TimeStamp"` + Nonce CDATA `xml:"Nonce"` +} + +func NewWXBizMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizMsg4Send { + return &WXBizMsg4Send{Encrypt: CDATA{Value: encrypt}, Signature: CDATA{Value: signature}, Timestamp: timestamp, Nonce: CDATA{Value: nonce}} +} + +type ProtocolProcessor interface { + parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) + serialize(msg_send *WXBizMsg4Send) ([]byte, *CryptError) +} + +type WXBizMsgCrypt struct { + token string + encoding_aeskey string + receiver_id string + protocol_processor ProtocolProcessor +} + +type XmlProcessor struct { +} + +func (self *XmlProcessor) parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) { + var msg4_recv WXBizMsg4Recv + err := xml.Unmarshal(src_data, &msg4_recv) + if nil != err { + return nil, NewCryptError(ParseXmlError, "xml to msg fail") + } + return &msg4_recv, nil +} + +func (self *XmlProcessor) serialize(msg4_send *WXBizMsg4Send) ([]byte, *CryptError) { + xml_msg, err := xml.Marshal(msg4_send) + if nil != err { + return nil, NewCryptError(GenXmlError, err.Error()) + } + return xml_msg, nil +} + +func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt { + var protocol_processor ProtocolProcessor + if protocol_type != XmlType { + panic("unsupport protocal") + } else { + protocol_processor = new(XmlProcessor) + } + + return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor} +} + +func (self *WXBizMsgCrypt) randString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] + } + return string(b) +} + +func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte { + padding := block_size - (len(plaintext) % block_size) + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + var buffer bytes.Buffer + buffer.WriteString(plaintext) + buffer.Write(padtext) + return buffer.Bytes() +} + +func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) { + plaintext_len := len(plaintext) + if nil == plaintext || plaintext_len == 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero") + } + if plaintext_len%block_size != 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size") + } + padding_len := int(plaintext[plaintext_len-1]) + return plaintext[:plaintext_len-padding_len], nil +} + +func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + const block_size = 32 + pad_msg := self.pKCS7Padding(plaintext, block_size) + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(EncryptAESError, err.Error()) + } + + ciphertext := make([]byte, len(pad_msg)) + iv := aeskey[:aes.BlockSize] + + mode := cipher.NewCBCEncrypter(block, iv) + + mode.CryptBlocks(ciphertext, pad_msg) + base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) + base64.StdEncoding.Encode(base64_msg, ciphertext) + + return base64_msg, nil +} + +func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(DecryptAESError, err.Error()) + } + + if len(encrypt_msg) < aes.BlockSize { + return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid") + } + + iv := aeskey[:aes.BlockSize] + + if len(encrypt_msg)%aes.BlockSize != 0 { + return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + mode.CryptBlocks(encrypt_msg, encrypt_msg) + + return encrypt_msg, nil +} + +func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string { + sort_arr := []string{self.token, timestamp, nonce, data} + sort.Strings(sort_arr) + var buffer bytes.Buffer + for _, value := range sort_arr { + buffer.WriteString(value) + } + + sha := sha1.New() + sha.Write(buffer.Bytes()) + signature := fmt.Sprintf("%x", sha.Sum(nil)) + return string(signature) +} + +func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) { + const block_size = 32 + plaintext, err := self.pKCS7Unpadding(plaintext, block_size) + if nil != err { + return nil, 0, nil, nil, err + } + + text_len := uint32(len(plaintext)) + if text_len < 20 { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1") + } + random := plaintext[:16] + msg_len := binary.BigEndian.Uint32(plaintext[16:20]) + if text_len < (20 + msg_len) { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2") + } + + msg := plaintext[20 : 20+msg_len] + receiver_id := plaintext[20+msg_len:] + + return random, msg_len, msg, receiver_id, nil +} + +func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) { + signature := self.calSignature(timestamp, nonce, echostr) + + if strings.Compare(signature, msg_signature) != 0 { + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, err := self.cbcDecrypter(echostr) + if nil != err { + return nil, err + } + + _, _, msg, receiver_id, err := self.ParsePlainText(plaintext) + if nil != err { + return nil, err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id)) + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} + +func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) { + rand_str := self.randString(16) + var buffer bytes.Buffer + buffer.WriteString(rand_str) + + msg_len_buf := make([]byte, 4) + binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg))) + buffer.Write(msg_len_buf) + buffer.WriteString(reply_msg) + buffer.WriteString(self.receiver_id) + + tmp_ciphertext, err := self.cbcEncrypter(buffer.String()) + if nil != err { + return nil, err + } + ciphertext := string(tmp_ciphertext) + + signature := self.calSignature(timestamp, nonce, ciphertext) + + msg4_send := NewWXBizMsg4Send(ciphertext, signature, timestamp, nonce) + return self.protocol_processor.serialize(msg4_send) +} + +func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) { + msg4_recv, crypt_err := self.protocol_processor.parse(post_data) + if nil != crypt_err { + return nil, crypt_err + } + + signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt) + + if strings.Compare(signature, msg_signature) != 0 { + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt) + if nil != crypt_err { + return nil, crypt_err + } + + _, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext) + if nil != crypt_err { + return nil, crypt_err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} diff --git a/models/wechatcallback/callbacklog.go b/models/wechatcallback/callbacklog.go new file mode 100644 index 0000000..899cf5a --- /dev/null +++ b/models/wechatcallback/callbacklog.go @@ -0,0 +1,18 @@ +package wechatcallback + +//企业微信回调记录 +type CallbackLog struct { + Id int64 `json:"id" gorm:"column:id;type:bigint(20);;primaryKey;unique;not null;autoIncrement;index"` + MsgSignature string `json:"msgSignature" gorm:"column:msg_signature;type:varchar(255);not null;comment:组织名称"` + TimeStamp int64 `json:"timestamp" gorm:"column:timestamp;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` + Nonce string `json:"nonce" gorm:"column:nonce;type:varchar(255);not null;comment:组织名称"` + Echostr string `json:"echostr" gorm:"column:echostr;type:varchar(255);not null;comment:组织名称"` + Xmlstr string `json:"xmlstr" gorm:"column:xmlstr;type:text;comment:组织名称"` + Jsonstr string `json:"jsonstr" gorm:"column:jsonstr;type:text;comment:组织名称"` + AddTime int64 `json:"addtime" gorm:"column:addtime;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` + Reqdata string `json:"reqdata" gorm:"column:reqdata;type:text;comment:组织名称"` +} + +func (CallbackLog *CallbackLog) TableName() string { + return "callback_log" +}