Сгенерированные Brightscript подписанные URL-адреса для CloudFront дают «Доступ запрещен» - PullRequest
2 голосов
/ 21 июня 2019

Я работаю на канале Roku, и мы хотим, чтобы файлы размещались в корзине AWS S3 с CloudFront для распространения контента. Прежде чем рассматривать безопасность, он работал нормально. Однако теперь, когда я пытаюсь помнить о проблемах безопасности, у меня возникают проблемы. У меня есть корзина S3 как частная корзина (в ней нет открытого доступа к чему-либо), и я создал исходный идентификатор доступа для дистрибутива CloudFront, чтобы он мог получить доступ к содержимому корзины.

Проблема в том, что я не могу создать устаревшие подписанные URL-адреса (или файлы cookie) в коде brightscript канала, чтобы получить доступ к контенту. Я могу создать подписанный URL-адрес с помощью сценария Amazon perl через командную строку, и если я скопирую / вставлю часть подписи ссылки, она выдаст меня в часть подписи URL-адреса, который я создаю в brightscript (заменяя подпись) это работает. Конечно, это потому, что все остальное в URL-адресах идентично, поэтому после замены подписи у меня просто остается другой URL-адрес. Так что я знаю (по крайней мере, я думаю, что могу с уверенностью сказать), что проблема с подписью. Я выполняю действия, указанные в документации AWS , но она всегда возвращается с сообщением об ошибке «Отказано в доступе».

Единственная часть процесса подписи, которую я пропустил, - это кодировка base 64. Я пробовал base 64, кодирующую подпись, которую создает brightscript, используя этот сайт и обновляя URL и пробуя его, но все же не повезло. Я чувствую, что это как-то связано с тем, как brightscript хэширует или подписывает вещи. В сообщении Stack Overflow я видел, что openssl (то, что сценарий perl использует для хеширования / подписи) также кодирует в ASN.1 перед подписанием ... Я пытался с этим поработать, чтобы увидеть если бы я мог заставить его работать, включая этот шаг, но мне тоже не повезло.

Может быть, я не делаю это правильно, или, может быть, это не проблема. Я знаю, что некоторые люди используют S3 и CloudFront для размещения контента для каналов Roku, поэтому я не знаю, почему он не должен работать. Надеюсь, кто-то там может пролить немного света ... Если кто-то знает решение, я был бы рад услышать его!

Edit: Я понял, что способ преобразования байтового массива в строку был НЕПРАВИЛЬНЫМ! Я изменил это из этого:

  //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 выглядят как URL, которые создает скрипт perl - до того, как часть подписи была просто набором цифр. Кроме того, теперь я использую кодировку подписи base 64. Я чувствую, что могу быть ближе! :)

Редактировать 2: У меня есть открытая тема на форумах Roku с небольшой активностью: https://forums.roku.com/viewtopic.php?t=54797 Я обнаружил, что строка политики была неправильной - у нее было «Condition» перед «Resource» (в результате анализа 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)

1 Ответ

1 голос
/ 21 июня 2019

Я наконец-то вылечил это! Я начал просто проходить (снова, но более тщательно) и видеть то же самое и что отличалось при создании подписанного URL-адреса с помощью BrightScript и создании одного с помощью сценария perl. Добавляя к исходному сообщению с вопросом, я неправильно менял сигнатуру с byteArray на строку. Исправление было первым шагом. Как только я понял это, я заметил, что когда я подписывал URL-адрес из командной строки (а не из сценария perl), он получал ту же (нерабочую) подпись, которую я получал от 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())


Спасибо всем, кто участвовал / давал предложения. Я ценю поддержку.

...