Files
xk-hy-transit-go/internal/fileauth/ver233.go
2026-05-28 16:39:51 +08:00

183 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package fileauth
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
// ReturnBodyTemplate 监管 Ver2.0 §2.3.3 文档示例中的 returnBody 固定模板。
const ReturnBodyTemplate = `{"name":$(fname),"size":$(fsize),"w":$(imageInfo.width),"h":$(imageInfo.height),"hash":$(etag)}`
// DocExamplePolicyJSON 文档教学样例 putPolicy固定向量单测用
const DocExamplePolicyJSON = `{"scope":"my-bucket:sunflower.jpg","deadline":1451491200,"returnBody":"{\"name\":$(fname),\"size\":$(fsize),\"w\":$(imageInfo.width),\"h\":$(imageInfo.height),\"hash\":$(etag)}"}`
// DocExampleEncodedPutPolicy 文档 §2.3.3 步骤 2 给出的 encodedPutPolicy。
const DocExampleEncodedPutPolicy = "eyJzY29wZSI6Im15LWJ1Y2tldDpzdW5mbG93ZXIuanBnIiwiZGVhZGxpbmUiOjE0NTE0OTEyMDAsInJldHVybkJvZHkiOiJ7XCJuYW1lXCI6JChmbmFtZSksXCJzaXplXCI6JChmc2l6ZSksXCJ3XCI6JChpbWFnZUluZm8ud2lkdGgpLFwiaFwiOiQoaW1hZ2VJbmZvLmhlaWdodCksXCJoYXNoXCI6JChldGFnKX0ifQ=="
// DocExampleSignHex 文档样例 HMAC 原始字节十六进制SecretKey=MY_SECRET_KEY
const DocExampleSignHex = "c10e287f2b1e7f547b20a9ebce2aada26ab20ef2"
// DocExampleEncodedSign 文档样例 encodedSign。
const DocExampleEncodedSign = "wQ4ofysef1R7IKnrziqtomqyDvI="
// DocExampleUploadToken 文档样例完整 uploadTokenAccessKey=MY_ACCESS_KEY
const DocExampleUploadToken = "MY_ACCESS_KEY:wQ4ofysef1R7IKnrziqtomqyDvI=:" + DocExampleEncodedPutPolicy
// MarshalPutPolicyDoc 文档 profilescope 可含文件名 + returnBody。
func MarshalPutPolicyDoc(scope string, deadlineUnix int64) ([]byte, error) {
scope = strings.TrimSpace(scope)
if scope == "" {
return nil, errors.New("empty scope")
}
if deadlineUnix <= 0 {
return nil, errors.New("invalid deadline")
}
scopeJSON, err := json.Marshal(scope)
if err != nil {
return nil, err
}
rbJSON, err := json.Marshal(ReturnBodyTemplate)
if err != nil {
return nil, err
}
return []byte(`{"scope":` + string(scopeJSON) + `,"deadline":` + strconv.FormatInt(deadlineUnix, 10) + `,"returnBody":` + string(rbJSON) + `}`), nil
}
// MarshalPutPolicyUpload 生产 profilescope=仅 bucket无 returnBody。
func MarshalPutPolicyUpload(bucket string, deadlineUnix int64) ([]byte, error) {
bucket = strings.TrimSpace(bucket)
if bucket == "" {
return nil, errors.New("empty bucket")
}
if deadlineUnix <= 0 {
return nil, errors.New("invalid deadline")
}
scopeJSON, err := json.Marshal(bucket)
if err != nil {
return nil, err
}
return []byte(`{"scope":` + string(scopeJSON) + `,"deadline":` + strconv.FormatInt(deadlineUnix, 10) + `}`), nil
}
// MarshalPutPolicyVer233 兼容旧名;等同 doc profile。
func MarshalPutPolicyVer233(scope string, deadlineUnix int64) ([]byte, error) {
return MarshalPutPolicyDoc(scope, deadlineUnix)
}
// signPolicyVer233 §2.3.3HMAC-SHA1(secret, UTF-8(encodedPutPolicy)) → urlSafeB64。
func signPolicyVer233(secret, encodedPutPolicy string) (string, error) {
mac := hmac.New(sha1.New, []byte(secret))
if _, err := mac.Write([]byte(encodedPutPolicy)); err != nil {
return "", fmt.Errorf("hmac: %w", err)
}
return encodeURLSafe(mac.Sum(nil)), nil
}
// urlSafeBase64Ver233 供单测;使用当前 B64 模式doc 单测应 SetB64Mode(padded))。
func urlSafeBase64Ver233(data []byte) string {
return encodeURLSafe(data)
}
// BuildUploadTokenFromPolicy 按 §2.3.3 五步由 putPolicy JSON 生成 token。
func BuildUploadTokenFromPolicy(accessKey, secret string, putPolicyJSON []byte, algorithm string) (UploadTokenParts, error) {
accessKey = strings.TrimSpace(accessKey)
secret = strings.TrimSpace(secret)
if accessKey == "" || secret == "" {
return UploadTokenParts{}, errors.New("empty key or secret")
}
if len(putPolicyJSON) == 0 {
return UploadTokenParts{}, errors.New("empty putPolicy")
}
if algorithm == "" {
algorithm = "ver233"
}
encodedPutPolicy := encodeURLSafe(putPolicyJSON)
sign, err := signPolicyVer233(secret, encodedPutPolicy)
if err != nil {
return UploadTokenParts{}, err
}
token := accessKey + ":" + sign + ":" + encodedPutPolicy
var policy struct {
Deadline int64 `json:"deadline"`
}
_ = json.Unmarshal(putPolicyJSON, &policy)
return UploadTokenParts{
AccessKey: accessKey,
PolicyJSON: string(putPolicyJSON),
EncodedPutPolicy: encodedPutPolicy,
SignInput: encodedPutPolicy,
EncodedSign: sign,
UploadToken: token,
DeadlineUnix: policy.Deadline,
Algorithm: algorithm,
}, nil
}
// BuildUploadTokenPartsForUpload 生产路径scope=仅 bucket§2.3.3 验签。
func BuildUploadTokenPartsForUpload(accessKey, secret, bucket string, deadlineUnix int64) (UploadTokenParts, error) {
accessKey = strings.TrimSpace(accessKey)
secret = strings.TrimSpace(secret)
bucket = strings.TrimSpace(bucket)
if accessKey == "" || secret == "" {
return UploadTokenParts{}, errors.New("empty key or secret")
}
if bucket == "" {
return UploadTokenParts{}, errors.New("empty bucket")
}
if deadlineUnix <= 0 {
return UploadTokenParts{}, errors.New("invalid deadline")
}
jsonPolicy, err := MarshalPutPolicyUpload(bucket, deadlineUnix)
if err != nil {
return UploadTokenParts{}, err
}
parts, err := BuildUploadTokenFromPolicy(accessKey, secret, jsonPolicy, "ver233-upload")
if err != nil {
return UploadTokenParts{}, err
}
now := time.Now().Unix()
expires := deadlineUnix - now
if expires < 0 {
expires = 0
}
parts.ExpiresSec = expires
return parts, nil
}
// BuildUploadTokenPartsAtFile filename 不参与 scopeHTTP 为 PDF 二进制体,非 multipart
func BuildUploadTokenPartsAtFile(accessKey, secret, bucket, filename string, deadlineUnix int64) (UploadTokenParts, error) {
_ = filename
return BuildUploadTokenPartsForUpload(accessKey, secret, bucket, deadlineUnix)
}
// CreateUploadTokenAtFile 生产上传凭证。
func CreateUploadTokenAtFile(accessKey, secret, bucket, filename string, deadlineUnix int64) (string, error) {
parts, err := BuildUploadTokenPartsAtFile(accessKey, secret, bucket, filename, deadlineUnix)
if err != nil {
return "", err
}
return parts.UploadToken, nil
}
// BuildUploadTokenPartsDoc 文档黄金样例(固定 scope/deadline
func BuildUploadTokenPartsDoc(accessKey, secret string) (UploadTokenParts, error) {
return BuildUploadTokenFromPolicy(accessKey, secret, []byte(DocExamplePolicyJSON), "ver233-doc")
}
// HMACSignHex 返回 HMAC-SHA1 原始字节的十六进制(调试用)。
func HMACSignHex(secret, encodedPutPolicy string) string {
mac := hmac.New(sha1.New, []byte(secret))
_, _ = mac.Write([]byte(encodedPutPolicy))
return hex.EncodeToString(mac.Sum(nil))
}