WeChat applet development practice 10_3 platform certificate and response signature

11.3 Platform Certificate

WeChat Pay will include a response signature in the header of the HTTP request response, and the merchant must verify the signature of the response to ensure that the response is sent by the WeChat Pay platform. It is also necessary to verify the signature of the HTTP header of the WeChat payment callback.
The signature verification of the response and callback uses the WeChat Pay platform certificate, not the merchant certificate. The platform certificate refers to the certificate that WeChat Pay is responsible for applying for, including the WeChat Pay platform logo and public key information. Merchants can use the public key in the platform certificate for signature verification. The platform certificate of WeChat Pay can be obtained by calling the "obtain platform certificate interface". The interface address for obtaining the platform certificate is:
https://api.mch.weixin.qq.com/v3/certificates
The processing logic of calling the WeChat payment platform certificate obtaining interface is as follows:

  • First obtain the merchant certificate and merchant private key, use the merchant certificate and merchant private key to construct an HTTP request signature, and set the fields specified in the request header
  • Send HTTP GET request
  • Parse the data returned by the request, and use the AEAD_AES_256_GCM algorithm to decrypt the certificate data.
    The following is the relevant code for the interface call of the WeChat payment platform certificate:
type WxCertRet struct {
    
    
   Data []WxCertRetItem            `json:"data"`
}
type WxCertRetItem struct {
    
    
   EffectiveTime      string          `json:"effective_time"`
   Certificate       WxCertificate   `json:"encrypt_certificate"`
   ExpireTime         string          `json:"expire_time"`
   SerialNo           string          `json:"serial_no"`
}
type WxCertificate struct {
    
    
   Algorithm      string           `json:"algorithm"`
   AssociatedData string           `json:"associated_data"`
   Ciphertext     string           `json:"ciphertext"`
   Nonce          string           `json:"nonce"`
}
//获取微信支付平台证书
func downloadPlatCertificate(ctx *MchParam) error {
    
    
   const publicKeyUrl = "https://api.mch.weixin.qq.com/v3/certificates"
   log.Println(publicKeyUrl)
   token, err := GenerateWxPayReqHeader(ctx, http.MethodGet, publicKeyUrl, "")
   if err != nil {
    
    
      log.Println(err)
      return err
   }

   request, err := http.NewRequest(http.MethodGet, publicKeyUrl, nil)
   if err != nil {
    
    
      log.Println(err)
      return err
   }

   request.Header.Add("Authorization", token)
   request.Header.Add("User-Agent", "go pay sdk")
   request.Header.Add("Content-type", "application/json;charset='utf-8'")
   request.Header.Add("Accept", "application/json")

   client := http.DefaultClient
   response, err := client.Do(request)
   if err != nil {
    
    
      log.Println(err)
      return err
   }
   defer response.Body.Close()

   bodyBytes, err := ioutil.ReadAll(response.Body)
   if err != nil {
    
    
      log.Println(err)
      return err
   }

   var tokenResponse WxCertRet
   if err = json.Unmarshal(bodyBytes, &tokenResponse); err != nil {
    
    
      log.Println(err)
      return err
   }

   //使用 AEAD_AES_256_GCM 算法进行解密
   for _, encrypt_cert := range tokenResponse.Data {
    
    
      decryptBytes, err := DecryptAES256GCM(
         ctx.MchAPIKey,
         encrypt_cert.Certificate.AssociatedData,
         encrypt_cert.Certificate.Nonce,
         encrypt_cert.Certificate.Ciphertext)
      if err != nil {
    
    
         log.Println(err)
         return err
      }

      cert_ret, _ := LoadCertificate(decryptBytes)
      if cert_ret != nil {
    
    
	serial_no := encrypt_cert.SerialNo
	map_plat_cert[serial_no] = cert_ret
      }
   }
   return nil
}

illustrate:

  • The validity period of the platform certificate is set, and the merchant needs to call the interface before the certificate expires and update the operation certificate.
  • The WeChat platform will generate a new certificate 10 days before the old certificate expires. It is recommended that merchants download and deploy the new certificate 5-10 days before the old certificate expires.
  • Within 5 days before the old certificate expires, WeChat Pay allows the old and new certificates to be used at the same time, so it is recommended that the merchant system can support multi-platform certificates.
    The following code is an example of multi-platform certificate management:
var map_plat_cert = make(map[string]*x509.Certificate)
var lock_plat_cert sync.RWMutex
func GetPlatCertificate(ctx *MchParam, serial_no string) *x509.Certificate {
    
    
   lock_plat_cert.Lock()
   defer lock_plat_cert.Unlock()

   cert, ok := map_plat_cert[serial_no]
   if !ok {
    
    
      //调用证书获取接口
      downloadPlatCertificate(ctx)
      cert = map_plat_cert[serial_no]
      if cert == nil {
    
    
         log.Println("GetPlatCertificate error !!!")
      }
   }
   return cert
}

11.4 Response signature

If the verification merchant's request signature is correct, WeChat Pay will include the response signature in the HTTP header of the response. We recommend that merchants verify the response signature. Similarly, WeChat Pay will include the signature of the callback message in the HTTP header of the callback. Merchants must verify the signature of the callback to ensure that the callback was sent by the WeChat Pay platform.
The WeChat payment platform uses the platform private key (not the merchant's private key) to sign the response. The platform certificate serial number of WeChat Pay is located in the HTTP request header Wechatpay-Serial. Before verifying the signature, the merchant is required to check whether the serial number is consistent with the serial number of the WeChat payment platform certificate currently held by the merchant. If not, obtain the certificate again. Otherwise, the signed private key and certificate do not match, and the signature cannot be successfully verified.
First, the merchant obtains the following information from the response.

  • The response timestamp in the HTTP header Wechatpay-Timestamp.
  • The response random string in the HTTP header Wechatpay-Nonce
  • Response body (response Body)
    Then, please construct the signature string of the response according to the following rules. There are three lines in the signature string, ending with \n, including the last line.
    Response timestamp\nResponse random string\nResponse message body\
    nWechatpay’s response signature is passed through the HTTP header Wechatpay-Signature. Use Base64 to decode the field value of Wechatpay-Signature to get the response signature. Here is the relevant code for signature verification:
//对微信支付应答报文进行验证
//sign_param:微信支付应答报文数据
//certificate:微信支付平台证书中,使用微信支付平台证书中的公钥验签
func ResponseValidate(sign_param *WxSignParam, certificate *x509.Certificate) error {
    
    
   //构造签名串
   message := sign_param.BuildResponseMessage()
   signature, err := base64.StdEncoding.DecodeString(sign_param.Signature)
   if err != nil {
    
    
      return fmt.Errorf("base64 decode err:%s", err.Error())
   }
   hashed := sha256.Sum256([]byte(message))
   err = rsa.VerifyPKCS1v15(certificate.PublicKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], []byte(signature))
   if err != nil {
    
    
      return fmt.Errorf("verifty signature err:%s", err.Error())
   }
   return nil
}

WxSignParam is the relevant data of response signature, and the definition of WxSignParam is as follows:

type WxSignParam struct {
    
    
   Timestamp   string //微信支付回包时间戳
   Nonce       string //微信支付回包随机字符串
   Signature   string //微信支付回包签名信息
   CertSerial  string //微信支付回包平台序列号
   RequestId   string //微信支付回包请求ID
   Body        string
}

The data of WxSignParam is obtained through the function: InitFromResponse. InitFromResponse obtains the parameters required to verify the signature from the HTTP response header, including: response time stamp, response random string, response signature and platform certificate serial number:

func (ent *WxSignParam)InitFromResponse(response *http.Response, body string) error {
    
    
   ent.RequestId = strings.TrimSpace(response.Header.Get("Request-Id"))
   ent.CertSerial = response.Header.Get("Wechatpay-Serial")
   ent.Signature = response.Header.Get("Wechatpay-Signature")
   ent.Timestamp = response.Header.Get("Wechatpay-Timestamp")
   ent.Nonce = response.Header.Get("Wechatpay-Nonce")
   ent.Body = body
   return nil
}

The code to construct the signature string from WxSignParam data is:

unc (ent *WxSignParam)BuildResponseMessage() string {
    
    
   message := fmt.Sprintf("%s\n%s\n%s\n",
      ent.Timestamp, ent.Nonce, ent.Body)
   return message
}

With the signature verification function, we can perform signature verification on the response to the payment request. The following code is a payment interface call function with signature verification logic added:

func WxPayPostV3(ctx *MchParam, url string, data []byte) (string, error) {
    
    
   token, err := GenerateWxPayReqHeader(ctx, http.MethodPost, url, string(data))
   if err != nil {
    
    
      log.Println(err)
      return "", err
   }

   request, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
   if err != nil {
    
    
      return "", err
   }
   request.Header.Add("Authorization", token)
   request.Header.Add("User-Agent", "go pay sdk")
   request.Header.Add("Content-type", "application/json;charset='utf-8'")
   request.Header.Add("Accept", "application/json")

   client := &http.Client{
    
    Timeout: 5 * time.Second}
   resp, err := client.Do(request)
   if err != nil {
    
    
      log.Println(err)
      return "", err
   }
   defer resp.Body.Close()

   result, _ := ioutil.ReadAll(resp.Body)
   if resp.StatusCode != 200 && resp.StatusCode != 204 {
    
    
      err := fmt.Errorf("status:%d;msg=%s", resp.StatusCode, string(result))
      log.Println(err)
      return string(result), err
   }

   var sign_param WxSignParam
   err = sign_param.InitFromResponse(resp, string(result))
   if err != nil {
    
    
      log.Println(err)
      return string(result), err
   }

   //Validate WechatPay Signature
   plat_certificate := GetPlatCertificate(ctx, sign_param.CertSerial)
   err = ResponseValidate(&sign_param, plat_certificate);
   if err != nil {
    
    
      log.Println(err)
      return string(result), err
   }

   log.Println(string(result))
   return string(result), nil
}

11.5 Message Decryption

In order to ensure security, WeChat Pay has encrypted the key information with AES-256-GCM in the callback notification and platform certificate download interface. AES-GCM is a NIST-standard certified encryption algorithm and an encryption mode that can simultaneously ensure data confidentiality, integrity, and authenticity. Its most widespread use is in TLS. The encryption key used in the certificate and the callback message is a NIST-standard APIv3 key. Merchants need to set the APIv3 key on the [Merchant Platform] -> [API Security] page first, and the length of the key is 32 bytes. The following is the function implementation of message decryption:

func DecryptAES256GCM(aesKey, associatedData, nonce, ciphertext string) (string, error) {
    
    
   decodedCiphertext, err := base64.StdEncoding.DecodeString(ciphertext)
   if err != nil {
    
    
      return "", err
   }
   c, err := aes.NewCipher([]byte(aesKey))
   if err != nil {
    
    
      return "", err
   }
   gcm, err := cipher.NewGCM(c)
   if err != nil {
    
    
      return "", err
   }
   dataBytes, err := gcm.Open(nil, []byte(nonce), decodedCiphertext, []byte(associatedData))
   if err != nil {
    
    
      return "", err
   }
   return string(dataBytes), nil
}

Guess you like

Origin blog.csdn.net/gz_hm/article/details/128475845