1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.BufferedInputStream;
25import java.io.BufferedOutputStream;
26import java.io.File;
27import java.io.IOException;
28import java.security.NoSuchAlgorithmException;
29import java.security.PrivilegedActionException;
30import java.security.PrivilegedExceptionAction;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.List;
34import java.util.Map;
35import javax.net.ssl.SNIHostName;
36import javax.net.ssl.SNIMatcher;
37import javax.net.ssl.SNIServerName;
38import javax.net.ssl.SSLContext;
39import javax.net.ssl.SSLParameters;
40import javax.net.ssl.SSLServerSocket;
41import javax.net.ssl.SSLServerSocketFactory;
42import javax.net.ssl.SSLSocket;
43import javax.net.ssl.SSLSocketFactory;
44import javax.security.auth.Subject;
45import javax.security.auth.login.LoginContext;
46import javax.security.auth.login.LoginException;
47
48/*
49 * Helper class for unbound krb5 tests.
50 */
51class UnboundSSLUtils {
52
53    static final String KTAB_FILENAME = "krb5.keytab.data";
54    static final String HOST = "localhost";
55    static final String REALM = "TEST.REALM";
56    static final String KRBTGT_PRINCIPAL = "krbtgt/" + REALM;
57    static final String TEST_SRC = System.getProperty("test.src", ".");
58    static final String TLS_KRB5_FILTER = "TLS_KRB5";
59    static final String USER = "USER";
60    static final String USER_PASSWORD = "password";
61    static final String FS = System.getProperty("file.separator");
62    static final String SNI_PATTERN = ".*";
63    static final String USER_PRINCIPAL = USER + "@" + REALM;
64    static final String KRB5_CONF_FILENAME = "krb5.conf";
65    static final int DELAY = 1000;
66
67   static String[] filterStringArray(String[] src, String filter) {
68        return Arrays.stream(src).filter((item) -> item.startsWith(filter))
69                .toArray(size -> new String[size]);
70    }
71
72    /*
73     * The method does JAAS login,
74     * and runs an SSL server in the JAAS context.
75     */
76    static void startServerWithJaas(final SSLEchoServer server,
77            String config) throws LoginException, PrivilegedActionException {
78        LoginContext context = new LoginContext(config);
79        context.login();
80        System.out.println("Server: successful authentication");
81        Subject.doAs(context.getSubject(),
82                (PrivilegedExceptionAction<Object>) () -> {
83            SSLEchoServer.startServer(server);
84            return null;
85        });
86    }
87
88}
89
90class SSLClient {
91
92    private final static byte[][] arrays = {
93        new byte[] {-1, 0, 2},
94        new byte[] {}
95    };
96
97    private final SSLSocket socket;
98
99    private SSLClient(SSLSocket socket) {
100        this.socket = socket;
101    }
102
103    void connect() throws IOException {
104        System.out.println("Client: connect to server");
105        try (BufferedInputStream bis = new BufferedInputStream(
106                        socket.getInputStream());
107                BufferedOutputStream bos = new BufferedOutputStream(
108                        socket.getOutputStream())) {
109
110            for (byte[] bytes : arrays) {
111                System.out.println("Client: send byte array: "
112                        + Arrays.toString(bytes));
113
114                bos.write(bytes);
115                bos.flush();
116
117                byte[] recieved = new byte[bytes.length];
118                int read = bis.read(recieved, 0, bytes.length);
119                if (read < 0) {
120                    throw new IOException("Client: couldn't read a response");
121                }
122
123                System.out.println("Client: recieved byte array: "
124                        + Arrays.toString(recieved));
125
126                if (!Arrays.equals(bytes, recieved)) {
127                    throw new IOException("Client: sent byte array "
128                                + "is not equal with recieved byte array");
129                }
130            }
131            socket.getSession().invalidate();
132        } finally {
133            if (!socket.isClosed()) {
134                socket.close();
135            }
136        }
137    }
138
139    static SSLClient init(String host, int port, String cipherSuiteFilter,
140            String sniHostName) throws NoSuchAlgorithmException, IOException {
141        SSLContext sslContext = SSLContext.getDefault();
142        SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
143        SSLSocket socket = (SSLSocket) ssf.createSocket(host, port);
144        SSLParameters params = new SSLParameters();
145
146        if (cipherSuiteFilter != null) {
147            String[] cipherSuites = UnboundSSLUtils.filterStringArray(
148                    ssf.getSupportedCipherSuites(), cipherSuiteFilter);
149            System.out.println("Client: enabled cipher suites: "
150                    + Arrays.toString(cipherSuites));
151            params.setCipherSuites(cipherSuites);
152        }
153
154        if (sniHostName != null) {
155            System.out.println("Client: set SNI hostname: " + sniHostName);
156            SNIHostName serverName = new SNIHostName(sniHostName);
157            List<SNIServerName> serverNames = new ArrayList<>();
158            serverNames.add(serverName);
159            params.setServerNames(serverNames);
160        }
161
162        socket.setSSLParameters(params);
163
164        return new SSLClient(socket);
165    }
166
167}
168
169class SSLEchoServer implements Runnable, AutoCloseable {
170
171    private final SSLServerSocket ssocket;
172    private volatile boolean stopped = false;
173    private volatile boolean ready = false;
174
175    /*
176     * Starts the server in a separate thread.
177     */
178    static void startServer(SSLEchoServer server) {
179        Thread serverThread = new Thread(server, "SSL echo server thread");
180        serverThread.setDaemon(true);
181        serverThread.start();
182    }
183
184    private SSLEchoServer(SSLServerSocket ssocket) {
185        this.ssocket = ssocket;
186    }
187
188    /*
189     * Main server loop.
190     */
191    @Override
192    public void run() {
193        System.out.println("Server: started");
194        while (!stopped) {
195            ready = true;
196            try (SSLSocket socket = (SSLSocket) ssocket.accept()) {
197                System.out.println("Server: client connection accepted");
198                try (
199                    BufferedInputStream bis = new BufferedInputStream(
200                            socket.getInputStream());
201                    BufferedOutputStream bos = new BufferedOutputStream(
202                            socket.getOutputStream())
203                ) {
204                    byte[] buffer = new byte[1024];
205                    int read;
206                    while ((read = bis.read(buffer)) > 0) {
207                        bos.write(buffer, 0, read);
208                        System.out.println("Server: recieved " + read
209                                + " bytes: "
210                                + Arrays.toString(Arrays.copyOf(buffer, read)));
211                        bos.flush();
212                    }
213                }
214            } catch (IOException e) {
215                if (stopped) {
216                    // stopped == true means that stop() method was called,
217                    // so just ignore the exception, and finish the loop
218                    break;
219                }
220                System.out.println("Server: couldn't accept client connection: "
221                        + e);
222            }
223        }
224        System.out.println("Server: finished");
225    }
226
227    boolean isReady() {
228        return ready;
229    }
230
231    void stop() {
232        stopped = true;
233        ready = false;
234
235        // close the server socket to interupt accept() method
236        try {
237            if (!ssocket.isClosed()) {
238                ssocket.close();
239            }
240        } catch (IOException e) {
241            throw new RuntimeException("Unexpected exception: " + e);
242        }
243    }
244
245    @Override
246    public void close() {
247        stop();
248    }
249
250    int getPort() {
251        return ssocket.getLocalPort();
252    }
253
254    /*
255     * Creates server instance.
256     *
257     * @param cipherSuiteFilter Filter for enabled cipher suites
258     * @param sniMatcherPattern Pattern for SNI server hame
259     */
260    static SSLEchoServer init(String cipherSuiteFilter,
261            String sniPattern) throws NoSuchAlgorithmException, IOException {
262        SSLContext context = SSLContext.getDefault();
263        SSLServerSocketFactory ssf =
264                (SSLServerSocketFactory) context.getServerSocketFactory();
265        SSLServerSocket ssocket =
266                (SSLServerSocket) ssf.createServerSocket(0);
267
268        // specify enabled cipher suites
269        if (cipherSuiteFilter != null) {
270            String[] ciphersuites = UnboundSSLUtils.filterStringArray(
271                    ssf.getSupportedCipherSuites(), cipherSuiteFilter);
272            System.out.println("Server: enabled cipher suites: "
273                    + Arrays.toString(ciphersuites));
274            ssocket.setEnabledCipherSuites(ciphersuites);
275        }
276
277        // specify SNI matcher pattern
278        if (sniPattern != null) {
279            System.out.println("Server: set SNI matcher: " + sniPattern);
280            SNIMatcher matcher = SNIHostName.createSNIMatcher(sniPattern);
281            List<SNIMatcher> matchers = new ArrayList<>();
282            matchers.add(matcher);
283            SSLParameters params = ssocket.getSSLParameters();
284            params.setSNIMatchers(matchers);
285            ssocket.setSSLParameters(params);
286        }
287
288        return new SSLEchoServer(ssocket);
289    }
290
291}
292
293