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