package signing import ( "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/hex" "fmt" "strconv" "strings" "time" "git.apinb.com/ops/files/internal/storage" ) func SignUpload(secret string, request storage.UploadRequest) string { canonical := strings.Join([]string{ "PUT", request.FileID, strconv.FormatInt(request.ExpiresAt.Unix(), 10), strconv.FormatInt(request.ExpectedSize, 10), request.ContentType, }, "\n") return sign(secret, canonical) } func VerifyUpload(secret, signature string, request storage.UploadRequest, now time.Time) bool { return request.ExpiresAt.After(now) && verify(secret, SignUpload(secret, request), signature) } func SignAccess(secret, fileID string, expiresAt time.Time, disposition string) string { canonical := strings.Join([]string{"GET", fileID, strconv.FormatInt(expiresAt.Unix(), 10), disposition}, "\n") return sign(secret, canonical) } func VerifyAccess(secret, fileID string, expiresAt time.Time, disposition, signature string, now time.Time) bool { return expiresAt.After(now) && verify(secret, SignAccess(secret, fileID, expiresAt, disposition), signature) } func sign(secret, canonical string) string { mac := hmac.New(sha256.New, []byte(secret)) _, _ = mac.Write([]byte(canonical)) return hex.EncodeToString(mac.Sum(nil)) } func verify(secret, expected, actual string) bool { if strings.TrimSpace(secret) == "" || len(actual) != sha256.Size*2 { return false } expectedBytes, expectedErr := hex.DecodeString(expected) actualBytes, actualErr := hex.DecodeString(actual) if expectedErr != nil || actualErr != nil { return false } return subtle.ConstantTimeCompare(expectedBytes, actualBytes) == 1 } func UploadNonce(secret string, request storage.UploadRequest) (string, error) { signature := SignUpload(secret, request) if len(signature) < 16 { return "", fmt.Errorf("上传签名长度无效") } return signature[:16], nil }