Files
lone-services/services/analysis/internal/oss/verify.go
T
2026-10-21 13:29:03 +08:00

118 lines
2.7 KiB
Go

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)
}