package sign import ( "crypto/hmac" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "encoding/hex" "fmt" "net/url" "strings" "time" "git.misaka.ren/M1saka/zhanlu_proxy/internal/auth" ) type Signer struct { PublicKey *rsa.PublicKey Now func() time.Time Nonce func() string Encryptor func(string) (string, error) } func (s Signer) BuildOpURL(path string, creds auth.Credentials, baseURL string, method string) (string, error) { if err := creds.Validate(); err != nil { return "", err } if method == "" { method = "POST" } now := time.Now if s.Now != nil { now = s.Now } nonce := randomUUID if s.Nonce != nil { nonce = s.Nonce } encryptor := s.Encryptor if encryptor == nil { encryptor = func(text string) (string, error) { return auth.EncryptAuthorization(s.PublicKey, text) } } base64Auth, err := encryptor(fmt.Sprintf("%d:%s", now().UnixMilli(), creds.Token)) if err != nil { return "", err } timestamp := now().Format("2006-01-02T15:04:05Z") query := "AccessKey=" + creds.AccessKey + "&SignatureMethod=HmacSHA1" + "&SignatureNonce=" + nonce() + "&SignatureVersion=V2.0" + "&Timestamp=" + timestamp + "&Version=2016-12-05" + "&authorization=" + url.QueryEscape(base64Auth) canonicalQuery := strings.ReplaceAll(query, ":", "%3A") queryHash := sha256.Sum256([]byte(canonicalQuery)) stringToSign := strings.ToUpper(method) + "\n" + strings.ReplaceAll(path, "/", "%2F") + "\n" + hex.EncodeToString(queryHash[:]) mac := hmac.New(sha1.New, []byte("BC_SIGNATURE&"+creds.SecretKey)) mac.Write([]byte(stringToSign)) signature := hex.EncodeToString(mac.Sum(nil)) return strings.TrimRight(baseURL, "/") + path + "?" + query + "&Signature=" + signature, nil } func randomUUID() string { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return fmt.Sprintf("%d", time.Now().UnixNano()) } b[6] = (b[6] & 0x0f) | 0x40 b[8] = (b[8] & 0x3f) | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) }