utils.GenerateAWSSignature.java Source code

Java tutorial

Introduction

Here is the source code for utils.GenerateAWSSignature.java

Source

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

}