com.eucalyptus.objectstorage.pipeline.auth.S3Authentication.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.objectstorage.pipeline.auth.S3Authentication.java

Source

/*************************************************************************
 * Copyright 2009-2015 Eucalyptus Systems, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 ************************************************************************/

package com.eucalyptus.objectstorage.pipeline.auth;

import com.eucalyptus.auth.euare.DelegatingUserPrincipal;
import com.eucalyptus.auth.principal.PolicyVersion;
import com.eucalyptus.auth.principal.PolicyVersions;
import com.eucalyptus.auth.principal.Principals;
import com.eucalyptus.auth.principal.UserPrincipal;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.context.NoSuchContextException;
import com.eucalyptus.crypto.util.SecurityHeader;
import com.eucalyptus.crypto.util.SecurityParameter;
import com.eucalyptus.crypto.util.Timestamps;
import com.eucalyptus.crypto.util.Timestamps.Type;
import com.eucalyptus.http.MappingHttpRequest;
import com.eucalyptus.objectstorage.exceptions.s3.*;
import com.eucalyptus.objectstorage.pipeline.auth.S3V4Authentication.V4AuthComponent;
import com.eucalyptus.objectstorage.pipeline.handlers.AwsChunkStream.AwsChunk;
import com.eucalyptus.objectstorage.util.ObjectStorageProperties;
import com.eucalyptus.ws.util.HmacUtils.SignatureCredential;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import javaslang.control.Try.CheckedFunction;
import org.apache.log4j.Logger;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.joda.time.DateTime;

import javax.annotation.Nonnull;
import javax.security.auth.Subject;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

import static com.eucalyptus.objectstorage.pipeline.auth.S3V2Authentication.AWS_V2_AUTH_TYPE;

/**
 * REST and query string based V2 and V4 authentication for S3.
 *
 * @see <a href="http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html">AWS S3 Sigv4 docs</a>
 * @see <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html">AWS S3 Sigv2 docs</a>
 */
public final class S3Authentication {
    private static final Logger LOG = Logger.getLogger(S3Authentication.class);

    private S3Authentication() {
    }

    public enum S3Authenticator {
        V2_HEADER {
            public void authenticate(MappingHttpRequest request, Map<String, String> lowercaseParams)
                    throws S3Exception {
                String authHeader = request.getHeader(HttpHeaders.Names.AUTHORIZATION)
                        .replaceFirst(AWS_V2_AUTH_TYPE, "").trim();
                String[] signatureElements = authHeader.split(":");
                String accessKeyId = signatureElements[0];
                String signature = signatureElements[1];
                String dateStr = getDateFromHeaders(request);
                Date date = parseDate(dateStr);
                assertDateNotSkewed(date);
                if (request.getHeader(SecurityHeader.X_Amz_Date.header()) != null)
                    dateStr = "";
                String canonicalizedAmzHeaders = S3V2Authentication.buildCanonicalHeaders(request, false);
                String securityToken = request.getHeader(SecurityParameter.X_Amz_Security_Token.parameter());
                S3V2Authentication.login(request, date, dateStr, canonicalizedAmzHeaders, accessKeyId, signature,
                        securityToken);
            }
        },

        V2_PARAMS {
            public void authenticate(MappingHttpRequest request, Map<String, String> lowercaseParams)
                    throws S3Exception {
                String expiresStr = S3V2Authentication.getAndValidateExpiresFromParameters(lowercaseParams);
                String canonicalizedAmzHeaders = S3V2Authentication.buildCanonicalHeaders(request, true);
                String accessKeyId = request.getParameters().remove(SecurityParameter.AWSAccessKeyId.toString());
                String signature = getSignatureFromParameters(lowercaseParams);
                String securityToken = lowercaseParams
                        .get(SecurityParameter.X_Amz_Security_Token.parameter().toLowerCase());
                S3V2Authentication.login(request, null, expiresStr, canonicalizedAmzHeaders, accessKeyId, signature,
                        securityToken);
            }
        },

        V4_HEADER {
            public void authenticate(MappingHttpRequest request, Map<String, String> lowercaseParams)
                    throws S3Exception {
                Map<V4AuthComponent, String> authComponents = S3V4Authentication
                        .getV4AuthComponents(request.getHeader(HttpHeaders.Names.AUTHORIZATION));
                String dateStr = getDateFromHeaders(request);
                Date date = parseDate(dateStr);
                assertDateNotSkewed(date);
                SignatureCredential credential = S3V4Authentication.getAndVerifyCredential(date,
                        authComponents.get(V4AuthComponent.Credential));
                String signedHeaders = authComponents.get(V4AuthComponent.SignedHeaders);
                String signature = authComponents.get(V4AuthComponent.Signature);
                String securityToken = request.getHeader(SecurityParameter.X_Amz_Security_Token.parameter());
                String payloadHash = S3V4Authentication.getUnverifiedPayloadHash(request);
                Long decodedContentLength = S3V4Authentication.getAndVerifyDecodedContentLength(request,
                        payloadHash);
                S3V4Authentication.login(request, date, credential, signedHeaders, signature, securityToken,
                        payloadHash);

                // Convert content length from V4 to V2
                if (decodedContentLength != null)
                    HttpHeaders.setContentLength(request, decodedContentLength);
            }
        },

        V4_PARAMS {
            public void authenticate(MappingHttpRequest request, Map<String, String> lowercaseParams)
                    throws S3Exception {
                String dateStr = S3V4Authentication.getDateFromParams(lowercaseParams);
                Date date = parseDate(dateStr);
                S3V4Authentication.validateExpiresFromParams(lowercaseParams, date);
                String credentialStr = lowercaseParams
                        .get(SecurityParameter.X_Amz_Credential.parameter().toLowerCase());
                SignatureCredential credential = S3V4Authentication.getAndVerifyCredential(date, credentialStr);
                String signedHeaders = lowercaseParams
                        .get(SecurityParameter.X_Amz_SignedHeaders.parameter().toLowerCase());
                String signature = lowercaseParams.get(SecurityParameter.X_Amz_Signature.parameter().toLowerCase());
                String securityToken = lowercaseParams
                        .get(SecurityParameter.X_Amz_Security_Token.parameter().toLowerCase());
                S3V4Authentication.login(request, date, credential, signedHeaders, signature, securityToken,
                        S3V4Authentication.UNSIGNED_PAYLOAD);
            }
        },

        ANONYMOUS {
            public void authenticate(MappingHttpRequest request, Map<String, String> lowercaseParams)
                    throws S3Exception {
                try {
                    final Context context = Contexts.lookup(request.getCorrelationId());
                    final Subject subject = new Subject();
                    final UserPrincipal principal = new DelegatingUserPrincipal(Principals.nobodyUser()) {
                        @Nonnull
                        @Override
                        public List<PolicyVersion> getPrincipalPolicies() {
                            return ImmutableList.of(PolicyVersions.getAdministratorPolicy());
                        }
                    };
                    subject.getPrincipals().add(principal);
                    context.setUser(principal);
                    context.setSubject(subject);
                } catch (NoSuchContextException e) {
                    LOG.error(e, e);
                    throw new AccessDeniedException();
                }
            }
        };

        /**
         * Authenticates the request.
         *
         * @throws AccessDeniedException          if the auth info is invalid
         * @throws SignatureDoesNotMatchException if the signature is invalid
         * @throws InvalidAccessKeyIdException    if the contextual AWS key is is invalid
         * @throws InternalErrorException         if something unexpected occurs
         */
        public abstract void authenticate(MappingHttpRequest request, Map<String, String> lowercaseParams)
                throws S3Exception;

        /**
         * Returns the S3Authenticator for the request.
         *
         * @throws MissingSecurityHeaderException if an Authorization header is present, but is invalid
         */
        public static S3Authenticator of(MappingHttpRequest request, Map<String, String> lowercaseParams)
                throws MissingSecurityHeaderException {
            // Handle headers request
            String authHeader = request.getHeader(SecurityParameter.Authorization.toString());
            if (!Strings.isNullOrEmpty(authHeader)) {
                if (authHeader.startsWith(S3V4Authentication.AWS_V4_AUTH_TYPE))
                    return S3Authenticator.V4_HEADER;
                else if (authHeader.startsWith(S3V2Authentication.AWS_V2_AUTH_TYPE))
                    return S3Authenticator.V2_HEADER;
                else
                    throw new MissingSecurityHeaderException(null,
                            "Malformed or unexpected format for Authentication header");
            }

            // Handle param request
            if (lowercaseParams.containsKey(SecurityParameter.X_Amz_Algorithm.parameter().toLowerCase())
                    || lowercaseParams.containsKey(SecurityParameter.X_Amz_Date.parameter().toLowerCase()))
                return S3Authenticator.V4_PARAMS;
            else if (request.getParameters().containsKey(SecurityParameter.AWSAccessKeyId.parameter()))
                return S3Authenticator.V2_PARAMS;

            // Handle anonymous request
            return S3Authenticator.ANONYMOUS;
        }
    }

    public static void authenticateV4Streaming(MappingHttpRequest request, List<AwsChunk> chunks)
            throws S3Exception {
        Map<V4AuthComponent, String> authComponents = S3V4Authentication
                .getV4AuthComponents(request.getHeader(HttpHeaders.Names.AUTHORIZATION));
        String dateStr = getDateFromHeaders(request);
        Date date = parseDate(dateStr);
        SignatureCredential credential = S3V4Authentication.getAndVerifyCredential(date,
                authComponents.get(V4AuthComponent.Credential));
        String signedHeaders = authComponents.get(V4AuthComponent.SignedHeaders);
        String securityToken = request.getHeader(SecurityParameter.X_Amz_Security_Token.parameter());
        String seedSignature = authComponents.get(V4AuthComponent.Signature);

        for (AwsChunk chunk : chunks) {
            String previousSignature = chunk.previousSignature == null ? seedSignature : chunk.previousSignature;
            S3V4Authentication.loginChunk(request, date, credential, signedHeaders, chunk.chunkSignature,
                    securityToken, previousSignature, chunk.getPayload());
        }
    }

    private static String getDateFromHeaders(MappingHttpRequest request) throws AccessDeniedException {
        String result = request.getHeader(SecurityHeader.X_Amz_Date.header());
        if (result == null)
            result = request.getHeader(SecurityHeader.Date.header());
        if (result == null)
            throw new AccessDeniedException(null, "X-Amz-Date header must be specified.");
        return result;
    }

    static Date parseDate(String dateStr) throws AccessDeniedException {
        Date date = null;

        try {
            date = Timestamps.parseTimestamp(dateStr, Type.ISO_8601);
        } catch (Exception ignore) {
        }

        try {
            if (date == null)
                date = Timestamps.parseTimestamp(dateStr, Type.RFC_2616);
        } catch (Exception ex) {
            LOG.error("Cannot parse date: " + dateStr);
            throw new AccessDeniedException(null, "Unable to parse date.");
        }

        return date;
    }

    static void assertDateNotSkewed(final Date date) throws RequestTimeTooSkewedException {
        DateTime currentTime = DateTime.now();
        DateTime dt = new DateTime(date);
        if (dt.isBefore(currentTime.minusMillis((int) ObjectStorageProperties.EXPIRATION_LIMIT)))
            throw new RequestTimeTooSkewedException();
        if (dt.isAfter(currentTime.plusMillis((int) ObjectStorageProperties.EXPIRATION_LIMIT)))
            throw new RequestTimeTooSkewedException();
    }

    private static String getSignatureFromParameters(Map<String, String> parameters)
            throws InvalidSecurityException {
        String signature = parameters.remove(SecurityParameter.Signature.toString().toLowerCase());
        if (signature == null)
            throw new InvalidSecurityException("No signature found");
        return signature;
    }
}