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.Flags.IPSEC_TRANSFORM_STATE;
19 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.FlaggedApi;
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RequiresFeature;
27 import android.annotation.RequiresPermission;
28 import android.annotation.SystemApi;
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.os.Binder;
32 import android.os.OutcomeReceiver;
33 import android.os.RemoteException;
34 import android.os.ServiceSpecificException;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import dalvik.system.CloseGuard;
40 
41 import java.io.IOException;
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.net.InetAddress;
45 import java.util.Objects;
46 import java.util.concurrent.Executor;
47 
48 /**
49  * This class represents a transform, which roughly corresponds to an IPsec Security Association.
50  *
51  * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
52  * object encapsulates the properties and state of an IPsec security association. That includes,
53  * but is not limited to, algorithm choice, key material, and allocated system resources.
54  *
55  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
56  *     Internet Protocol</a>
57  */
58 public final class IpSecTransform implements AutoCloseable {
59     private static final String TAG = "IpSecTransform";
60 
61     /** @hide */
62     public static final int MODE_TRANSPORT = 0;
63 
64     /** @hide */
65     public static final int MODE_TUNNEL = 1;
66 
67     /** @hide */
68     public static final int ENCAP_NONE = 0;
69 
70     /**
71      * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
72      * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
73      *
74      * @hide
75      */
76     public static final int ENCAP_ESPINUDP_NON_IKE = 1;
77 
78     /**
79      * IPsec traffic will be encapsulated within UDP as per
80      * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
81      *
82      * @hide
83      */
84     public static final int ENCAP_ESPINUDP = 2;
85 
86     /** @hide */
87     @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
88     @Retention(RetentionPolicy.SOURCE)
89     public @interface EncapType {}
90 
91     /** @hide */
92     @VisibleForTesting
IpSecTransform(Context context, IpSecConfig config)93     public IpSecTransform(Context context, IpSecConfig config) {
94         mContext = context;
95         mConfig = new IpSecConfig(config);
96         mResourceId = INVALID_RESOURCE_ID;
97     }
98 
getIpSecManager(Context context)99     private IpSecManager getIpSecManager(Context context) {
100         return context.getSystemService(IpSecManager.class);
101     }
102     /**
103      * Checks the result status and throws an appropriate exception if the status is not Status.OK.
104      */
checkResultStatus(int status)105     private void checkResultStatus(int status)
106             throws IOException, IpSecManager.ResourceUnavailableException,
107                     IpSecManager.SpiUnavailableException {
108         switch (status) {
109             case IpSecManager.Status.OK:
110                 return;
111                 // TODO: Pass Error string back from bundle so that errors can be more specific
112             case IpSecManager.Status.RESOURCE_UNAVAILABLE:
113                 throw new IpSecManager.ResourceUnavailableException(
114                         "Failed to allocate a new IpSecTransform");
115             case IpSecManager.Status.SPI_UNAVAILABLE:
116                 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
117                 // Fall through
118             default:
119                 throw new IllegalStateException(
120                         "Failed to Create a Transform with status code " + status);
121         }
122     }
123 
activate()124     private IpSecTransform activate()
125             throws IOException, IpSecManager.ResourceUnavailableException,
126                     IpSecManager.SpiUnavailableException {
127         synchronized (mLock) {
128             try {
129                 IpSecTransformResponse result = getIpSecManager(mContext).createTransform(
130                         mConfig, new Binder(), mContext.getOpPackageName());
131                 int status = result.status;
132                 checkResultStatus(status);
133                 mResourceId = result.resourceId;
134                 Log.d(TAG, "Added Transform with Id " + mResourceId);
135                 mCloseGuard.open("close");
136             } catch (ServiceSpecificException e) {
137                 throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
138             }
139         }
140 
141         return this;
142     }
143 
144     /**
145      * Standard equals.
146      */
equals(@ullable Object other)147     public boolean equals(@Nullable Object other) {
148         if (this == other) return true;
149         if (!(other instanceof IpSecTransform)) return false;
150         final IpSecTransform rhs = (IpSecTransform) other;
151         return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId;
152     }
153 
154     /**
155      * Deactivate this {@code IpSecTransform} and free allocated resources.
156      *
157      * <p>Deactivating a transform while it is still applied to a socket will result in errors on
158      * that socket. Make sure to remove transforms by calling {@link
159      * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
160      * socket will not deactivate it (because one transform may be applied to multiple sockets).
161      *
162      * <p>It is safe to call this method on a transform that has already been deactivated.
163      */
close()164     public void close() {
165         Log.d(TAG, "Removing Transform with Id " + mResourceId);
166 
167         synchronized(mLock) {
168             // Always safe to attempt cleanup
169             if (mResourceId == INVALID_RESOURCE_ID) {
170                 mCloseGuard.close();
171                 return;
172             }
173 
174             try {
175                     getIpSecManager(mContext).deleteTransform(mResourceId);
176             } catch (Exception e) {
177                 // On close we swallow all random exceptions since failure to close is not
178                 // actionable by the user.
179                 Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
180             } finally {
181                 mResourceId = INVALID_RESOURCE_ID;
182                 mCloseGuard.close();
183             }
184         }
185     }
186 
187     /** Check that the transform was closed properly. */
188     @Override
finalize()189     protected void finalize() throws Throwable {
190         if (mCloseGuard != null) {
191             mCloseGuard.warnIfOpen();
192         }
193         close();
194     }
195 
196     /* Package */
getConfig()197     IpSecConfig getConfig() {
198         return mConfig;
199     }
200 
201     private final IpSecConfig mConfig;
202     private final Object mLock = new Object();
203     private int mResourceId; // Partly guarded by mLock to ensure basic safety, not correctness
204     private final Context mContext;
205     private final CloseGuard mCloseGuard = CloseGuard.get();
206 
207     /** @hide */
208     @VisibleForTesting
getResourceId()209     public int getResourceId() {
210         synchronized(mLock) {
211             return mResourceId;
212         }
213     }
214 
215     /**
216      * Retrieve the current state of this IpSecTransform.
217      *
218      * @param executor The {@link Executor} on which to call the supplied callback.
219      * @param callback Callback that's called after the transform state is ready or when an error
220      *     occurs.
221      * @see IpSecTransformState
222      */
223     @FlaggedApi(IPSEC_TRANSFORM_STATE)
requestIpSecTransformState( @allbackExecutor @onNull Executor executor, @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback)224     public void requestIpSecTransformState(
225             @CallbackExecutor @NonNull Executor executor,
226             @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
227         Objects.requireNonNull(executor);
228         Objects.requireNonNull(callback);
229 
230         // TODO: Consider adding check to prevent DDoS attack.
231 
232         try {
233             IpSecTransformState ipSecTransformState;
234             synchronized(mLock) {
235                 ipSecTransformState = getIpSecManager(mContext).getTransformState(mResourceId);
236             }
237             executor.execute(
238                     () -> {
239                         callback.onResult(ipSecTransformState);
240                     });
241         } catch (IllegalStateException e) {
242             executor.execute(
243                     () -> {
244                         callback.onError(e);
245                     });
246         } catch (RemoteException e) {
247             executor.execute(
248                     () -> {
249                         callback.onError(e.rethrowFromSystemServer());
250                     });
251         }
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     /** This class is used to build {@link IpSecTransform} objects. */
279     public static class Builder {
280         private Context mContext;
281         private IpSecConfig mConfig;
282 
283         /**
284          * Set the encryption algorithm.
285          *
286          * <p>Encryption is mutually exclusive with authenticated encryption.
287          *
288          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
289          */
290         @NonNull
setEncryption(@onNull IpSecAlgorithm algo)291         public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
292             // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
293             Objects.requireNonNull(algo);
294             mConfig.setEncryption(algo);
295             return this;
296         }
297 
298         /**
299          * Set the authentication (integrity) algorithm.
300          *
301          * <p>Authentication is mutually exclusive with authenticated encryption.
302          *
303          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
304          */
305         @NonNull
setAuthentication(@onNull IpSecAlgorithm algo)306         public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
307             // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
308             Objects.requireNonNull(algo);
309             mConfig.setAuthentication(algo);
310             return this;
311         }
312 
313         /**
314          * Set the authenticated encryption algorithm.
315          *
316          * <p>The Authenticated Encryption (AE) class of algorithms are also known as
317          * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
318          * algorithms (as referred to in
319          * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
320          *
321          * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
322          *
323          * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
324          *     be applied.
325          */
326         @NonNull
setAuthenticatedEncryption(@onNull IpSecAlgorithm algo)327         public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
328             Objects.requireNonNull(algo);
329             mConfig.setAuthenticatedEncryption(algo);
330             return this;
331         }
332 
333         /**
334          * Add UDP encapsulation to an IPv4 transform.
335          *
336          * <p>This allows IPsec traffic to pass through a NAT.
337          *
338          * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
339          *     ESP Packets</a>
340          * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
341          *     NAT Traversal of IKEv2</a>
342          * @param localSocket a socket for sending and receiving encapsulated traffic
343          * @param remotePort the UDP port number of the remote host that will send and receive
344          *     encapsulated traffic. In the case of IKEv2, this should be port 4500.
345          */
346         @NonNull
setIpv4Encapsulation( @onNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort)347         public IpSecTransform.Builder setIpv4Encapsulation(
348                 @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
349             Objects.requireNonNull(localSocket);
350             mConfig.setEncapType(ENCAP_ESPINUDP);
351             if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
352                 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
353             }
354             mConfig.setEncapSocketResourceId(localSocket.getResourceId());
355             mConfig.setEncapRemotePort(remotePort);
356             return this;
357         }
358 
359         /**
360          * Build a transport mode {@link IpSecTransform}.
361          *
362          * <p>This builds and activates a transport mode transform. Note that an active transform
363          * will not affect any network traffic until it has been applied to one or more sockets.
364          *
365          * @see IpSecManager#applyTransportModeTransform
366          * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
367          *     this transform; this address must belong to the Network used by all sockets that
368          *     utilize this transform; if provided, then only traffic originating from the
369          *     specified source address will be processed.
370          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
371          *     traffic
372          * @throws IllegalArgumentException indicating that a particular combination of transform
373          *     properties is invalid
374          * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
375          *     are active
376          * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
377          *     collides with an existing transform
378          * @throws IOException indicating other errors
379          */
380         @NonNull
buildTransportModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)381         public IpSecTransform buildTransportModeTransform(
382                 @NonNull InetAddress sourceAddress,
383                 @NonNull IpSecManager.SecurityParameterIndex spi)
384                 throws IpSecManager.ResourceUnavailableException,
385                         IpSecManager.SpiUnavailableException, IOException {
386             Objects.requireNonNull(sourceAddress);
387             Objects.requireNonNull(spi);
388             if (spi.getResourceId() == INVALID_RESOURCE_ID) {
389                 throw new IllegalArgumentException("Invalid SecurityParameterIndex");
390             }
391             mConfig.setMode(MODE_TRANSPORT);
392             mConfig.setSourceAddress(sourceAddress.getHostAddress());
393             mConfig.setSpiResourceId(spi.getResourceId());
394             // FIXME: modifying a builder after calling build can change the built transform.
395             return new IpSecTransform(mContext, mConfig).activate();
396         }
397 
398         /**
399          * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
400          * parameters have interdependencies that are checked at build time.
401          *
402          * @param sourceAddress the {@link InetAddress} that provides the source address for this
403          *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
404          *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
405          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
406          *     traffic
407          * @throws IllegalArgumentException indicating that a particular combination of transform
408          *     properties is invalid.
409          * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
410          *     are active
411          * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
412          *     collides with an existing transform
413          * @throws IOException indicating other errors
414          * @hide
415          */
416         @SystemApi
417         @NonNull
418         @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
419         @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
buildTunnelModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)420         public IpSecTransform buildTunnelModeTransform(
421                 @NonNull InetAddress sourceAddress,
422                 @NonNull IpSecManager.SecurityParameterIndex spi)
423                 throws IpSecManager.ResourceUnavailableException,
424                         IpSecManager.SpiUnavailableException, IOException {
425             Objects.requireNonNull(sourceAddress);
426             Objects.requireNonNull(spi);
427             if (spi.getResourceId() == INVALID_RESOURCE_ID) {
428                 throw new IllegalArgumentException("Invalid SecurityParameterIndex");
429             }
430             mConfig.setMode(MODE_TUNNEL);
431             mConfig.setSourceAddress(sourceAddress.getHostAddress());
432             mConfig.setSpiResourceId(spi.getResourceId());
433             return new IpSecTransform(mContext, mConfig).activate();
434         }
435 
436         /**
437          * Create a new IpSecTransform.Builder.
438          *
439          * @param context current context
440          */
Builder(@onNull Context context)441         public Builder(@NonNull Context context) {
442             Objects.requireNonNull(context);
443             mContext = context;
444             mConfig = new IpSecConfig();
445         }
446     }
447 
448     @Override
toString()449     public String toString() {
450         return new StringBuilder()
451             .append("IpSecTransform{resourceId=")
452             .append(mResourceId)
453             .append("}")
454             .toString();
455     }
456 }
457