/* * Copyright 2017 ThoughtWorks, Inc. * * 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 com.thoughtworks.go.server.util; import org.apache.commons.io.IOUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.X509V1CertificateGenerator; import org.eclipse.jetty.server.*; import org.eclipse.jetty.server.session.HashSessionManager; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.webapp.WebAppContext; import javax.security.auth.x500.X500Principal; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.math.BigInteger; import java.security.*; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.Date; import java.util.GregorianCalendar; import static com.thoughtworks.go.util.TestFileUtil.createTempFile; /** * @understands test http server that is used to test http client code end-to-end * * Flicked from https://github.com/test-load-balancer/tlb (pre http-components) e19d4911b089eeaf1a2c */ public class HttpTestUtil { private static final String STORE_PASSWORD = "tlb"; private Server server; private Thread blocker; private File serverKeyStore; private static final int MAX_IDLE_TIME = 30000; private static final int RESPONSE_BUFFER_SIZE = 32768; public static class EchoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { handleRequest(request, resp); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { handleRequest(request, resp); } @Override protected void doPut(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { handleRequest(request, resp); } private void handleRequest(HttpServletRequest request, HttpServletResponse resp) throws IOException { PrintWriter writer = resp.getWriter(); Request req = (Request) request; writer.write(req.getScheme()); writer.write("://"); writer.write(req.getLocalName()); writer.write(":"); writer.write(String.valueOf(req.getLocalPort())); writer.write(req.getContextPath()); writer.write(req.getPathInfo()); String query = req.getQueryString(); if (query != null) { writer.write("?" + query); } writer.close(); } } public static interface ContextCustomizer { void customize(WebAppContext ctx) throws Exception; } public HttpTestUtil(final ContextCustomizer customizer) throws Exception { Security.addProvider(new BouncyCastleProvider()); serverKeyStore = createTempFile("server.jks"); prepareCertStore(serverKeyStore); server = new Server(); WebAppContext ctx = new WebAppContext(); SessionManager sm = new HashSessionManager(); SessionHandler sh = new SessionHandler(sm); ctx.setSessionHandler(sh); customizer.customize(ctx); ctx.setContextPath("/go"); server.setHandler(ctx); } public void httpConnector(final int port) { ServerConnector connector = connectorWithPort(port); server.addConnector(connector); } public void httpConnector(final int port, final String host) { ServerConnector connector = connectorWithPort(port); connector.setHost(host); server.addConnector(connector); } private ServerConnector connectorWithPort(int port) { ServerConnector http = new ServerConnector(server); http.setPort(port); return http; } public void httpsConnector(final int port) { HttpConfiguration httpsConfig = new HttpConfiguration(); httpsConfig.setOutputBufferSize(RESPONSE_BUFFER_SIZE); // 32 MB httpsConfig.addCustomizer(new SecureRequestCustomizer()); SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setKeyStorePath(serverKeyStore.getAbsolutePath()); sslContextFactory.setKeyStorePassword(STORE_PASSWORD); sslContextFactory.setKeyManagerPassword(STORE_PASSWORD); sslContextFactory.setWantClientAuth(true); ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig)); // https.setHost(host); https.setPort(port); https.setIdleTimeout(MAX_IDLE_TIME); server.addConnector(https); } public synchronized void start() throws InterruptedException { if (blocker != null) throw new IllegalStateException("Aborting server start, it seems server is already running."); blocker = new Thread(new Runnable() { public void run() { try { server.start(); } catch (Exception e) { throw new RuntimeException(e); } try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { //ignore } } }); blocker.start(); while (!server.isStarted()) { Thread.sleep(50); } } public synchronized void stop() { if (blocker == null) throw new IllegalStateException("Aborting server stop, it seems there is no server running."); try { server.stop(); blocker.interrupt(); blocker.join(); blocker = null; } catch (Exception e) { throw new RuntimeException(e); } } private void prepareCertStore(File serverKeyStore) { KeyPair keyPair = generateKeyPair(); X509Certificate cert = generateCert(keyPair); FileOutputStream os = null; try { KeyStore store = KeyStore.getInstance("JKS"); store.load(null, null); store.setKeyEntry("test", keyPair.getPrivate(), STORE_PASSWORD.toCharArray(), new Certificate[]{cert}); os = new FileOutputStream(serverKeyStore); store.store(os, STORE_PASSWORD.toCharArray()); } catch (Exception e) { throw new RuntimeException(e); } finally { if (os != null) { IOUtils.closeQuietly(os); } } } private X509Certificate generateCert(final KeyPair keyPair) { Date startDate = day(-1); Date expiryDate = day(+1); BigInteger serialNumber = new BigInteger("1000200030004000"); X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); X500Principal dnName = new X500Principal("CN=Test CA Certificate"); certGen.setSerialNumber(serialNumber); certGen.setIssuerDN(dnName); certGen.setNotBefore(startDate); certGen.setNotAfter(expiryDate); certGen.setSubjectDN(dnName); // note: same as issuer certGen.setPublicKey(keyPair.getPublic()); certGen.setSignatureAlgorithm("SHA1WITHRSA"); try { return certGen.generate(keyPair.getPrivate()); } catch (Exception e) { throw new RuntimeException(e); } } private Date day(final int offset) { GregorianCalendar gregorianCalendar = new GregorianCalendar(); gregorianCalendar.add(GregorianCalendar.DAY_OF_MONTH, offset); return gregorianCalendar.getTime(); } private KeyPair generateKeyPair() { try { KeyPair seed = KeyPairGenerator.getInstance("RSA", "BC").generateKeyPair(); RSAPrivateKey privateSeed = (RSAPrivateKey) seed.getPrivate(); RSAPublicKey publicSeed = (RSAPublicKey) seed.getPublic(); KeyFactory fact = KeyFactory.getInstance("RSA", "BC"); RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(privateSeed.getModulus(), privateSeed.getPrivateExponent()); RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(publicSeed.getModulus(), publicSeed.getPublicExponent()); return new KeyPair(fact.generatePublic(publicKeySpec), fact.generatePrivate(privateKeySpec)); } catch (Exception e) { throw new RuntimeException(e); } } }