amazonka-core-2.0: src/Amazonka/Sign/V4.hs
-- |
-- Module : Amazonka.Sign.V4
-- Copyright : (c) 2013-2023 Brendan Hay
-- License : Mozilla Public License, v. 2.0.
-- Maintainer : Brendan Hay <brendan.g.hay+amazonka@gmail.com>
-- Stability : provisional
-- Portability : non-portable (GHC extensions)
module Amazonka.Sign.V4
( Base.V4 (..),
v4,
)
where
import Amazonka.Bytes
import Amazonka.Core.Lens.Internal ((<>~))
import Amazonka.Data.Body
import Amazonka.Data.ByteString
import Amazonka.Data.Headers
import Amazonka.Data.Query
import Amazonka.Data.Time
import Amazonka.Prelude
import Amazonka.Request
import qualified Amazonka.Sign.V4.Base as Base
import qualified Amazonka.Sign.V4.Chunked as Chunked
import Amazonka.Types hiding (presign, sign)
import qualified Data.ByteString as BS
import qualified Data.CaseInsensitive as CI
v4 :: Signer
v4 = Signer sign presign
-- |
-- Presigns a URL according to the AWS Request Signature V4 spec <https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html>.
-- In the case that the URL contains a payload that is not signed when sending requests to Amazon S3, a literal `UNSIGNED-PAYLOAD`
-- must be included when constructing the cannonical request. See <https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html>
-- In the edge case that the request body is a @Amazonka.Data.Body.ChunkedBody@ we will also use the `UNSIGNED-PAYLOAD` literal as we won't consume the stream
-- to hash it.
presign :: Seconds -> Algorithm a
presign ex rq@Request {body, service} a region ts =
Base.signRequest meta mempty auth
where
auth = clientRequestQuery <>~ ("&X-Amz-Signature=" <> toBS (Base.metaSignature meta))
meta = Base.signMetadata a region ts presigner digest (prepare rq)
presigner c shs =
pair (CI.original hAMZAlgorithm) Base.algorithm
. pair (CI.original hAMZCredential) (toBS c)
. pair (CI.original hAMZDate) (Time ts :: AWSTime)
. pair (CI.original hAMZExpires) ex
. pair (CI.original hAMZSignedHeaders) (toBS shs)
. pair (CI.original hAMZToken) (toBS <$> sessionToken a)
digest =
case body of
Chunked _ -> unsignedPayload
Hashed (HashedStream h _ _) -> Base.Tag $ encodeBase16 h
Hashed (HashedBytes h b)
| BS.null b && signingName service == "s3" -> unsignedPayload
| otherwise -> Base.Tag $ encodeBase16 h
unsignedPayload = Base.Tag "UNSIGNED-PAYLOAD"
prepare :: Request a -> Request a
prepare r@Request {headers} = r {headers = hdr hHost realHost headers}
realHost =
case (secure, port) of
(False, 80) -> host
(True, 443) -> host
_ -> mconcat [host, ":", toBS port]
Endpoint {host, port, secure} = endpoint service region
sign :: Algorithm a
sign rq@Request {body} a r ts =
case body of
Chunked x -> Chunked.chunked x rq a r ts
Hashed x -> hashed x rq a r ts
hashed :: HashedBody -> Algorithm a
hashed x rq a r ts =
let (meta, auth) = Base.base (Base.Tag (sha256Base16 x)) rq a r ts
in Base.signRequest meta (toRequestBody (Hashed x)) auth