0

我正在尝试将文件从 Web 表单直接异步上传到 Amazon S3。为此,我必须验证客户端请求以在服务器上上传文件。

通过使用我的 AWS 密钥对上传请求进行数字签名,我可以创建一个经过身份验证的临时 URL,客户端可以使用该 URL 将文件上传到 S3 存储桶。

amazon S3 文档指定签名必须由以下生成

Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, 
                                         UTF-8-Encoding-Of( StringToSign ) ) ) );

我在服务器上使用 Haskell,所以我的实现如下所示:

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Base64.Lazy as B64
import qualified Data.Digest.Pure.SHA as SHA
import qualified Data.ByteString.Lazy.Char8 as BL8

sign :: BL8.ByteString -> BL8.ByteString
sign = B64.encode . SHA.bytestringDigest . SHA.hmacSha1 secret
  where secret = "aws-secret-key"

亚马逊文档的格式要求 StringToSign 看起来像:

StringToSign = HTTP-VERB + "\n" +
    Content-MD5 + "\n" +
    Content-Type + "\n" +
    Expires + "\n" +
    CanonicalizedAmzHeaders +
    CanonicalizedResource; 

亚马逊的另一个例子:

GET\n
\n
\n
1175139620\n

/johnsmith/photos/puppy.jpg

所以我的字符串看起来像:

"PUT\n\n\n1384330538\n/bucketname/objname"

我签署了上面的字符串(使用 sign 函数)并制作了一个如下所示的 url:

https://s3.amazonaws.com/bucketname/objname?AWSAccessKeyId=accessskey&Signature=signature=&Expires=1384330979

然后在上传之前通过 AJAX 请求将其发送到客户端。我还更新了存储桶上的 CORS 策略以允许 PUT 请求。

问题是,每次我尝试使用上述签名的 url 上传内容时,我都会收到此消息(在 XML 文档中)。

我们计算的请求签名与您提供的签名不匹配。检查您的密钥和签名方法。

所以我不确定我哪里出错了。注意:如果我使用公共 url ( https://s3.amazonaws.com/bucketname/objname ),我可以上传(但这不应该,我只希望用户上传 blob,而不是读取或删除等)

4

2 回答 2

1

作为一个经常这样做的人,很难构建像这样正确签署 HTTP-digest 认证请求的软件。特别是,如果你只依靠服务器响应来引导你,那将需要很长时间。出于安全目的,服务器在拒绝您时故意保持神秘。

我最好的建议是(a)获得一个你知道可行的替代实现,(b)将你的 Haskell 接口构建为纯粹的,以便很容易使其准确地复制来自其他框架的请求,并且(c)确保你可以从替代框架和您自己的代码中获取准确的请求文本和准确的String-To-Sign。特别是,您通常必须估算准确的时间戳和随机数,并密切注意百分比编码。

使用这两个工具,只需从替代实现中创建各种成功的请求,看看您是否可以使用您自己的框架复制准确的字符串签名和准确的请求文本。

大多数情况下,我自己的错误涉及不正确的编码、缺少引号、不包括所有正确的参数(或错误的参数),或者不正确地使用 hmac 函数。

于 2013-11-13T15:03:18.357 回答
0

这是我的上传 url 代码,自从我把它从深处拉出来后,我可能错过了几个导入。

{-# LANGUAGE OverloadedStrings, FlexibleContexts, TypeFamilies, DeriveDataTypeable, TemplateHaskell, QuasiQuotes #-}

import qualified Aws
import qualified Aws.Core          as Aws
import qualified Aws.S3            as S3
import qualified Data.Text         as T
import qualified Codec.Binary.Base64         as B64
import qualified Data.ByteString             as BS
import Text.Shakespeare.Text(st)
import qualified Codec.Binary.Url            as Url
import System.Posix.Time(epochTime)
import Crypto.MAC.HMAC(hmac)
import Crypto.Hash.SHA1(hash)

data Cfg = Cfg { baseCfg :: Aws.Configuration
               , s3Cfg :: S3.S3Configuration Aws.NormalQuery
               , s3Bucket :: S3.Bucket
               }

uploadUrl :: Cfg -> T.Text -> T.Text -> IO T.Text
uploadUrl cfg mime filename = do
   time <- epochTime
   let expires = show $ time + 600
       msg = E.encodeUtf8 $ [st|PUT

#{mime}
#{expires}
x-amz-acl:public-read
/#{s3Bucket cfg}/#{filename}|] --the gap is necessary
       key = Aws.secretAccessKey $ Aws.credentials $ baseCfg cfg
       accessid = T.pack $ Url.encode $ BS.unpack $ Aws.accessKeyID $ Aws.credentials $ baseCfg cfg
       signature = encode . T.pack $ B64.encode $ BS.unpack $ hmac hash 64 key msg
       encode = T.pack . Url.encode .  BS.unpack . E.encodeUtf8
   return $ [st|http://#{s3Bucket cfg}.s3.amazonaws.com/#{filename}?AWSAccessKeyId=#{accessid}&Expires=#{expires}&Signature=#{signature}|]
于 2014-01-11T08:16:17.943 回答