150 lines
3.7 KiB
Go
150 lines
3.7 KiB
Go
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
|
|
}
|