/*
* Licensed 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 io.airlift.http.server;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
import io.airlift.event.client.NullEventClient;
import io.airlift.node.NodeInfo;
import io.airlift.testing.FileUtils;
import io.airlift.tracetoken.TraceTokenManager;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.servlet.Filter;
import javax.servlet.http.HttpServlet;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import static com.google.common.io.Resources.getResource;
@Test(singleThreaded = true)
public class TestHttpServerCipher
{
private static final String KEY_STORE_PATH = constructKeyStorePath();
private static final String KEY_STORE_PASSWORD = "airlift";
public static final String CIPHER_1 = "TLS_RSA_WITH_AES_128_CBC_SHA256";
public static final String CIPHER_2 = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256";
public static final String CIPHER_3 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
private File tempDir;
private static String constructKeyStorePath()
{
try {
return new File(getResource("test.keystore").toURI()).getAbsolutePath();
}
catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@BeforeMethod
public void setup()
throws IOException
{
tempDir = Files.createTempDir();
}
@AfterMethod(alwaysRun = true)
public void teardown()
throws Exception
{
FileUtils.deleteRecursively(tempDir);
}
@Test
public void testIncludeCipherEmpty()
throws Exception
{
HttpServerConfig config = createHttpServerConfig()
.setHttpsIncludedCipherSuites(" , ");
NodeInfo nodeInfo = new NodeInfo("test");
HttpServerInfo httpServerInfo = new HttpServerInfo(config, nodeInfo);
HttpServer server = createServer(nodeInfo, httpServerInfo, config);
try {
server.start();
URI httpsUri = httpServerInfo.getHttpsUri();
HttpClient httpClient = createClientIncludeCiphers(CIPHER_1);
httpClient.GET(httpsUri);
httpClient = createClientIncludeCiphers(CIPHER_2);
httpClient.GET(httpsUri);
httpClient = createClientIncludeCiphers(CIPHER_3);
httpClient.GET(httpsUri);
}
finally {
server.stop();
}
}
@Test
public void testIncludedCipher()
throws Exception
{
HttpServerConfig config = createHttpServerConfig()
.setHttpsIncludedCipherSuites(CIPHER_1 + "," + CIPHER_2);
NodeInfo nodeInfo = new NodeInfo("test");
HttpServerInfo httpServerInfo = new HttpServerInfo(config, nodeInfo);
HttpServer server = createServer(nodeInfo, httpServerInfo, config);
try {
server.start();
URI httpsUri = httpServerInfo.getHttpsUri();
// should succeed because only one of the two allowed certificate is excluded
HttpClient httpClient = createClientIncludeCiphers(CIPHER_1);
httpClient.GET(httpsUri);
// should succeed because only one of the two allowed certificate is excluded
httpClient = createClientIncludeCiphers(CIPHER_2);
httpClient.GET(httpsUri);
httpClient = createClientIncludeCiphers(CIPHER_3);
try {
httpClient.GET(httpsUri);
Assert.fail("SSL handshake should fail because client included only ciphers the server didn't include");
}
catch (ExecutionException e) {
// expected
}
}
finally {
server.stop();
}
}
@Test
public void testExcludedCipher()
throws Exception
{
HttpServerConfig config = createHttpServerConfig()
.setHttpsExcludedCipherSuites(CIPHER_1 + "," + CIPHER_2);
NodeInfo nodeInfo = new NodeInfo("test");
HttpServerInfo httpServerInfo = new HttpServerInfo(config, nodeInfo);
HttpServer server = createServer(nodeInfo, httpServerInfo, config);
try {
server.start();
URI httpsUri = httpServerInfo.getHttpsUri();
// should succeed because all ciphers accepted
HttpClient httpClient = createClientIncludeCiphers();
httpClient.GET(httpsUri);
httpClient = createClientIncludeCiphers(CIPHER_1, CIPHER_2);
try {
httpClient.GET(httpsUri);
Assert.fail("SSL handshake should fail because client included only ciphers the server excluded");
}
catch (ExecutionException e) {
// expected
}
}
finally {
server.stop();
}
}
private HttpServerConfig createHttpServerConfig()
{
return new HttpServerConfig()
.setHttpEnabled(false)
.setHttpsEnabled(true)
.setHttpsPort(0)
.setKeystorePath(KEY_STORE_PATH)
.setKeystorePassword(KEY_STORE_PASSWORD)
.setLogPath(new File(tempDir, "http-request.log").getAbsolutePath());
}
private static HttpClient createClientIncludeCiphers(String... includedCipherSuites)
throws Exception
{
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setIncludeCipherSuites(includedCipherSuites);
sslContextFactory.setKeyStorePath(KEY_STORE_PATH);
sslContextFactory.setKeyStorePassword(KEY_STORE_PASSWORD);
HttpClient httpClient = new HttpClient(sslContextFactory);
httpClient.start();
return httpClient;
}
private static HttpServer createServer(NodeInfo nodeInfo, HttpServerInfo httpServerInfo, HttpServerConfig config)
{
return createServer(new DummyServlet(), nodeInfo, httpServerInfo, config);
}
private static HttpServer createServer(HttpServlet servlet, NodeInfo nodeInfo, HttpServerInfo httpServerInfo, HttpServerConfig config)
{
HashLoginServiceProvider loginServiceProvider = new HashLoginServiceProvider(config);
HttpServerProvider serverProvider = new HttpServerProvider(
httpServerInfo,
nodeInfo,
config,
servlet,
ImmutableSet.<Filter>of(new DummyFilter()),
ImmutableSet.<HttpServerBinder.HttpResourceBinding>of(),
ImmutableSet.<Filter>of(),
new RequestStats(),
new NullEventClient());
serverProvider.setTheAdminServlet(new DummyServlet());
serverProvider.setLoginService(loginServiceProvider.get());
serverProvider.setTokenManager(new TraceTokenManager());
return serverProvider.get();
}
}