blob: 05979726e959d21d85c835105467d9e72ea1393a [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.net.eap.test.crypto;
import static com.android.internal.net.TestUtils.hexStringToByteArray;
import static com.android.internal.net.eap.test.crypto.TlsSession.TLS_STATUS_CLOSED;
import static com.android.internal.net.eap.test.crypto.TlsSession.TLS_STATUS_FAILURE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.internal.net.eap.test.crypto.TlsSession.EapTtlsKeyingMaterial;
import com.android.internal.net.eap.test.crypto.TlsSession.TlsResult;
import com.android.internal.net.eap.test.exceptions.EapInvalidRequestException;
import org.junit.Before;
import org.junit.Test;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
public class TlsSessionTest {
// Package-private
SSLEngine mMockSslEngine;
SSLContext mMockSslContext;
SSLSession mMockSslSession;
SecureRandom mMockSecureRandom;
Runnable mMockRunnable;
TlsSession mTlsSession;
static final byte[] SAMPLE_PACKET_TLS_MESSAGE =
hexStringToByteArray(
"17030300800A7516313DA811E690BAF1E76B5C25A1B57B891FC03AECDE89B5C75044B3111"
+ "966EF9149ADA96F07255C9A169105B0C57E2C57780CDA72CD43B3316F923AB2"
+ "1074BE70CC87F5C862F85E862F69105B0C57E2C57780C9C8D74BE705CC87F5C"
+ "8");
static final byte[] SAMPLE_APPLICATION_TLS_MESSAGE =
hexStringToByteArray(
"1603010085010000810303DE76A65F038E90315BB25B49CB9AB4E2540586C3B25851604C8"
+ "D6EFECA11C16D00001CC02BC02C");
static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
static final int APPLICATION_BUFFER_SIZE_TLS_MESSAGE = 50;
private static final int PACKET_BUFFER_SIZE_TLS_MESSAGE = 100;
static final ByteBuffer EMPTY_APPLICATION_BUFFER =
ByteBuffer.allocate(APPLICATION_BUFFER_SIZE_TLS_MESSAGE);
static final ByteBuffer EMPTY_PACKET_BUFFER =
ByteBuffer.allocate(PACKET_BUFFER_SIZE_TLS_MESSAGE);
static final ByteBuffer APPLICATION_BUFFER_POSITION_RESET =
(ByteBuffer)
ByteBuffer.allocate(APPLICATION_BUFFER_SIZE_TLS_MESSAGE)
.put(SAMPLE_APPLICATION_TLS_MESSAGE)
.flip();
static final ByteBuffer PACKET_BUFFER_POSITION_RESET =
(ByteBuffer)
ByteBuffer.allocate(PACKET_BUFFER_SIZE_TLS_MESSAGE)
.put(SAMPLE_PACKET_TLS_MESSAGE)
.flip();
static final ByteBuffer PACKET_BUFFER_POSITION_LIMIT =
ByteBuffer.allocate(PACKET_BUFFER_SIZE_TLS_MESSAGE).put(SAMPLE_PACKET_TLS_MESSAGE);
static final ByteBuffer APPLICATION_BUFFER_POSITION_LIMIT =
(ByteBuffer)
ByteBuffer.allocate(APPLICATION_BUFFER_SIZE_TLS_MESSAGE)
.put(SAMPLE_APPLICATION_TLS_MESSAGE);
public static final SSLEngineResult RESULT_NEED_WRAP_OK =
new SSLEngineResult(Status.OK, HandshakeStatus.NEED_WRAP, 0, 0);
public static final SSLEngineResult RESULT_NEED_UNWRAP_OK =
new SSLEngineResult(Status.OK, HandshakeStatus.NEED_UNWRAP, 0, 0);
public static final SSLEngineResult RESULT_FINISHED_OK =
new SSLEngineResult(Status.OK, HandshakeStatus.FINISHED, 0, 0);
public static final SSLEngineResult RESULT_NOT_HANDSHAKING_OK =
new SSLEngineResult(Status.OK, HandshakeStatus.NOT_HANDSHAKING, 0, 0);
static final SSLEngineResult RESULT_NEED_WRAP_CLOSED =
new SSLEngineResult(Status.CLOSED, HandshakeStatus.NEED_WRAP, 0, 0);
static final SSLEngineResult RESULT_NEED_UNWRAP_OVERFLOW =
new SSLEngineResult(Status.BUFFER_OVERFLOW, HandshakeStatus.NEED_UNWRAP, 0, 0);
static final SSLEngineResult RESULT_NEED_UNWRAP_UNDERFLOW =
new SSLEngineResult(Status.BUFFER_UNDERFLOW, HandshakeStatus.NEED_UNWRAP, 0, 0);
static final SSLEngineResult RESULT_NEED_UNWRAP_CLOSED =
new SSLEngineResult(Status.CLOSED, HandshakeStatus.NEED_UNWRAP, 0, 0);
static final SSLEngineResult RESULT_NOT_HANDSHAKING_OVERFLOW =
new SSLEngineResult(Status.BUFFER_OVERFLOW, HandshakeStatus.NOT_HANDSHAKING, 0, 0);
static final SSLEngineResult RESULT_NOT_HANDSHAKING_UNDERFLOW =
new SSLEngineResult(Status.BUFFER_UNDERFLOW, HandshakeStatus.NOT_HANDSHAKING, 0, 0);
static final SSLEngineResult RESULT_NOT_HANDSHAKING_CLOSED =
new SSLEngineResult(Status.CLOSED, HandshakeStatus.NOT_HANDSHAKING, 0, 0);
@Before
public void setUp() {
mMockSslContext = mock(SSLContext.class);
mMockSslEngine = mock(SSLEngine.class);
mMockSslSession = mock(SSLSession.class);
mMockSecureRandom = mock(SecureRandom.class);
mMockRunnable = mock(Runnable.class);
when(mMockSslSession.getApplicationBufferSize())
.thenReturn(APPLICATION_BUFFER_SIZE_TLS_MESSAGE);
when(mMockSslSession.getPacketBufferSize()).thenReturn(PACKET_BUFFER_SIZE_TLS_MESSAGE);
mTlsSession =
new TlsSession(mMockSslContext, mMockSslEngine, mMockSslSession, mMockSecureRandom);
}
@Test
public void testCloseConnection_success_withData() throws Exception {
when(mMockSslEngine.getHandshakeStatus()).thenReturn(HandshakeStatus.NEED_WRAP);
setupWrap(EMPTY_APPLICATION_BUFFER, EMPTY_PACKET_BUFFER, RESULT_NOT_HANDSHAKING_CLOSED);
TlsResult result = mTlsSession.closeConnection();
assertEquals(TLS_STATUS_CLOSED, result.status);
assertArrayEquals(SAMPLE_PACKET_TLS_MESSAGE, result.data);
verify(mMockSslEngine).wrap(eq(EMPTY_APPLICATION_BUFFER), eq(PACKET_BUFFER_POSITION_LIMIT));
}
@Test
public void testCloseConnection_success_withoutData() throws Exception {
when(mMockSslEngine.getHandshakeStatus()).thenReturn(HandshakeStatus.NOT_HANDSHAKING);
TlsResult result = mTlsSession.closeConnection();
assertEquals(TLS_STATUS_CLOSED, result.status);
assertArrayEquals(EMPTY_BYTE_ARRAY, result.data);
}
@Test
public void testCloseConnection_failure_sslException() throws Exception {
when(mMockSslEngine.getHandshakeStatus()).thenReturn(HandshakeStatus.NEED_WRAP);
when(mMockSslEngine.wrap(eq(EMPTY_APPLICATION_BUFFER), eq(EMPTY_PACKET_BUFFER)))
.thenThrow(SSLException.class);
TlsResult result = mTlsSession.closeConnection();
assertEquals(TLS_STATUS_FAILURE, result.status);
assertArrayEquals(EMPTY_BYTE_ARRAY, result.data);
verify(mMockSslEngine).wrap(eq(EMPTY_APPLICATION_BUFFER), eq(EMPTY_PACKET_BUFFER));
}
@Test
public void testCloseConnection_failure_bufferOverflow() throws Exception {
when(mMockSslEngine.getHandshakeStatus()).thenReturn(HandshakeStatus.NEED_WRAP);
when(mMockSslEngine.wrap(eq(EMPTY_APPLICATION_BUFFER), eq(EMPTY_PACKET_BUFFER)))
.thenReturn(RESULT_NEED_UNWRAP_OVERFLOW);
TlsResult result = mTlsSession.closeConnection();
assertEquals(TLS_STATUS_FAILURE, result.status);
assertArrayEquals(EMPTY_BYTE_ARRAY, result.data);
verify(mMockSslEngine).wrap(eq(EMPTY_APPLICATION_BUFFER), eq(EMPTY_PACKET_BUFFER));
}
@Test
public void testGenerateKeyingMaterial_handshakeNotComplete() throws Exception {
EapTtlsKeyingMaterial result = mTlsSession.generateKeyingMaterial();
assertFalse(result.isSuccessful());
assertTrue(result.eapError.cause instanceof EapInvalidRequestException);
}
/**
* Mocks a wrap operation and inserts data into the packet buffer
*
* @param applicationBuffer the application (source) buffer
* @param packetBuffer the packet (destination) buffer
* @param result the SSLEngineResult to return
*/
void setupWrap(ByteBuffer applicationBuffer, ByteBuffer packetBuffer, SSLEngineResult result)
throws Exception {
when(mMockSslEngine.wrap(eq(applicationBuffer), eq(packetBuffer)))
.thenAnswer(
invocation -> {
ByteBuffer buffer = invocation.getArgument(1);
buffer.put(SAMPLE_PACKET_TLS_MESSAGE);
return result;
});
}
/**
* Mocks a chained wrap operation and inserts data into the packet buffer
*
* @param applicationBuffer the application (source) buffer
* @param packetBuffer the packet (destination) buffer
* @param firstResult the first SSLEngineResult to return
* @param secondResult the second SSLEngineResult to return
*/
void setupChainedWrap(
ByteBuffer applicationBuffer,
ByteBuffer packetBuffer,
SSLEngineResult firstResult,
SSLEngineResult secondResult)
throws Exception {
when(mMockSslEngine.wrap(eq(applicationBuffer), eq(packetBuffer)))
.thenReturn(firstResult)
.thenAnswer(
invocation -> {
ByteBuffer buffer = invocation.getArgument(1);
buffer.put(SAMPLE_PACKET_TLS_MESSAGE);
return secondResult;
});
}
/**
* Mocks an unwrap operation and inserts data into the application buffer
*
* @param applicationBuffer the application (destination) buffer
* @param packetBuffer the packet (source) buffer
* @param result the SSLEngineResult to return
*/
void setupUnwrap(ByteBuffer applicationBuffer, ByteBuffer packetBuffer, SSLEngineResult result)
throws Exception {
when(mMockSslEngine.unwrap(eq(packetBuffer), eq(applicationBuffer)))
.thenAnswer(
invocation -> {
ByteBuffer buffer = invocation.getArgument(1);
buffer.put(SAMPLE_APPLICATION_TLS_MESSAGE);
return result;
});
}
/**
* Mocks a chained wrap operation and inserts data into the packet buffer
*
* <p>The first result during a wrap will be an exception in this case
*
* @param applicationBuffer the application (source) buffer
* @param packetBuffer the packet (destination) buffer
* @param result the SSLEngineResult to return
*/
void setupChainedWrap_exception(
ByteBuffer applicationBuffer, ByteBuffer packetBuffer, SSLEngineResult result)
throws Exception {
when(mMockSslEngine.wrap(eq(applicationBuffer), eq(packetBuffer)))
.thenThrow(SSLException.class)
.thenAnswer(
invocation -> {
ByteBuffer buffer = invocation.getArgument(1);
buffer.put(SAMPLE_PACKET_TLS_MESSAGE);
return result;
});
}
}