TestSSLEnginePair.java revision 727df1258e3b8386afea4778626c9ab16ef467d6
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.javax.net.ssl;
18
19import java.io.IOException;
20import java.nio.ByteBuffer;
21import javax.net.ssl.SSLEngine;
22import javax.net.ssl.SSLEngineResult;
23import javax.net.ssl.SSLEngineResult.HandshakeStatus;
24import javax.net.ssl.SSLSession;
25import junit.framework.Assert;
26
27/**
28 * TestSSLEnginePair is a convenience class for other tests that want
29 * a pair of connected and handshaked client and server SSLEngines for
30 * testing.
31 */
32public final class TestSSLEnginePair extends Assert {
33    public final TestSSLContext c;
34    public final SSLEngine server;
35    public final SSLEngine client;
36
37    private TestSSLEnginePair(TestSSLContext c,
38                              SSLEngine server,
39                              SSLEngine client) {
40        this.c = c;
41        this.server = server;
42        this.client = client;
43    }
44
45    public static TestSSLEnginePair create(Hooks hooks) throws IOException {
46        return create(TestSSLContext.create(), hooks);
47    }
48
49    public static TestSSLEnginePair create(TestSSLContext c, Hooks hooks) throws IOException {
50        return create(c, hooks, null);
51    }
52
53    public static TestSSLEnginePair create(TestSSLContext c, Hooks hooks, boolean[] finished)
54            throws IOException {
55        SSLEngine[] engines = connect(c, hooks, finished);
56        return new TestSSLEnginePair(c, engines[0], engines[1]);
57    }
58
59    public static SSLEngine[] connect(TestSSLContext c, Hooks hooks) throws IOException {
60        return connect(c, hooks, null);
61    }
62
63    /**
64     * Create a new connected server/client engine pair within a
65     * existing SSLContext. Optionally specify clientCipherSuites to
66     * allow forcing new SSLSession to test SSLSessionContext
67     * caching. Optionally specify serverCipherSuites for testing
68     * cipher suite negotiation.
69     */
70    public static SSLEngine[] connect(final TestSSLContext c,
71                                      Hooks hooks,
72                                      boolean finished[]) throws IOException {
73        if (hooks == null) {
74            hooks = new Hooks();
75        }
76
77        // FINISHED state should be returned only once.
78        boolean[] clientFinished = new boolean[1];
79        boolean[] serverFinished = new boolean[1];
80
81        SSLSession session = c.clientContext.createSSLEngine().getSession();
82
83        int packetBufferSize = session.getPacketBufferSize();
84        ByteBuffer clientToServer = ByteBuffer.allocate(packetBufferSize);
85        ByteBuffer serverToClient = ByteBuffer.allocate(packetBufferSize);
86
87        int applicationBufferSize = session.getApplicationBufferSize();
88        ByteBuffer scratch = ByteBuffer.allocate(applicationBufferSize);
89
90        SSLEngine client = c.clientContext.createSSLEngine(c.host.getHostName(), c.port);
91        SSLEngine server = c.serverContext.createSSLEngine();
92        client.setUseClientMode(true);
93        server.setUseClientMode(false);
94        hooks.beforeBeginHandshake(client, server);
95        client.beginHandshake();
96        server.beginHandshake();
97
98        while (true) {
99            boolean clientDone = client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
100            boolean serverDone = server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
101            if (clientDone && serverDone) {
102                break;
103            }
104
105            boolean progress = false;
106            if (!clientDone) {
107                progress |= handshakeCompleted(client,
108                                               clientToServer,
109                                               serverToClient,
110                                               scratch,
111                                               clientFinished);
112            }
113            if (!serverDone) {
114                progress |= handshakeCompleted(server,
115                                               serverToClient,
116                                               clientToServer,
117                                               scratch,
118                                               serverFinished);
119            }
120            if (!progress) {
121                break;
122            }
123        }
124
125        if (finished != null) {
126            assertEquals(2, finished.length);
127            finished[0] = clientFinished[0];
128            finished[1] = clientFinished[0];
129        }
130        return new SSLEngine[] { server, client };
131    }
132
133    public static class Hooks {
134        void beforeBeginHandshake(SSLEngine client, SSLEngine server) {}
135    }
136
137    private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0);
138
139    private static boolean handshakeCompleted(SSLEngine engine,
140                                              ByteBuffer output,
141                                              ByteBuffer input,
142                                              ByteBuffer scratch,
143                                              boolean[] finished) throws IOException {
144        try {
145            // make the other side's output into our input
146            input.flip();
147
148            HandshakeStatus status = engine.getHandshakeStatus();
149            switch (status) {
150
151                case NEED_TASK: {
152                    boolean progress = false;
153                    while (true) {
154                        Runnable runnable = engine.getDelegatedTask();
155                        if (runnable == null) {
156                            return progress;
157                        }
158                        runnable.run();
159                        progress = true;
160                    }
161                }
162
163                case NEED_UNWRAP: {
164                    // avoid underflow
165                    if (input.remaining() == 0) {
166                        return false;
167                    }
168                    SSLEngineResult unwrapResult = engine.unwrap(input, scratch);
169                    assertEquals(SSLEngineResult.Status.OK, unwrapResult.getStatus());
170                    assertEquals(0, scratch.position());
171                    assertFinishedOnce(finished, unwrapResult);
172                    return true;
173                }
174
175                case NEED_WRAP: {
176                    // avoid possible overflow
177                    if (output.remaining() != output.capacity()) {
178                        return false;
179                    }
180                    SSLEngineResult wrapResult = engine.wrap(EMPTY_BYTE_BUFFER, output);
181                    assertEquals(SSLEngineResult.Status.OK, wrapResult.getStatus());
182                    assertFinishedOnce(finished, wrapResult);
183                    return true;
184                }
185
186                case NOT_HANDSHAKING:
187                    // should have been checked by caller before calling
188                case FINISHED:
189                    // only returned by wrap/unrap status, not getHandshakeStatus
190                    throw new IllegalStateException("Unexpected HandshakeStatus = " + status);
191                default:
192                    throw new IllegalStateException("Unknown HandshakeStatus = " + status);
193            }
194        } finally {
195            // shift consumed input, restore to output mode
196            input.compact();
197        }
198    }
199
200    private static void assertFinishedOnce(boolean[] finishedOut, SSLEngineResult result) {
201        if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
202            assertFalse("should only return FINISHED once", finishedOut[0]);
203            finishedOut[0] = true;
204        }
205    }
206}
207