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 import static android.net.IpSecManager.KEY_RESOURCE_ID;
20 import static android.net.IpSecManager.KEY_STATUS;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.SystemApi;
25 import android.content.Context;
26 import android.os.Binder;
27 import android.os.Bundle;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.util.Log;
32 import com.android.internal.util.Preconditions;
33 import dalvik.system.CloseGuard;
34 import java.io.IOException;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.net.InetAddress;
38 
39 /**
40  * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
41  *
42  * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
43  * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
44  * transform may be disabled automatically, with likely undesirable results.
45  *
46  * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
47  * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
48  *
49  * @hide
50  */
51 public final class IpSecTransform implements AutoCloseable {
52     private static final String TAG = "IpSecTransform";
53 
54     /**
55      * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
56      * to traffic towards the host.
57      */
58     public static final int DIRECTION_IN = 0;
59 
60     /**
61      * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
62      * to traffic from the host.
63      */
64     public static final int DIRECTION_OUT = 1;
65 
66     /** @hide */
67     @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface TransformDirection {}
70 
71     /** @hide */
72     private static final int MODE_TUNNEL = 0;
73 
74     /** @hide */
75     private static final int MODE_TRANSPORT = 1;
76 
77     /** @hide */
78     public static final int ENCAP_NONE = 0;
79 
80     /**
81      * IpSec traffic will be encapsulated within UDP as per <a
82      * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
83      *
84      * @hide
85      */
86     public static final int ENCAP_ESPINUDP = 1;
87 
88     /**
89      * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
90      * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
91      *
92      * @hide
93      */
94     public static final int ENCAP_ESPINUDP_NONIKE = 2;
95 
96     /** @hide */
97     @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE})
98     @Retention(RetentionPolicy.SOURCE)
99     public @interface EncapType {}
100 
IpSecTransform(Context context, IpSecConfig config)101     private IpSecTransform(Context context, IpSecConfig config) {
102         mContext = context;
103         mConfig = config;
104         mResourceId = INVALID_RESOURCE_ID;
105     }
106 
getIpSecService()107     private IIpSecService getIpSecService() {
108         IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
109         if (b == null) {
110             throw new RemoteException("Failed to connect to IpSecService")
111                     .rethrowAsRuntimeException();
112         }
113 
114         return IIpSecService.Stub.asInterface(b);
115     }
116 
checkResultStatusAndThrow(int status)117     private void checkResultStatusAndThrow(int status)
118             throws IOException, IpSecManager.ResourceUnavailableException,
119                     IpSecManager.SpiUnavailableException {
120         switch (status) {
121             case IpSecManager.Status.OK:
122                 return;
123                 // TODO: Pass Error string back from bundle so that errors can be more specific
124             case IpSecManager.Status.RESOURCE_UNAVAILABLE:
125                 throw new IpSecManager.ResourceUnavailableException(
126                         "Failed to allocate a new IpSecTransform");
127             case IpSecManager.Status.SPI_UNAVAILABLE:
128                 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
129                 // Fall through
130             default:
131                 throw new IllegalStateException(
132                         "Failed to Create a Transform with status code " + status);
133         }
134     }
135 
activate()136     private IpSecTransform activate()
137             throws IOException, IpSecManager.ResourceUnavailableException,
138                     IpSecManager.SpiUnavailableException {
139         synchronized (this) {
140             try {
141                 IIpSecService svc = getIpSecService();
142                 Bundle result = svc.createTransportModeTransform(mConfig, new Binder());
143                 int status = result.getInt(KEY_STATUS);
144                 checkResultStatusAndThrow(status);
145                 mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
146 
147                 /* Keepalive will silently fail if not needed by the config; but, if needed and
148                  * it fails to start, we need to bail because a transform will not be reliable
149                  * to use if keepalive is expected to offload and fails.
150                  */
151                 // FIXME: if keepalive fails, we need to fail spectacularly
152                 startKeepalive(mContext);
153                 Log.d(TAG, "Added Transform with Id " + mResourceId);
154                 mCloseGuard.open("build");
155             } catch (RemoteException e) {
156                 throw e.rethrowAsRuntimeException();
157             }
158         }
159 
160         return this;
161     }
162 
163     /**
164      * Deactivate an IpSecTransform and free all resources for that transform that are managed by
165      * the system for this Transform.
166      *
167      * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
168      * refusing to send or receive data. This method will silently succeed if the specified
169      * transform has already been removed; thus, it is always safe to attempt cleanup when a
170      * transform is no longer needed.
171      */
close()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             /* Order matters here because the keepalive is best-effort but could fail in some
182              * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we
183              * still want to clear out the transform.
184              */
185             IIpSecService svc = getIpSecService();
186             svc.deleteTransportModeTransform(mResourceId);
187             stopKeepalive();
188         } catch (RemoteException e) {
189             throw e.rethrowAsRuntimeException();
190         } finally {
191             mResourceId = INVALID_RESOURCE_ID;
192             mCloseGuard.close();
193         }
194     }
195 
196     @Override
finalize()197     protected void finalize() throws Throwable {
198         if (mCloseGuard != null) {
199             mCloseGuard.warnIfOpen();
200         }
201         close();
202     }
203 
204     /* Package */
getConfig()205     IpSecConfig getConfig() {
206         return mConfig;
207     }
208 
209     private final IpSecConfig mConfig;
210     private int mResourceId;
211     private final Context mContext;
212     private final CloseGuard mCloseGuard = CloseGuard.get();
213     private ConnectivityManager.PacketKeepalive mKeepalive;
214     private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
215     private Object mKeepaliveSyncLock = new Object();
216     private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
217             new ConnectivityManager.PacketKeepaliveCallback() {
218 
219                 @Override
220                 public void onStarted() {
221                     synchronized (mKeepaliveSyncLock) {
222                         mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS;
223                         mKeepaliveSyncLock.notifyAll();
224                     }
225                 }
226 
227                 @Override
228                 public void onStopped() {
229                     synchronized (mKeepaliveSyncLock) {
230                         mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
231                         mKeepaliveSyncLock.notifyAll();
232                     }
233                 }
234 
235                 @Override
236                 public void onError(int error) {
237                     synchronized (mKeepaliveSyncLock) {
238                         mKeepaliveStatus = error;
239                         mKeepaliveSyncLock.notifyAll();
240                     }
241                 }
242             };
243 
244     /* Package */
startKeepalive(Context c)245     void startKeepalive(Context c) {
246         // FIXME: NO_KEEPALIVE needs to be a constant
247         if (mConfig.getNattKeepaliveInterval() == 0) {
248             return;
249         }
250 
251         ConnectivityManager cm =
252                 (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
253 
254         if (mKeepalive != null) {
255             Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
256             return;
257         }
258 
259         synchronized (mKeepaliveSyncLock) {
260             mKeepalive =
261                     cm.startNattKeepalive(
262                             mConfig.getNetwork(),
263                             mConfig.getNattKeepaliveInterval(),
264                             mKeepaliveCallback,
265                             mConfig.getLocalAddress(),
266                             mConfig.getEncapLocalPort(),
267                             mConfig.getRemoteAddress());
268             try {
269                 // FIXME: this is still a horrible way to fudge the synchronous callback
270                 mKeepaliveSyncLock.wait(2000);
271             } catch (InterruptedException e) {
272             }
273         }
274         if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
275             throw new UnsupportedOperationException("Packet Keepalive cannot be started");
276         }
277     }
278 
279     /* Package */
getResourceId()280     int getResourceId() {
281         return mResourceId;
282     }
283 
284     /* Package */
stopKeepalive()285     void stopKeepalive() {
286         if (mKeepalive == null) {
287             return;
288         }
289         mKeepalive.stop();
290         synchronized (mKeepaliveSyncLock) {
291             if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
292                 try {
293                     mKeepaliveSyncLock.wait(2000);
294                 } catch (InterruptedException e) {
295                 }
296             }
297         }
298     }
299 
300     /**
301      * Builder object to facilitate the creation of IpSecTransform objects.
302      *
303      * <p>Apply additional properties to the transform and then call a build() method to return an
304      * IpSecTransform object.
305      *
306      * @see Builder#buildTransportModeTransform(InetAddress)
307      */
308     public static class Builder {
309         private Context mContext;
310         private IpSecConfig mConfig;
311 
312         /**
313          * Add an encryption algorithm to the transform for the given direction.
314          *
315          * <p>If encryption is set for a given direction without also providing an SPI for that
316          * direction, creation of an IpSecTransform will fail upon calling a build() method.
317          *
318          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
319          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
320          */
setEncryption( @ransformDirection int direction, IpSecAlgorithm algo)321         public IpSecTransform.Builder setEncryption(
322                 @TransformDirection int direction, IpSecAlgorithm algo) {
323             mConfig.flow[direction].encryption = algo;
324             return this;
325         }
326 
327         /**
328          * Add an authentication/integrity algorithm to the transform.
329          *
330          * <p>If authentication is set for a given direction without also providing an SPI for that
331          * direction, creation of an IpSecTransform will fail upon calling a build() method.
332          *
333          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
334          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
335          */
setAuthentication( @ransformDirection int direction, IpSecAlgorithm algo)336         public IpSecTransform.Builder setAuthentication(
337                 @TransformDirection int direction, IpSecAlgorithm algo) {
338             mConfig.flow[direction].authentication = algo;
339             return this;
340         }
341 
342         /**
343          * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
344          * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
345          * given destination address.
346          *
347          * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
348          * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
349          * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
350          * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
351          *
352          * <p>Unless an SPI is set for a given direction, traffic in that direction will be
353          * sent/received without any IPsec applied.
354          *
355          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
356          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
357          *     traffic
358          */
setSpi( @ransformDirection int direction, IpSecManager.SecurityParameterIndex spi)359         public IpSecTransform.Builder setSpi(
360                 @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
361             // TODO: convert to using the resource Id of the SPI. Then build() can validate
362             // the owner in the IpSecService
363             mConfig.flow[direction].spi = spi.getSpi();
364             return this;
365         }
366 
367         /**
368          * Specify the network on which this transform will emit its traffic; (otherwise it will
369          * emit on the default network).
370          *
371          * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
372          * tunnel mode.
373          *
374          * @hide
375          */
376         @SystemApi
setUnderlyingNetwork(Network net)377         public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
378             mConfig.network = net;
379             return this;
380         }
381 
382         /**
383          * Add UDP encapsulation to an IPv4 transform
384          *
385          * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
386          * details on how UDP should be applied to IPsec.
387          *
388          * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
389          *     receiving encapsulating traffic.
390          * @param remotePort the UDP port number of the remote that will send and receive
391          *     encapsulated traffic. In the case of IKE, this is likely port 4500.
392          */
setIpv4Encapsulation( IpSecManager.UdpEncapsulationSocket localSocket, int remotePort)393         public IpSecTransform.Builder setIpv4Encapsulation(
394                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
395             // TODO: check encap type is valid.
396             mConfig.encapType = ENCAP_ESPINUDP;
397             mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket
398             mConfig.encapRemotePort = remotePort;
399             return this;
400         }
401 
402         // TODO: Decrease the minimum keepalive to maybe 10?
403         // TODO: Probably a better exception to throw for NATTKeepalive failure
404         // TODO: Specify the needed NATT keepalive permission.
405         /**
406          * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
407          * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
408          * be activated, then the transform will fail to activate and throw an IOException.
409          *
410          * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
411          *     than 20s and no more than 3600s.
412          * @hide
413          */
414         @SystemApi
setNattKeepalive(int intervalSeconds)415         public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
416             mConfig.nattKeepaliveInterval = intervalSeconds;
417             return this;
418         }
419 
420         /**
421          * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
422          * Some parameters have interdependencies that are checked at build time. If a well-formed
423          * transform cannot be created from the supplied parameters, this method will throw an
424          * Exception.
425          *
426          * <p>Upon a successful return from this call, the provided IpSecTransform will be active
427          * and may be applied to sockets. If too many IpSecTransform objects are active for a given
428          * user this operation will fail and throw ResourceUnavailableException. To avoid these
429          * exceptions, unused Transform objects must be cleaned up by calling {@link
430          * IpSecTransform#close()} when they are no longer needed.
431          *
432          * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
433          *     socket will cause the transform to be applied.
434          *     <p>Note that an active transform will not impact any network traffic until it has
435          *     been applied to one or more Sockets. Calling this method is a necessary precondition
436          *     for applying it to a socket, but is not sufficient to actually apply IPsec.
437          * @throws IllegalArgumentException indicating that a particular combination of transform
438          *     properties is invalid.
439          * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
440          *     may be allocated
441          * @throws SpiUnavailableException if the SPI collides with an existing transform
442          *     (unlikely).
443          * @throws ResourceUnavailableException if the current user currently has exceeded the
444          *     number of allowed active transforms.
445          */
buildTransportModeTransform(InetAddress remoteAddress)446         public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
447                 throws IpSecManager.ResourceUnavailableException,
448                         IpSecManager.SpiUnavailableException, IOException {
449             //FIXME: argument validation here
450             //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
451             mConfig.mode = MODE_TRANSPORT;
452             mConfig.remoteAddress = remoteAddress;
453             return new IpSecTransform(mContext, mConfig).activate();
454         }
455 
456         /**
457          * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
458          * parameters have interdependencies that are checked at build time.
459          *
460          * @param localAddress the {@link InetAddress} that provides the local endpoint for this
461          *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
462          *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
463          * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this
464          *     IPsec tunnel.
465          * @throws IllegalArgumentException indicating that a particular combination of transform
466          *     properties is invalid.
467          * @hide
468          */
buildTunnelModeTransform( InetAddress localAddress, InetAddress remoteAddress)469         public IpSecTransform buildTunnelModeTransform(
470                 InetAddress localAddress, InetAddress remoteAddress) {
471             //FIXME: argument validation here
472             //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
473             mConfig.localAddress = localAddress;
474             mConfig.remoteAddress = remoteAddress;
475             mConfig.mode = MODE_TUNNEL;
476             return new IpSecTransform(mContext, mConfig);
477         }
478 
479         /**
480          * Create a new IpSecTransform.Builder to construct an IpSecTransform
481          *
482          * @param context current Context
483          */
Builder(@onNull Context context)484         public Builder(@NonNull Context context) {
485             Preconditions.checkNotNull(context);
486             mContext = context;
487             mConfig = new IpSecConfig();
488         }
489     }
490 }
491