|
@ -6,13 +6,10 @@ import ( |
|
|
"crypto/sha256" |
|
|
"crypto/sha256" |
|
|
"encoding/base64" |
|
|
"encoding/base64" |
|
|
"encoding/hex" |
|
|
"encoding/hex" |
|
|
"errors" |
|
|
|
|
|
"fmt" |
|
|
"fmt" |
|
|
"io" |
|
|
"io" |
|
|
"net/http" |
|
|
"net/http" |
|
|
"net/url" |
|
|
|
|
|
"sort" |
|
|
"sort" |
|
|
"strconv" |
|
|
|
|
|
"strings" |
|
|
"strings" |
|
|
"sync" |
|
|
"sync" |
|
|
"testing" |
|
|
"testing" |
|
@ -210,6 +207,53 @@ func mustNewPresignedRequest(iam *IdentityAccessManagement, method string, urlSt |
|
|
return req |
|
|
return req |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// preSignV4 adds presigned URL parameters to the request
|
|
|
|
|
|
func preSignV4(iam *IdentityAccessManagement, req *http.Request, accessKey, secretKey string, expires int64) error { |
|
|
|
|
|
// Create credential scope
|
|
|
|
|
|
now := time.Now().UTC() |
|
|
|
|
|
dateStr := now.Format(iso8601Format) |
|
|
|
|
|
|
|
|
|
|
|
// Create credential header
|
|
|
|
|
|
scope := fmt.Sprintf("%s/%s/%s/%s", now.Format(yyyymmdd), "us-east-1", "s3", "aws4_request") |
|
|
|
|
|
credential := fmt.Sprintf("%s/%s", accessKey, scope) |
|
|
|
|
|
|
|
|
|
|
|
// Get the query parameters
|
|
|
|
|
|
query := req.URL.Query() |
|
|
|
|
|
query.Set("X-Amz-Algorithm", signV4Algorithm) |
|
|
|
|
|
query.Set("X-Amz-Credential", credential) |
|
|
|
|
|
query.Set("X-Amz-Date", dateStr) |
|
|
|
|
|
query.Set("X-Amz-Expires", fmt.Sprintf("%d", expires)) |
|
|
|
|
|
query.Set("X-Amz-SignedHeaders", "host") |
|
|
|
|
|
|
|
|
|
|
|
// Set the query on the URL (without signature yet)
|
|
|
|
|
|
req.URL.RawQuery = query.Encode() |
|
|
|
|
|
|
|
|
|
|
|
// Get the payload hash
|
|
|
|
|
|
hashedPayload := getContentSha256Cksum(req) |
|
|
|
|
|
|
|
|
|
|
|
// Extract signed headers
|
|
|
|
|
|
extractedSignedHeaders := make(http.Header) |
|
|
|
|
|
extractedSignedHeaders["host"] = []string{req.Host} |
|
|
|
|
|
|
|
|
|
|
|
// Get canonical request
|
|
|
|
|
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, req.URL.RawQuery, req.URL.Path, req.Method) |
|
|
|
|
|
|
|
|
|
|
|
// Get string to sign
|
|
|
|
|
|
stringToSign := getStringToSign(canonicalRequest, now, scope) |
|
|
|
|
|
|
|
|
|
|
|
// Get signing key
|
|
|
|
|
|
signingKey := getSigningKey(secretKey, now.Format(yyyymmdd), "us-east-1", "s3") |
|
|
|
|
|
|
|
|
|
|
|
// Calculate signature
|
|
|
|
|
|
signature := getSignature(signingKey, stringToSign) |
|
|
|
|
|
|
|
|
|
|
|
// Add signature to query
|
|
|
|
|
|
query.Set("X-Amz-Signature", signature) |
|
|
|
|
|
req.URL.RawQuery = query.Encode() |
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Returns new HTTP request object.
|
|
|
// Returns new HTTP request object.
|
|
|
func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeeker) (*http.Request, error) { |
|
|
func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeeker) (*http.Request, error) { |
|
|
if method == "" { |
|
|
if method == "" { |
|
@ -459,47 +503,6 @@ func signRequestV4(req *http.Request, accessKey, secretKey string) error { |
|
|
return nil |
|
|
return nil |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// preSignV4 presign the request, in accordance with
|
|
|
|
|
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html.
|
|
|
|
|
|
func preSignV4(iam *IdentityAccessManagement, req *http.Request, accessKeyID, secretAccessKey string, expires int64) error { |
|
|
|
|
|
// Presign is not needed for anonymous credentials.
|
|
|
|
|
|
if accessKeyID == "" || secretAccessKey == "" { |
|
|
|
|
|
return errors.New("Presign cannot be generated without access and secret keys") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
region := "us-east-1" |
|
|
|
|
|
date := time.Now().UTC() |
|
|
|
|
|
scope := getScope(date, region) |
|
|
|
|
|
credential := fmt.Sprintf("%s/%s", accessKeyID, scope) |
|
|
|
|
|
|
|
|
|
|
|
// Set URL query.
|
|
|
|
|
|
query := req.URL.Query() |
|
|
|
|
|
query.Set("X-Amz-Algorithm", signV4Algorithm) |
|
|
|
|
|
query.Set("X-Amz-Date", date.Format(iso8601Format)) |
|
|
|
|
|
query.Set("X-Amz-Expires", strconv.FormatInt(expires, 10)) |
|
|
|
|
|
query.Set("X-Amz-SignedHeaders", "host") |
|
|
|
|
|
query.Set("X-Amz-Credential", credential) |
|
|
|
|
|
query.Set("X-Amz-Content-Sha256", unsignedPayload) |
|
|
|
|
|
|
|
|
|
|
|
// "host" is the only header required to be signed for Presigned URLs.
|
|
|
|
|
|
extractedSignedHeaders := make(http.Header) |
|
|
|
|
|
extractedSignedHeaders.Set("host", req.Host) |
|
|
|
|
|
|
|
|
|
|
|
queryStr := strings.Replace(query.Encode(), "+", "%20", -1) |
|
|
|
|
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method) |
|
|
|
|
|
stringToSign := getStringToSign(canonicalRequest, date, scope) |
|
|
|
|
|
signingKey := getSigningKey(secretAccessKey, date.Format(yyyymmdd), region, "s3") |
|
|
|
|
|
signature := getSignature(signingKey, stringToSign) |
|
|
|
|
|
|
|
|
|
|
|
req.URL.RawQuery = query.Encode() |
|
|
|
|
|
|
|
|
|
|
|
// Add signature header to RawQuery.
|
|
|
|
|
|
req.URL.RawQuery += "&X-Amz-Signature=" + url.QueryEscape(signature) |
|
|
|
|
|
|
|
|
|
|
|
// Construct the final presigned URL.
|
|
|
|
|
|
return nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
|
|
|
// EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
|
|
|
//
|
|
|
//
|
|
|
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
|
|
|
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
|
|
|