IpSecManager.java revision 09098dc441913f30905f6ffd6f73262924858dd0
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 */
16package android.net;
17
18import static com.android.internal.util.Preconditions.checkNotNull;
19
20import android.annotation.NonNull;
21import android.os.Binder;
22import android.os.Bundle;
23import android.os.ParcelFileDescriptor;
24import android.os.RemoteException;
25import android.util.AndroidException;
26import dalvik.system.CloseGuard;
27import java.io.FileDescriptor;
28import java.io.IOException;
29import java.net.DatagramSocket;
30import java.net.InetAddress;
31import java.net.Socket;
32
33/**
34 * This class contains methods for managing IPsec sessions, which will perform kernel-space
35 * encryption and decryption of socket or Network traffic.
36 *
37 * <p>An IpSecManager may be obtained by calling {@link
38 * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
39 * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
40 */
41public final class IpSecManager {
42    private static final String TAG = "IpSecManager";
43
44    /**
45     * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
46     *
47     * <p>No IPsec packet may contain an SPI of 0.
48     */
49    public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
50
51    /** @hide */
52    public interface Status {
53        public static final int OK = 0;
54        public static final int RESOURCE_UNAVAILABLE = 1;
55        public static final int SPI_UNAVAILABLE = 2;
56    }
57
58    /** @hide */
59    public static final String KEY_STATUS = "status";
60    /** @hide */
61    public static final String KEY_RESOURCE_ID = "resourceId";
62    /** @hide */
63    public static final String KEY_SPI = "spi";
64    /** @hide */
65    public static final int INVALID_RESOURCE_ID = 0;
66
67    /**
68     * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
69     * request. If encountered, selection of a new SPI is required before a transform may be
70     * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
71     * or reserved using reserveSecurityParameterIndex.
72     */
73    public static final class SpiUnavailableException extends AndroidException {
74        private final int mSpi;
75
76        /**
77         * Construct an exception indicating that a transform with the given SPI is already in use
78         * or otherwise unavailable.
79         *
80         * @param msg Description indicating the colliding SPI
81         * @param spi the SPI that could not be used due to a collision
82         */
83        SpiUnavailableException(String msg, int spi) {
84            super(msg + "(spi: " + spi + ")");
85            mSpi = spi;
86        }
87
88        /** Retrieve the SPI that caused a collision */
89        public int getSpi() {
90            return mSpi;
91        }
92    }
93
94    /**
95     * Indicates that the requested system resource for IPsec, such as a socket or other system
96     * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
97     * type requested.
98     */
99    public static final class ResourceUnavailableException extends AndroidException {
100
101        ResourceUnavailableException(String msg) {
102            super(msg);
103        }
104    }
105
106    private final IIpSecService mService;
107
108    public static final class SecurityParameterIndex implements AutoCloseable {
109        private final IIpSecService mService;
110        private final InetAddress mRemoteAddress;
111        private final CloseGuard mCloseGuard = CloseGuard.get();
112        private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
113        private int mResourceId;
114
115        /** Return the underlying SPI held by this object */
116        public int getSpi() {
117            return mSpi;
118        }
119
120        /**
121         * Release an SPI that was previously reserved.
122         *
123         * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
124         * applied to an IpSecTransform, it will become unusable for future transforms but should
125         * still be closed to ensure system resources are released.
126         */
127        @Override
128        public void close() {
129            mSpi = INVALID_SECURITY_PARAMETER_INDEX;
130            mCloseGuard.close();
131        }
132
133        @Override
134        protected void finalize() {
135            if (mCloseGuard != null) {
136                mCloseGuard.warnIfOpen();
137            }
138
139            close();
140        }
141
142        private SecurityParameterIndex(
143                @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi)
144                throws ResourceUnavailableException, SpiUnavailableException {
145            mService = service;
146            mRemoteAddress = remoteAddress;
147            try {
148                Bundle result =
149                        mService.reserveSecurityParameterIndex(
150                                direction, remoteAddress.getHostAddress(), spi, new Binder());
151
152                if (result == null) {
153                    throw new NullPointerException("Received null response from IpSecService");
154                }
155
156                int status = result.getInt(KEY_STATUS);
157                switch (status) {
158                    case Status.OK:
159                        break;
160                    case Status.RESOURCE_UNAVAILABLE:
161                        throw new ResourceUnavailableException(
162                                "No more SPIs may be allocated by this requester.");
163                    case Status.SPI_UNAVAILABLE:
164                        throw new SpiUnavailableException("Requested SPI is unavailable", spi);
165                    default:
166                        throw new RuntimeException(
167                                "Unknown status returned by IpSecService: " + status);
168                }
169                mSpi = result.getInt(KEY_SPI);
170                mResourceId = result.getInt(KEY_RESOURCE_ID);
171
172                if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
173                    throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
174                }
175
176                if (mResourceId == INVALID_RESOURCE_ID) {
177                    throw new RuntimeException(
178                            "Invalid Resource ID returned by IpSecService: " + status);
179                }
180
181            } catch (RemoteException e) {
182                throw e.rethrowFromSystemServer();
183            }
184            mCloseGuard.open("open");
185        }
186    }
187
188    /**
189     * Reserve an SPI for traffic bound towards the specified remote address.
190     *
191     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
192     * SecurityParameterIndex#close()}.
193     *
194     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
195     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
196     * @return the reserved SecurityParameterIndex
197     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
198     *     for this user
199     * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
200     */
201    public SecurityParameterIndex reserveSecurityParameterIndex(
202            int direction, InetAddress remoteAddress)
203            throws ResourceUnavailableException {
204        try {
205            return new SecurityParameterIndex(
206                    mService,
207                    direction,
208                    remoteAddress,
209                    IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
210        } catch (SpiUnavailableException unlikely) {
211            throw new ResourceUnavailableException("No SPIs available");
212        }
213    }
214
215    /**
216     * Reserve an SPI for traffic bound towards the specified remote address.
217     *
218     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
219     * SecurityParameterIndex#close()}.
220     *
221     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
222     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
223     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
224     * @return the reserved SecurityParameterIndex
225     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
226     *     for this user
227     */
228    public SecurityParameterIndex reserveSecurityParameterIndex(
229            int direction, InetAddress remoteAddress, int requestedSpi)
230            throws SpiUnavailableException, ResourceUnavailableException {
231        if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
232            throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
233        }
234        return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
235    }
236
237    /**
238     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
239     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
240     * transform. For security reasons, attempts to send traffic to any IP address other than the
241     * address associated with that transform will throw an IOException. In addition, if the
242     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
243     * send() or receive() until the transform is removed from the socket by calling {@link
244     * #removeTransportModeTransform(Socket, IpSecTransform)};
245     *
246     * @param socket a stream socket
247     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
248     */
249    public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
250            throws IOException {
251        applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
252    }
253
254    /**
255     * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
256     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
257     * transform. For security reasons, attempts to send traffic to any IP address other than the
258     * address associated with that transform will throw an IOException. In addition, if the
259     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
260     * send() or receive() until the transform is removed from the socket by calling {@link
261     * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
262     *
263     * @param socket a datagram socket
264     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
265     */
266    public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
267            throws IOException {
268        applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
269    }
270
271    /* Call down to activate a transform */
272    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
273        try {
274            mService.applyTransportModeTransform(pfd, transform.getResourceId());
275        } catch (RemoteException e) {
276            throw e.rethrowFromSystemServer();
277        }
278    }
279
280    /**
281     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
282     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
283     * transform. For security reasons, attempts to send traffic to any IP address other than the
284     * address associated with that transform will throw an IOException. In addition, if the
285     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
286     * send() or receive() until the transform is removed from the socket by calling {@link
287     * #removeTransportModeTransform(Socket, IpSecTransform)};
288     *
289     * @param socket a socket file descriptor
290     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
291     */
292    public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
293            throws IOException {
294        applyTransportModeTransform(new ParcelFileDescriptor(socket), transform);
295    }
296
297    /**
298     * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
299     * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to
300     * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic).
301     * Applications should probably not use this API directly. Instead, they should use {@link
302     * VpnService} to provide VPN capability in a more generic fashion.
303     *
304     * @param net a {@link Network} that will be tunneled via IP Sec.
305     * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
306     * @hide
307     */
308    public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
309
310    /**
311     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
312     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
313     * communication in the clear in the event socket reuse is desired. This operation will succeed
314     * regardless of the underlying state of a transform. If a transform is removed, communication
315     * on all sockets to which that transform was applied will fail until this method is called.
316     *
317     * @param socket a socket that previously had a transform applied to it.
318     * @param transform the IPsec Transform that was previously applied to the given socket
319     */
320    public void removeTransportModeTransform(Socket socket, IpSecTransform transform) {
321        removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
322    }
323
324    /**
325     * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
326     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
327     * communication in the clear in the event socket reuse is desired. This operation will succeed
328     * regardless of the underlying state of a transform. If a transform is removed, communication
329     * on all sockets to which that transform was applied will fail until this method is called.
330     *
331     * @param socket a socket that previously had a transform applied to it.
332     * @param transform the IPsec Transform that was previously applied to the given socket
333     */
334    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) {
335        removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
336    }
337
338    /**
339     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
340     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
341     * communication in the clear in the event socket reuse is desired. This operation will succeed
342     * regardless of the underlying state of a transform. If a transform is removed, communication
343     * on all sockets to which that transform was applied will fail until this method is called.
344     *
345     * @param socket a socket file descriptor that previously had a transform applied to it.
346     * @param transform the IPsec Transform that was previously applied to the given socket
347     */
348    public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) {
349        removeTransportModeTransform(new ParcelFileDescriptor(socket), transform);
350    }
351
352    /* Call down to activate a transform */
353    private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
354        try {
355            mService.removeTransportModeTransform(pfd, transform.getResourceId());
356        } catch (RemoteException e) {
357            throw e.rethrowFromSystemServer();
358        }
359    }
360
361    /**
362     * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
363     * cleanup if a tunneled Network experiences a change in default route. The Network will drop
364     * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
365     * lost, all traffic will drop.
366     *
367     * @param net a network that currently has transform applied to it.
368     * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
369     *     network
370     * @hide
371     */
372    public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
373
374    /**
375     * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
376     * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
377     *
378     * <p>The socket provided by this class cannot be re-bound or closed via the inner
379     * FileDescriptor. Instead, disposing of this socket requires a call to close().
380     */
381    public static final class UdpEncapsulationSocket implements AutoCloseable {
382        private final FileDescriptor mFd;
383        private final IIpSecService mService;
384        private final CloseGuard mCloseGuard = CloseGuard.get();
385
386        private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
387                throws ResourceUnavailableException {
388            mService = service;
389            mCloseGuard.open("constructor");
390            // TODO: go down to the kernel and get a socket on the specified
391            mFd = new FileDescriptor();
392        }
393
394        private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException {
395            mService = service;
396            mCloseGuard.open("constructor");
397            // TODO: go get a random socket on a random port
398            mFd = new FileDescriptor();
399        }
400
401        /** Access the inner UDP Encapsulation Socket */
402        public FileDescriptor getSocket() {
403            return mFd;
404        }
405
406        /** Retrieve the port number of the inner encapsulation socket */
407        public int getPort() {
408            return 0; // TODO get the port number from the Socket;
409        }
410
411        @Override
412        /**
413         * Release the resources that have been reserved for this Socket.
414         *
415         * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
416         * system. This must be done as part of cleanup following use of a socket. Failure to do so
417         * will cause the socket to count against a total allocation limit for IpSec and eventually
418         * fail due to resource limits.
419         *
420         * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
421         */
422        public void close() {
423            // TODO: Go close the socket
424            mCloseGuard.close();
425        }
426
427        @Override
428        protected void finalize() throws Throwable {
429            if (mCloseGuard != null) {
430                mCloseGuard.warnIfOpen();
431            }
432
433            close();
434        }
435    };
436
437    /**
438     * Open a socket that is bound to a free UDP port on the system.
439     *
440     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
441     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
442     * Encapsulation port.
443     *
444     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
445     * socket port. Explicitly opening this port is only necessary if communication is desired on
446     * that port.
447     *
448     * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
449     *     method will bind to the specified port or fail. To retrieve the port number, call {@link
450     *     android.system.Os#getsockname(FileDescriptor)}.
451     * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
452     *     of the object.
453     */
454    // Returning a socket in this fashion that has been created and bound by the system
455    // is the only safe way to ensure that a socket is both accessible to the user and
456    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
457    // the port, which could potentially impact the traffic of the next user who binds to that
458    // socket.
459    public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
460            throws IOException, ResourceUnavailableException {
461        // Temporary code
462        return new UdpEncapsulationSocket(mService, port);
463    }
464
465    /**
466     * Open a socket that is bound to a port selected by the system.
467     *
468     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
469     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
470     * Encapsulation port.
471     *
472     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
473     * socket port. Explicitly opening this port is only necessary if communication is desired on
474     * that port.
475     *
476     * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
477     */
478    // Returning a socket in this fashion that has been created and bound by the system
479    // is the only safe way to ensure that a socket is both accessible to the user and
480    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
481    // the port, which could potentially impact the traffic of the next user who binds to that
482    // socket.
483    public UdpEncapsulationSocket openUdpEncapsulationSocket()
484            throws IOException, ResourceUnavailableException {
485        // Temporary code
486        return new UdpEncapsulationSocket(mService);
487    }
488
489    /**
490     * Retrieve an instance of an IpSecManager within you application context
491     *
492     * @param context the application context for this manager
493     * @hide
494     */
495    public IpSecManager(IIpSecService service) {
496        mService = checkNotNull(service, "missing service");
497    }
498}
499