diff options
Diffstat (limited to 'framework-t/src')
| -rw-r--r-- | framework-t/src/android/net/IIpSecService.aidl | 78 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecAlgorithm.java | 500 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecConfig.aidl | 20 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecConfig.java | 358 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecManager.java | 1034 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecSpiResponse.aidl | 20 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecSpiResponse.java | 78 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecTransform.java | 421 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecTransformResponse.aidl | 20 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecTransformResponse.java | 73 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl | 20 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecTunnelInterfaceResponse.java | 78 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecUdpEncapResponse.aidl | 20 | ||||
| -rw-r--r-- | framework-t/src/android/net/IpSecUdpEncapResponse.java | 96 |
14 files changed, 2816 insertions, 0 deletions
diff --git a/framework-t/src/android/net/IIpSecService.aidl b/framework-t/src/android/net/IIpSecService.aidl new file mode 100644 index 0000000000..933256a3b4 --- /dev/null +++ b/framework-t/src/android/net/IIpSecService.aidl @@ -0,0 +1,78 @@ +/* +** Copyright 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.net.LinkAddress; +import android.net.Network; +import android.net.IpSecConfig; +import android.net.IpSecUdpEncapResponse; +import android.net.IpSecSpiResponse; +import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +/** + * @hide + */ +interface IIpSecService +{ + IpSecSpiResponse allocateSecurityParameterIndex( + in String destinationAddress, int requestedSpi, in IBinder binder); + + void releaseSecurityParameterIndex(int resourceId); + + IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder); + + void closeUdpEncapsulationSocket(int resourceId); + + IpSecTunnelInterfaceResponse createTunnelInterface( + in String localAddr, + in String remoteAddr, + in Network underlyingNetwork, + in IBinder binder, + in String callingPackage); + + void addAddressToTunnelInterface( + int tunnelResourceId, + in LinkAddress localAddr, + in String callingPackage); + + void removeAddressFromTunnelInterface( + int tunnelResourceId, + in LinkAddress localAddr, + in String callingPackage); + + void setNetworkForTunnelInterface( + int tunnelResourceId, in Network underlyingNetwork, in String callingPackage); + + void deleteTunnelInterface(int resourceId, in String callingPackage); + + IpSecTransformResponse createTransform( + in IpSecConfig c, in IBinder binder, in String callingPackage); + + void deleteTransform(int transformId); + + void applyTransportModeTransform( + in ParcelFileDescriptor socket, int direction, int transformId); + + void applyTunnelModeTransform( + int tunnelResourceId, int direction, int transformResourceId, in String callingPackage); + + void removeTransportModeTransforms(in ParcelFileDescriptor socket); +} diff --git a/framework-t/src/android/net/IpSecAlgorithm.java b/framework-t/src/android/net/IpSecAlgorithm.java new file mode 100644 index 0000000000..86052484ea --- /dev/null +++ b/framework-t/src/android/net/IpSecAlgorithm.java @@ -0,0 +1,500 @@ +/* + * 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.NonNull; +import android.annotation.StringDef; +import android.content.res.Resources; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.HexDump; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * This class represents a single algorithm that can be used by an {@link IpSecTransform}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> + */ +public final class IpSecAlgorithm implements Parcelable { + private static final String TAG = "IpSecAlgorithm"; + + /** + * Null cipher. + * + * @hide + */ + public static final String CRYPT_NULL = "ecb(cipher_null)"; + + /** + * AES-CBC Encryption/Ciphering Algorithm. + * + * <p>Valid lengths for this key are {128, 192, 256}. + */ + public static final String CRYPT_AES_CBC = "cbc(aes)"; + + /** + * AES-CTR Encryption/Ciphering Algorithm. + * + * <p>Valid lengths for keying material are {160, 224, 288}. + * + * <p>As per <a href="https://tools.ietf.org/html/rfc3686#section-5.1">RFC3686 (Section + * 5.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit + * nonce. RFC compliance requires that the nonce must be unique per security association. + * + * <p>This algorithm may be available on the device. Caller MUST check if it is supported before + * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is + * included in the returned algorithm set. The returned algorithm set will not change unless the + * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is + * requested on an unsupported device. + * + * <p>@see {@link #getSupportedAlgorithms()} + */ + // This algorithm may be available on devices released before Android 12, and is guaranteed + // to be available on devices first shipped with Android 12 or later. + public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))"; + + /** + * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> + * + * <p>Keys for this algorithm must be 128 bits in length. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128. + */ + public static final String AUTH_HMAC_MD5 = "hmac(md5)"; + + /** + * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> + * + * <p>Keys for this algorithm must be 160 bits in length. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160. + */ + public static final String AUTH_HMAC_SHA1 = "hmac(sha1)"; + + /** + * SHA256 HMAC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 256 bits in length. + * + * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256. + */ + public static final String AUTH_HMAC_SHA256 = "hmac(sha256)"; + + /** + * SHA384 HMAC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 384 bits in length. + * + * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384. + */ + public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; + + /** + * SHA512 HMAC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 512 bits in length. + * + * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512. + */ + public static final String AUTH_HMAC_SHA512 = "hmac(sha512)"; + + /** + * AES-XCBC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 128 bits in length. + * + * <p>The only valid truncation length is 96 bits. + * + * <p>This algorithm may be available on the device. Caller MUST check if it is supported before + * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is + * included in the returned algorithm set. The returned algorithm set will not change unless the + * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is + * requested on an unsupported device. + * + * <p>@see {@link #getSupportedAlgorithms()} + */ + // This algorithm may be available on devices released before Android 12, and is guaranteed + // to be available on devices first shipped with Android 12 or later. + public static final String AUTH_AES_XCBC = "xcbc(aes)"; + + /** + * AES-CMAC Authentication/Integrity Algorithm. + * + * <p>Keys for this algorithm must be 128 bits in length. + * + * <p>The only valid truncation length is 96 bits. + * + * <p>This algorithm may be available on the device. Caller MUST check if it is supported before + * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is + * included in the returned algorithm set. The returned algorithm set will not change unless the + * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is + * requested on an unsupported device. + * + * <p>@see {@link #getSupportedAlgorithms()} + */ + // This algorithm may be available on devices released before Android 12, and is guaranteed + // to be available on devices first shipped with Android 12 or later. + public static final String AUTH_AES_CMAC = "cmac(aes)"; + + /** + * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm. + * + * <p>Valid lengths for keying material are {160, 224, 288}. + * + * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section + * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit + * salt. RFC compliance requires that the salt must be unique per invocation with the same key. + * + * <p>Valid ICV (truncation) lengths are {64, 96, 128}. + */ + public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))"; + + /** + * ChaCha20-Poly1305 Authentication/Integrity + Encryption/Ciphering Algorithm. + * + * <p>Keys for this algorithm must be 288 bits in length. + * + * <p>As per <a href="https://tools.ietf.org/html/rfc7634#section-2">RFC7634 (Section 2)</a>, + * keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per + * security association. + * + * <p>The only valid ICV (truncation) length is 128 bits. + * + * <p>This algorithm may be available on the device. Caller MUST check if it is supported before + * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is + * included in the returned algorithm set. The returned algorithm set will not change unless the + * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is + * requested on an unsupported device. + * + * <p>@see {@link #getSupportedAlgorithms()} + */ + // This algorithm may be available on devices released before Android 12, and is guaranteed + // to be available on devices first shipped with Android 12 or later. + public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)"; + + /** @hide */ + @StringDef({ + CRYPT_AES_CBC, + CRYPT_AES_CTR, + AUTH_HMAC_MD5, + AUTH_HMAC_SHA1, + AUTH_HMAC_SHA256, + AUTH_HMAC_SHA384, + AUTH_HMAC_SHA512, + AUTH_AES_XCBC, + AUTH_AES_CMAC, + AUTH_CRYPT_AES_GCM, + AUTH_CRYPT_CHACHA20_POLY1305 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AlgorithmName {} + + /** @hide */ + @VisibleForTesting + public static final Map<String, Integer> ALGO_TO_REQUIRED_FIRST_SDK = new HashMap<>(); + + private static final int SDK_VERSION_ZERO = 0; + + static { + ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CBC, SDK_VERSION_ZERO); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_MD5, SDK_VERSION_ZERO); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA1, SDK_VERSION_ZERO); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA256, SDK_VERSION_ZERO); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA384, SDK_VERSION_ZERO); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO); + + ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S); + } + + private static final Set<String> ENABLED_ALGOS = + Collections.unmodifiableSet(loadAlgos(Resources.getSystem())); + + private final String mName; + private final byte[] mKey; + private final int mTruncLenBits; + + /** + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. + * + * <p>For algorithms that produce an integrity check value, the truncation length is a required + * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)} + * + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. + * @throws IllegalArgumentException if algorithm or key length is invalid. + */ + public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) { + this(algorithm, key, 0); + } + + /** + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. + * + * <p>This constructor only supports algorithms that use a truncation length. i.e. + * Authentication and Authenticated Encryption algorithms. + * + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. + * @param truncLenBits number of bits of output hash to use. + * @throws IllegalArgumentException if algorithm, key length or truncation length is invalid. + */ + public IpSecAlgorithm( + @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) { + mName = algorithm; + mKey = key.clone(); + mTruncLenBits = truncLenBits; + checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits); + } + + /** Get the algorithm name */ + @NonNull + public String getName() { + return mName; + } + + /** Get the key for this algorithm */ + @NonNull + public byte[] getKey() { + return mKey.clone(); + } + + /** Get the truncation length of this algorithm, in bits */ + 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 @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR = + new Parcelable.Creator<IpSecAlgorithm>() { + public IpSecAlgorithm createFromParcel(Parcel in) { + final String name = in.readString(); + final byte[] key = in.createByteArray(); + final int truncLenBits = in.readInt(); + + return new IpSecAlgorithm(name, key, truncLenBits); + } + + public IpSecAlgorithm[] newArray(int size) { + return new IpSecAlgorithm[size]; + } + }; + + /** + * Returns supported IPsec algorithms for the current device. + * + * <p>Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is + * supported before using it. + */ + @NonNull + public static Set<String> getSupportedAlgorithms() { + return ENABLED_ALGOS; + } + + /** @hide */ + @VisibleForTesting + public static Set<String> loadAlgos(Resources systemResources) { + final Set<String> enabledAlgos = new HashSet<>(); + + // Load and validate the optional algorithm resource. Undefined or duplicate algorithms in + // the resource are not allowed. + final String[] resourceAlgos = systemResources.getStringArray( + com.android.internal.R.array.config_optionalIpSecAlgorithms); + for (String str : resourceAlgos) { + if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) { + // This error should be caught by CTS and never be thrown to API callers + throw new IllegalArgumentException("Invalid or repeated algorithm " + str); + } + } + + for (Entry<String, Integer> entry : ALGO_TO_REQUIRED_FIRST_SDK.entrySet()) { + if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= entry.getValue()) { + enabledAlgos.add(entry.getKey()); + } + } + + return enabledAlgos; + } + + private static void checkValidOrThrow(String name, int keyLen, int truncLen) { + final boolean isValidLen; + final boolean isValidTruncLen; + + if (!getSupportedAlgorithms().contains(name)) { + throw new IllegalArgumentException("Unsupported algorithm: " + name); + } + + switch (name) { + case CRYPT_AES_CBC: + isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256; + isValidTruncLen = true; + break; + case CRYPT_AES_CTR: + // The keying material for AES-CTR is a key plus a 32-bit salt + isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; + isValidTruncLen = true; + break; + case AUTH_HMAC_MD5: + isValidLen = keyLen == 128; + isValidTruncLen = truncLen >= 96 && truncLen <= 128; + break; + case AUTH_HMAC_SHA1: + isValidLen = keyLen == 160; + isValidTruncLen = truncLen >= 96 && truncLen <= 160; + break; + case AUTH_HMAC_SHA256: + isValidLen = keyLen == 256; + isValidTruncLen = truncLen >= 96 && truncLen <= 256; + break; + case AUTH_HMAC_SHA384: + isValidLen = keyLen == 384; + isValidTruncLen = truncLen >= 192 && truncLen <= 384; + break; + case AUTH_HMAC_SHA512: + isValidLen = keyLen == 512; + isValidTruncLen = truncLen >= 256 && truncLen <= 512; + break; + case AUTH_AES_XCBC: + isValidLen = keyLen == 128; + isValidTruncLen = truncLen == 96; + break; + case AUTH_AES_CMAC: + isValidLen = keyLen == 128; + isValidTruncLen = truncLen == 96; + break; + case AUTH_CRYPT_AES_GCM: + // The keying material for GCM is a key plus a 32-bit salt + isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; + isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128; + break; + case AUTH_CRYPT_CHACHA20_POLY1305: + // The keying material for ChaCha20Poly1305 is a key plus a 32-bit salt + isValidLen = keyLen == 256 + 32; + isValidTruncLen = truncLen == 128; + break; + default: + // Should never hit here. + throw new IllegalArgumentException("Couldn't find an algorithm: " + name); + } + + if (!isValidLen) { + throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen); + } + if (!isValidTruncLen) { + throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen); + } + } + + /** @hide */ + public boolean isAuthentication() { + switch (getName()) { + // Fallthrough + case AUTH_HMAC_MD5: + case AUTH_HMAC_SHA1: + case AUTH_HMAC_SHA256: + case AUTH_HMAC_SHA384: + case AUTH_HMAC_SHA512: + case AUTH_AES_XCBC: + case AUTH_AES_CMAC: + return true; + default: + return false; + } + } + + /** @hide */ + public boolean isEncryption() { + switch (getName()) { + case CRYPT_AES_CBC: // fallthrough + case CRYPT_AES_CTR: + return true; + default: + return false; + } + } + + /** @hide */ + public boolean isAead() { + switch (getName()) { + case AUTH_CRYPT_AES_GCM: // fallthrough + case AUTH_CRYPT_CHACHA20_POLY1305: + return true; + default: + return false; + } + } + + // Because encryption keys are sensitive and userdebug builds are used by large user pools + // such as beta testers, we only allow sensitive info such as keys on eng builds. + private static boolean isUnsafeBuild() { + return Build.IS_DEBUGGABLE && Build.IS_ENG; + } + + @Override + @NonNull + public String toString() { + return new StringBuilder() + .append("{mName=") + .append(mName) + .append(", mKey=") + .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>") + .append(", mTruncLenBits=") + .append(mTruncLenBits) + .append("}") + .toString(); + } + + /** @hide */ + @VisibleForTesting + public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) { + if (lhs == null || rhs == null) return (lhs == rhs); + return (lhs.mName.equals(rhs.mName) + && Arrays.equals(lhs.mKey, rhs.mKey) + && lhs.mTruncLenBits == rhs.mTruncLenBits); + } +}; diff --git a/framework-t/src/android/net/IpSecConfig.aidl b/framework-t/src/android/net/IpSecConfig.aidl new file mode 100644 index 0000000000..eaefca74d3 --- /dev/null +++ b/framework-t/src/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/framework-t/src/android/net/IpSecConfig.java b/framework-t/src/android/net/IpSecConfig.java new file mode 100644 index 0000000000..575c5ed968 --- /dev/null +++ b/framework-t/src/android/net/IpSecConfig.java @@ -0,0 +1,358 @@ +/* + * 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.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * This class encapsulates all the configuration parameters needed to create IPsec transforms and + * policies. + * + * @hide + */ +public final class IpSecConfig implements Parcelable { + private static final String TAG = "IpSecConfig"; + + // MODE_TRANSPORT or MODE_TUNNEL + private int mMode = IpSecTransform.MODE_TRANSPORT; + + // Preventing this from being null simplifies Java->Native binder + private String mSourceAddress = ""; + + // Preventing this from being null simplifies Java->Native binder + private String mDestinationAddress = ""; + + // The underlying Network that represents the "gateway" Network + // for outbound packets. It may also be used to select packets. + private Network mNetwork; + + // Minimum requirements for identifying a transform + // SPI identifying the IPsec SA in packet processing + // and a destination IP address + private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; + + // Encryption Algorithm + private IpSecAlgorithm mEncryption; + + // Authentication Algorithm + private IpSecAlgorithm mAuthentication; + + // Authenticated Encryption Algorithm + private IpSecAlgorithm mAuthenticatedEncryption; + + // For tunnel mode IPv4 UDP Encapsulation + // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE + private int mEncapType = IpSecTransform.ENCAP_NONE; + private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID; + private int mEncapRemotePort; + + // An interval, in seconds between the NattKeepalive packets + private int mNattKeepaliveInterval; + + // XFRM mark and mask; defaults to 0 (no mark/mask) + private int mMarkValue; + private int mMarkMask; + + // XFRM interface id + private int mXfrmInterfaceId; + + /** Set the mode for this IPsec transform */ + public void setMode(int mode) { + mMode = mode; + } + + /** Set the source IP addres for this IPsec transform */ + public void setSourceAddress(String sourceAddress) { + mSourceAddress = sourceAddress; + } + + /** Set the destination IP address for this IPsec transform */ + public void setDestinationAddress(String destinationAddress) { + mDestinationAddress = destinationAddress; + } + + /** Set the SPI by resource ID */ + public void setSpiResourceId(int resourceId) { + mSpiResourceId = resourceId; + } + + /** Set the encryption algorithm */ + public void setEncryption(IpSecAlgorithm encryption) { + mEncryption = encryption; + } + + /** Set the authentication algorithm */ + public void setAuthentication(IpSecAlgorithm authentication) { + mAuthentication = authentication; + } + + /** Set the authenticated encryption algorithm */ + public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) { + mAuthenticatedEncryption = authenticatedEncryption; + } + + /** Set the underlying network that will carry traffic for this transform */ + public void setNetwork(Network network) { + mNetwork = network; + } + + public void setEncapType(int encapType) { + mEncapType = encapType; + } + + public void setEncapSocketResourceId(int resourceId) { + mEncapSocketResourceId = resourceId; + } + + public void setEncapRemotePort(int port) { + mEncapRemotePort = port; + } + + public void setNattKeepaliveInterval(int interval) { + mNattKeepaliveInterval = interval; + } + + /** + * Sets the mark value + * + * <p>Internal (System server) use only. Marks passed in by users will be overwritten or + * ignored. + */ + public void setMarkValue(int mark) { + mMarkValue = mark; + } + + /** + * Sets the mark mask + * + * <p>Internal (System server) use only. Marks passed in by users will be overwritten or + * ignored. + */ + public void setMarkMask(int mask) { + mMarkMask = mask; + } + + public void setXfrmInterfaceId(int xfrmInterfaceId) { + mXfrmInterfaceId = xfrmInterfaceId; + } + + // Transport or Tunnel + public int getMode() { + return mMode; + } + + public String getSourceAddress() { + return mSourceAddress; + } + + public int getSpiResourceId() { + return mSpiResourceId; + } + + public String getDestinationAddress() { + return mDestinationAddress; + } + + public IpSecAlgorithm getEncryption() { + return mEncryption; + } + + public IpSecAlgorithm getAuthentication() { + return mAuthentication; + } + + public IpSecAlgorithm getAuthenticatedEncryption() { + return mAuthenticatedEncryption; + } + + public Network getNetwork() { + return mNetwork; + } + + public int getEncapType() { + return mEncapType; + } + + public int getEncapSocketResourceId() { + return mEncapSocketResourceId; + } + + public int getEncapRemotePort() { + return mEncapRemotePort; + } + + public int getNattKeepaliveInterval() { + return mNattKeepaliveInterval; + } + + public int getMarkValue() { + return mMarkValue; + } + + public int getMarkMask() { + return mMarkMask; + } + + public int getXfrmInterfaceId() { + return mXfrmInterfaceId; + } + + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mMode); + out.writeString(mSourceAddress); + out.writeString(mDestinationAddress); + out.writeParcelable(mNetwork, flags); + out.writeInt(mSpiResourceId); + out.writeParcelable(mEncryption, flags); + out.writeParcelable(mAuthentication, flags); + out.writeParcelable(mAuthenticatedEncryption, flags); + out.writeInt(mEncapType); + out.writeInt(mEncapSocketResourceId); + out.writeInt(mEncapRemotePort); + out.writeInt(mNattKeepaliveInterval); + out.writeInt(mMarkValue); + out.writeInt(mMarkMask); + out.writeInt(mXfrmInterfaceId); + } + + @VisibleForTesting + public IpSecConfig() {} + + /** Copy constructor */ + @VisibleForTesting + public IpSecConfig(IpSecConfig c) { + mMode = c.mMode; + mSourceAddress = c.mSourceAddress; + mDestinationAddress = c.mDestinationAddress; + mNetwork = c.mNetwork; + mSpiResourceId = c.mSpiResourceId; + mEncryption = c.mEncryption; + mAuthentication = c.mAuthentication; + mAuthenticatedEncryption = c.mAuthenticatedEncryption; + mEncapType = c.mEncapType; + mEncapSocketResourceId = c.mEncapSocketResourceId; + mEncapRemotePort = c.mEncapRemotePort; + mNattKeepaliveInterval = c.mNattKeepaliveInterval; + mMarkValue = c.mMarkValue; + mMarkMask = c.mMarkMask; + mXfrmInterfaceId = c.mXfrmInterfaceId; + } + + private IpSecConfig(Parcel in) { + mMode = in.readInt(); + mSourceAddress = in.readString(); + mDestinationAddress = in.readString(); + mNetwork = (Network) in.readParcelable(Network.class.getClassLoader()); + mSpiResourceId = in.readInt(); + mEncryption = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + mAuthentication = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + mAuthenticatedEncryption = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + mEncapType = in.readInt(); + mEncapSocketResourceId = in.readInt(); + mEncapRemotePort = in.readInt(); + mNattKeepaliveInterval = in.readInt(); + mMarkValue = in.readInt(); + mMarkMask = in.readInt(); + mXfrmInterfaceId = in.readInt(); + } + + @Override + public String toString() { + StringBuilder strBuilder = new StringBuilder(); + strBuilder + .append("{mMode=") + .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT") + .append(", mSourceAddress=") + .append(mSourceAddress) + .append(", mDestinationAddress=") + .append(mDestinationAddress) + .append(", mNetwork=") + .append(mNetwork) + .append(", mEncapType=") + .append(mEncapType) + .append(", mEncapSocketResourceId=") + .append(mEncapSocketResourceId) + .append(", mEncapRemotePort=") + .append(mEncapRemotePort) + .append(", mNattKeepaliveInterval=") + .append(mNattKeepaliveInterval) + .append("{mSpiResourceId=") + .append(mSpiResourceId) + .append(", mEncryption=") + .append(mEncryption) + .append(", mAuthentication=") + .append(mAuthentication) + .append(", mAuthenticatedEncryption=") + .append(mAuthenticatedEncryption) + .append(", mMarkValue=") + .append(mMarkValue) + .append(", mMarkMask=") + .append(mMarkMask) + .append(", mXfrmInterfaceId=") + .append(mXfrmInterfaceId) + .append("}"); + + return strBuilder.toString(); + } + + public static final @android.annotation.NonNull 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]; + } + }; + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof IpSecConfig)) return false; + final IpSecConfig rhs = (IpSecConfig) other; + return (mMode == rhs.mMode + && mSourceAddress.equals(rhs.mSourceAddress) + && mDestinationAddress.equals(rhs.mDestinationAddress) + && ((mNetwork != null && mNetwork.equals(rhs.mNetwork)) + || (mNetwork == rhs.mNetwork)) + && mEncapType == rhs.mEncapType + && mEncapSocketResourceId == rhs.mEncapSocketResourceId + && mEncapRemotePort == rhs.mEncapRemotePort + && mNattKeepaliveInterval == rhs.mNattKeepaliveInterval + && mSpiResourceId == rhs.mSpiResourceId + && IpSecAlgorithm.equals(mEncryption, rhs.mEncryption) + && IpSecAlgorithm.equals(mAuthenticatedEncryption, rhs.mAuthenticatedEncryption) + && IpSecAlgorithm.equals(mAuthentication, rhs.mAuthentication) + && mMarkValue == rhs.mMarkValue + && mMarkMask == rhs.mMarkMask + && mXfrmInterfaceId == rhs.mXfrmInterfaceId); + } +} diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java new file mode 100644 index 0000000000..c10680761f --- /dev/null +++ b/framework-t/src/android/net/IpSecManager.java @@ -0,0 +1,1034 @@ +/* + * 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 android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.RequiresFeature; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.annotations.PolicyDirection; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.system.ErrnoException; +import android.system.OsConstants; +import android.util.AndroidException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +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. Once configured, the kernel will apply + * confidentiality (encryption) and integrity (authentication) to IP traffic. + * + * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create + * transport mode security associations and apply them to individual sockets. Applications looking + * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> + */ +@SystemService(Context.IPSEC_SERVICE) +public final class IpSecManager { + private static final String TAG = "IpSecManager"; + + /** + * Used when applying a transform to direct traffic through an {@link IpSecTransform} + * towards the host. + * + * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}. + */ + public static final int DIRECTION_IN = 0; + + /** + * Used when applying a transform to direct traffic through an {@link IpSecTransform} + * away from the host. + * + * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}. + */ + public static final int DIRECTION_OUT = 1; + + /** + * Used when applying a transform to direct traffic through an {@link IpSecTransform} for + * forwarding between interfaces. + * + * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}. + * + * @hide + */ + public static final int DIRECTION_FWD = 2; + + /** + * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. + * + * <p>No IPsec packet may contain an SPI of 0. + * + * @hide + */ + @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + + /** @hide */ + public interface Status { + public static final int OK = 0; + public static final int RESOURCE_UNAVAILABLE = 1; + public static final int SPI_UNAVAILABLE = 2; + } + + /** @hide */ + public static final int INVALID_RESOURCE_ID = -1; + + /** + * Thrown to indicate that a requested SPI is in use. + * + * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on + * one device. If this error is encountered, a new SPI is required before a transform may be + * created. This error can be avoided by calling {@link + * IpSecManager#allocateSecurityParameterIndex}. + */ + 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; + } + + /** Get the SPI that caused a collision. */ + public int getSpi() { + return mSpi; + } + } + + /** + * Thrown to indicate that an IPsec resource is unavailable. + * + * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link + * IpSecTransform}, or other system resources. If this exception is thrown, users should release + * allocated objects of the type requested. + */ + public static final class ResourceUnavailableException extends AndroidException { + + ResourceUnavailableException(String msg) { + super(msg); + } + } + + private final Context mContext; + private final IIpSecService mService; + + /** + * This class represents a reserved SPI. + * + * <p>Objects of this type are used to track reserved security parameter indices. They can be + * obtained by calling {@link IpSecManager#allocateSecurityParameterIndex} and must be released + * by calling {@link #close()} when they are no longer needed. + */ + public static final class SecurityParameterIndex implements AutoCloseable { + private final IIpSecService mService; + private final InetAddress mDestinationAddress; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; + private int mResourceId = INVALID_RESOURCE_ID; + + /** Get the underlying SPI held by this object. */ + public int getSpi() { + return mSpi; + } + + /** + * Release an SPI that was previously reserved. + * + * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is + * applied to an IpSecTransform, it will become unusable for future transforms but should + * still be closed to ensure system resources are released. + */ + @Override + public void close() { + try { + mService.releaseSecurityParameterIndex(mResourceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (Exception e) { + // On close we swallow all random exceptions since failure to close is not + // actionable by the user. + Log.e(TAG, "Failed to close " + this + ", Exception=" + e); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } + } + + /** Check that the SPI was closed properly. */ + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + + close(); + } + + private SecurityParameterIndex( + @NonNull IIpSecService service, InetAddress destinationAddress, int spi) + throws ResourceUnavailableException, SpiUnavailableException { + mService = service; + mDestinationAddress = destinationAddress; + try { + IpSecSpiResponse result = + mService.allocateSecurityParameterIndex( + destinationAddress.getHostAddress(), spi, new Binder()); + + if (result == null) { + throw new NullPointerException("Received null response from IpSecService"); + } + + int status = result.status; + switch (status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more SPIs may be allocated by this requester."); + case Status.SPI_UNAVAILABLE: + throw new SpiUnavailableException("Requested SPI is unavailable", spi); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + status); + } + mSpi = result.spi; + mResourceId = result.resourceId; + + if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) { + throw new RuntimeException("Invalid SPI returned by IpSecService: " + status); + } + + if (mResourceId == INVALID_RESOURCE_ID) { + throw new RuntimeException( + "Invalid Resource ID returned by IpSecService: " + status); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("open"); + } + + /** @hide */ + @VisibleForTesting + public int getResourceId() { + return mResourceId; + } + + @Override + public String toString() { + return new StringBuilder() + .append("SecurityParameterIndex{spi=") + .append(mSpi) + .append(",resourceId=") + .append(mResourceId) + .append("}") + .toString(); + } + } + + /** + * Reserve a random SPI for traffic bound to or from the specified destination address. + * + * <p>If successful, this SPI is guaranteed available until released by a call to {@link + * SecurityParameterIndex#close()}. + * + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. + * @return the reserved SecurityParameterIndex + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user + */ + @NonNull + public SecurityParameterIndex allocateSecurityParameterIndex( + @NonNull InetAddress destinationAddress) throws ResourceUnavailableException { + try { + return new SecurityParameterIndex( + mService, + destinationAddress, + IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); + } catch (ServiceSpecificException e) { + throw rethrowUncheckedExceptionFromServiceSpecificException(e); + } catch (SpiUnavailableException unlikely) { + // Because this function allocates a totally random SPI, it really shouldn't ever + // fail to allocate an SPI; we simply need this because the exception is checked. + throw new ResourceUnavailableException("No SPIs available"); + } + } + + /** + * Reserve the requested SPI for traffic bound to or from the specified destination address. + * + * <p>If successful, this SPI is guaranteed available until released by a call to {@link + * SecurityParameterIndex#close()}. + * + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. + * @param requestedSpi the requested SPI. The range 1-255 is reserved and may not be used. See + * RFC 4303 Section 2.1. + * @return the reserved SecurityParameterIndex + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user + * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be + * reserved + */ + @NonNull + public SecurityParameterIndex allocateSecurityParameterIndex( + @NonNull InetAddress destinationAddress, int requestedSpi) + throws SpiUnavailableException, ResourceUnavailableException { + if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) { + throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI"); + } + try { + return new SecurityParameterIndex(mService, destinationAddress, requestedSpi); + } catch (ServiceSpecificException e) { + throw rethrowUncheckedExceptionFromServiceSpecificException(e); + } + } + + /** + * Apply an IPsec transform to a stream socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. + * + * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an + * applied transform before completion of graceful shutdown may result in the shutdown sequence + * failing to complete. As such, applications requiring graceful shutdown MUST close the socket + * prior to deactivating the applied transform. Socket closure may be performed asynchronously + * (in batches), so the returning of a close function does not guarantee shutdown of a socket. + * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is + * sufficient to ensure shutdown. + * + * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}), + * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST] + * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the + * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped. + * + * <h4>Rekey Procedure</h4> + * + * <p>When applying a new tranform to a socket in the outbound direction, the previous transform + * will be removed and the new transform will take effect immediately, sending all traffic on + * the new transform; however, when applying a transform in the inbound direction, traffic + * on the old transform will continue to be decrypted and delivered until that transform is + * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey + * procedures where both transforms are valid until both endpoints are using the new transform + * and all in-flight packets have been received. + * + * @param socket a stream socket + * @param direction the direction in which the transform should be applied + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied + */ + public void applyTransportModeTransform(@NonNull Socket socket, + @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException { + // Ensure creation of FD. See b/77548890 for more details. + socket.getSoLinger(); + + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); + } + + /** + * Apply an IPsec transform to a datagram socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. + * + * <h4>Rekey Procedure</h4> + * + * <p>When applying a new tranform to a socket in the outbound direction, the previous transform + * will be removed and the new transform will take effect immediately, sending all traffic on + * the new transform; however, when applying a transform in the inbound direction, traffic + * on the old transform will continue to be decrypted and delivered until that transform is + * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey + * procedures where both transforms are valid until both endpoints are using the new transform + * and all in-flight packets have been received. + * + * @param socket a datagram socket + * @param direction the direction in which the transform should be applied + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied + */ + public void applyTransportModeTransform(@NonNull DatagramSocket socket, + @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException { + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); + } + + /** + * Apply an IPsec transform to a socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. + * + * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an + * applied transform before completion of graceful shutdown may result in the shutdown sequence + * failing to complete. As such, applications requiring graceful shutdown MUST close the socket + * prior to deactivating the applied transform. Socket closure may be performed asynchronously + * (in batches), so the returning of a close function does not guarantee shutdown of a socket. + * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is + * sufficient to ensure shutdown. + * + * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}), + * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST] + * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the + * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped. + * + * <h4>Rekey Procedure</h4> + * + * <p>When applying a new tranform to a socket in the outbound direction, the previous transform + * will be removed and the new transform will take effect immediately, sending all traffic on + * the new transform; however, when applying a transform in the inbound direction, traffic + * on the old transform will continue to be decrypted and delivered until that transform is + * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey + * procedures where both transforms are valid until both endpoints are using the new transform + * and all in-flight packets have been received. + * + * @param socket a socket file descriptor + * @param direction the direction in which the transform should be applied + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied + */ + public void applyTransportModeTransform(@NonNull FileDescriptor socket, + @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException { + // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor() + // constructor takes control and closes the user's FD when we exit the method. + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { + mService.applyTransportModeTransform(pfd, direction, transform.getResourceId()); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove an IPsec transform from a stream socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it + * @throws IOException indicating that the transform could not be removed from the socket + */ + public void removeTransportModeTransforms(@NonNull Socket socket) throws IOException { + // Ensure creation of FD. See b/77548890 for more details. + socket.getSoLinger(); + + removeTransportModeTransforms(socket.getFileDescriptor$()); + } + + /** + * Remove an IPsec transform from a datagram socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it + * @throws IOException indicating that the transform could not be removed from the socket + */ + public void removeTransportModeTransforms(@NonNull DatagramSocket socket) throws IOException { + removeTransportModeTransforms(socket.getFileDescriptor$()); + } + + /** + * Remove an IPsec transform from a socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it + * @throws IOException indicating that the transform could not be removed from the socket + */ + public void removeTransportModeTransforms(@NonNull FileDescriptor socket) throws IOException { + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { + mService.removeTransportModeTransforms(pfd); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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. + * + * <p>TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. + * + * @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 + */ + public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} + + /** + * This class provides access to a UDP encapsulation Socket. + * + * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2 + * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link + * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the + * caller. The caller should not close the {@code FileDescriptor} returned by {@link + * #getFileDescriptor}, but should use {@link #close} instead. + * + * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic + * of the next user who binds to that port. To prevent this scenario, these sockets are held + * open by the system so that they may only be closed by calling {@link #close} or when the user + * process exits. + */ + public static final class UdpEncapsulationSocket implements AutoCloseable { + private final ParcelFileDescriptor mPfd; + private final IIpSecService mService; + private int mResourceId = INVALID_RESOURCE_ID; + private final int mPort; + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private UdpEncapsulationSocket(@NonNull IIpSecService service, int port) + throws ResourceUnavailableException, IOException { + mService = service; + try { + IpSecUdpEncapResponse result = + mService.openUdpEncapsulationSocket(port, new Binder()); + switch (result.status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more Sockets may be allocated by this requester."); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + result.status); + } + mResourceId = result.resourceId; + mPort = result.port; + mPfd = result.fileDescriptor; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("constructor"); + } + + /** Get the encapsulation socket's file descriptor. */ + public FileDescriptor getFileDescriptor() { + if (mPfd == null) { + return null; + } + return mPfd.getFileDescriptor(); + } + + /** Get the bound port of the wrapped socket. */ + public int getPort() { + return mPort; + } + + /** + * Close this socket. + * + * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's + * resource limits, and forgetting to close them eventually will result in {@link + * ResourceUnavailableException} being thrown. + */ + @Override + public void close() throws IOException { + try { + mService.closeUdpEncapsulationSocket(mResourceId); + mResourceId = INVALID_RESOURCE_ID; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (Exception e) { + // On close we swallow all random exceptions since failure to close is not + // actionable by the user. + Log.e(TAG, "Failed to close " + this + ", Exception=" + e); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } + + try { + mPfd.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort); + throw e; + } + } + + /** Check that the socket was closed properly. */ + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + /** @hide */ + @SystemApi(client = MODULE_LIBRARIES) + public int getResourceId() { + return mResourceId; + } + + @Override + public String toString() { + return new StringBuilder() + .append("UdpEncapsulationSocket{port=") + .append(mPort) + .append(",resourceId=") + .append(mResourceId) + .append("}") + .toString(); + } + }; + + /** + * Open a socket for UDP encapsulation and bind to the given port. + * + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. + * + * @param port a local UDP port + * @return a socket that is bound to the given port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open + */ + // 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. + @NonNull + public UdpEncapsulationSocket openUdpEncapsulationSocket(int port) + throws IOException, ResourceUnavailableException { + /* + * Most range checking is done in the service, but this version of the constructor expects + * a valid port number, and zero cannot be checked after being passed to the service. + */ + if (port == 0) { + throw new IllegalArgumentException("Specified port must be a valid port number!"); + } + try { + return new UdpEncapsulationSocket(mService, port); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } + } + + /** + * Open a socket for UDP encapsulation. + * + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. + * + * <p>The local port of the returned socket can be obtained by calling {@link + * UdpEncapsulationSocket#getPort()}. + * + * @return a socket that is bound to a local port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open + */ + // 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. + @NonNull + public UdpEncapsulationSocket openUdpEncapsulationSocket() + throws IOException, ResourceUnavailableException { + try { + return new UdpEncapsulationSocket(mService, 0); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } + } + + /** + * This class represents an IpSecTunnelInterface + * + * <p>IpSecTunnelInterface objects track tunnel interfaces that serve as + * local endpoints for IPsec tunnels. + * + * <p>Creating an IpSecTunnelInterface creates a device to which IpSecTransforms may be + * applied to provide IPsec security to packets sent through the tunnel. While a tunnel + * cannot be used in standalone mode within Android, the higher layers may use the tunnel + * to create Network objects which are accessible to the Android system. + * @hide + */ + @SystemApi + public static final class IpSecTunnelInterface implements AutoCloseable { + private final String mOpPackageName; + private final IIpSecService mService; + private final InetAddress mRemoteAddress; + private final InetAddress mLocalAddress; + private final Network mUnderlyingNetwork; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private String mInterfaceName; + private int mResourceId = INVALID_RESOURCE_ID; + + /** Get the underlying SPI held by this object. */ + @NonNull + public String getInterfaceName() { + return mInterfaceName; + } + + /** + * Add an address to the IpSecTunnelInterface + * + * <p>Add an address which may be used as the local inner address for + * tunneled traffic. + * + * @param address the local address for traffic inside the tunnel + * @param prefixLen length of the InetAddress prefix + * @hide + */ + @SystemApi + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void addAddress(@NonNull InetAddress address, int prefixLen) throws IOException { + try { + mService.addAddressToTunnelInterface( + mResourceId, new LinkAddress(address, prefixLen), mOpPackageName); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove an address from the IpSecTunnelInterface + * + * <p>Remove an address which was previously added to the IpSecTunnelInterface + * + * @param address to be removed + * @param prefixLen length of the InetAddress prefix + * @hide + */ + @SystemApi + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void removeAddress(@NonNull InetAddress address, int prefixLen) throws IOException { + try { + mService.removeAddressFromTunnelInterface( + mResourceId, new LinkAddress(address, prefixLen), mOpPackageName); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Update the underlying network for this IpSecTunnelInterface. + * + * <p>This new underlying network will be used for all transforms applied AFTER this call is + * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to + * this tunnel interface, traffic will still use the old SA, and be routed on the old + * underlying network. + * + * <p>To migrate IPsec tunnel mode traffic, a caller should: + * + * <ol> + * <li>Update the IpSecTunnelInterface’s underlying network. + * <li>Apply {@link IpSecTransform}(s) with matching addresses to this + * IpSecTunnelInterface. + * </ol> + * + * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel. + * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise + * this method will throw an {@link IllegalArgumentException}. If the + * IpSecTunnelInterface is later added to this network, all outbound traffic will be + * blackholed. + */ + // TODO: b/169171001 Update the documentation when transform migration is supported. + // The purpose of making updating network and applying transforms separate is to leave open + // the possibility to support lossless migration procedures. To do that, Android platform + // will need to support multiple inbound tunnel mode transforms, just like it can support + // multiple transport mode transforms. + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException { + try { + mService.setNetworkForTunnelInterface( + mResourceId, underlyingNetwork, mOpPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service, + @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress, + @NonNull Network underlyingNetwork) + throws ResourceUnavailableException, IOException { + mOpPackageName = ctx.getOpPackageName(); + mService = service; + mLocalAddress = localAddress; + mRemoteAddress = remoteAddress; + mUnderlyingNetwork = underlyingNetwork; + + try { + IpSecTunnelInterfaceResponse result = + mService.createTunnelInterface( + localAddress.getHostAddress(), + remoteAddress.getHostAddress(), + underlyingNetwork, + new Binder(), + mOpPackageName); + switch (result.status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more tunnel interfaces may be allocated by this requester."); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + result.status); + } + mResourceId = result.resourceId; + mInterfaceName = result.interfaceName; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("constructor"); + } + + /** + * Delete an IpSecTunnelInterface + * + * <p>Calling close will deallocate the IpSecTunnelInterface and all of its system + * resources. Any packets bound for this interface either inbound or outbound will + * all be lost. + */ + @Override + public void close() { + try { + mService.deleteTunnelInterface(mResourceId, mOpPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (Exception e) { + // On close we swallow all random exceptions since failure to close is not + // actionable by the user. + Log.e(TAG, "Failed to close " + this + ", Exception=" + e); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } + } + + /** Check that the Interface was closed properly. */ + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + /** @hide */ + @VisibleForTesting + public int getResourceId() { + return mResourceId; + } + + @NonNull + @Override + public String toString() { + return new StringBuilder() + .append("IpSecTunnelInterface{ifname=") + .append(mInterfaceName) + .append(",resourceId=") + .append(mResourceId) + .append("}") + .toString(); + } + } + + /** + * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic. + * + * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the + * underlying network goes away, and the onLost() callback is received. + * + * @param localAddress The local addres of the tunnel + * @param remoteAddress The local addres of the tunnel + * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. + * This network should almost certainly be a network such as WiFi with an L2 address. + * @return a new {@link IpSecManager#IpSecTunnelInterface} with the specified properties + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open + * @hide + */ + @SystemApi + @NonNull + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull InetAddress localAddress, + @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork) + throws ResourceUnavailableException, IOException { + try { + return new IpSecTunnelInterface( + mContext, mService, localAddress, remoteAddress, underlyingNetwork); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } + } + + /** + * Apply an active Tunnel Mode IPsec Transform to a {@link IpSecTunnelInterface}, which will + * tunnel all traffic for the given direction through the underlying 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). + * <p>Applications should probably not use this API directly. + * + * + * @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied + * transform. + * @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which + * the transform will be used. + * @param transform an {@link IpSecTransform} created in tunnel mode + * @throws IOException indicating that the transform could not be applied due to a lower + * layer failure. + * @hide + */ + @SystemApi + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void applyTunnelModeTransform(@NonNull IpSecTunnelInterface tunnel, + @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException { + try { + mService.applyTunnelModeTransform( + tunnel.getResourceId(), direction, + transform.getResourceId(), mContext.getOpPackageName()); + } catch (ServiceSpecificException e) { + throw rethrowCheckedExceptionFromServiceSpecificException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Construct an instance of IpSecManager within an application context. + * + * @param context the application context for this manager + * @hide + */ + public IpSecManager(Context ctx, IIpSecService service) { + mContext = ctx; + mService = checkNotNull(service, "missing service"); + } + + private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) { + // OsConstants are late binding, so switch statements can't be used. + if (sse.errorCode == OsConstants.EINVAL) { + throw new IllegalArgumentException(sse); + } else if (sse.errorCode == OsConstants.EAGAIN) { + throw new IllegalStateException(sse); + } else if (sse.errorCode == OsConstants.EOPNOTSUPP + || sse.errorCode == OsConstants.EPROTONOSUPPORT) { + throw new UnsupportedOperationException(sse); + } + } + + /** + * Convert an Errno SSE to the correct Unchecked exception type. + * + * This method never actually returns. + */ + // package + static RuntimeException + rethrowUncheckedExceptionFromServiceSpecificException(ServiceSpecificException sse) { + maybeHandleServiceSpecificException(sse); + throw new RuntimeException(sse); + } + + /** + * Convert an Errno SSE to the correct Checked or Unchecked exception type. + * + * This method may throw IOException, or it may throw an unchecked exception; it will never + * actually return. + */ + // package + static IOException rethrowCheckedExceptionFromServiceSpecificException( + ServiceSpecificException sse) throws IOException { + // First see if this is an unchecked exception of a type we know. + // If so, then we prefer the unchecked (specific) type of exception. + maybeHandleServiceSpecificException(sse); + // If not, then all we can do is provide the SSE in the form of an IOException. + throw new ErrnoException( + "IpSec encountered errno=" + sse.errorCode, sse.errorCode).rethrowAsIOException(); + } +} diff --git a/framework-t/src/android/net/IpSecSpiResponse.aidl b/framework-t/src/android/net/IpSecSpiResponse.aidl new file mode 100644 index 0000000000..6484a0013c --- /dev/null +++ b/framework-t/src/android/net/IpSecSpiResponse.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 IpSecSpiResponse; diff --git a/framework-t/src/android/net/IpSecSpiResponse.java b/framework-t/src/android/net/IpSecSpiResponse.java new file mode 100644 index 0000000000..f99e570fb7 --- /dev/null +++ b/framework-t/src/android/net/IpSecSpiResponse.java @@ -0,0 +1,78 @@ +/* + * 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; + +/** + * This class is used to return an SPI and corresponding status from the IpSecService to an + * IpSecManager.SecurityParameterIndex. + * + * @hide + */ +public final class IpSecSpiResponse implements Parcelable { + private static final String TAG = "IpSecSpiResponse"; + + public final int resourceId; + public final int status; + public final int spi; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeInt(spi); + } + + public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) { + status = inStatus; + resourceId = inResourceId; + spi = inSpi; + } + + public IpSecSpiResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + } + + private IpSecSpiResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + spi = in.readInt(); + } + + public static final @android.annotation.NonNull Parcelable.Creator<IpSecSpiResponse> CREATOR = + new Parcelable.Creator<IpSecSpiResponse>() { + public IpSecSpiResponse createFromParcel(Parcel in) { + return new IpSecSpiResponse(in); + } + + public IpSecSpiResponse[] newArray(int size) { + return new IpSecSpiResponse[size]; + } + }; +} diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java new file mode 100644 index 0000000000..b48c1fdaf1 --- /dev/null +++ b/framework-t/src/android/net/IpSecTransform.java @@ -0,0 +1,421 @@ +/* + * 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 android.net.IpSecManager.INVALID_RESOURCE_ID; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresFeature; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +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 a transform, which roughly corresponds to an IPsec Security Association. + * + * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} + * object encapsulates the properties and state of an IPsec security association. That includes, + * but is not limited to, algorithm choice, key material, and allocated system resources. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> + */ +public final class IpSecTransform implements AutoCloseable { + private static final String TAG = "IpSecTransform"; + + /** @hide */ + public static final int MODE_TRANSPORT = 0; + + /** @hide */ + public static final int MODE_TUNNEL = 1; + + /** @hide */ + public static final int ENCAP_NONE = 0; + + /** + * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP + * header and payload. This prevents traffic from being interpreted as ESP or IKEv2. + * + * @hide + */ + public static final int ENCAP_ESPINUDP_NON_IKE = 1; + + /** + * IPsec traffic will be encapsulated within UDP as per + * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>. + * + * @hide + */ + public static final int ENCAP_ESPINUDP = 2; + + /** @hide */ + @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE}) + @Retention(RetentionPolicy.SOURCE) + public @interface EncapType {} + + /** @hide */ + @VisibleForTesting + public IpSecTransform(Context context, IpSecConfig config) { + mContext = context; + mConfig = new IpSecConfig(config); + mResourceId = INVALID_RESOURCE_ID; + } + + private IIpSecService getIpSecService() { + IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); + if (b == null) { + throw new RemoteException("Failed to connect to IpSecService") + .rethrowAsRuntimeException(); + } + + return IIpSecService.Stub.asInterface(b); + } + + /** + * Checks the result status and throws an appropriate exception if the status is not Status.OK. + */ + private void checkResultStatus(int status) + throws IOException, IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException { + switch (status) { + case IpSecManager.Status.OK: + return; + // TODO: Pass Error string back from bundle so that errors can be more specific + case IpSecManager.Status.RESOURCE_UNAVAILABLE: + throw new IpSecManager.ResourceUnavailableException( + "Failed to allocate a new IpSecTransform"); + case IpSecManager.Status.SPI_UNAVAILABLE: + Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); + // Fall through + default: + throw new IllegalStateException( + "Failed to Create a Transform with status code " + status); + } + } + + private IpSecTransform activate() + throws IOException, IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException { + synchronized (this) { + try { + IIpSecService svc = getIpSecService(); + IpSecTransformResponse result = svc.createTransform( + mConfig, new Binder(), mContext.getOpPackageName()); + int status = result.status; + checkResultStatus(status); + mResourceId = result.resourceId; + Log.d(TAG, "Added Transform with Id " + mResourceId); + mCloseGuard.open("build"); + } catch (ServiceSpecificException e) { + throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + return this; + } + + /** + * Standard equals. + */ + public boolean equals(@Nullable Object other) { + if (this == other) return true; + if (!(other instanceof IpSecTransform)) return false; + final IpSecTransform rhs = (IpSecTransform) other; + return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId; + } + + /** + * Deactivate this {@code IpSecTransform} and free allocated resources. + * + * <p>Deactivating a transform while it is still applied to a socket will result in errors on + * that socket. Make sure to remove transforms by calling {@link + * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a + * socket will not deactivate it (because one transform may be applied to multiple sockets). + * + * <p>It is safe to call this method on a transform that has already been deactivated. + */ + public void close() { + Log.d(TAG, "Removing Transform with Id " + mResourceId); + + // Always safe to attempt cleanup + if (mResourceId == INVALID_RESOURCE_ID) { + mCloseGuard.close(); + return; + } + try { + IIpSecService svc = getIpSecService(); + svc.deleteTransform(mResourceId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } catch (Exception e) { + // On close we swallow all random exceptions since failure to close is not + // actionable by the user. + Log.e(TAG, "Failed to close " + this + ", Exception=" + e); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } + } + + /** Check that the transform was closed properly. */ + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + /* Package */ + IpSecConfig getConfig() { + return mConfig; + } + + private final IpSecConfig mConfig; + private int mResourceId; + private final Context mContext; + private final CloseGuard mCloseGuard = CloseGuard.get(); + + /** @hide */ + @VisibleForTesting + public int getResourceId() { + return mResourceId; + } + + /** + * A callback class to provide status information regarding a NAT-T keepalive session + * + * <p>Use this callback to receive status information regarding a NAT-T keepalive session + * by registering it when calling {@link #startNattKeepalive}. + * + * @hide + */ + public static class NattKeepaliveCallback { + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = 1; + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = 2; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = 3; + + /** The requested keepalive was successfully started. */ + public void onStarted() {} + /** The keepalive was successfully stopped. */ + public void onStopped() {} + /** An error occurred. */ + public void onError(int error) {} + } + + /** This class is used to build {@link IpSecTransform} objects. */ + public static class Builder { + private Context mContext; + private IpSecConfig mConfig; + + /** + * Set the encryption algorithm. + * + * <p>Encryption is mutually exclusive with authenticated encryption. + * + * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. + */ + @NonNull + public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. + Preconditions.checkNotNull(algo); + mConfig.setEncryption(algo); + return this; + } + + /** + * Set the authentication (integrity) algorithm. + * + * <p>Authentication is mutually exclusive with authenticated encryption. + * + * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. + */ + @NonNull + public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. + Preconditions.checkNotNull(algo); + mConfig.setAuthentication(algo); + return this; + } + + /** + * Set the authenticated encryption algorithm. + * + * <p>The Authenticated Encryption (AE) class of algorithms are also known as + * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode + * algorithms (as referred to in + * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). + * + * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * + * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to + * be applied. + */ + @NonNull + public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { + Preconditions.checkNotNull(algo); + mConfig.setAuthenticatedEncryption(algo); + return this; + } + + /** + * Add UDP encapsulation to an IPv4 transform. + * + * <p>This allows IPsec traffic to pass through a NAT. + * + * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec + * ESP Packets</a> + * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, + * NAT Traversal of IKEv2</a> + * @param localSocket a socket for sending and receiving encapsulated traffic + * @param remotePort the UDP port number of the remote host that will send and receive + * encapsulated traffic. In the case of IKEv2, this should be port 4500. + */ + @NonNull + public IpSecTransform.Builder setIpv4Encapsulation( + @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + Preconditions.checkNotNull(localSocket); + mConfig.setEncapType(ENCAP_ESPINUDP); + if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); + } + mConfig.setEncapSocketResourceId(localSocket.getResourceId()); + mConfig.setEncapRemotePort(remotePort); + return this; + } + + /** + * Build a transport mode {@link IpSecTransform}. + * + * <p>This builds and activates a transport mode transform. Note that an active transform + * will not affect any network traffic until it has been applied to one or more sockets. + * + * @see IpSecManager#applyTransportModeTransform + * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use + * this transform; this address must belong to the Network used by all sockets that + * utilize this transform; if provided, then only traffic originating from the + * specified source address will be processed. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic + * @throws IllegalArgumentException indicating that a particular combination of transform + * properties is invalid + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors + */ + @NonNull + public IpSecTransform buildTransportModeTransform( + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) + throws IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException, IOException { + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); + } + mConfig.setMode(MODE_TRANSPORT); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); + // FIXME: modifying a builder after calling build can change the built transform. + 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 sourceAddress the {@link InetAddress} that provides the source address 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 spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic + * @throws IllegalArgumentException indicating that a particular combination of transform + * properties is invalid. + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors + * @hide + */ + @SystemApi + @NonNull + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public IpSecTransform buildTunnelModeTransform( + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) + throws IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException, IOException { + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); + } + mConfig.setMode(MODE_TUNNEL); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); + return new IpSecTransform(mContext, mConfig).activate(); + } + + /** + * Create a new IpSecTransform.Builder. + * + * @param context current context + */ + public Builder(@NonNull Context context) { + Preconditions.checkNotNull(context); + mContext = context; + mConfig = new IpSecConfig(); + } + } + + @Override + public String toString() { + return new StringBuilder() + .append("IpSecTransform{resourceId=") + .append(mResourceId) + .append("}") + .toString(); + } +} diff --git a/framework-t/src/android/net/IpSecTransformResponse.aidl b/framework-t/src/android/net/IpSecTransformResponse.aidl new file mode 100644 index 0000000000..546230d5b8 --- /dev/null +++ b/framework-t/src/android/net/IpSecTransformResponse.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 IpSecTransformResponse; diff --git a/framework-t/src/android/net/IpSecTransformResponse.java b/framework-t/src/android/net/IpSecTransformResponse.java new file mode 100644 index 0000000000..a38488954f --- /dev/null +++ b/framework-t/src/android/net/IpSecTransformResponse.java @@ -0,0 +1,73 @@ +/* + * 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; + +/** + * This class is used to return an IpSecTransform resource Id and and corresponding status from the + * IpSecService to an IpSecTransform object. + * + * @hide + */ +public final class IpSecTransformResponse implements Parcelable { + private static final String TAG = "IpSecTransformResponse"; + + public final int resourceId; + public final int status; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + } + + public IpSecTransformResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + } + + public IpSecTransformResponse(int inStatus, int inResourceId) { + status = inStatus; + resourceId = inResourceId; + } + + private IpSecTransformResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + } + + public static final @android.annotation.NonNull Parcelable.Creator<IpSecTransformResponse> CREATOR = + new Parcelable.Creator<IpSecTransformResponse>() { + public IpSecTransformResponse createFromParcel(Parcel in) { + return new IpSecTransformResponse(in); + } + + public IpSecTransformResponse[] newArray(int size) { + return new IpSecTransformResponse[size]; + } + }; +} diff --git a/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl new file mode 100644 index 0000000000..7239221415 --- /dev/null +++ b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** @hide */ +parcelable IpSecTunnelInterfaceResponse; diff --git a/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java new file mode 100644 index 0000000000..e3411e003d --- /dev/null +++ b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status + * from the IpSecService to an IpSecTunnelInterface object. + * + * @hide + */ +public final class IpSecTunnelInterfaceResponse implements Parcelable { + private static final String TAG = "IpSecTunnelInterfaceResponse"; + + public final int resourceId; + public final String interfaceName; + public final int status; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeString(interfaceName); + } + + public IpSecTunnelInterfaceResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + interfaceName = ""; + } + + public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) { + status = inStatus; + resourceId = inResourceId; + interfaceName = inInterfaceName; + } + + private IpSecTunnelInterfaceResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + interfaceName = in.readString(); + } + + public static final @android.annotation.NonNull Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR = + new Parcelable.Creator<IpSecTunnelInterfaceResponse>() { + public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) { + return new IpSecTunnelInterfaceResponse(in); + } + + public IpSecTunnelInterfaceResponse[] newArray(int size) { + return new IpSecTunnelInterfaceResponse[size]; + } + }; +} diff --git a/framework-t/src/android/net/IpSecUdpEncapResponse.aidl b/framework-t/src/android/net/IpSecUdpEncapResponse.aidl new file mode 100644 index 0000000000..5e451f3651 --- /dev/null +++ b/framework-t/src/android/net/IpSecUdpEncapResponse.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 IpSecUdpEncapResponse; diff --git a/framework-t/src/android/net/IpSecUdpEncapResponse.java b/framework-t/src/android/net/IpSecUdpEncapResponse.java new file mode 100644 index 0000000000..4e7ba9b515 --- /dev/null +++ b/framework-t/src/android/net/IpSecUdpEncapResponse.java @@ -0,0 +1,96 @@ +/* + * 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.ParcelFileDescriptor; +import android.os.Parcelable; +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * This class is used to return a UDP Socket and corresponding status from the IpSecService to an + * IpSecManager.UdpEncapsulationSocket. + * + * @hide + */ +public final class IpSecUdpEncapResponse implements Parcelable { + private static final String TAG = "IpSecUdpEncapResponse"; + + public final int resourceId; + public final int port; + public final int status; + // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor + // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD + // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate + // on writeParcel() by setting the flag to do close-on-write. + // TODO: tests to ensure this doesn't leak + public final ParcelFileDescriptor fileDescriptor; + + // Parcelable Methods + + @Override + public int describeContents() { + return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeInt(port); + out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } + + public IpSecUdpEncapResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + port = -1; + fileDescriptor = null; // yes I know it's redundant, but readability + } + + public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd) + throws IOException { + if (inStatus == IpSecManager.Status.OK && inFd == null) { + throw new IllegalArgumentException("Valid status implies FD must be non-null"); + } + status = inStatus; + resourceId = inResourceId; + port = inPort; + fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null; + } + + private IpSecUdpEncapResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + port = in.readInt(); + fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + } + + public static final @android.annotation.NonNull Parcelable.Creator<IpSecUdpEncapResponse> CREATOR = + new Parcelable.Creator<IpSecUdpEncapResponse>() { + public IpSecUdpEncapResponse createFromParcel(Parcel in) { + return new IpSecUdpEncapResponse(in); + } + + public IpSecUdpEncapResponse[] newArray(int size) { + return new IpSecUdpEncapResponse[size]; + } + }; +} |
