%PDF- %PDF-
| Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/vendor/async-aws/core/src/Signer/ |
| Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/vendor/async-aws/core/src/Signer/SignerV4.php |
<?php
namespace AsyncAws\Core\Signer;
use AsyncAws\Core\Credentials\Credentials;
use AsyncAws\Core\Exception\InvalidArgument;
use AsyncAws\Core\Request;
use AsyncAws\Core\RequestContext;
use AsyncAws\Core\Stream\ReadOnceResultStream;
use AsyncAws\Core\Stream\RewindableStream;
use AsyncAws\Core\Stream\StringStream;
/**
* Version4 of signer.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class SignerV4 implements Signer
{
private const ALGORITHM_REQUEST = 'AWS4-HMAC-SHA256';
private const BLACKLIST_HEADERS = [
'cache-control' => true,
'content-type' => true,
'content-length' => true,
'expect' => true,
'max-forwards' => true,
'pragma' => true,
'range' => true,
'te' => true,
'if-match' => true,
'if-none-match' => true,
'if-modified-since' => true,
'if-unmodified-since' => true,
'if-range' => true,
'accept' => true,
'authorization' => true,
'proxy-authorization' => true,
'from' => true,
'referer' => true,
'user-agent' => true,
'x-amzn-trace-id' => true,
'aws-sdk-invocation-id' => true,
'aws-sdk-retry' => true,
];
private $scopeName;
private $region;
public function __construct(string $scopeName, string $region)
{
$this->scopeName = $scopeName;
$this->region = $region;
}
public function presign(Request $request, Credentials $credentials, RequestContext $context): void
{
$now = $context->getCurrentDate() ?? new \DateTimeImmutable();
// Signer date have to be UTC https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html
$now = $now->setTimezone(new \DateTimeZone('UTC'));
$expires = $context->getExpirationDate() ?? $now->add(new \DateInterval('PT1H'));
$this->handleSignature($request, $credentials, $now, $expires, true);
}
public function sign(Request $request, Credentials $credentials, RequestContext $context): void
{
$now = $context->getCurrentDate() ?? new \DateTimeImmutable();
// Signer date have to be UTC https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html
$now = $now->setTimezone(new \DateTimeZone('UTC'));
$this->handleSignature($request, $credentials, $now, $now, false);
}
protected function buildBodyDigest(Request $request, bool $isPresign): string
{
if ($request->hasHeader('x-amz-content-sha256')) {
/** @var string $hash */
$hash = $request->getHeader('x-amz-content-sha256');
} else {
$body = $request->getBody();
if ($body instanceof ReadOnceResultStream) {
$request->setBody($body = RewindableStream::create($body));
}
$hash = $request->getBody()->hash();
}
if ('UNSIGNED-PAYLOAD' === $hash) {
$request->setHeader('x-amz-content-sha256', $hash);
}
return $hash;
}
protected function convertBodyToStream(SigningContext $context): void
{
$request = $context->getRequest();
$request->setBody(StringStream::create($request->getBody()));
}
protected function buildCanonicalPath(Request $request): string
{
$doubleEncoded = rawurlencode(ltrim($request->getUri(), '/'));
return '/' . str_replace('%2F', '/', $doubleEncoded);
}
private function handleSignature(Request $request, Credentials $credentials, \DateTimeImmutable $now, \DateTimeImmutable $expires, bool $isPresign): void
{
$this->removePresign($request);
$this->sanitizeHostForHeader($request);
$this->assignAmzQueryValues($request, $credentials, $isPresign);
$this->buildTime($request, $now, $expires, $isPresign);
$credentialScope = $this->buildCredentialString($request, $credentials, $now, $isPresign);
$context = new SigningContext(
$request,
$now,
implode('/', $credentialScope),
$this->buildSigningKey($credentials, $credentialScope)
);
if ($isPresign) {
// Should be called before `buildBodyDigest` because this method may alter the body
$this->convertBodyToQuery($request);
} else {
$this->convertBodyToStream($context);
}
$bodyDigest = $this->buildBodyDigest($request, $isPresign);
if ($isPresign) {
// Should be called after `buildBodyDigest` because this method may remove the header `x-amz-content-sha256`
$this->convertHeaderToQuery($request);
}
$canonicalHeaders = $this->buildCanonicalHeaders($request, $isPresign);
$canonicalRequest = $this->buildCanonicalRequest($request, $canonicalHeaders, $bodyDigest);
$stringToSign = $this->buildStringToSign($context->getNow(), $context->getCredentialString(), $canonicalRequest);
$context->setSignature($signature = $this->buildSignature($stringToSign, $context->getSigningKey()));
if ($isPresign) {
$request->setQueryAttribute('X-Amz-Signature', $signature);
} else {
$request->setHeader('authorization', sprintf(
'%s Credential=%s/%s, SignedHeaders=%s, Signature=%s',
self::ALGORITHM_REQUEST,
$credentials->getAccessKeyId(),
implode('/', $credentialScope),
implode(';', array_keys($canonicalHeaders)),
$signature
));
}
}
private function removePresign(Request $request): void
{
$request->removeQueryAttribute('X-Amz-Algorithm');
$request->removeQueryAttribute('X-Amz-Signature');
$request->removeQueryAttribute('X-Amz-Security-Token');
$request->removeQueryAttribute('X-Amz-Date');
$request->removeQueryAttribute('X-Amz-Expires');
$request->removeQueryAttribute('X-Amz-Credential');
$request->removeQueryAttribute('X-Amz-SignedHeaders');
}
private function sanitizeHostForHeader(Request $request): void
{
if (false === $parsedUrl = parse_url($request->getEndpoint())) {
throw new InvalidArgument(sprintf('The endpoint "%s" is invalid.', $request->getEndpoint()));
}
if (!isset($parsedUrl['host'])) {
return;
}
$host = $parsedUrl['host'];
if (isset($parsedUrl['port'])) {
$host .= ':' . $parsedUrl['port'];
}
$request->setHeader('host', $host);
}
private function assignAmzQueryValues(Request $request, Credentials $credentials, bool $isPresign): void
{
if ($isPresign) {
$request->setQueryAttribute('X-Amz-Algorithm', self::ALGORITHM_REQUEST);
if (null !== $sessionToken = $credentials->getSessionToken()) {
$request->setQueryAttribute('X-Amz-Security-Token', $sessionToken);
}
return;
}
if (null !== $sessionToken = $credentials->getSessionToken()) {
$request->setHeader('x-amz-security-token', $sessionToken);
}
}
private function buildTime(Request $request, \DateTimeImmutable $now, \DateTimeImmutable $expires, bool $isPresign): void
{
if ($isPresign) {
$duration = $expires->getTimestamp() - $now->getTimestamp();
if ($duration > 604800) {
throw new InvalidArgument('The expiration date of presigned URL must be less than one week');
}
if ($duration < 0) {
throw new InvalidArgument('The expiration date of presigned URL must be in the future');
}
$request->setQueryAttribute('X-Amz-Date', $now->format('Ymd\THis\Z'));
$request->setQueryAttribute('X-Amz-Expires', $duration);
} else {
$request->setHeader('X-Amz-Date', $now->format('Ymd\THis\Z'));
}
}
private function buildCredentialString(Request $request, Credentials $credentials, \DateTimeImmutable $now, bool $isPresign): array
{
$credentialScope = [$now->format('Ymd'), $this->region, $this->scopeName, 'aws4_request'];
if ($isPresign) {
$request->setQueryAttribute('X-Amz-Credential', $credentials->getAccessKeyId() . '/' . implode('/', $credentialScope));
}
return $credentialScope;
}
private function convertHeaderToQuery(Request $request): void
{
foreach ($request->getHeaders() as $name => $value) {
if ('x-amz' === substr($name, 0, 5)) {
$request->setQueryAttribute($name, $value);
}
if (isset(self::BLACKLIST_HEADERS[$name])) {
$request->removeHeader($name);
}
}
$request->removeHeader('x-amz-content-sha256');
}
private function convertBodyToQuery(Request $request): void
{
if ('POST' !== $request->getMethod()) {
return;
}
$request->setMethod('GET');
if ('application/x-www-form-urlencoded' === $request->getHeader('Content-Type')) {
parse_str($request->getBody()->stringify(), $params);
foreach ($params as $name => $value) {
$request->setQueryAttribute($name, $value);
}
}
$request->removeHeader('content-type');
$request->removeHeader('content-length');
$request->setBody(StringStream::create(''));
}
private function buildCanonicalHeaders(Request $request, bool $isPresign): array
{
// Case-insensitively aggregate all of the headers.
$canonicalHeaders = [];
foreach ($request->getHeaders() as $key => $value) {
$key = strtolower($key);
if (isset(self::BLACKLIST_HEADERS[$key])) {
continue;
}
$canonicalHeaders[$key] = $key . ':' . preg_replace('/\s+/', ' ', $value);
}
ksort($canonicalHeaders);
if ($isPresign) {
$request->setQueryAttribute('X-Amz-SignedHeaders', implode(';', array_keys($canonicalHeaders)));
}
return $canonicalHeaders;
}
private function buildCanonicalRequest(Request $request, array $canonicalHeaders, string $bodyDigest): string
{
return implode("\n", [
$request->getMethod(),
$this->buildCanonicalPath($request),
$this->buildCanonicalQuery($request),
implode("\n", array_values($canonicalHeaders)),
'', // empty line after headers
implode(';', array_keys($canonicalHeaders)),
$bodyDigest,
]);
}
private function buildCanonicalQuery(Request $request): string
{
$query = $request->getQuery();
unset($query['X-Amz-Signature']);
if (!$query) {
return '';
}
ksort($query);
$encodedQuery = [];
foreach ($query as $key => $values) {
if (!\is_array($values)) {
$encodedQuery[] = rawurlencode($key) . '=' . rawurlencode($values);
continue;
}
sort($values);
foreach ($values as $value) {
$encodedQuery[] = rawurlencode($key) . '=' . rawurlencode($value);
}
}
return implode('&', $encodedQuery);
}
private function buildStringToSign(\DateTimeImmutable $now, string $credentialString, string $canonicalRequest): string
{
return implode("\n", [
self::ALGORITHM_REQUEST,
$now->format('Ymd\THis\Z'),
$credentialString,
hash('sha256', $canonicalRequest),
]);
}
private function buildSigningKey(Credentials $credentials, array $credentialScope): string
{
$signingKey = 'AWS4' . $credentials->getSecretKey();
foreach ($credentialScope as $scopePart) {
$signingKey = hash_hmac('sha256', $scopePart, $signingKey, true);
}
return $signingKey;
}
private function buildSignature(string $stringToSign, string $signingKey): string
{
return hash_hmac('sha256', $stringToSign, $signingKey);
}
}