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