diff options
| author | 2017-03-01 14:05:47 +0000 | |
|---|---|---|
| committer | 2017-03-01 14:05:48 +0000 | |
| commit | 7645fa281649c46149d5a78b9fe99898d287eb4c (patch) | |
| tree | f1e5a421fc0f807b8caf8768c5bddbe060098604 | |
| parent | 355dbae680994002c48d7a66cb276a65393ecbbb (diff) | |
| parent | 330e1089da80cddcd68758512370d217b19f8890 (diff) | |
Merge "Add API Surface for creating IpSec Transforms"
| -rw-r--r-- | api/current.txt | 63 | ||||
| -rw-r--r-- | api/system-current.txt | 68 | ||||
| -rw-r--r-- | api/test-current.txt | 63 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 13 | ||||
| -rw-r--r-- | core/java/android/net/IpSecAlgorithm.java | 181 | ||||
| -rw-r--r-- | core/java/android/net/IpSecConfig.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/net/IpSecConfig.java | 197 | ||||
| -rw-r--r-- | core/java/android/net/IpSecManager.java | 379 | ||||
| -rw-r--r-- | core/java/android/net/IpSecTransform.java | 471 |
9 files changed, 1455 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index 159b025008ae..f3cb3fc16f36 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8188,6 +8188,7 @@ package android.content { field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties"; field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method"; field public static final java.lang.String INPUT_SERVICE = "input"; + field public static final java.lang.String IPSEC_SERVICE = "ipsec"; field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler"; field public static final java.lang.String KEYGUARD_SERVICE = "keyguard"; field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps"; @@ -23724,6 +23725,68 @@ package android.net { field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; } + public final class IpSecAlgorithm implements android.os.Parcelable { + ctor public IpSecAlgorithm(java.lang.String, byte[]); + ctor public IpSecAlgorithm(java.lang.String, byte[], int); + method public int describeContents(); + method public byte[] getKey(); + method public java.lang.String getName(); + method public int getTruncationLengthBits(); + method public void writeToParcel(android.os.Parcel, int); + field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)"; + field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)"; + field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR; + } + + public final class IpSecManager { + method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException; + method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform); + method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform); + method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 + } + + public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { + } + + public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable { + method public void close(); + method public int getSpi(); + } + + public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException { + method public int getSpi(); + } + + public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { + method public void close(); + method public int getPort(); + method public java.io.FileDescriptor getSocket(); + } + + public final class IpSecTransform implements java.lang.AutoCloseable { + method public void close(); + field public static final int DIRECTION_IN = 0; // 0x0 + field public static final int DIRECTION_OUT = 1; // 0x1 + } + + public static class IpSecTransform.Builder { + ctor public IpSecTransform.Builder(android.content.Context); + method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); + method public android.net.IpSecTransform.Builder setSpi(int, int); + method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); + } + public class LinkAddress implements android.os.Parcelable { method public int describeContents(); method public java.net.InetAddress getAddress(); diff --git a/api/system-current.txt b/api/system-current.txt index deeb6794d193..27a8b9ecf177 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8522,6 +8522,7 @@ package android.content { field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control"; field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method"; field public static final java.lang.String INPUT_SERVICE = "input"; + field public static final java.lang.String IPSEC_SERVICE = "ipsec"; field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler"; field public static final java.lang.String KEYGUARD_SERVICE = "keyguard"; field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps"; @@ -25561,6 +25562,73 @@ package android.net { field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; } + public final class IpSecAlgorithm implements android.os.Parcelable { + ctor public IpSecAlgorithm(java.lang.String, byte[]); + ctor public IpSecAlgorithm(java.lang.String, byte[], int); + method public int describeContents(); + method public byte[] getKey(); + method public java.lang.String getName(); + method public int getTruncationLengthBits(); + method public void writeToParcel(android.os.Parcel, int); + field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)"; + field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)"; + field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR; + } + + public final class IpSecManager { + method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTunnelModeTransform(android.net.Network, android.net.IpSecTransform); + method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform); + method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform); + method public void removeTunnelModeTransform(android.net.Network, android.net.IpSecTransform); + method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 + } + + public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { + } + + public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable { + method public void close(); + method public int getSpi(); + } + + public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException { + method public int getSpi(); + } + + public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { + method public void close(); + method public int getPort(); + method public java.io.FileDescriptor getSocket(); + } + + public final class IpSecTransform implements java.lang.AutoCloseable { + method public void close(); + field public static final int DIRECTION_IN = 0; // 0x0 + field public static final int DIRECTION_OUT = 1; // 0x1 + } + + public static class IpSecTransform.Builder { + ctor public IpSecTransform.Builder(android.content.Context); + method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, java.net.InetAddress); + method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); + method public android.net.IpSecTransform.Builder setNattKeepalive(int); + method public android.net.IpSecTransform.Builder setSpi(int, int); + method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); + method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network); + } + public class LinkAddress implements android.os.Parcelable { method public int describeContents(); method public java.net.InetAddress getAddress(); diff --git a/api/test-current.txt b/api/test-current.txt index 694415f4ff71..c7f7fd1b0d9f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -8199,6 +8199,7 @@ package android.content { field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties"; field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method"; field public static final java.lang.String INPUT_SERVICE = "input"; + field public static final java.lang.String IPSEC_SERVICE = "ipsec"; field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler"; field public static final java.lang.String KEYGUARD_SERVICE = "keyguard"; field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps"; @@ -23797,6 +23798,68 @@ package android.net { field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; } + public final class IpSecAlgorithm implements android.os.Parcelable { + ctor public IpSecAlgorithm(java.lang.String, byte[]); + ctor public IpSecAlgorithm(java.lang.String, byte[], int); + method public int describeContents(); + method public byte[] getKey(); + method public java.lang.String getName(); + method public int getTruncationLengthBits(); + method public void writeToParcel(android.os.Parcel, int); + field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)"; + field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)"; + field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)"; + field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR; + } + + public final class IpSecManager { + method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException; + method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform); + method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform); + method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 + } + + public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { + } + + public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable { + method public void close(); + method public int getSpi(); + } + + public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException { + method public int getSpi(); + } + + public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { + method public void close(); + method public int getPort(); + method public java.io.FileDescriptor getSocket(); + } + + public final class IpSecTransform implements java.lang.AutoCloseable { + method public void close(); + field public static final int DIRECTION_IN = 0; // 0x0 + field public static final int DIRECTION_OUT = 1; // 0x1 + } + + public static class IpSecTransform.Builder { + ctor public IpSecTransform.Builder(android.content.Context); + method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); + method public android.net.IpSecTransform.Builder setSpi(int, int); + method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); + } + public class LinkAddress implements android.os.Parcelable { method public int describeContents(); method public java.net.InetAddress getAddress(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 02b86fe33f3b..46d383597e79 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2662,6 +2662,7 @@ public abstract class Context { VIBRATOR_SERVICE, //@hide: STATUS_BAR_SERVICE, CONNECTIVITY_SERVICE, + IPSEC_SERVICE, //@hide: UPDATE_LOCK_SERVICE, //@hide: NETWORKMANAGEMENT_SERVICE, NETWORK_STATS_SERVICE, @@ -2763,6 +2764,9 @@ public abstract class Context { * <dt> {@link #CONNECTIVITY_SERVICE} ("connection") * <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for * handling management of network connections. + * <dt> {@link #IPSEC_SERVICE} ("ipsec") + * <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on + * sockets and networks. * <dt> {@link #WIFI_SERVICE} ("wifi") * <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi * connectivity. On releases before NYC, it should only be obtained from an application @@ -3093,6 +3097,15 @@ public abstract class Context { public static final String CONNECTIVITY_SERVICE = "connectivity"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.net.IpSecManager} for encrypting Sockets or Networks with + * IPSec. + * + * @see #getSystemService + */ + public static final String IPSEC_SERVICE = "ipsec"; + + /** * Use with {@link #getSystemService} to retrieve a {@link * android.os.IUpdateLock} for managing runtime sequences that * must not be interrupted by headless OTA application or similar. diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java new file mode 100644 index 000000000000..da5cb37961ab --- /dev/null +++ b/core/java/android/net/IpSecAlgorithm.java @@ -0,0 +1,181 @@ +/* + * 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; + +import android.annotation.StringDef; +import android.os.Parcel; +import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to + * RFC 4301. + */ +public final class IpSecAlgorithm implements Parcelable { + + /** + * AES-CBC Encryption/Ciphering Algorithm. + * + * <p>Valid lengths for this key are {128, 192, 256}. + */ + public static final String ALGO_CRYPT_AES_CBC = "cbc(aes)"; + + /** + * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new + * applications and is provided for legacy compatibility with 3gpp infrastructure. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128. + */ + public static final String ALGO_AUTH_HMAC_MD5 = "hmac(md5)"; + + /** + * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160. + */ + public static final String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)"; + + /** + * SHA256 HMAC Authentication/Integrity Algorithm. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 256. + */ + public static final String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)"; + + /** + * SHA384 HMAC Authentication/Integrity Algorithm. + * + * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384. + */ + public static final String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)"; + /** + * SHA512 HMAC Authentication/Integrity Algorithm + * + * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512. + */ + public static final String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)"; + + /** @hide */ + @StringDef({ + ALGO_CRYPT_AES_CBC, + ALGO_AUTH_HMAC_MD5, + ALGO_AUTH_HMAC_SHA1, + ALGO_AUTH_HMAC_SHA256, + ALGO_AUTH_HMAC_SHA512 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AlgorithmName {} + + private final String mName; + private final byte[] mKey; + private final int mTruncLenBits; + + /** + * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the + * algorithm + * + * @param algorithm type for IpSec. + * @param key non-null Key padded to a multiple of 8 bits. + */ + public IpSecAlgorithm(String algorithm, byte[] key) { + this(algorithm, key, key.length * 8); + } + + /** + * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the + * algorithm + * + * @param algoName precise name of the algorithm to be used. + * @param key non-null Key padded to a multiple of 8 bits. + * @param truncLenBits the number of bits of output hash to use; only meaningful for + * Authentication. + */ + public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) { + if (!isTruncationLengthValid(algoName, truncLenBits)) { + throw new IllegalArgumentException("Unknown algorithm or invalid length"); + } + mName = algoName; + mKey = key.clone(); + mTruncLenBits = Math.min(truncLenBits, key.length * 8); + } + + /** Retrieve the algorithm name */ + public String getName() { + return mName; + } + + /** Retrieve the key for this algorithm */ + public byte[] getKey() { + return mKey.clone(); + } + + /** + * Retrieve the truncation length, in bits, for the key in this algo. By default this will be + * the length in bits of the key. + */ + public int getTruncationLengthBits() { + return mTruncLenBits; + } + + /* Parcelable Implementation */ + public int describeContents() { + return 0; + } + + /** Write to parcel */ + public void writeToParcel(Parcel out, int flags) { + out.writeString(mName); + out.writeByteArray(mKey); + out.writeInt(mTruncLenBits); + } + + /** Parcelable Creator */ + public static final Parcelable.Creator<IpSecAlgorithm> CREATOR = + new Parcelable.Creator<IpSecAlgorithm>() { + public IpSecAlgorithm createFromParcel(Parcel in) { + return new IpSecAlgorithm(in); + } + + public IpSecAlgorithm[] newArray(int size) { + return new IpSecAlgorithm[size]; + } + }; + + private IpSecAlgorithm(Parcel in) { + mName = in.readString(); + mKey = in.createByteArray(); + mTruncLenBits = in.readInt(); + } + + private static boolean isTruncationLengthValid(String algo, int truncLenBits) { + switch (algo) { + case ALGO_AUTH_HMAC_MD5: + return (truncLenBits >= 96 && truncLenBits <= 128); + case ALGO_AUTH_HMAC_SHA1: + return (truncLenBits >= 96 && truncLenBits <= 160); + case ALGO_AUTH_HMAC_SHA256: + return (truncLenBits >= 96 && truncLenBits <= 256); + case ALGO_AUTH_HMAC_SHA384: + return (truncLenBits >= 192 && truncLenBits <= 384); + case ALGO_AUTH_HMAC_SHA512: + return (truncLenBits >= 256 && truncLenBits <= 512); + default: + return false; + } + } +}; diff --git a/core/java/android/net/IpSecConfig.aidl b/core/java/android/net/IpSecConfig.aidl new file mode 100644 index 000000000000..eaefca74d3a0 --- /dev/null +++ b/core/java/android/net/IpSecConfig.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +/** @hide */ +parcelable IpSecConfig; diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java new file mode 100644 index 000000000000..b58bf421a86a --- /dev/null +++ b/core/java/android/net/IpSecConfig.java @@ -0,0 +1,197 @@ +/* + * 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; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** @hide */ +public final class IpSecConfig implements Parcelable { + private static final String TAG = IpSecConfig.class.getSimpleName(); + + //MODE_TRANSPORT or MODE_TUNNEL + int mode; + + // For tunnel mode + InetAddress localAddress; + + InetAddress remoteAddress; + + // Limit selection by network interface + Network network; + + public static class Flow { + // Minimum requirements for identifying a transform + // SPI identifying the IPsec flow in packet processing + // and a remote IP address + int spi; + + // Encryption Algorithm + IpSecAlgorithm encryptionAlgo; + + // Authentication Algorithm + IpSecAlgorithm authenticationAlgo; + } + + Flow[] flow = new Flow[2]; + + // For tunnel mode IPv4 UDP Encapsulation + // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE + int encapType; + int encapLocalPort; + int encapRemotePort; + + // An optional protocol to match with the selector + int selectorProto; + + // A bitmask of FEATURE_* indicating which of the fields + // of this class are valid. + long features; + + // An interval, in seconds between the NattKeepalive packets + int nattKeepaliveInterval; + + public InetAddress getLocalIp() { + return localAddress; + } + + public int getSpi(int direction) { + return flow[direction].spi; + } + + public InetAddress getRemoteIp() { + return remoteAddress; + } + + public IpSecAlgorithm getEncryptionAlgo(int direction) { + return flow[direction].encryptionAlgo; + } + + public IpSecAlgorithm getAuthenticationAlgo(int direction) { + return flow[direction].authenticationAlgo; + } + + Network getNetwork() { + return network; + } + + public int getEncapType() { + return encapType; + } + + public int getEncapLocalPort() { + return encapLocalPort; + } + + public int getEncapRemotePort() { + return encapRemotePort; + } + + public int getSelectorProto() { + return selectorProto; + } + + int getNattKeepaliveInterval() { + return nattKeepaliveInterval; + } + + public boolean hasProperty(int featureBits) { + return (features & featureBits) == featureBits; + } + + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(features); + // TODO: Use a byte array or other better method for storing IPs that can also include scope + out.writeString((localAddress != null) ? localAddress.getHostAddress() : null); + // TODO: Use a byte array or other better method for storing IPs that can also include scope + out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null); + out.writeParcelable(network, flags); + out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags); + out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags); + out.writeInt(encapType); + out.writeInt(encapLocalPort); + out.writeInt(encapRemotePort); + out.writeInt(selectorProto); + } + + // Package Private: Used by the IpSecTransform.Builder; + // there should be no public constructor for this object + IpSecConfig() { + flow[IpSecTransform.DIRECTION_IN].spi = 0; + flow[IpSecTransform.DIRECTION_OUT].spi = 0; + nattKeepaliveInterval = 0; //FIXME constant + } + + private static InetAddress readInetAddressFromParcel(Parcel in) { + String addrString = in.readString(); + if (addrString == null) { + return null; + } + try { + return InetAddress.getByName(addrString); + } catch (UnknownHostException e) { + Log.wtf(TAG, "Invalid IpAddress " + addrString); + return null; + } + } + + private IpSecConfig(Parcel in) { + features = in.readLong(); + localAddress = readInetAddressFromParcel(in); + remoteAddress = readInetAddressFromParcel(in); + network = (Network) in.readParcelable(Network.class.getClassLoader()); + flow[IpSecTransform.DIRECTION_IN].spi = in.readInt(); + flow[IpSecTransform.DIRECTION_IN].encryptionAlgo = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + flow[IpSecTransform.DIRECTION_IN].authenticationAlgo = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt(); + flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + encapType = in.readInt(); + encapLocalPort = in.readInt(); + encapRemotePort = in.readInt(); + selectorProto = in.readInt(); + } + + public static final Parcelable.Creator<IpSecConfig> CREATOR = + new Parcelable.Creator<IpSecConfig>() { + public IpSecConfig createFromParcel(Parcel in) { + return new IpSecConfig(in); + } + + public IpSecConfig[] newArray(int size) { + return new IpSecConfig[size]; + } + }; +} diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java new file mode 100644 index 000000000000..2c544e9b9bfe --- /dev/null +++ b/core/java/android/net/IpSecManager.java @@ -0,0 +1,379 @@ +/* + * 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; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.SystemApi; +import android.content.Context; +import android.os.INetworkManagementService; +import android.os.ParcelFileDescriptor; +import android.util.AndroidException; +import dalvik.system.CloseGuard; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Socket; + +/** + * This class contains methods for managing IPsec sessions, which will perform kernel-space + * encryption and decryption of socket or Network traffic. + * + * <p>An IpSecManager may be obtained by calling {@link + * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link + * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE} + */ +public final class IpSecManager { + private static final String TAG = "IpSecManager"; + + /** + * Indicates that the combination of remote InetAddress and SPI was non-unique for a given + * request. If encountered, selection of a new SPI is required before a transform may be + * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random + * or reserved using reserveSecurityParameterIndex. + */ + public static final class SpiUnavailableException extends AndroidException { + private final int mSpi; + + /** + * Construct an exception indicating that a transform with the given SPI is already in use + * or otherwise unavailable. + * + * @param msg Description indicating the colliding SPI + * @param spi the SPI that could not be used due to a collision + */ + SpiUnavailableException(String msg, int spi) { + super(msg + "(spi: " + spi + ")"); + mSpi = spi; + } + + /** Retrieve the SPI that caused a collision */ + public int getSpi() { + return mSpi; + } + } + + /** + * Indicates that the requested system resource for IPsec, such as a socket or other system + * resource is unavailable. If this exception is thrown, try releasing allocated objects of the + * type requested. + */ + public static final class ResourceUnavailableException extends AndroidException { + + ResourceUnavailableException(String msg) { + super(msg); + } + } + + private final Context mContext; + private final INetworkManagementService mService; + + public static final class SecurityParameterIndex implements AutoCloseable { + private final Context mContext; + private final InetAddress mDestinationAddress; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private int mSpi; + + /** Return the underlying SPI held by this object */ + public int getSpi() { + return mSpi; + } + + private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi) + throws ResourceUnavailableException, SpiUnavailableException { + mContext = context; + mDestinationAddress = destinationAddress; + mSpi = spi; + mCloseGuard.open("open"); + } + + /** + * Release an SPI that was previously reserved. + * + * <p>Release an SPI for use by other users in the system. This will fail if the SPI is + * currently in use by an IpSecTransform. + * + * @param destinationAddress SPIs must be unique for each combination of SPI and destination + * address. Thus, the destinationAddress to which the SPI will communicate must be + * supplied. + * @param spi the previously reserved SPI to be freed. + */ + @Override + public void close() { + mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI + mCloseGuard.close(); + } + + @Override + protected void finalize() { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + + close(); + } + } + + /** + * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * + * <p>No IPsec packet may contain an SPI of 0. + */ + public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + + /** + * Reserve an SPI for traffic bound towards the specified destination address. + * + * <p>If successful, this SPI is guaranteed available until released by a call to {@link + * SecurityParameterIndex#close()}. + * + * @param destinationAddress SPIs must be unique for each combination of SPI and destination + * address. + * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. + * @return the reserved SecurityParameterIndex + * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated + * for this user + * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved + */ + public SecurityParameterIndex reserveSecurityParameterIndex( + InetAddress destinationAddress, int requestedSpi) + throws SpiUnavailableException, ResourceUnavailableException { + return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi); + } + + /** + * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec + * encapsulation of the traffic flowing between the socket and the remote InetAddress of that + * transform. For security reasons, attempts to send traffic to any IP address other than the + * address associated with that transform will throw an IOException. In addition, if the + * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to + * send() or receive() until the transform is removed from the socket by calling {@link + * #removeTransportModeTransform(Socket, IpSecTransform)}; + * + * @param socket a stream socket + * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + */ + public void applyTransportModeTransform(Socket socket, IpSecTransform transform) + throws IOException { + applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform); + } + + /** + * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec + * encapsulation of the traffic flowing between the socket and the remote InetAddress of that + * transform. For security reasons, attempts to send traffic to any IP address other than the + * address associated with that transform will throw an IOException. In addition, if the + * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to + * send() or receive() until the transform is removed from the socket by calling {@link + * #removeTransportModeTransform(DatagramSocket, IpSecTransform)}; + * + * @param socket a datagram socket + * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + */ + public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) + throws IOException { + applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform); + } + + /* Call down to activate a transform */ + private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + + /** + * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to + * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to + * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic). + * Applications should probably not use this API directly. Instead, they should use {@link + * VpnService} to provide VPN capability in a more generic fashion. + * + * @param net a {@link Network} that will be tunneled via IP Sec. + * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. + * @hide + */ + @SystemApi + public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} + + /** + * Remove a transform from a given stream socket. Once removed, traffic on the socket will not + * be encypted. This allows sockets that have been used for IPsec to be reclaimed for + * communication in the clear in the event socket reuse is desired. This operation will succeed + * regardless of the underlying state of a transform. If a transform is removed, communication + * on all sockets to which that transform was applied will fail until this method is called. + * + * @param socket a socket that previously had a transform applied to it. + * @param transform the IPsec Transform that was previously applied to the given socket + */ + public void removeTransportModeTransform(Socket socket, IpSecTransform transform) { + removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform); + } + + /** + * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not + * be encypted. This allows sockets that have been used for IPsec to be reclaimed for + * communication in the clear in the event socket reuse is desired. This operation will succeed + * regardless of the underlying state of a transform. If a transform is removed, communication + * on all sockets to which that transform was applied will fail until this method is called. + * + * @param socket a socket that previously had a transform applied to it. + * @param transform the IPsec Transform that was previously applied to the given socket + */ + public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) { + removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform); + } + + /* Call down to activate a transform */ + private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + + /** + * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of + * cleanup if a tunneled Network experiences a change in default route. The Network will drop + * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is + * lost, all traffic will drop. + * + * @param net a network that currently has transform applied to it. + * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given + * network + * @hide + */ + @SystemApi + public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} + + /** + * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for + * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic. + * + * <p>The socket provided by this class cannot be re-bound or closed via the inner + * FileDescriptor. Instead, disposing of this socket requires a call to close(). + */ + public static final class UdpEncapsulationSocket implements AutoCloseable { + private final FileDescriptor mFd; + private final Context mContext; + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private UdpEncapsulationSocket(Context context, int port) + throws ResourceUnavailableException { + mContext = context; + mCloseGuard.open("constructor"); + // TODO: go down to the kernel and get a socket on the specified + mFd = new FileDescriptor(); + } + + private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException { + mContext = context; + mCloseGuard.open("constructor"); + // TODO: go get a random socket on a random port + mFd = new FileDescriptor(); + } + + /** Access the inner UDP Encapsulation Socket */ + public FileDescriptor getSocket() { + return mFd; + } + + /** Retrieve the port number of the inner encapsulation socket */ + public int getPort() { + return 0; // TODO get the port number from the Socket; + } + + @Override + /** + * Release the resources that have been reserved for this Socket. + * + * <p>This method closes the underlying socket, reducing a user's allocated sockets in the + * system. This must be done as part of cleanup following use of a socket. Failure to do so + * will cause the socket to count against a total allocation limit for IpSec and eventually + * fail due to resource limits. + * + * @param fd a file descriptor previously returned as a UDP Encapsulation socket. + */ + public void close() { + // TODO: Go close the socket + mCloseGuard.close(); + } + + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + + close(); + } + }; + + /** + * Open a socket that is bound to a free UDP port on the system. + * + * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by + * the caller. This provides safe access to a socket on a port that can later be used as a UDP + * Encapsulation port. + * + * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the + * socket port. Explicitly opening this port is only necessary if communication is desired on + * that port. + * + * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this + * method will bind to the specified port or fail. To retrieve the port number, call {@link + * android.system.Os#getsockname(FileDescriptor)}. + * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime + * of the object. + */ + // Returning a socket in this fashion that has been created and bound by the system + // is the only safe way to ensure that a socket is both accessible to the user and + // safely usable for Encapsulation without allowing a user to possibly unbind from/close + // the port, which could potentially impact the traffic of the next user who binds to that + // socket. + public UdpEncapsulationSocket openUdpEncapsulationSocket(int port) + throws IOException, ResourceUnavailableException { + // Temporary code + return new UdpEncapsulationSocket(mContext, port); + } + + /** + * Open a socket that is bound to a port selected by the system. + * + * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by + * the caller. This provides safe access to a socket on a port that can later be used as a UDP + * Encapsulation port. + * + * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the + * socket port. Explicitly opening this port is only necessary if communication is desired on + * that port. + * + * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port + */ + // Returning a socket in this fashion that has been created and bound by the system + // is the only safe way to ensure that a socket is both accessible to the user and + // safely usable for Encapsulation without allowing a user to possibly unbind from/close + // the port, which could potentially impact the traffic of the next user who binds to that + // socket. + public UdpEncapsulationSocket openUdpEncapsulationSocket() + throws IOException, ResourceUnavailableException { + // Temporary code + return new UdpEncapsulationSocket(mContext); + } + + /** + * Retrieve an instance of an IpSecManager within you application context + * + * @param context the application context for this manager + * @hide + */ + public IpSecManager(Context context, INetworkManagementService service) { + mContext = checkNotNull(context, "missing context"); + mService = checkNotNull(service, "missing service"); + } +} diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java new file mode 100644 index 000000000000..d6dd28bec390 --- /dev/null +++ b/core/java/android/net/IpSecTransform.java @@ -0,0 +1,471 @@ +/* + * 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; + +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.content.Context; +import android.system.ErrnoException; +import android.util.Log; +import dalvik.system.CloseGuard; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; + +/** + * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec. + * + * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout + * the lifetime of the underlying transform. If a transform object leaves scope, the underlying + * transform may be disabled automatically, with likely undesirable results. + * + * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array + * of traffic or may represent a transport mode transform operating on a Socket or Sockets. + */ +public final class IpSecTransform implements AutoCloseable { + private static final String TAG = "IpSecTransform"; + + /** + * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies + * to traffic towards the host. + */ + public static final int DIRECTION_IN = 0; + + /** + * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies + * to traffic from the host. + */ + public static final int DIRECTION_OUT = 1; + + /** @hide */ + @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) + @Retention(RetentionPolicy.SOURCE) + public @interface TransformDirection {} + + /** @hide */ + private static final int MODE_TUNNEL = 0; + + /** @hide */ + private static final int MODE_TRANSPORT = 1; + + /** @hide */ + public static final int ENCAP_NONE = 0; + + /** + * IpSec traffic will be encapsulated within UDP as per <a + * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. + * + * @hide + */ + public static final int ENCAP_ESPINUDP = 1; + + /** + * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad + * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. + * + * @hide + */ + public static final int ENCAP_ESPINUDP_NONIKE = 2; + + /** @hide */ + @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE}) + @Retention(RetentionPolicy.SOURCE) + public @interface EncapType {} + + /** + * Sentinel for an invalid transform (means that this transform is inactive). + * + * @hide + */ + public static final int INVALID_TRANSFORM_ID = -1; + + private IpSecTransform(Context context, IpSecConfig config) { + mContext = context; + mConfig = config; + mTransformId = INVALID_TRANSFORM_ID; + } + + private IpSecTransform activate() + throws IOException, IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException { + int transformId; + synchronized (this) { + //try { + transformId = INVALID_TRANSFORM_ID; + //} catch (RemoteException e) { + // throw e.rethrowFromSystemServer(); + //} + + if (transformId < 0) { + throw new ErrnoException("addTransform", -transformId).rethrowAsIOException(); + } + + startKeepalive(mContext); // Will silently fail if not required + mTransformId = transformId; + Log.d(TAG, "Added Transform with Id " + transformId); + } + mCloseGuard.open("build"); + + return this; + } + + /** + * Deactivate an IpSecTransform and free all resources for that transform that are managed by + * the system for this Transform. + * + * <p>Deactivating a transform while it is still applied to any Socket will result in sockets + * refusing to send or receive data. This method will silently succeed if the specified + * transform has already been removed; thus, it is always safe to attempt cleanup when a + * transform is no longer needed. + */ + public void close() { + Log.d(TAG, "Removing Transform with Id " + mTransformId); + + // Always safe to attempt cleanup + if (mTransformId == INVALID_TRANSFORM_ID) { + return; + } + //try { + stopKeepalive(); + //} catch (RemoteException e) { + // transform.setTransformId(transformId); + // throw e.rethrowFromSystemServer(); + //} finally { + mTransformId = INVALID_TRANSFORM_ID; + //} + mCloseGuard.close(); + } + + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + /* Package */ + IpSecConfig getConfig() { + return mConfig; + } + + private final IpSecConfig mConfig; + private int mTransformId; + private final Context mContext; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private ConnectivityManager.PacketKeepalive mKeepalive; + private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; + private Object mKeepaliveSyncLock = new Object(); + private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = + new ConnectivityManager.PacketKeepaliveCallback() { + + @Override + public void onStarted() { + synchronized (mKeepaliveSyncLock) { + mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS; + mKeepaliveSyncLock.notifyAll(); + } + } + + @Override + public void onStopped() { + synchronized (mKeepaliveSyncLock) { + mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; + mKeepaliveSyncLock.notifyAll(); + } + } + + @Override + public void onError(int error) { + synchronized (mKeepaliveSyncLock) { + mKeepaliveStatus = error; + mKeepaliveSyncLock.notifyAll(); + } + } + }; + + /* Package */ + void startKeepalive(Context c) { + if (mConfig.getNattKeepaliveInterval() == 0) { + return; + } + + ConnectivityManager cm = + (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); + + if (mKeepalive != null) { + Log.e(TAG, "Keepalive already started for this IpSecTransform."); + return; + } + + synchronized (mKeepaliveSyncLock) { + mKeepalive = + cm.startNattKeepalive( + mConfig.getNetwork(), + mConfig.getNattKeepaliveInterval(), + mKeepaliveCallback, + mConfig.getLocalIp(), + mConfig.getEncapLocalPort(), + mConfig.getRemoteIp()); + try { + mKeepaliveSyncLock.wait(2000); + } catch (InterruptedException e) { + } + } + if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) { + throw new UnsupportedOperationException("Packet Keepalive cannot be started"); + } + } + + /* Package */ + void stopKeepalive() { + if (mKeepalive == null) { + return; + } + mKeepalive.stop(); + synchronized (mKeepaliveSyncLock) { + if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) { + try { + mKeepaliveSyncLock.wait(2000); + } catch (InterruptedException e) { + } + } + } + } + + /* Package */ + void setTransformId(int transformId) { + mTransformId = transformId; + } + + /* Package */ + int getTransformId() { + return mTransformId; + } + + /** + * Builder object to facilitate the creation of IpSecTransform objects. + * + * <p>Apply additional properties to the transform and then call a build() method to return an + * IpSecTransform object. + * + * @see Builder#buildTransportModeTransform(InetAddress) + */ + public static class Builder { + private Context mContext; + private IpSecConfig mConfig; + + /** + * Add an encryption algorithm to the transform for the given direction. + * + * <p>If encryption is set for a given direction without also providing an SPI for that + * direction, creation of an IpSecTransform will fail upon calling a build() method. + * + * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. + */ + public IpSecTransform.Builder setEncryption( + @TransformDirection int direction, IpSecAlgorithm algo) { + mConfig.flow[direction].encryptionAlgo = algo; + return this; + } + + /** + * Add an authentication/integrity algorithm to the transform. + * + * <p>If authentication is set for a given direction without also providing an SPI for that + * direction, creation of an IpSecTransform will fail upon calling a build() method. + * + * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. + */ + public IpSecTransform.Builder setAuthentication( + @TransformDirection int direction, IpSecAlgorithm algo) { + mConfig.flow[direction].authenticationAlgo = algo; + return this; + } + + /** + * Set the SPI, which uniquely identifies a particular IPsec session from others. Because + * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a + * given destination address. + * + * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as + * possible. Random number generation is a reasonable approach to selecting an SPI. For + * outbound SPIs, they must be reserved by calling {@link + * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will + * fail to build. + * + * <p>Unless an SPI is set for a given direction, traffic in that direction will be + * sent/received without any IPsec applied. + * + * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param spi a unique 32-bit integer to identify transformed traffic + */ + public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) { + mConfig.flow[direction].spi = spi; + return this; + } + + /** + * Set the SPI, which uniquely identifies a particular IPsec session from others. Because + * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a + * given destination address. + * + * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as + * possible. Random number generation is a reasonable approach to selecting an SPI. For + * outbound SPIs, they must be reserved by calling {@link + * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will + * fail to activate. + * + * <p>Unless an SPI is set for a given direction, traffic in that direction will be + * sent/received without any IPsec applied. + * + * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic + */ + public IpSecTransform.Builder setSpi( + @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { + mConfig.flow[direction].spi = spi.getSpi(); + return this; + } + + /** + * Specify the network on which this transform will emit its traffic; (otherwise it will + * emit on the default network). + * + * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in + * tunnel mode. + * + * @hide + */ + @SystemApi + public IpSecTransform.Builder setUnderlyingNetwork(Network net) { + mConfig.network = net; + return this; + } + + /** + * Add UDP encapsulation to an IPv4 transform + * + * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for + * details on how UDP should be applied to IPsec. + * + * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and + * receiving encapsulating traffic. + * @param remotePort the UDP port number of the remote that will send and receive + * encapsulated traffic. In the case of IKE, this is likely port 4500. + */ + public IpSecTransform.Builder setIpv4Encapsulation( + IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + // TODO: check encap type is valid. + mConfig.encapType = ENCAP_ESPINUDP; + mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket + mConfig.encapRemotePort = remotePort; + return this; + } + + // TODO: Decrease the minimum keepalive to maybe 10? + // TODO: Probably a better exception to throw for NATTKeepalive failure + // TODO: Specify the needed NATT keepalive permission. + /** + * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded + * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot + * be activated, then the transform will fail to activate and throw an IOException. + * + * @param intervalSeconds the maximum number of seconds between keepalive packets, no less + * than 20s and no more than 3600s. + * @hide + */ + @SystemApi + public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) { + mConfig.nattKeepaliveInterval = intervalSeconds; + return this; + } + + /** + * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform. + * Some parameters have interdependencies that are checked at build time. If a well-formed + * transform cannot be created from the supplied parameters, this method will throw an + * Exception. + * + * <p>Upon a successful return from this call, the provided IpSecTransform will be active + * and may be applied to sockets. If too many IpSecTransform objects are active for a given + * user this operation will fail and throw ResourceUnavailableException. To avoid these + * exceptions, unused Transform objects must be cleaned up by calling {@link + * IpSecTransform#close()} when they are no longer needed. + * + * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this + * socket will cause the transform to be applied. + * <p>Note that an active transform will not impact any network traffic until it has + * been applied to one or more Sockets. Calling this method is a necessary precondition + * for applying it to a socket, but is not sufficient to actually apply IPsec. + * @throws IllegalArgumentException indicating that a particular combination of transform + * properties is invalid. + * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms + * may be allocated + * @throws SpiUnavailableException if the SPI collides with an existing transform + * (unlikely). + * @throws ResourceUnavailableException if the current user currently has exceeded the + * number of allowed active transforms. + */ + public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) + throws IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException, IOException { + //FIXME: argument validation here + //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); + mConfig.mode = MODE_TRANSPORT; + mConfig.remoteAddress = remoteAddress; + return new IpSecTransform(mContext, mConfig).activate(); + } + + /** + * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some + * parameters have interdependencies that are checked at build time. + * + * @param localAddress the {@link InetAddress} that provides the local endpoint for this + * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} + * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. + * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this + * IPsec tunnel. + * @throws IllegalArgumentException indicating that a particular combination of transform + * properties is invalid. + * @hide + */ + @SystemApi + public IpSecTransform buildTunnelModeTransform( + InetAddress localAddress, InetAddress remoteAddress) { + //FIXME: argument validation here + //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); + mConfig.localAddress = localAddress; + mConfig.remoteAddress = remoteAddress; + mConfig.mode = MODE_TUNNEL; + return new IpSecTransform(mContext, mConfig); + } + + /** + * Create a new IpSecTransform.Builder to construct an IpSecTransform + * + * @param context current Context + */ + public Builder(Context context) { + mContext = context; + mConfig = new IpSecConfig(); + } + } +} |