refactor: Presigned URL generator to use byte strings
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
package simples3
|
package simples3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -66,7 +67,7 @@ var newLine = []byte{'\n'}
|
|||||||
// https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html
|
// https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html
|
||||||
func (s3 *S3) CreateUploadPolicies(uploadConfig UploadConfig) (UploadPolicies, error) {
|
func (s3 *S3) CreateUploadPolicies(uploadConfig UploadConfig) (UploadPolicies, error) {
|
||||||
nowTime := NowTime()
|
nowTime := NowTime()
|
||||||
credential := s3.buildCredential(nowTime)
|
credential := string(s3.buildCredential(nowTime))
|
||||||
data, err := buildUploadSign(nowTime, credential, uploadConfig)
|
data, err := buildUploadSign(nowTime, credential, uploadConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UploadPolicies{}, err
|
return UploadPolicies{}, err
|
||||||
@@ -126,12 +127,30 @@ func buildUploadSign(nowTime time.Time, credential string, uploadConfig UploadCo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3 S3) buildCredential(nowTime time.Time) string {
|
func (s3 S3) buildCredential(nowTime time.Time) []byte {
|
||||||
return fmt.Sprintf("%s/%s/%s/%s/%s", s3.AccessKey, nowTime.UTC().Format(shortTimeFormat), s3.Region, serviceName, "aws4_request")
|
var b bytes.Buffer
|
||||||
|
b.WriteString(s3.AccessKey)
|
||||||
|
b.WriteRune('/')
|
||||||
|
b.WriteString(nowTime.UTC().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) string {
|
func (s3 S3) buildCredentialWithoutKey(nowTime time.Time) []byte {
|
||||||
return fmt.Sprintf("%s/%s/%s/%s", nowTime.UTC().Format(shortTimeFormat), s3.Region, serviceName, "aws4_request")
|
var b bytes.Buffer
|
||||||
|
b.WriteString(nowTime.UTC().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 {
|
func buildSignature(nowTime time.Time, secretAccessKey string, regionName string, serviceName string) []byte {
|
||||||
|
|||||||
+82
-49
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -13,8 +12,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultPresignedURLFormat = "%s.s3.amazonaws.com" // <bucket>
|
defaultPresignedHost = "s3.amazonaws.com" // <bucket>
|
||||||
defaultProtocol = "https://" // <bucket>
|
defaultProtocol = "https://" // <bucket>
|
||||||
)
|
)
|
||||||
|
|
||||||
// PresignedInput is passed to GeneratePresignedURL as a parameter.
|
// PresignedInput is passed to GeneratePresignedURL as a parameter.
|
||||||
@@ -33,36 +32,50 @@ type PresignedInput struct {
|
|||||||
// (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html)
|
// (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html)
|
||||||
func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
|
func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
|
||||||
var (
|
var (
|
||||||
nowTime = in.Timestamp.UTC()
|
nowTime = in.Timestamp.UTC()
|
||||||
cred = fmt.Sprintf("%s/"+s3.buildCredentialWithoutKey(nowTime), s3.AccessKey)
|
|
||||||
amzdate = nowTime.Format(amzDateISO8601TimeFormat)
|
amzdate = nowTime.Format(amzDateISO8601TimeFormat)
|
||||||
expiry = strconv.Itoa(in.ExpirySeconds)
|
protocol = defaultProtocol
|
||||||
protocol = in.Protocol
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Create cred
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
b.WriteString(s3.AccessKey)
|
||||||
|
b.WriteRune('/')
|
||||||
|
b.Write(s3.buildCredentialWithoutKey(nowTime))
|
||||||
|
cred := b.Bytes()
|
||||||
|
b.Reset()
|
||||||
|
|
||||||
// Set the protocol as default if not provided.
|
// Set the protocol as default if not provided.
|
||||||
if protocol == "" {
|
if in.Protocol != "" {
|
||||||
protocol = defaultProtocol
|
protocol = in.Protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add host to Headers
|
// Add host to Headers
|
||||||
signedHeaders := in.ExtraHeaders
|
signedHeaders := map[string][]byte{}
|
||||||
if signedHeaders == nil {
|
for k, v := range in.ExtraHeaders {
|
||||||
signedHeaders = map[string]string{}
|
signedHeaders[k] = []byte(v)
|
||||||
}
|
}
|
||||||
signedHeaders["host"] = fmt.Sprintf(defaultPresignedURLFormat, in.Bucket)
|
host := bytes.Buffer{}
|
||||||
|
host.WriteString(in.Bucket)
|
||||||
|
host.WriteRune('.')
|
||||||
|
host.WriteString(defaultPresignedHost)
|
||||||
|
signedHeaders["host"] = host.Bytes()
|
||||||
|
|
||||||
// Start Canonical Request Formation
|
// Start Canonical Request Formation
|
||||||
h := sha256.New() // We write the canonical request directly to the SHA256 hash.
|
h := sha256.New() // We write the canonical request directly to the SHA256 hash.
|
||||||
fmt.Fprintf(h, "%s\n", in.Method) // HTTP Verb
|
h.Write([]byte(in.Method)) // HTTP Verb
|
||||||
fmt.Fprintf(h, "/%s\n", in.ObjectKey) // CanonicalURL
|
h.Write(newLine)
|
||||||
|
h.Write([]byte{'/'})
|
||||||
|
h.Write([]byte(in.ObjectKey)) // CanonicalURL
|
||||||
|
h.Write(newLine)
|
||||||
|
|
||||||
// Start QueryString Params (before SignedHeaders)
|
// Start QueryString Params (before SignedHeaders)
|
||||||
queryString := map[string]string{
|
queryString := map[string]string{
|
||||||
"X-Amz-Algorithm": algorithm,
|
"X-Amz-Algorithm": algorithm,
|
||||||
"X-Amz-Credential": cred,
|
"X-Amz-Credential": string(cred),
|
||||||
"X-Amz-Date": amzdate,
|
"X-Amz-Date": amzdate,
|
||||||
"X-Amz-Expires": expiry,
|
"X-Amz-Expires": strconv.Itoa(in.ExpirySeconds),
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to have a sorted order,
|
// We need to have a sorted order,
|
||||||
@@ -81,57 +94,66 @@ func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
|
|||||||
|
|
||||||
// Proceed to write canonical query params
|
// Proceed to write canonical query params
|
||||||
for _, k := range sortedQS {
|
for _, k := range sortedQS {
|
||||||
fmt.Fprintf(h, "%s=%s&", url.QueryEscape(k), url.QueryEscape(queryString[k])) // HTTP Verb
|
// HTTP Verb
|
||||||
|
h.Write([]byte(url.QueryEscape(k)))
|
||||||
|
h.Write([]byte{'='})
|
||||||
|
h.Write([]byte(url.QueryEscape(string(queryString[k]))))
|
||||||
|
h.Write([]byte{'&'})
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(h, "X-Amz-SignedHeaders=")
|
h.Write([]byte("X-Amz-SignedHeaders="))
|
||||||
// Add Signed Headers to Query String
|
// Add Signed Headers to Query String
|
||||||
first := true
|
first := true
|
||||||
for k := range sortedSH {
|
for i := 0; i < len(sortedSH); i++ {
|
||||||
if first {
|
if first {
|
||||||
fmt.Fprintf(h, "%s", url.QueryEscape(sortedSH[k]))
|
h.Write([]byte(url.QueryEscape(sortedSH[i])))
|
||||||
first = false
|
first = false
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(h, "%s", url.QueryEscape(";"+sortedSH[k]))
|
h.Write([]byte{';'})
|
||||||
|
h.Write([]byte(url.QueryEscape(sortedSH[i])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(h, "\n")
|
h.Write(newLine)
|
||||||
// End QueryString Params
|
// End QueryString Params
|
||||||
|
|
||||||
// Start Canonical Headers
|
// Start Canonical Headers
|
||||||
for _, k := range sortedSH {
|
for i := 0; i < len(sortedSH); i++ {
|
||||||
fmt.Fprintf(h, "%s:%s\n", strings.ToLower(k), strings.TrimSpace(signedHeaders[k]))
|
h.Write([]byte(strings.ToLower(sortedSH[i])))
|
||||||
|
h.Write([]byte{':'})
|
||||||
|
h.Write([]byte(strings.TrimSpace(string(signedHeaders[sortedSH[i]]))))
|
||||||
|
h.Write(newLine)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(h, "\n")
|
h.Write(newLine)
|
||||||
// End Canonical Headers
|
// End Canonical Headers
|
||||||
|
|
||||||
// Start Signed Headers
|
// Start Signed Headers
|
||||||
first = true
|
first = true
|
||||||
for k := range sortedSH {
|
for i := 0; i < len(sortedSH); i++ {
|
||||||
if first {
|
if first {
|
||||||
fmt.Fprintf(h, "%s", url.QueryEscape(sortedSH[k]))
|
h.Write([]byte(url.QueryEscape(sortedSH[i])))
|
||||||
first = false
|
first = false
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(h, "%s", url.QueryEscape(";"+sortedSH[k]))
|
h.Write([]byte{';'})
|
||||||
|
h.Write([]byte(url.QueryEscape(sortedSH[i])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(h, "\n")
|
h.Write(newLine)
|
||||||
// End Canonical Headers
|
// End Canonical Headers
|
||||||
|
|
||||||
// Mention Unsigned Payload
|
// Mention Unsigned Payload
|
||||||
fmt.Fprintf(h, "UNSIGNED-PAYLOAD")
|
h.Write([]byte("UNSIGNED-PAYLOAD"))
|
||||||
|
|
||||||
// canonicalReq := h.Bytes()
|
// canonicalReq := h.Bytes()
|
||||||
// Reset Buffer to create StringToSign
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
// Start StringToSign
|
// Start StringToSign
|
||||||
fmt.Fprintf(&b, "%s\n", algorithm) // Algo
|
b.WriteString(algorithm)
|
||||||
fmt.Fprintf(&b, "%s\n", amzdate) // Date
|
b.WriteRune('\n')
|
||||||
fmt.Fprintf(&b, "%s\n", s3.buildCredentialWithoutKey(nowTime)) //Cred
|
b.WriteString(amzdate)
|
||||||
|
b.WriteRune('\n')
|
||||||
|
b.Write(s3.buildCredentialWithoutKey(nowTime))
|
||||||
|
b.WriteRune('\n')
|
||||||
|
|
||||||
hashed := hex.EncodeToString(h.Sum(nil))
|
hashed := hex.EncodeToString(h.Sum(nil))
|
||||||
fmt.Fprintf(&b, "%s", hashed)
|
b.WriteString(hashed)
|
||||||
|
|
||||||
stringToSign := b.Bytes()
|
stringToSign := b.Bytes()
|
||||||
|
|
||||||
@@ -155,29 +177,40 @@ func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
|
|||||||
signature := hex.EncodeToString(signedStrToSign)
|
signature := hex.EncodeToString(signedStrToSign)
|
||||||
// End Signature
|
// End Signature
|
||||||
|
|
||||||
// Reset Buffer to create URl
|
// Reset Buffer to create URL
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
|
||||||
// Start Generating URL
|
// Start Generating URL
|
||||||
fmt.Fprintf(&b, protocol+defaultPresignedURLFormat+"/", in.Bucket)
|
b.WriteString(protocol)
|
||||||
fmt.Fprintf(&b, "%s?", in.ObjectKey)
|
b.WriteString(in.Bucket)
|
||||||
|
b.WriteRune('.')
|
||||||
|
b.WriteString(defaultPresignedHost)
|
||||||
|
b.WriteRune('/')
|
||||||
|
b.WriteString(in.ObjectKey)
|
||||||
|
b.WriteRune('?')
|
||||||
|
|
||||||
// We don't need to have a sorted order here,
|
// We don't need to have a sorted order here,
|
||||||
// but just to preserve tests.
|
// but just to preserve tests.
|
||||||
for _, k := range sortedQS {
|
for i := 0; i < len(sortedQS); i++ {
|
||||||
fmt.Fprintf(&b, "%s=%s&", url.QueryEscape(k), url.QueryEscape(queryString[k])) // HTTP Verb
|
b.WriteString(url.QueryEscape(sortedQS[i]))
|
||||||
|
b.WriteRune('=')
|
||||||
|
b.WriteString(url.QueryEscape(string(queryString[sortedQS[i]])))
|
||||||
|
b.WriteRune('&')
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, "%s=", "X-Amz-SignedHeaders")
|
b.WriteString("X-Amz-SignedHeaders")
|
||||||
|
b.WriteRune('=')
|
||||||
first = true
|
first = true
|
||||||
for k := range sortedSH {
|
for i := 0; i < len(sortedSH); i++ {
|
||||||
if first {
|
if first {
|
||||||
fmt.Fprintf(&b, "%s", url.QueryEscape(sortedSH[k]))
|
b.WriteString(url.QueryEscape(sortedSH[i]))
|
||||||
first = false
|
first = false
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(&b, "%s", url.QueryEscape(";"+sortedSH[k]))
|
b.WriteRune(';')
|
||||||
|
b.WriteString(url.QueryEscape(sortedSH[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, "&X-Amz-Signature=%s", signature)
|
b.WriteString("&X-Amz-Signature=")
|
||||||
|
b.WriteString(signature)
|
||||||
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ func BenchmarkS3_GeneratePresigned(b *testing.B) {
|
|||||||
os.Getenv("AWS_S3_ACCESS_KEY"),
|
os.Getenv("AWS_S3_ACCESS_KEY"),
|
||||||
os.Getenv("AWS_S3_SECRET_KEY"),
|
os.Getenv("AWS_S3_SECRET_KEY"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
s.GeneratePresignedURL(PresignedInput{
|
s.GeneratePresignedURL(PresignedInput{
|
||||||
Bucket: os.Getenv("AWS_S3_BUCKET"),
|
Bucket: os.Getenv("AWS_S3_BUCKET"),
|
||||||
|
|||||||
Reference in New Issue
Block a user