com.wandisco.s3hdfs.auth.AWSAuthenticationHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.wandisco.s3hdfs.auth.AWSAuthenticationHandler.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package com.wandisco.s3hdfs.auth;

import org.apache.commons.codec.binary.Base64;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.SignatureException;
import java.util.Properties;

import static com.wandisco.s3hdfs.conf.S3HdfsConstants.DEFAULT_CHARSET;

public class AWSAuthenticationHandler implements AuthenticationHandler {
    public final static String AUTH_TYPE_KEY = AuthenticationFilter.AUTH_TYPE;
    /**
     * Constant that identifies the authentication mechanism.
     */
    public static final String TYPE = "AWS";
    /**
     * HTTP header used by the AWS client endpoint during an authentication sequence.
     */
    public static final String AUTHORIZATION = "Authorization";
    private static Logger LOG = LoggerFactory.getLogger(AWSAuthenticationHandler.class);
    private static String[] tempHackCredentialsArray = { "AKIAD9F6L38VNF8MS6F9", "jagane",
            "kd8urKd984Kc8/Jf84Jgfu7F54dgbLd0Jf8gGf5l", "AKIAJKG98EJKFGER89AQ", "shv",
            "fg8945jkgkw8s/JKLD904JKDjs8ksjg873kxncur" };

    private static String[] getNameAndSecretAccessKey(String accessKeyId) {
        for (int i = 0; i < tempHackCredentialsArray.length; i += 3) {
            if (accessKeyId.equals(tempHackCredentialsArray[i])) {
                String[] rv = new String[2];
                rv[0] = tempHackCredentialsArray[i + 1];
                rv[1] = tempHackCredentialsArray[i + 2];
                return rv;
            }
        }
        return null;
    }

    /**
     * Returns the authentication type of the authentication handler, 'AWS'.
     * <p/>
     *
     * @return the authentication type of the authentication handler, 'AWS'.
     */
    @Override
    public String getType() {
        return TYPE;
    }

    /**
     * Verifies the AWS authentication header
     * <p/>
     *
     * @param request  the HTTP client request.
     * @param response the HTTP client response.
     * @return an authentication token if the AWS authentication header is correct
     * <code>null</code> if it is in progress (in this case the handler handles the response to the client).
     * @throws IOException             thrown if an IO error occurred.
     * @throws AuthenticationException thrown if the AWS authentication header is incorrect
     */
    @Override
    public AuthenticationToken authenticate(HttpServletRequest request, final HttpServletResponse response)
            throws IOException, AuthenticationException {
        String authorization = request.getHeader(AUTHORIZATION);
        LOG.debug("authenticate - authorization = " + authorization);
        if (authorization == null || !authorization.startsWith("AWS")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return null;
        } else {
            if (LOG.isDebugEnabled())
                LOG.debug("authenticate - returning jagane");
            String[] splitAuth = authorization.split("\\s");
            if (splitAuth.length < 2) {
                LOG.warn("authenticate - auth string does not have enough info. " + authorization);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return null;
            }
            String[] splitKey = splitAuth[1].split(":");
            if (splitKey.length < 2) {
                LOG.warn("authenticate - auth string does not have key/signature. " + authorization);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return null;
            }
            String nameAndSecretAccessKey[] = getNameAndSecretAccessKey(splitKey[0]);
            if (nameAndSecretAccessKey == null || nameAndSecretAccessKey[1].length() == 0) {
                LOG.warn("authenticate - cannot find secretAccessKey for accessKeyId " + splitKey[0]);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return null;
            }
            String hostHeader = request.getHeader("Host");
            try {
                verifySignature(request.getMethod(), nameAndSecretAccessKey[1], splitKey[1], "HmacSHA1", hostHeader,
                        request.getRequestURI(), getCanonicalizedQueryString(request));
            } catch (Exception ex) {
                LOG.warn("verifySignature threw " + ex);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return null;
            }
            return new AuthenticationToken(nameAndSecretAccessKey[0], nameAndSecretAccessKey[0], "AWS");
        }
    }

    private String getCanonicalizedQueryString(HttpServletRequest request) {
        return null;
    }

    /**
     * The request is authenticated if we can regenerate the same signature given
     * on the request.  Before calling this function make sure to set the header values
     * defined by the public values above.
     *
     * @param httpVerb  - the type of HTTP request (e.g., GET, PUT)
     * @param secretKey - value obtained from the AWSAccessKeyId
     * @param signature - the signature we are trying to recreate, note can be URL-encoded
     * @param method    - { "HmacSHA1", "HmacSHA256" }
     * @return true if request has been authenticated, false otherwise
     * @throws SignatureException
     * @throws UnsupportedEncodingException
     */
    public boolean verifySignature(String httpVerb, String secretKey, String signature, String method,
            String hostHeader, String httpRequestURI, String canonicalizedQueryString)
            throws SignatureException, UnsupportedEncodingException {

        if (null == httpVerb || null == secretKey || null == signature)
            return false;

        httpVerb = httpVerb.trim();
        secretKey = secretKey.trim();
        signature = signature.trim();

        // -> first calculate the StringToSign after the caller has initialized all the header values
        String StringToSign = genStringToSign(httpVerb, hostHeader, httpRequestURI, canonicalizedQueryString);
        String calSig = calculateRFC2104HMAC(StringToSign, secretKey, method.equalsIgnoreCase("HmacSHA1"));

        // -> the passed in signature is defined to be URL encoded? (and it must be base64 encoded)
        int offset = signature.indexOf("%");
        if (-1 != offset)
            signature = URLDecoder.decode(signature, "UTF-8");

        boolean match = signature.equals(calSig);
        return match;
    }

    /**
     * This function generates the single string that will be used to sign with a users
     * secret key.
     * <p/>
     * StringToSign = HTTP-Verb + "\n" +
     * ValueOfHostHeaderInLowercase + "\n" +
     * HTTPRequestURI + "\n" +
     * CanonicalizedQueryString
     *
     * @return The single StringToSign or null.
     */
    private String genStringToSign(String httpVerb, String hostHeader, String httpRequestURI,
            String canonicalizedQueryString) {
        StringBuffer stringToSign = new StringBuffer();

        stringToSign.append(httpVerb).append("\n");

        if (null != hostHeader)
            stringToSign.append(hostHeader);
        stringToSign.append("\n");

        if (null != httpRequestURI)
            stringToSign.append(httpRequestURI);
        stringToSign.append("\n");

        if (null != canonicalizedQueryString)
            stringToSign.append(canonicalizedQueryString);

        if (0 == stringToSign.length())
            return null;
        else
            return stringToSign.toString();
    }

    /**
     * Create a signature by the following method:
     * new String( Base64( SHA1 or SHA256 ( key, byte array )))
     *
     * @param signIt    - the data to generate a keyed HMAC over
     * @param secretKey - the user's unique key for the HMAC operation
     * @param useSHA1   - if false use SHA256
     * @return String   - the recalculated string
     * @throws SignatureException
     */
    private String calculateRFC2104HMAC(String signIt, String secretKey, boolean useSHA1)
            throws SignatureException {
        SecretKeySpec key = null;
        Mac hmacShaAlg = null;
        String result = null;

        try {
            if (useSHA1) {
                key = new SecretKeySpec(secretKey.getBytes(DEFAULT_CHARSET), "HmacSHA1");
                hmacShaAlg = Mac.getInstance("HmacSHA1");
            } else {
                key = new SecretKeySpec(secretKey.getBytes(DEFAULT_CHARSET), "HmacSHA256");
                hmacShaAlg = Mac.getInstance("HmacSHA256");
            }

            hmacShaAlg.init(key);
            byte[] rawHmac = hmacShaAlg.doFinal(signIt.getBytes(DEFAULT_CHARSET));
            result = new String(Base64.encodeBase64(rawHmac), DEFAULT_CHARSET);

        } catch (Exception e) {
            throw new SignatureException("Failed to generate keyed HMAC on REST request: " + e.getMessage());
        }
        return result.trim();
    }

    /**
     * Releases any resources initialized by the authentication handler.
     */
    @Override
    public void destroy() {
    }

    @Override
    public boolean managementOperation(AuthenticationToken authenticationToken,
            HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws IOException, AuthenticationException {
        return false;
    }

    /**
     * Initializes the authentication handler instance.
     * <p/>
     * This method is invoked by the {@link AuthenticationFilter#init} method.
     *
     * @param config configuration properties to initialize the handler.
     * @throws ServletException thrown if the handler could not be initialized.
     */
    @Override
    public void init(Properties config) throws ServletException {
    }
}