使用Haskell签名临时s3上载URL

使用Haskell签名临时s3上载URL,haskell,encryption,amazon-web-services,amazon-s3,Haskell,Encryption,Amazon Web Services,Amazon S3,我正在尝试将文件从web表单直接异步上传到AmazonS3。为了做到这一点,我必须验证客户端在服务器上上传文件的请求 通过使用我的AWS密钥对上传请求进行数字签名,我可以创建一个临时的经过身份验证的URL,客户端可以使用该URL将文件上传到S3存储桶 AmazonS3文档规定签名必须由以下程序生成 Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID,

我正在尝试将文件从web表单直接异步上传到AmazonS3。为了做到这一点,我必须验证客户端在服务器上上传文件的请求

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

AmazonS3文档规定签名必须由以下程序生成

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"
https://s3.amazonaws.com/bucketname/objname?AWSAccessKeyId=accessskey&Signature=signature=&Expires=1384330979
amazon文档的格式要求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
所以我的字符串看起来像:

{-# 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"
https://s3.amazonaws.com/bucketname/objname?AWSAccessKeyId=accessskey&Signature=signature=&Expires=1384330979
“放置\n\n\n1384330538\n/bucketname/objname”

我在上面的字符串上签名(使用签名函数),并创建一个如下所示的url:

{-# 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"
https://s3.amazonaws.com/bucketname/objname?AWSAccessKeyId=accessskey&Signature=signature=&Expires=1384330979
然后在上传之前通过AJAX请求将其发送到客户端。我还更新了桶上的CORS策略,以允许PUT请求

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

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


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

作为一个经常这样做的人,很难构建这样的软件来正确签署HTTP摘要认证请求。特别是,如果您仅依赖服务器响应来指导您,则需要很长时间。出于安全目的,服务器在拒绝您时故意隐藏

我最好的建议是(a)获得一个您知道有效的替代实现,(b)将Haskell接口构建为纯接口,这样就可以很容易地精确地复制来自另一个框架的请求,(c)确保您可以从替代框架和您自己的代码中获得要签名的确切请求文本和字符串。特别是,您通常必须输入准确的时间戳和非时间戳,并密切注意百分比编码

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


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

这是我的上传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}|]
{-#语言重载字符串、FlexibleContext、TypeFamilies、DeriveDataTypeable、TemplateHaskell、Quasikotes#-}
进口合格Aws
导入合格的Aws.Core作为Aws
将合格的Aws.S3导入为S3
导入符合条件的数据。文本为T
将合格的Codec.Binary.Base64导入为B64
将符合条件的数据.ByteString作为BS导入
导入Text.Shakespeare.Text(st)
将符合条件的Codec.Binary.Url导入为Url
导入System.Posix.Time(epochTime)
导入Crypto.MAC.HMAC(HMAC)
导入Crypto.Hash.SHA1(哈希)
数据Cfg=Cfg{baseCfg::Aws.Configuration
,s3Cfg::S3.S3配置Aws.NormalQuery
,s3Bucket::S3.Bucket
}
上传URL::Cfg->T.Text->T.Text->IO T.Text
上传URL cfg mime文件名=do

请尝试交替检查您的签名(例如使用PHP)。它是否有效?真的很感激。我只是不想上传通过服务器。我接受了你的建议,做了一个python实现,我让它们匹配。我不得不使用Data.ByteString.Base64.Lazy(而不是Base64.URL)。但是仍然在服务器端得到403:/python代码:binascii.b2a_base64(hmac.new(“secret”,“PUT\n\n\n1000\n/bucket/obj”,sha1.digest())产生:7/gYazzt/I3iWG+pBsc1AtlNwA0=haskell代码产生:B64.encode$bytestringDigest$hmacSha1“secret”\n\n\n1000\n/bucket/obj==7/gYazzt/I3iWG+pBsc1AtlNwA0=确保Haskell库发送的请求与Python请求匹配。例如,如果您使用的是
http管道
,它会自动为您添加一些标题(
主机
内容长度
,如果我的内存可用,可能
内容接受
)。如果您的签名中没有考虑这些标题,那么请求将与签名不匹配。。。即使正确计算了要签名的字符串,我也会通过ajax检索签名的url,然后提交一个xhr.open('PUT',signed_url);xhr.send(fd);(fd是一个文件数组)一旦我从服务器取回它。这是一个异步上传。我的请求是这样的:末尾的“=”符号需要在那里吗?(这两种方法都不起作用,我试过)在heroku python文档中,他试图删除它,但[:-1]没有删除它。。。我欺骗了我的localhost(编辑的/etc/hosts)来显示该域。s3文档中的请求非常少。签名末尾的
=
是绝对必要的,它是base64编码的一部分。恐怕要从外界得到帮助是非常困难的。设置摘要签名以使其正常工作通常是相当棘手的。看起来您的签名工作正常,所以我唯一的猜测是它与实际发送的HTTP请求不正确对应。最终得到:)但没有明显的流血和流泪。一些咬牙切齿等。你能为下载做同样的事情吗,比如获取请求?是的,只是把PUT改为GET。