3

I am generating a presigned URL server-side to allow my client application to upload a file directly to the S3 bucket. Everything works fine unless the client application is running on a computer in a timezone that is technically a day ahead of my server clock.

I can recreate the issue locally by setting my system clock ahead to a timezone on the next day.

Here is how I am generating the presigned URL using the .NET SDK (I originally had DateTime.Now instead of UTCNow):

var request = new GetPreSignedUrlRequest
{
 BucketName = bucketName,
 Key = objectName,
 Verb = HttpVerb.PUT,
 Expires = DateTime.UtcNow.AddDays(5),
 ContentType = "application/octet-stream"
};

request.Headers["x-amz-acl"] = "bucket-owner-full-control";
request.Metadata.Add("call", JsonConvert.SerializeObject(call).ToString());

return client.GetPreSignedURL(request);

and then I am using that presigned URL in the client application like this:

using (var fileStream = new FileStream(recordingPath, FileMode.Open))
using (var client = new WebClient())
{
    HttpContent fileStreamContent = new StreamContent(fileStream);
    var bytes = await fileStreamContent.ReadAsByteArrayAsync();
    client.Headers.Add("Content-Type", "application/octet-stream");
    //include metadata in PUT request
    client.Headers.Add("x-amz-meta-call", JsonConvert.SerializeObject(Call));

    await client.UploadDataTaskAsync(new Uri(presignedUrl), "PUT", bytes);
}

Here is the error I am receiving from AWS:

<?xml version="1.0" encoding="UTF-8"?>
<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>{access}</AWSAccessKeyId><StringToSign>....


The requests appear mostly identical to me in Fiddler.

Works:

PUT https://{bucketname}.s3.amazonaws.com/1c849c76-dd2a-4ff7-aad7-23ec7e9ddd45_encoded.opus?X-Amz-Expires=18000&x-amz-security-token={security_token}&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential={cred}&X-Amz-Date=20190312T021419Z&X-Amz-SignedHeaders=content-type;host;x-amz-acl;x-amz-meta-call;x-amz-security-token&X-Amz-Signature={sig} HTTP/1.1
x-amz-meta-call: {json_string}
x-amz-acl: bucket-owner-full-control
Content-Type: application/octet-stream
Host: {bucketname}.s3.amazonaws.com
Content-Length: 28289
Expect: 100-continue

{file}

Does not work:

PUT https://{bucketname}.s3.amazonaws.com/4cca3ec3-9f3f-4ba4-9d81-6336090610c0_encoded.opus?X-Amz-Expires=18000&x-amz-security-token={security_token}&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential={credentials}&X-Amz-Date=20190312T021541Z&X-Amz-SignedHeaders=content-type;host;x-amz-acl;x-amz-meta-call;x-amz-security-token&X-Amz-Signature={sig} HTTP/1.1
x-amz-meta-call: {json_string}
x-amz-acl: bucket-owner-full-control
Content-Type: application/octet-stream
Host: {bucketname}.s3.amazonaws.com
Content-Length: 18714
Expect: 100-continue


{file}

In both scenarios, the presigned URL has the same x-amz-date parameter generated. I have even tried parsing out the x-amz-date parameter from the URL and explicitly setting it as a header in my PUT but that did not work either.

What am I missing?

gtcowgill
  • 31
  • 1
  • 4

2 Answers2

11

It turned out to me that I was using a different version of the signature. v4 worked perfectly for me.

In JS, require S3 as

const s3 = new AWS.S3({
  signatureVersion: 'v4'
});
Dikshit Kathuria
  • 1,182
  • 12
  • 15
0

So the issue ended up being within the metadata. In our setup, we had the client application posting a JSON string up to our API along with the file to generate the presigned URL. We were using Json.net to deserialize into the C# class:

var call = JsonConvert.DeserializeObject<Call>(request.Params["metadata"]);

Apparently, this call converts any timestamps in the Json to local time. This means that we would sign the URL with metadata timestamps local to the API server, but actually upload the file with metadata timestamps local to the client. This difference is why the calculated signatures are different.

gtcowgill
  • 31
  • 1
  • 4