2

I'm currently trying to set up client-side uploading to my bucket via the blueimp library and this more up to date tutorial for the setup.

I seem to be constructing the signature incorrectly but how I'm doing so is beyond me. If someone would be willing to lend a fresh pair of eyes it'd be much appreciated.

Also the exact response is "SignatureDoesNotMatchThe request signature we calculated does not match the signature you provided. Check your key and signing method"

PHP API where signature is generated

        $policy = base64_encode(
     preg_replace("/\n|\r/", "",
        json_encode(

                array(
                    "expiration" => $expires,
                    "bucket" => $S3_BUCKET, 
                    "acl" => "public-read",
                    "starts-with" => $object_name,
                    "success_action_status" => "201"
                )
            )
        ) 
    );

    //$policy = preg_replace("/\n|\r/", "", $policy);

    $signature = base64_encode(
            hash_hmac(
                'sha1', 
                $config->aws_secret,
                $policy
            )

    );

    $signature = preg_replace("/\n/", "", $signature);

    $awsAccessInfo = array(
        "signature" => $signature, 
        "aws_key" => $AWS_ACCESS_KEY, 
        "policy" => $policy, 
        "bucket" => $S3_BUCKET,
            "key" => $AWS_ACCESS_KEY
    );

    return $this->getResponse()->json($awsAccessInfo);

JS

$('.direct-upload').each(function() {

    var form = $(this);
    $(this).fileupload({
      url: form.attr('action'),
      type: 'POST',
      autoUpload: true,
      dataType: 'xml', // This is really important as s3 gives us back the url of the file in a XML document
      add: function (event, data) {
                console.log(data.files[0].name);
        $.ajax({
          url: "http://api/sign_request_s3?allowOrigin=1",
          type: 'GET',
          dataType: 'json',
          data: { s3_object_name: data.files[0].name}, // send the file name to the server so it can generate the key param
          async: false,
          success: function(data) {

            // Now that we have our data, we update the form so it contains all
            // the needed data to sign the request
                        console.log("Key: " + data.aws_key + " Signature: " + data.signature);
            form.find('input[name=key]').val(data.aws_key);
                        form.find('input[name=AWSAccessKeyId]').val(data.aws_key);
            form.find('input[name=policy]').val(data.policy);
            form.find('input[name=signature]').val(data.signature);
          }
        });
        data.submit();
      },
      send: function(e, data) {
        $('.progress').fadeIn();
                console.log("sending...");
      },
      progress: function(e, data){
        // This is what makes everything really cool, thanks to that callback
        // you can now update the progress bar based on the upload progress
        var percent = Math.round((e.loaded / e.total) * 100)
        $('.bar').css('width', percent + '%')
      },
      fail: function(e, data) {
        console.log('failed');

      },
      success: function(data) {
        // Here we get the file url on s3 in an xml doc
        var url = $(data).find('Location').text()
                console.log('success');
        $('#real_file_url').val(url) // Update the real input in the other form
      },
      done: function (event, data) {
        $('.progress').fadeOut(300, function() {
          $('.bar').css('width', 0)
        })
      },
    })
  })
Raphi
  • 410
  • 4
  • 17

2 Answers2

1

Even more than that, the official AWS SDK for PHP handles the hard stuff for you.

<?php
error_reporting(-1);
header('Content-type: text/html; charset=utf-8');
require_once __DIR__ . '/vendor/autoload.php';
#---------------------------------------------

define('INDENT', '    ');

// Import namespaces
use Aws\S3\S3Client;
use Aws\S3\Enum\CannedAcl;
use Aws\S3\Model\PostObject;

// Instantiate S3 client
$s3 = S3Client::factory(array(
    'key'    => '...',
    'secret' => '...',
));

// Instantiate and prepare PostObject
$post = new PostObject($s3, 'my-test-bucket', array(
    'acl' => CannedAcl::PUBLIC_READ,
));
$post->prepareData();

// Get the attributes for the <form> tag
$attributes = array();
foreach ($post->getFormAttributes() as $attr => $value)
{
    $attributes[] = "${attr}=\"${value}\"";
}
$attributes = implode(' ', $attributes);

// Write some HTML via PHP. This is for learning. Never do this in real life.
echo "<form ${attributes}>" . PHP_EOL;
foreach ($post->getFormInputs() as $name => $value)
{
    // Write hidden fields
    echo INDENT . "<input type=\"hidden\" name=\"${name}\" value=\"${value}\">" . PHP_EOL;
}

// Upload and submit
echo INDENT . "<input type=\"file\" name=\"file\">" . PHP_EOL;
echo INDENT . "<input type=\"submit\" name=\"upload\" value=\"Upload\">" . PHP_EOL;

echo "</form>" . PHP_EOL;
Ryan Parman
  • 6,855
  • 1
  • 29
  • 43
1

It wound up that the cause of 403 was the signature not properly being generated. The order of the arguments was slightly off and should've precisely matched the order stated in the AWS docs.

POST http://iam.amazonaws.com/ HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east- 1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ced6826de92d2bdeed8f846f0bf508e8559e98e4b0199114b84c54174deb456c
host: iam.amazonaws.com
Content-type: application/x-www-form-urlencoded; charset=utf-8
x-amz-date: 20110909T233600Z
Action=ListUsers&Version=2010-05-08
Ivar
  • 6,138
  • 12
  • 49
  • 61
Raphi
  • 410
  • 4
  • 17
  • I wondered if this was my problem (and cleaned up my policy as a result!) but for me it was a missing bucket policy, answered here: http://stackoverflow.com/a/10884964/963195 – Andrew E Nov 09 '14 at 07:55