1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
// LICENSE MIT
// Copyright (c) 2018, Rohan Verma <hello@rohanverma.net>
// Copyright (c) 2017, L Campbell
// forked from: https://github.com/lye/s3/
// For previous license information visit
// https://github.com/lye/s3/blob/master/LICENSE
package simples3
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"time"
)
// UploadConfig generate policies from config
// for POST requests to S3 using Signing V4.
type UploadConfig struct {
// Required
BucketName string
ObjectKey string
ContentType string
FileSize int64
// Optional
UploadURL string
Expiration time.Duration
MetaData map[string]string
}
// UploadPolicies Amazon s3 upload policies
type UploadPolicies struct {
URL string
Form map[string]string
}
// PolicyJSON is policy rule
type PolicyJSON struct {
Expiration string `json:"expiration"`
Conditions []interface{} `json:"conditions"`
}
const (
expirationTimeFormat = "2006-01-02T15:04:05Z07:00"
amzDateISO8601TimeFormat = "20060102T150405Z"
shortTimeFormat = "20060102"
algorithm = "AWS4-HMAC-SHA256"
serviceName = "s3"
defaultUploadURLFormat = "http://%s.s3.amazonaws.com/" // <bucketName>
defaultExpirationHour = 1 * time.Hour
)
// nowTime mockable time.Now()
var nowTime = func() time.Time {
return time.Now().UTC()
}
var newLine = []byte{'\n'}
// CreateUploadPolicies creates amazon s3 sigv4 compatible
// policy and signing keys with the signature returns the upload policy.
// https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html
func (s3 *S3) CreateUploadPolicies(uploadConfig UploadConfig) (UploadPolicies, error) {
nowTime := nowTime()
credential := string(s3.buildCredential(nowTime))
data, err := buildUploadSign(nowTime, credential, uploadConfig)
if err != nil {
return UploadPolicies{}, err
}
// 1. StringToSign
policy := base64.StdEncoding.EncodeToString(data)
// 2. Signing Key
hash := hmac.New(sha256.New, buildSignature(nowTime, s3.SecretKey, s3.Region, serviceName))
hash.Write([]byte(policy))
// 3. Signature
signature := hex.EncodeToString(hash.Sum(nil))
uploadURL := uploadConfig.UploadURL
if uploadURL == "" {
uploadURL = fmt.Sprintf(defaultUploadURLFormat, uploadConfig.BucketName)
}
form := map[string]string{
"key": uploadConfig.ObjectKey,
"Content-Type": uploadConfig.ContentType,
"X-Amz-Credential": credential,
"X-Amz-Algorithm": algorithm,
"X-Amz-Date": nowTime.Format(amzDateISO8601TimeFormat),
"Policy": policy,
"X-Amz-Signature": signature,
}
for k, v := range uploadConfig.MetaData {
form[k] = v
}
return UploadPolicies{
URL: uploadURL,
Form: form,
}, nil
}
func buildUploadSign(nowTime time.Time, credential string, uploadConfig UploadConfig) ([]byte, error) {
conditions := []interface{}{
map[string]string{"bucket": uploadConfig.BucketName},
map[string]string{"key": uploadConfig.ObjectKey},
map[string]string{"Content-Type": uploadConfig.ContentType},
[]interface{}{"content-length-range", uploadConfig.FileSize, uploadConfig.FileSize},
map[string]string{"x-amz-credential": credential},
map[string]string{"x-amz-algorithm": algorithm},
map[string]string{"x-amz-date": nowTime.Format(amzDateISO8601TimeFormat)},
}
for k, v := range uploadConfig.MetaData {
conditions = append(conditions, map[string]string{k: v})
}
expiration := defaultExpirationHour
if uploadConfig.Expiration > 0 {
expiration = uploadConfig.Expiration
}
return json.Marshal(&PolicyJSON{
Expiration: nowTime.Add(expiration).Format(expirationTimeFormat),
Conditions: conditions,
})
}
func (s3 S3) buildCredential(nowTime time.Time) []byte {
var b bytes.Buffer
b.WriteString(s3.AccessKey)
b.WriteRune('/')
b.WriteString(nowTime.Format(shortTimeFormat))
b.WriteRune('/')
b.WriteString(s3.Region)
b.WriteRune('/')
b.WriteString(serviceName)
b.WriteRune('/')
b.WriteString("aws4_request")
return b.Bytes()
}
func (s3 S3) buildCredentialWithoutKey(nowTime time.Time) []byte {
var b bytes.Buffer
b.WriteString(nowTime.Format(shortTimeFormat))
b.WriteRune('/')
b.WriteString(s3.Region)
b.WriteRune('/')
b.WriteString(serviceName)
b.WriteRune('/')
b.WriteString("aws4_request")
return b.Bytes()
}
func buildSignature(nowTime time.Time, secretAccessKey string, regionName string, serviceName string) []byte {
shortTime := nowTime.Format(shortTimeFormat)
date := makeHMac([]byte("AWS4"+secretAccessKey), []byte(shortTime))
region := makeHMac(date, []byte(regionName))
service := makeHMac(region, []byte(serviceName))
credentials := makeHMac(service, []byte("aws4_request"))
return credentials
}
func makeHMac(key []byte, data []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(nil)
}
|