package oss import ( "crypto" "crypto/md5" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "io" "net/http" "net/url" "strings" "time" ) const ( pubKeyURLHeader = "X-Oss-Pub-Key-Url" authorizationHeader = "Authorization" ) type VerifyInput struct { Path string RawQuery string Body []byte Authorization string PubKeyURLHdr string } func VerifyCallback(in VerifyInput) error { pubKeyURLBase64 := in.PubKeyURLHdr if pubKeyURLBase64 == "" { return fmt.Errorf("missing %s", pubKeyURLHeader) } pubKeyURLBytes, err := base64.StdEncoding.DecodeString(pubKeyURLBase64) if err != nil { return fmt.Errorf("decode pub key url: %w", err) } pubKeyURL := string(pubKeyURLBytes) if !isTrustedPubKeyURL(pubKeyURL) { return fmt.Errorf("untrusted pub key url: %s", pubKeyURL) } pubKeyPEM, err := fetchPublicKey(pubKeyURL) if err != nil { return fmt.Errorf("fetch pub key: %w", err) } authBase64 := in.Authorization if authBase64 == "" { return fmt.Errorf("missing %s", authorizationHeader) } signature, err := base64.StdEncoding.DecodeString(authBase64) if err != nil { return fmt.Errorf("decode authorization: %w", err) } authString, err := buildAuthString(in.Path, in.RawQuery, in.Body) if err != nil { return err } digest := md5.Sum([]byte(authString)) block, _ := pem.Decode(pubKeyPEM) if block == nil { return fmt.Errorf("invalid pub key pem") } pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return fmt.Errorf("parse pub key: %w", err) } pubKey, ok := pubInterface.(*rsa.PublicKey) if !ok { return fmt.Errorf("pub key is not rsa") } if err := rsa.VerifyPKCS1v15(pubKey, crypto.MD5, digest[:], signature); err != nil { return fmt.Errorf("verify signature: %w", err) } return nil } func buildAuthString(path, query string, body []byte) (string, error) { decodedPath, err := url.PathUnescape(path) if err != nil { return "", fmt.Errorf("unescape path: %w", err) } if query == "" { return decodedPath + "\n" + string(body), nil } return decodedPath + "?" + query + "\n" + string(body), nil } func isTrustedPubKeyURL(raw string) bool { u, err := url.Parse(raw) if err != nil { return false } if u.Scheme != "http" && u.Scheme != "https" { return false } host := strings.ToLower(u.Hostname()) return host == "gosspublic.alicdn.com" || strings.HasSuffix(host, ".alicdn.com") } func fetchPublicKey(pubKeyURL string) ([]byte, error) { client := &http.Client{Timeout: 3 * time.Second} resp, err := client.Get(pubKeyURL) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("pub key http status %d", resp.StatusCode) } return io.ReadAll(resp.Body) }