1 /* 2 * Copyright (C) 2017 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 org.conscrypt; 18 19 import static org.conscrypt.Conscrypt.Engines.setBufferAllocator; 20 import static org.conscrypt.TestUtils.PROTOCOL_TLS_V1_2; 21 import static org.conscrypt.TestUtils.TEST_CIPHER; 22 import static org.conscrypt.TestUtils.initEngine; 23 import static org.conscrypt.TestUtils.initSslContext; 24 import static org.conscrypt.TestUtils.newTextMessage; 25 import static org.junit.Assert.assertArrayEquals; 26 import static org.junit.Assert.assertEquals; 27 28 import java.io.ByteArrayOutputStream; 29 import java.nio.ByteBuffer; 30 import java.security.NoSuchAlgorithmException; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 import javax.net.ssl.SSLContext; 35 import javax.net.ssl.SSLEngine; 36 import javax.net.ssl.SSLEngineResult; 37 import javax.net.ssl.SSLEngineResult.HandshakeStatus; 38 import javax.net.ssl.SSLEngineResult.Status; 39 import javax.net.ssl.SSLException; 40 import javax.net.ssl.SSLHandshakeException; 41 import libcore.java.security.TestKeyStore; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 import org.junit.runners.Parameterized; 45 import org.junit.runners.Parameterized.Parameter; 46 import org.junit.runners.Parameterized.Parameters; 47 48 @RunWith(Parameterized.class) 49 public class ConscryptEngineTest { 50 private static final int MESSAGE_SIZE = 4096; 51 52 @SuppressWarnings("ImmutableEnumChecker") 53 public enum BufferType { 54 HEAP_ALLOCATOR(BufferAllocator.unpooled()) { 55 @Override 56 ByteBuffer newBuffer(int size) { 57 return ByteBuffer.allocate(size); 58 } 59 }, 60 HEAP_NO_ALLOCATOR(null) { 61 @Override 62 ByteBuffer newBuffer(int size) { 63 return ByteBuffer.allocate(size); 64 } 65 }, 66 DIRECT(null) { 67 @Override 68 ByteBuffer newBuffer(int size) { 69 return ByteBuffer.allocateDirect(size); 70 } 71 }; 72 73 abstract ByteBuffer newBuffer(int size); 74 75 BufferType(BufferAllocator allocator) { 76 this.allocator = allocator; 77 } 78 79 private final BufferAllocator allocator; 80 } 81 82 private enum ClientAuth { 83 NONE { 84 @Override 85 void apply(SSLEngine engine) { 86 engine.setWantClientAuth(false); 87 engine.setNeedClientAuth(false); 88 } 89 }, 90 OPTIONAL { 91 @Override 92 void apply(SSLEngine engine) { 93 engine.setWantClientAuth(true); 94 engine.setNeedClientAuth(false); 95 } 96 }, 97 REQUIRED { 98 @Override 99 void apply(SSLEngine engine) { 100 engine.setWantClientAuth(false); 101 engine.setNeedClientAuth(true); 102 } 103 }; 104 105 abstract void apply(SSLEngine engine); 106 } 107 108 @Parameters(name = "{0}") 109 public static Iterable<BufferType> data() { 110 return Arrays.asList( 111 BufferType.HEAP_ALLOCATOR, BufferType.HEAP_NO_ALLOCATOR, BufferType.DIRECT); 112 } 113 114 @Parameter public BufferType bufferType; 115 116 private SSLEngine clientEngine; 117 private SSLEngine serverEngine; 118 private ByteBuffer clientApplicationBuffer; 119 private ByteBuffer clientPacketBuffer; 120 private ByteBuffer serverApplicationBuffer; 121 private ByteBuffer serverPacketBuffer; 122 123 @Test 124 public void mutualAuthWithSameCertsShouldSucceed() throws Exception { 125 doMutualAuthHandshake(TestKeyStore.getServer(), TestKeyStore.getServer(), ClientAuth.NONE); 126 } 127 128 @Test 129 public void mutualAuthWithDifferentCertsShouldSucceed() throws Exception { 130 doMutualAuthHandshake(TestKeyStore.getClient(), TestKeyStore.getServer(), ClientAuth.NONE); 131 } 132 133 @Test(expected = SSLHandshakeException.class) 134 public void mutualAuthWithUntrustedServerShouldFail() throws Exception { 135 doMutualAuthHandshake( 136 TestKeyStore.getClientCA2(), TestKeyStore.getServer(), ClientAuth.NONE); 137 } 138 139 @Test(expected = SSLHandshakeException.class) 140 public void mutualAuthWithUntrustedClientShouldFail() throws Exception { 141 doMutualAuthHandshake(TestKeyStore.getClient(), TestKeyStore.getClient(), ClientAuth.NONE); 142 } 143 144 @Test 145 public void optionalClientAuthShouldSucceed() throws Exception { 146 doMutualAuthHandshake( 147 TestKeyStore.getClient(), TestKeyStore.getServer(), ClientAuth.OPTIONAL); 148 } 149 150 @Test(expected = SSLHandshakeException.class) 151 public void optionalClientAuthShouldFail() throws Exception { 152 doMutualAuthHandshake( 153 TestKeyStore.getClient(), TestKeyStore.getClient(), ClientAuth.OPTIONAL); 154 } 155 156 @Test 157 public void requiredClientAuthShouldSucceed() throws Exception { 158 doMutualAuthHandshake( 159 TestKeyStore.getServer(), TestKeyStore.getServer(), ClientAuth.REQUIRED); 160 } 161 162 @Test(expected = SSLHandshakeException.class) 163 public void requiredClientAuthShouldFail() throws Exception { 164 doMutualAuthHandshake( 165 TestKeyStore.getClient(), TestKeyStore.getClient(), ClientAuth.REQUIRED); 166 } 167 168 @Test 169 public void exchangeMessages() throws Exception { 170 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 171 doHandshake(); 172 173 ByteBuffer clientCleartextBuffer = bufferType.newBuffer(MESSAGE_SIZE); 174 clientCleartextBuffer.put(newTextMessage(MESSAGE_SIZE)); 175 clientCleartextBuffer.flip(); 176 177 // Wrap the original message and create the encrypted data. 178 final int numMessages = 100; 179 ByteBuffer[] encryptedBuffers = new ByteBuffer[numMessages]; 180 for (int i = 0; i < numMessages; ++i) { 181 clientCleartextBuffer.position(0); 182 ByteBuffer out = bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize()); 183 SSLEngineResult wrapResult = clientEngine.wrap(clientCleartextBuffer, out); 184 assertEquals(SSLEngineResult.Status.OK, wrapResult.getStatus()); 185 out.flip(); 186 encryptedBuffers[i] = out; 187 } 188 189 // Create the expected cleartext message 190 clientCleartextBuffer.position(0); 191 byte[] expectedMessage = toArray(clientCleartextBuffer); 192 193 // Unwrap the all of the encrypted messages. 194 for (int i = 0; i < numMessages; ++i) { 195 ByteBuffer out = bufferType.newBuffer(2 * MESSAGE_SIZE); 196 SSLEngineResult unwrapResult = Conscrypt.Engines.unwrap( 197 serverEngine, encryptedBuffers, new ByteBuffer[] {out}); 198 assertEquals(SSLEngineResult.Status.OK, unwrapResult.getStatus()); 199 assertEquals(MESSAGE_SIZE, unwrapResult.bytesProduced()); 200 201 out.flip(); 202 byte[] actualMessage = toArray(out); 203 assertArrayEquals(expectedMessage, actualMessage); 204 } 205 } 206 207 @Test 208 public void exchangeLargeMessage() throws Exception { 209 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 210 doHandshake(); 211 212 // Create the input message. 213 final int largeMessageSize = 16413; 214 final byte[] message = newTextMessage(largeMessageSize); 215 ByteBuffer inputBuffer = bufferType.newBuffer(largeMessageSize); 216 inputBuffer.put(message); 217 inputBuffer.flip(); 218 219 // Encrypt the input message. 220 List<ByteBuffer> encryptedBufferList = new ArrayList<ByteBuffer>(); 221 while (inputBuffer.hasRemaining()) { 222 ByteBuffer encryptedBuffer = 223 bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize()); 224 SSLEngineResult wrapResult = clientEngine.wrap(inputBuffer, encryptedBuffer); 225 assertEquals(SSLEngineResult.Status.OK, wrapResult.getStatus()); 226 encryptedBuffer.flip(); 227 encryptedBufferList.add(encryptedBuffer); 228 } 229 230 // Unwrap the all of the encrypted messages. 231 ByteArrayOutputStream cleartextStream = new ByteArrayOutputStream(); 232 ByteBuffer[] encryptedBuffers = 233 encryptedBufferList.toArray(new ByteBuffer[encryptedBufferList.size()]); 234 int decryptedBufferSize = 8192; 235 final ByteBuffer decryptedBuffer = bufferType.newBuffer(decryptedBufferSize); 236 for (ByteBuffer encryptedBuffer : encryptedBuffers) { 237 SSLEngineResult.Status status = SSLEngineResult.Status.OK; 238 while (encryptedBuffer.hasRemaining() || status.equals(Status.BUFFER_OVERFLOW)) { 239 if (!decryptedBuffer.hasRemaining()) { 240 decryptedBuffer.clear(); 241 } 242 int prevPos = decryptedBuffer.position(); 243 SSLEngineResult unwrapResult = Conscrypt.Engines.unwrap( 244 serverEngine, encryptedBuffers, new ByteBuffer[] {decryptedBuffer}); 245 status = unwrapResult.getStatus(); 246 int newPos = decryptedBuffer.position(); 247 int bytesProduced = unwrapResult.bytesProduced(); 248 assertEquals(bytesProduced, newPos - prevPos); 249 250 // Add any generated bytes to the output stream. 251 if (bytesProduced > 0) { 252 byte[] decryptedBytes = new byte[unwrapResult.bytesProduced()]; 253 254 // Read the chunk that was just written to the output array. 255 int limit = decryptedBuffer.limit(); 256 decryptedBuffer.limit(newPos); 257 decryptedBuffer.position(prevPos); 258 decryptedBuffer.get(decryptedBytes); 259 260 // Restore the position and limit. 261 decryptedBuffer.limit(limit); 262 263 // Write the decrypted bytes to the stream. 264 cleartextStream.write(decryptedBytes); 265 } 266 } 267 } 268 byte[] actualMessage = cleartextStream.toByteArray(); 269 assertArrayEquals(message, actualMessage); 270 } 271 272 private void doMutualAuthHandshake( 273 TestKeyStore clientKs, TestKeyStore serverKs, ClientAuth clientAuth) throws Exception { 274 setupEngines(clientKs, serverKs); 275 clientAuth.apply(serverEngine); 276 doHandshake(); 277 assertEquals(HandshakeStatus.NOT_HANDSHAKING, clientEngine.getHandshakeStatus()); 278 assertEquals(HandshakeStatus.NOT_HANDSHAKING, serverEngine.getHandshakeStatus()); 279 } 280 281 private void doHandshake() throws SSLException { 282 TestUtils.doEngineHandshake(clientEngine, serverEngine, clientApplicationBuffer, 283 clientPacketBuffer, serverApplicationBuffer, serverPacketBuffer); 284 } 285 286 private void setupEngines(TestKeyStore clientKeyStore, TestKeyStore serverKeyStore) 287 throws SSLException { 288 SSLContext clientContext = initSslContext(newContext(), clientKeyStore); 289 SSLContext serverContext = initSslContext(newContext(), serverKeyStore); 290 291 clientEngine = initEngine(clientContext.createSSLEngine(), TEST_CIPHER, true); 292 serverEngine = initEngine(serverContext.createSSLEngine(), TEST_CIPHER, false); 293 setBufferAllocator(clientEngine, bufferType.allocator); 294 setBufferAllocator(serverEngine, bufferType.allocator); 295 296 // Create the application and packet buffers for both endpoints. 297 clientApplicationBuffer = 298 bufferType.newBuffer(clientEngine.getSession().getApplicationBufferSize()); 299 serverApplicationBuffer = 300 bufferType.newBuffer(serverEngine.getSession().getApplicationBufferSize()); 301 clientPacketBuffer = bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize()); 302 serverPacketBuffer = bufferType.newBuffer(serverEngine.getSession().getPacketBufferSize()); 303 } 304 305 private static byte[] toArray(ByteBuffer buffer) { 306 byte[] data = new byte[buffer.remaining()]; 307 buffer.get(data); 308 return data; 309 } 310 311 private static SSLContext newContext() { 312 try { 313 return SSLContext.getInstance(PROTOCOL_TLS_V1_2, new OpenSSLProvider()); 314 } catch (NoSuchAlgorithmException e) { 315 throw new RuntimeException(e); 316 } 317 } 318 } 319