Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my S3 pre-signed request invalid when I set a response header override that contains a "+"?

Tags:

c#

amazon-s3

I'm using the Amazon .NET SDK to generate a pre-signed URL like this:

public System.Web.Mvc.ActionResult AsActionResult(string contentType, string contentDisposition)
{
    ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides();
    headerOverrides.ContentType = contentType;
    if (!string.IsNullOrWhiteSpace(contentDisposition))
    {
        headerOverrides.ContentDisposition = contentDisposition;
    }

    GetPreSignedUrlRequest request = new GetPreSignedUrlRequest()
               .WithBucketName(bucketName)
               .WithKey(objectKey)
               .WithProtocol(Protocol.HTTPS)
               .WithExpires(DateTime.Now.AddMinutes(6))
               .WithResponseHeaderOverrides(headerOverrides);

    string url = S3Client.GetPreSignedURL(request);

    return new RedirectResult(url, permanent: false);
}

This works perfectly, except if my contentType contains a + in it. This happens when I try to get an SVG file, for example, which gets a content type of image/svg+xml. In this case, S3 throws a SignatureDoesNotMatch error.

The error message shows the StringToSign like this:

GET 1234567890 /blah/blabh/blah.svg?response-content-disposition=filename="blah.svg"&response-content-type=image/svg xml

Notice there's a space in the response-content-type, where it now says image/svg xml instead of image/svg+xml. It seems to me like that's what is causing the problem, but what's the right way to fix it?

Should I be encoding my content type? Enclose it within quotes or something? The documentation doesn't say anything about this.

like image 494
Ragesh Avatar asked Jan 29 '12 07:01

Ragesh


People also ask

How do S3 pre-signed URLs work?

Pre-signed URLs are used to provide short-term access to a private object in your S3 bucket. They work by appending an AWS Access Key, expiration time, and Sigv4 signature as query parameters to the S3 object. There are two common use cases when you may want to use them: Simple, occasional sharing of private files.

How long is S3 Presigned URL valid for?

In the Amazon S3 console, the maximum expiration time for a presigned URL is 12 hours from the time of creation.

How long is a pre-signed URL valid?

The default pre-signed URL expiration time is 15 minutes. Make sure to adjust this value to your specific needs. Security-wise, you should keep it to the minimum possible — eventually, it depends on your design. To upload a large file — larger than 10MB — you need to use multi-part upload.

What is pre-signed URL in S3?

A presigned URL gives you access to the object identified in the URL, provided that the creator of the presigned URL has permissions to access that object.


2 Answers

Update

This bug has been fixed as of Version 1.4.1.0 of the SDK.


Workaround

This is a confirmed bug in the AWS SDK, so until they issue a fix I'm going with this hack to make things work:

Specify the content type exactly how you want it to look like in the response header. So, if you want S3 to return a content type of image/svg+xml, set it exactly like this:

ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides();
headerOverrides.ContentType = "image/svg+xml";

Now, go ahead and generate the pre signed request as usual:

GetPreSignedUrlRequest request = new GetPreSignedUrlRequest()
  .WithBucketName(bucketName)
  .WithKey(objectKey)
  .WithProtocol(Protocol.HTTPS)
  .WithExpires(DateTime.Now.AddMinutes(6))
  .WithResponseHeaderOverrides(headerOverrides);

string url = S3Client.GetPreSignedURL(request);

Finally, "fix" the resulting URL with the properly URL encoded value for your content type:

url = url.Replace(contentType, HttpUtility.UrlEncode(contentType));

Yes, it's a dirty workaround but, hey, it works for me! :)

like image 151
Ragesh Avatar answered Oct 19 '22 23:10

Ragesh


Strange indeed - I've been able reproduce this easily, with the following observed behavior:

  • replacing + in the the URL generated by GetPreSignedURL() with its encoded form %2B yields a working URL/signature
    • this holds true, no matter whether / is replaced with its encoded form %2F or not
  • encoding the contentType upfront before calling GetPreSignedURL(), e.g. via the HttpUtility.UrlEncode Method, yields invalid signatures regardless of any variation of the generated URL

Given how long this functionality is available already, this is somewhat surprising, but I'd still consider it to be a bug - accordingly it might be best to inquiry about this in the Amazon Simple Storage Service forum.
Update: I just realized you asked the same question there already and the bug got confirmed indeed, so the correct answer can be figured out over time by monitoring the AWS team response ;)
Update: This bug has been fixed as of Version 1.4.1.0 of the SDK.

like image 22
Steffen Opel Avatar answered Oct 20 '22 01:10

Steffen Opel