com.amazon.advertising.api.common.HmacSecurityHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.amazon.advertising.api.common.HmacSecurityHandler.java

Source

package com.amazon.advertising.api.common;
/**********************************************************************************************
 * Copyright 2009 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
 * except in compliance with the License. A copy of the License is located at
 *
 *       http://aws.amazon.com/apache2.0/
 *
 * or in the "LICENSE.txt" file accompanying this file. This file 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.
 *
 * ********************************************************************************************
 *
 *  Amazon Product Advertising API
 *  Signed Requests Sample Code
 *
 *  API Version: 2009-03-31
 *
 */

import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.handlers.BasicHandler;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.soap.*;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class HmacSecurityHandler extends BasicHandler {
    /*
     * Configuration keys to use in the WSDD.
     */

    /**
     * Use this to specify the AWS Access Key ID
     */
    public static final String OPTION_AWS_ACCESS_KEY_ID = "awsAccessKeyId";

    /**
     * Use this to specify the AWS Secret Key
     */
    public static final String OPTION_AWS_SECRETY_KEY = "awsSecretyKey";

    /**
     * Algorithm used to calculate string hashes
     */
    private static final String SIGNATURE_ALGORITHM = "HmacSHA256";

    /**
     * Namespace for all AWS Security elements
     */
    private static final String AWS_SECURITY_NS = "http://security.amazonaws.com/doc/2007-01-01/";

    /**
     * Prefix for AWS Security namespace
     */
    private static final String AWS_SECURITY_NS_PREFIX = "aws";

    private String awsAccessKeyId = null;
    private String awsSecretKey = null;
    private SecretKeySpec keySpec = null;
    private Mac mac = null;

    public HmacSecurityHandler(String awsAccessKeyId, String awsSecretKey) {
        super();
        this.setOption(OPTION_AWS_ACCESS_KEY_ID, awsAccessKeyId);
        this.setOption(OPTION_AWS_SECRETY_KEY, awsSecretKey);
    }

    /* (non-Javadoc)
     * @see org.apache.axis.handlers.BasicHandler#init()
     */

    /**
     * Initializes the handler. Will throw RuntimeException if
     */
    public void init() {
        super.init();

        this.awsAccessKeyId = (String) getOption(OPTION_AWS_ACCESS_KEY_ID);
        this.awsSecretKey = (String) getOption(OPTION_AWS_SECRETY_KEY);

        if (null == this.awsAccessKeyId || null == this.awsSecretKey || this.awsAccessKeyId.length() == 0
                || this.awsSecretKey.length() == 0) {
            throw new RuntimeException("Missing configuration for handler!");
        }

        try {
            byte[] bytes = awsSecretKey.getBytes("UTF-8");
            this.keySpec = new SecretKeySpec(bytes, SIGNATURE_ALGORITHM);
            this.mac = Mac.getInstance(SIGNATURE_ALGORITHM);
            this.mac.init(keySpec);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        }

    }

    public void invoke(MessageContext mc) throws AxisFault {
        String actionUri = mc.getSOAPActionURI();
        String tokens[] = actionUri.split("/");
        String action = tokens[tokens.length - 1];

        String timestamp = getTimestamp();
        String signature;
        try {
            signature = this.calculateSignature(action, timestamp);
        } catch (Exception e) {
            throw AxisFault.makeFault(e);
        }

        try {
            SOAPMessageContext smc = (SOAPMessageContext) mc;
            SOAPMessage message = smc.getMessage();
            SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
            SOAPHeader header = envelope.getHeader();
            header.addNamespaceDeclaration(AWS_SECURITY_NS_PREFIX, AWS_SECURITY_NS);

            Name akidName = envelope.createName("AWSAccessKeyId", AWS_SECURITY_NS_PREFIX, AWS_SECURITY_NS);
            Name tsName = envelope.createName("Timestamp", AWS_SECURITY_NS_PREFIX, AWS_SECURITY_NS);
            Name sigName = envelope.createName("Signature", AWS_SECURITY_NS_PREFIX, AWS_SECURITY_NS);

            SOAPHeaderElement akidElement = header.addHeaderElement(akidName);
            SOAPHeaderElement tsElement = header.addHeaderElement(tsName);
            SOAPHeaderElement sigElement = header.addHeaderElement(sigName);

            akidElement.addTextNode(awsAccessKeyId);
            tsElement.addTextNode(timestamp);
            sigElement.addTextNode(signature);
        } catch (SOAPException e) {
            throw AxisFault.makeFault(e);
        }
    }

    /**
     * Calculates a time stamp from "now" in UTC and returns it in ISO8601 string
     * format. The soap message expires 15 minutes after this time stamp.
     * AWS only supports UTC and it's canonical representation as 'Z' in an
     * ISO8601 time string. E.g.  2008-02-10T23:59:59Z
     * <p/>
     * See http://www.w3.org/TR/xmlschema-2/#dateTime for more details.
     *
     * @return ISO8601 time stamp string for "now" in UTC.
     */
    public String getTimestamp() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat is08601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

        is08601.setTimeZone(TimeZone.getTimeZone("UTC"));
        return is08601.format(calendar.getTime());
    }

    /**
     * Method borrowed from AWS example code at http://developer.amazonwebservices.com/
     *
     * @param action    The single SOAP body element that is the action the
     *                  request is taking.
     * @param timestamp The time stamp string as provided in the &lt;aws:Timestamp&gt;
     *                  header element.
     * @return A hash calculated according to AWS security rules to be provided in the
     * &lt;aws:signature&gt; header element.
     * @throws Exception If there were errors or missing, required classes when
     *                   trying to calculate the hash.
     */
    public String calculateSignature(String action, String timestamp) throws Exception {
        String toSign = (action + timestamp);

        byte[] sigBytes = mac.doFinal(toSign.getBytes());
        return new String(Base64.encodeBase64(sigBytes));
    }

    /**
     * Keep serialization happy.
     */
    private static final long serialVersionUID = -5427862350845747488L;

}