package pay import ( "context" "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "fmt" "io" "net/http" "os" "strings" ) const certificatesPath = "/v3/certificates" type certificatesResponse struct { Data []platformCertificateData `json:"data"` } type platformCertificateData struct { SerialNo string `json:"serial_no"` EffectiveTime string `json:"effective_time"` ExpireTime string `json:"expire_time"` EncryptCertificate encryptCertificate `json:"encrypt_certificate"` } type encryptCertificate struct { Algorithm string `json:"algorithm"` Nonce string `json:"nonce"` AssociatedData string `json:"associated_data"` Ciphertext string `json:"ciphertext"` } func certSerialNumber(cert *x509.Certificate) string { return strings.ToUpper(cert.SerialNumber.Text(16)) } func loadPlatformCertificate(path string) (string, *rsa.PublicKey, error) { data, err := os.ReadFile(path) if err != nil { return "", nil, fmt.Errorf("read platform certificate %s: %w", path, err) } block, _ := pem.Decode(data) if block == nil { return "", nil, fmt.Errorf("invalid platform certificate pem: %s", path) } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return "", nil, fmt.Errorf("parse platform certificate %s: %w", path, err) } publicKey, ok := cert.PublicKey.(*rsa.PublicKey) if !ok { return "", nil, fmt.Errorf("platform certificate public key is not rsa: %s", path) } return certSerialNumber(cert), publicKey, nil } func (c *Client) addPlatformCert(serial string, publicKey *rsa.PublicKey) { if c.platformCerts == nil { c.platformCerts = make(map[string]*rsa.PublicKey) } c.platformCerts[strings.ToUpper(serial)] = publicKey if c.platformSerial == "" { c.platformSerial = strings.ToUpper(serial) } } func (c *Client) refreshPlatformCertificates(ctx context.Context) error { httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, c.Host+certificatesPath, nil) if err != nil { return err } httpReq.Header.Set("Accept", "application/json") httpReq.Header.Set("User-Agent", "lone-services-wechatpay") authorization, err := c.buildAuthorization(http.MethodGet, certificatesPath, nil) if err != nil { return err } httpReq.Header.Set("Authorization", authorization) resp, err := c.httpClient.Do(httpReq) if err != nil { return err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return err } if !isSuccessStatus(resp.StatusCode) { return newAPIError(resp.StatusCode, respBody) } if len(c.platformCerts) > 0 { if err := c.validateResponse(resp.Header, respBody); err != nil { return fmt.Errorf("validate certificates response: %w", err) } } var result certificatesResponse if err := json.Unmarshal(respBody, &result); err != nil { return err } for _, item := range result.Data { plaintext, err := decryptAES256GCM( c.APIv3Key, item.EncryptCertificate.AssociatedData, item.EncryptCertificate.Nonce, item.EncryptCertificate.Ciphertext, ) if err != nil { return fmt.Errorf("decrypt platform certificate %s: %w", item.SerialNo, err) } block, _ := pem.Decode(plaintext) if block == nil { return fmt.Errorf("invalid decrypted platform certificate pem: %s", item.SerialNo) } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return fmt.Errorf("parse platform certificate %s: %w", item.SerialNo, err) } publicKey, ok := cert.PublicKey.(*rsa.PublicKey) if !ok { return fmt.Errorf("platform certificate public key is not rsa: %s", item.SerialNo) } c.addPlatformCert(item.SerialNo, publicKey) } if len(c.platformCerts) == 0 { return fmt.Errorf("no platform certificates available") } return nil }