Monday, February 25, 2013

AWS Java - Securing S3 content using query string authentication

Amazon S3 is a highly available and durable hosting environment that can let you serve websites, images, and large files. Sometimes, you may want to secure your contents so only you or your authenticated users can access them. This becomes more important when it's pay content.

This post is about using query string authentication to make the content to be available for a specified period of time.

Specs:
  • Java 1.7
  • Eclipse Juno

Before you begin, make sure you have all the AWS Eclipse tools ready. Read Using Java AWS SDK to upload files to Amazon S3 for how to install the AWS SDK tool and a basic guide on how to upload, delete and retrieve files on S3.

Signing the request will require the following structure:

Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;

Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );

StringToSign = HTTP-Verb + "\n" +
 Content-MD5 + "\n" +
 Content-Type + "\n" +
 Date + "\n" +
 CanonicalizedAmzHeaders +
 CanonicalizedResource;

CanonicalizedResource = [ "/" + Bucket ] +
  +
 [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];

CanonicalizedAmzHeaders = 


Assuming that you either have read the post above or you have implemented upload, upload a file to your Amazon S3 account.

In the AWS Management console, set the file's ACL permissions to your administrative account only (By default, it should be already if you didn't programmatically changed the ACL permission).

We will implement the following function called getS3Url().


import java.util.Calendar;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.*;
import org.apache.commons.codec.binary.Hex;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.net.URLEncoder;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException;
/* method */
public String getS3Url(String filename) {
// get your AWS credentials; remember to have AwsCredentials.properties at your resource load path
AWSCredentials credentials = new ClasspathPropertiesFileCredentialsProvider().getCredentials();
String accessKey = credentials.getAWSAccessKeyId();
String secretKey = credentials.getAWSSecretKey();
// set the expiration to one hour later
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.add(Calendar.HOUR, 1);
long secondsSinceEpoch = calendar.getTimeInMillis() / 1000L;
String canonicalizedResource = "/" + BUCKET_NAME + "/" + filename;
String stringToSign = "GET" + "\n\n\n" + secondsSinceEpoch + "\n" + canonicalizedResource;
String signature = null;
try {
byte[] keyBytes = secretKey.getBytes();
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] digest = mac.doFinal(stringToSign.getBytes());
byte[] base64bytes = Base64.encodeBase64(digest);
String signedString = new String(base64bytes, "UTF-8");
signature = URLEncoder.encode(signedString, "UTF-8");
} catch (NoSuchAlgorithmException nsae) {
} catch (InvalidKeyException ike) {
} catch (UnsupportedEncodingException uee) {
}
return "https://s3.amazonaws.com" + canonicalizedResource + "?AWSAccessKeyId=" + accessKey + "&Expires=" + secondsSinceEpoch + "&Signature=" + signature;
}
view raw gistfile1.java hosted with ❤ by GitHub


We have set the expiration date to be one hour later. You would see the following expiration message an hour later.


< Error>
< Code>AccessDenied</ Code>
< Message>Access Denied</ Message>
< RequestId>8ECB67C2458CE483</ RequestId>
< HostId>
vL6wXNOkvYlpHXbvvlG1SGhy3q/+Ocb3guXtyaDZjmEu24Z4XQpwjfmNAvM+SViz
</ HostId>
</ Error>


No comments:

Post a Comment