123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- // 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/internal/protocol/rest"
- )
- const (
- authHeaderPrefix = "AWS4-HMAC-SHA256"
- timeFormat = "20060102T150405Z"
- shortTimeFormat = "20060102"
- )
- var ignoredHeaders = map[string]bool{
- "Authorization": true,
- "Content-Type": true,
- "Content-Length": true,
- "User-Agent": true,
- }
- 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
- }
- // 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.Service.Config.Credentials == credentials.AnonymousCredentials {
- return
- }
- region := req.Service.SigningRegion
- if region == "" {
- region = aws.StringValue(req.Service.Config.Region)
- }
- name := req.Service.SigningName
- if name == "" {
- name = req.Service.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.Service.Config.Credentials,
- Debug: req.Service.Config.LogLevel.Value(),
- Logger: req.Service.Config.Logger,
- }
- req.Error = s.sign()
- }
- func (v4 *signer) sign() error {
- if v4.ExpireTime != 0 {
- v4.isPresign = true
- }
- if v4.isRequestSigned() {
- if !v4.Credentials.IsExpired() {
- // If the request is already signed, and the credentials have not
- // expired yet ignore the signing request.
- return nil
- }
- // 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
- if v4.isPresign {
- v4.buildQuery() // no depends
- }
- v4.buildCanonicalHeaders() // depends on cred string
- 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 (v4 *signer) buildQuery() {
- for k, h := range v4.Request.Header {
- if strings.HasPrefix(http.CanonicalHeaderKey(k), "X-Amz-") {
- continue // never hoist x-amz-* headers, they must be signed
- }
- if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
- continue // never hoist ignored headers
- }
- v4.Request.Header.Del(k)
- v4.Query.Del(k)
- for _, v := range h {
- v4.Query.Add(k, v)
- }
- }
- }
- func (v4 *signer) buildCanonicalHeaders() {
- var headers []string
- headers = append(headers, "host")
- for k := range v4.Request.Header {
- if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
- continue // ignored header
- }
- headers = append(headers, strings.ToLower(k))
- }
- 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.Request.Header[http.CanonicalHeaderKey(k)], ",")
- }
- }
- v4.canonicalHeaders = strings.Join(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)
- }
|