Мне нужно создать подписанные URL-адреса CloudFront с настраиваемой политикой с использованием PHP, но, независимо от того, что я делаю, моя политика явно «искажена».
Вот пример политики, сгенерированной в функции:
{"Statement":{"Resource":"https://d15xojelh58w5d.cloudfront.net/memo/kwz/cvyhkfdqn5oz0z1dz5at4z4s1jsn.kwz","Condition":{"DateLessThan":{"AWS:EpochTime":1490463203},"IpAddress":{"AWS:SourceIp":"1.2.3.4/32"}}}}
Сгенерированный URL:
https://d15xojelh58w5d.cloudfront.net/memo/kwz/cvyhkfdqn5oz0z1dz5at4z4s1jsn.kwz?Policy=eyJTdGF0ZW1lbnQiOnsiUmVzb3VyY2UiOiJodHRwczovL2QxNXhvamVsaDU4dzVkLmNsb3VkZnJvbnQubmV0L21lbW8va3d6L2N2eWhrZmRxbjVvejB6MWR6NWF0NHo0czFqc24ua3d6IiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNDkwNDYzMjAzfSwiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI6IjEuMi4zLjQvMzIifX19fQ__&Signature=MmBPtpipFLuNwaPliGLJajG4gJ7INwD0ptFdxPFYQP9CT-luq6W0SrAs9O9CqbJPHoukXwDzG~c88Rr5I2I9KP5QwD8MHpogGh~3SM3gBYm8ao0Zm7a5C9tWnBVRCtzuGrCrFstK-qLswWmqo6tNiOynSuFpvm9uDe3C8oWE2RzSZavEXoL35D3F8y98NeM0aOJe37EeSpdz3lrZZxei2TugoO-OmnApXa2YYJR2HiQ2l0t8paxcb3xyhCK1c1AR51uOpWLm63k~d0eNZJGo3x0Y6bx0GBqafdvV6jiUv6PbhiMC1ZcTxGnZhLmsz3~ONsEvaR1jyyOPt6y9Nos8yA__&Key-Pair-Id=APKAJ6RV6ACUX5M5IAOQ
Код:
function cloudfront_sign($url, $expiry = null, $ipLock = true) {
$policy = array(
'Statement' => array(
'Resource' => $url,
'Condition' => array(),
),
);
if(!$expiry || $expiry <= time()) $expiry = 2147483647; // CloudFront *requires* an expiry date, so set to 03:14:07 UTC on Tuesday, 19 January 2038 if one is not provided
$policy['Statement']['Condition']['DateLessThan'] = array('AWS:EpochTime' => $expiry);
if($ipLock) $policy['Statement']['Condition']['IpAddress'] = array('AWS:SourceIp' => $_SERVER['REMOTE_ADDR'].'/32');
$signer = new Aws\CloudFront\UrlSigner($_config['keyID'], $_config['keyPath']);
$jsonPolicy = json_encode($policy, JSON_UNESCAPED_SLASHES);
$url = $signer->getSignedUrl($url, null, $jsonPolicy);
return $url;
}
$url = kaeru_cloudfront_sign('https://d15xojelh58w5d.cloudfront.net/memo/kwz/cvyhkfdqn5oz0z1dz5at4z4s1jsn.kwz', 1490463203);
Я могу точно сказать вам, что происходит, но я не могу сказать вам, почему, если только это не ошибка в версии SDK, которую вы (очевидно) используете.
Политический документ действительно искажен, и подпись тоже.
И не похоже, что вы на самом деле делают что-то не так.
Я предполагаю, что вы знакомы с base64, где 8-битные данные расширяются до 6 бит на октет, чтобы позволить двоичным данным переноситься по транспортам, которые не обязательно являются 8-битными чистыми с использованием 64 символов (где 64 — 2 ^ 6, количество дискретных значений в 6 битах).
0-9
а также A-Z
а также a-z
составьте 10 + 26 + 26 = 62 необходимых символов, тогда есть +
а также /
довести общее количество символов до 64, но поскольку в конце может быть октет вывода с только 2 или 4 битами ввода, закодированный в него, 65-й символ =
«заполнение» означает, что в предыдущем символе есть неиспользуемые биты, которые не представляют входные данные. Таким образом, любое представление base64 всегда заканчивается 0, 1 или 2 =
символы. По этой причине закодированное в base64 значение никогда не может иметь =
где угодно, кроме как в конце. Это оказывается важным, ниже.
Выбор символов +
/
=
ужасно для URL из-за неоднозначности, вызванной тем, что очень многие пользовательские агенты (браузеры и клиентские библиотеки HTTP) обрабатывают их неправильно, когда дело доходит до экранирования url (также называемого url-кодировкой или процентным кодированием).
+
иногда считается эквивалентным %20
(пространство), в других случаях это сбежало как %2B
… =
используется для разделения полей в строке запроса, поэтому некоторые пользовательские агенты экранируют его как %3D
… а иногда /
может быть экранирован как %2F
… все это приводит к кошмару совместимости.
(Даже у самого S3 есть, по крайней мере, ошибка, связанная с неправильным экранированием URL-адреса, которая слишком долго была исправлена, чтобы исправить ее сейчас, потому что она сломала бы весь код, который был написан, чтобы предвидеть неправильное поведение S3. Но это не имеет отношения к проблеме под рукой.)
Дизайнеры CloudFront умело работали над этим.
CloudFront транслитерирует три потенциально проблемных символа следующим образом:
+ => -
/ => _
= => ~
Это работает, потому что персонажи -
_
~
не так часто подвержены ошибкам в URL.
Но как-то в вашем коде этот перевод неверен.
&Signature=...~ONsEvaR1jyyOPt6y9Nos8yA__&Key-...
Это определенно неправильно. ~
в середине подписи не может быть действительным. Как указано выше, ~
является =
и это может быть действительным только на конец кодировки base64. Это означает, что __
в конце подпись и политика тоже неверны и должны быть на самом деле ~~
,
?Policy=ey...X19fQ__&Signature=
Вы можете частично подтвердить это, поменяв их местами. Изменить __
в конце политики ~~
и вы обнаружите, что вы больше не видите Malformed Policy
ошибка, потому что это, кажется, единственная проблема с политикой.
К сожалению, поскольку политика не содержит каких-либо ~
или же -
, невозможно прийти к выводу о том, какие замены символов необходимы для того, чтобы сделать подпись действительной — не то, чтобы это было правильное решение, но оно действительно должно работать. Проблема в том, что мы не знаем, ~
а также _
транспонированы друг с другом, или если все три (включая -
) неверны.
Но это определенно кажется проблемой с реальным кодом, который генерирует окончательный URL, не проблема с документом политики JSON, который вы ему предоставляете.
Других решений пока нет …