• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
Question by Dan-J-31 · Aug 21, 2015 at 08:35 AM · wwwhtmlwwwformpostamazon

How do I make a simple POST request to Amazon S3?

Hello Unity Community, I am trying to have a web-build of a project put a simple .csv file onto our Amazon S3 bucket. The idea is that the web-player would gather some information during play, and then store that information in .csv format on the bucket that we have created on Amazon S3.

I am using the WWW class, as well as the WWWForm class to build an HTML form, and send that to Amazon's servers. The documentation for building the request form to amazon is here, the documentation for building the policy is here and the documentation on authentication and signing is here.

Below is the code that constructs the form:

 using UnityEngine;
 using System;
 using System.Text;
 using System.Collections;
 using System.Security.Cryptography;
 
 public class AWSWebFormBuilder
 {
     // TODO: still need to store the accessKeyID of the actual root account (mturk needs this and can't use amazon IAM).
     const string rootID = "REMOVED";
     const string rootAccessKeyID = "";
     const string rootSecretAccessKey = "";
 
     const string appAccessKeyID = "REMOVED";
     const string appSecretAccessKey = "REMOVED";
 
     const double requestValidForSeconds = 15.0f;
 
     string CreateSignature(string date, string region, string service, string policy)
     {
         // Source: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
 
         byte[] policyBytes = Encoding.Default.GetBytes(policy);
 
         byte[] dateBytes = Encoding.Default.GetBytes(date);
         byte[] regionBytes = Encoding.Default.GetBytes(region);
         byte[] serviceBytes = Encoding.Default.GetBytes(service);
 
         byte[] secretAccessKeyBytes = Encoding.Default.GetBytes("AWS4" + appSecretAccessKey);
         byte[] requestBytes = Encoding.Default.GetBytes("aws4_request");
 
         HMACSHA256 dateHash = new HMACSHA256(secretAccessKeyBytes);
         dateHash.ComputeHash(dateBytes);
 
         HMACSHA256 dateRegionHash = new HMACSHA256(dateHash.Hash);
         dateRegionHash.ComputeHash(regionBytes);
 
         HMACSHA256 dateRegionServiceHash = new HMACSHA256(dateRegionHash.Hash);
         dateRegionServiceHash.ComputeHash(serviceBytes);
 
         HMACSHA256 signingKey = new HMACSHA256(dateRegionServiceHash.Hash);
         signingKey.ComputeHash(requestBytes);
 
         HMACSHA256 finalSignature = new HMACSHA256(signingKey.Hash);
         finalSignature.ComputeHash(policyBytes);
 
         string signatureString = Convert.ToBase64String(finalSignature.Hash);
 
         //Debug.Log(signature);
 
         //string signature = BitConverter.ToString(finalSignature.Hash);
         //signature = signature.Replace("-", "");
 
         return signatureString;
     }
 
     public void BuildUploadRequest(ref WWWForm requestForm, string fileName, string fileContent)
     {
         DateTime currentTime = DateTime.UtcNow;
         DateTime requestExpiryTime = currentTime.AddSeconds(requestValidForSeconds);
 
         string iso8601ExpiryTime = requestExpiryTime.ToString("s", System.Globalization.CultureInfo.InvariantCulture);
         string iso8601CurrentTime = currentTime.ToString("s", System.Globalization.CultureInfo.InvariantCulture);
 
         iso8601CurrentTime = iso8601CurrentTime.Replace(" ", "").Replace("-", "").Replace(":", "");
 
         string policy =     "{ \"expiration\": \"" + iso8601ExpiryTime + "\"," +
                             "  \"conditions\": [" +
                             "  {\"bucket\": \"ourbucket\" }," +
                             "  [\"starts-with\", \"$key\", \"Project 1 Output/\"]," +
                             "  {\"x-amz-credential\": \"" + appAccessKeyID + "/" + currentTime.ToString("yyyymmdd") + "/us-east-1/s3/aws4_request\"}," +
                             "  {\"x-amz-algorithm\": \"AWS4-HMAC-SHA256\"}," +
                             "  {\"x-amz-date\": \"" + iso8601CurrentTime + "\" }" +
                             "]}";
 
         policy = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));
 
         string signature = CreateSignature(currentTime.ToString("yyyymmdd"), "us-east-1", "s3", policy);
 
         requestForm.AddField("AWSAccessKeyId", appAccessKeyID);
         requestForm.AddField("policy", policy);
         requestForm.AddField("key", fileName);
         requestForm.AddField("x-amz-credential", appAccessKeyID + "/" + currentTime.ToString("yyyymmdd") + "/us-east-1/s3/aws4_request");
         requestForm.AddField("x-amz-algorithm", "AWS4-HMAC-SHA256");
         requestForm.AddField("x-amz-date", iso8601CurrentTime);
         requestForm.AddField("Signature", signature);
 
         requestForm.AddBinaryData("file", Encoding.UTF8.GetBytes(fileContent));
     }
 }

Now, based on the documentation, that code should work. Unfortunately it does not. Whenever I try to make a POST request to Amazon's servers I get the following error:

403 Forbidden - SignatureDoesNotMatch - The request signature we calculated does not match the signature you provided. Check your key and signing method.

As the error suggests, I must not be generating my signature correctly. I've been trying to figure this out for quite some time now, and I'm at my wit's end as I don't really know what I'm doing wrong.

I've also tried resetting the access & secret access keys on the IAM console, but that didn't help.

Can anybody help me out?

Comment

People who like this

0 Show 1
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Dan-J-31 · Aug 21, 2015 at 02:45 AM 0
Share

One lead might be how I'm converting the strings in the 'CreateSignature' function into bytes. Since the HMAC hashing functions need 'byte[]' as a parameter, I need to store the strings as a byte array. Unfortunately, amazon's documentation doesn't state how I should encode those particular variables. It does say to use UTF-8 for the policy, and Base64 for the final policy string and signature, but there is no mention on how to encode the other variables. I'm assuming this does matter, as the algorithm on their end might be using a different encoding method on those variables.

2 Replies

· Add your reply
  • Sort: 
avatar image
Best Answer

Answer by Dan-J-31 · Aug 27, 2015 at 11:31 PM

Well, it turns out following that particular documentation just doesn't work, and the server will always return a 403 Access Denied error. I found this article instead that suggested to use a SHA-1 algorithm instead of the SHA256 one, and the process of signing was just using your secret key instead of using a concatenation of keys as was outlined in the other documentation.

Once I used that algorithm (by actually using it, and specifying it in both the policy and the form), the server was now sending me actual useful errors. It turns out my format for time was also incorrect, and that my policy was already expiring before it even got to the servers, so I fixed that too.

The below code now works for me when I try to do a POST request to Amazon S3 (for uploading a file from a web build):

 using UnityEngine;
 using System;
 using System.Text;
 using System.Collections;
 using System.Security.Cryptography;
 
 public class AWSWebFormBuilder
 {
     // TODO: still need to store the accessKeyID of the actual root account (mturk needs this and can't use amazon IAM).
     const string rootID = "REMOVED";
     const string rootAccessKeyID = "";
     const string rootSecretAccessKey = "";
 
     const string appAccessKeyID = "REMOVED";
     const string appSecretAccessKey = "REMOVED";
 
     const double requestValidForSeconds = 60.0f;
 
     string CreateSignatureSHA1(string policy)
     {
         byte[] policyBytes = Encoding.Default.GetBytes(policy);
         byte[] keyBytes = Encoding.Default.GetBytes(appSecretAccessKey);
 
         HMACSHA1 signHash = new HMACSHA1(keyBytes);
         signHash.ComputeHash(policyBytes);
 
         string finalSignature = Convert.ToBase64String(signHash.Hash);
         return finalSignature;
     }
 
     public void BuildUploadRequest(ref WWWForm requestForm, string fileName, string fileContent)
     {
         DateTime currentTime = DateTime.UtcNow;
         DateTime requestExpiryTime = currentTime.AddSeconds(requestValidForSeconds);
 
         string iso8601ExpiryTime = requestExpiryTime.ToString("s") + "Z";
         string iso8601CurrentTime = currentTime.ToString("s", System.Globalization.CultureInfo.InvariantCulture);
 
         iso8601CurrentTime = iso8601CurrentTime.Replace(" ", "").Replace("-", "").Replace(":", "");
 
         string policy =     "{ \"expiration\": \"" + iso8601ExpiryTime + "\"," +
                             "  \"conditions\": [" +
                             "  {\"acl\": \"private\" }, " +
                             "  {\"bucket\": \"ourbucket\" }," +
                             "  [\"starts-with\", \"$key\", \"Project 1 Output/\"]," +
                             "  [\"starts-with\", \"$Content-Type\", \"\"]," +
                             "  [\"content-length-range\", 1, 102400], " +
                             "  {\"x-amz-credential\": \"" + appAccessKeyID + "/" + currentTime.ToString("yyyymmdd") + "/us-east-1/s3/aws4_request\"}," +
                             "  {\"x-amz-algorithm\": \"AWS4-HMAC-SHA1\"}," +
                             "  {\"x-amz-date\": \"" + iso8601CurrentTime + "\" }" +
                             "]}";
 
         policy = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));
 
         string signature = CreateSignatureSHA1(policy);
 
         requestForm.AddField("key", fileName);
         requestForm.AddField("AWSAccessKeyId", appAccessKeyID);
         requestForm.AddField("policy", policy);
         requestForm.AddField("acl", "private");
         requestForm.AddField("x-amz-credential", appAccessKeyID + "/" + currentTime.ToString("yyyymmdd") + "/us-east-1/s3/aws4_request");
         requestForm.AddField("x-amz-algorithm", "AWS4-HMAC-SHA1");
         requestForm.AddField("x-amz-date", iso8601CurrentTime);
         requestForm.AddField("Signature", signature);
         requestForm.AddField("Content-Type", "text/plain");
 
         requestForm.AddBinaryData("file", Encoding.UTF8.GetBytes(fileContent), fileName, "text/plain");
     }
 }

I'm probably doing some redundant stuff by supplying both a "Content-Type" field, and supplying a mime-type parameter when uploading the binary data.

In any case, the above code works for me, and, after spending more than a week on this, I finally got this to work.

P.S. It's also not a good idea to openly store access keys in your code. I know I have them written in my above example, but this is still in-development. I will have to think of a security solution later (probably requires a webserver).

Comment

People who like this

0 Show 0 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image

Answer by charlie_sbg · Apr 27, 2018 at 05:37 PM

Thanks for this info! I'm suffering through this right now myself. It seems that SHA1 is no longer allowed for uploading to S3. It seems that before you could upload using SHA1 in some regions, for whatever reason. But not any more.

Do you have a follow up based on SHA64? I'll try to post anything I find out myself.

Comment

People who like this

0 Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image charlie_sbg · May 02, 2018 at 12:01 AM 0
Share

quick comments on this now that I have it working...

  • S3 seems to require "Signing Version 4" nowadays, which is more difficult due to it calling hashes with other hashes. Each stage of this key building is nonsensical from a human-readable point of view. However this page was useful as it gives the results of each hash at each step: https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html

  • when running the form with UnityWebRequest make sure to call Post() AND Send()! Then when I tried to wait on the result (yield return www) that didn't work either! It returned immediatley with no result. I had to spin watching for www.isDone() instead.

  • if you want this to upload to s3 during a build (not gameplay), then there is a S3 Upload plugin for Jenkins. I didn't play with it much but it might work. It might not. :P

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

3 People are following this question.

avatar image avatar image avatar image

Related Questions

How to Upload multiple files to a server using UnityWebRequest.Post(); 3 Answers

Upload large files 0 Answers

POST request using WWW class. error: necessary data rewind wasn't possible 2 Answers

WWW object returns "Failed Downloading" on URL 0 Answers

How do I post a cURL request from Unity? 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges