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 文档样例完整 uploadToken(AccessKey=MY_ACCESS_KEY)。 const DocExampleUploadToken = "MY_ACCESS_KEY:wQ4ofysef1R7IKnrziqtomqyDvI=:" + DocExampleEncodedPutPolicy // MarshalPutPolicyDoc 文档 profile:scope 可含文件名 + 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 生产 profile:scope=仅 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.3:HMAC-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 不参与 scope(HTTP 为 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)) }