支付宝token获取
This commit is contained in:
parent
d9c3e7ad88
commit
d15e6d43be
|
@ -0,0 +1,74 @@
|
||||||
|
package alipay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gitea.cdlsxd.cn/BaseSystem/plugin/alipay/vo"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BaseUri = "https://openapi.alipay.com/gateway.do"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = "1.0"
|
||||||
|
Format = "json"
|
||||||
|
SignType = "RSA2"
|
||||||
|
Charset = "UTF-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
AppId string // 应用ID
|
||||||
|
Prk string // 私钥
|
||||||
|
PukPath string // 验签公钥
|
||||||
|
MchCertPath string // 商户证书路径
|
||||||
|
RootCertPath string // 根证书路径
|
||||||
|
NotifyUrl string // 回调地址
|
||||||
|
}
|
||||||
|
|
||||||
|
type Req interface {
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Req = (*Param)(nil)
|
||||||
|
|
||||||
|
type Param struct {
|
||||||
|
AlipayRootCertSn string `json:"alipay_root_cert_sn"`
|
||||||
|
AppCertSn string `json:"app_cert_sn"`
|
||||||
|
AppId string `json:"app_id" validate:"required"`
|
||||||
|
Method string `json:"method" validate:"required"`
|
||||||
|
Format string `json:"format" validate:"required"`
|
||||||
|
Charset string `json:"charset" validate:"required"`
|
||||||
|
SignType string `json:"sign_type" validate:"required"`
|
||||||
|
Timestamp string `json:"timestamp" validate:"required"`
|
||||||
|
Version string `json:"version" validate:"required"`
|
||||||
|
BizContent string `json:"biz_content"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Code vo.Code `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
SubCode string `json:"sub_code"`
|
||||||
|
SubMsg string `json:"sub_msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notify struct {
|
||||||
|
Charset string `json:"charset"`
|
||||||
|
UtcTimestamp string `json:"utc_timestamp"`
|
||||||
|
AppId string `json:"app_id"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
SignType string `json:"sign_type"`
|
||||||
|
NotifyId string `json:"notify_id"`
|
||||||
|
MsgMethod string `json:"msg_method"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
BizContent string `json:"biz_content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Param) Validate() error {
|
||||||
|
err := validator.New().Struct(req)
|
||||||
|
if err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf("参数有误:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gitea.cdlsxd.cn/BaseSystem/plugin/alipay"
|
||||||
|
"gitea.cdlsxd.cn/BaseSystem/plugin/alipay/utils"
|
||||||
|
"github.com/carlmjohnson/requests"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token alipay.Client
|
||||||
|
|
||||||
|
func (t *Token) Client(ctx context.Context, code string) (*AlipayTokenResponse, error) {
|
||||||
|
cert, err := utils.GetCert(t.MchCertPath, t.RootCertPath, t.PukPath, t.AppId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
param := &Param{
|
||||||
|
AlipayRootCertSn: cert.RootCertSN,
|
||||||
|
AppCertSn: cert.MchCertSN,
|
||||||
|
AppId: t.AppId,
|
||||||
|
Method: "alipay.system.oauth.token",
|
||||||
|
Format: alipay.Format,
|
||||||
|
Charset: alipay.Charset,
|
||||||
|
SignType: alipay.SignType,
|
||||||
|
Timestamp: time.Now().Format(time.DateTime),
|
||||||
|
Version: alipay.Version,
|
||||||
|
Sign: "",
|
||||||
|
GrantType: "authorization_code",
|
||||||
|
Code: code,
|
||||||
|
}
|
||||||
|
uv, err := utils.UrlValues(t.Prk, param)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response AlipayTokenResponse
|
||||||
|
err = requests.URL(alipay.BaseUri).Post().Params(uv).ToJSON(&response).Fetch(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求异常,msg:" + err.Error())
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToken_Client(t *testing.T) {
|
||||||
|
d := Token{
|
||||||
|
AppId: "2021004100663111",
|
||||||
|
Prk: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbA+YuMp4JUVj6rjzgwGKNXWkEMGX/rinqkfyBZ6B6p8EKz8zgA+ypiJLOixD3GyKnUnAzx4waNRZHfdEu+l57kJFtd/ipfwtJ28aTi7TqtqEpqD+UPY4ourt2CuyCFxWsonS6dczqtTvfAVArTdbGJYY+kNNVR3WiXgGUhkUu8N7vEowU00RUQGNdSVMUs4FX+HlU3RnEoRc/xUhPiaLf0Bm/g9wG96kwyg/TZvkNU7PpMVRdXeLrVORn0qThs3VA4dqondF+O12iC1TK4TKYGzFYczGAUsfuurtDyCc2GMoE+hH2FR8U7amQOVuYZFkutTdqaqukWpFQOr8wLeMzAgMBAAECggEAD715/3v3y6ejA3EeQvDQpGRANeLckcGMlaUkRpCSAf6oawSALuFZUt3T3zAzae7zUJ8mHTKMKR4DmeO68utfevXq3bkvj87nmslGvjfeKrgxYPMMjrTV0KuK6XLjiH3sOtn6FaR9s6iOwvovLs2LT/ZGbZyu84QNOjwTVP9JXZQkBgMItdKf+U3H2Cjp7U/qXBt8/9yzVFklp1g1883DAty0lzmT27dJimGVGaPQ8vNxo81+ZUEJAn6GUTk0K/GwJfhPTU8hh8G90n2LTyskoMjGxQe9lXfCcS9DmWawEQL4WTctPrDYlnS/cjCVMS0KXIFuxRNf6qaMYDeywC8BgQKBgQDyf40dvmw34Rrb46+NLayQ9W5CJI/dYeRajpCjoOomq5QYhbXzUCpVfbtByeGMsg3zN58NNsZGhl5SU0GdEdoOlxCk+2Hey2yQYF4ugQm/dTd68Jgqi3yujigAbNYa1ZhL9t3FqouPY1dGiaxl+DFYdSMIrsVFXh2NbrPyqTk5IQKBgQDnNaH7LCcIUqg9H8Tsls+8GLzP2HwF3hdll8asEsF3K3HX6/Zlp4VnnEcIkAxLRL/L0o5akXrmA18ZwfoSguTPXV9va3G2GgIiJHgcytmGtQVvbpFKuPnCXKz+avxnfO0flJqyYEuHr/40jsGbMkk0Kr52/n3ivXZbBUlT+tkt0wKBgB0qLgSnxEgsMJjFl3V5SsncWrhlwU+02Evz3X1wevjPpe4VFr7+ozjI+F5/MztCpt7bj6t9LPeKbYmlLb0ASqN6k6vj9+9ds97hWDJrnoqCRHvqt8JWKFauDi2O6WksyzZHqIB/dG14WyTGpg9VfEnRPLdsnZksKo26BLZol9NBAoGBAMz7oMNljqlzVtLyMo2q2zuhFuySusoc79NTL4FpE3rK2qCbA5V2YvDL/bIau7uTlRNodmrXZgU84fidIE9/Gsq5tp26vVK8Vj3c5Vxpf1dNcCct+MQtoMjvjzP0uBgsCrKf9lLEytHed1ozYnRsrbgBWWF4GTWH0cG6uxsoX5mfAoGAfYXKZdyRU+Su4y4EnDLMXd320ar7PaeuY8aZU7V6UQEsaOj6H3O8JMEOuBrOVEhAP2EkAC8ayargSXTSOkN97pg88agKDwA6jh4N6TKAK8XMft81YPPliVwZMsAUqihSKQBnKZ7ssHHLGWWWp5vfkyb7Y7dIkZcPzB0X3q/jL58=",
|
||||||
|
PukPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayCertPublicKey_RSA2.crt",
|
||||||
|
MchCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt",
|
||||||
|
RootCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt",
|
||||||
|
}
|
||||||
|
resp, err := d.Client(context.Background(), "349feb5fb58e455da1b00a7748ceMD68")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
if resp.IsSuccess() {
|
||||||
|
t.Logf("Response: %+v", resp.Response)
|
||||||
|
} else {
|
||||||
|
t.Errorf("ErrorResponse: %+v", resp.ErrorResponse)
|
||||||
|
t.Errorf("ErrorResponse Msg: %+v", resp.ErrorResponse.Msg)
|
||||||
|
t.Errorf("ErrorResponse SubCode: %+v", resp.ErrorResponse.SubCode)
|
||||||
|
t.Errorf("ErrorResponse SubMsg: %+v", resp.ErrorResponse.SubMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gitea.cdlsxd.cn/BaseSystem/plugin/alipay"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Param struct {
|
||||||
|
AlipayRootCertSn string `json:"alipay_root_cert_sn"`
|
||||||
|
AppCertSn string `json:"app_cert_sn"`
|
||||||
|
AppId string `json:"app_id" validate:"required"`
|
||||||
|
Method string `json:"method" validate:"required"`
|
||||||
|
Format string `json:"format" validate:"required"`
|
||||||
|
Charset string `json:"charset" validate:"required"`
|
||||||
|
SignType string `json:"sign_type" validate:"required"`
|
||||||
|
Timestamp string `json:"timestamp" validate:"required"`
|
||||||
|
Version string `json:"version" validate:"required"`
|
||||||
|
BizContent string `json:"biz_content"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
type AlipayTokenSuccessResponse struct {
|
||||||
|
UserId string `json:"user_id"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn string `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
ReExpiresIn string `json:"re_expires_in"`
|
||||||
|
AuthStart string `json:"auth_start"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlipayTokenResponse struct {
|
||||||
|
ErrorResponse *alipay.ErrorResponse `json:"error_response"`
|
||||||
|
Response *AlipayTokenSuccessResponse `json:"alipay_system_oauth_token_response"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Param) Validate() error {
|
||||||
|
err := validator.New().Struct(req)
|
||||||
|
if err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf("参数有误:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AlipayTokenResponse) IsSuccess() bool {
|
||||||
|
return r.ErrorResponse == nil
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CertConfig struct {
|
||||||
|
MchCertSN string
|
||||||
|
RootCertSN string
|
||||||
|
PublicKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
once sync.Once
|
||||||
|
mutex sync.RWMutex
|
||||||
|
CertConfigs map[string]*CertConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance manager
|
||||||
|
|
||||||
|
func getCertConfig(appId string) *CertConfig {
|
||||||
|
c, ok := instance.CertConfigs[appId]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCertConfig(appId string, certConfig *CertConfig) {
|
||||||
|
instance.mutex.Lock()
|
||||||
|
defer instance.mutex.Unlock()
|
||||||
|
instance.CertConfigs[appId] = certConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
instance.CertConfigs = make(map[string]*CertConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCert(mchCertPath, rootCertPath, PublicKeyPath, appId string) (*CertConfig, error) {
|
||||||
|
if mchCertPath == "" || rootCertPath == "" || appId == "" {
|
||||||
|
return nil, fmt.Errorf("mchCertPath or rootCertPath or appId is empty")
|
||||||
|
}
|
||||||
|
c := getCertConfig(appId)
|
||||||
|
if c != nil {
|
||||||
|
return nil, fmt.Errorf("appId %s already exists", appId)
|
||||||
|
}
|
||||||
|
mchCertSN, err := getMchCertSN(mchCertPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get mchCertSN error: %v", err)
|
||||||
|
}
|
||||||
|
rootCertSN, err := getRootCertSN(rootCertPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get rootCertSN error: %v", err)
|
||||||
|
}
|
||||||
|
publicKey, err := getPublicKey(PublicKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get publicKey error: %v", err)
|
||||||
|
}
|
||||||
|
c = &CertConfig{
|
||||||
|
MchCertSN: mchCertSN,
|
||||||
|
RootCertSN: rootCertSN,
|
||||||
|
PublicKey: publicKey,
|
||||||
|
}
|
||||||
|
setCertConfig(appId, c)
|
||||||
|
return c, nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCertSN(t *testing.T) {
|
||||||
|
ms, err := getMchCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
//78a57140055b8e7853b1576bcf763361
|
||||||
|
//78a57140055b8e7853b1576bcf763361
|
||||||
|
t.Logf("merchantCertSN:%s", ms)
|
||||||
|
}
|
||||||
|
rs, err := getRootCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
//687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6
|
||||||
|
//687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6
|
||||||
|
t.Logf("alipayRootCertSN:%s", rs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMchCertSN(t *testing.T) {
|
||||||
|
s, err := getMchCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
//78a57140055b8e7853b1576bcf763361
|
||||||
|
t.Log(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootCertSN(t *testing.T) {
|
||||||
|
s, err := getRootCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
t.Log(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKey(t *testing.T) {
|
||||||
|
s, err := getPublicKey("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayCertPublicKey_RSA2.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
//78a57140055b8e7853b1576bcf763361
|
||||||
|
t.Log(s)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// md5Hash 计算 MD5 哈希值
|
||||||
|
func md5Hash(s string) string {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// hex2dec 将字节数组转换为十六进制字符串
|
||||||
|
func hex2dec(hex string) string {
|
||||||
|
var dec int
|
||||||
|
fmt.Sscanf(hex, "%x", &dec)
|
||||||
|
return fmt.Sprintf("%d", dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCert(certData []byte) (*x509.Certificate, error) {
|
||||||
|
block, _ := pem.Decode(certData)
|
||||||
|
if block == nil {
|
||||||
|
log.Fatal("Failed to parse PEM block containing the cert")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getMchCertSN 提取证书序列号
|
||||||
|
func getMchCertSN(certPath string) (string, error) {
|
||||||
|
certData, err := ioutil.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read the cert file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := getCert(certData)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5Hash(cert.Issuer.ToRDNSequence().String() + cert.SerialNumber.String()), nil
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从证书文件中提取公钥
|
||||||
|
func getPublicKey(certPath string) (string, error) {
|
||||||
|
// 读取证书文件内容
|
||||||
|
certData, err := ioutil.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read certificate file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码 PEM 编码的证书
|
||||||
|
block, _ := pem.Decode(certData)
|
||||||
|
if block == nil {
|
||||||
|
return "", fmt.Errorf("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析证书
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取公钥
|
||||||
|
pubKey := cert.PublicKey
|
||||||
|
|
||||||
|
// 转换公钥为 PEM 格式
|
||||||
|
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to marshal public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 PEM 块
|
||||||
|
pemBlock := &pem.Block{
|
||||||
|
Type: "PUBLIC KEY",
|
||||||
|
Bytes: pubKeyBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编码为 PEM 格式字符串
|
||||||
|
pemBytes := pem.EncodeToMemory(pemBlock)
|
||||||
|
publicKey := strings.TrimSpace(string(pemBytes))
|
||||||
|
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getRootCertSN 计算证书的序列号并返回最终字符串
|
||||||
|
func getRootCertSN(certPath string) (string, error) {
|
||||||
|
certData, err := ioutil.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read certificate file: %v", err)
|
||||||
|
}
|
||||||
|
var sn string
|
||||||
|
blocks := strings.Split(string(certData), "-----END CERTIFICATE-----")
|
||||||
|
for _, blockStr := range blocks[:len(blocks)-1] {
|
||||||
|
cert, err := getCert([]byte(strings.TrimSpace(blockStr) + "\n-----END CERTIFICATE-----"))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
serialNumber := cert.SerialNumber.String()
|
||||||
|
if strings.HasPrefix(serialNumber, "0x") {
|
||||||
|
serialNumber = hex2dec(serialNumber[2:])
|
||||||
|
}
|
||||||
|
if cert.SignatureAlgorithm == x509.SHA1WithRSA || cert.SignatureAlgorithm == x509.SHA256WithRSA {
|
||||||
|
hash := md5Hash(cert.Issuer.ToRDNSequence().String() + serialNumber)
|
||||||
|
if sn == "" {
|
||||||
|
sn = hash
|
||||||
|
} else {
|
||||||
|
sn += "_" + hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sn, nil
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gitea.cdlsxd.cn/BaseSystem/plugin/alipay"
|
||||||
|
"gitea.cdlsxd.cn/BaseSystem/plugin/utils"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UrlValues(prk string, req alipay.Req) (url.Values, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var strToBeSigned strings.Builder
|
||||||
|
uv := url.Values{}
|
||||||
|
kvRows := utils.SortStructJsonTag(req)
|
||||||
|
for _, kv := range kvRows {
|
||||||
|
if kv.Key == "sign" || kv.Value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uv.Set(kv.Key, kv.Value)
|
||||||
|
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||||
|
}
|
||||||
|
s := strings.TrimRight(strToBeSigned.String(), "&")
|
||||||
|
sign, err := Sign(s, []byte(utils.NewPrivate().Build(prk)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uv.Set("sign", sign)
|
||||||
|
|
||||||
|
return uv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sign(data string, privateKeyPEM []byte) (string, error) {
|
||||||
|
block, _ := pem.Decode(privateKeyPEM)
|
||||||
|
if block == nil {
|
||||||
|
return "", errors.New("failed to parse PEM block containing the private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
privyKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse DER encoded private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed := sha256.Sum256([]byte(data))
|
||||||
|
signature, err := rsa.SignPKCS1v15(rand.Reader, privyKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to sign: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(signature), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Verify(n *alipay.Notify, publicKeyPEM string) (bool, error) {
|
||||||
|
var strToBeSigned strings.Builder
|
||||||
|
uv := url.Values{}
|
||||||
|
kvRows := utils.SortStructJsonTag(n)
|
||||||
|
for _, kv := range kvRows {
|
||||||
|
if kv.Key == "sign" || kv.Key == "sign_type" || kv.Value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uv.Set(kv.Key, kv.Value)
|
||||||
|
strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
|
||||||
|
}
|
||||||
|
s := strings.TrimRight(strToBeSigned.String(), "&")
|
||||||
|
return check(s, n.Sign, []byte(publicKeyPEM))
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(data, signature string, publicKeyPEM []byte) (bool, error) {
|
||||||
|
block, _ := pem.Decode(publicKeyPEM)
|
||||||
|
if block == nil {
|
||||||
|
return false, fmt.Errorf("failed to parse DER encoded public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to parse DER encoded public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rsaPubKey, ok := pubKey.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("failed to parse DER encoded public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed := sha256.Sum256([]byte(data))
|
||||||
|
sig, err := base64.StdEncoding.DecodeString(signature)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to decode signature: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hashed[:], sig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("signature verification error:", err)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package vo
|
||||||
|
|
||||||
|
type Code string
|
||||||
|
|
||||||
|
const CodeSuccess Code = "10000"
|
||||||
|
|
||||||
|
func (c Code) IsSuccess() bool {
|
||||||
|
return c == CodeSuccess
|
||||||
|
}
|
10
go.mod
10
go.mod
|
@ -3,7 +3,7 @@ module gitea.cdlsxd.cn/BaseSystem/plugin
|
||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.4
|
github.com/carlmjohnson/requests v0.24.2
|
||||||
github.com/go-playground/validator/v10 v10.22.0
|
github.com/go-playground/validator/v10 v10.22.0
|
||||||
github.com/hashicorp/go-plugin v1.6.1
|
github.com/hashicorp/go-plugin v1.6.1
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
@ -24,9 +24,9 @@ require (
|
||||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
||||||
github.com/oklog/run v1.0.0 // indirect
|
github.com/oklog/run v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.21.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/net v0.22.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||||
)
|
)
|
||||||
|
|
20
go.sum
20
go.sum
|
@ -1,9 +1,9 @@
|
||||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||||
|
github.com/carlmjohnson/requests v0.24.2 h1:JDakhAmTIKL/qL/1P7Kkc2INGBJIkIFP6xUeUmPzLso=
|
||||||
|
github.com/carlmjohnson/requests v0.24.2/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
@ -46,16 +46,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||||
|
|
Loading…
Reference in New Issue