From 68fde6e39cfa181454778f46091fae7b9f2fba6d Mon Sep 17 00:00:00 2001 From: evitayan Date: Fri, 15 Feb 2019 16:06:02 -0800 Subject: Support decoding Traffic Selector This commit: - Create IkeTrafficSelector class - Suuport decoding IkeTrafficSelector from inbound IKE packet. Bug: 0124528083 Test: FrameworksIkeTests IkeTrafficSelectorTest Change-Id: I09d9684bd26831bd7012cc32fdb1fbdf5028ecfb --- .../com/android/ike/ikev2/IkeTrafficSelector.java | 231 +++++++++++++++++++++ .../ikev2/exceptions/InvalidSyntaxException.java | 9 + .../android/ike/ikev2/message/IkeTsPayload.java | 7 +- .../android/ike/ikev2/IkeTrafficSelectorTest.java | 174 ++++++++++++++++ .../ike/ikev2/message/IkeTsPayloadTest.java | 4 +- 5 files changed, 422 insertions(+), 3 deletions(-) create mode 100644 src/java/com/android/ike/ikev2/IkeTrafficSelector.java create mode 100644 tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java diff --git a/src/java/com/android/ike/ikev2/IkeTrafficSelector.java b/src/java/com/android/ike/ikev2/IkeTrafficSelector.java new file mode 100644 index 00000000..baebe647 --- /dev/null +++ b/src/java/com/android/ike/ikev2/IkeTrafficSelector.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.IntDef; +import android.util.ArraySet; + +import com.android.ike.ikev2.exceptions.InvalidSyntaxException; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; + +/** + * IkeTrafficSelector represents a Traffic Selector of a Child SA. + * + *

IkeTrafficSelector can be constructed by users for initiating Create Child exchange or be + * constructed from a decoded inbound Traffic Selector Payload. + * + * @see RFC 7296, Internet Key Exchange + * Protocol Version 2 (IKEv2) + */ +public final class IkeTrafficSelector { + + // IpProtocolId consists of standard IP Protocol IDs. + @Retention(RetentionPolicy.SOURCE) + @IntDef({IP_PROTOCOL_ID_UNSPEC, IP_PROTOCOL_ID_ICMP, IP_PROTOCOL_ID_TCP, IP_PROTOCOL_ID_UCP}) + public @interface IpProtocolId {} + + // Zero value is re-defined by IKE to indicate that all IP protocols are acceptable. + @VisibleForTesting static final int IP_PROTOCOL_ID_UNSPEC = 0; + @VisibleForTesting static final int IP_PROTOCOL_ID_ICMP = 1; + @VisibleForTesting static final int IP_PROTOCOL_ID_TCP = 6; + @VisibleForTesting static final int IP_PROTOCOL_ID_UCP = 17; + + private static final ArraySet IP_PROTOCOL_ID_SET = new ArraySet<>(); + + static { + IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_UNSPEC); + IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_ICMP); + IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_TCP); + IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_UCP); + } + + /** + * TrafficSelectorType consists of IKE standard Traffic Selector Types. + * + * @see Internet + * Key Exchange Version 2 (IKEv2) Parameters + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE}) + public @interface TrafficSelectorType {} + + public static final int TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE = 7; + public static final int TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE = 8; + + // TODO: Consider defining these constants in a central place in Connectivity. + private static final int IPV4_ADDR_LEN = 4; + private static final int IPV6_ADDR_LEN = 16; + + @VisibleForTesting static final int TRAFFIC_SELECTOR_IPV4_LEN = 16; + @VisibleForTesting static final int TRAFFIC_SELECTOR_IPV6_LEN = 40; + + public final int tsType; + public final int ipProtocolId; + public final int selectorLength; + public final int startPort; + public final int endPort; + public final InetAddress startingAddress; + public final InetAddress endingAddress; + + private IkeTrafficSelector( + int tsType, + int ipProtocolId, + int selectorLength, + int startPort, + int endPort, + InetAddress startingAddress, + InetAddress endingAddress) { + this.tsType = tsType; + this.ipProtocolId = ipProtocolId; + this.selectorLength = selectorLength; + this.startPort = startPort; + this.endPort = endPort; + this.startingAddress = startingAddress; + this.endingAddress = endingAddress; + } + + // TODO: Add a constructor for users to construct IkeTrafficSelector. + + /** + * Decode IkeTrafficSelectors from inbound Traffic Selector Payload. + * + *

This method is only called by IkeTsPayload when decoding inbound IKE message. + * + * @param numTs number or Traffic Selectors + * @param tsBytes encoded byte array of Traffic Selectors + * @return an array of decoded IkeTrafficSelectors + * @throws InvalidSyntaxException if received bytes are malformed. + */ + public static IkeTrafficSelector[] decodeIkeTrafficSelectors(int numTs, byte[] tsBytes) + throws InvalidSyntaxException { + IkeTrafficSelector[] tsArray = new IkeTrafficSelector[numTs]; + ByteBuffer inputBuffer = ByteBuffer.wrap(tsBytes); + + try { + for (int i = 0; i < numTs; i++) { + int tsType = Byte.toUnsignedInt(inputBuffer.get()); + switch (tsType) { + case TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE: + tsArray[i] = decodeIpv4TrafficSelector(inputBuffer); + break; + case TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE: + // TODO: Support it. + throw new UnsupportedOperationException("Cannot decode this type."); + default: + throw new InvalidSyntaxException( + "Invalid Traffic Selector type: " + tsType); + } + } + } catch (BufferOverflowException e) { + // Throw exception if any Traffic Selector has invalid length. + throw new InvalidSyntaxException(e); + } + + if (inputBuffer.remaining() != 0) { + throw new InvalidSyntaxException( + "Unexpected trailing characters of Traffic Selectors."); + } + + return tsArray; + } + + // Decode Traffic Selector using IPv4 address range from a ByteBuffer. A BufferOverflowException + // will be thrown and caught by method caller if operation reaches the input ByteBuffer's limit. + private static IkeTrafficSelector decodeIpv4TrafficSelector(ByteBuffer inputBuffer) + throws InvalidSyntaxException { + // Decode and validate IP Protocol ID + int ipProtocolId = Byte.toUnsignedInt(inputBuffer.get()); + if (!IP_PROTOCOL_ID_SET.contains(ipProtocolId)) { + throw new InvalidSyntaxException("Invalid IP Protocol ID."); + } + + // Decode and validate Selector Length + int tsLength = Short.toUnsignedInt(inputBuffer.getShort()); + if (TRAFFIC_SELECTOR_IPV4_LEN != tsLength) { + throw new InvalidSyntaxException("Invalid Traffic Selector Length."); + } + + // Decode and validate ports + int startPort = Short.toUnsignedInt(inputBuffer.getShort()); + int endPort = Short.toUnsignedInt(inputBuffer.getShort()); + if (startPort > endPort) { + throw new InvalidSyntaxException("Received invalid port range."); + } + + // Decode and validate IPv4 addresses + byte[] startAddressBytes = new byte[IPV4_ADDR_LEN]; + byte[] endAddressBytes = new byte[IPV4_ADDR_LEN]; + inputBuffer.get(startAddressBytes); + inputBuffer.get(endAddressBytes); + try { + Inet4Address startAddress = + (Inet4Address) (Inet4Address.getByAddress(startAddressBytes)); + Inet4Address endAddress = (Inet4Address) (Inet4Address.getByAddress(endAddressBytes)); + + // Validate address range. + if (!isInetAddressRangeValid(startAddress, endAddress)) { + throw new InvalidSyntaxException("Received invalid IPv4 address range."); + } + + return new IkeTrafficSelector( + TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, + ipProtocolId, + TRAFFIC_SELECTOR_IPV4_LEN, + startPort, + endPort, + startAddress, + endAddress); + } catch (ClassCastException | UnknownHostException | IllegalArgumentException e) { + throw new InvalidSyntaxException(e); + } + } + + // TODO: Add a method for decoding IPv6 traffic selector. + + // Validate address range. Caller must ensure two address are same types. + // TODO: Consider moving it to the platform code in the future. + private static boolean isInetAddressRangeValid( + InetAddress startAddress, InetAddress endAddress) { + byte[] startAddrBytes = startAddress.getAddress(); + byte[] endAddrBytes = endAddress.getAddress(); + + if (startAddrBytes.length != endAddrBytes.length) { + throw new IllegalArgumentException("Two addresses are different types."); + } + + for (int i = 0; i < startAddrBytes.length; i++) { + int unsignedByteStart = Byte.toUnsignedInt(startAddrBytes[i]); + int unsignedByteEnd = Byte.toUnsignedInt(endAddrBytes[i]); + + if (unsignedByteStart < unsignedByteEnd) { + return true; + } else if (unsignedByteStart > unsignedByteEnd) { + return false; + } + } + return true; + } +} diff --git a/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java b/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java index ea4b55ae..489eeff8 100644 --- a/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java +++ b/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java @@ -35,4 +35,13 @@ public final class InvalidSyntaxException extends IkeException { public InvalidSyntaxException(String message) { super(IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX); } + + /** + * Construct a instance of InvalidSyntaxException. + * + * @param cause the reason of exception. + */ + public InvalidSyntaxException(Throwable cause) { + super(IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX, cause); + } } diff --git a/src/java/com/android/ike/ikev2/message/IkeTsPayload.java b/src/java/com/android/ike/ikev2/message/IkeTsPayload.java index 9763af2c..8231e29c 100644 --- a/src/java/com/android/ike/ikev2/message/IkeTsPayload.java +++ b/src/java/com/android/ike/ikev2/message/IkeTsPayload.java @@ -16,6 +16,7 @@ package com.android.ike.ikev2.message; +import com.android.ike.ikev2.IkeTrafficSelector; import com.android.ike.ikev2.exceptions.IkeException; import java.nio.ByteBuffer; @@ -37,6 +38,7 @@ public final class IkeTsPayload extends IkePayload { /** Number of Traffic Selectors */ public final int numTs; + public final IkeTrafficSelector[] trafficSelectors; IkeTsPayload(boolean critical, byte[] payloadBody, boolean isInitiator) throws IkeException { super((isInitiator ? PAYLOAD_TYPE_TS_INITIATOR : PAYLOAD_TYPE_TS_RESPONDER), critical); @@ -46,7 +48,10 @@ public final class IkeTsPayload extends IkePayload { // Skip RESERVED byte inputBuffer.get(new byte[TS_HEADER_RESERVED_LEN]); - // TODO: Decode Traffic Selectors. + // Decode Traffic Selectors + byte[] tsBytes = new byte[inputBuffer.remaining()]; + inputBuffer.get(tsBytes); + trafficSelectors = IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); } /** diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java new file mode 100644 index 00000000..1982e62f --- /dev/null +++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.android.ike.ikev2.exceptions.InvalidSyntaxException; +import com.android.ike.ikev2.message.TestUtils; + +import libcore.net.InetAddressUtils; + +import org.junit.Test; + +import java.net.Inet4Address; + +public final class IkeTrafficSelectorTest { + private static final String TS_IPV4_ONE_HEX_STRING = "070000100010fff0c0000264c0000365"; + private static final int TS_ONE_START_PORT = 16; + private static final int TS_ONE_END_PORT = 65520; + private static final Inet4Address TS_ONE_START_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100")); + private static final Inet4Address TS_ONE_END_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.3.101")); + + private static final String TS_IPV4_TWO_HEX_STRING = "070000100000ffffc0000464c0000466"; + private static final int TS_TWO_START_PORT = 0; + private static final int TS_TWO_END_PORT = 65535; + private static final Inet4Address TS_TWO_START_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.100")); + private static final Inet4Address TS_TWO_END_ADDRESS = + (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.102")); + + private static final String TX_IPV4_INVALID_PORT_RANGE_HEX_STRING = + "0700001022221111c0000464c0000466"; + private static final String TX_IPV4_INVALID_ADDRESS_RANGE_HEX_STRING = + "070000100000ffffc0000466c0000366"; + + private static final int TS_TYPE_OFFSET = 0; + private static final int PROTOCOL_ID_OFFSET = 1; + private static final int TS_LENGTH_OFFSET = 2; + + @Test + public void testDecodeIkeTrafficSelectors() throws Exception { + int numTs = 2; + + byte[] tsBytes = + TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING + TS_IPV4_TWO_HEX_STRING); + IkeTrafficSelector[] selectors = + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + + assertEquals(numTs, selectors.length); + + // Verify first traffic selector + IkeTrafficSelector tsOne = selectors[0]; + + assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, tsOne.tsType); + assertEquals(IkeTrafficSelector.IP_PROTOCOL_ID_UNSPEC, tsOne.ipProtocolId); + assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_IPV4_LEN, tsOne.selectorLength); + assertEquals(TS_ONE_START_PORT, tsOne.startPort); + assertEquals(TS_ONE_END_PORT, tsOne.endPort); + assertEquals(TS_ONE_START_ADDRESS, tsOne.startingAddress); + assertEquals(TS_ONE_END_ADDRESS, tsOne.endingAddress); + + // Verify second traffic selector + IkeTrafficSelector tsTwo = selectors[1]; + + assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, tsTwo.tsType); + assertEquals(IkeTrafficSelector.IP_PROTOCOL_ID_UNSPEC, tsTwo.ipProtocolId); + assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_IPV4_LEN, tsTwo.selectorLength); + assertEquals(TS_TWO_START_PORT, tsTwo.startPort); + assertEquals(TS_TWO_END_PORT, tsTwo.endPort); + assertEquals(TS_TWO_START_ADDRESS, tsTwo.startingAddress); + assertEquals(TS_TWO_END_ADDRESS, tsTwo.endingAddress); + } + + @Test + public void testDecodeIkeTrafficSelectorWithInvalidTsType() throws Exception { + int numTs = 1; + byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING); + tsBytes[TS_TYPE_OFFSET] = -1; + + try { + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + fail("Expected to fail due to invalid Traffic Selector Type."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeIkeTrafficSelectorWithInvalidIpProtocol() throws Exception { + int numTs = 1; + byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING); + tsBytes[PROTOCOL_ID_OFFSET] = -1; + + try { + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + fail("Expected to fail due to invalid IP Protocol ID."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeIkeTrafficSelectorWithExpectedTrailing() throws Exception { + int numTs = 1; + byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING + "FFFF"); + + try { + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + fail("Expected to fail due to unexpected trailing characters."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeIkeTrafficSelectorWithInvalidTsLength() throws Exception { + int numTs = 1; + byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING); + + // Traffic Selector field is two octets + tsBytes[TS_LENGTH_OFFSET] = 0; + tsBytes[TS_LENGTH_OFFSET + 1] = 0; + + try { + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + fail("Expected to fail due to invalid Traffic Selector length."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeIkeTrafficSelectorWithInvalidPortRange() throws Exception { + int numTs = 1; + byte[] tsBytes = TestUtils.hexStringToByteArray(TX_IPV4_INVALID_PORT_RANGE_HEX_STRING); + + try { + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + fail("Expected to fail when start port is larger than end port."); + } catch (InvalidSyntaxException expected) { + + } + } + + @Test + public void testDecodeIkeTrafficSelectorWithInvalidAddressRange() throws Exception { + int numTs = 1; + byte[] tsBytes = TestUtils.hexStringToByteArray(TX_IPV4_INVALID_ADDRESS_RANGE_HEX_STRING); + + try { + IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes); + fail("Expected to fail when starting address is larger than ending address."); + } catch (InvalidSyntaxException expected) { + + } + } +} diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java index 67f394f4..f547526a 100644 --- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java +++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java @@ -25,8 +25,8 @@ import java.nio.ByteBuffer; public final class IkeTsPayloadTest { private static final String TS_INITIATOR_PAYLOAD_HEX_STRING = - "2d00001801000000070000100000ffff00000000ffffffff"; - private static final int NUMBER_OF_TS = 1; + "2d00002802000000070000100000ffff00000000ffffffff070000100000ffff00000001fffffffe"; + private static final int NUMBER_OF_TS = 2; @Test public void testDecodeTsInitiatorPayload() throws Exception { -- cgit v1.2.3