diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java index cbb9c195d0..35d0f485e0 100644 --- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java +++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java @@ -52,17 +52,6 @@ public class IpSecBaseTest extends AndroidTestCase { protected static final int[] DIRECTIONS = new int[] {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT}; - protected static final int TCP_HDRLEN_WITH_OPTIONS = 32; - protected static final int UDP_HDRLEN = 8; - protected static final int IP4_HDRLEN = 20; - protected static final int IP6_HDRLEN = 40; - - // Encryption parameters - protected static final int AES_GCM_IV_LEN = 8; - protected static final int AES_CBC_IV_LEN = 16; - protected static final int AES_GCM_BLK_SIZE = 4; - protected static final int AES_CBC_BLK_SIZE = 16; - protected static final byte[] TEST_DATA = "Best test data ever!".getBytes(); protected static final int DATA_BUFFER_LEN = 4096; protected static final int SOCK_TIMEOUT = 500; diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java index e788d9602f..60d1c03ee2 100644 --- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java +++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java @@ -16,6 +16,14 @@ package android.net.cts; +import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE; +import static android.net.cts.PacketUtils.AES_CBC_IV_LEN; +import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE; +import static android.net.cts.PacketUtils.AES_GCM_IV_LEN; +import static android.net.cts.PacketUtils.IP4_HDRLEN; +import static android.net.cts.PacketUtils.IP6_HDRLEN; +import static android.net.cts.PacketUtils.TCP_HDRLEN_WITH_TIMESTAMP_OPT; +import static android.net.cts.PacketUtils.UDP_HDRLEN; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; import static org.junit.Assert.assertArrayEquals; @@ -421,19 +429,6 @@ public class IpSecManagerTest extends IpSecBaseTest { } } - /** Helper function to calculate expected ESP packet size. */ - private int calculateEspPacketSize( - int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) { - final int ESP_HDRLEN = 4 + 4; // SPI + Seq# - final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length - payloadLen += cryptIvLength; // Initialization Vector - payloadLen += 2; // ESP trailer - - // Align to block size of encryption algorithm - payloadLen += (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize; - return payloadLen + ESP_HDRLEN + ICV_LEN; - } - public void checkTransform( int protocol, String localAddress, @@ -474,7 +469,7 @@ public class IpSecManagerTest extends IpSecBaseTest { try (IpSecTransform transform = transformBuilder.buildTransportModeTransform(local, spi)) { if (protocol == IPPROTO_TCP) { - transportHdrLen = TCP_HDRLEN_WITH_OPTIONS; + transportHdrLen = TCP_HDRLEN_WITH_TIMESTAMP_OPT; checkTcp(transform, local, sendCount, useJavaSockets); } else if (protocol == IPPROTO_UDP) { transportHdrLen = UDP_HDRLEN; @@ -511,7 +506,7 @@ public class IpSecManagerTest extends IpSecBaseTest { int innerPacketSize = TEST_DATA.length + transportHdrLen + ipHdrLen; int outerPacketSize = - calculateEspPacketSize( + PacketUtils.calculateEspPacketSize( TEST_DATA.length + transportHdrLen, ivLen, blkSize, truncLenBits) + udpEncapLen + ipHdrLen; @@ -529,13 +524,13 @@ public class IpSecManagerTest extends IpSecBaseTest { // Add TCP ACKs for data packets if (protocol == IPPROTO_TCP) { int encryptedTcpPktSize = - calculateEspPacketSize(TCP_HDRLEN_WITH_OPTIONS, ivLen, blkSize, truncLenBits); + PacketUtils.calculateEspPacketSize( + TCP_HDRLEN_WITH_TIMESTAMP_OPT, ivLen, blkSize, truncLenBits); - - // Add data packet ACKs - expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount); - expectedInnerBytes += (TCP_HDRLEN_WITH_OPTIONS + ipHdrLen) * (sendCount); - expectedPackets += sendCount; + // Add data packet ACKs + expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount); + expectedInnerBytes += (TCP_HDRLEN_WITH_TIMESTAMP_OPT + ipHdrLen) * (sendCount); + expectedPackets += sendCount; } StatsChecker.waitForNumPackets(expectedPackets); diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java new file mode 100644 index 0000000000..6177827ba6 --- /dev/null +++ b/tests/cts/net/src/android/net/cts/PacketUtils.java @@ -0,0 +1,460 @@ +/* + * 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 android.net.cts; + +import static android.system.OsConstants.IPPROTO_IPV6; +import static android.system.OsConstants.IPPROTO_UDP; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class PacketUtils { + private static final String TAG = PacketUtils.class.getSimpleName(); + + private static final int DATA_BUFFER_LEN = 4096; + + static final int IP4_HDRLEN = 20; + static final int IP6_HDRLEN = 40; + static final int UDP_HDRLEN = 8; + static final int TCP_HDRLEN = 20; + static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12; + + // Not defined in OsConstants + static final int IPPROTO_IPV4 = 4; + static final int IPPROTO_ESP = 50; + + // Encryption parameters + static final int AES_GCM_IV_LEN = 8; + static final int AES_CBC_IV_LEN = 16; + static final int AES_GCM_BLK_SIZE = 4; + static final int AES_CBC_BLK_SIZE = 16; + + // Encryption algorithms + static final String AES = "AES"; + static final String AES_CBC = "AES/CBC/NoPadding"; + static final String HMAC_SHA_256 = "HmacSHA256"; + + public interface Payload { + byte[] getPacketBytes(IpHeader header) throws Exception; + + void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception; + + short length(); + + int getProtocolId(); + } + + public abstract static class IpHeader { + + public final byte proto; + public final InetAddress srcAddr; + public final InetAddress dstAddr; + public final Payload payload; + + public IpHeader(int proto, InetAddress src, InetAddress dst, Payload payload) { + this.proto = (byte) proto; + this.srcAddr = src; + this.dstAddr = dst; + this.payload = payload; + } + + public abstract byte[] getPacketBytes() throws Exception; + + public abstract int getProtocolId(); + } + + public static class Ip4Header extends IpHeader { + private short checksum; + + public Ip4Header(int proto, Inet4Address src, Inet4Address dst, Payload payload) { + super(proto, src, dst, payload); + } + + public byte[] getPacketBytes() throws Exception { + ByteBuffer resultBuffer = buildHeader(); + payload.addPacketBytes(this, resultBuffer); + + return getByteArrayFromBuffer(resultBuffer); + } + + public ByteBuffer buildHeader() { + ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN); + + // Version, IHL + bb.put((byte) (0x45)); + + // DCSP, ECN + bb.put((byte) 0); + + // Total Length + bb.putShort((short) (IP4_HDRLEN + payload.length())); + + // Empty for Identification, Flags and Fragment Offset + bb.putShort((short) 0); + bb.put((byte) 0x40); + bb.put((byte) 0x00); + + // TTL + bb.put((byte) 64); + + // Protocol + bb.put(proto); + + // Header Checksum + final int ipChecksumOffset = bb.position(); + bb.putShort((short) 0); + + // Src/Dst addresses + bb.put(srcAddr.getAddress()); + bb.put(dstAddr.getAddress()); + + bb.putShort(ipChecksumOffset, calculateChecksum(bb)); + + return bb; + } + + private short calculateChecksum(ByteBuffer bb) { + int checksum = 0; + + // Calculate sum of 16-bit values, excluding checksum. IPv4 headers are always 32-bit + // aligned, so no special cases needed for unaligned values. + ShortBuffer shortBuffer = ByteBuffer.wrap(getByteArrayFromBuffer(bb)).asShortBuffer(); + while (shortBuffer.hasRemaining()) { + short val = shortBuffer.get(); + + // Wrap as needed + checksum = addAndWrapForChecksum(checksum, val); + } + + return onesComplement(checksum); + } + + public int getProtocolId() { + return IPPROTO_IPV4; + } + } + + public static class Ip6Header extends IpHeader { + public Ip6Header(int nextHeader, Inet6Address src, Inet6Address dst, Payload payload) { + super(nextHeader, src, dst, payload); + } + + public byte[] getPacketBytes() throws Exception { + ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN); + + // Version | Traffic Class (First 4 bits) + bb.put((byte) 0x60); + + // Traffic class (Last 4 bits), Flow Label + bb.put((byte) 0); + bb.put((byte) 0); + bb.put((byte) 0); + + // Payload Length + bb.putShort((short) payload.length()); + + // Next Header + bb.put(proto); + + // Hop Limit + bb.put((byte) 64); + + // Src/Dst addresses + bb.put(srcAddr.getAddress()); + bb.put(dstAddr.getAddress()); + + // Payload + payload.addPacketBytes(this, bb); + + return getByteArrayFromBuffer(bb); + } + + public int getProtocolId() { + return IPPROTO_IPV6; + } + } + + public static class BytePayload implements Payload { + public final byte[] payload; + + public BytePayload(byte[] payload) { + this.payload = payload; + } + + public int getProtocolId() { + return -1; + } + + public byte[] getPacketBytes(IpHeader header) { + ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN); + + addPacketBytes(header, bb); + return getByteArrayFromBuffer(bb); + } + + public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) { + resultBuffer.put(payload); + } + + public short length() { + return (short) payload.length; + } + } + + public static class UdpHeader implements Payload { + + public final short srcPort; + public final short dstPort; + public final Payload payload; + + public UdpHeader(int srcPort, int dstPort, Payload payload) { + this.srcPort = (short) srcPort; + this.dstPort = (short) dstPort; + this.payload = payload; + } + + public int getProtocolId() { + return IPPROTO_UDP; + } + + public short length() { + return (short) (payload.length() + 8); + } + + public byte[] getPacketBytes(IpHeader header) throws Exception { + ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN); + + addPacketBytes(header, bb); + return getByteArrayFromBuffer(bb); + } + + public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception { + // Source, Destination port + resultBuffer.putShort(srcPort); + resultBuffer.putShort(dstPort); + + // Payload Length + resultBuffer.putShort(length()); + + // Get payload bytes for checksum + payload + ByteBuffer payloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN); + payload.addPacketBytes(header, payloadBuffer); + byte[] payloadBytes = getByteArrayFromBuffer(payloadBuffer); + + // Checksum + resultBuffer.putShort(calculateChecksum(header, payloadBytes)); + + // Payload + resultBuffer.put(payloadBytes); + } + + private short calculateChecksum(IpHeader header, byte[] payloadBytes) throws Exception { + int newChecksum = 0; + ShortBuffer srcBuffer = ByteBuffer.wrap(header.srcAddr.getAddress()).asShortBuffer(); + ShortBuffer dstBuffer = ByteBuffer.wrap(header.dstAddr.getAddress()).asShortBuffer(); + + while (srcBuffer.hasRemaining() || dstBuffer.hasRemaining()) { + short val = srcBuffer.hasRemaining() ? srcBuffer.get() : dstBuffer.get(); + + // Wrap as needed + newChecksum = addAndWrapForChecksum(newChecksum, val); + } + + // Add pseudo-header values. Proto is 0-padded, so just use the byte. + newChecksum = addAndWrapForChecksum(newChecksum, header.proto); + newChecksum = addAndWrapForChecksum(newChecksum, length()); + newChecksum = addAndWrapForChecksum(newChecksum, srcPort); + newChecksum = addAndWrapForChecksum(newChecksum, dstPort); + newChecksum = addAndWrapForChecksum(newChecksum, length()); + + ShortBuffer payloadShortBuffer = ByteBuffer.wrap(payloadBytes).asShortBuffer(); + while (payloadShortBuffer.hasRemaining()) { + newChecksum = addAndWrapForChecksum(newChecksum, payloadShortBuffer.get()); + } + if (payload.length() % 2 != 0) { + newChecksum = + addAndWrapForChecksum( + newChecksum, (payloadBytes[payloadBytes.length - 1] << 8)); + } + + return onesComplement(newChecksum); + } + } + + public static class EspHeader implements Payload { + public final int nextHeader; + public final int spi; + public final int seqNum; + public final byte[] key; + public final byte[] payload; + + /** + * Generic constructor for ESP headers. + * + *

For Tunnel mode, payload will be a full IP header + attached payloads + * + *

For Transport mode, payload will be only the attached payloads, but with the checksum + * calculated using the pre-encryption IP header + */ + public EspHeader(int nextHeader, int spi, int seqNum, byte[] key, byte[] payload) { + this.nextHeader = nextHeader; + this.spi = spi; + this.seqNum = seqNum; + this.key = key; + this.payload = payload; + } + + public int getProtocolId() { + return IPPROTO_ESP; + } + + public short length() { + // ALWAYS uses AES-CBC, HMAC-SHA256 (128b trunc len) + return (short) + calculateEspPacketSize(payload.length, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, 128); + } + + public byte[] getPacketBytes(IpHeader header) throws Exception { + ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN); + + addPacketBytes(header, bb); + return getByteArrayFromBuffer(bb); + } + + public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception { + ByteBuffer espPayloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN); + espPayloadBuffer.putInt(spi); + espPayloadBuffer.putInt(seqNum); + espPayloadBuffer.put(getCiphertext(key)); + + espPayloadBuffer.put(getIcv(getByteArrayFromBuffer(espPayloadBuffer)), 0, 16); + resultBuffer.put(getByteArrayFromBuffer(espPayloadBuffer)); + } + + private byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException { + Mac sha256HMAC = Mac.getInstance(HMAC_SHA_256); + SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256); + sha256HMAC.init(authKey); + + return sha256HMAC.doFinal(authenticatedSection); + } + + /** + * Encrypts and builds ciphertext block. Includes the IV, Padding and Next-Header blocks + * + *

The ciphertext does NOT include the SPI/Sequence numbers, or the ICV. + */ + private byte[] getCiphertext(byte[] key) throws GeneralSecurityException { + int paddedLen = calculateEspEncryptedLength(payload.length, AES_CBC_BLK_SIZE); + ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen); + paddedPayload.put(payload); + + // Add padding - consecutive integers from 0x01 + int pad = 1; + while (paddedPayload.position() < paddedPayload.limit()) { + paddedPayload.put((byte) pad++); + } + + paddedPayload.position(paddedPayload.limit() - 2); + paddedPayload.put((byte) (paddedLen - 2 - payload.length)); // Pad length + paddedPayload.put((byte) nextHeader); + + // Generate Initialization Vector + byte[] iv = new byte[AES_CBC_IV_LEN]; + new SecureRandom().nextBytes(iv); + IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); + SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES); + + // Encrypt payload + Cipher cipher = Cipher.getInstance(AES_CBC); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); + byte[] encrypted = cipher.doFinal(getByteArrayFromBuffer(paddedPayload)); + + // Build ciphertext + ByteBuffer cipherText = ByteBuffer.allocate(AES_CBC_IV_LEN + encrypted.length); + cipherText.put(iv); + cipherText.put(encrypted); + + return getByteArrayFromBuffer(cipherText); + } + } + + private static int addAndWrapForChecksum(int currentChecksum, int value) { + currentChecksum += value & 0x0000ffff; + + // Wrap anything beyond the first 16 bits, and add to lower order bits + return (currentChecksum >>> 16) + (currentChecksum & 0x0000ffff); + } + + private static short onesComplement(int val) { + val = (val >>> 16) + (val & 0xffff); + + if (val == 0) return 0; + return (short) ((~val) & 0xffff); + } + + public static int calculateEspPacketSize( + int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) { + final int ESP_HDRLEN = 4 + 4; // SPI + Seq# + final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length + payloadLen += cryptIvLength; // Initialization Vector + + // Align to block size of encryption algorithm + payloadLen = calculateEspEncryptedLength(payloadLen, cryptBlockSize); + return payloadLen + ESP_HDRLEN + ICV_LEN; + } + + private static int calculateEspEncryptedLength(int payloadLen, int cryptBlockSize) { + payloadLen += 2; // ESP trailer + + // Align to block size of encryption algorithm + return payloadLen + calculateEspPadLen(payloadLen, cryptBlockSize); + } + + private static int calculateEspPadLen(int payloadLen, int cryptBlockSize) { + return (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize; + } + + private static byte[] getByteArrayFromBuffer(ByteBuffer buffer) { + return Arrays.copyOfRange(buffer.array(), 0, buffer.position()); + } + + /* + * Debug printing + */ + private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(hexArray[b >>> 4]); + sb.append(hexArray[b & 0x0F]); + sb.append(' '); + } + return sb.toString(); + } +} diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java index ca233ce1e8..4d5533fc62 100644 --- a/tests/cts/net/src/android/net/cts/TunUtils.java +++ b/tests/cts/net/src/android/net/cts/TunUtils.java @@ -16,6 +16,10 @@ package android.net.cts; +import static android.net.cts.PacketUtils.IP4_HDRLEN; +import static android.net.cts.PacketUtils.IP6_HDRLEN; +import static android.net.cts.PacketUtils.IPPROTO_ESP; +import static android.net.cts.PacketUtils.UDP_HDRLEN; import static android.system.OsConstants.IPPROTO_UDP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -46,9 +50,6 @@ public class TunUtils { private static final int IP6_ADDR_OFFSET = 8; private static final int IP6_ADDR_LEN = 16; - // Not defined in OsConstants - private static final int IPPROTO_ESP = 50; - private final ParcelFileDescriptor mTunFd; private final List mPackets = new ArrayList<>(); private final Thread mReaderThread; @@ -178,17 +179,14 @@ public class TunUtils { private static boolean isEsp(byte[] pkt, int spi, boolean encap) { if (isIpv6(pkt)) { // IPv6 UDP encap not supported by kernels; assume non-encap. - return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP - && isSpiEqual(pkt, IpSecBaseTest.IP6_HDRLEN, spi); + return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi); } else { // Use default IPv4 header length (assuming no options) if (encap) { return pkt[IP4_PROTO_OFFSET] == IPPROTO_UDP - && isSpiEqual( - pkt, IpSecBaseTest.IP4_HDRLEN + IpSecBaseTest.UDP_HDRLEN, spi); + && isSpiEqual(pkt, IP4_HDRLEN + UDP_HDRLEN, spi); } else { - return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP - && isSpiEqual(pkt, IpSecBaseTest.IP4_HDRLEN, spi); + return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP4_HDRLEN, spi); } } }