aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go')
-rw-r--r--vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go465
1 files changed, 465 insertions, 0 deletions
diff --git a/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go b/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go
new file mode 100644
index 0000000..4765800
--- /dev/null
+++ b/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go
@@ -0,0 +1,465 @@
+// Package v4 implements signing for AWS V4 signer
+package v4
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/credentials"
+ "github.com/aws/aws-sdk-go/aws/request"
+ "github.com/aws/aws-sdk-go/private/protocol/rest"
+)
+
+const (
+ authHeaderPrefix = "AWS4-HMAC-SHA256"
+ timeFormat = "20060102T150405Z"
+ shortTimeFormat = "20060102"
+)
+
+var ignoredHeaders = rules{
+ blacklist{
+ mapRule{
+ "Authorization": struct{}{},
+ "User-Agent": struct{}{},
+ },
+ },
+}
+
+// requiredSignedHeaders is a whitelist for build canonical headers.
+var requiredSignedHeaders = rules{
+ whitelist{
+ mapRule{
+ "Cache-Control": struct{}{},
+ "Content-Disposition": struct{}{},
+ "Content-Encoding": struct{}{},
+ "Content-Language": struct{}{},
+ "Content-Md5": struct{}{},
+ "Content-Type": struct{}{},
+ "Expires": struct{}{},
+ "If-Match": struct{}{},
+ "If-Modified-Since": struct{}{},
+ "If-None-Match": struct{}{},
+ "If-Unmodified-Since": struct{}{},
+ "Range": struct{}{},
+ "X-Amz-Acl": struct{}{},
+ "X-Amz-Copy-Source": struct{}{},
+ "X-Amz-Copy-Source-If-Match": struct{}{},
+ "X-Amz-Copy-Source-If-Modified-Since": struct{}{},
+ "X-Amz-Copy-Source-If-None-Match": struct{}{},
+ "X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
+ "X-Amz-Copy-Source-Range": struct{}{},
+ "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
+ "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{},
+ "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
+ "X-Amz-Grant-Full-control": struct{}{},
+ "X-Amz-Grant-Read": struct{}{},
+ "X-Amz-Grant-Read-Acp": struct{}{},
+ "X-Amz-Grant-Write": struct{}{},
+ "X-Amz-Grant-Write-Acp": struct{}{},
+ "X-Amz-Metadata-Directive": struct{}{},
+ "X-Amz-Mfa": struct{}{},
+ "X-Amz-Request-Payer": struct{}{},
+ "X-Amz-Server-Side-Encryption": struct{}{},
+ "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{},
+ "X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{},
+ "X-Amz-Server-Side-Encryption-Customer-Key": struct{}{},
+ "X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
+ "X-Amz-Storage-Class": struct{}{},
+ "X-Amz-Website-Redirect-Location": struct{}{},
+ },
+ },
+ patterns{"X-Amz-Meta-"},
+}
+
+// allowedHoisting is a whitelist for build query headers. The boolean value
+// represents whether or not it is a pattern.
+var allowedQueryHoisting = inclusiveRules{
+ blacklist{requiredSignedHeaders},
+ patterns{"X-Amz-"},
+}
+
+type signer struct {
+ Request *http.Request
+ Time time.Time
+ ExpireTime time.Duration
+ ServiceName string
+ Region string
+ CredValues credentials.Value
+ Credentials *credentials.Credentials
+ Query url.Values
+ Body io.ReadSeeker
+ Debug aws.LogLevelType
+ Logger aws.Logger
+
+ isPresign bool
+ formattedTime string
+ formattedShortTime string
+
+ signedHeaders string
+ canonicalHeaders string
+ canonicalString string
+ credentialString string
+ stringToSign string
+ signature string
+ authorization string
+ notHoist bool
+ signedHeaderVals http.Header
+}
+
+// Sign requests with signature version 4.
+//
+// Will sign the requests with the service config's Credentials object
+// Signing is skipped if the credentials is the credentials.AnonymousCredentials
+// object.
+func Sign(req *request.Request) {
+ // If the request does not need to be signed ignore the signing of the
+ // request if the AnonymousCredentials object is used.
+ if req.Config.Credentials == credentials.AnonymousCredentials {
+ return
+ }
+
+ region := req.ClientInfo.SigningRegion
+ if region == "" {
+ region = aws.StringValue(req.Config.Region)
+ }
+
+ name := req.ClientInfo.SigningName
+ if name == "" {
+ name = req.ClientInfo.ServiceName
+ }
+
+ s := signer{
+ Request: req.HTTPRequest,
+ Time: req.Time,
+ ExpireTime: req.ExpireTime,
+ Query: req.HTTPRequest.URL.Query(),
+ Body: req.Body,
+ ServiceName: name,
+ Region: region,
+ Credentials: req.Config.Credentials,
+ Debug: req.Config.LogLevel.Value(),
+ Logger: req.Config.Logger,
+ notHoist: req.NotHoist,
+ }
+
+ req.Error = s.sign()
+ req.Time = s.Time
+ req.SignedHeaderVals = s.signedHeaderVals
+}
+
+func (v4 *signer) sign() error {
+ if v4.ExpireTime != 0 {
+ v4.isPresign = true
+ }
+
+ if v4.isRequestSigned() {
+ if !v4.Credentials.IsExpired() && time.Now().Before(v4.Time.Add(10*time.Minute)) {
+ // If the request is already signed, and the credentials have not
+ // expired, and the request is not too old ignore the signing request.
+ return nil
+ }
+ v4.Time = time.Now()
+
+ // The credentials have expired for this request. The current signing
+ // is invalid, and needs to be request because the request will fail.
+ if v4.isPresign {
+ v4.removePresign()
+ // Update the request's query string to ensure the values stays in
+ // sync in the case retrieving the new credentials fails.
+ v4.Request.URL.RawQuery = v4.Query.Encode()
+ }
+ }
+
+ var err error
+ v4.CredValues, err = v4.Credentials.Get()
+ if err != nil {
+ return err
+ }
+
+ if v4.isPresign {
+ v4.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
+ if v4.CredValues.SessionToken != "" {
+ v4.Query.Set("X-Amz-Security-Token", v4.CredValues.SessionToken)
+ } else {
+ v4.Query.Del("X-Amz-Security-Token")
+ }
+ } else if v4.CredValues.SessionToken != "" {
+ v4.Request.Header.Set("X-Amz-Security-Token", v4.CredValues.SessionToken)
+ }
+
+ v4.build()
+
+ if v4.Debug.Matches(aws.LogDebugWithSigning) {
+ v4.logSigningInfo()
+ }
+
+ return nil
+}
+
+const logSignInfoMsg = `DEBUG: Request Signiture:
+---[ CANONICAL STRING ]-----------------------------
+%s
+---[ STRING TO SIGN ]--------------------------------
+%s%s
+-----------------------------------------------------`
+const logSignedURLMsg = `
+---[ SIGNED URL ]------------------------------------
+%s`
+
+func (v4 *signer) logSigningInfo() {
+ signedURLMsg := ""
+ if v4.isPresign {
+ signedURLMsg = fmt.Sprintf(logSignedURLMsg, v4.Request.URL.String())
+ }
+ msg := fmt.Sprintf(logSignInfoMsg, v4.canonicalString, v4.stringToSign, signedURLMsg)
+ v4.Logger.Log(msg)
+}
+
+func (v4 *signer) build() {
+
+ v4.buildTime() // no depends
+ v4.buildCredentialString() // no depends
+
+ unsignedHeaders := v4.Request.Header
+ if v4.isPresign {
+ if !v4.notHoist {
+ urlValues := url.Values{}
+ urlValues, unsignedHeaders = buildQuery(allowedQueryHoisting, unsignedHeaders) // no depends
+ for k := range urlValues {
+ v4.Query[k] = urlValues[k]
+ }
+ }
+ }
+
+ v4.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders)
+ v4.buildCanonicalString() // depends on canon headers / signed headers
+ v4.buildStringToSign() // depends on canon string
+ v4.buildSignature() // depends on string to sign
+
+ if v4.isPresign {
+ v4.Request.URL.RawQuery += "&X-Amz-Signature=" + v4.signature
+ } else {
+ parts := []string{
+ authHeaderPrefix + " Credential=" + v4.CredValues.AccessKeyID + "/" + v4.credentialString,
+ "SignedHeaders=" + v4.signedHeaders,
+ "Signature=" + v4.signature,
+ }
+ v4.Request.Header.Set("Authorization", strings.Join(parts, ", "))
+ }
+}
+
+func (v4 *signer) buildTime() {
+ v4.formattedTime = v4.Time.UTC().Format(timeFormat)
+ v4.formattedShortTime = v4.Time.UTC().Format(shortTimeFormat)
+
+ if v4.isPresign {
+ duration := int64(v4.ExpireTime / time.Second)
+ v4.Query.Set("X-Amz-Date", v4.formattedTime)
+ v4.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
+ } else {
+ v4.Request.Header.Set("X-Amz-Date", v4.formattedTime)
+ }
+}
+
+func (v4 *signer) buildCredentialString() {
+ v4.credentialString = strings.Join([]string{
+ v4.formattedShortTime,
+ v4.Region,
+ v4.ServiceName,
+ "aws4_request",
+ }, "/")
+
+ if v4.isPresign {
+ v4.Query.Set("X-Amz-Credential", v4.CredValues.AccessKeyID+"/"+v4.credentialString)
+ }
+}
+
+func buildQuery(r rule, header http.Header) (url.Values, http.Header) {
+ query := url.Values{}
+ unsignedHeaders := http.Header{}
+ for k, h := range header {
+ if r.IsValid(k) {
+ query[k] = h
+ } else {
+ unsignedHeaders[k] = h
+ }
+ }
+
+ return query, unsignedHeaders
+}
+func (v4 *signer) buildCanonicalHeaders(r rule, header http.Header) {
+ var headers []string
+ headers = append(headers, "host")
+ for k, v := range header {
+ canonicalKey := http.CanonicalHeaderKey(k)
+ if !r.IsValid(canonicalKey) {
+ continue // ignored header
+ }
+ if v4.signedHeaderVals == nil {
+ v4.signedHeaderVals = make(http.Header)
+ }
+
+ lowerCaseKey := strings.ToLower(k)
+ if _, ok := v4.signedHeaderVals[lowerCaseKey]; ok {
+ // include additional values
+ v4.signedHeaderVals[lowerCaseKey] = append(v4.signedHeaderVals[lowerCaseKey], v...)
+ continue
+ }
+
+ headers = append(headers, lowerCaseKey)
+ v4.signedHeaderVals[lowerCaseKey] = v
+ }
+ sort.Strings(headers)
+
+ v4.signedHeaders = strings.Join(headers, ";")
+
+ if v4.isPresign {
+ v4.Query.Set("X-Amz-SignedHeaders", v4.signedHeaders)
+ }
+
+ headerValues := make([]string, len(headers))
+ for i, k := range headers {
+ if k == "host" {
+ headerValues[i] = "host:" + v4.Request.URL.Host
+ } else {
+ headerValues[i] = k + ":" +
+ strings.Join(v4.signedHeaderVals[k], ",")
+ }
+ }
+
+ v4.canonicalHeaders = strings.Join(stripExcessSpaces(headerValues), "\n")
+}
+
+func (v4 *signer) buildCanonicalString() {
+ v4.Request.URL.RawQuery = strings.Replace(v4.Query.Encode(), "+", "%20", -1)
+ uri := v4.Request.URL.Opaque
+ if uri != "" {
+ uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
+ } else {
+ uri = v4.Request.URL.Path
+ }
+ if uri == "" {
+ uri = "/"
+ }
+
+ if v4.ServiceName != "s3" {
+ uri = rest.EscapePath(uri, false)
+ }
+
+ v4.canonicalString = strings.Join([]string{
+ v4.Request.Method,
+ uri,
+ v4.Request.URL.RawQuery,
+ v4.canonicalHeaders + "\n",
+ v4.signedHeaders,
+ v4.bodyDigest(),
+ }, "\n")
+}
+
+func (v4 *signer) buildStringToSign() {
+ v4.stringToSign = strings.Join([]string{
+ authHeaderPrefix,
+ v4.formattedTime,
+ v4.credentialString,
+ hex.EncodeToString(makeSha256([]byte(v4.canonicalString))),
+ }, "\n")
+}
+
+func (v4 *signer) buildSignature() {
+ secret := v4.CredValues.SecretAccessKey
+ date := makeHmac([]byte("AWS4"+secret), []byte(v4.formattedShortTime))
+ region := makeHmac(date, []byte(v4.Region))
+ service := makeHmac(region, []byte(v4.ServiceName))
+ credentials := makeHmac(service, []byte("aws4_request"))
+ signature := makeHmac(credentials, []byte(v4.stringToSign))
+ v4.signature = hex.EncodeToString(signature)
+}
+
+func (v4 *signer) bodyDigest() string {
+ hash := v4.Request.Header.Get("X-Amz-Content-Sha256")
+ if hash == "" {
+ if v4.isPresign && v4.ServiceName == "s3" {
+ hash = "UNSIGNED-PAYLOAD"
+ } else if v4.Body == nil {
+ hash = hex.EncodeToString(makeSha256([]byte{}))
+ } else {
+ hash = hex.EncodeToString(makeSha256Reader(v4.Body))
+ }
+ v4.Request.Header.Add("X-Amz-Content-Sha256", hash)
+ }
+ return hash
+}
+
+// isRequestSigned returns if the request is currently signed or presigned
+func (v4 *signer) isRequestSigned() bool {
+ if v4.isPresign && v4.Query.Get("X-Amz-Signature") != "" {
+ return true
+ }
+ if v4.Request.Header.Get("Authorization") != "" {
+ return true
+ }
+
+ return false
+}
+
+// unsign removes signing flags for both signed and presigned requests.
+func (v4 *signer) removePresign() {
+ v4.Query.Del("X-Amz-Algorithm")
+ v4.Query.Del("X-Amz-Signature")
+ v4.Query.Del("X-Amz-Security-Token")
+ v4.Query.Del("X-Amz-Date")
+ v4.Query.Del("X-Amz-Expires")
+ v4.Query.Del("X-Amz-Credential")
+ v4.Query.Del("X-Amz-SignedHeaders")
+}
+
+func makeHmac(key []byte, data []byte) []byte {
+ hash := hmac.New(sha256.New, key)
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+func makeSha256(data []byte) []byte {
+ hash := sha256.New()
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+func makeSha256Reader(reader io.ReadSeeker) []byte {
+ hash := sha256.New()
+ start, _ := reader.Seek(0, 1)
+ defer reader.Seek(start, 0)
+
+ io.Copy(hash, reader)
+ return hash.Sum(nil)
+}
+
+func stripExcessSpaces(headerVals []string) []string {
+ vals := make([]string, len(headerVals))
+ for i, str := range headerVals {
+ stripped := ""
+ found := false
+ str = strings.TrimSpace(str)
+ for _, c := range str {
+ if !found && c == ' ' {
+ stripped += string(c)
+ found = true
+ } else if c != ' ' {
+ stripped += string(c)
+ found = false
+ }
+ }
+ vals[i] = stripped
+ }
+ return vals
+}