|
@@ -0,0 +1,475 @@
|
|
|
|
|
+package utils
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "bytes"
|
|
|
|
|
+ "crypto"
|
|
|
|
|
+ "crypto/rand"
|
|
|
|
|
+ "crypto/rsa"
|
|
|
|
|
+ "crypto/sha256"
|
|
|
|
|
+ "crypto/x509"
|
|
|
|
|
+ "encoding/base64"
|
|
|
|
|
+ "encoding/json"
|
|
|
|
|
+ "encoding/pem"
|
|
|
|
|
+ "errors"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "io"
|
|
|
|
|
+ "net/http"
|
|
|
|
|
+ "net/url"
|
|
|
|
|
+ "os"
|
|
|
|
|
+ "strconv"
|
|
|
|
|
+ "strings"
|
|
|
|
|
+ "time"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+type WxPayClient struct {
|
|
|
|
|
+ MchID string
|
|
|
|
|
+ SerialNo string
|
|
|
|
|
+ NotifyURL string
|
|
|
|
|
+ JSAPIAppID string
|
|
|
|
|
+ H5AppID string
|
|
|
|
|
+ AppAppID string
|
|
|
|
|
+ PrivateKey *rsa.PrivateKey
|
|
|
|
|
+ HTTPClient *http.Client
|
|
|
|
|
+ APIBase string
|
|
|
|
|
+ Enabled bool
|
|
|
|
|
+ UserAgent string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxPayAmount struct {
|
|
|
|
|
+ Total int `json:"total"`
|
|
|
|
|
+ Currency string `json:"currency,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxPayPayer struct {
|
|
|
|
|
+ OpenID string `json:"openid,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxPayH5Info struct {
|
|
|
|
|
+ Type string `json:"type"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxPaySceneInfo struct {
|
|
|
|
|
+ PayerClientIP string `json:"payer_client_ip,omitempty"`
|
|
|
|
|
+ H5Info *WxPayH5Info `json:"h5_info,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type wxPayTxnRequest struct {
|
|
|
|
|
+ AppID string `json:"appid"`
|
|
|
|
|
+ MchID string `json:"mchid"`
|
|
|
|
|
+ Description string `json:"description"`
|
|
|
|
|
+ OutTradeNo string `json:"out_trade_no"`
|
|
|
|
|
+ NotifyURL string `json:"notify_url"`
|
|
|
|
|
+ Attach string `json:"attach,omitempty"`
|
|
|
|
|
+ Amount WxPayAmount `json:"amount"`
|
|
|
|
|
+ Payer *WxPayPayer `json:"payer,omitempty"`
|
|
|
|
|
+ SceneInfo *WxPaySceneInfo `json:"scene_info,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type wxPayTxnResp struct {
|
|
|
|
|
+ PrepayID string `json:"prepay_id"`
|
|
|
|
|
+ H5URL string `json:"h5_url"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxMiniProgramOrderReq struct {
|
|
|
|
|
+ Description string
|
|
|
|
|
+ OutTradeNo string
|
|
|
|
|
+ Total int
|
|
|
|
|
+ Currency string
|
|
|
|
|
+ OpenID string
|
|
|
|
|
+ NotifyURL string
|
|
|
|
|
+ Attach string
|
|
|
|
|
+ AppID string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxMiniProgramPayResult struct {
|
|
|
|
|
+ AppID string `json:"appId"`
|
|
|
|
|
+ TimeStamp string `json:"timeStamp"`
|
|
|
|
|
+ NonceStr string `json:"nonceStr"`
|
|
|
|
|
+ Package string `json:"package"`
|
|
|
|
|
+ SignType string `json:"signType"`
|
|
|
|
|
+ PaySign string `json:"paySign"`
|
|
|
|
|
+ PrepayID string `json:"prepayId"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxH5OrderReq struct {
|
|
|
|
|
+ Description string
|
|
|
|
|
+ OutTradeNo string
|
|
|
|
|
+ Total int
|
|
|
|
|
+ Currency string
|
|
|
|
|
+ NotifyURL string
|
|
|
|
|
+ Attach string
|
|
|
|
|
+ PayerClientIP string
|
|
|
|
|
+ AppID string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxH5PayResult struct {
|
|
|
|
|
+ H5URL string `json:"h5Url"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxAppOrderReq struct {
|
|
|
|
|
+ Description string
|
|
|
|
|
+ OutTradeNo string
|
|
|
|
|
+ Total int
|
|
|
|
|
+ Currency string
|
|
|
|
|
+ NotifyURL string
|
|
|
|
|
+ Attach string
|
|
|
|
|
+ AppID string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type WxAppPayResult struct {
|
|
|
|
|
+ AppID string `json:"appid"`
|
|
|
|
|
+ PartnerID string `json:"partnerid"`
|
|
|
|
|
+ PrepayID string `json:"prepayid"`
|
|
|
|
|
+ Package string `json:"package"`
|
|
|
|
|
+ NonceStr string `json:"noncestr"`
|
|
|
|
|
+ TimeStamp string `json:"timestamp"`
|
|
|
|
|
+ Sign string `json:"sign"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+var wxPayClient *WxPayClient
|
|
|
|
|
+
|
|
|
|
|
+func InitWxPay() error {
|
|
|
|
|
+ enabled := redisBool("wxpay_enable", false)
|
|
|
|
|
+ if !enabled {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mchID := redisString("wxpay_mch_id", "")
|
|
|
|
|
+ serialNo := redisString("wxpay_serial_no", "")
|
|
|
|
|
+ privateKeyPath := redisString("wxpay_private_key_path", "")
|
|
|
|
|
+ notifyURL := redisString("wxpay_notify_url", "")
|
|
|
|
|
+ jsapiAppID := redisString("wxpay_jsapi_appid", "")
|
|
|
|
|
+ h5AppID := redisString("wxpay_h5_appid", "")
|
|
|
|
|
+ appAppID := redisString("wxpay_app_appid", "")
|
|
|
|
|
+ apiBase := redisString("wxpay_api_base", "https://api.mch.weixin.qq.com")
|
|
|
|
|
+
|
|
|
|
|
+ if mchID == "" || serialNo == "" || privateKeyPath == "" || notifyURL == "" {
|
|
|
|
|
+ return errors.New("wxpay config missing: wxpay_mch_id/wxpay_serial_no/wxpay_private_key_path/wxpay_notify_url are required")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ privateKey, err := loadRSAPrivateKey(privateKeyPath)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ wxPayClient = &WxPayClient{
|
|
|
|
|
+ MchID: mchID,
|
|
|
|
|
+ SerialNo: serialNo,
|
|
|
|
|
+ NotifyURL: notifyURL,
|
|
|
|
|
+ JSAPIAppID: jsapiAppID,
|
|
|
|
|
+ H5AppID: h5AppID,
|
|
|
|
|
+ AppAppID: appAppID,
|
|
|
|
|
+ PrivateKey: privateKey,
|
|
|
|
|
+ HTTPClient: &http.Client{Timeout: 15 * time.Second},
|
|
|
|
|
+ APIBase: strings.TrimRight(apiBase, "/"),
|
|
|
|
|
+ Enabled: true,
|
|
|
|
|
+ UserAgent: "think-go-wxpay/1.0",
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func GetWxPayClient() (*WxPayClient, error) {
|
|
|
|
|
+ if wxPayClient == nil || !wxPayClient.Enabled {
|
|
|
|
|
+ return nil, errors.New("wxpay not initialized, set wxpay_enable=true and call InitWxPay")
|
|
|
|
|
+ }
|
|
|
|
|
+ return wxPayClient, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func CreateWxMiniProgramPay(req WxMiniProgramOrderReq) (*WxMiniProgramPayResult, error) {
|
|
|
|
|
+ client, err := GetWxPayClient()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ return client.CreateMiniProgramPay(req)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func CreateWxH5Pay(req WxH5OrderReq) (*WxH5PayResult, error) {
|
|
|
|
|
+ client, err := GetWxPayClient()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ return client.CreateH5Pay(req)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func CreateWxAppPay(req WxAppOrderReq) (*WxAppPayResult, error) {
|
|
|
|
|
+ client, err := GetWxPayClient()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ return client.CreateAppPay(req)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *WxPayClient) CreateMiniProgramPay(req WxMiniProgramOrderReq) (*WxMiniProgramPayResult, error) {
|
|
|
|
|
+ appID := req.AppID
|
|
|
|
|
+ if appID == "" {
|
|
|
|
|
+ appID = c.JSAPIAppID
|
|
|
|
|
+ }
|
|
|
|
|
+ if appID == "" {
|
|
|
|
|
+ return nil, errors.New("jsapi appid is required")
|
|
|
|
|
+ }
|
|
|
|
|
+ if req.OpenID == "" {
|
|
|
|
|
+ return nil, errors.New("openid is required")
|
|
|
|
|
+ }
|
|
|
|
|
+ if req.OutTradeNo == "" || req.Description == "" || req.Total <= 0 {
|
|
|
|
|
+ return nil, errors.New("description/outTradeNo/total are required")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ requestBody := wxPayTxnRequest{
|
|
|
|
|
+ AppID: appID,
|
|
|
|
|
+ MchID: c.MchID,
|
|
|
|
|
+ Description: req.Description,
|
|
|
|
|
+ OutTradeNo: req.OutTradeNo,
|
|
|
|
|
+ NotifyURL: firstNotEmpty(req.NotifyURL, c.NotifyURL),
|
|
|
|
|
+ Attach: req.Attach,
|
|
|
|
|
+ Amount: WxPayAmount{
|
|
|
|
|
+ Total: req.Total,
|
|
|
|
|
+ Currency: firstNotEmpty(req.Currency, "CNY"),
|
|
|
|
|
+ },
|
|
|
|
|
+ Payer: &WxPayPayer{OpenID: req.OpenID},
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var resp wxPayTxnResp
|
|
|
|
|
+ if err := c.postJSON("/v3/pay/transactions/jsapi", requestBody, &resp); err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ if resp.PrepayID == "" {
|
|
|
|
|
+ return nil, errors.New("wxpay jsapi response missing prepay_id")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
|
+ nonceStr := randomString(32)
|
|
|
|
|
+ pkg := "prepay_id=" + resp.PrepayID
|
|
|
|
|
+ signPayload := appID + "\n" + timeStamp + "\n" + nonceStr + "\n" + pkg + "\n"
|
|
|
|
|
+ paySign, err := c.signBase64(signPayload)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return &WxMiniProgramPayResult{
|
|
|
|
|
+ AppID: appID,
|
|
|
|
|
+ TimeStamp: timeStamp,
|
|
|
|
|
+ NonceStr: nonceStr,
|
|
|
|
|
+ Package: pkg,
|
|
|
|
|
+ SignType: "RSA",
|
|
|
|
|
+ PaySign: paySign,
|
|
|
|
|
+ PrepayID: resp.PrepayID,
|
|
|
|
|
+ }, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *WxPayClient) CreateH5Pay(req WxH5OrderReq) (*WxH5PayResult, error) {
|
|
|
|
|
+ appID := req.AppID
|
|
|
|
|
+ if appID == "" {
|
|
|
|
|
+ appID = c.H5AppID
|
|
|
|
|
+ }
|
|
|
|
|
+ if appID == "" {
|
|
|
|
|
+ return nil, errors.New("h5 appid is required")
|
|
|
|
|
+ }
|
|
|
|
|
+ if req.OutTradeNo == "" || req.Description == "" || req.Total <= 0 {
|
|
|
|
|
+ return nil, errors.New("description/outTradeNo/total are required")
|
|
|
|
|
+ }
|
|
|
|
|
+ if req.PayerClientIP == "" {
|
|
|
|
|
+ return nil, errors.New("payer_client_ip is required for h5 pay")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ requestBody := wxPayTxnRequest{
|
|
|
|
|
+ AppID: appID,
|
|
|
|
|
+ MchID: c.MchID,
|
|
|
|
|
+ Description: req.Description,
|
|
|
|
|
+ OutTradeNo: req.OutTradeNo,
|
|
|
|
|
+ NotifyURL: firstNotEmpty(req.NotifyURL, c.NotifyURL),
|
|
|
|
|
+ Attach: req.Attach,
|
|
|
|
|
+ Amount: WxPayAmount{
|
|
|
|
|
+ Total: req.Total,
|
|
|
|
|
+ Currency: firstNotEmpty(req.Currency, "CNY"),
|
|
|
|
|
+ },
|
|
|
|
|
+ SceneInfo: &WxPaySceneInfo{
|
|
|
|
|
+ PayerClientIP: req.PayerClientIP,
|
|
|
|
|
+ H5Info: &WxPayH5Info{Type: "Wap"},
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var resp wxPayTxnResp
|
|
|
|
|
+ if err := c.postJSON("/v3/pay/transactions/h5", requestBody, &resp); err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ if resp.H5URL == "" {
|
|
|
|
|
+ return nil, errors.New("wxpay h5 response missing h5_url")
|
|
|
|
|
+ }
|
|
|
|
|
+ return &WxH5PayResult{H5URL: resp.H5URL}, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *WxPayClient) CreateAppPay(req WxAppOrderReq) (*WxAppPayResult, error) {
|
|
|
|
|
+ appID := req.AppID
|
|
|
|
|
+ if appID == "" {
|
|
|
|
|
+ appID = c.AppAppID
|
|
|
|
|
+ }
|
|
|
|
|
+ if appID == "" {
|
|
|
|
|
+ return nil, errors.New("app appid is required")
|
|
|
|
|
+ }
|
|
|
|
|
+ if req.OutTradeNo == "" || req.Description == "" || req.Total <= 0 {
|
|
|
|
|
+ return nil, errors.New("description/outTradeNo/total are required")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ requestBody := wxPayTxnRequest{
|
|
|
|
|
+ AppID: appID,
|
|
|
|
|
+ MchID: c.MchID,
|
|
|
|
|
+ Description: req.Description,
|
|
|
|
|
+ OutTradeNo: req.OutTradeNo,
|
|
|
|
|
+ NotifyURL: firstNotEmpty(req.NotifyURL, c.NotifyURL),
|
|
|
|
|
+ Attach: req.Attach,
|
|
|
|
|
+ Amount: WxPayAmount{
|
|
|
|
|
+ Total: req.Total,
|
|
|
|
|
+ Currency: firstNotEmpty(req.Currency, "CNY"),
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var resp wxPayTxnResp
|
|
|
|
|
+ if err := c.postJSON("/v3/pay/transactions/app", requestBody, &resp); err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ if resp.PrepayID == "" {
|
|
|
|
|
+ return nil, errors.New("wxpay app response missing prepay_id")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
|
+ nonceStr := randomString(32)
|
|
|
|
|
+ signPayload := appID + "\n" + timeStamp + "\n" + nonceStr + "\n" + resp.PrepayID + "\n"
|
|
|
|
|
+ sign, err := c.signBase64(signPayload)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return &WxAppPayResult{
|
|
|
|
|
+ AppID: appID,
|
|
|
|
|
+ PartnerID: c.MchID,
|
|
|
|
|
+ PrepayID: resp.PrepayID,
|
|
|
|
|
+ Package: "Sign=WXPay",
|
|
|
|
|
+ NonceStr: nonceStr,
|
|
|
|
|
+ TimeStamp: timeStamp,
|
|
|
|
|
+ Sign: sign,
|
|
|
|
|
+ }, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *WxPayClient) postJSON(path string, payload interface{}, out interface{}) error {
|
|
|
|
|
+ bodyBytes, err := json.Marshal(payload)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ fullURL := c.APIBase + path
|
|
|
|
|
+
|
|
|
|
|
+ auth, err := c.buildAuthorization("POST", fullURL, string(bodyBytes))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(bodyBytes))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
+ req.Header.Set("Accept", "application/json")
|
|
|
|
|
+ req.Header.Set("Authorization", auth)
|
|
|
|
|
+ req.Header.Set("User-Agent", c.UserAgent)
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.HTTPClient.Do(req)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ defer resp.Body.Close()
|
|
|
|
|
+
|
|
|
|
|
+ respBody, err := io.ReadAll(resp.Body)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
|
|
+ return fmt.Errorf("wxpay request failed: status=%d body=%s", resp.StatusCode, string(respBody))
|
|
|
|
|
+ }
|
|
|
|
|
+ if out == nil {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(respBody, out); err != nil {
|
|
|
|
|
+ return fmt.Errorf("parse wxpay response failed: %w, body=%s", err, string(respBody))
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *WxPayClient) buildAuthorization(method, fullURL, body string) (string, error) {
|
|
|
|
|
+ u, err := url.Parse(fullURL)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ canonicalURL := u.Path
|
|
|
|
|
+ if u.RawQuery != "" {
|
|
|
|
|
+ canonicalURL += "?" + u.RawQuery
|
|
|
|
|
+ }
|
|
|
|
|
+ nonce := randomString(32)
|
|
|
|
|
+ timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
|
+ message := method + "\n" + canonicalURL + "\n" + timestamp + "\n" + nonce + "\n" + body + "\n"
|
|
|
|
|
+
|
|
|
|
|
+ signature, err := c.signBase64(message)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ token := fmt.Sprintf(`mchid="%s",nonce_str="%s",timestamp="%s",serial_no="%s",signature="%s"`,
|
|
|
|
|
+ c.MchID, nonce, timestamp, c.SerialNo, signature,
|
|
|
|
|
+ )
|
|
|
|
|
+ return "WECHATPAY2-SHA256-RSA2048 " + token, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *WxPayClient) signBase64(message string) (string, error) {
|
|
|
|
|
+ sum := sha256.Sum256([]byte(message))
|
|
|
|
|
+ sign, err := rsa.SignPKCS1v15(rand.Reader, c.PrivateKey, crypto.SHA256, sum[:])
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ return base64.StdEncoding.EncodeToString(sign), nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func loadRSAPrivateKey(path string) (*rsa.PrivateKey, error) {
|
|
|
|
|
+ raw, err := os.ReadFile(path)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ block, _ := pem.Decode(raw)
|
|
|
|
|
+ if block == nil {
|
|
|
|
|
+ return nil, errors.New("invalid private key pem")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
|
|
|
|
+ if rsaKey, ok := key.(*rsa.PrivateKey); ok {
|
|
|
|
|
+ return rsaKey, nil
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
|
|
|
|
+ return key, nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil, errors.New("unsupported private key format")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func randomString(n int) string {
|
|
|
|
|
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
|
+ if n <= 0 {
|
|
|
|
|
+ return ""
|
|
|
|
|
+ }
|
|
|
|
|
+ b := make([]byte, n)
|
|
|
|
|
+ rb := make([]byte, n)
|
|
|
|
|
+ if _, err := rand.Read(rb); err != nil {
|
|
|
|
|
+ return strconv.FormatInt(time.Now().UnixNano(), 10)
|
|
|
|
|
+ }
|
|
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
|
|
+ b[i] = chars[int(rb[i])%len(chars)]
|
|
|
|
|
+ }
|
|
|
|
|
+ return string(b)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func firstNotEmpty(values ...string) string {
|
|
|
|
|
+ for _, v := range values {
|
|
|
|
|
+ if strings.TrimSpace(v) != "" {
|
|
|
|
|
+ return strings.TrimSpace(v)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ""
|
|
|
|
|
+}
|