/*
* Copyright 2013 Cloud4SOA, www.cloud4soa.eu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package utils;
import java.util.Map;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.SignatureException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.codec.binary.Base64;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map.Entry;
import javax.xml.transform.stream.StreamSource;
/**
*
* @author Ledakis Giannis (SingularLogic)
*/
public class GenerateAWSSignature {
private static String service_host = "elasticbeanstalk.us-east-1.amazonaws.com";
private String AWS_BASE_URL = "https://" + service_host + "/?";
//private AmazonFPSConfig config = null;
private String awsAccessKeyId = null;
private String awsSecretAccessKey = null;
private static JAXBContext jaxbContext;
private static ThreadLocal<Unmarshaller> unmarshaller;
private static Pattern ERROR_PATTERN_ONE = Pattern.compile(".*\\<RequestId>(.*)\\</RequestId>.*\\<Error>" +
"\\<Code>(.*)\\</Code>\\<Message>(.*)\\</Message>\\</Error>.*(\\<Error>)?.*",
Pattern.MULTILINE | Pattern.DOTALL);
private static Pattern ERROR_PATTERN_TWO = Pattern.compile(".*\\<Error>\\<Code>(.*)\\</Code>\\<Message>(.*)" +
"\\</Message>\\</Error>.*(\\<Error>)?.*\\<RequestID>(.*)\\</RequestID>.*",
Pattern.MULTILINE | Pattern.DOTALL);
private static String DEFAULT_ENCODING = "UTF-8";
/**
* Computes RFC 2104-compliant HMAC signature for request parameters
* Implements AWS Signature, as per following spec:
*
* If Signature Version is 0, it signs concatenated Action and Timestamp
*
* If Signature Version is 1, it performs the following:
*
* Sorts all parameters (including SignatureVersion and excluding Signature,
* the value of which is being created), ignoring case.
*
* Iterate over the sorted list and append the parameter name (in original case)
* and then its value. It will not URL-encode the parameter values before
* constructing this string. There are no separators.
*
* If Signature Version is 2, string to sign is based on following:
*
* 1. The HTTP Request Method followed by an ASCII newline (%0A)
* 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline.
* 3. The URL encoded HTTP absolute path component of the URI
* (up to but not including the query string parameters);
* if this is empty use a forward '/'. This parameter is followed by an ASCII newline.
* 4. The concatenation of all query string components (names and values)
* as UTF-8 characters which are URL encoded as per RFC 3986
* (hex characters MUST be uppercase), sorted using lexicographic byte ordering.
* Parameter names are separated from their values by the '=' character
* (ASCII character 61), even if the value is empty.
* Pairs of parameter and values are separated by the '&' character (ASCII code 38).
*
*/
private String signParameters(Map<String, String> parameters, String key)
throws SignatureException {
String signatureVersion = parameters.get("SignatureVersion");
String algorithm = "HmacSHA1";
String stringToSign = null;
if ("0".equals(signatureVersion)) {
stringToSign = calculateStringToSignV0(parameters);
} else if ("1".equals(signatureVersion)) {
stringToSign = calculateStringToSignV1(parameters);
} else if ("2".equals(signatureVersion)) {
//algorithm = config.getSignatureMethod();
algorithm = "HmacSHA256";
parameters.put("SignatureMethod", algorithm);
stringToSign = calculateStringToSignV2(parameters);
} else {
throw new SignatureException("Invalid Signature Version specified");
}
System.out.println("Calculated string to sign: " + stringToSign);
return sign(stringToSign, key, algorithm);
}
/**
* Calculate String to Sign for SignatureVersion 0
* @param parameters request parameters
* @return String to Sign
* @throws java.security.SignatureException
*/
private String calculateStringToSignV0(Map<String, String> parameters) {
StringBuilder data = new StringBuilder();
data.append(parameters.get("Action")).append(parameters.get("Timestamp"));
return data.toString();
}
/**
* Calculate String to Sign for SignatureVersion 1
* @param parameters request parameters
* @return String to Sign
* @throws java.security.SignatureException
*/
private String calculateStringToSignV1(Map<String, String> parameters) {
StringBuilder data = new StringBuilder();
Map<String, String> sorted = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
sorted.putAll(parameters);
Iterator pairs = sorted.entrySet().iterator();
while (pairs.hasNext()) {
Map.Entry pair = (Map.Entry)pairs.next();
data.append(pair.getKey());
data.append(pair.getValue());
}
return data.toString();
}
/**
* Calculate String to Sign for SignatureVersion 2
* @param parameters request parameters
* @return String to Sign
* @throws java.security.SignatureException
*/
private String calculateStringToSignV2(Map<String, String> parameters)
throws SignatureException {
StringBuilder data = new StringBuilder();
data.append("POST");
data.append("\n");
URI endpoint = null;
try {
//endpoint = new URI(config.getServiceURL().toLowerCase());
endpoint = new URI(AWS_BASE_URL.toLowerCase() );
} catch (URISyntaxException ex) {
System.out.println("URI Syntax Exception");
throw new SignatureException("URI Syntax Exception thrown " +
"while constructing string to sign", ex);
}
data.append(endpoint.getHost());
data.append("\n");
String uri = endpoint.getPath();
if (uri == null || uri.length() == 0) {
uri = "/";
}
data.append(urlEncode(uri, true));
data.append("\n");
Map<String, String> sorted = new TreeMap<String, String>();
sorted.putAll(parameters);
Iterator<Map.Entry<String, String>> pairs = sorted.entrySet().iterator();
while (pairs.hasNext()) {
Map.Entry<String, String> pair = pairs.next();
String key = pair.getKey();
data.append(urlEncode(key, false));
data.append("=");
String value = pair.getValue();
data.append(urlEncode(value, false));
if (pairs.hasNext()) {
data.append("&");
}
}
return data.toString();
}
private String urlEncode(String value, boolean path) {
String encoded = null;
try {
encoded = URLEncoder.encode(value, DEFAULT_ENCODING)
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E","~");
if (path) {
encoded = encoded.replace("%2F", "/");
}
} catch (UnsupportedEncodingException ex) {
System.out.println("Unsupported Encoding Exception");
throw new RuntimeException(ex);
}
return encoded;
}
/**
* Computes RFC 2104-compliant HMAC signature.
*
*/
private String sign(String data, String key, String algorithm) throws SignatureException {
byte [] signature;
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key.getBytes(), algorithm));
signature = Base64.encodeBase64(mac.doFinal(data.getBytes(DEFAULT_ENCODING)));
} catch (Exception e) {
throw new SignatureException("Failed to generate signature: " + e.getMessage(), e);
}
return new String(signature);
}
}