【问题标题】:Brightscript generated signed URLs for CloudFront yield "Access Denied"Brightscript 为 CloudFront 生成签名 URL 产生“拒绝访问”
【发布时间】:2019-11-03 19:00:12
【问题描述】:

我正在使用 Roku 频道,我们希望将文件托管在 AWS S3 存储桶中,并使用 CloudFront 来分发内容。在考虑安全性之前,它工作得很好。但是,现在我正试图注意安全问题,我遇到了问题。我将 S3 存储桶作为私有存储桶(不能公开访问其中的任何内容),并为 CloudFront 分配创建了源访问身份,以便它可以访问存储桶中的内容。

问题是,我无法在频道亮脚本代码中创建过期的签名 URL(或 cookie)来访问内容。我可以通过命令行使用Amazon's perl script 创建一个签名的URL,如果我复制/粘贴链接的签名部分,它会给我在brightscript 中创建的URL 的签名部分,(替换签名)它可以工作。当然,这是因为 URL 的其他所有内容都是相同的,所以一旦我替换了签名,我就只有另一个 URL。所以我知道(至少我认为我可以肯定地说)问题出在签名上。我按照AWS' documentation 中指示的步骤操作,但它总是返回“拒绝访问”错误消息。

我唯一遗漏的签名过程是 base 64 编码。我已经尝试对brightscript使用this site创建的签名进行base 64编码并更新URL并尝试它,但仍然没有运气。我觉得这与brightscript 如何散列或签署事物有关。我在Stack Overflow post 中看到,openssl(这是 perl 脚本用来散列/签名的)也在签名之前编码为 ASN.1...我也尝试过修改它,看看我是否可以得到它包括那一步的工作,但也没有运气。

也许我做得不对,或者这不是问题所在。我知道有些人使用 S3 和 CloudFront 来托管 Roku 频道的内容,所以我不知道为什么它不应该工作。希望有人能提供一些启示......如果有人知道解决方案,我会很高兴听到它!

编辑: 我意识到我从 byteArray 转换为 string 的方式是错误的!我从这里改变了它:

  //To convert from byteArray to string
  signatureString = ""
  for each byte in signature
      signatureString = signatureString + stri(byte)
  end for

对此:

  sigString = signature.ToAsciiString()
  print "sigString: ";sigString
  signatureString = signature.ToBase64String()

很遗憾,我仍然被拒绝访问。但是,至少现在我的 URL 看起来像 perl 脚本创建的 URL——在签名部分只是一堆数字之前。另外,我现在也是 base 64 编码签名。我觉得我可能越来越近了! :)

编辑 2: 我在 Roku 论坛上打开了一个话题,其中有一些活动:https://forums.roku.com/viewtopic.php?t=54797 我发现策略字符串不正确——它在“资源”之前有“条件”(由于 JSON 从 roAssociativeArray 解析它的结果)。我能够获得正确的策略字符串,但仍然被拒绝访问。

这是我用来创建签名 URL 的代码:

  readInternet = createObject("roUrlTransfer")


  policy = { "Statement": [
                  {
                     "Resource":"http://XXXXXXXXXXXXX.cloudfront.net/icon_focus_sd.png",
                     "Condition": {
                         "DateLessThan": {
                               "AWS:EpochTime": 1561230905
                          }
                      }
                   }
  ]
  }

  policyString = FormatJson(policy)
  print "policyString: ";policyString

  //UPDATE: correct policy string now:
  policyString = "{" + Chr(34) + "Statement" + Chr(34) + ":[{" + Chr(34) + "Resource" + Chr(34) + ":" + Chr(34) + "http://d1uuhuldzrqhow.cloudfront.net/icon_focus_sd.png" + Chr(34) + "," + Chr(34) + "Condition" + Chr(34) + ":{" + Chr(34) + "DateLessThan" + Chr(34) + ":{" + Chr(34) + "AWS:EpochTime" + Chr(34) + ":1561230905}}}]}"
  ba.FromAsciiString(policyString)
  print "New policy string: ";policyString

  ba = CreateObject("roByteArray")
  ba.FromAsciiString(policyString)

  digest = CreateObject("roEVPDigest")
  digest.Setup("sha1")
  hashString = digest.Process(ba)
  print "hashString: ";hashString

  hashBA = CreateObject("roByteArray")
  hashBA.FromHexString(hashString)

  rsa = CreateObject("roRSA")  
  rsa.setPrivateKey("pkg:/components/key/privateKey.pem")
  rsa.SetDigestAlgorithm("sha1")

  signature = rsa.Sign(hashBA)

  //EDIT! The following 3 lines are a big development!
  sigString = signature.ToAsciiString()
  print "sigString: ";sigString
  signatureString = signature.ToBase64String()

  //To convert from byteArray to string --Commented this part out as it was WRONG!!!
  //signatureString = ""
  //for each byte in signature
  //    signatureString = signatureString + stri(byte)
  //end for

  print "Signature: ";signature
  print "SignatureString: ";signatureString

  baseURL = policy.statement[0].resource
  print "BaseURL: ";baseURL
  fixedSignatureString = signatureString.replace(" ", "").replace("=", "_").replace("/", "~").replace("+", "-")


  dateKeys = policy.statement[0].condition.datelessthan.Keys()
  print"dateKeys: ";dateKeys
  print"dateKey: ";dateKeys[0]

  epochTime = policy.statement[0].condition.dateLessThan.Lookup(dateKeys[0])

  finalURL = baseURL + "?Expires=" + stri(epochTime).Replace(" ","") + "&Signature=" + fixedSignatureString + "&Key-Pair-Id=APKXXXXXXXXXXVWQ"
  print "finalURL: ";finalURL


  readInternet.setUrl(finalURL)

  readInternet.SetCertificatesFile("common:/certs/ca-bundle.crt")
  readInternet.AddHeader("X-Roku-Reserved-Dev-Id", "")
  readInternet.InitClientCertificates()

  readInternet.RetainBodyOnError(true)
  response = ParseJson(readInternet.GetToString())

  print "response:" response

我也尝试使用它来创建签名 cookie:

cookies = [
   {Name:"CloudFront-Policy",Value:policy,Path:"/", Domain:"XXXXXXXXXX.cloudfront.net"},
   {Name:"CloudFront-Expires",Value:"1561230905",Path:"/", Domain:"XXXXXXXXXX.cloudfront.net"},
   {Name:"CloudFront-Signature",Value:fixedSignatureString,Path:"/", Domain:"XXXXXXXXXX.cloudfront.net"},
   {Name:"CloudFront-Key-Pair-Id",Value:"APKAXXXXXXXXXXAVWQ", Path:"/", Domain:"XXXXXXXXXX.cloudfront.net"}
]

readInternet.EnableCookies()
readInternet.AddCookies(cookies)

我也尝试了以下方法,而不是之前添加 cookie 的方法:

expires = "CloudFront-Expires=1561146117" //I have been careful to make sure the expire times are still good
sig = "CloudFront-Signature=" + fixedSignatureString
pairid = "CloudFront-Key-Pair-Id=APKAXXXXXXXXXXVWQ"
readInternet.AddHeader("Cookie",expires + "; " + sig + "; " + pairid)

【问题讨论】:

  • 我与 IIS 托管的 SSL 证书有同样的问题。但现在我想一些不同的东西,我的代码在自托管中发生了变化。所以,不需要任何签名。
  • 如果您对多个 url 使用相同的签名 url,您需要使用自定义策略并使用资源作为 example.com* 并为此创建一个签名 url,这将适用于所有 URI .
  • @JamesDean 现在我只是在测试,所以我只有一个要访问的文件。策略中的资源值为XXXXXXXX.cloudfront.net/icon_focus_sd.png。您是说我应该在策略中省略我尝试访问的特定文件的名称吗?我不打算对多个文件使用相同的 URL,我的计划是每次我需要访问一个文件时创建一个新的签名 URL,并让这些 URL 尽快过期而不会造成问题。
  • 不,如果你做得对,在创建签名 url 时应该完全定义资源值(我说的是罐装策略与自定义策略),这个访问被拒绝是来自 CloudFront 还是 S3 的错误?如果它来自 S3,您将看到带有两个长请求 ID 的 xml,否则如果它是 CloudFront,则会在 html 中看到一个简单的访问被拒绝错误。
  • 应该来自 CloudFront:AccessDeniedAccess denied错误>

标签: amazon-s3 amazon-cloudfront roku brightscript


【解决方案1】:

我终于解决了这个问题!我开始只是检查(再次,但更仔细地)并查看使用 BrightScript 创建签名 URL 和使用 perl 脚本创建签名 URL 时的相同之处和不同之处。当我添加到原始问题帖子中时,我以错误的方式将签名从 byteArray 更改为字符串。解决这个问题是第一步。一旦我弄清楚这一点,我注意到当我从命令行(而不是从 perl 脚本)签署 URL 时,它会得到与从 BrightScript 获得的相同(非工作)签名。然后我发现我使用了错误的私钥! (我知道 - 很棒,对吗?)切换到使用正确的密钥,现在一切正常!我只需要将它全部动态化,以便它适用于我的所有文件并动态设置到期时间。

这里是我学到的总结:

  • 确保策略字符串正确 - 顺序很重要!使用formatJSON(roAssociativeArray) 会破坏它,因为它按字母顺序排列。
  • 对 roByteArray 中的每个字节进行字符串化并不能正确地将其转换为字符串!!相反,请使用 .ToAsciiString().ToBase64String(),具体取决于您的需要。
  • 事实证明,使用正确的密钥很重要 - 谁知道呢。 *插入手掌*

这是我的代码,现在它正在运行:

    readInternet = createObject("roUrlTransfer")

    ba = CreateObject("roByteArray")
    policyString = "{" + Chr(34) + "Statement" + Chr(34) + ":[{" + Chr(34) + "Resource" + Chr(34) + ":" + Chr(34) + "http://XXXXXXXXX.cloudfront.net/icon_focus_sd.png" + Chr(34) + "," + Chr(34) + "Condition" + Chr(34) + ":{" + Chr(34) + "DateLessThan" + Chr(34) + ":{" + Chr(34) + "AWS:EpochTime" + Chr(34) + ":1561230905}}}]}"
    ba.FromAsciiString(policyString)
    print "New policy string: ";policyString

    digest = CreateObject("roEVPDigest")
    digest.Setup("sha1")

    hashString = digest.Process(ba)
    print "hashString: ";hashString
    hashBA = CreateObject("roByteArray")
    hashBA.FromHexString(hashString)

    rsa = CreateObject("roRSA")
    rsa.setPrivateKey("pkg:/components/key/privateKey.pem")
    rsa.SetDigestAlgorithm("sha1")

    signature = rsa.Sign(hashBA)
    signatureString = signature.ToBase64String()

    print "Signature: ";signature
    print "SignatureString: ";signatureString

    baseURL = "http://XXXXXXXXXXX.cloudfront.net/icon_focus_sd.png"
    print "BaseURL: ";baseURL
    fixedSignatureString = signatureString.replace(" ", "").replace("=", "_").replace("/", "~").replace("+", "-")

    epochTime = 1561230905

    finalURL = baseURL + "?Expires=" + stri(epochTime).Replace(" ","") + "&Signature=" + fixedSignatureString + "&Key-Pair-Id=APKAXXXXXXXXXXXVWQ"
    print "finalURL: ";finalURL

    readInternet.setUrl(finalURL)

    readInternet.SetCertificatesFile("common:/certs/ca-bundle.crt")
    readInternet.AddHeader("X-Roku-Reserved-Dev-Id", "")
    readInternet.InitClientCertificates()

    readInternet.RetainBodyOnError(true)
    response = ParseJson(readInternet.GetToString())


感谢所有参与/提出建议的人。感谢您的支持。

【讨论】:

  • 请注意,JSON 字符串中的顺序和格式仅在您生成固定策略时才重要,因为您实际所做的是构建 canonical 策略语句,排序是标准化的,还有空格。如果您使用自定义策略,则没有必要这样做,但签名的 URL 当然要长得多。
猜你喜欢
  • 1970-01-01
  • 2018-01-16
  • 2019-08-30
  • 2015-05-21
  • 2013-01-03
  • 2015-02-13
  • 2017-10-03
  • 2021-09-07
  • 2018-09-18
相关资源
最近更新 更多