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