edu.ucsb.eucalyptus.transport.query.WalrusQuerySecurityHandler.java Source code

Java tutorial

Introduction

Here is the source code for edu.ucsb.eucalyptus.transport.query.WalrusQuerySecurityHandler.java

Source

/*
 * Author: Sunil Soman sunils@cs.ucsb.edu
 */

package edu.ucsb.eucalyptus.transport.query;

import edu.ucsb.eucalyptus.cloud.entities.UserInfo;
import edu.ucsb.eucalyptus.keys.AbstractKeyStore;
import edu.ucsb.eucalyptus.keys.ServiceKeyStore;
import edu.ucsb.eucalyptus.util.CaseInsensitiveMap;
import edu.ucsb.eucalyptus.util.EucalyptusProperties;
import edu.ucsb.eucalyptus.util.WalrusProperties;
import org.apache.log4j.Logger;
import org.apache.commons.httpclient.util.DateUtil;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.util.encoders.Base64;

import java.io.StringReader;
import java.net.URLDecoder;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

/*******************************************************************************
 * Copyright (c) 2009  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, only version 3 of the License.
 *
 *
 * This file 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., 130 Castilian
 * Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/>
 * if you need additional information or have any questions.
 *
 * This file may incorporate work covered under the following copyright and
 * permission notice:
 *
 *   Software License Agreement (BSD License)
 *
 *   Copyright (c) 2008, Regents of the University of California
 *   All rights reserved.
 *
 *   Redistribution and use of this software in source and binary forms, with
 *   or without modification, are permitted provided that the following
 *   conditions are met:
 *
 *     Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *     Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 *   IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 *   TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *   PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 *   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF
 *   THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE
 *   LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS
 *   SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *   IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA
 *   BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN
 *   THE REGENTS DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
 *   OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR
 *   WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH
 *   ANY SUCH LICENSES OR RIGHTS.
 ******************************************************************************/

public class WalrusQuerySecurityHandler extends HMACQuerySecurityHandler {

    private static Logger LOG = Logger.getLogger(WalrusQuerySecurityHandler.class);
    private final static long EXPIRATION_LIMIT = 900000;

    public QuerySecurityHandler getInstance() {
        return new WalrusQuerySecurityHandler();
    }

    public String getName() {
        return "2";
    }

    public UserInfo authenticate(final HttpRequest httpRequest) throws QuerySecurityException {
        String addr = httpRequest.getRequestURL();

        String verb = httpRequest.getHttpMethod();
        Map<String, String> httpParams = httpRequest.getParameters();
        Map<String, String> headers = httpRequest.getHeaders();
        return handle(addr, verb, httpParams, headers);
    }

    private String getStringStats(String s) {
        int len = s.length();
        String out = "length=" + len + " buf[n-1]=" + s.getBytes()[len - 1] + " hash=" + s.hashCode();
        return out;
    }

    public UserInfo handle(String addr, String verb, Map<String, String> parameters, Map<String, String> headers)
            throws QuerySecurityException {
        CaseInsensitiveMap hdrs = new CaseInsensitiveMap(headers);

        //this.checkParameters( hdrs );
        //:: check the signature :://

        if (hdrs.containsKey(StorageQuerySecurityHandler.StorageSecurityParameters.EucaSignature)) {
            //possible internal request -- perform authentication using internal credentials
            String date = (String) hdrs.remove(SecurityParameter.Date);
            String eucaCert = (String) hdrs.remove(StorageQuerySecurityHandler.StorageSecurityParameters.EucaCert);
            String signature = (String) hdrs
                    .remove(StorageQuerySecurityHandler.StorageSecurityParameters.EucaSignature);
            String data = verb + "\n" + date + "\n" + addr + "\n";

            Signature sig;
            boolean valid = false;
            try {
                byte[] bytes = Base64.decode(eucaCert);
                String certString = new String(bytes);
                PEMReader pemReader = new PEMReader(new StringReader(certString));
                X509Certificate cert = (X509Certificate) pemReader.readObject();
                AbstractKeyStore keyStore = ServiceKeyStore.getInstance();
                if (keyStore.getCertificateAlias(cert) != null) {
                    //cert found in keystore
                    PublicKey publicKey = cert.getPublicKey();
                    sig = Signature.getInstance("SHA1withRSA");

                    sig.initVerify(publicKey);
                    sig.update(data.getBytes());
                    valid = sig.verify(Base64.decode(signature));
                } else {
                    LOG.warn("WalrusQuerySecurityHandler(): certificate not found in keystore");
                }
            } catch (Exception ex) {
                LOG.warn("Authentication exception: " + ex.getMessage());
                ex.printStackTrace();
            }

            if (!valid) {
                throw new QuerySecurityException("User authentication failed.");
            }
            //run as admin
            UserInfo admin = new UserInfo(EucalyptusProperties.NAME);
            admin.setIsAdministrator(Boolean.TRUE);
            return admin;
        } else if (hdrs.containsKey(WalrusProperties.FormField.FormUploadPolicyData)) {
            String data = (String) hdrs.remove(WalrusProperties.FormField.FormUploadPolicyData);
            String auth_part = (String) hdrs.remove(SecurityParameter.Authorization);

            if (auth_part != null) {
                String sigString[] = getSigInfo(auth_part);
                String signature = sigString[1];
                return getUserInfo(sigString[0], signature, data, false);
            }
            throw new QuerySecurityException("User authentication failed.");
        } else {
            //external user request
            String date;
            String verifyDate;
            if (hdrs.containsKey("x-amz-date")) {
                date = "";
                verifyDate = (String) hdrs.get("x-amz-date");
            } else {
                date = (String) hdrs.remove(SecurityParameter.Date);
                verifyDate = date;
                if (date == null || date.length() <= 0)
                    throw new QuerySecurityException("User authentication failed. Date must be specified.");
            }

            try {
                Date dateToVerify = DateUtil.parseDate(verifyDate);
                Date currentDate = new Date();
                if (Math.abs(currentDate.getTime() - dateToVerify.getTime()) > EXPIRATION_LIMIT)
                    throw new QuerySecurityException("Message expired. Sorry.");
            } catch (Exception ex) {
                throw new QuerySecurityException("Unable to parse date.");
            }
            String content_md5 = (String) hdrs.remove("Content-MD5");
            content_md5 = content_md5 == null ? "" : content_md5;
            String content_type = (String) hdrs.remove("Content-Type");
            content_type = content_type == null ? "" : content_type;

            String[] addrStrings = addr.split("\\?");
            String addrString = addrStrings[0];

            if (addrStrings.length > 1) {
                for (SubResource subResource : SubResource.values()) {
                    if (addr.endsWith(subResource.toString())) {
                        addrString += "?" + subResource.toString();
                        break;
                    }
                }
            }

            String data = verb + "\n" + content_md5 + "\n" + content_type + "\n" + date + "\n"
                    + getCanonicalizedAmzHeaders(hdrs) + addrString;

            String auth_part = hdrs.remove(SecurityParameter.Authorization);

            if (auth_part != null) {
                String sigString[] = getSigInfo(auth_part);
                String signature = sigString[1];
                return getUserInfo(sigString[0], signature, data, false);
            } else if (parameters.containsKey(SecurityParameter.AWSAccessKeyId.toString())) {
                //query string authentication
                String accesskeyid = parameters.remove(SecurityParameter.AWSAccessKeyId.toString());
                try {
                    String signature = URLDecoder.decode(parameters.remove(SecurityParameter.Signature.toString()),
                            "UTF-8");
                    if (signature == null) {
                        throw new QuerySecurityException("User authentication failed. Null signature.");
                    }
                    String expires = parameters.remove(SecurityParameter.Expires.toString());
                    if (expires == null) {
                        throw new QuerySecurityException("Authentication failed. Expires must be specified.");
                    }
                    if (checkExpires(expires)) {
                        String stringToSign = verb + "\n" + content_md5 + "\n" + content_type + "\n"
                                + Long.parseLong(expires) + "\n" + getCanonicalizedAmzHeaders(hdrs) + addrString;
                        return getUserInfo(accesskeyid, signature, stringToSign, true);
                    } else {
                        throw new QuerySecurityException("Cannot process request. Expired.");
                    }
                } catch (Exception ex) {
                    throw new QuerySecurityException("Could not verify request " + ex.getMessage());
                }
            } else {
                //anonymous request
                return null;
            }
        }
    }

    private UserInfo getUserInfo(String accessKeyID, String signature, String data, boolean decode)
            throws QuerySecurityException {
        signature = signature.replaceAll("=", "");

        String queryKey = findQueryKey(accessKeyID);

        String authSig = checkSignature(queryKey, data);

        if (decode) {
            try {
                authSig = URLDecoder.decode(authSig, "UTF-8");
            } catch (Exception ex) {
                throw new QuerySecurityException(ex.getMessage());
            }
        }

        if (!authSig.equals(signature))
            throw new QuerySecurityException("User authentication failed. Could not verify signature");

        return findUserId(accessKeyID);
    }

    private boolean checkExpires(String expires) {
        Long expireTime = Long.parseLong(expires);
        Long currentTime = new Date().getTime() / 1000;
        if (currentTime > expireTime)
            return false;
        return true;
    }

    private void checkParameters(final CaseInsensitiveMap header) throws QuerySecurityException {
        if (!header.containsKey(SecurityParameter.Authorization.toString()))
            throw new QuerySecurityException("Missing required parameter: " + SecurityParameter.Authorization);
        if (!header.containsKey(SecurityParameter.Date.toString()))
            throw new QuerySecurityException("Missing required parameter: " + SecurityParameter.Date);
    }

    private String[] getSigInfo(String auth_part) {
        int index = auth_part.lastIndexOf(" ");
        String sigString = auth_part.substring(index + 1);
        return sigString.split(":");
    }

    private String getCanonicalizedAmzHeaders(CaseInsensitiveMap headers) {
        String result = "";
        TreeMap amzHeaders = headers.removeSub("x-amz-");

        Iterator iterator = amzHeaders.keySet().iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            String trimmedKey = key.toString().trim();
            String value = (String) amzHeaders.get(key);
            String trimmedValue = value.trim();
            result += trimmedKey + ":" + trimmedValue + "\n";
        }
        return result;
    }
}