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
IpSecTransform(Context context, IpSecConfig config)89     public IpSecTransform(Context context, IpSecConfig config) {
90         mContext = context;
91         mConfig = new IpSecConfig(config);
92         mResourceId = INVALID_RESOURCE_ID;
93     }
94 
getIpSecService()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      */
checkResultStatus(int status)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 
activate()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
equals(IpSecTransform lhs, IpSecTransform rhs)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      */
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             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
finalize()198     protected void finalize() throws Throwable {
199         if (mCloseGuard != null) {
200             mCloseGuard.warnIfOpen();
201         }
202         close();
203     }
204 
205     /* Package */
getConfig()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
getResourceId()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. */
onStarted()268         public void onStarted() {}
269         /** The keepalive was successfully stopped. */
onStopped()270         public void onStopped() {}
271         /** An error occurred. */
onError(int error)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     })
startNattKeepalive(@onNull NattKeepaliveCallback userCallback, int intervalSeconds, @NonNull Handler handler)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     })
stopNattKeepalive()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
setEncryption(@onNull IpSecAlgorithm algo)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
setAuthentication(@onNull IpSecAlgorithm algo)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
setAuthenticatedEncryption(@onNull IpSecAlgorithm algo)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
setIpv4Encapsulation( @onNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort)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
buildTransportModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)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)
buildTunnelModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)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          */
Builder(@onNull Context context)506         public Builder(@NonNull Context context) {
507             Preconditions.checkNotNull(context);
508             mContext = context;
509             mConfig = new IpSecConfig();
510         }
511     }
512 
513     @Override
toString()514     public String toString() {
515         return new StringBuilder()
516             .append("IpSecTransform{resourceId=")
517             .append(mResourceId)
518             .append("}")
519             .toString();
520     }
521 }
522