001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */package org.apache.mina.filter.ssl;
020
021import java.io.BufferedReader;
022import java.io.IOException;
023import java.io.InputStreamReader;
024import java.net.InetAddress;
025import java.net.InetSocketAddress;
026import java.net.Socket;
027import java.security.GeneralSecurityException;
028import java.security.KeyStore;
029import java.security.Security;
030
031import javax.net.ssl.KeyManagerFactory;
032import javax.net.ssl.SSLContext;
033import javax.net.ssl.SSLSocketFactory;
034import javax.net.ssl.TrustManagerFactory;
035
036import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
037import org.apache.mina.core.service.IoHandlerAdapter;
038import org.apache.mina.core.session.IoSession;
039import org.apache.mina.filter.codec.ProtocolCodecFilter;
040import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
041import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
042import org.apache.mina.util.AvailablePortFinder;
043import org.junit.Test;
044
045/**
046 * Test a SSL session where the connection is established and closed twice. It should be
047 * processed correctly (Test for DIRMINA-650)
048 *
049 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
050 */
051public class SslTest {
052    /** A static port used for his test, chosen to avoid collisions */
053    private static final int port = AvailablePortFinder.getNextAvailable(5555);
054
055    private static Exception clientError = null;
056
057    private static InetAddress address;
058
059    private static SSLSocketFactory factory;
060
061    /** A JVM independant KEY_MANAGER_FACTORY algorithm */
062    private static final String KEY_MANAGER_FACTORY_ALGORITHM;
063
064    static {
065        String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
066        if (algorithm == null) {
067            algorithm = KeyManagerFactory.getDefaultAlgorithm();
068        }
069
070        KEY_MANAGER_FACTORY_ALGORITHM = algorithm;
071    }
072
073    private static class TestHandler extends IoHandlerAdapter {
074        public void messageReceived(IoSession session, Object message) throws Exception {
075            String line = (String) message;
076
077            if (line.startsWith("hello")) {
078                //System.out.println("Server got: 'hello', waiting for 'send'");
079                Thread.sleep(1500);
080            } else if (line.startsWith("send")) {
081                //System.out.println("Server got: 'send', sending 'data'");
082                StringBuilder sb = new StringBuilder();
083                
084                for ( int i = 0; i < 10000; i++) {
085                    sb.append('A');
086                }
087                    
088                session.write(sb.toString());
089                session.close(true);
090            }
091        }
092    }
093
094    /**
095     * Starts a Server with the SSL Filter and a simple text line 
096     * protocol codec filter
097     */
098    private static void startServer() throws Exception {
099        NioSocketAcceptor acceptor = new NioSocketAcceptor();
100
101        acceptor.setReuseAddress(true);
102        DefaultIoFilterChainBuilder filters = acceptor.getFilterChain();
103
104        // Inject the SSL filter
105        SslFilter sslFilter = new SslFilter(createSSLContext());
106        filters.addLast("sslFilter", sslFilter);
107
108        // Inject the TestLine codec filter
109        filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory()));
110
111        acceptor.setHandler(new TestHandler());
112        acceptor.bind(new InetSocketAddress(port));
113    }
114
115    /**
116     * Starts a client which will connect twice using SSL
117     */
118    private static void startClient() throws Exception {
119        address = InetAddress.getByName("localhost");
120
121        SSLContext context = createSSLContext();
122        factory = context.getSocketFactory();
123
124        connectAndSend();
125
126        // This one will throw a SocketTimeoutException if DIRMINA-650 is not fixed
127        connectAndSend();
128    }
129
130    private static void connectAndSend() throws Exception {
131        Socket parent = new Socket(address, port);
132        Socket socket = factory.createSocket(parent, address.getCanonicalHostName(), port, false);
133
134        //System.out.println("Client sending: hello");
135        socket.getOutputStream().write("hello                      \n".getBytes());
136        socket.getOutputStream().flush();
137        socket.setSoTimeout(1000000);
138
139        //System.out.println("Client sending: send");
140        socket.getOutputStream().write("send\n".getBytes());
141        socket.getOutputStream().flush();
142
143        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
144        String line = in.readLine();
145        //System.out.println("Client got: " + line);
146        socket.close();
147
148    }
149
150    private static SSLContext createSSLContext() throws IOException, GeneralSecurityException {
151        char[] passphrase = "password".toCharArray();
152
153        SSLContext ctx = SSLContext.getInstance("TLS");
154        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
155        TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
156
157        KeyStore ks = KeyStore.getInstance("JKS");
158        KeyStore ts = KeyStore.getInstance("JKS");
159
160        ks.load(SslTest.class.getResourceAsStream("keystore.sslTest"), passphrase);
161        ts.load(SslTest.class.getResourceAsStream("truststore.sslTest"), passphrase);
162
163        kmf.init(ks, passphrase);
164        tmf.init(ts);
165        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
166
167        return ctx;
168    }
169
170    @Test
171    public void testSSL() throws Exception {
172        startServer();
173
174        Thread t = new Thread() {
175            public void run() {
176                try {
177                    startClient();
178                } catch (Exception e) {
179                    clientError = e;
180                }
181            }
182        };
183        t.start();
184        t.join();
185        if (clientError != null)
186            throw clientError;
187    }
188}