【发布时间】: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