Home | History | Annotate | Download | only in conscrypt
      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