Brightscript 为 CloudFront 生成的签名 URL 产量 "Access Denied"

Brightscript generated signed URLs for CloudFront yield "Access Denied"

我正在使用 Roku 频道,我们希望将文件托管在 AWS S3 存储桶中,并使用 CloudFront 来分发内容。在考虑安全性之前,它运行良好。但是,现在我正在努力注意安全问题,但遇到了问题。我将 S3 存储桶作为私有存储桶(无法 public 访问其中的任何内容)并且我为 CloudFront 分配创建了一个原始访问身份,以便它可以访问存储桶中的内容。

问题是,我无法在频道 brightscript 代码中创建过期签名的 URLs(或 cookie)来访问内容。我可以使用 Amazon's perl script via the command line, and if I copy/paste the signature portion of the link it gives me into the signature portion of the URL I create in brightscript, (replacing the signature) it works. Of course, that's because everything else about the URLs is identical, so once I replace the signature I just have the other URL. So I know (at least I think I can safely say) that the problem is with the signature. I follow the steps indicated in AWS' documentation 创建签名的 URL,但它总是 returns 并带有 "Access Denied" 错误消息。

签名过程中我唯一遗漏的部分是 base 64 编码。我已经尝试使用 base 64 编码 brightscript 使用 this site and updating the URL and trying it, but still no luck. I'm feeling like it has something to do with how brightscript hashes or signs things. I saw in a Stack Overflow post 创建的签名,openssl(这是 perl 脚本用于 hash/sign 的)也在签名之前编码到 ASN.1 中......我试过修补也看看我是否可以让它工作,包括那一步,但也没有运气。

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

编辑: 我意识到我从 byteArray 转换为字符串的方式是错误的!我将其更改为:

  //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()

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

编辑 2: 我在 Roku 论坛上打开了一个主题 activity:https://forums.roku.com/viewtopic.php?t=54797 我发现策略字符串不正确 - 它在 "Resource" 之前有 "Condition"(作为 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)

我也尝试了下面的方法来代替之前的添加cookies的方法:

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)

我终于解决了这个问题!我开始只是(再次,但更仔细地)检查并查看在使用 BrightScript 创建签名的 URL 和使用 perl 脚本创建签名时的相同点和不同点。当我添加到原始问题 post 时,我以错误的方式将签名从 byteArray 更改为字符串。解决这个问题是第一步。一旦我弄明白了,我注意到当我从命令行(而不是从 perl 脚本)签署 URL 时,它会得到与我从中获得的相同的(non-working)签名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())


感谢所有 participated/gave 提出建议的人。感谢支持。