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.TestUtils.getConscryptProvider; 20 import static org.conscrypt.TestUtils.getJdkProvider; 21 import static org.conscrypt.TestUtils.getProtocols; 22 import static org.conscrypt.TestUtils.initSslContext; 23 import static org.conscrypt.TestUtils.newTextMessage; 24 import static org.junit.Assert.assertArrayEquals; 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNull; 28 import static org.junit.Assert.assertTrue; 29 import static org.mockito.Matchers.same; 30 import static org.mockito.Mockito.when; 31 32 import java.io.ByteArrayOutputStream; 33 import java.io.IOException; 34 import java.nio.ByteBuffer; 35 import java.security.NoSuchAlgorithmException; 36 import java.security.Provider; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 import javax.net.ssl.SSLContext; 41 import javax.net.ssl.SSLEngine; 42 import javax.net.ssl.SSLEngineResult; 43 import javax.net.ssl.SSLEngineResult.HandshakeStatus; 44 import javax.net.ssl.SSLEngineResult.Status; 45 import javax.net.ssl.SSLException; 46 import javax.net.ssl.SSLHandshakeException; 47 import javax.net.ssl.SSLSession; 48 import org.conscrypt.java.security.TestKeyStore; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 import org.junit.runners.Parameterized; 52 import org.junit.runners.Parameterized.Parameter; 53 import org.junit.runners.Parameterized.Parameters; 54 import org.mockito.Matchers; 55 import org.mockito.Mockito; 56 57 @RunWith(Parameterized.class) 58 public class ConscryptEngineTest { 59 private static final int MESSAGE_SIZE = 4096; 60 private static final int LARGE_MESSAGE_SIZE = 16413; 61 private static final String[] CIPHERS = TestUtils.getCommonCipherSuites(); 62 private static final String RENEGOTIATION_CIPHER = CIPHERS[CIPHERS.length - 1]; 63 64 @SuppressWarnings("ImmutableEnumChecker") 65 public enum BufferType { 66 HEAP_ALLOCATOR(BufferAllocator.unpooled()) { 67 @Override 68 ByteBuffer newBuffer(int size) { 69 return ByteBuffer.allocate(size); 70 } 71 }, 72 HEAP_NO_ALLOCATOR(null) { 73 @Override 74 ByteBuffer newBuffer(int size) { 75 return ByteBuffer.allocate(size); 76 } 77 }, 78 DIRECT(null) { 79 @Override 80 ByteBuffer newBuffer(int size) { 81 return ByteBuffer.allocateDirect(size); 82 } 83 }; 84 85 abstract ByteBuffer newBuffer(int size); 86 87 BufferType(BufferAllocator allocator) { 88 this.allocator = allocator; 89 } 90 91 private final BufferAllocator allocator; 92 } 93 94 private enum ClientAuth { 95 NONE { 96 @Override 97 void apply(SSLEngine engine) { 98 engine.setWantClientAuth(false); 99 engine.setNeedClientAuth(false); 100 } 101 }, 102 OPTIONAL { 103 @Override 104 void apply(SSLEngine engine) { 105 engine.setWantClientAuth(true); 106 engine.setNeedClientAuth(false); 107 } 108 }, 109 REQUIRED { 110 @Override 111 void apply(SSLEngine engine) { 112 engine.setWantClientAuth(false); 113 engine.setNeedClientAuth(true); 114 } 115 }; 116 117 abstract void apply(SSLEngine engine); 118 } 119 120 @Parameters(name = "{0}") 121 public static Iterable<BufferType> data() { 122 return Arrays.asList( 123 BufferType.HEAP_ALLOCATOR, BufferType.HEAP_NO_ALLOCATOR, BufferType.DIRECT); 124 } 125 126 @Parameter public BufferType bufferType; 127 128 private SSLEngine clientEngine; 129 private SSLEngine serverEngine; 130 131 @Test 132 public void closingOutboundBeforeHandshakeShouldCloseAll() throws Exception { 133 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 134 assertFalse(clientEngine.isInboundDone()); 135 assertFalse(clientEngine.isOutboundDone()); 136 assertFalse(serverEngine.isInboundDone()); 137 assertFalse(serverEngine.isOutboundDone()); 138 139 clientEngine.closeOutbound(); 140 serverEngine.closeOutbound(); 141 142 assertTrue(clientEngine.isInboundDone()); 143 assertTrue(clientEngine.isOutboundDone()); 144 assertTrue(serverEngine.isInboundDone()); 145 assertTrue(serverEngine.isOutboundDone()); 146 } 147 148 @Test 149 public void closingOutboundAfterHandshakeShouldOnlyCloseOutbound() throws Exception { 150 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 151 doHandshake(true); 152 153 assertFalse(clientEngine.isInboundDone()); 154 assertFalse(clientEngine.isOutboundDone()); 155 assertFalse(serverEngine.isInboundDone()); 156 assertFalse(serverEngine.isOutboundDone()); 157 158 clientEngine.closeOutbound(); 159 serverEngine.closeOutbound(); 160 161 assertFalse(clientEngine.isInboundDone()); 162 assertTrue(clientEngine.isOutboundDone()); 163 assertFalse(serverEngine.isInboundDone()); 164 assertTrue(serverEngine.isOutboundDone()); 165 } 166 167 @Test 168 public void closingInboundShouldOnlyCloseInbound() throws Exception { 169 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 170 doHandshake(true); 171 172 assertFalse(clientEngine.isInboundDone()); 173 assertFalse(clientEngine.isOutboundDone()); 174 assertFalse(serverEngine.isInboundDone()); 175 assertFalse(serverEngine.isOutboundDone()); 176 177 clientEngine.closeInbound(); 178 serverEngine.closeInbound(); 179 180 assertTrue(clientEngine.isInboundDone()); 181 assertFalse(clientEngine.isOutboundDone()); 182 assertTrue(serverEngine.isInboundDone()); 183 assertFalse(serverEngine.isOutboundDone()); 184 } 185 186 @Test 187 public void mutualAuthWithSameCertsShouldSucceed() throws Exception { 188 doMutualAuthHandshake(TestKeyStore.getServer(), TestKeyStore.getServer(), ClientAuth.NONE); 189 } 190 191 @Test 192 public void mutualAuthWithDifferentCertsShouldSucceed() throws Exception { 193 doMutualAuthHandshake(TestKeyStore.getClient(), TestKeyStore.getServer(), ClientAuth.NONE); 194 } 195 196 @Test(expected = SSLHandshakeException.class) 197 public void mutualAuthWithUntrustedServerShouldFail() throws Exception { 198 doMutualAuthHandshake( 199 TestKeyStore.getClientCA2(), TestKeyStore.getServer(), ClientAuth.NONE); 200 } 201 202 @Test(expected = SSLHandshakeException.class) 203 public void mutualAuthWithUntrustedClientShouldFail() throws Exception { 204 doMutualAuthHandshake(TestKeyStore.getClient(), TestKeyStore.getClient(), ClientAuth.NONE); 205 } 206 207 @Test 208 public void optionalClientAuthShouldSucceed() throws Exception { 209 doMutualAuthHandshake( 210 TestKeyStore.getClient(), TestKeyStore.getServer(), ClientAuth.OPTIONAL); 211 } 212 213 @Test(expected = SSLHandshakeException.class) 214 public void optionalClientAuthShouldFail() throws Exception { 215 doMutualAuthHandshake( 216 TestKeyStore.getClient(), TestKeyStore.getClient(), ClientAuth.OPTIONAL); 217 } 218 219 @Test 220 public void requiredClientAuthShouldSucceed() throws Exception { 221 doMutualAuthHandshake( 222 TestKeyStore.getServer(), TestKeyStore.getServer(), ClientAuth.REQUIRED); 223 } 224 225 @Test(expected = SSLHandshakeException.class) 226 public void requiredClientAuthShouldFail() throws Exception { 227 doMutualAuthHandshake( 228 TestKeyStore.getClient(), TestKeyStore.getClient(), ClientAuth.REQUIRED); 229 } 230 231 @Test 232 public void exchangeMessages() throws Exception { 233 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 234 doHandshake(true); 235 236 ByteBuffer message = newMessage(MESSAGE_SIZE); 237 byte[] messageBytes = toArray(message); 238 239 // Wrap the original message and create the encrypted data. 240 final int numMessages = 100; 241 ByteBuffer[] encryptedBuffers = new ByteBuffer[numMessages]; 242 for (int i = 0; i < numMessages; ++i) { 243 List<ByteBuffer> wrapped = wrap(message.duplicate(), clientEngine); 244 // Small message, we should only have 1 buffer created. 245 assertEquals(1, wrapped.size()); 246 encryptedBuffers[i] = wrapped.get(0); 247 } 248 249 // Unwrap the all of the encrypted messages. 250 byte[] actualBytes = unwrap(encryptedBuffers, serverEngine); 251 assertEquals(MESSAGE_SIZE * numMessages, actualBytes.length); 252 for (int i = 0; i < numMessages; ++i) { 253 int offset = i * MESSAGE_SIZE; 254 byte[] actualMessageBytes = 255 Arrays.copyOfRange(actualBytes, offset, offset + MESSAGE_SIZE); 256 assertArrayEquals(messageBytes, actualMessageBytes); 257 } 258 } 259 260 @Test 261 public void exchangeLargeMessage() throws Exception { 262 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 263 doHandshake(true); 264 265 ByteBuffer inputBuffer = newMessage(LARGE_MESSAGE_SIZE); 266 exchangeMessage(inputBuffer, clientEngine, serverEngine); 267 } 268 269 @Test 270 public void alpnWithProtocolListShouldSucceed() throws Exception { 271 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 272 273 // Configure ALPN protocols 274 String[] clientAlpnProtocols = new String[]{"http/1.1", "foo", "spdy/2"}; 275 String[] serverAlpnProtocols = new String[]{"spdy/2", "foo", "bar"}; 276 277 Conscrypt.setApplicationProtocols(clientEngine, clientAlpnProtocols); 278 Conscrypt.setApplicationProtocols(serverEngine, serverAlpnProtocols); 279 280 doHandshake(true); 281 assertEquals("spdy/2", Conscrypt.getApplicationProtocol(clientEngine)); 282 assertEquals("spdy/2", Conscrypt.getApplicationProtocol(serverEngine)); 283 } 284 285 @Test 286 public void alpnWithProtocolListShouldFail() throws Exception { 287 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 288 289 // Configure ALPN protocols 290 String[] clientAlpnProtocols = new String[]{"http/1.1", "foo", "spdy/2"}; 291 String[] serverAlpnProtocols = new String[]{"h2", "bar", "baz"}; 292 293 Conscrypt.setApplicationProtocols(clientEngine, clientAlpnProtocols); 294 Conscrypt.setApplicationProtocols(serverEngine, serverAlpnProtocols); 295 296 doHandshake(true); 297 assertNull(Conscrypt.getApplicationProtocol(clientEngine)); 298 assertNull(Conscrypt.getApplicationProtocol(serverEngine)); 299 } 300 301 @Test 302 public void alpnWithServerProtocolSelectorShouldSucceed() throws Exception { 303 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 304 305 // Configure client protocols. 306 String[] clientAlpnProtocols = new String[]{"http/1.1", "foo", "spdy/2"}; 307 Conscrypt.setApplicationProtocols(clientEngine, clientAlpnProtocols); 308 309 // Configure server selector 310 ApplicationProtocolSelector selector = Mockito.mock(ApplicationProtocolSelector.class); 311 when(selector.selectApplicationProtocol(same(serverEngine), Matchers.anyListOf(String.class))) 312 .thenReturn("spdy/2"); 313 Conscrypt.setApplicationProtocolSelector(serverEngine, selector); 314 315 doHandshake(true); 316 assertEquals("spdy/2", Conscrypt.getApplicationProtocol(clientEngine)); 317 assertEquals("spdy/2", Conscrypt.getApplicationProtocol(serverEngine)); 318 } 319 320 @Test 321 public void alpnWithServerProtocolSelectorShouldFail() throws Exception { 322 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 323 324 // Configure client protocols. 325 String[] clientAlpnProtocols = new String[]{"http/1.1", "foo", "spdy/2"}; 326 Conscrypt.setApplicationProtocols(clientEngine, clientAlpnProtocols); 327 328 // Configure server selector 329 ApplicationProtocolSelector selector = Mockito.mock(ApplicationProtocolSelector.class); 330 when(selector.selectApplicationProtocol(same(serverEngine), Matchers.anyListOf(String.class))) 331 .thenReturn("h2"); 332 Conscrypt.setApplicationProtocolSelector(serverEngine, selector); 333 334 doHandshake(true); 335 assertNull(Conscrypt.getApplicationProtocol(clientEngine)); 336 assertNull(Conscrypt.getApplicationProtocol(serverEngine)); 337 } 338 339 /** 340 * BoringSSL server doesn't support renegotiation. BoringSSL clients do not support 341 * initiating a renegotiation, only processing a renegotiation initiated by the 342 * (non-BoringSSL) server. For this reason we test a server-initiated renegotiation with 343 * a Conscrypt client and a JDK server. 344 */ 345 @Test 346 public void serverInitiatedRenegotiationShouldSucceed() throws Exception { 347 setupClientEngine(getConscryptProvider(), TestKeyStore.getClient()); 348 setupServerEngine(getJdkProvider(), TestKeyStore.getServer()); 349 350 // Perform the initial handshake. 351 doHandshake(true); 352 353 // Send a message from client->server. 354 exchangeMessage(newMessage(MESSAGE_SIZE), clientEngine, serverEngine); 355 356 // Trigger a renegotiation from the server and send a message back from Server->Client 357 serverEngine.setEnabledCipherSuites(new String[] {RENEGOTIATION_CIPHER}); 358 serverEngine.beginHandshake(); 359 doHandshake(false); 360 361 exchangeMessage(newMessage(MESSAGE_SIZE), serverEngine, clientEngine); 362 } 363 364 @Test 365 public void savedSessionWorksAfterClose() throws Exception { 366 setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); 367 doHandshake(true); 368 369 SSLSession session = clientEngine.getSession(); 370 String cipherSuite = session.getCipherSuite(); 371 372 clientEngine.closeOutbound(); 373 clientEngine.closeInbound(); 374 375 assertEquals(cipherSuite, session.getCipherSuite()); 376 } 377 378 private void doMutualAuthHandshake( 379 TestKeyStore clientKs, TestKeyStore serverKs, ClientAuth clientAuth) throws Exception { 380 setupEngines(clientKs, serverKs); 381 clientAuth.apply(serverEngine); 382 doHandshake(true); 383 assertEquals(HandshakeStatus.NOT_HANDSHAKING, clientEngine.getHandshakeStatus()); 384 assertEquals(HandshakeStatus.NOT_HANDSHAKING, serverEngine.getHandshakeStatus()); 385 } 386 387 private void doHandshake(boolean beginHandshake) throws SSLException { 388 ByteBuffer clientApplicationBuffer = 389 bufferType.newBuffer(clientEngine.getSession().getApplicationBufferSize()); 390 ByteBuffer clientPacketBuffer = 391 bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize()); 392 ByteBuffer serverApplicationBuffer = 393 bufferType.newBuffer(serverEngine.getSession().getApplicationBufferSize()); 394 ByteBuffer serverPacketBuffer = 395 bufferType.newBuffer(serverEngine.getSession().getPacketBufferSize()); 396 TestUtils.doEngineHandshake(clientEngine, serverEngine, clientApplicationBuffer, 397 clientPacketBuffer, serverApplicationBuffer, serverPacketBuffer, beginHandshake); 398 } 399 400 private void setupEngines(TestKeyStore clientKeyStore, TestKeyStore serverKeyStore) throws SSLException { 401 setupClientEngine(getConscryptProvider(), clientKeyStore); 402 setupServerEngine(getConscryptProvider(), serverKeyStore); 403 } 404 405 private void setupClientEngine(Provider provider, TestKeyStore clientKeyStore) 406 throws SSLException { 407 clientEngine = newEngine(provider, clientKeyStore, true); 408 } 409 410 private void setupServerEngine(Provider provider, TestKeyStore serverKeyStore) 411 throws SSLException { 412 serverEngine = newEngine(provider, serverKeyStore, false); 413 } 414 415 private SSLEngine newEngine( 416 Provider provider, TestKeyStore keyStore, boolean client) { 417 SSLContext serverContext = newContext(provider, keyStore); 418 SSLEngine engine = serverContext.createSSLEngine(); 419 engine.setEnabledCipherSuites(CIPHERS); 420 engine.setUseClientMode(client); 421 if (Conscrypt.isConscrypt(engine)) { 422 Conscrypt.setBufferAllocator(engine, bufferType.allocator); 423 } 424 return engine; 425 } 426 427 private void exchangeMessage(ByteBuffer inputBuffer, SSLEngine src, SSLEngine dest) 428 throws IOException { 429 byte[] messageBytes = toArray(inputBuffer); 430 431 // Encrypt the input message. 432 List<ByteBuffer> encryptedBufferList = wrap(inputBuffer, src); 433 434 // Unwrap the all of the encrypted messages. 435 ByteBuffer[] encryptedBuffers = 436 encryptedBufferList.toArray(new ByteBuffer[encryptedBufferList.size()]); 437 byte[] actualBytes = unwrap(encryptedBuffers, dest); 438 assertArrayEquals(messageBytes, actualBytes); 439 } 440 441 private List<ByteBuffer> wrap(ByteBuffer input, SSLEngine engine) throws SSLException { 442 // Encrypt the input message. 443 List<ByteBuffer> wrapped = new ArrayList<ByteBuffer>(); 444 while (input.hasRemaining()) { 445 ByteBuffer encryptedBuffer = 446 bufferType.newBuffer(engine.getSession().getPacketBufferSize()); 447 SSLEngineResult wrapResult = engine.wrap(input, encryptedBuffer); 448 assertEquals(SSLEngineResult.Status.OK, wrapResult.getStatus()); 449 encryptedBuffer.flip(); 450 wrapped.add(encryptedBuffer); 451 } 452 return wrapped; 453 } 454 455 private byte[] unwrap(ByteBuffer[] encryptedBuffers, SSLEngine engine) throws IOException { 456 ByteArrayOutputStream cleartextStream = new ByteArrayOutputStream(); 457 int decryptedBufferSize = 8192; 458 final ByteBuffer encryptedBuffer = combine(encryptedBuffers); 459 final ByteBuffer decryptedBuffer = bufferType.newBuffer(decryptedBufferSize); 460 while (encryptedBuffer.hasRemaining()) { 461 if (!decryptedBuffer.hasRemaining()) { 462 decryptedBuffer.clear(); 463 } 464 int prevPos = decryptedBuffer.position(); 465 SSLEngineResult unwrapResult = engine.unwrap(encryptedBuffer, decryptedBuffer); 466 SSLEngineResult.Status status = unwrapResult.getStatus(); 467 switch (status) { 468 case BUFFER_OVERFLOW: 469 case OK: { 470 break; 471 } 472 default: { throw new RuntimeException("Unexpected SSLEngine status: " + status); } 473 } 474 int newPos = decryptedBuffer.position(); 475 int bytesProduced = unwrapResult.bytesProduced(); 476 assertEquals(bytesProduced, newPos - prevPos); 477 478 // Add any generated bytes to the output stream. 479 if (bytesProduced > 0 || status == Status.BUFFER_OVERFLOW) { 480 byte[] decryptedBytes = new byte[unwrapResult.bytesProduced()]; 481 482 // Read the chunk that was just written to the output array. 483 int limit = decryptedBuffer.limit(); 484 decryptedBuffer.limit(newPos); 485 decryptedBuffer.position(prevPos); 486 decryptedBuffer.get(decryptedBytes); 487 488 // Restore the position and limit. 489 decryptedBuffer.limit(limit); 490 491 // Write the decrypted bytes to the stream. 492 cleartextStream.write(decryptedBytes); 493 } 494 } 495 496 return cleartextStream.toByteArray(); 497 } 498 499 private ByteBuffer combine(ByteBuffer[] buffers) { 500 int size = 0; 501 for (ByteBuffer buffer : buffers) { 502 size += buffer.remaining(); 503 } 504 ByteBuffer combined = bufferType.newBuffer(size); 505 for (ByteBuffer buffer : buffers) { 506 combined.put(buffer); 507 } 508 combined.flip(); 509 return combined; 510 } 511 512 private ByteBuffer newMessage(int size) { 513 ByteBuffer buffer = bufferType.newBuffer(size); 514 buffer.put(newTextMessage(size)); 515 buffer.flip(); 516 return buffer; 517 } 518 519 private static byte[] toArray(ByteBuffer buffer) { 520 int pos = buffer.position(); 521 byte[] bytes = new byte[buffer.remaining()]; 522 buffer.get(bytes); 523 buffer.position(pos); 524 return bytes; 525 } 526 527 private static SSLContext newContext(Provider provider, TestKeyStore keyStore) { 528 try { 529 SSLContext ctx = SSLContext.getInstance(getProtocols()[0], provider); 530 return initSslContext(ctx, keyStore); 531 } catch (NoSuchAlgorithmException e) { 532 throw new RuntimeException(e); 533 } 534 } 535 } 536