Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.net;
     17 
     18 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
     19 
     20 import static com.android.internal.util.Preconditions.checkNotNull;
     21 
     22 import android.annotation.IntDef;
     23 import android.annotation.NonNull;
     24 import android.annotation.RequiresPermission;
     25 import android.content.Context;
     26 import android.os.Binder;
     27 import android.os.Handler;
     28 import android.os.IBinder;
     29 import android.os.RemoteException;
     30 import android.os.ServiceManager;
     31 import android.os.ServiceSpecificException;
     32 import android.util.Log;
     33 
     34 import com.android.internal.annotations.VisibleForTesting;
     35 import com.android.internal.util.Preconditions;
     36 
     37 import dalvik.system.CloseGuard;
     38 
     39 import java.io.IOException;
     40 import java.lang.annotation.Retention;
     41 import java.lang.annotation.RetentionPolicy;
     42 import java.net.InetAddress;
     43 
     44 /**
     45  * This class represents a transform, which roughly corresponds to an IPsec Security Association.
     46  *
     47  * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
     48  * object encapsulates the properties and state of an IPsec security association. That includes,
     49  * but is not limited to, algorithm choice, key material, and allocated system resources.
     50  *
     51  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
     52  *     Internet Protocol</a>
     53  */
     54 public final class IpSecTransform implements AutoCloseable {
     55     private static final String TAG = "IpSecTransform";
     56 
     57     /** @hide */
     58     public static final int MODE_TRANSPORT = 0;
     59 
     60     /** @hide */
     61     public static final int MODE_TUNNEL = 1;
     62 
     63     /** @hide */
     64     public static final int ENCAP_NONE = 0;
     65 
     66     /**
     67      * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
     68      * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
     69      *
     70      * @hide
     71      */
     72     public static final int ENCAP_ESPINUDP_NON_IKE = 1;
     73 
     74     /**
     75      * IPsec traffic will be encapsulated within UDP as per
     76      * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
     77      *
     78      * @hide
     79      */
     80     public static final int ENCAP_ESPINUDP = 2;
     81 
     82     /** @hide */
     83     @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
     84     @Retention(RetentionPolicy.SOURCE)
     85     public @interface EncapType {}
     86 
     87     /** @hide */
     88     @VisibleForTesting
     89     public IpSecTransform(Context context, IpSecConfig config) {
     90         mContext = context;
     91         mConfig = new IpSecConfig(config);
     92         mResourceId = INVALID_RESOURCE_ID;
     93     }
     94 
     95     private IIpSecService getIpSecService() {
     96         IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
     97         if (b == null) {
     98             throw new RemoteException("Failed to connect to IpSecService")
     99                     .rethrowAsRuntimeException();
    100         }
    101 
    102         return IIpSecService.Stub.asInterface(b);
    103     }
    104 
    105     /**
    106      * Checks the result status and throws an appropriate exception if the status is not Status.OK.
    107      */
    108     private void checkResultStatus(int status)
    109             throws IOException, IpSecManager.ResourceUnavailableException,
    110                     IpSecManager.SpiUnavailableException {
    111         switch (status) {
    112             case IpSecManager.Status.OK:
    113                 return;
    114                 // TODO: Pass Error string back from bundle so that errors can be more specific
    115             case IpSecManager.Status.RESOURCE_UNAVAILABLE:
    116                 throw new IpSecManager.ResourceUnavailableException(
    117                         "Failed to allocate a new IpSecTransform");
    118             case IpSecManager.Status.SPI_UNAVAILABLE:
    119                 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
    120                 // Fall through
    121             default:
    122                 throw new IllegalStateException(
    123                         "Failed to Create a Transform with status code " + status);
    124         }
    125     }
    126 
    127     private IpSecTransform activate()
    128             throws IOException, IpSecManager.ResourceUnavailableException,
    129                     IpSecManager.SpiUnavailableException {
    130         synchronized (this) {
    131             try {
    132                 IIpSecService svc = getIpSecService();
    133                 IpSecTransformResponse result = svc.createTransform(
    134                         mConfig, new Binder(), mContext.getOpPackageName());
    135                 int status = result.status;
    136                 checkResultStatus(status);
    137                 mResourceId = result.resourceId;
    138                 Log.d(TAG, "Added Transform with Id " + mResourceId);
    139                 mCloseGuard.open("build");
    140             } catch (ServiceSpecificException e) {
    141                 throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
    142             } catch (RemoteException e) {
    143                 throw e.rethrowAsRuntimeException();
    144             }
    145         }
    146 
    147         return this;
    148     }
    149 
    150     /**
    151      * Equals method used for testing
    152      *
    153      * @hide
    154      */
    155     @VisibleForTesting
    156     public static boolean equals(IpSecTransform lhs, IpSecTransform rhs) {
    157         if (lhs == null || rhs == null) return (lhs == rhs);
    158         return IpSecConfig.equals(lhs.getConfig(), rhs.getConfig())
    159                 && lhs.mResourceId == rhs.mResourceId;
    160     }
    161 
    162     /**
    163      * Deactivate this {@code IpSecTransform} and free allocated resources.
    164      *
    165      * <p>Deactivating a transform while it is still applied to a socket will result in errors on
    166      * that socket. Make sure to remove transforms by calling {@link
    167      * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
    168      * socket will not deactivate it (because one transform may be applied to multiple sockets).
    169      *
    170      * <p>It is safe to call this method on a transform that has already been deactivated.
    171      */
    172     public void close() {
    173         Log.d(TAG, "Removing Transform with Id " + mResourceId);
    174 
    175         // Always safe to attempt cleanup
    176         if (mResourceId == INVALID_RESOURCE_ID) {
    177             mCloseGuard.close();
    178             return;
    179         }
    180         try {
    181             IIpSecService svc = getIpSecService();
    182             svc.deleteTransform(mResourceId);
    183             stopNattKeepalive();
    184         } catch (RemoteException e) {
    185             throw e.rethrowAsRuntimeException();
    186         } catch (Exception e) {
    187             // On close we swallow all random exceptions since failure to close is not
    188             // actionable by the user.
    189             Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
    190         } finally {
    191             mResourceId = INVALID_RESOURCE_ID;
    192             mCloseGuard.close();
    193         }
    194     }
    195 
    196     /** Check that the transform was closed properly. */
    197     @Override
    198     protected void finalize() throws Throwable {
    199         if (mCloseGuard != null) {
    200             mCloseGuard.warnIfOpen();
    201         }
    202         close();
    203     }
    204 
    205     /* Package */
    206     IpSecConfig getConfig() {
    207         return mConfig;
    208     }
    209 
    210     private final IpSecConfig mConfig;
    211     private int mResourceId;
    212     private final Context mContext;
    213     private final CloseGuard mCloseGuard = CloseGuard.get();
    214     private ConnectivityManager.PacketKeepalive mKeepalive;
    215     private Handler mCallbackHandler;
    216     private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
    217             new ConnectivityManager.PacketKeepaliveCallback() {
    218 
    219                 @Override
    220                 public void onStarted() {
    221                     synchronized (this) {
    222                         mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted());
    223                     }
    224                 }
    225 
    226                 @Override
    227                 public void onStopped() {
    228                     synchronized (this) {
    229                         mKeepalive = null;
    230                         mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped());
    231                     }
    232                 }
    233 
    234                 @Override
    235                 public void onError(int error) {
    236                     synchronized (this) {
    237                         mKeepalive = null;
    238                         mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error));
    239                     }
    240                 }
    241             };
    242 
    243     private NattKeepaliveCallback mUserKeepaliveCallback;
    244 
    245     /** @hide */
    246     @VisibleForTesting
    247     public int getResourceId() {
    248         return mResourceId;
    249     }
    250 
    251     /**
    252      * A callback class to provide status information regarding a NAT-T keepalive session
    253      *
    254      * <p>Use this callback to receive status information regarding a NAT-T keepalive session
    255      * by registering it when calling {@link #startNattKeepalive}.
    256      *
    257      * @hide
    258      */
    259     public static class NattKeepaliveCallback {
    260         /** The specified {@code Network} is not connected. */
    261         public static final int ERROR_INVALID_NETWORK = 1;
    262         /** The hardware does not support this request. */
    263         public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
    264         /** The hardware returned an error. */
    265         public static final int ERROR_HARDWARE_ERROR = 3;
    266 
    267         /** The requested keepalive was successfully started. */
    268         public void onStarted() {}
    269         /** The keepalive was successfully stopped. */
    270         public void onStopped() {}
    271         /** An error occurred. */
    272         public void onError(int error) {}
    273     }
    274 
    275     /**
    276      * Start a NAT-T keepalive session for the current transform.
    277      *
    278      * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides
    279      * a power efficient mechanism of sending NAT-T packets at a specified interval.
    280      *
    281      * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status
    282      *      information about the requested NAT-T keepalive session.
    283      * @param intervalSeconds the interval between NAT-T keepalives being sent. The
    284      *      the allowed range is between 20 and 3600 seconds.
    285      * @param handler a handler on which to post callbacks when received.
    286      *
    287      * @hide
    288      */
    289     @RequiresPermission(anyOf = {
    290             android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
    291             android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
    292     })
    293     public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback,
    294             int intervalSeconds, @NonNull Handler handler) throws IOException {
    295         checkNotNull(userCallback);
    296         if (intervalSeconds < 20 || intervalSeconds > 3600) {
    297             throw new IllegalArgumentException("Invalid NAT-T keepalive interval");
    298         }
    299         checkNotNull(handler);
    300         if (mResourceId == INVALID_RESOURCE_ID) {
    301             throw new IllegalStateException(
    302                     "Packet keepalive cannot be started for an inactive transform");
    303         }
    304 
    305         synchronized (mKeepaliveCallback) {
    306             if (mKeepaliveCallback != null) {
    307                 throw new IllegalStateException("Keepalive already active");
    308             }
    309 
    310             mUserKeepaliveCallback = userCallback;
    311             ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
    312                     Context.CONNECTIVITY_SERVICE);
    313             mKeepalive = cm.startNattKeepalive(
    314                     mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback,
    315                     NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()),
    316                     4500, // FIXME urgently, we need to get the port number from the Encap socket
    317                     NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress()));
    318             mCallbackHandler = handler;
    319         }
    320     }
    321 
    322     /**
    323      * Stop an ongoing NAT-T keepalive session.
    324      *
    325      * Calling this API will request that an ongoing NAT-T keepalive session be terminated.
    326      * If this API is not called when a Transform is closed, the underlying NAT-T session will
    327      * be terminated automatically.
    328      *
    329      * @hide
    330      */
    331     @RequiresPermission(anyOf = {
    332             android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
    333             android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
    334     })
    335     public void stopNattKeepalive() {
    336         synchronized (mKeepaliveCallback) {
    337             if (mKeepalive == null) {
    338                 Log.e(TAG, "No active keepalive to stop");
    339                 return;
    340             }
    341             mKeepalive.stop();
    342         }
    343     }
    344 
    345     /** This class is used to build {@link IpSecTransform} objects. */
    346     public static class Builder {
    347         private Context mContext;
    348         private IpSecConfig mConfig;
    349 
    350         /**
    351          * Set the encryption algorithm.
    352          *
    353          * <p>Encryption is mutually exclusive with authenticated encryption.
    354          *
    355          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
    356          */
    357         @NonNull
    358         public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
    359             // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
    360             Preconditions.checkNotNull(algo);
    361             mConfig.setEncryption(algo);
    362             return this;
    363         }
    364 
    365         /**
    366          * Set the authentication (integrity) algorithm.
    367          *
    368          * <p>Authentication is mutually exclusive with authenticated encryption.
    369          *
    370          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
    371          */
    372         @NonNull
    373         public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
    374             // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
    375             Preconditions.checkNotNull(algo);
    376             mConfig.setAuthentication(algo);
    377             return this;
    378         }
    379 
    380         /**
    381          * Set the authenticated encryption algorithm.
    382          *
    383          * <p>The Authenticated Encryption (AE) class of algorithms are also known as
    384          * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
    385          * algorithms (as referred to in
    386          * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
    387          *
    388          * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
    389          *
    390          * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
    391          *     be applied.
    392          */
    393         @NonNull
    394         public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
    395             Preconditions.checkNotNull(algo);
    396             mConfig.setAuthenticatedEncryption(algo);
    397             return this;
    398         }
    399 
    400         /**
    401          * Add UDP encapsulation to an IPv4 transform.
    402          *
    403          * <p>This allows IPsec traffic to pass through a NAT.
    404          *
    405          * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
    406          *     ESP Packets</a>
    407          * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
    408          *     NAT Traversal of IKEv2</a>
    409          * @param localSocket a socket for sending and receiving encapsulated traffic
    410          * @param remotePort the UDP port number of the remote host that will send and receive
    411          *     encapsulated traffic. In the case of IKEv2, this should be port 4500.
    412          */
    413         @NonNull
    414         public IpSecTransform.Builder setIpv4Encapsulation(
    415                 @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
    416             Preconditions.checkNotNull(localSocket);
    417             mConfig.setEncapType(ENCAP_ESPINUDP);
    418             if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
    419                 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
    420             }
    421             mConfig.setEncapSocketResourceId(localSocket.getResourceId());
    422             mConfig.setEncapRemotePort(remotePort);
    423             return this;
    424         }
    425 
    426         /**
    427          * Build a transport mode {@link IpSecTransform}.
    428          *
    429          * <p>This builds and activates a transport mode transform. Note that an active transform
    430          * will not affect any network traffic until it has been applied to one or more sockets.
    431          *
    432          * @see IpSecManager#applyTransportModeTransform
    433          * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
    434          *     this transform; this address must belong to the Network used by all sockets that
    435          *     utilize this transform; if provided, then only traffic originating from the
    436          *     specified source address will be processed.
    437          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
    438          *     traffic
    439          * @throws IllegalArgumentException indicating that a particular combination of transform
    440          *     properties is invalid
    441          * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
    442          *     are active
    443          * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
    444          *     collides with an existing transform
    445          * @throws IOException indicating other errors
    446          */
    447         @NonNull
    448         public IpSecTransform buildTransportModeTransform(
    449                 @NonNull InetAddress sourceAddress,
    450                 @NonNull IpSecManager.SecurityParameterIndex spi)
    451                 throws IpSecManager.ResourceUnavailableException,
    452                         IpSecManager.SpiUnavailableException, IOException {
    453             Preconditions.checkNotNull(sourceAddress);
    454             Preconditions.checkNotNull(spi);
    455             if (spi.getResourceId() == INVALID_RESOURCE_ID) {
    456                 throw new IllegalArgumentException("Invalid SecurityParameterIndex");
    457             }
    458             mConfig.setMode(MODE_TRANSPORT);
    459             mConfig.setSourceAddress(sourceAddress.getHostAddress());
    460             mConfig.setSpiResourceId(spi.getResourceId());
    461             // FIXME: modifying a builder after calling build can change the built transform.
    462             return new IpSecTransform(mContext, mConfig).activate();
    463         }
    464 
    465         /**
    466          * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
    467          * parameters have interdependencies that are checked at build time.
    468          *
    469          * @param sourceAddress the {@link InetAddress} that provides the source address for this
    470          *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
    471          *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
    472          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
    473          *     traffic
    474          * @throws IllegalArgumentException indicating that a particular combination of transform
    475          *     properties is invalid.
    476          * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
    477          *     are active
    478          * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
    479          *     collides with an existing transform
    480          * @throws IOException indicating other errors
    481          * @hide
    482          */
    483         @NonNull
    484         @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
    485         public IpSecTransform buildTunnelModeTransform(
    486                 @NonNull InetAddress sourceAddress,
    487                 @NonNull IpSecManager.SecurityParameterIndex spi)
    488                 throws IpSecManager.ResourceUnavailableException,
    489                         IpSecManager.SpiUnavailableException, IOException {
    490             Preconditions.checkNotNull(sourceAddress);
    491             Preconditions.checkNotNull(spi);
    492             if (spi.getResourceId() == INVALID_RESOURCE_ID) {
    493                 throw new IllegalArgumentException("Invalid SecurityParameterIndex");
    494             }
    495             mConfig.setMode(MODE_TUNNEL);
    496             mConfig.setSourceAddress(sourceAddress.getHostAddress());
    497             mConfig.setSpiResourceId(spi.getResourceId());
    498             return new IpSecTransform(mContext, mConfig).activate();
    499         }
    500 
    501         /**
    502          * Create a new IpSecTransform.Builder.
    503          *
    504          * @param context current context
    505          */
    506         public Builder(@NonNull Context context) {
    507             Preconditions.checkNotNull(context);
    508             mContext = context;
    509             mConfig = new IpSecConfig();
    510         }
    511     }
    512 
    513     @Override
    514     public String toString() {
    515         return new StringBuilder()
    516             .append("IpSecTransform{resourceId=")
    517             .append(mResourceId)
    518             .append("}")
    519             .toString();
    520     }
    521 }
    522