Skip to content

Instantly share code, notes, and snippets.

@Aracki
Created January 31, 2017 14:16
Show Gist options
  • Save Aracki/a1bbdfb60bc3132ed6438a307778f0b0 to your computer and use it in GitHub Desktop.
Save Aracki/a1bbdfb60bc3132ed6438a307778f0b0 to your computer and use it in GitHub Desktop.
package com.copperdm.global.web.controllers.file_controllers;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.util.BinaryUtils;
import com.copperdm.global.web.config.props.AmazonProps;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Java Server-Side Example for Fine Uploader S3.
* Maintained by Widen Enterprises.
*
* This example:
* - handles non-CORS environments
* - handles delete file requests via the DELETE method
* - signs policy documents (simple uploads) and REST requests
* (chunked/multipart uploads)
* - handles both version 2 and version 4 signatures
*
* Requirements:
* - Java 1.5 or newer
* - Google GSon
* - Amazon Java SDK (only if utilizing the delete file feature)
*
* If you need to install the AWS SDK, see http://docs.aws.amazon.com/aws-sdk-php-2/guide/latest/installation.html.
*/
@Controller
@Slf4j
@RequestMapping(value = "/s3")
public class S3Controller extends HttpServlet {
@Autowired
AmazonProps amazonProps;
@RequestMapping(value = "/success", method = RequestMethod.POST)
public void success(HttpServletRequest req, HttpServletResponse resp) throws IOException {
handleUploadSuccessRequest(req, resp);
}
@RequestMapping(value = "/signature", method = RequestMethod.POST)
public void signature(HttpServletRequest req, HttpServletResponse resp) throws IOException {
handleSignatureRequest(req, resp);
}
// Main entry point for DELETE requests sent by Fine Uploader.
@RequestMapping(method = RequestMethod.DELETE)
public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String key = req.getParameter("key");
String bucket = req.getParameter("bucket");
resp.setStatus(200);
AWSCredentials myCredentials = new BasicAWSCredentials(amazonProps.getAccessKey(), amazonProps.getSecretKey());
AmazonS3 s3Client = new AmazonS3Client(myCredentials);
s3Client.deleteObject(bucket, key);
}
// Called by the main POST request handler if Fine Uploader has asked for an item to be signed. The item may be a
// policy document or a string that represents multipart upload request headers.
private void handleSignatureRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setStatus(200);
JsonParser jsonParser = new JsonParser();
JsonElement contentJson = jsonParser.parse(req.getReader());
JsonObject jsonObject = contentJson.getAsJsonObject();
if (req.getQueryString() != null && req.getQueryString().contains("v4=true")) {
handleV4SignatureRequest(jsonObject, contentJson, req, resp);
}
else {
handleV2SignatureRequest(jsonObject, contentJson, req, resp);
}
resp.setStatus(200);
}
private void handleV2SignatureRequest(JsonObject payload, JsonElement contentJson, HttpServletRequest req, HttpServletResponse resp) throws IOException{
String signature;
JsonElement headers = payload.get("headers");
JsonObject response = new JsonObject();
try
{
// If this is not a multipart upload-related request, Fine Uploader will send a policy document
// as the value of a "policy" property in the request. In that case, we must base-64 encode
// the policy document and then sign it. The will include the base-64 encoded policy and the signed policy document.
if (headers == null)
{
String base64Policy = base64EncodePolicy(contentJson);
signature = sign(base64Policy);
// Validate the policy document to ensure the client hasn't tampered with it.
// If it has been tampered with, set this property on the response and set the status to a non-200 value.
// response.addProperty("invalid", true);
response.addProperty("policy", base64Policy);
}
// If this is a request to sign a multipart upload-related request, we only need to sign the headers,
// which are passed as the value of a "headers" property from Fine Uploader. In this case,
// we only need to return the signed value.
else
{
signature = sign(headers.getAsString());
}
response.addProperty("signature", signature);
resp.getWriter().write(response.toString());
}
catch (Exception e)
{
resp.setStatus(500);
}
}
private void handleV4SignatureRequest(JsonObject payload, JsonElement contentJson, HttpServletRequest req, HttpServletResponse resp) throws IOException{
String signature;
JsonElement headers = payload.get("headers");
JsonObject response = new JsonObject();
try
{
// If this is not a multipart upload-related request, Fine Uploader will send a policy document
// as the value of a "policy" property in the request. In that case, we must base-64 encode
// the policy document and then sign it. The will include the base-64 encoded policy and the signed policy document.
if (headers == null)
{
String base64Policy = base64EncodePolicy(contentJson);
JsonArray conditions = payload.getAsJsonArray("conditions");
String credentialCondition = null;
for (int i = 0; i < conditions.size(); i++) {
JsonObject condition = conditions.get(i).getAsJsonObject();
JsonElement value = condition.get("x-amz-credential");
if (value != null) {
credentialCondition = value.getAsString();
break;
}
}
// Validate the policy document to ensure the client hasn't tampered with it.
// If it has been tampered with, set this property on the response and set the status to a non-200 value.
// response.addProperty("invalid", true);
Pattern pattern = Pattern.compile(".+\\/(.+)\\/(.+)\\/s3\\/aws4_request");
Matcher matcher = pattern.matcher(credentialCondition);
matcher.matches();
signature = getV4Signature(matcher.group(1), matcher.group(2), base64Policy);
response.addProperty("policy", base64Policy);
}
// If this is a request to sign a multipart upload-related request, we only need to sign the headers,
// which are passed as the value of a "headers" property from Fine Uploader. In this case,
// we only need to return the signed value.
else
{
Pattern pattern = Pattern.compile(".+\\n.+\\n(\\d+)\\/(.+)\\/s3\\/aws4_request\\n(.+)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(headers.getAsString());
matcher.matches();
String canonicalRequest = matcher.group(3);
String hashedCanonicalRequest = hash256(canonicalRequest);
String stringToSign = headers.getAsString().replaceAll("(?s)(.+s3\\/aws4_request\\n).+", "$1" + hashedCanonicalRequest);
// Validate the policy document to ensure the client hasn't tampered with it.
// If it has been tampered with, set this property on the response and set the status to a non-200 value.
// response.addProperty("invalid", true);
signature = getV4Signature(matcher.group(1), matcher.group(2), stringToSign);
}
response.addProperty("signature", signature);
resp.getWriter().write(response.toString());
}
catch (Exception e)
{
resp.setStatus(500);
}
}
// Called by the main POST request handler if Fine Uploader has indicated that the file has been
// successfully sent to S3. You have the opportunity here to examine the file in S3 and "fail" the upload
// if something in not correct.
private void handleUploadSuccessRequest(HttpServletRequest req, HttpServletResponse resp)
{
String key = req.getParameter("key");
String uuid = req.getParameter("uuid");
String bucket = req.getParameter("bucket");
String name = req.getParameter("name");
resp.setStatus(200);
System.out.println(String.format("Upload successfully sent to S3! Bucket: %s, Key: %s, UUID: %s, Filename: %s",
bucket, key, uuid, name));
}
private String getV4Signature(String date, String region, String stringToSign) throws Exception {
byte[] kSecret = ("AWS4" + amazonProps.getSecretKey()).getBytes("UTF8");
byte[] kDate = sha256Encode(date, kSecret);
byte[] kRegion = sha256Encode(region, kDate);
byte[] kService = sha256Encode("s3", kRegion);
byte[] kSigning = sha256Encode("aws4_request", kService);
byte[] kSignature = sha256Encode(stringToSign, kSigning);
return Hex.encodeHexString(kSignature);
}
private byte[] sha256Encode(String data, byte[] key) throws Exception {
String algorithm="HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF8"));
}
private String hash256(String data) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(data.getBytes());
return bytesToHex(md.digest());
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte byt : bytes) result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
private String base64EncodePolicy(JsonElement jsonElement) throws UnsupportedEncodingException {
String policyJsonStr = jsonElement.toString();
return BinaryUtils.toBase64 (policyJsonStr.getBytes("UTF-8"));
}
private String sign(String toSign) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
Mac hmac = Mac.getInstance("HmacSHA1");
hmac.init(new SecretKeySpec(amazonProps.getSecretKey().getBytes("UTF-8"), "HmacSHA1"));
return BinaryUtils.toBase64 (hmac.doFinal(toSign.getBytes("UTF-8")));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment