/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.catalina.startup;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.cert.X509Certificate;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.buf.ByteChunk;

/**
 * Requires test.keystore (checked in), generated with:
 *  keytool -genkey -alias tomcat -keyalg RSA
 *  pass: changeit 
 *  CN: localhost ( for hostname validation )
 */
public class TestTomcatSSL extends TomcatBaseTest {
    static TrustManager[] trustAllCerts = new TrustManager[] { 
        new X509TrustManager() { 
            public X509Certificate[] getAcceptedIssuers() { 
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs,
                    String authType) {
                // NOOP - Trust everything
            }
            public void checkServerTrusted(X509Certificate[] certs,
                    String authType) {
                // NOOP - Trust everything
            }
        }
    };

    private void initSsl(Tomcat tomcat, boolean nio) {
        if (nio) {
            Connector connector = 
                new Connector("org.apache.coyote.http11.Http11NioProtocol");
            connector.setPort(getPort());
            tomcat.getService().addConnector(connector);
            tomcat.setConnector(connector);
            tomcat.getConnector().setSecure(true);            
        } else {
            tomcat.getConnector().setSecure(true);
        }
        tomcat.getConnector().setProperty("SSLEnabled", "true");
        tomcat.getConnector().setProperty("sslProtocol",
            "tls");
        // test runs in output/tmp
        tomcat.getConnector().setAttribute("keystoreFile", 
            "../../test/org/apache/catalina/startup/test.keystore");
    }
    
    public void testSimpleSsl() throws Exception {
        simpleSsl(false);
    }
    
    public void testSimpleSslNio() throws Exception {
        simpleSsl(true);
    }
    
    public void simpleSsl(boolean nio) throws Exception {
        // Install the all-trusting trust manager so https:// works 
        // with unsigned certs. 

        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(
                    sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        } 
        
        Tomcat tomcat = getTomcatInstance();

        File appDir = 
            new File("output/build/webapps/examples");
        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
        
        initSsl(tomcat, nio);

        tomcat.start();
        ByteChunk res = getUrl("https://localhost:" + getPort() +
            "/examples/servlets/servlet/HelloWorldExample");
        assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
    }

    boolean handshakeDone = false;
    
    public void testRenegotiateFail() throws Exception {
        renegotiateFail(false);
    }
    
    public void renegotiateFail(boolean nio) throws Exception {
        Tomcat tomcat = getTomcatInstance();

        File appDir = 
            new File("output/build/webapps/examples");
        // app dir is relative to server home
        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());

        initSsl(tomcat, nio);
        // Default - MITM not enabled

        tomcat.start();
        SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(null, trustAllCerts, new java.security.SecureRandom());
        SSLSocketFactory socketFactory = sslCtx.getSocketFactory();
        SSLSocket socket = (SSLSocket) socketFactory.createSocket("localhost", getPort());

        socket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
            @Override
            public void handshakeCompleted(HandshakeCompletedEvent event) {
                handshakeDone = true;
            }
        });
        
        OutputStream os = socket.getOutputStream();
        os.write("GET /examples/servlets/servlet/HelloWorldExample HTTP/1.0\n".getBytes());
        os.flush();

        InputStream is = socket.getInputStream();

        socket.startHandshake();
        handshakeDone = false;
        byte[] b = new byte[0];
        int maxTries = 5;  // 5 sec should be enough - in NIO we'll timeout
        socket.setSoTimeout(1000);
        for (int i = 0; i < maxTries; i++) {
            try {
                is.read(b);
            } catch (IOException e) {
                // timeout
            }
            if (handshakeDone) {
                break;
            }
        }
        os = socket.getOutputStream();
        if (!handshakeDone) {
            // success - we timedout without handshake
            return;
        }
        try {
            os.write("Host: localhost\n\n".getBytes());
        } catch (IOException ex) {
            // success - connection closed
            return;
        }
        
        fail("Re-negotiation worked");
        
    }
    
    public void testRenegotiateWorks() throws Exception {
        renegotiateWorks(false);
    }
    
    
    // Re-negotiation not implemented in NIO
    //    public void testRenegotiateWorksNio() throws Exception {
    //        renegotiateWorks(true);    
    //    }

    public void testRenegotiateFailNio() throws Exception {
        renegotiateFail(true);        
    }
    
    
    public void renegotiateWorks(boolean nio) throws Exception {
        Tomcat tomcat = getTomcatInstance();

        File appDir = 
            new File("output/build/webapps/examples");
        // app dir is relative to server home
        tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());

        initSsl(tomcat, nio);
        // Enable MITM attack
        tomcat.getConnector().setAttribute("allowUnsafeLegacyRenegotiation", "true");

        tomcat.start();
        SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(null, trustAllCerts, new java.security.SecureRandom());
        SSLSocketFactory socketFactory = sslCtx.getSocketFactory();
        SSLSocket socket = (SSLSocket) socketFactory.createSocket("localhost", getPort());

        socket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
            @Override
            public void handshakeCompleted(HandshakeCompletedEvent event) {
                handshakeDone = true;
            }
        });
        
        OutputStream os = socket.getOutputStream();
        os.write("GET /examples/servlets/servlet/HelloWorldExample HTTP/1.0\n".getBytes());
        os.flush();

        InputStream is = socket.getInputStream();

        socket.startHandshake();
        handshakeDone = false;
        byte[] b = new byte[0];
        int maxTries = 5; 
        socket.setSoTimeout(1000);
        for (int i = 0; i < maxTries; i++) {
            try {
                is.read(b);
            } catch (IOException e) {
                // timeout
            }
            if (handshakeDone) {
                break;
            }
        }
        os = socket.getOutputStream();
        
        try {
            os.write("Host: localhost\n\n".getBytes());
        } catch (IOException ex) {
            fail("Re-negotiation failed");
        }
        
    }
}

@@ -5315,32 +5315,41 @@
    os.flush();%0A%0A
+        %0A
         InputStr
@@ -5375,32 +5375,140 @@
 InputStream();%0A%0A
+        // Make sure the NIO connector has read the request before the handshake%0A        Thread.sleep(100);%0A
         socket.s

Fix intermittent test failures