| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796 |
- // Package v4 implements signing for AWS V4 signer
- //
- // Provides request signing for request that need to be signed with
- // AWS V4 Signatures.
- //
- // Standalone Signer
- //
- // Generally using the signer outside of the SDK should not require any additional
- // logic when using Go v1.5 or higher. The signer does this by taking advantage
- // of the URL.EscapedPath method. If your request URI requires additional escaping
- // you many need to use the URL.Opaque to define what the raw URI should be sent
- // to the service as.
- //
- // The signer will first check the URL.Opaque field, and use its value if set.
- // The signer does require the URL.Opaque field to be set in the form of:
- //
- // "//<hostname>/<path>"
- //
- // // e.g.
- // "//example.com/some/path"
- //
- // The leading "//" and hostname are required or the URL.Opaque escaping will
- // not work correctly.
- //
- // If URL.Opaque is not set the signer will fallback to the URL.EscapedPath()
- // method and using the returned value. If you're using Go v1.4 you must set
- // URL.Opaque if the URI path needs escaping. If URL.Opaque is not set with
- // Go v1.5 the signer will fallback to URL.Path.
- //
- // AWS v4 signature validation requires that the canonical string's URI path
- // element must be the URI escaped form of the HTTP request's path.
- // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
- //
- // The Go HTTP client will perform escaping automatically on the request. Some
- // of these escaping may cause signature validation errors because the HTTP
- // request differs from the URI path or query that the signature was generated.
- // https://golang.org/pkg/net/url/#URL.EscapedPath
- //
- // Because of this, it is recommended that when using the signer outside of the
- // SDK that explicitly escaping the request prior to being signed is preferable,
- // and will help prevent signature validation errors. This can be done by setting
- // the URL.Opaque or URL.RawPath. The SDK will use URL.Opaque first and then
- // call URL.EscapedPath() if Opaque is not set.
- //
- // If signing a request intended for HTTP2 server, and you're using Go 1.6.2
- // through 1.7.4 you should use the URL.RawPath as the pre-escaped form of the
- // request URL. https://github.com/golang/go/issues/16847 points to a bug in
- // Go pre 1.8 that fails to make HTTP2 requests using absolute URL in the HTTP
- // message. URL.Opaque generally will force Go to make requests with absolute URL.
- // URL.RawPath does not do this, but RawPath must be a valid escaping of Path
- // or url.EscapedPath will ignore the RawPath escaping.
- //
- // Test `TestStandaloneSign` provides a complete example of using the signer
- // outside of the SDK and pre-escaping the URI path.
- package v4
- import (
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "fmt"
- "io"
- "io/ioutil"
- "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/sdkio"
- "github.com/aws/aws-sdk-go/private/protocol/rest"
- )
- const (
- authHeaderPrefix = "AWS4-HMAC-SHA256"
- timeFormat = "20060102T150405Z"
- shortTimeFormat = "20060102"
- // emptyStringSHA256 is a SHA256 of an empty string
- emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
- )
- var ignoredHeaders = rules{
- blacklist{
- mapRule{
- "Authorization": struct{}{},
- "User-Agent": struct{}{},
- "X-Amzn-Trace-Id": 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-Tagging": struct{}{},
- "X-Amz-Website-Redirect-Location": struct{}{},
- "X-Amz-Content-Sha256": 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-"},
- }
- // Signer applies AWS v4 signing to given request. Use this to sign requests
- // that need to be signed with AWS V4 Signatures.
- type Signer struct {
- // The authentication credentials the request will be signed against.
- // This value must be set to sign requests.
- Credentials *credentials.Credentials
- // Sets the log level the signer should use when reporting information to
- // the logger. If the logger is nil nothing will be logged. See
- // aws.LogLevelType for more information on available logging levels
- //
- // By default nothing will be logged.
- Debug aws.LogLevelType
- // The logger loging information will be written to. If there the logger
- // is nil, nothing will be logged.
- Logger aws.Logger
- // Disables the Signer's moving HTTP header key/value pairs from the HTTP
- // request header to the request's query string. This is most commonly used
- // with pre-signed requests preventing headers from being added to the
- // request's query string.
- DisableHeaderHoisting bool
- // Disables the automatic escaping of the URI path of the request for the
- // siganture's canonical string's path. For services that do not need additional
- // escaping then use this to disable the signer escaping the path.
- //
- // S3 is an example of a service that does not need additional escaping.
- //
- // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
- DisableURIPathEscaping bool
- // Disales the automatical setting of the HTTP request's Body field with the
- // io.ReadSeeker passed in to the signer. This is useful if you're using a
- // custom wrapper around the body for the io.ReadSeeker and want to preserve
- // the Body value on the Request.Body.
- //
- // This does run the risk of signing a request with a body that will not be
- // sent in the request. Need to ensure that the underlying data of the Body
- // values are the same.
- DisableRequestBodyOverwrite bool
- // currentTimeFn returns the time value which represents the current time.
- // This value should only be used for testing. If it is nil the default
- // time.Now will be used.
- currentTimeFn func() time.Time
- // UnsignedPayload will prevent signing of the payload. This will only
- // work for services that have support for this.
- UnsignedPayload bool
- }
- // NewSigner returns a Signer pointer configured with the credentials and optional
- // option values provided. If not options are provided the Signer will use its
- // default configuration.
- func NewSigner(credentials *credentials.Credentials, options ...func(*Signer)) *Signer {
- v4 := &Signer{
- Credentials: credentials,
- }
- for _, option := range options {
- option(v4)
- }
- return v4
- }
- type signingCtx struct {
- ServiceName string
- Region string
- Request *http.Request
- Body io.ReadSeeker
- Query url.Values
- Time time.Time
- ExpireTime time.Duration
- SignedHeaderVals http.Header
- DisableURIPathEscaping bool
- credValues credentials.Value
- isPresign bool
- formattedTime string
- formattedShortTime string
- unsignedPayload bool
- bodyDigest string
- signedHeaders string
- canonicalHeaders string
- canonicalString string
- credentialString string
- stringToSign string
- signature string
- authorization string
- }
- // Sign signs AWS v4 requests with the provided body, service name, region the
- // request is made to, and time the request is signed at. The signTime allows
- // you to specify that a request is signed for the future, and cannot be
- // used until then.
- //
- // Returns a list of HTTP headers that were included in the signature or an
- // error if signing the request failed. Generally for signed requests this value
- // is not needed as the full request context will be captured by the http.Request
- // value. It is included for reference though.
- //
- // Sign will set the request's Body to be the `body` parameter passed in. If
- // the body is not already an io.ReadCloser, it will be wrapped within one. If
- // a `nil` body parameter passed to Sign, the request's Body field will be
- // also set to nil. Its important to note that this functionality will not
- // change the request's ContentLength of the request.
- //
- // Sign differs from Presign in that it will sign the request using HTTP
- // header values. This type of signing is intended for http.Request values that
- // will not be shared, or are shared in a way the header values on the request
- // will not be lost.
- //
- // The requests body is an io.ReadSeeker so the SHA256 of the body can be
- // generated. To bypass the signer computing the hash you can set the
- // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
- // only compute the hash if the request header value is empty.
- func (v4 Signer) Sign(r *http.Request, body io.ReadSeeker, service, region string, signTime time.Time) (http.Header, error) {
- return v4.signWithBody(r, body, service, region, 0, false, signTime)
- }
- // Presign signs AWS v4 requests with the provided body, service name, region
- // the request is made to, and time the request is signed at. The signTime
- // allows you to specify that a request is signed for the future, and cannot
- // be used until then.
- //
- // Returns a list of HTTP headers that were included in the signature or an
- // error if signing the request failed. For presigned requests these headers
- // and their values must be included on the HTTP request when it is made. This
- // is helpful to know what header values need to be shared with the party the
- // presigned request will be distributed to.
- //
- // Presign differs from Sign in that it will sign the request using query string
- // instead of header values. This allows you to share the Presigned Request's
- // URL with third parties, or distribute it throughout your system with minimal
- // dependencies.
- //
- // Presign also takes an exp value which is the duration the
- // signed request will be valid after the signing time. This is allows you to
- // set when the request will expire.
- //
- // The requests body is an io.ReadSeeker so the SHA256 of the body can be
- // generated. To bypass the signer computing the hash you can set the
- // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
- // only compute the hash if the request header value is empty.
- //
- // Presigning a S3 request will not compute the body's SHA256 hash by default.
- // This is done due to the general use case for S3 presigned URLs is to share
- // PUT/GET capabilities. If you would like to include the body's SHA256 in the
- // presigned request's signature you can set the "X-Amz-Content-Sha256"
- // HTTP header and that will be included in the request's signature.
- func (v4 Signer) Presign(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, signTime time.Time) (http.Header, error) {
- return v4.signWithBody(r, body, service, region, exp, true, signTime)
- }
- func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, isPresign bool, signTime time.Time) (http.Header, error) {
- currentTimeFn := v4.currentTimeFn
- if currentTimeFn == nil {
- currentTimeFn = time.Now
- }
- ctx := &signingCtx{
- Request: r,
- Body: body,
- Query: r.URL.Query(),
- Time: signTime,
- ExpireTime: exp,
- isPresign: isPresign,
- ServiceName: service,
- Region: region,
- DisableURIPathEscaping: v4.DisableURIPathEscaping,
- unsignedPayload: v4.UnsignedPayload,
- }
- for key := range ctx.Query {
- sort.Strings(ctx.Query[key])
- }
- if ctx.isRequestSigned() {
- ctx.Time = currentTimeFn()
- ctx.handlePresignRemoval()
- }
- var err error
- ctx.credValues, err = v4.Credentials.Get()
- if err != nil {
- return http.Header{}, err
- }
- ctx.sanitizeHostForHeader()
- ctx.assignAmzQueryValues()
- if err := ctx.build(v4.DisableHeaderHoisting); err != nil {
- return nil, err
- }
- // If the request is not presigned the body should be attached to it. This
- // prevents the confusion of wanting to send a signed request without
- // the body the request was signed for attached.
- if !(v4.DisableRequestBodyOverwrite || ctx.isPresign) {
- var reader io.ReadCloser
- if body != nil {
- var ok bool
- if reader, ok = body.(io.ReadCloser); !ok {
- reader = ioutil.NopCloser(body)
- }
- }
- r.Body = reader
- }
- if v4.Debug.Matches(aws.LogDebugWithSigning) {
- v4.logSigningInfo(ctx)
- }
- return ctx.SignedHeaderVals, nil
- }
- func (ctx *signingCtx) sanitizeHostForHeader() {
- request.SanitizeHostForHeader(ctx.Request)
- }
- func (ctx *signingCtx) handlePresignRemoval() {
- if !ctx.isPresign {
- return
- }
- // The credentials have expired for this request. The current signing
- // is invalid, and needs to be request because the request will fail.
- ctx.removePresign()
- // Update the request's query string to ensure the values stays in
- // sync in the case retrieving the new credentials fails.
- ctx.Request.URL.RawQuery = ctx.Query.Encode()
- }
- func (ctx *signingCtx) assignAmzQueryValues() {
- if ctx.isPresign {
- ctx.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
- if ctx.credValues.SessionToken != "" {
- ctx.Query.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
- } else {
- ctx.Query.Del("X-Amz-Security-Token")
- }
- return
- }
- if ctx.credValues.SessionToken != "" {
- ctx.Request.Header.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
- }
- }
- // SignRequestHandler is a named request handler the SDK will use to sign
- // service client request with using the V4 signature.
- var SignRequestHandler = request.NamedHandler{
- Name: "v4.SignRequestHandler", Fn: SignSDKRequest,
- }
- // SignSDKRequest signs an AWS request with the V4 signature. This
- // request handler should only be used with the SDK's built in service client's
- // API operation requests.
- //
- // This function should not be used on its on its own, but in conjunction with
- // an AWS service client's API operation call. To sign a standalone request
- // not created by a service client's API operation method use the "Sign" or
- // "Presign" functions of the "Signer" type.
- //
- // If the credentials of the request's config are set to
- // credentials.AnonymousCredentials the request will not be signed.
- func SignSDKRequest(req *request.Request) {
- SignSDKRequestWithCurrentTime(req, time.Now)
- }
- // BuildNamedHandler will build a generic handler for signing.
- func BuildNamedHandler(name string, opts ...func(*Signer)) request.NamedHandler {
- return request.NamedHandler{
- Name: name,
- Fn: func(req *request.Request) {
- SignSDKRequestWithCurrentTime(req, time.Now, opts...)
- },
- }
- }
- // SignSDKRequestWithCurrentTime will sign the SDK's request using the time
- // function passed in. Behaves the same as SignSDKRequest with the exception
- // the request is signed with the value returned by the current time function.
- func SignSDKRequestWithCurrentTime(req *request.Request, curTimeFn func() time.Time, opts ...func(*Signer)) {
- // 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
- }
- v4 := NewSigner(req.Config.Credentials, func(v4 *Signer) {
- v4.Debug = req.Config.LogLevel.Value()
- v4.Logger = req.Config.Logger
- v4.DisableHeaderHoisting = req.NotHoist
- v4.currentTimeFn = curTimeFn
- if name == "s3" {
- // S3 service should not have any escaping applied
- v4.DisableURIPathEscaping = true
- }
- // Prevents setting the HTTPRequest's Body. Since the Body could be
- // wrapped in a custom io.Closer that we do not want to be stompped
- // on top of by the signer.
- v4.DisableRequestBodyOverwrite = true
- })
- for _, opt := range opts {
- opt(v4)
- }
- curTime := curTimeFn()
- signedHeaders, err := v4.signWithBody(req.HTTPRequest, req.GetBody(),
- name, region, req.ExpireTime, req.ExpireTime > 0, curTime,
- )
- if err != nil {
- req.Error = err
- req.SignedHeaderVals = nil
- return
- }
- req.SignedHeaderVals = signedHeaders
- req.LastSignedAt = curTime
- }
- const logSignInfoMsg = `DEBUG: Request Signature:
- ---[ CANONICAL STRING ]-----------------------------
- %s
- ---[ STRING TO SIGN ]--------------------------------
- %s%s
- -----------------------------------------------------`
- const logSignedURLMsg = `
- ---[ SIGNED URL ]------------------------------------
- %s`
- func (v4 *Signer) logSigningInfo(ctx *signingCtx) {
- signedURLMsg := ""
- if ctx.isPresign {
- signedURLMsg = fmt.Sprintf(logSignedURLMsg, ctx.Request.URL.String())
- }
- msg := fmt.Sprintf(logSignInfoMsg, ctx.canonicalString, ctx.stringToSign, signedURLMsg)
- v4.Logger.Log(msg)
- }
- func (ctx *signingCtx) build(disableHeaderHoisting bool) error {
- ctx.buildTime() // no depends
- ctx.buildCredentialString() // no depends
- if err := ctx.buildBodyDigest(); err != nil {
- return err
- }
- unsignedHeaders := ctx.Request.Header
- if ctx.isPresign {
- if !disableHeaderHoisting {
- urlValues := url.Values{}
- urlValues, unsignedHeaders = buildQuery(allowedQueryHoisting, unsignedHeaders) // no depends
- for k := range urlValues {
- ctx.Query[k] = urlValues[k]
- }
- }
- }
- ctx.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders)
- ctx.buildCanonicalString() // depends on canon headers / signed headers
- ctx.buildStringToSign() // depends on canon string
- ctx.buildSignature() // depends on string to sign
- if ctx.isPresign {
- ctx.Request.URL.RawQuery += "&X-Amz-Signature=" + ctx.signature
- } else {
- parts := []string{
- authHeaderPrefix + " Credential=" + ctx.credValues.AccessKeyID + "/" + ctx.credentialString,
- "SignedHeaders=" + ctx.signedHeaders,
- "Signature=" + ctx.signature,
- }
- ctx.Request.Header.Set("Authorization", strings.Join(parts, ", "))
- }
- return nil
- }
- func (ctx *signingCtx) buildTime() {
- ctx.formattedTime = ctx.Time.UTC().Format(timeFormat)
- ctx.formattedShortTime = ctx.Time.UTC().Format(shortTimeFormat)
- if ctx.isPresign {
- duration := int64(ctx.ExpireTime / time.Second)
- ctx.Query.Set("X-Amz-Date", ctx.formattedTime)
- ctx.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
- } else {
- ctx.Request.Header.Set("X-Amz-Date", ctx.formattedTime)
- }
- }
- func (ctx *signingCtx) buildCredentialString() {
- ctx.credentialString = strings.Join([]string{
- ctx.formattedShortTime,
- ctx.Region,
- ctx.ServiceName,
- "aws4_request",
- }, "/")
- if ctx.isPresign {
- ctx.Query.Set("X-Amz-Credential", ctx.credValues.AccessKeyID+"/"+ctx.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 (ctx *signingCtx) 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 ctx.SignedHeaderVals == nil {
- ctx.SignedHeaderVals = make(http.Header)
- }
- lowerCaseKey := strings.ToLower(k)
- if _, ok := ctx.SignedHeaderVals[lowerCaseKey]; ok {
- // include additional values
- ctx.SignedHeaderVals[lowerCaseKey] = append(ctx.SignedHeaderVals[lowerCaseKey], v...)
- continue
- }
- headers = append(headers, lowerCaseKey)
- ctx.SignedHeaderVals[lowerCaseKey] = v
- }
- sort.Strings(headers)
- ctx.signedHeaders = strings.Join(headers, ";")
- if ctx.isPresign {
- ctx.Query.Set("X-Amz-SignedHeaders", ctx.signedHeaders)
- }
- headerValues := make([]string, len(headers))
- for i, k := range headers {
- if k == "host" {
- if ctx.Request.Host != "" {
- headerValues[i] = "host:" + ctx.Request.Host
- } else {
- headerValues[i] = "host:" + ctx.Request.URL.Host
- }
- } else {
- headerValues[i] = k + ":" +
- strings.Join(ctx.SignedHeaderVals[k], ",")
- }
- }
- stripExcessSpaces(headerValues)
- ctx.canonicalHeaders = strings.Join(headerValues, "\n")
- }
- func (ctx *signingCtx) buildCanonicalString() {
- ctx.Request.URL.RawQuery = strings.Replace(ctx.Query.Encode(), "+", "%20", -1)
- uri := getURIPath(ctx.Request.URL)
- if !ctx.DisableURIPathEscaping {
- uri = rest.EscapePath(uri, false)
- }
- ctx.canonicalString = strings.Join([]string{
- ctx.Request.Method,
- uri,
- ctx.Request.URL.RawQuery,
- ctx.canonicalHeaders + "\n",
- ctx.signedHeaders,
- ctx.bodyDigest,
- }, "\n")
- }
- func (ctx *signingCtx) buildStringToSign() {
- ctx.stringToSign = strings.Join([]string{
- authHeaderPrefix,
- ctx.formattedTime,
- ctx.credentialString,
- hex.EncodeToString(makeSha256([]byte(ctx.canonicalString))),
- }, "\n")
- }
- func (ctx *signingCtx) buildSignature() {
- secret := ctx.credValues.SecretAccessKey
- date := makeHmac([]byte("AWS4"+secret), []byte(ctx.formattedShortTime))
- region := makeHmac(date, []byte(ctx.Region))
- service := makeHmac(region, []byte(ctx.ServiceName))
- credentials := makeHmac(service, []byte("aws4_request"))
- signature := makeHmac(credentials, []byte(ctx.stringToSign))
- ctx.signature = hex.EncodeToString(signature)
- }
- func (ctx *signingCtx) buildBodyDigest() error {
- hash := ctx.Request.Header.Get("X-Amz-Content-Sha256")
- if hash == "" {
- includeSHA256Header := ctx.unsignedPayload ||
- ctx.ServiceName == "s3" ||
- ctx.ServiceName == "glacier"
- s3Presign := ctx.isPresign && ctx.ServiceName == "s3"
- if ctx.unsignedPayload || s3Presign {
- hash = "UNSIGNED-PAYLOAD"
- includeSHA256Header = !s3Presign
- } else if ctx.Body == nil {
- hash = emptyStringSHA256
- } else {
- if !aws.IsReaderSeekable(ctx.Body) {
- return fmt.Errorf("cannot use unseekable request body %T, for signed request with body", ctx.Body)
- }
- hash = hex.EncodeToString(makeSha256Reader(ctx.Body))
- }
- if includeSHA256Header {
- ctx.Request.Header.Set("X-Amz-Content-Sha256", hash)
- }
- }
- ctx.bodyDigest = hash
- return nil
- }
- // isRequestSigned returns if the request is currently signed or presigned
- func (ctx *signingCtx) isRequestSigned() bool {
- if ctx.isPresign && ctx.Query.Get("X-Amz-Signature") != "" {
- return true
- }
- if ctx.Request.Header.Get("Authorization") != "" {
- return true
- }
- return false
- }
- // unsign removes signing flags for both signed and presigned requests.
- func (ctx *signingCtx) removePresign() {
- ctx.Query.Del("X-Amz-Algorithm")
- ctx.Query.Del("X-Amz-Signature")
- ctx.Query.Del("X-Amz-Security-Token")
- ctx.Query.Del("X-Amz-Date")
- ctx.Query.Del("X-Amz-Expires")
- ctx.Query.Del("X-Amz-Credential")
- ctx.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, sdkio.SeekCurrent)
- defer reader.Seek(start, sdkio.SeekStart)
- // Use CopyN to avoid allocating the 32KB buffer in io.Copy for bodies
- // smaller than 32KB. Fall back to io.Copy if we fail to determine the size.
- size, err := aws.SeekerLen(reader)
- if err != nil {
- io.Copy(hash, reader)
- } else {
- io.CopyN(hash, reader, size)
- }
- return hash.Sum(nil)
- }
- const doubleSpace = " "
- // stripExcessSpaces will rewrite the passed in slice's string values to not
- // contain multiple side-by-side spaces.
- func stripExcessSpaces(vals []string) {
- var j, k, l, m, spaces int
- for i, str := range vals {
- // Trim trailing spaces
- for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
- }
- // Trim leading spaces
- for k = 0; k < j && str[k] == ' '; k++ {
- }
- str = str[k : j+1]
- // Strip multiple spaces.
- j = strings.Index(str, doubleSpace)
- if j < 0 {
- vals[i] = str
- continue
- }
- buf := []byte(str)
- for k, m, l = j, j, len(buf); k < l; k++ {
- if buf[k] == ' ' {
- if spaces == 0 {
- // First space.
- buf[m] = buf[k]
- m++
- }
- spaces++
- } else {
- // End of multiple spaces.
- spaces = 0
- buf[m] = buf[k]
- m++
- }
- }
- vals[i] = string(buf[:m])
- }
- }
|