| /* |
| * 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.allocateSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, addr); |
| assertTrue( |
| "Failed to receive a valid SPI", |
| randomSpi.getSpi() != IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); |
| |
| droidSpi = |
| mISM.allocateSecurityParameterIndex( |
| IpSecTransform.DIRECTION_IN, addr, DROID_SPI); |
| assertTrue( |
| "Failed to allocate specified SPI, " + DROID_SPI, |
| droidSpi.getSpi() == DROID_SPI); |
| |
| try { |
| mISM.allocateSecurityParameterIndex(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.allocateSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local); |
| |
| IpSecManager.SecurityParameterIndex inSpi = |
| mISM.allocateSecurityParameterIndex( |
| 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.allocateSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local); |
| |
| IpSecManager.SecurityParameterIndex inSpi = |
| mISM.allocateSecurityParameterIndex( |
| 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.allocateSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local); |
| IpSecManager.SecurityParameterIndex inSpi = |
| mISM.allocateSecurityParameterIndex( |
| 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.allocateSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local); |
| IpSecManager.SecurityParameterIndex inSpi = |
| mISM.allocateSecurityParameterIndex(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(); |
| } |
| } |