diff options
| author | Treehugger Robot <treehugger-gerrit@google.com> | 2021-12-14 09:16:43 +0000 |
|---|---|---|
| committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-12-14 09:16:43 +0000 |
| commit | db045cc3861f191550139baf1a4ca471ee9dc12b (patch) | |
| tree | 885a15b460e2a5f1e5c989e5dbd630d8b02a39e2 /framework-t/src/android/net/IpSecTransform.java | |
| parent | 5be67adab48167890abd053e35de8332cf8aa9c2 (diff) | |
| parent | 89bb4b174110f108eccd0881b617a78f2f771e6d (diff) | |
Merge "Move IpSec associated files to f/b/packages/ConnectivityT" am: 1bb6b56c6b am: 76187327f6 am: 89bb4b1741
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1909906
Change-Id: I4d11e1e71f50786aa2853460729e17b89c109008
Diffstat (limited to 'framework-t/src/android/net/IpSecTransform.java')
| -rw-r--r-- | framework-t/src/android/net/IpSecTransform.java | 421 |
1 files changed, 421 insertions, 0 deletions
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(); + } +} |
