/* * JBoss, Home of Professional Open Source. * Copyright 2014, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This 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 (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.manualmode.web.ssl; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALLOW_RESOURCE_SERVICE_RESTART; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PORT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROTOCOL; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE; import static org.jboss.as.test.integration.management.util.ModelUtil.createOpNode; import static org.jboss.as.test.integration.security.common.Utils.makeCallWithHttpClient; import static org.junit.Assert.assertEquals; import static org.jboss.as.test.shared.ServerReload.executeReloadAndWaitForCompletion; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.Locale; import javax.net.ssl.SSLHandshakeException; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.SystemUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.jboss.as.arquillian.api.ServerSetupTask; import org.jboss.as.arquillian.container.ManagementClient; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.test.integration.security.common.SSLTruststoreUtil; import org.jboss.as.test.integration.security.common.SecurityTestConstants; import org.jboss.as.test.integration.security.common.SecurityTraceLoggingServerSetupTask; import org.jboss.as.test.integration.security.common.Utils; import org.jboss.as.test.integration.security.common.servlets.PrincipalPrintingServlet; import org.jboss.as.test.integration.security.common.servlets.SimpleSecuredServlet; import org.jboss.as.test.integration.security.common.servlets.SimpleServlet; import org.jboss.as.test.shared.TestSuiteEnvironment; import org.jboss.dmr.ModelNode; import org.jboss.logging.Logger; import org.junit.Assume; /** * Abstract class which serve as a base for CertificateLoginModule tests. It is * used for setting up server/client keystores, https connector and contains * useful methods for testing two-way SSL connection. * * @author Filip Bogyai */ public abstract class AbstractCertificateLoginModuleTestCase { private static Logger LOGGER = Logger.getLogger(AbstractCertificateLoginModuleTestCase.class); protected static SecurityTraceLoggingServerSetupTask TRACE_SECURITY = new SecurityTraceLoggingServerSetupTask(); protected static final File WORK_DIR = new File("keystores-workdir"); protected static final File SERVER_KEYSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.SERVER_KEYSTORE); protected static final File SERVER_TRUSTSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.SERVER_TRUSTSTORE); protected static final File CLIENT_KEYSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.CLIENT_KEYSTORE); protected static final File CLIENT_TRUSTSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.CLIENT_TRUSTSTORE); protected static final File UNTRUSTED_KEYSTORE_FILE = new File(WORK_DIR, SecurityTestConstants.UNTRUSTED_KEYSTORE); protected static final String HTTPS_REALM = "https_realm"; protected static final String CONTAINER = "default-jbossas"; protected static final String SECURED_SERVLET_WITH_SESSION = SimpleSecuredServlet.SERVLET_PATH + "?" + SimpleSecuredServlet.CREATE_SESSION_PARAM + "=true"; private static final int HTTPS_PORT = 8444; /** * Testing access to HTTPS connector which have configured truststore with * trusted certificates. Client with trusted certificate is allowed to * access both secured/unsecured resource. Client with untrusted certificate * can only access unprotected resources. * * @throws Exception */ public void testLoginWithCertificate(String appName) throws Exception { Assume.assumeFalse(SystemUtils.IS_JAVA_1_6 && SystemUtils.JAVA_VENDOR.toUpperCase(Locale.ENGLISH).contains("IBM")); final URL printPrincipalUrl = getServletUrl(HTTPS_PORT, appName, PrincipalPrintingServlet.SERVLET_PATH); final URL securedUrl = getServletUrl(HTTPS_PORT, appName, SECURED_SERVLET_WITH_SESSION); final URL unsecuredUrl = getServletUrl(HTTPS_PORT, appName, SimpleServlet.SERVLET_PATH); final HttpClient httpClient = getHttpsClient(CLIENT_KEYSTORE_FILE); final HttpClient httpClientUntrusted = getHttpsClient(UNTRUSTED_KEYSTORE_FILE); try { makeCallWithHttpClient(printPrincipalUrl, httpClient, HttpServletResponse.SC_FORBIDDEN); String responseBody = makeCallWithHttpClient(securedUrl, httpClient, HttpServletResponse.SC_OK); assertEquals("Secured page was not reached", SimpleSecuredServlet.RESPONSE_BODY, responseBody); String principal = makeCallWithHttpClient(printPrincipalUrl, httpClient, HttpServletResponse.SC_OK); assertEquals("Unexpected principal", "cn=client", principal.toLowerCase()); responseBody = makeCallWithHttpClient(unsecuredUrl, httpClientUntrusted, HttpServletResponse.SC_OK); assertEquals("Secured page was not reached", SimpleServlet.RESPONSE_BODY, responseBody); try { makeCallWithHttpClient(securedUrl, httpClientUntrusted, HttpServletResponse.SC_FORBIDDEN); } catch (SSLHandshakeException e) { // OK } catch (java.net.SocketException se) { // OK - on windows usually fails with this one } } finally { httpClient.getConnectionManager().shutdown(); httpClientUntrusted.getConnectionManager().shutdown(); } } public URL getServletUrl(int connectorPort, String appName, String servletPath) throws MalformedURLException { return new URL("https", TestSuiteEnvironment.getServerAddress(), connectorPort, "/" + appName + servletPath); } /** * Requests given URL and checks if the returned HTTP status code is the * expected one. Returns HTTP response body */ public static String makeCall(URL url, HttpClient httpClient, int expectedStatusCode) throws ClientProtocolException, IOException, URISyntaxException { String httpResponseBody = null; HttpGet httpGet = new HttpGet(url.toURI()); HttpResponse response = httpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); LOGGER.trace("Request to: " + url + " responds: " + statusCode); assertEquals("Unexpected status code", expectedStatusCode, statusCode); HttpEntity entity = response.getEntity(); if (entity != null) { httpResponseBody = EntityUtils.toString(response.getEntity()); EntityUtils.consume(entity); } return httpResponseBody; } public static HttpClient getHttpsClient(File keystoreFile) { return SSLTruststoreUtil.getHttpClientWithSSL(keystoreFile, SecurityTestConstants.KEYSTORE_PASSWORD, CLIENT_TRUSTSTORE_FILE, SecurityTestConstants.KEYSTORE_PASSWORD); } static class HTTPSConnectorSetup implements ServerSetupTask { protected static final HTTPSConnectorSetup INSTANCE = new HTTPSConnectorSetup(); @Override public void setup(ManagementClient managementClient, String containerId) throws Exception { FileUtils.deleteDirectory(WORK_DIR); WORK_DIR.mkdirs(); Utils.createKeyMaterial(WORK_DIR); TRACE_SECURITY.setup(managementClient, null); final ModelControllerClient client = managementClient.getControllerClient(); // add new HTTPS_REALM with SSL ModelNode operation = createOpNode("core-service=management/security-realm=" + HTTPS_REALM, ModelDescriptionConstants.ADD); Utils.applyUpdate(operation, client); operation = createOpNode("core-service=management/security-realm=" + HTTPS_REALM + "/authentication=truststore", ModelDescriptionConstants.ADD); operation.get("keystore-path").set(SERVER_TRUSTSTORE_FILE.getAbsolutePath()); operation.get("keystore-password").set(SecurityTestConstants.KEYSTORE_PASSWORD); Utils.applyUpdate(operation, client); operation = createOpNode("core-service=management/security-realm=" + HTTPS_REALM + "/server-identity=ssl", ModelDescriptionConstants.ADD); operation.get(PROTOCOL).set("TLSv1"); operation.get("keystore-path").set(SERVER_KEYSTORE_FILE.getAbsolutePath()); operation.get("keystore-password").set(SecurityTestConstants.KEYSTORE_PASSWORD); Utils.applyUpdate(operation, client); executeReloadAndWaitForCompletion(client, 100000); operation = createOpNode("socket-binding-group=standard-sockets/socket-binding=https2" , ADD); operation.get(PORT).set(Integer.toString(HTTPS_PORT)); operation.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).set(false); operation.get(OPERATION_HEADERS, ALLOW_RESOURCE_SERVICE_RESTART).set(true); Utils.applyUpdate(operation, client); operation = createOpNode("subsystem=undertow/server=default-server/https-listener=https2", ModelDescriptionConstants.ADD); operation.get("socket-binding").set("https2"); operation.get("security-realm").set(HTTPS_REALM); Utils.applyUpdate(operation, client); } @Override public void tearDown(ManagementClient managementClient, String containerId) throws Exception { ModelNode operation = createOpNode("subsystem=undertow/server=default-server/https-listener=https2", ModelDescriptionConstants.REMOVE); operation.get(OPERATION_HEADERS, ALLOW_RESOURCE_SERVICE_RESTART).set(true); Utils.applyUpdate(operation, managementClient.getControllerClient()); operation = createOpNode("socket-binding-group=standard-sockets/socket-binding=https2", ModelDescriptionConstants.REMOVE); Utils.applyUpdate(operation, managementClient.getControllerClient()); operation = createOpNode("core-service=management/security-realm=" + HTTPS_REALM, ModelDescriptionConstants.REMOVE); Utils.applyUpdate(operation, managementClient.getControllerClient()); FileUtils.deleteDirectory(WORK_DIR); TRACE_SECURITY.tearDown(managementClient, null); } } }