blob: d19b8693c53c4af16c7cd4670c4d704474aa48a6 [file] [log] [blame]
/*
* Copyright (C) 2018 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.ike.ikev2.message;
import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_OK;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.android.ike.TestUtils;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.crypto.IkeCipher;
import com.android.ike.ikev2.crypto.IkeMacIntegrity;
import com.android.ike.ikev2.exceptions.IkeInternalException;
import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.exceptions.InvalidMessageIdException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.LinkedList;
import javax.crypto.IllegalBlockSizeException;
public final class IkeMessageTest {
private static final String IKE_SA_INIT_HEADER_RAW_PACKET =
"8f54bf6d8b48e6e10000000000000000212022080000000000000150";
private static final String IKE_SA_INIT_BODY_RAW_PACKET =
"220000300000002c010100040300000c0100000c"
+ "800e00800300000803000002030000080400000200000008"
+ "020000022800008800020000b4a2faf4bb54878ae21d6385"
+ "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656"
+ "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37"
+ "534036040610ebdd92f46bef84f0be7db860351843858f8a"
+ "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb"
+ "0b278fd4b7b0a4c26bbeb08214c707137607958729000024"
+ "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb"
+ "881eab2051d8663f421d10b02b00001c00004005d915368c"
+ "a036004cb578ae3e3fb268509aeab1900000002069936922"
+ "8741c6d4ca094c93e242c9de19e7b7c60000000500000500";
private static final String IKE_SA_INIT_RAW_PACKET =
IKE_SA_INIT_HEADER_RAW_PACKET + IKE_SA_INIT_BODY_RAW_PACKET;
// Byte offsets of first payload type in IKE message header.
private static final int FIRST_PAYLOAD_TYPE_OFFSET = 16;
// Byte offsets of first payload's critical bit in IKE message body.
private static final int PAYLOAD_CRITICAL_BIT_OFFSET = 1;
// Byte offsets of first payload length in IKE message body.
private static final int FIRST_PAYLOAD_LENGTH_OFFSET = 2;
// Byte offsets of last payload length in IKE message body.
private static final int LAST_PAYLOAD_LENGTH_OFFSET = 278;
private static final String IKE_AUTH_HEADER_HEX_STRING =
"5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec";
private static final String IKE_AUTH_BODY_HEX_STRING =
"230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c"
+ "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58"
+ "46de333ecd3ea2b705d18293b130395300ba92a351041345"
+ "0a10525cea51b2753b4e92b081fd78d995659a98f742278f"
+ "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60"
+ "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b"
+ "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423"
+ "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3"
+ "59eb4e81ae6e0f22abdad69ba8007d50";
private static final String IKE_AUTH_EXPECTED_CHECKSUM_HEX_STRING = "ae6e0f22abdad69ba8007d50";
private static final String IKE_AUTH_HEX_STRING =
IKE_AUTH_HEADER_HEX_STRING + IKE_AUTH_BODY_HEX_STRING;
private static final String IKE_AUTH_UNENCRYPTED_PADDED_DATA_HEX_STRING =
"2400000c010000000a50500d2700000c010000000a505050"
+ "2100001c02000000df7c038aefaaa32d3f44b228b52a3327"
+ "44dfb2c12c00002c00000028010304032ad4c0a20300000c"
+ "0100000c800e008003000008030000020000000805000000"
+ "2d00001801000000070000100000ffff00000000ffffffff"
+ "2900001801000000070000100000ffff00000000ffffffff"
+ "29000008000040000000000c000040010000000100000000"
+ "000000000000000b";
private static final long INIT_SPI = 0x5f54bf6d8b48e6e1L;
private static final long RESP_SPI = 0x909232b3d1edcb5cL;
private static final String IKE_EMPTY_INFO_MSG_HEX_STRING =
"5f54bf6d8b48e6e1909232b3d1edcb5c2e20252800000000"
+ "0000004c00000030e376871750fdba9f7012446c5dc3f97a"
+ "f83b48ba0dbc68bcc4a78136832100aa4192f251cd4d1b97"
+ "d298e550";
private static final String IKE_EMPTY_INFO_MSG_IV_HEX_STRING =
"e376871750fdba9f7012446c5dc3f97a";
private static final String IKE_EMPTY_INFO_MSG_ENCRYPTED_DATA_HEX_STRING =
"f83b48ba0dbc68bcc4a78136832100aa";
private static final String IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING = "4192f251cd4d1b97d298e550";
private static final int IKE_AUTH_EXPECTED_MESSAGE_ID = 1;
private static final int IKE_AUTH_CIPHER_IV_SIZE = 16;
private static final int IKE_AUTH_CIPHER_BLOCK_SIZE = 16;
private static final int IKE_AUTH_PAYLOAD_SIZE = 8;
private byte[] mIkeAuthPacket;
private byte[] mUnencryptedPaddedData;
private IkeHeader mIkeAuthHeader;
private IkeMacIntegrity mMockIntegrity;
private IkeCipher mMockCipher;
private IkeSaRecord mMockIkeSaRecord;
private static final int[] EXPECTED_IKE_INIT_PAYLOAD_LIST = {
IkePayload.PAYLOAD_TYPE_SA,
IkePayload.PAYLOAD_TYPE_KE,
IkePayload.PAYLOAD_TYPE_NONCE,
IkePayload.PAYLOAD_TYPE_NOTIFY,
IkePayload.PAYLOAD_TYPE_NOTIFY,
IkePayload.PAYLOAD_TYPE_VENDOR
};
class TestIkeSupportedPayload extends IkePayload {
TestIkeSupportedPayload(int payload, boolean critical) {
super(payload, critical);
}
@Override
protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
throw new UnsupportedOperationException(
"It is not supported to encode " + getTypeString());
}
@Override
protected int getPayloadLength() {
throw new UnsupportedOperationException(
"It is not supported to get payload length of " + getTypeString());
}
@Override
public String getTypeString() {
return "Test Payload";
}
}
@Before
public void setUp() throws Exception {
IkePayloadFactory.sDecoderInstance =
new IkePayloadFactory.IIkePayloadDecoder() {
@Override
public IkePayload decodeIkePayload(
int payloadType, boolean isCritical, boolean isResp, byte[] payloadBody)
throws IkeProtocolException {
if (support(payloadType)) {
return new TestIkeSupportedPayload(payloadType, isCritical);
} else {
return new IkeUnsupportedPayload(payloadType, isCritical);
}
}
};
mIkeAuthPacket = TestUtils.hexStringToByteArray(IKE_AUTH_HEX_STRING);
mUnencryptedPaddedData =
TestUtils.hexStringToByteArray(IKE_AUTH_UNENCRYPTED_PADDED_DATA_HEX_STRING);
mIkeAuthHeader = new IkeHeader(mIkeAuthPacket);
mMockIntegrity = mock(IkeMacIntegrity.class);
byte[] expectedChecksum =
TestUtils.hexStringToByteArray(IKE_AUTH_EXPECTED_CHECKSUM_HEX_STRING);
when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(expectedChecksum);
when(mMockIntegrity.getChecksumLen()).thenReturn(expectedChecksum.length);
mMockCipher = mock(IkeCipher.class);
when(mMockCipher.getIvLen()).thenReturn(IKE_AUTH_CIPHER_IV_SIZE);
when(mMockCipher.getBlockSize()).thenReturn(IKE_AUTH_CIPHER_BLOCK_SIZE);
when(mMockCipher.decrypt(any(), any(), any())).thenReturn(mUnencryptedPaddedData);
mMockIkeSaRecord = mock(IkeSaRecord.class);
when(mMockIkeSaRecord.getInboundDecryptionKey()).thenReturn(new byte[0]);
when(mMockIkeSaRecord.getInboundIntegrityKey()).thenReturn(new byte[0]);
}
@After
public void tearDown() {
IkePayloadFactory.sDecoderInstance = new IkePayloadFactory.IkePayloadDecoder();
}
@Test
public void testDecodeIkeMessage() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
IkeHeader header = new IkeHeader(inputPacket);
DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
assertEquals(DECODE_STATUS_OK, decodeResult.status);
assertNotNull(decodeResult.ikeMessage);
assertNull(decodeResult.ikeException);
IkeMessage message = decodeResult.ikeMessage;
assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length, message.ikePayloadList.size());
for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length; i++) {
assertEquals(
EXPECTED_IKE_INIT_PAYLOAD_LIST[i], message.ikePayloadList.get(i).payloadType);
}
}
@Test
public void testDecodeMessageWithUnsupportedUncriticalPayload() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
// Set first payload unsupported uncritical
inputPacket[FIRST_PAYLOAD_TYPE_OFFSET] = (byte) 0xff;
IkeHeader header = new IkeHeader(inputPacket);
DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
assertEquals(DECODE_STATUS_OK, decodeResult.status);
assertNotNull(decodeResult.ikeMessage);
assertNull(decodeResult.ikeException);
IkeMessage message = decodeResult.ikeMessage;
assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1, message.ikePayloadList.size());
for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1; i++) {
assertEquals(
EXPECTED_IKE_INIT_PAYLOAD_LIST[i + 1],
message.ikePayloadList.get(i).payloadType);
}
}
@Test
public void testThrowUnsupportedCriticalPayloadException() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
// Set first payload unsupported critical
inputPacket[FIRST_PAYLOAD_TYPE_OFFSET] = (byte) 0xff;
inputPacket[IkeHeader.IKE_HEADER_LENGTH + PAYLOAD_CRITICAL_BIT_OFFSET] = (byte) 0x80;
UnsupportedCriticalPayloadException exception =
IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(
inputPacket, UnsupportedCriticalPayloadException.class);
assertEquals(1, exception.payloadTypeList.size());
}
@Test
public void testDecodeMessageWithTooShortPayloadLength() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
// Set first payload length to 0
inputPacket[IkeHeader.IKE_HEADER_LENGTH + FIRST_PAYLOAD_LENGTH_OFFSET] = (byte) 0;
inputPacket[IkeHeader.IKE_HEADER_LENGTH + FIRST_PAYLOAD_LENGTH_OFFSET + 1] = (byte) 0;
IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
}
@Test
public void testDecodeMessageWithTooLongPayloadLength() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
// Increase last payload length by one byte
inputPacket[IkeHeader.IKE_HEADER_LENGTH + LAST_PAYLOAD_LENGTH_OFFSET]++;
IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
}
@Test
public void testDecodeMessageWithUnexpectedBytesInTheEnd() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET + "0000");
IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
}
@Test
public void testDecodeEncryptedMessage() throws Exception {
DecodeResult decodeResult =
IkeMessage.decode(
IKE_AUTH_EXPECTED_MESSAGE_ID,
mMockIntegrity,
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
mIkeAuthPacket);
assertEquals(IkeMessage.DECODE_STATUS_OK, decodeResult.status);
assertNotNull(decodeResult.ikeMessage);
assertNull(decodeResult.ikeException);
assertEquals(IKE_AUTH_PAYLOAD_SIZE, decodeResult.ikeMessage.ikePayloadList.size());
}
@Test
public void testDecodeEncryptedMessageWithWrongId() throws Exception {
DecodeResult decodeResult =
IkeMessage.decode(
2,
mMockIntegrity,
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
mIkeAuthPacket);
assertEquals(IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
assertNull(decodeResult.ikeMessage);
assertNotNull(decodeResult.ikeException);
assertTrue(decodeResult.ikeException instanceof InvalidMessageIdException);
}
@Test
public void testDecodeEncryptedMessageWithWrongChecksum() throws Exception {
when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(new byte[0]);
DecodeResult decodeResult =
IkeMessage.decode(
IKE_AUTH_EXPECTED_MESSAGE_ID,
mMockIntegrity,
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
mIkeAuthPacket);
assertEquals(IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
assertNull(decodeResult.ikeMessage);
assertNotNull(decodeResult.ikeException);
assertTrue(
((IkeInternalException) decodeResult.ikeException).getCause()
instanceof GeneralSecurityException);
}
@Test
public void testDecryptFail() throws Exception {
when(mMockCipher.decrypt(any(), any(), any())).thenThrow(IllegalBlockSizeException.class);
DecodeResult decodeResult =
IkeMessage.decode(
IKE_AUTH_EXPECTED_MESSAGE_ID,
mMockIntegrity,
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
mIkeAuthPacket);
assertEquals(IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
assertNull(decodeResult.ikeMessage);
assertNotNull(decodeResult.ikeException);
assertTrue(
((IkeInternalException) decodeResult.ikeException).getCause()
instanceof IllegalBlockSizeException);
}
@Test
public void testParsingErrorInEncryptedMessage() throws Exception {
// Set first payload length to 0
byte[] decryptedData =
Arrays.copyOfRange(mUnencryptedPaddedData, 0, mUnencryptedPaddedData.length);
decryptedData[FIRST_PAYLOAD_LENGTH_OFFSET] = (byte) 0;
decryptedData[FIRST_PAYLOAD_LENGTH_OFFSET + 1] = (byte) 0;
when(mMockCipher.decrypt(any(), any(), any())).thenReturn(decryptedData);
DecodeResult decodeResult =
IkeMessage.decode(
IKE_AUTH_EXPECTED_MESSAGE_ID,
mMockIntegrity,
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
mIkeAuthPacket);
assertEquals(IkeMessage.DECODE_STATUS_PROTECTED_ERROR_MESSAGE, decodeResult.status);
assertNull(decodeResult.ikeMessage);
assertNotNull(decodeResult.ikeException);
assertTrue(decodeResult.ikeException instanceof InvalidSyntaxException);
}
private boolean support(int payloadType) {
// Supports all payload typs from 33 to 46
return (payloadType >= 33 && payloadType <= 46);
}
@Test
public void testAttachEncodedHeader() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
byte[] ikeBodyBytes = TestUtils.hexStringToByteArray(IKE_SA_INIT_BODY_RAW_PACKET);
IkeHeader header = new IkeHeader(inputPacket);
IkeMessage message = IkeMessage.decode(0, header, inputPacket).ikeMessage;
byte[] encodedIkeMessage = message.attachEncodedHeader(ikeBodyBytes);
assertArrayEquals(inputPacket, encodedIkeMessage);
}
@Test
public void testEncodeAndEncryptEmptyMsg() throws Exception {
when(mMockCipher.generateIv())
.thenReturn(TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_IV_HEX_STRING));
when(mMockCipher.encrypt(any(), any(), any()))
.thenReturn(
TestUtils.hexStringToByteArray(
IKE_EMPTY_INFO_MSG_ENCRYPTED_DATA_HEX_STRING));
byte[] checkSum = TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING);
when(mMockIntegrity.getChecksumLen()).thenReturn(checkSum.length);
when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(checkSum);
IkeHeader ikeHeader =
new IkeHeader(
INIT_SPI,
RESP_SPI,
IkePayload.PAYLOAD_TYPE_SK,
IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
true /*isResp*/,
true /*fromInit*/,
0);
IkeMessage ikeMessage = new IkeMessage(ikeHeader, new LinkedList<>());
byte[] ikeMessageBytes =
ikeMessage.encryptAndEncode(mMockIntegrity, mMockCipher, mMockIkeSaRecord);
byte[] expectedBytes = TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_HEX_STRING);
assertArrayEquals(expectedBytes, ikeMessageBytes);
}
// TODO: Implement encodeToByteBuffer() of each payload and add test for encoding message
}