Create AWS S3 presigned URL with MD5 hash using PHP SDK - php

I've been unable to construct an S3 presigned URL containing a MD5 hash. Here is the code I'm using:
$s3 = new Aws\S3\S3Client( ... );
$md5Hash = "YmEwMWVmNzE5MjI3YzdjNmI0ZjYzNGMxZWI1MmY1Y2U=";
$cmd = $s3->getCommand('PutObject', [
'Bucket' => $bucket,
'Key' => $key,
'Content-MD5' => $md5Hash,
'ContentType' => $contentType,
'Body' => ''
]);
$request = $s3->createPresignedRequest($cmd, '+5 minutes');
$presignedUrl = (string)$request->getUri();
The resulting presignedURL looks something like:
http://s3-external-1.amazonaws.com/{bucket}/{key}?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWC7FFD2T3NCEUNQ%2F20170503%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170503T215723Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3000&X-Amz-Signature=ca61d80dcfee351e7ee8440a47c5007f1bc905ddc8229077abddab9c39412dc7
When I add the Content-MD5 hash to my PUT request, I get back an error indicating the MD5 header wasn't signed:
AccessDenied - There were headers present in the request which were not signed
HeadersNotSigned - content-md5
Any help appreciated!

Related

The provided token is malformed or otherwise invalid

latestAll S3 bucket file is displayed, but when I upload file then error is generate.
I have ARN and instance profile.
use Aws\Credentials\CredentialProvider;
use Aws\Credentials\InstanceProfileProvider;
use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\S3\S3Client;
use Aws\Sts\StsClient;
$profile = new InstanceProfileProvider();
$ARN = ""; // MY ARN
$sessionName = "s3-access-example";
$assumeRoleCredentials = new AssumeRoleCredentialProvider([
'client' => new StsClient([
'region' => "ap-east-1",
'version' => "latest",
'credentials' => $profile
]),
'assume_role_params' => [
'RoleArn' => $ARN,
'RoleSessionName' => $sessionName,
],
]);
$provider = CredentialProvider::memoize($assumeRoleCredentials);
$this->s3hd = S3Client::factory([
'credentials' => $provider,
'version' => "latest",
'region' => "ap-east-1"
]);
public function upload($name, $file, $type, $Bucket = false)
{
if (! $Bucket) {
$Bucket = $this->bucket;
}
$result = $this->s3hd->putObject([
'Bucket' => $Bucket,
'Key' => $name,
'SourceFile' => $file,
'ContentType' => $type,
'ACL' => 'public-read'
]);
$this->s3hd->waitUntil('ObjectExists', [
'Bucket' => $Bucket,
'Key' => $name
]);
return $result;
}
Message: Error executing "PutObject" on error file url here; AWS HTTP error: Client error: PUT error file url here resulted in a 400 Bad Request` response: InvalidTokenThe provided token is malformed or other (truncated...) InvalidToken (client): The provided token is malformed or otherwise invalid. - InvalidTokenThe provided token is malformed or otherwise invalid.
In my case. I was trying through CLI.
I configured AWS cli by running
aws configure
Inside ~/.aws/credentials, look for session_token. If you have the session_token. Then update it.
Otherwise remove session_token and try again.
And Voila It worked!
You're getting this error because Hong Kong (ap-east-1) is not enabled by default. You need to enable it in the AWS console if you have the correct permissions, then try again.
See the AWS Docs for instructions on how to do that.
I am using s3cmd and based on the above answer I was able to solve by deleting the .s3cmd file and reconfiguring (s3cmd --configure), which cleared the access_token:
diff .s3cfg previous_s3cmd
< access_token =
---
> access_token = vkp1Jf\etc...
Also increased the chunk sizes:
< recv_chunk = 65536
---
> recv_chunk = 4096
< send_chunk = 65536
---
> send_chunk = 4096
And I guess I had previously opted for https requests, as opposed to the (current) default:
< use_https = True
---
> use_https = False
Success
Success. Your access key and secret key worked fine :-)
Now verifying that encryption works...
Success. Encryption and decryption worked fine :-)

Temporary S3 Bucket URL for serving Images

Using "aws/aws-sdk-php": "^3.0#dev"
I am creating a image sharing website but do not want people to copy my URLs to another site to steal my content/bandwidth.
I was originally storing the objects as
return $s3->putObject([
'Bucket' => $bucket,
'Key' => $key,
'Body' => $file,
'ACL' => 'public-read',
]);
But I have removed 'public-read' so now the URL below no longer works
https://mybucket-images.s3.us-west-1.amazonaws.com/' . $key);
What do I need to do to create a temporary URL that can still be client side cached to access the object?
One thing I was thinking was to change the key once a week or month, but it would require me to update all objects with a cronjob. There must be a way to create a temporary access URL?
Use your server to generate presigned url for the keys in the bucket.
//Creating a presigned request
$s3Client = new Aws\S3\S3Client([
'profile' => 'default',
'region' => 'us-east-2',
'version' => '2006-03-01',
]);
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => 'my-bucket',
'Key' => 'testKey'
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
$presignedUrl = (string) $request->getUri();
taken from https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-presigned-url.html
But you'd have to do this every time there's a request to your page. And the link will be valid everywhere. You just minimize the period of its validity.
If your website is an API based and you retrieve the url via API, this may be relevant to you:
If your website has a login function, you can check for the auth logic prior giving the presigned url.
If not, you can use Http Referrer (which can be spoofed). Or an api key (like in API Gateway)
You can use the following code:
// initiate connection to your S3 bucket
$client = new S3Client(['credentials' => ['key' => 's3 key', 'secret' =>'s3 secrete'], 'region' => 's3 region', 'version' => 'latest']);
$object = $client->getCommand('GetObject', [
'Bucket' => 's3 bucket',
'Key' => 'images/image.png' // file
]);
$presignedRequest = $client->createPresignedRequest($object, '+20 minutes');
$presignedUrl = (string)$presignedRequest->getUri();
if ($presignedUrl) {
return $presignedUrl;//presigned URL
} else {
throw new FileNotFoundException();
}
If your intent is to make your content readable ONLY via a URL posted on your website - versus having the same web client using the same url accessed from another site NOT work, I think you are likely to find that rather difficult. Most of the ways that come to mind are fairly spoofable.
I would take a look at this and see if its good enough for you:
Restricting Access to a Specific HTTP Referrer

Uploading file to S3 using presigned URL in PHP

I am developing a Web Application using PHP. In my application, I need to upload the file to the AWS S3 bucket using Presigned URL. Now, I can read the private file from the S3 bucket using pre-signed like this.
$s3Client = new S3Client([
'version' => 'latest',
'region' => env('AWS_REGION', ''),
'credentials' => [
'key' => env('AWS_IAM_KEY', ''),
'secret' => env('AWS_IAM_SECRET', '')
]
]);
//GetObject
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => env('AWS_BUCKET',''),
'Key' => 'this-is-uploaded-using-presigned-url.png'
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
//This is for reading the image. It is working.
$presignedUrl = (string) $request->getUri();
When I access the $presignedUrl from the browser, I can get the file from the s3. It is working. But now, I am uploading a file to S3. Not reading the file from s3. Normally, I can upload the file to the S3 like this.
$client->putObject(array(
'Bucket' => $bucket,
'Key' => 'data.txt',
'Body' => 'Hello!'
));
The above code is not using the pre-signed URL. But I need to upload the file using a pre-signed URL. How, can I upload the file using a pre-signed URL. For example, what I am thinking is something like this.
$client->putObject(array(
'presigned-url' => 'url'
'Bucket' => $bucket,
'Key' => 'data.txt',
'Body' => 'Hello!'
));
How can I upload?
It seems reasonable that you can create a pre-signed PutPobject command by running:
$cmd = $s3Client->getCommand('PutObject', [
'Bucket' => $bucket,
'Key' => $key
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes')->withMethod('PUT');
Then you might want to perform the PUT call from PHP using:
file_put_contents(
$request->getUri(),
'Hello!',
stream_context_create(['http' => [ 'method' => 'PUT' ]])
);
If you want to create a URL that a browser can submit, then you need to have the browser send the file as a form POST. This AWS documentation explains how to create a pre-signed POST request with the fields that you then need to put into an HTML form and display to the user: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-presigned-post.html
Also, this answer might be useful: https://stackoverflow.com/a/59644117/53538

Trying to register an amazon s3 presigned url and getting an error

The question it self explanatory, when trying to create a presigned url I get the following error:
Error retrieving credentials from the instance profile metadata server. (cURL error 28: Connection timed out after 1001 milliseconds (see http://curl.haxx.se/libcurl/c/libcurl-errors.html))
I have used the code from here exactly https://docs.aws.amazon.com/aws-sdk-php/v3/guide/service/s3-presigned-url.html
My code is below:
$s3Client = new S3Client([
'region' => 'eu-west-1',
'version' => '2006-03-01',
]);
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => 'my-bucket-name',
'Key' => 'AKIAJNCZ5***********'
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
// Get the actual presigned-url
$presignedUrl = (string) $request->getUri();
print_r($presignedUrl);
Any reason why this is happening?
EDIT::
Ok so this fixed my problem, but it wasnt not actually even in the docs:
$s3Client = new S3Client([
'region' => 'eu-west-1',
'version' => '2006-03-01',
'credentials' => ['key' => 'AKIAJNCZ5MY*******8','secret'=>'NgeFc+2/Q2cUAmL/+lP2gp***********8']
]);
Adding the credentials assoc array :)
However I am now unsure how to use this presigned url to download one of my files aha, so if anyone knows and doesnt mind putting me in the right direction :)
'Key' in the getCommand array is the name/path to the file you want to generate a pre-signed URL for, not your AWS key :)
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => 'my-bucket-name',
'Key' => 'path/to/file.txt', // or just file.txt if it's in the root of the bucket
]);

Amazon S3 PHP SDK pre-signed requests

I am trying to generate a presigned request with the S3 PHP SDK like this:
$s3Client = Aws\S3\S3Client::factory([
'credentials' => new Aws\Common\Credentials\Credentials('my-access-code', 'xxx')
]);
$command = $s3Client->getCommand('GetObject', [
'Bucket' => 'my-bucket-name',
'Key' => 'awesome-cat-image.png',
]);
$signedUrl = $command->createPresignedUrl('+10 minutes');
But when I goto the URL I get an error saying:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
<AWSAccessKeyId>xxx</AWSAccessKeyId>
<StringToSign>GET 1422564095 /my-bucket-name/awesome-cat-image.png</StringToSign>
<SignatureProvided>xxx</SignatureProvided>
<StringToSignBytes>
xxx
</StringToSignBytes>
<RequestId>xxx</RequestId>
<HostId>
xxx
</HostId>
</Error>
Accessing https://my-bucket-name.s3.amazonaws.com/awesome-cat-image.png works just fine with permissions set to allow non authenticated users.
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => 'my-bucket',
'Key' => 'testKey'
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
// Get the actual presigned-url
$presignedUrl = (string) $request->getUri();
You can find more details here http://docs.aws.amazon.com/aws-sdk-php/v3/guide/service/s3-presigned-url.html

Categories