From 2b2db7a57f6fbcb548ce762e5303f3100fbf556b Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Thu, 23 Apr 2020 23:20:28 +0000 Subject: [PATCH 1/4] Make a copy of TunUtils and PacketUtils Temporarily make copies. Eventually will statically include the source files in CtsIkeTestCases Bug: 148689509 Test: atest CtsIkeTestCases Change-Id: I7dd5c8b849f0d987fa6d76bc5a0bc1a7eed49b0d Merged-In: I7dd5c8b849f0d987fa6d76bc5a0bc1a7eed49b0d (cherry picked from commit 52716ec0e6807e7c7c78d9789788a0de2bab8b06) --- .../net/ipsec/ike/cts/PacketUtils.java | 467 ++++++++++++++++++ .../android/net/ipsec/ike/cts/TunUtils.java | 264 ++++++++++ 2 files changed, 731 insertions(+) create mode 100644 tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java create mode 100644 tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java new file mode 100644 index 0000000000..35e6719fb7 --- /dev/null +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java @@ -0,0 +1,467 @@ +/* + * 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 android.net.ipsec.ike.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; + +/** + * This code is a exact copy of {@link PacketUtils} in + * cts/tests/tests/net/src/android/net/cts/PacketUtils.java. + * + *

TODO(b/148689509): Statically include the PacketUtils source file instead of copying it. + */ +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/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java new file mode 100644 index 0000000000..71450ea9c0 --- /dev/null +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java @@ -0,0 +1,264 @@ +/* + * 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 android.net.ipsec.ike.cts; + +import static android.net.ipsec.ike.cts.PacketUtils.IP4_HDRLEN; +import static android.net.ipsec.ike.cts.PacketUtils.IP6_HDRLEN; +import static android.net.ipsec.ike.cts.PacketUtils.IPPROTO_ESP; +import static android.net.ipsec.ike.cts.PacketUtils.UDP_HDRLEN; +import static android.system.OsConstants.IPPROTO_UDP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import android.os.ParcelFileDescriptor; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; + +/** + * This code is a exact copy of {@link TunUtils} in + * cts/tests/tests/net/src/android/net/cts/TunUtils.java, except the import path of PacketUtils is + * the path to the copy of PacktUtils. + * + *

TODO(b/148689509): Statically include the TunUtils source file instead of copying it. + */ +public class TunUtils { + private static final String TAG = TunUtils.class.getSimpleName(); + + private static final int DATA_BUFFER_LEN = 4096; + private static final int TIMEOUT = 100; + + private static final int IP4_PROTO_OFFSET = 9; + private static final int IP6_PROTO_OFFSET = 6; + + private static final int IP4_ADDR_OFFSET = 12; + private static final int IP4_ADDR_LEN = 4; + private static final int IP6_ADDR_OFFSET = 8; + private static final int IP6_ADDR_LEN = 16; + + private final ParcelFileDescriptor mTunFd; + private final List mPackets = new ArrayList<>(); + private final Thread mReaderThread; + + public TunUtils(ParcelFileDescriptor tunFd) { + mTunFd = tunFd; + + // Start background reader thread + mReaderThread = + new Thread( + () -> { + try { + // Loop will exit and thread will quit when tunFd is closed. + // Receiving either EOF or an exception will exit this reader loop. + // FileInputStream in uninterruptable, so there's no good way to + // ensure that this thread shuts down except upon FD closure. + while (true) { + byte[] intercepted = receiveFromTun(); + if (intercepted == null) { + // Exit once we've hit EOF + return; + } else if (intercepted.length > 0) { + // Only save packet if we've received any bytes. + synchronized (mPackets) { + mPackets.add(intercepted); + mPackets.notifyAll(); + } + } + } + } catch (IOException ignored) { + // Simply exit this reader thread + return; + } + }); + mReaderThread.start(); + } + + private byte[] receiveFromTun() throws IOException { + FileInputStream in = new FileInputStream(mTunFd.getFileDescriptor()); + byte[] inBytes = new byte[DATA_BUFFER_LEN]; + int bytesRead = in.read(inBytes); + + if (bytesRead < 0) { + return null; // return null for EOF + } else if (bytesRead >= DATA_BUFFER_LEN) { + throw new IllegalStateException("Too big packet. Fragmentation unsupported"); + } + return Arrays.copyOf(inBytes, bytesRead); + } + + private byte[] getFirstMatchingPacket(Predicate verifier, int startIndex) { + synchronized (mPackets) { + for (int i = startIndex; i < mPackets.size(); i++) { + byte[] pkt = mPackets.get(i); + if (verifier.test(pkt)) { + return pkt; + } + } + } + return null; + } + + /** + * Checks if the specified bytes were ever sent in plaintext. + * + *

Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like + * + * @param plaintext the plaintext bytes to check for + * @param startIndex the index in the list to check for + */ + public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) { + Predicate verifier = + (pkt) -> { + return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) + != -1; + }; + return getFirstMatchingPacket(verifier, startIndex) != null; + } + + public byte[] getEspPacket(int spi, boolean encap, int startIndex) { + return getFirstMatchingPacket( + (pkt) -> { + return isEsp(pkt, spi, encap); + }, + startIndex); + } + + public byte[] awaitEspPacketNoPlaintext( + int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception { + long endTime = System.currentTimeMillis() + TIMEOUT; + int startIndex = 0; + + synchronized (mPackets) { + while (System.currentTimeMillis() < endTime) { + byte[] espPkt = getEspPacket(spi, useEncap, startIndex); + if (espPkt != null) { + // Validate packet size + assertEquals(expectedPacketSize, espPkt.length); + + // Always check plaintext from start + assertFalse(hasPlaintextPacket(plaintext, 0)); + return espPkt; // We've found the packet we're looking for. + } + + startIndex = mPackets.size(); + + // Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout + long waitTimeout = endTime - System.currentTimeMillis(); + if (waitTimeout > 0) { + mPackets.wait(waitTimeout); + } + } + + fail("No such ESP packet found with SPI " + spi); + } + return null; + } + + private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) { + // Check SPI byte by byte. + return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff) + && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff) + && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff) + && pkt[espOffset + 3] == (byte) (spi & 0xff); + } + + 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, IP6_HDRLEN, spi); + } else { + // Use default IPv4 header length (assuming no options) + if (encap) { + return pkt[IP4_PROTO_OFFSET] == IPPROTO_UDP + && isSpiEqual(pkt, IP4_HDRLEN + UDP_HDRLEN, spi); + } else { + return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP4_HDRLEN, spi); + } + } + } + + private static boolean isIpv6(byte[] pkt) { + // First nibble shows IP version. 0x60 for IPv6 + return (pkt[0] & (byte) 0xF0) == (byte) 0x60; + } + + private static byte[] getReflectedPacket(byte[] pkt) { + byte[] reflected = Arrays.copyOf(pkt, pkt.length); + + if (isIpv6(pkt)) { + // Set reflected packet's dst to that of the original's src + System.arraycopy( + pkt, // src + IP6_ADDR_OFFSET + IP6_ADDR_LEN, // src offset + reflected, // dst + IP6_ADDR_OFFSET, // dst offset + IP6_ADDR_LEN); // len + // Set reflected packet's src IP to that of the original's dst IP + System.arraycopy( + pkt, // src + IP6_ADDR_OFFSET, // src offset + reflected, // dst + IP6_ADDR_OFFSET + IP6_ADDR_LEN, // dst offset + IP6_ADDR_LEN); // len + } else { + // Set reflected packet's dst to that of the original's src + System.arraycopy( + pkt, // src + IP4_ADDR_OFFSET + IP4_ADDR_LEN, // src offset + reflected, // dst + IP4_ADDR_OFFSET, // dst offset + IP4_ADDR_LEN); // len + // Set reflected packet's src IP to that of the original's dst IP + System.arraycopy( + pkt, // src + IP4_ADDR_OFFSET, // src offset + reflected, // dst + IP4_ADDR_OFFSET + IP4_ADDR_LEN, // dst offset + IP4_ADDR_LEN); // len + } + return reflected; + } + + /** Takes all captured packets, flips the src/dst, and re-injects them. */ + public void reflectPackets() throws IOException { + synchronized (mPackets) { + for (byte[] pkt : mPackets) { + injectPacket(getReflectedPacket(pkt)); + } + } + } + + public void injectPacket(byte[] pkt) throws IOException { + FileOutputStream out = new FileOutputStream(mTunFd.getFileDescriptor()); + out.write(pkt); + out.flush(); + } + + /** Resets the intercepted packets. */ + public void reset() throws IOException { + synchronized (mPackets) { + mPackets.clear(); + } + } +} From 5d8b8a1f7128dce20e724e8568462b182ea11433 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Sat, 25 Apr 2020 03:08:19 +0000 Subject: [PATCH 2/4] Add initial CTS test for IkeSessionParams This commit adds tests for building IkeSessionParams with PSK. It also tests configuring SA lifetimes, retransmissions and PCSCF server requests Bug: 148689509 Test: atest CtsIkeTestCases Change-Id: I16fdc1ff9a22acb82b376211e0f187c4ead4cae5 Merged-In: I16fdc1ff9a22acb82b376211e0f187c4ead4cae5 (cherry picked from commit c98c75308fae272eb4c9f539bed22c1a91aab2a4) --- .../ipsec/ike/cts/IkeSessionParamsTest.java | 262 ++++++++++++++++++ .../net/ipsec/ike/cts/IkeTestBase.java | 13 + 2 files changed, 275 insertions(+) create mode 100644 tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java new file mode 100644 index 0000000000..91ef7789e3 --- /dev/null +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java @@ -0,0 +1,262 @@ +/* + * 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 android.net.ipsec.ike.cts; + +import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID; +import static android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig; +import static android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +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 android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeIdentification; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv4PcscfServer; +import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv6PcscfServer; +import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { + private static final int HARD_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(20L); + private static final int SOFT_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(10L); + private static final int DPD_DELAY_SECONDS = (int) TimeUnit.MINUTES.toSeconds(10L); + private static final int[] RETRANS_TIMEOUT_MS_LIST = new int[] {500, 500, 500, 500, 500, 500}; + + private static final Map, Integer> EXPECTED_REQ_COUNT = + new HashMap<>(); + private static final HashSet EXPECTED_PCSCF_SERVERS = new HashSet<>(); + + static { + EXPECTED_REQ_COUNT.put(ConfigRequestIpv4PcscfServer.class, 3); + EXPECTED_REQ_COUNT.put(ConfigRequestIpv6PcscfServer.class, 3); + + EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV4_ADDRESS_1); + EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV4_ADDRESS_2); + EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV6_ADDRESS_1); + EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV6_ADDRESS_2); + } + + // Arbitrary proposal and remote ID. Local ID is chosen to match the client end cert in the + // following CL + private static final IkeSaProposal SA_PROPOSAL = + SaProposalTest.buildIkeSaProposalWithNormalModeCipher(); + private static final IkeIdentification LOCAL_ID = new IkeFqdnIdentification(LOCAL_HOSTNAME); + private static final IkeIdentification REMOTE_ID = new IkeFqdnIdentification(REMOTE_HOSTNAME); + + /** + * Create a Builder that has minimum configurations to build an IkeSessionParams. + * + *

Authentication method is arbitrarily selected. Using other method (e.g. setAuthEap) also + * works. + */ + private IkeSessionParams.Builder createIkeParamsBuilderMinimum() { + return new IkeSessionParams.Builder(sContext) + .setNetwork(sTunNetwork) + .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress()) + .addSaProposal(SA_PROPOSAL) + .setLocalIdentification(LOCAL_ID) + .setRemoteIdentification(REMOTE_ID) + .setAuthPsk(IKE_PSK); + } + + /** + * Verify the minimum configurations to build an IkeSessionParams. + * + * @see #createIkeParamsBuilderMinimum + */ + private void verifyIkeParamsMinimum(IkeSessionParams sessionParams) { + assertEquals(sTunNetwork, sessionParams.getNetwork()); + assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname()); + assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals()); + assertEquals(LOCAL_ID, sessionParams.getLocalIdentification()); + assertEquals(REMOTE_ID, sessionParams.getRemoteIdentification()); + + IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); + assertTrue(localConfig instanceof IkeAuthPskConfig); + assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) localConfig).getPsk()); + IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig(); + assertTrue(remoteConfig instanceof IkeAuthPskConfig); + assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk()); + } + + private void verifySpecificPcscfConfigReqs( + HashSet expectedAddresses, IkeSessionParams sessionParams) { + Set resultAddresses = new HashSet<>(); + + for (IkeConfigRequest req : sessionParams.getConfigurationRequests()) { + if (req instanceof ConfigRequestIpv4PcscfServer + && ((ConfigRequestIpv4PcscfServer) req).getAddress() != null) { + resultAddresses.add(((ConfigRequestIpv4PcscfServer) req).getAddress()); + } else if (req instanceof ConfigRequestIpv6PcscfServer + && ((ConfigRequestIpv6PcscfServer) req).getAddress() != null) { + resultAddresses.add(((ConfigRequestIpv6PcscfServer) req).getAddress()); + } + } + + assertEquals(expectedAddresses, resultAddresses); + } + + @Test + public void testBuildWithMinimumSet() throws Exception { + IkeSessionParams sessionParams = createIkeParamsBuilderMinimum().build(); + + verifyIkeParamsMinimum(sessionParams); + + // Verify default values that do not need explicit configuration. Do not do assertEquals + // to be avoid being a change-detector test + assertTrue(sessionParams.getHardLifetimeSeconds() > sessionParams.getSoftLifetimeSeconds()); + assertTrue(sessionParams.getSoftLifetimeSeconds() > 0); + assertTrue(sessionParams.getDpdDelaySeconds() > 0); + assertTrue(sessionParams.getRetransmissionTimeoutsMillis().length > 0); + for (int timeout : sessionParams.getRetransmissionTimeoutsMillis()) { + assertTrue(timeout > 0); + } + assertTrue(sessionParams.getConfigurationRequests().isEmpty()); + assertFalse(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)); + } + + @Test + public void testSetLifetimes() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimum() + .setLifetimeSeconds(HARD_LIFETIME_SECONDS, SOFT_LIFETIME_SECONDS) + .build(); + + verifyIkeParamsMinimum(sessionParams); + assertEquals(HARD_LIFETIME_SECONDS, sessionParams.getHardLifetimeSeconds()); + assertEquals(SOFT_LIFETIME_SECONDS, sessionParams.getSoftLifetimeSeconds()); + } + + @Test + public void testSetDpdDelay() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimum().setDpdDelaySeconds(DPD_DELAY_SECONDS).build(); + + verifyIkeParamsMinimum(sessionParams); + assertEquals(DPD_DELAY_SECONDS, sessionParams.getDpdDelaySeconds()); + } + + @Test + public void testSetRetransmissionTimeouts() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimum() + .setRetransmissionTimeoutsMillis(RETRANS_TIMEOUT_MS_LIST) + .build(); + + verifyIkeParamsMinimum(sessionParams); + assertArrayEquals(RETRANS_TIMEOUT_MS_LIST, sessionParams.getRetransmissionTimeoutsMillis()); + } + + @Test + public void testSetPcscfConfigRequests() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimum() + .setRetransmissionTimeoutsMillis(RETRANS_TIMEOUT_MS_LIST) + .addPcscfServerRequest(AF_INET) + .addPcscfServerRequest(PCSCF_IPV4_ADDRESS_1) + .addPcscfServerRequest(PCSCF_IPV6_ADDRESS_1) + .addPcscfServerRequest(AF_INET6) + .addPcscfServerRequest(PCSCF_IPV4_ADDRESS_2) + .addPcscfServerRequest(PCSCF_IPV6_ADDRESS_2) + .build(); + + verifyIkeParamsMinimum(sessionParams); + verifyConfigRequestTypes(EXPECTED_REQ_COUNT, sessionParams.getConfigurationRequests()); + + Set resultAddresses = new HashSet<>(); + for (IkeConfigRequest req : sessionParams.getConfigurationRequests()) { + if (req instanceof ConfigRequestIpv4PcscfServer + && ((ConfigRequestIpv4PcscfServer) req).getAddress() != null) { + resultAddresses.add(((ConfigRequestIpv4PcscfServer) req).getAddress()); + } else if (req instanceof ConfigRequestIpv6PcscfServer + && ((ConfigRequestIpv6PcscfServer) req).getAddress() != null) { + resultAddresses.add(((ConfigRequestIpv6PcscfServer) req).getAddress()); + } + } + assertEquals(EXPECTED_PCSCF_SERVERS, resultAddresses); + } + + @Test + public void testAddIkeOption() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimum() + .addIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID) + .build(); + + verifyIkeParamsMinimum(sessionParams); + assertTrue(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)); + } + + @Test + public void testRemoveIkeOption() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimum() + .addIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID) + .removeIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID) + .build(); + + verifyIkeParamsMinimum(sessionParams); + assertFalse(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)); + } + + @Test + public void testBuildWithPsk() throws Exception { + IkeSessionParams sessionParams = + new IkeSessionParams.Builder(sContext) + .setNetwork(sTunNetwork) + .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress()) + .addSaProposal(SA_PROPOSAL) + .setLocalIdentification(LOCAL_ID) + .setRemoteIdentification(REMOTE_ID) + .setAuthPsk(IKE_PSK) + .build(); + assertEquals(sTunNetwork, sessionParams.getNetwork()); + assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname()); + assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals()); + assertEquals(LOCAL_ID, sessionParams.getLocalIdentification()); + assertEquals(REMOTE_ID, sessionParams.getRemoteIdentification()); + + IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); + assertTrue(localConfig instanceof IkeAuthPskConfig); + assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) localConfig).getPsk()); + IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig(); + assertTrue(remoteConfig instanceof IkeAuthPskConfig); + assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk()); + } + + // TODO(b/148689509): Add tests for building IkeSessionParams using EAP and + // digital-signature-based authentication +} diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java index d3aa8d03d5..5f608df137 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java @@ -24,6 +24,7 @@ import android.net.ipsec.ike.IkeTrafficSelector; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,6 +41,11 @@ abstract class IkeTestBase { static final int IP4_PREFIX_LEN = 32; static final int IP6_PREFIX_LEN = 64; + static final byte[] IKE_PSK = "ikeAndroidPsk".getBytes(); + + static final String LOCAL_HOSTNAME = "client.test.ike.android.net"; + static final String REMOTE_HOSTNAME = "server.test.ike.android.net"; + static final Inet4Address IPV4_ADDRESS_LOCAL = (Inet4Address) (InetAddresses.parseNumericAddress("192.0.2.100")); static final Inet4Address IPV4_ADDRESS_REMOTE = @@ -49,6 +55,13 @@ abstract class IkeTestBase { static final Inet6Address IPV6_ADDRESS_REMOTE = (Inet6Address) (InetAddresses.parseNumericAddress("2001:db8:255::100")); + static final InetAddress PCSCF_IPV4_ADDRESS_1 = InetAddresses.parseNumericAddress("192.0.2.1"); + static final InetAddress PCSCF_IPV4_ADDRESS_2 = InetAddresses.parseNumericAddress("192.0.2.2"); + static final InetAddress PCSCF_IPV6_ADDRESS_1 = + InetAddresses.parseNumericAddress("2001:DB8::1"); + static final InetAddress PCSCF_IPV6_ADDRESS_2 = + InetAddresses.parseNumericAddress("2001:DB8::2"); + static final IkeTrafficSelector DEFAULT_V4_TS = new IkeTrafficSelector( MIN_PORT, From 913ce19d40b07d49d8b10f35e8f676299ccf7c1a Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Sun, 26 Apr 2020 22:34:31 +0000 Subject: [PATCH 3/4] Test building IkeSessionParams with EAP Bug: 148689509 Test: atest CtsIkeTestCases Change-Id: Iea7f4d14502f4b204c7a0d7357e1aaec99954e1f Merged-In: Iea7f4d14502f4b204c7a0d7357e1aaec99954e1f (cherry picked from commit 5ad4adaff63b06c66ce9466387118a077331f0c3) --- tests/cts/net/ipsec/Android.bp | 1 + .../assets/pem/server-a-self-signed-ca.pem | 20 +++ .../ipsec/ike/cts/IkeSessionParamsTest.java | 144 ++++++++++++++---- .../net/ipsec/ike/cts/IkeTestBase.java | 6 + 4 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem diff --git a/tests/cts/net/ipsec/Android.bp b/tests/cts/net/ipsec/Android.bp index 86969c31ae..1d9128b7fe 100644 --- a/tests/cts/net/ipsec/Android.bp +++ b/tests/cts/net/ipsec/Android.bp @@ -26,6 +26,7 @@ android_test { srcs: [ "src/**/*.java", + ":ike-test-utils", ], static_libs: [ diff --git a/tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem b/tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem new file mode 100644 index 0000000000..972fd55372 --- /dev/null +++ b/tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSDCCAjCgAwIBAgIITJQJ6HC1rjwwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxITAfBgNVBAMTGHJvb3QuY2EudGVzdC5h +bmRyb2lkLm5ldDAeFw0xOTA5MzAxNzU1NTJaFw0yOTA5MjcxNzU1NTJaMEIxCzAJ +BgNVBAYTAlVTMRAwDgYDVQQKEwdBbmRyb2lkMSEwHwYDVQQDExhyb290LmNhLnRl +c3QuYW5kcm9pZC5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCT +q3hGF+JvLaB1xW7KGKmaxiQ7BxX2Sn7cbp7ggoVYXsFlBUuPPv3+Vg5PfPCPhsJ8 +/7w4HyKo3uc/vHs5HpQ7rSd9blhAkfmJci2ULLq73FB8Mix4CzPwMx29RrN1X9bU +z4G0vJMczIBGxbZ0uw7n8bKcXBV7AIeax+J8lseEZ3k8iSuBkUJqGIpPFKTqByFZ +A1Lvt47xkON5SZh6c/Oe+o6291wXaCOJUSAKv6PAWZkq9HeD2fqKA/ck9dBaz1M3 +YvzQ9V/7so3/dECjAfKia388h1I6XSGNUM+d5hpxMXpAFgG42eUXHpJ10OjDvSwd +7ZSC91/kRQewUomEKBK1AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBRJn6hHhdeDY/dXpCKUfrFYQhKAGjANBgkqhkiG +9w0BAQsFAAOCAQEAig/94aGfHBhZuvbbhwAK4rUNpizmR567u0ZJ+QUEKyAlo9lT +ZWYHSm7qTAZYvPEjzTQIptnAlxCHePXh3Cfwgo+r82lhG2rcdI03iRyvHWjM8gyk +BXCJTi0Q08JHHpTP6GnAqpz58qEIFkk8P766zNXdhYrGPOydF+p7MFcb1Zv1gum3 +zmRLt0XUAMfjPUv1Bl8kTKFxH5lkMBLR1E0jnoJoTTfgRPrf9CuFSoh48n7YhoBT +KV75xZY8b8+SuB0v6BvQmkpKZGoxBjuVsShyG7q1+4JTAtwhiP7BlkDvVkaBEi7t +WIMFp2r2ZDisHgastNaeYFyzHYz9g1FCCrHQ4w== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java index 91ef7789e3..532be675aa 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java @@ -17,16 +17,22 @@ package android.net.ipsec.ike.cts; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID; +import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH; import static android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig; +import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig; +import static android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig; import static android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; +import static android.telephony.TelephonyManager.APPTYPE_USIM; 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.junit.Assert.fail; +import android.net.eap.EapSessionConfig; import android.net.ipsec.ike.IkeFqdnIdentification; import android.net.ipsec.ike.IkeIdentification; import android.net.ipsec.ike.IkeSaProposal; @@ -37,10 +43,14 @@ import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.internal.net.ipsec.ike.testutils.CertUtils; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.net.InetAddress; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -76,6 +86,29 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { private static final IkeIdentification LOCAL_ID = new IkeFqdnIdentification(LOCAL_HOSTNAME); private static final IkeIdentification REMOTE_ID = new IkeFqdnIdentification(REMOTE_HOSTNAME); + private static final EapSessionConfig EAP_ALL_METHODS_CONFIG = + createEapOnlySafeMethodsBuilder() + .setEapMsChapV2Config(EAP_MSCHAPV2_USERNAME, EAP_MSCHAPV2_PASSWORD) + .build(); + private static final EapSessionConfig EAP_ONLY_SAFE_METHODS_CONFIG = + createEapOnlySafeMethodsBuilder().build(); + + private X509Certificate mServerCaCert; + + @Before + public void setUp() throws Exception { + mServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem"); + } + + private static EapSessionConfig.Builder createEapOnlySafeMethodsBuilder() { + return new EapSessionConfig.Builder() + .setEapIdentity(EAP_IDENTITY) + .setEapSimConfig(SUB_ID, APPTYPE_USIM) + .setEapAkaConfig(SUB_ID, APPTYPE_USIM) + .setEapAkaPrimeConfig( + SUB_ID, APPTYPE_USIM, NETWORK_NAME, true /* allowMismatchedNetworkNames */); + } + /** * Create a Builder that has minimum configurations to build an IkeSessionParams. * @@ -112,23 +145,6 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk()); } - private void verifySpecificPcscfConfigReqs( - HashSet expectedAddresses, IkeSessionParams sessionParams) { - Set resultAddresses = new HashSet<>(); - - for (IkeConfigRequest req : sessionParams.getConfigurationRequests()) { - if (req instanceof ConfigRequestIpv4PcscfServer - && ((ConfigRequestIpv4PcscfServer) req).getAddress() != null) { - resultAddresses.add(((ConfigRequestIpv4PcscfServer) req).getAddress()); - } else if (req instanceof ConfigRequestIpv6PcscfServer - && ((ConfigRequestIpv6PcscfServer) req).getAddress() != null) { - resultAddresses.add(((ConfigRequestIpv6PcscfServer) req).getAddress()); - } - } - - assertEquals(expectedAddresses, resultAddresses); - } - @Test public void testBuildWithMinimumSet() throws Exception { IkeSessionParams sessionParams = createIkeParamsBuilderMinimum().build(); @@ -232,22 +248,39 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { assertFalse(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)); } - @Test - public void testBuildWithPsk() throws Exception { - IkeSessionParams sessionParams = - new IkeSessionParams.Builder(sContext) - .setNetwork(sTunNetwork) - .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress()) - .addSaProposal(SA_PROPOSAL) - .setLocalIdentification(LOCAL_ID) - .setRemoteIdentification(REMOTE_ID) - .setAuthPsk(IKE_PSK) - .build(); + /** + * Create a Builder that has minimum configurations to build an IkeSessionParams, except for + * authentication method. + */ + private IkeSessionParams.Builder createIkeParamsBuilderMinimumWithoutAuth() { + return new IkeSessionParams.Builder(sContext) + .setNetwork(sTunNetwork) + .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress()) + .addSaProposal(SA_PROPOSAL) + .setLocalIdentification(LOCAL_ID) + .setRemoteIdentification(REMOTE_ID); + } + + /** + * Verify the minimum configurations to build an IkeSessionParams, except for authentication + * method. + * + * @see #createIkeParamsBuilderMinimumWithoutAuth + */ + private void verifyIkeParamsMinimumWithoutAuth(IkeSessionParams sessionParams) { assertEquals(sTunNetwork, sessionParams.getNetwork()); assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname()); assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals()); assertEquals(LOCAL_ID, sessionParams.getLocalIdentification()); assertEquals(REMOTE_ID, sessionParams.getRemoteIdentification()); + } + + @Test + public void testBuildWithPsk() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimumWithoutAuth().setAuthPsk(IKE_PSK).build(); + + verifyIkeParamsMinimumWithoutAuth(sessionParams); IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); assertTrue(localConfig instanceof IkeAuthPskConfig); @@ -257,6 +290,57 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk()); } - // TODO(b/148689509): Add tests for building IkeSessionParams using EAP and - // digital-signature-based authentication + @Test + public void testBuildWithEap() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimumWithoutAuth() + .setAuthEap(mServerCaCert, EAP_ALL_METHODS_CONFIG) + .build(); + + verifyIkeParamsMinimumWithoutAuth(sessionParams); + + IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); + assertTrue(localConfig instanceof IkeAuthEapConfig); + assertEquals(EAP_ALL_METHODS_CONFIG, ((IkeAuthEapConfig) localConfig).getEapConfig()); + IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig(); + assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig); + assertEquals( + mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert()); + } + + @Test + public void testBuildWithEapOnlyAuth() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimumWithoutAuth() + .setAuthEap(mServerCaCert, EAP_ONLY_SAFE_METHODS_CONFIG) + .addIkeOption(IKE_OPTION_EAP_ONLY_AUTH) + .build(); + + assertTrue(sessionParams.hasIkeOption(IKE_OPTION_EAP_ONLY_AUTH)); + verifyIkeParamsMinimumWithoutAuth(sessionParams); + + IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); + assertTrue(localConfig instanceof IkeAuthEapConfig); + assertEquals(EAP_ONLY_SAFE_METHODS_CONFIG, ((IkeAuthEapConfig) localConfig).getEapConfig()); + IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig(); + assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig); + assertEquals( + mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert()); + } + + @Test + public void testThrowBuildEapOnlyAuthWithUnsafeMethod() throws Exception { + try { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimumWithoutAuth() + .setAuthEap(mServerCaCert, EAP_ALL_METHODS_CONFIG) + .addIkeOption(IKE_OPTION_EAP_ONLY_AUTH) + .build(); + fail("Expected to fail because EAP only unsafe method is proposed"); + } catch (IllegalArgumentException expected) { + } + } + + // TODO(b/148689509): Add tests for building IkeSessionParams using digital-signature-based + // authentication } diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java index 5f608df137..54c28e3a92 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java @@ -46,6 +46,12 @@ abstract class IkeTestBase { static final String LOCAL_HOSTNAME = "client.test.ike.android.net"; static final String REMOTE_HOSTNAME = "server.test.ike.android.net"; + static final int SUB_ID = 1; + static final byte[] EAP_IDENTITY = "test@android.net".getBytes(); + static final String NETWORK_NAME = "android.net"; + static final String EAP_MSCHAPV2_USERNAME = "username"; + static final String EAP_MSCHAPV2_PASSWORD = "password"; + static final Inet4Address IPV4_ADDRESS_LOCAL = (Inet4Address) (InetAddresses.parseNumericAddress("192.0.2.100")); static final Inet4Address IPV4_ADDRESS_REMOTE = From fe6c2565dac3d81d20294de01c221f7d125f7448 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Mon, 27 Apr 2020 03:28:29 +0000 Subject: [PATCH 4/4] Test configuring digital-signature-based auth Bug: 148689509 Test: atest CtsIkeTestCases Change-Id: Ieaceaadf116cb2885cbf22ae48579cec88268416 Merged-In: Ieaceaadf116cb2885cbf22ae48579cec88268416 (cherry picked from commit ca5515626ce03bde9116603485ec0e85f476ecd3) --- .../ipsec/assets/key/client-a-private-key.key | 28 +++++++++ .../ipsec/assets/pem/client-a-end-cert.pem | 21 +++++++ .../pem/client-a-intermediate-ca-one.pem | 21 +++++++ .../pem/client-a-intermediate-ca-two.pem | 21 +++++++ .../ipsec/ike/cts/IkeSessionParamsTest.java | 63 ++++++++++++++++++- 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 tests/cts/net/ipsec/assets/key/client-a-private-key.key create mode 100644 tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem create mode 100644 tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem create mode 100644 tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem diff --git a/tests/cts/net/ipsec/assets/key/client-a-private-key.key b/tests/cts/net/ipsec/assets/key/client-a-private-key.key new file mode 100644 index 0000000000..22736e98e0 --- /dev/null +++ b/tests/cts/net/ipsec/assets/key/client-a-private-key.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCv3CvrCGokJSWL +8ufg6u9LCW4EezztbktqpC0T+1m98+Ujb8/eJ0L2UaxZ9QBSBAqXxEoeZFBeoCXu +7ezUd5qUPfIhKLAkQTAyU/KgfhHh4i+MJK5ghPbGDE8r2gKUXOkM6M5//ZCpmu0K +Y/9uQL6D5bkxEaoWegEO+wSXm+hTTgKDtQKHvRibgdcZkcY0cA9JsLrC/nIkP+7i +pbBT+VTuV6gAnKIV0nq8zvI3A/Z3nAb5Gt0g3qaqs59StDT0QtuXzJkuZEo3XSrS +jon+8NjSNzqVbJj95B7+uiH+91VEbMtJYFz2MipKvJQDK7Zlxke7LxRj2xJfksJK +a92/ncxfAgMBAAECggEAQztaMvW5lm35J8LKsWs/5qEJRX9T8LWs8W0oqq36Riub +G2wgvR6ndAIPcSjAYZqX7iOl7m6NZ0+0kN63HxdGqovwKIskpAekBGmhpYftED1n +zh0r6UyMB3UnQ22KdOv8UOokIDxxdNX8728BdUYdT9Ggdkj5jLRB+VcwD0IUlNvo +zzTpURV9HEd87uiLqd4AAHXSI0lIHI5U43z24HI/J6/YbYHT3Rlh6CIa/LuwO6vL +gFkgqg0/oy6yJtjrHtzNVA67F0UaH62hR4YFgbC0d955SJnDidWOv/0j2DMpfdCc +9kFAcPwUSyykvUSLnGIKWSG4D+6gzIeAeUx4oO7kMQKBgQDVNRkX8AGTHyLg+NXf +spUWWcodwVioXl30Q7h6+4bt8OI61UbhQ7wX61wvJ1cySpa2KOYa2UdagQVhGhhL +ADu363R77uXF/jZgzVfmjjyJ2nfDqRgHWRTlSkuq/jCOQCz7VIPHRZg5WL/9D4ms +TAqMjpzqeMfFZI+w4/+xpcJIuQKBgQDTKBy+ZuerWrVT9icWKvLU58o5EVj/2yFy +GJvKm+wRAAX2WzjNnR4HVd4DmMREVz1BPYby0j5gqjvtDsxYYu39+NT7JvMioLLK +QPj+7k5geYgNqVgCxB1vP89RhY2X1RLrN9sTXOodgFPeXOQWNYITkGp3eQpx4nTJ ++K/al3oB1wKBgAjnc8nVIyuyxDEjE0OJYMKTM2a0uXAmqMPXxC+Wq5bqVXhhidlE +i+lv0eTCPtkB1nN7F8kNQ/aaps/cWCFhvBy9P5shagUvzbOTP9WIIS0cq53HRRKh +fMbqqGhWv05hjb9dUzeSR341n6cA7B3++v3Nwu3j52vt/DZF/1q68nc5AoGAS0SU +ImbKE/GsizZGLoe2sZ/CHN+LKwCwhlwxRGKaHmE0vuE7eUeVSaYZEo0lAPtb8WJ+ +NRYueASWgeTxgFwbW5mUScZTirdfo+rPFwhZVdhcYApKPgosN9i2DOgfVcz1BnWN +mPRY25U/0BaqkyQVruWeneG+kGPZn5kPDktKiVcCgYEAkzwU9vCGhm7ZVALvx/zR +wARz2zsL9ImBc0P4DK1ld8g90FEnHrEgeI9JEwz0zFHOCMLwlk7kG0Xev7vfjZ7G +xSqtQYOH33Qp6rtBOgdt8hSyDFvakvDl6bqhAw52gelO3MTpAB1+ZsfZ5gFx13Jf +idNFcaIrC52PtZIH7QCzdDY= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem b/tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem new file mode 100644 index 0000000000..e82da85c50 --- /dev/null +++ b/tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDaDCCAlCgAwIBAgIIcorRI3n29E4wDQYJKoZIhvcNAQELBQAwQTELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF3R3by5jYS50ZXN0LmFu +ZHJvaWQubmV0MB4XDTIwMDQxNDA1MDM0OVoXDTIzMDQxNDA1MDM0OVowRTELMAkG +A1UEBhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxJDAiBgNVBAMTG2NsaWVudC50ZXN0 +LmlrZS5hbmRyb2lkLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AK/cK+sIaiQlJYvy5+Dq70sJbgR7PO1uS2qkLRP7Wb3z5SNvz94nQvZRrFn1AFIE +CpfESh5kUF6gJe7t7NR3mpQ98iEosCRBMDJT8qB+EeHiL4wkrmCE9sYMTyvaApRc +6Qzozn/9kKma7Qpj/25AvoPluTERqhZ6AQ77BJeb6FNOAoO1Aoe9GJuB1xmRxjRw +D0mwusL+ciQ/7uKlsFP5VO5XqACcohXSerzO8jcD9necBvka3SDepqqzn1K0NPRC +25fMmS5kSjddKtKOif7w2NI3OpVsmP3kHv66If73VURsy0lgXPYyKkq8lAMrtmXG +R7svFGPbEl+Swkpr3b+dzF8CAwEAAaNgMF4wHwYDVR0jBBgwFoAUcqSu1uRYT/DL +bLoDNUz38nGvCKQwJgYDVR0RBB8wHYIbY2xpZW50LnRlc3QuaWtlLmFuZHJvaWQu +bmV0MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQCa53tK +I9RM9/MutZ5KNG2Gfs2cqaPyv8ZRhs90HDWZhkFVu7prywJAxOd2hxxHPsvgurio +4bKAxnT4EXevgz5YoCbj2TPIL9TdFYh59zZ97XXMxk+SRdypgF70M6ETqKPs3hDP +ZRMMoHvvYaqaPvp4StSBX9A44gSyjHxVYJkrjDZ0uffKg5lFL5IPvqfdmSRSpGab +SyGTP4OLTy0QiNV3pBsJGdl0h5BzuTPR9OTl4xgeqqBQy2bDjmfJBuiYyCSCkPi7 +T3ohDYCymhuSkuktHPNG1aKllUJaw0tuZuNydlgdAveXPYfM36uvK0sfd9qr9pAy +rmkYV2MAWguFeckh +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem new file mode 100644 index 0000000000..707e575bc3 --- /dev/null +++ b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDaDCCAlCgAwIBAgIIIbjMyRn2770wDQYJKoZIhvcNAQELBQAwQjELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxITAfBgNVBAMTGHJvb3QuY2EudGVzdC5h +bmRyb2lkLm5ldDAeFw0xOTA5MzAxODQzMThaFw0yNDA5MjgxODQzMThaMEExCzAJ +BgNVBAYTAlVTMRAwDgYDVQQKEwdBbmRyb2lkMSAwHgYDVQQDExdvbmUuY2EudGVz +dC5hbmRyb2lkLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNN +sRr5Z30rAEw2jrAh/BIekbEy/MvOucAr1w0lxH71p+ybRBx5Bj7G07UGXbL659gm +meMV6nabY4HjQXNMq22POiJBZj+U+rw34br6waljBttxCmmJac1VvgqNsSspXjRy +NbiVQdFjyKSX0NOPcEkwANk15mZbOgJBaYYc8jQCY2G/p8eARVBTLJCy8LEwEU6j +XRv/4eYST79qpBFc7gQQj2FLmh9oppDIvcIVBHwtd1tBoVuehRSud1o8vQRkl/HJ +Mrwp24nO5YYhmVNSFRtBpmWMSu1KknFUwkOebINUNsKXXHebVa7cP4XIQUL8mRT3 +5X9rFJFSQJE01S3NjNMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAQYwHQYDVR0OBBYEFHK3FIm7g8dxEIwK9zMAO8EWhRYxMB8GA1UdIwQY +MBaAFEmfqEeF14Nj91ekIpR+sVhCEoAaMA0GCSqGSIb3DQEBCwUAA4IBAQAeMlXT +TnxZo8oz0204gKZ63RzlgDpJ7SqA3qFG+pV+TiqGfSuVkXuIdOskjxJnA9VxUzrr +LdMTCn5e0FK6wCYjZ2GT/CD7oD3vSMkzGbLGNcNJhhDHUq8BOLPkPzz/rwQFPBSb +zr6hsiVXphEt/psGoN7Eu9blPeQaIwMfWnaufAwF664S/3dmCRbNMWSam1qzzz8q +jr0cDOIMa//ZIAcM16cvoBK6pFGnUmuoJYYRtfpY5MmfCWz0sCJxENIX/lxyhd7N +FdRALA1ZP3E//Tn2vQoeFjbKaAba527RE26HgHJ9zZDo1nn8J8J/YwYRJdBWM/3S +LYebNiMtcyB5nIkj +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem new file mode 100644 index 0000000000..39808f885e --- /dev/null +++ b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIIKWCREnNCs+wwDQYJKoZIhvcNAQELBQAwQTELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF29uZS5jYS50ZXN0LmFu +ZHJvaWQubmV0MB4XDTE5MDkzMDE4NDQwMloXDTI0MDkyODE4NDQwMlowQTELMAkG +A1UEBhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF3R3by5jYS50ZXN0 +LmFuZHJvaWQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLUa +RqkYl2m7lUmMnkooqO0DNNY1aN9r7mJc3ndYn5gjkpb3yLgOYPDNLcQerV6uWk/u +qKudNHed2dInGonl3oxwwv7++6oUvvtrSWLDZlRg16GsdIE1Y98DSMQWkSxevYy9 +Nh6FGTdlBFQVMpiMa8qHEkrOyKsy85yCW1sgzlpGTIBwbDAqYtwe3rgbwyHwUtfy +0EU++DBcR4ll/pDqB0OQtW5E3AOq2GH1iaGeFLKSUQ5KAbdI8y4/b8IkSDffvxcc +kXig7S54aLrNlL/ZjQ+H4Chgjj2A5wMucd81+Fb60Udej73ICL9PpMPnXQ1+BVYd +MJ/txjLNmrOJG9yEHQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQUcqSu1uRYT/DLbLoDNUz38nGvCKQwHwYDVR0jBBgw +FoAUcrcUibuDx3EQjAr3MwA7wRaFFjEwDQYJKoZIhvcNAQELBQADggEBADY461GT +Rw0dGnD07xaGJcI0i0pV+WnGSrl1s1PAIdMYihJAqYnh10fXbFXLm2WMWVmv/pxs +FI/xDJno+pd4mCa/sIhm63ar/Nv+lFQmcpIlvSlKnhhV4SLNBeqbVhPBGTCHfrG4 +aIyCwm1KJsnkWbf03crhSskR/2CXIjX6lcAy7K3fE2u1ELpAdH0kMJR7VXkLFLUm +gqe9YCluR0weMpe2sCaOGzdVzQSmMMCzGP5cxeFR5U6K40kMOpiW11JNmQ06xI/m +YVkMNwoiV/ITT0/C/g9FxJmkO0mVSLEqxaLS/hNiQNDlroVM0rbxhzviXLI3R3AO +50VvlOQYGxWed/I= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java index 532be675aa..6fc7cb3634 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java @@ -19,6 +19,7 @@ package android.net.ipsec.ike.cts; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH; import static android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig; +import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig; import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig; import static android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig; import static android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig; @@ -51,9 +52,12 @@ import org.junit.runner.RunWith; import java.net.InetAddress; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -94,10 +98,20 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { createEapOnlySafeMethodsBuilder().build(); private X509Certificate mServerCaCert; + private X509Certificate mClientEndCert; + private X509Certificate mClientIntermediateCaCertOne; + private X509Certificate mClientIntermediateCaCertTwo; + private RSAPrivateKey mClientPrivateKey; @Before public void setUp() throws Exception { mServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem"); + mClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem"); + mClientIntermediateCaCertOne = + CertUtils.createCertFromPemFile("client-a-intermediate-ca-one.pem"); + mClientIntermediateCaCertTwo = + CertUtils.createCertFromPemFile("client-a-intermediate-ca-two.pem"); + mClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key"); } private static EapSessionConfig.Builder createEapOnlySafeMethodsBuilder() { @@ -341,6 +355,51 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase { } } - // TODO(b/148689509): Add tests for building IkeSessionParams using digital-signature-based - // authentication + @Test + public void testBuildWithDigitalSignature() throws Exception { + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimumWithoutAuth() + .setAuthDigitalSignature(mServerCaCert, mClientEndCert, mClientPrivateKey) + .build(); + + verifyIkeParamsMinimumWithoutAuth(sessionParams); + + IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); + assertTrue(localConfig instanceof IkeAuthDigitalSignLocalConfig); + IkeAuthDigitalSignLocalConfig localSignConfig = (IkeAuthDigitalSignLocalConfig) localConfig; + assertEquals(mClientEndCert, localSignConfig.getClientEndCertificate()); + assertEquals(Collections.EMPTY_LIST, localSignConfig.getIntermediateCertificates()); + assertEquals(mClientPrivateKey, localSignConfig.getPrivateKey()); + + IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig(); + assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig); + assertEquals( + mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert()); + } + + @Test + public void testBuildWithDigitalSignatureAndIntermediateCerts() throws Exception { + List intermediateCerts = + Arrays.asList(mClientIntermediateCaCertOne, mClientIntermediateCaCertTwo); + + IkeSessionParams sessionParams = + createIkeParamsBuilderMinimumWithoutAuth() + .setAuthDigitalSignature( + mServerCaCert, mClientEndCert, intermediateCerts, mClientPrivateKey) + .build(); + + verifyIkeParamsMinimumWithoutAuth(sessionParams); + + IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig(); + assertTrue(localConfig instanceof IkeAuthDigitalSignLocalConfig); + IkeAuthDigitalSignLocalConfig localSignConfig = (IkeAuthDigitalSignLocalConfig) localConfig; + assertEquals(mClientEndCert, localSignConfig.getClientEndCertificate()); + assertEquals(intermediateCerts, localSignConfig.getIntermediateCertificates()); + assertEquals(mClientPrivateKey, localSignConfig.getPrivateKey()); + + IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig(); + assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig); + assertEquals( + mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert()); + } }