blob: 9f3ad2fb44790e74abb8b49bb3640a7f9cbc1d54 [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.test.AndroidTestCase;
import java.net.ServerSocket;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;
import android.system.OsConstants;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
public class IpSecManagerTest extends AndroidTestCase {
private static final String TAG = IpSecManagerTest.class.getSimpleName();
private IpSecManager mISM;
private ConnectivityManager mCM;
private static InetAddress IpAddress(String addrString) {
try {
return InetAddress.getByName(addrString);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Invalid IP address: " + e);
}
}
private static final InetAddress GOOGLE_DNS_4 = IpAddress("8.8.8.8");
private static final InetAddress GOOGLE_DNS_6 = IpAddress("2001:4860:4860::8888");
private static final InetAddress LOOPBACK_4 = IpAddress("127.0.0.1");
private static final InetAddress[] GOOGLE_DNS_LIST =
new InetAddress[] {GOOGLE_DNS_4, GOOGLE_DNS_6};
private static final int DROID_SPI = 0xD1201D;
private static final int MAX_PORT_BIND_ATTEMPTS = 10;
private static final byte[] CRYPT_KEY = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};
private static final byte[] AUTH_KEY = {
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
};
private static final String IPV4_LOOPBACK = "127.0.0.1";
private static final String IPV6_LOOPBACK = "::1";
protected void setUp() throws Exception {
super.setUp();
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
}
/*
* Allocate a random SPI
* Allocate a specific SPI using previous randomly created SPI value
* Realloc the same SPI that was specifically created (expect SpiUnavailable)
* Close SPIs
*/
public void testAllocSpi() throws Exception {
for (InetAddress addr : GOOGLE_DNS_LIST) {
IpSecManager.SecurityParameterIndex randomSpi = null, droidSpi = null;
randomSpi = mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, addr);
assertTrue(
"Failed to receive a valid SPI",
randomSpi.getSpi() != IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
droidSpi =
mISM.reserveSecurityParameterIndex(
IpSecTransform.DIRECTION_IN, addr, DROID_SPI);
assertTrue(
"Failed to allocate specified SPI, " + DROID_SPI,
droidSpi.getSpi() == DROID_SPI);
try {
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_IN, addr, DROID_SPI);
fail("Duplicate SPI was allowed to be created");
} catch (IpSecManager.SpiUnavailableException expected) {
// This is a success case because we expect a dupe SPI to throw
}
randomSpi.close();
droidSpi.close();
}
}
private byte[] getAuthKey(int bitLength) {
return Arrays.copyOf(AUTH_KEY, bitLength / 8);
}
private static int getDomain(InetAddress address) {
int domain;
if (address instanceof Inet6Address)
domain = OsConstants.AF_INET6;
else
domain = OsConstants.AF_INET;
return domain;
}
/** This function finds an available port */
private static int findUnusedPort() throws Exception {
// Get an available port.
DatagramSocket s = new DatagramSocket();
int port = s.getLocalPort();
s.close();
return port;
}
private static FileDescriptor getBoundUdpSocket(InetAddress address) throws Exception {
FileDescriptor sock =
Os.socket(getDomain(address), OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP);
for (int i = 0; i < MAX_PORT_BIND_ATTEMPTS; i++) {
try {
int port = findUnusedPort();
Os.bind(sock, address, port);
break;
} catch (ErrnoException e) {
// Someone claimed the port since we called findUnusedPort.
if (e.errno == OsConstants.EADDRINUSE) {
if (i == MAX_PORT_BIND_ATTEMPTS - 1) {
fail("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
}
continue;
}
throw e.rethrowAsIOException();
}
}
return sock;
}
private void checkUnconnectedUdp(IpSecTransform transform, InetAddress local) throws Exception {
FileDescriptor udpSocket = getBoundUdpSocket(local);
int localPort = getPort(udpSocket);
mISM.applyTransportModeTransform(udpSocket, transform);
byte[] data = new String("Best test data ever! Port: " + localPort).getBytes("UTF-8");
byte[] in = new byte[data.length];
Os.sendto(udpSocket, data, 0, data.length, 0, local, localPort);
Os.read(udpSocket, in, 0, in.length);
assertTrue("Encapsulated data did not match.", Arrays.equals(data, in));
mISM.removeTransportModeTransform(udpSocket, transform);
Os.close(udpSocket);
}
private void checkTcp(IpSecTransform transform, InetAddress local) throws Exception {
FileDescriptor server =
Os.socket(getDomain(local), OsConstants.SOCK_STREAM, IPPROTO_TCP);
FileDescriptor client =
Os.socket(getDomain(local), OsConstants.SOCK_STREAM, IPPROTO_TCP);
Os.bind(server, local, 0);
int port = ((InetSocketAddress) Os.getsockname(server)).getPort();
mISM.applyTransportModeTransform(client, transform);
mISM.applyTransportModeTransform(server, transform);
Os.listen(server, 10);
Os.connect(client, local, port);
FileDescriptor accepted = Os.accept(server, null);
mISM.applyTransportModeTransform(accepted, transform);
byte[] data = new String("Best test data ever! Port: " + port).getBytes("UTF-8");
byte[] in = new byte[data.length];
Os.write(client, data, 0, data.length);
Os.read(accepted, in, 0, in.length);
assertTrue("Client-to-server encrypted data did not match.", Arrays.equals(data, in));
data = new String("Best test data received !!!").getBytes("UTF-8");
in = new byte[data.length];
Os.write(accepted, data, 0, data.length);
Os.read(client, in, 0, in.length);
assertTrue("Server-to-client encrypted data did not match.", Arrays.equals(data, in));
mISM.removeTransportModeTransform(server, transform);
mISM.removeTransportModeTransform(client, transform);
mISM.removeTransportModeTransform(accepted, transform);
Os.close(server);
Os.close(client);
Os.close(accepted);
}
/*
* Alloc outbound SPI
* Alloc inbound SPI
* Create transport mode transform
* open socket
* apply transform to socket
* send data on socket
* release transform
* send data (expect exception)
*/
public void testCreateTransform() throws Exception {
InetAddress local = LOOPBACK_4;
IpSecManager.SecurityParameterIndex outSpi =
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
IpSecManager.SecurityParameterIndex inSpi =
mISM.reserveSecurityParameterIndex(
IpSecTransform.DIRECTION_IN, local, outSpi.getSpi());
IpSecTransform transform =
new IpSecTransform.Builder(mContext)
.setSpi(IpSecTransform.DIRECTION_OUT, outSpi)
.setEncryption(
IpSecTransform.DIRECTION_OUT,
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setAuthentication(
IpSecTransform.DIRECTION_OUT,
new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256,
AUTH_KEY,
AUTH_KEY.length * 8))
.setSpi(IpSecTransform.DIRECTION_IN, inSpi)
.setEncryption(
IpSecTransform.DIRECTION_IN,
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setAuthentication(
IpSecTransform.DIRECTION_IN,
new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256,
AUTH_KEY,
CRYPT_KEY.length * 8))
.buildTransportModeTransform(local);
// Bind localSocket to a random available port.
DatagramSocket localSocket = new DatagramSocket(0);
int localPort = localSocket.getLocalPort();
localSocket.setSoTimeout(500);
ParcelFileDescriptor pin = ParcelFileDescriptor.fromDatagramSocket(localSocket);
FileDescriptor udpSocket = pin.getFileDescriptor();
mISM.applyTransportModeTransform(udpSocket, transform);
byte[] data = new String("Best test data ever!").getBytes("UTF-8");
byte[] in = new byte[data.length];
Os.sendto(udpSocket, data, 0, data.length, 0, local, localPort);
Os.read(udpSocket, in, 0, in.length);
assertTrue("Encapsulated data did not match.", Arrays.equals(data, in));
mISM.removeTransportModeTransform(udpSocket, transform);
Os.close(udpSocket);
transform.close();
}
public void checkTransform(int protocol, String localAddress,
IpSecAlgorithm crypt, IpSecAlgorithm auth) throws Exception {
InetAddress local = InetAddress.getByName(localAddress);
IpSecManager.SecurityParameterIndex outSpi =
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
IpSecManager.SecurityParameterIndex inSpi =
mISM.reserveSecurityParameterIndex(
IpSecTransform.DIRECTION_IN, local, outSpi.getSpi());
IpSecTransform transform =
new IpSecTransform.Builder(mContext)
.setSpi(IpSecTransform.DIRECTION_OUT, outSpi)
.setEncryption(IpSecTransform.DIRECTION_OUT,crypt)
.setAuthentication(IpSecTransform.DIRECTION_OUT, auth)
.setSpi(IpSecTransform.DIRECTION_IN, inSpi)
.setEncryption(IpSecTransform.DIRECTION_IN, crypt)
.setAuthentication(IpSecTransform.DIRECTION_IN, auth)
.buildTransportModeTransform(local);
if (protocol == IPPROTO_TCP) {
checkTcp(transform, local);
} else if (protocol == IPPROTO_UDP) {
// TODO: Also check connected udp.
checkUnconnectedUdp(transform, local);
} else {
throw new IllegalArgumentException("Invalid protocol");
}
transform.close();
outSpi.close();
inSpi.close();
}
public void testAesCbcHmacMd5Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacMd5Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacMd5Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacMd5Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_MD5, getAuthKey(128), 96);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha1Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha1Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha1Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha1Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA1, getAuthKey(160), 96);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha256Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha256Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha256Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha256Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, getAuthKey(256), 128);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha384Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha384Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha384Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha384Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA384, getAuthKey(384), 192);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha512Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha512Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha512Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth);
}
public void testAesCbcHmacSha512Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA512, getAuthKey(512), 256);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth);
}
public void testOpenUdpEncapSocketSpecificPort() throws Exception {
IpSecManager.UdpEncapsulationSocket encapSocket = null;
int port = -1;
for (int i = 0; i < MAX_PORT_BIND_ATTEMPTS; i++) {
try {
port = findUnusedPort();
encapSocket = mISM.openUdpEncapsulationSocket(port);
break;
} catch (ErrnoException e) {
if (e.errno == OsConstants.EADDRINUSE) {
// Someone claimed the port since we called findUnusedPort.
continue;
}
throw e;
} finally {
if (encapSocket != null) {
encapSocket.close();
}
}
}
if (encapSocket == null) {
fail("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
}
assertTrue("Returned invalid port", encapSocket.getPort() == port);
}
public void testOpenUdpEncapSocketRandomPort() throws Exception {
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
assertTrue("Returned invalid port", encapSocket.getPort() != 0);
}
}
public void testUdpEncapsulation() throws Exception {
InetAddress local = LOOPBACK_4;
// TODO: Refactor to make this more representative of a normal application use case. (use
// separate sockets for inbound and outbound)
// Create SPIs, UDP encap socket
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
IpSecManager.SecurityParameterIndex outSpi =
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
IpSecManager.SecurityParameterIndex inSpi =
mISM.reserveSecurityParameterIndex(
IpSecTransform.DIRECTION_IN, local, outSpi.getSpi());
IpSecTransform transform =
buildIpSecTransform(mContext, inSpi, outSpi, encapSocket, local)) {
// Create user socket, apply transform to it
FileDescriptor udpSocket = null;
try {
udpSocket = getBoundUdpSocket(local);
int port = getPort(udpSocket);
mISM.applyTransportModeTransform(udpSocket, transform);
// Send an ESP packet from this socket to itself. Since the inbound and
// outbound transforms match, we should receive the data we sent.
byte[] data = new String("IPSec UDP-encap-ESP test data").getBytes("UTF-8");
Os.sendto(udpSocket, data, 0, data.length, 0, local, port);
byte[] in = new byte[data.length];
Os.read(udpSocket, in, 0, in.length);
assertTrue("Encapsulated data did not match.", Arrays.equals(data, in));
// Send an IKE packet from this socket to itself. IKE packets (SPI of 0)
// are not transformed in any way, and should be sent in the clear
// We expect this to work too (no inbound transforms)
final byte[] header = new byte[] {0, 0, 0, 0};
final String message = "Sample IKE Packet";
data = (new String(header) + message).getBytes("UTF-8");
Os.sendto(
encapSocket.getSocket(),
data,
0,
data.length,
0,
local,
encapSocket.getPort());
in = new byte[data.length];
Os.read(encapSocket.getSocket(), in, 0, in.length);
assertTrue(
"Encap socket was unable to send/receive IKE data",
Arrays.equals(data, in));
mISM.removeTransportModeTransform(udpSocket, transform);
} finally {
if (udpSocket != null) {
Os.close(udpSocket);
}
}
}
}
public void testIke() throws Exception {
InetAddress local = LOOPBACK_4;
// TODO: Refactor to make this more representative of a normal application use case. (use
// separate sockets for inbound and outbound)
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
IpSecManager.SecurityParameterIndex outSpi =
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
IpSecManager.SecurityParameterIndex inSpi =
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_IN, local);
IpSecTransform transform =
buildIpSecTransform(mContext, inSpi, outSpi, encapSocket, local)) {
// Create user socket, apply transform to it
FileDescriptor sock = null;
try {
sock = getBoundUdpSocket(local);
int port = getPort(sock);
mISM.applyTransportModeTransform(sock, transform);
// TODO: Find a way to set a timeout on the socket, and assert the ESP packet
// doesn't make it through. Setting sockopts currently throws EPERM (possibly
// because it is owned by a different UID).
// Send ESP packet from our socket to the encap socket. The SPIs do not
// match, and we should expect this packet to be dropped.
byte[] header = new byte[] {1, 1, 1, 1};
String message = "Sample ESP Packet";
byte[] data = (new String(header) + message).getBytes("UTF-8");
Os.sendto(sock, data, 0, data.length, 0, local, encapSocket.getPort());
// Send IKE packet from the encap socket to itself. Since IKE is not
// transformed in any way, this should succeed.
header = new byte[] {0, 0, 0, 0};
message = "Sample IKE Packet";
data = (new String(header) + message).getBytes("UTF-8");
Os.sendto(
encapSocket.getSocket(),
data,
0,
data.length,
0,
local,
encapSocket.getPort());
// ESP data should be dropped, due to different input SPI (as opposed to being
// readable from the encapSocket)
// Thus, only IKE data should be received from the socket.
// If the first four bytes are zero, assume non-ESP (IKE) traffic.
// Expect an nulled out SPI just as we sent out, without being modified.
byte[] in = new byte[4];
in[0] = 1; // Make sure the array has to be overwritten to pass
Os.read(encapSocket.getSocket(), in, 0, in.length);
assertTrue(
"Encap socket received UDP-encap-ESP data despite invalid SPIs",
Arrays.equals(header, in));
mISM.removeTransportModeTransform(sock, transform);
} finally {
if (sock != null) {
Os.close(sock);
}
}
}
}
private static IpSecTransform buildIpSecTransform(
Context mContext,
IpSecManager.SecurityParameterIndex inSpi,
IpSecManager.SecurityParameterIndex outSpi,
IpSecManager.UdpEncapsulationSocket encapSocket,
InetAddress remoteAddr)
throws Exception {
return new IpSecTransform.Builder(mContext)
.setSpi(IpSecTransform.DIRECTION_IN, inSpi)
.setSpi(IpSecTransform.DIRECTION_OUT, outSpi)
.setEncryption(
IpSecTransform.DIRECTION_IN,
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setEncryption(
IpSecTransform.DIRECTION_OUT,
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setAuthentication(
IpSecTransform.DIRECTION_IN,
new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4))
.setAuthentication(
IpSecTransform.DIRECTION_OUT,
new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4))
.setIpv4Encapsulation(encapSocket, encapSocket.getPort())
.buildTransportModeTransform(remoteAddr);
}
private static int getPort(FileDescriptor sock) throws Exception {
return ((InetSocketAddress) Os.getsockname(sock)).getPort();
}
}