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 17 package libcore.javax.net.ssl; 18 19 import java.io.IOException; 20 import java.nio.ByteBuffer; 21 import javax.net.ssl.SSLEngine; 22 import javax.net.ssl.SSLEngineResult; 23 import javax.net.ssl.SSLEngineResult.HandshakeStatus; 24 import javax.net.ssl.SSLSession; 25 import 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 */ 32 public 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 SSLEngine[] engines = connect(c, hooks); 51 return new TestSSLEnginePair(c, engines[0], engines[1]); 52 } 53 54 /** 55 * Create a new connected server/client engine pair within a 56 * existing SSLContext. Optionally specify clientCipherSuites to 57 * allow forcing new SSLSession to test SSLSessionContext 58 * caching. Optionally specify serverCipherSuites for testing 59 * cipher suite negotiation. 60 */ 61 public static SSLEngine[] connect(final TestSSLContext c, 62 Hooks hooks) throws IOException { 63 if (hooks == null) { 64 hooks = new Hooks(); 65 } 66 67 SSLSession session = c.clientContext.createSSLEngine().getSession(); 68 69 int packetBufferSize = session.getPacketBufferSize(); 70 ByteBuffer clientToServer = ByteBuffer.allocate(packetBufferSize); 71 ByteBuffer serverToClient = ByteBuffer.allocate(packetBufferSize); 72 73 int applicationBufferSize = session.getApplicationBufferSize(); 74 ByteBuffer scratch = ByteBuffer.allocate(applicationBufferSize); 75 76 SSLEngine client = c.clientContext.createSSLEngine(); 77 SSLEngine server = c.serverContext.createSSLEngine(); 78 client.setUseClientMode(true); 79 server.setUseClientMode(false); 80 hooks.beforeBeginHandshake(client, server); 81 client.beginHandshake(); 82 server.beginHandshake(); 83 84 while (true) { 85 boolean clientDone = client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; 86 boolean serverDone = server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; 87 if (clientDone && serverDone) { 88 break; 89 } 90 91 boolean progress = false; 92 if (!clientDone) { 93 progress |= handshakeCompleted(client, 94 clientToServer, 95 serverToClient, 96 scratch); 97 } 98 if (!serverDone) { 99 progress |= handshakeCompleted(server, 100 serverToClient, 101 clientToServer, 102 scratch); 103 } 104 if (!progress) { 105 // let caller detect the problem, but don't just hang here 106 break; 107 } 108 } 109 110 return new SSLEngine[] { server, client }; 111 } 112 113 public static class Hooks { 114 void beforeBeginHandshake(SSLEngine client, SSLEngine server) {} 115 } 116 117 private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0); 118 119 private static boolean handshakeCompleted(SSLEngine engine, 120 ByteBuffer output, 121 ByteBuffer input, 122 ByteBuffer scratch) throws IOException { 123 try { 124 // make the other side's output into our input 125 input.flip(); 126 127 HandshakeStatus status = engine.getHandshakeStatus(); 128 switch (status) { 129 130 case NEED_TASK: 131 boolean progress = false; 132 while (true) { 133 Runnable runnable = engine.getDelegatedTask(); 134 if (runnable == null) { 135 return progress; 136 } 137 runnable.run(); 138 progress = true; 139 } 140 141 case NEED_UNWRAP: 142 // avoid underflow 143 if (input.remaining() == 0) { 144 return false; 145 } 146 SSLEngineResult unwrapResult = engine.unwrap(input, scratch); 147 assertEquals(SSLEngineResult.Status.OK, unwrapResult.getStatus()); 148 assertEquals(0, scratch.position()); 149 return true; 150 151 case NEED_WRAP: 152 // avoid possible overflow 153 if (output.remaining() != output.capacity()) { 154 return false; 155 } 156 SSLEngineResult wrapResult = engine.wrap(EMPTY_BYTE_BUFFER, output); 157 assertEquals(SSLEngineResult.Status.OK, wrapResult.getStatus()); 158 return true; 159 160 case NOT_HANDSHAKING: 161 // should have been checked by caller before calling 162 case FINISHED: 163 // only returned by wrap/unrap status, not getHandshakeStatus 164 throw new IllegalStateException("Unexpected HandshakeStatus = " + status); 165 default: 166 throw new IllegalStateException("Unknown HandshakeStatus = " + status); 167 } 168 } finally { 169 // shift consumed input, restore to output mode 170 input.compact(); 171 } 172 } 173 } 174