org.ejbca.ui.web.pub.HttpMethodsTest.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.ui.web.pub.HttpMethodsTest.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: The OpenSource Certificate Authority                *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/

package org.ejbca.ui.web.pub;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;

import org.apache.commons.fileupload.util.Streams;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.X509KeyUsage;
import org.cesecore.CaTestUtils;
import org.cesecore.SystemTestsConfiguration;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authentication.tokens.UsernamePrincipal;
import org.cesecore.certificates.ca.CA;
import org.cesecore.certificates.ca.CaSessionRemote;
import org.cesecore.configuration.GlobalConfigurationSessionRemote;
import org.cesecore.keys.token.CryptoTokenTestUtils;
import org.cesecore.mock.authentication.tokens.TestAlwaysAllowLocalAuthenticationToken;
import org.cesecore.util.EjbRemoteHelper;
import org.ejbca.config.ScepConfiguration;
import org.ejbca.config.WebConfiguration;
import org.ejbca.core.ejb.config.ConfigurationSessionRemote;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Try HTTP methods that should be disabled, like HTTP DELETE, for all public
 * web modules.
 * 
 * @version $Id: HttpMethodsTest.java 19974 2014-10-09 15:19:06Z mikekushner $
 */
public class HttpMethodsTest {

    final private static Logger log = Logger.getLogger(WebdistHttpTest.class);

    private static final AuthenticationToken admin = new TestAlwaysAllowLocalAuthenticationToken(
            new UsernamePrincipal("HttpMethodsTest"));

    private String httpBaseUrl;
    private String httpPort;
    private String httpHost;
    private CA testx509ca;

    private ConfigurationSessionRemote configurationSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(ConfigurationSessionRemote.class, EjbRemoteHelper.MODULE_TEST);
    private CaSessionRemote caSession = EjbRemoteHelper.INSTANCE.getRemoteSession(CaSessionRemote.class);
    private GlobalConfigurationSessionRemote globalConfigSession = EjbRemoteHelper.INSTANCE
            .getRemoteSession(GlobalConfigurationSessionRemote.class);

    @Before
    public void setUp() throws Exception {
        httpHost = SystemTestsConfiguration.getRemoteHost("127.0.0.1");
        httpPort = SystemTestsConfiguration
                .getRemotePortHttp(configurationSession.getProperty(WebConfiguration.CONFIG_HTTPSERVERPUBHTTP));
        httpBaseUrl = "http://" + httpHost + ":" + httpPort;

        int keyusage = X509KeyUsage.digitalSignature + X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign;
        testx509ca = CaTestUtils.createTestX509CA("CN=TestCA", null, false, keyusage);
        caSession.addCA(admin, testx509ca);
    }

    @After
    public void tearDown() throws Exception {
        CryptoTokenTestUtils.removeCryptoToken(null, testx509ca.getCAToken().getCryptoTokenId());
        caSession.removeCA(admin, testx509ca.getCAId());
    }

    /** Test the doc.war module. */
    @Test
    public void testDocs() throws Exception {
        performResourceTest("/ejbca/doc/index.html");
    }

    /** Test the publicweb.war module. */
    @Test
    public void testPublicWeb() throws Exception {
        performResourceTest("/ejbca/index.jsp");
    }

    /** Test the publicweb.war module. */
    @Test
    public void testPublicWebSecurityHeaders() throws Exception {
        // Check for X-FRAME-OPTIONS headers
        HttpURLConnection con = getHttpURLConnection(httpBaseUrl + "/ejbca/index.jsp");
        String xframe = con.getHeaderField("X-FRAME-OPTIONS");
        String csp = con.getHeaderField("content-security-policy");
        String xcsp = con.getHeaderField("x-content-security-policy");
        con.disconnect();
        assertNotNull("Public web should return X-FRAME-OPTIONS header", xframe);
        assertNotNull("Public web page should return content-security-policy header", csp);
        assertNotNull("Public web page should return x-content-security-policy header", xcsp);
        assertEquals("Public web should return X-FRAME-OPTIONS DENY", "DENY", xframe);
        assertEquals(
                "Public web page should return csp default-src 'none'; object-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self'; frame-src 'self'; form-action 'self'; reflected-xss block",
                "default-src 'none'; object-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self'; frame-src 'self'; form-action 'self'; reflected-xss block",
                csp);
        assertEquals(
                "Public web page should return xcsp default-src 'none'; object-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self'; frame-src 'self'; form-action 'self'; reflected-xss block",
                "default-src 'none'; object-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self'; frame-src 'self'; form-action 'self'; reflected-xss block",
                xcsp);
    }

    /** Test the adminweb.war module that it returns X-FRAME-OPTIONS on the error page. */
    @Test
    public void testAdminWebXFrameOptionsOnError() throws Exception {
        // Check for X-FRAME-OPTIONS headers
        // We will not be able to actually read this url, because we use port 8080, and adminweb requires client authentication,
        // But EJBCA will still return a "blank" page with the correct http header.
        HttpURLConnection con = getHttpURLConnection(httpBaseUrl + "/ejbca/adminweb/index.jsp");
        String xframe = con.getHeaderField("X-FRAME-OPTIONS");
        String csp = con.getHeaderField("content-security-policy");
        String xcsp = con.getHeaderField("x-content-security-policy");
        con.disconnect();
        assertNotNull("Admin web error page should return X-FRAME-OPTIONS header", xframe);
        assertNotNull("Admin web error page should return content-security-policy header", csp);
        assertNotNull("Admin web error page should return x-content-security-policy header", xcsp);
        assertEquals("Admin web error page should return X-FRAME-OPTIONS SAMEORIGIN", "SAMEORIGIN", xframe);
        assertEquals(
                "Admin web error page should return csp default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self'; frame-src 'self'; reflected-xss block",
                "default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self'; frame-src 'self'; reflected-xss block",
                csp);
        assertEquals(
                "Admin web error page should return x-csp default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self'; frame-src 'self'; reflected-xss block",
                "default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self'; frame-src 'self'; reflected-xss block",
                xcsp);
    }

    /** Test the webdist.war module. */
    @Test
    public void testWebDist() throws Exception {
        performResourceTest("/ejbca/publicweb/webdist/certdist?cmd=cacert&issuer=CN%3dTestCA&level=0");
    }

    /** Test the status.war module. */
    @Test
    public void testStatus() throws Exception {
        performResourceTest("/ejbca/publicweb/status/ocsp");
    }

    /** Test the scep.war module. */
    @Test
    public void testScep() throws Exception {
        ScepConfiguration scepConfig = (ScepConfiguration) globalConfigSession
                .getCachedConfiguration(ScepConfiguration.SCEP_CONFIGURATION_ID);
        boolean remove = false;
        if (!scepConfig.aliasExists("scep")) {
            scepConfig.addAlias("scep");
            globalConfigSession.saveConfiguration(admin, scepConfig);
            remove = true;
        }

        try {
            performResourceTest("/ejbca/publicweb/apply/scep/pkiclient.exe?operation=GetCACert&message=TestCA");
        } finally {
            if (remove) {
                scepConfig.removeAlias("scep");
                globalConfigSession.saveConfiguration(admin, scepConfig);
            }
        }
    }

    /** Test the healthcheck.war module. */
    @Test
    public void testHealthCheck() throws Exception {
        performResourceTest("/ejbca/publicweb/healthcheck/ejbcahealth");
    }

    /** Test the cmp.war module. */
    @Test
    public void testCmp() throws Exception {
        // Servlet only answers to POST
        String resourceName = "/ejbca/publicweb/cmp";
        log.info("Started tests of " + resourceName);
        assertFalse("HTTP OPTIONS is supported.", allowHttpOptions(resourceName, httpPort));
    }

    // TODO: Renew bundle

    /** Perform basic HTTP method tests on the specified resource */
    private void performResourceTest(String resourceName) throws Exception {
        log.info("Started tests of " + resourceName);
        assertEquals("HTTP GET is not supported. (This test expects " + httpBaseUrl + resourceName
                + " to exist and return 200).", 200, getUrl(httpBaseUrl + resourceName));
        assertFalse("HTTP DELETE is supported.", allowsDeleteHttpRequest(resourceName, httpPort));
        assertFalse("HTTP PUT is supported.", allowsPutHttpRequest(resourceName + ".2", httpPort));
        assertFalse("HTTP TRACE is supported.", allowsTraceHttpRequest(resourceName, httpPort));
        assertFalse("HTTP OPTIONS is supported.haha ", allowHttpOptions(resourceName, httpPort));
    }

    /** Try an HTTP OPTIONS and return true if it was successful. */
    private boolean allowHttpOptions(String resource, String httpPort) throws IOException {
        // Create the HTTP header
        String headers = "OPTIONS " + resource + " HTTP/1.1\r\n" + "Host: " + httpHost + "\r\n\r\n";
        // Create the socket.
        Socket socket = new Socket(InetAddress.getByName(httpHost), Integer.parseInt(httpPort));
        // Send data byte for byte.
        OutputStream os = socket.getOutputStream();
        os.write(headers.getBytes());
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String nextLine;
        boolean allowsHttpOptions = false;
        while ((nextLine = br.readLine()) != null) {
            log.info("OPTIONS response contains: " + nextLine);
            if (nextLine.startsWith("Allow:")) {
                allowsHttpOptions = true;
                break;
            } else if (nextLine.equals("")) {
                log.debug("Got a pure newline.. we only care about the headers to skipping the rest..");
                break;
            }
        }
        socket.close();
        return allowsHttpOptions;
    }

    /** Try to perform an HTTP DELETE. */
    private boolean allowsDeleteHttpRequest(String resource, String httpPort) throws IOException {
        String headers = "DELETE " + resource + " HTTP/1.1\r\n" + "Host: " + httpHost + "\r\n\r\n";
        Socket socket = new Socket(InetAddress.getByName(httpHost), Integer.parseInt(httpPort));
        OutputStream os = socket.getOutputStream();
        os.write(headers.getBytes());
        InputStream is = socket.getInputStream();
        byte[] b = new byte[4096];
        is.read(b);
        socket.close();
        log.info("DELETE response contains: " + new String(b));
        return getUrl(httpBaseUrl + resource) != 200;
    }

    /** Try to upload some XML content. */
    private boolean allowsPutHttpRequest(String resource, String httpPort) throws IOException {
        String xml = "<dummy/>";
        String headers = "PUT " + resource + " HTTP/1.1\r\n" + "Host: " + httpHost + "\r\n"
                + "Content-Type: text/xml\r\n" + "Content-Length: " + xml.length() + "\r\n" + "\r\n";
        Socket socket = new Socket(InetAddress.getByName(httpHost), Integer.parseInt(httpPort));
        OutputStream os = socket.getOutputStream();
        os.write((headers + xml).getBytes());
        InputStream is = socket.getInputStream();
        byte[] b = new byte[4096];
        is.read(b);
        socket.close();
        log.info("PUT response contains: " + new String(b));
        int responseCode = getUrl(httpBaseUrl + resource);
        return responseCode == 200;
    }

    /** Try to perform an HTTP TRACE. */
    private boolean allowsTraceHttpRequest(String resource, String httpPort) throws IOException {
        String headers = "TRACE " + resource + " HTTP/1.1\r\n" + "Host: " + httpHost + "\r\n" + "A: qwertyuiop\r\n"
                + "\r\n";
        Socket socket = new Socket(InetAddress.getByName(httpHost), Integer.parseInt(httpPort));
        OutputStream os = socket.getOutputStream();
        os.write(headers.getBytes());
        InputStream is = socket.getInputStream();
        byte[] b = new byte[4096];
        is.read(b);
        socket.close();
        log.info("TRACE response contains: " + new String(b));
        return new String(b).indexOf("qwertyuiop") != -1;
    }

    /** Returns a connected HttpURLConnection, that must be closed with con.diconnect() when you are done 
     * 
     * @param url The URL to connect to 
     * @return HttpURLConnection that you can use
     * @throws IOException In case the connection can not be opened
     */
    private HttpURLConnection getHttpURLConnection(String url) throws IOException {
        final HttpURLConnection con;
        URL u = new URL(url);
        con = (HttpURLConnection) u.openConnection();
        con.setRequestMethod("GET");
        con.getDoOutput();
        con.connect();
        return con;
    }

    /** Do a HTTP GET. */
    private int getUrl(String url) throws IOException {
        final HttpURLConnection con = getHttpURLConnection(url);
        int ret = con.getResponseCode();
        log.debug("HTTP response code: " + ret + ". Response message: " + con.getResponseMessage());
        if (ret == 200) {
            log.debug(Streams.asString(con.getInputStream()));
        }
        con.disconnect();
        return ret;
    }
}