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