/* * 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); } }