IpSecManager.java revision 8dc1fd0237992e1d693376b4f6eea45e7447e9db
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.ParcelFileDescriptor;
23import android.os.RemoteException;
24import android.util.AndroidException;
25import android.util.Log;
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 *
41 * @hide
42 */
43public final class IpSecManager {
44    private static final String TAG = "IpSecManager";
45
46    /**
47     * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
48     *
49     * <p>No IPsec packet may contain an SPI of 0.
50     */
51    public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
52
53    /** @hide */
54    public interface Status {
55        public static final int OK = 0;
56        public static final int RESOURCE_UNAVAILABLE = 1;
57        public static final int SPI_UNAVAILABLE = 2;
58    }
59
60    /** @hide */
61    public static final int INVALID_RESOURCE_ID = 0;
62
63    /**
64     * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
65     * request. If encountered, selection of a new SPI is required before a transform may be
66     * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
67     * or reserved using reserveSecurityParameterIndex.
68     */
69    public static final class SpiUnavailableException extends AndroidException {
70        private final int mSpi;
71
72        /**
73         * Construct an exception indicating that a transform with the given SPI is already in use
74         * or otherwise unavailable.
75         *
76         * @param msg Description indicating the colliding SPI
77         * @param spi the SPI that could not be used due to a collision
78         */
79        SpiUnavailableException(String msg, int spi) {
80            super(msg + "(spi: " + spi + ")");
81            mSpi = spi;
82        }
83
84        /** Retrieve the SPI that caused a collision */
85        public int getSpi() {
86            return mSpi;
87        }
88    }
89
90    /**
91     * Indicates that the requested system resource for IPsec, such as a socket or other system
92     * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
93     * type requested.
94     */
95    public static final class ResourceUnavailableException extends AndroidException {
96
97        ResourceUnavailableException(String msg) {
98            super(msg);
99        }
100    }
101
102    private final IIpSecService mService;
103
104    public static final class SecurityParameterIndex implements AutoCloseable {
105        private final IIpSecService mService;
106        private final InetAddress mRemoteAddress;
107        private final CloseGuard mCloseGuard = CloseGuard.get();
108        private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
109        private int mResourceId;
110
111        /** Return the underlying SPI held by this object */
112        public int getSpi() {
113            return mSpi;
114        }
115
116        /**
117         * Release an SPI that was previously reserved.
118         *
119         * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
120         * applied to an IpSecTransform, it will become unusable for future transforms but should
121         * still be closed to ensure system resources are released.
122         */
123        @Override
124        public void close() {
125            try {
126                mService.releaseSecurityParameterIndex(mResourceId);
127            } catch (RemoteException e) {
128                throw e.rethrowFromSystemServer();
129            }
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                IpSecSpiResponse 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.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.spi;
170                mResourceId = result.resourceId;
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        /** @hide */
188        int getResourceId() {
189            return mResourceId;
190        }
191    }
192
193    /**
194     * Reserve an SPI for traffic bound towards the specified remote address.
195     *
196     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
197     * SecurityParameterIndex#close()}.
198     *
199     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
200     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
201     * @return the reserved SecurityParameterIndex
202     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
203     *     for this user
204     * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
205     */
206    public SecurityParameterIndex reserveSecurityParameterIndex(
207            int direction, InetAddress remoteAddress) throws ResourceUnavailableException {
208        try {
209            return new SecurityParameterIndex(
210                    mService,
211                    direction,
212                    remoteAddress,
213                    IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
214        } catch (SpiUnavailableException unlikely) {
215            throw new ResourceUnavailableException("No SPIs available");
216        }
217    }
218
219    /**
220     * Reserve an SPI for traffic bound towards the specified remote address.
221     *
222     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
223     * SecurityParameterIndex#close()}.
224     *
225     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
226     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
227     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
228     * @return the reserved SecurityParameterIndex
229     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
230     *     for this user
231     */
232    public SecurityParameterIndex reserveSecurityParameterIndex(
233            int direction, InetAddress remoteAddress, int requestedSpi)
234            throws SpiUnavailableException, ResourceUnavailableException {
235        if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
236            throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
237        }
238        return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
239    }
240
241    /**
242     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
243     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
244     * transform. For security reasons, attempts to send traffic to any IP address other than the
245     * address associated with that transform will throw an IOException. In addition, if the
246     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
247     * send() or receive() until the transform is removed from the socket by calling {@link
248     * #removeTransportModeTransform(Socket, IpSecTransform)};
249     *
250     * @param socket a stream socket
251     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
252     * @hide
253     */
254    public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
255            throws IOException {
256        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
257            applyTransportModeTransform(pfd, transform);
258        }
259    }
260
261    /**
262     * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
263     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
264     * transform. For security reasons, attempts to send traffic to any IP address other than the
265     * address associated with that transform will throw an IOException. In addition, if the
266     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
267     * send() or receive() until the transform is removed from the socket by calling {@link
268     * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
269     *
270     * @param socket a datagram socket
271     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
272     * @hide
273     */
274    public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
275            throws IOException {
276        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
277            applyTransportModeTransform(pfd, transform);
278        }
279    }
280
281    /**
282     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
283     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
284     * transform. For security reasons, attempts to send traffic to any IP address other than the
285     * address associated with that transform will throw an IOException. In addition, if the
286     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
287     * send() or receive() until the transform is removed from the socket by calling {@link
288     * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
289     *
290     * @param socket a socket file descriptor
291     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
292     */
293    public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
294            throws IOException {
295        // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
296        // constructor takes control and closes the user's FD when we exit the method
297        // This is behaviorally the same as the other versions, but the PFD constructor does not
298        // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup().
299        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
300            applyTransportModeTransform(pfd, transform);
301        }
302    }
303
304    /* Call down to activate a transform */
305    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
306        try {
307            mService.applyTransportModeTransform(pfd, transform.getResourceId());
308        } catch (RemoteException e) {
309            throw e.rethrowFromSystemServer();
310        }
311    }
312
313    /**
314     * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
315     * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to
316     * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic).
317     * Applications should probably not use this API directly. Instead, they should use {@link
318     * VpnService} to provide VPN capability in a more generic fashion.
319     *
320     * @param net a {@link Network} that will be tunneled via IP Sec.
321     * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
322     * @hide
323     */
324    public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
325
326    /**
327     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
328     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
329     * communication in the clear in the event socket reuse is desired. This operation will succeed
330     * regardless of the underlying state of a transform. If a transform is removed, communication
331     * on all sockets to which that transform was applied will fail until this method is called.
332     *
333     * @param socket a socket that previously had a transform applied to it.
334     * @param transform the IPsec Transform that was previously applied to the given socket
335     * @hide
336     */
337    public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
338            throws IOException {
339        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
340            removeTransportModeTransform(pfd, transform);
341        }
342    }
343
344    /**
345     * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
346     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
347     * communication in the clear in the event socket reuse is desired. This operation will succeed
348     * regardless of the underlying state of a transform. If a transform is removed, communication
349     * on all sockets to which that transform was applied will fail until this method is called.
350     *
351     * @param socket a socket that previously had a transform applied to it.
352     * @param transform the IPsec Transform that was previously applied to the given socket
353     * @hide
354     */
355    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
356            throws IOException {
357        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
358            removeTransportModeTransform(pfd, transform);
359        }
360    }
361
362    /**
363     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
364     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
365     * communication in the clear in the event socket reuse is desired. This operation will succeed
366     * regardless of the underlying state of a transform. If a transform is removed, communication
367     * on all sockets to which that transform was applied will fail until this method is called.
368     *
369     * @param socket a socket file descriptor that previously had a transform applied to it.
370     * @param transform the IPsec Transform that was previously applied to the given socket
371     */
372    public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
373            throws IOException {
374        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
375            removeTransportModeTransform(pfd, transform);
376        }
377    }
378
379    /* Call down to activate a transform */
380    private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
381        try {
382            mService.removeTransportModeTransform(pfd, transform.getResourceId());
383        } catch (RemoteException e) {
384            throw e.rethrowFromSystemServer();
385        }
386    }
387
388    /**
389     * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
390     * cleanup if a tunneled Network experiences a change in default route. The Network will drop
391     * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
392     * lost, all traffic will drop.
393     *
394     * @param net a network that currently has transform applied to it.
395     * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
396     *     network
397     * @hide
398     */
399    public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
400
401    /**
402     * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
403     * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
404     *
405     * <p>The socket provided by this class cannot be re-bound or closed via the inner
406     * FileDescriptor. Instead, disposing of this socket requires a call to close().
407     */
408    public static final class UdpEncapsulationSocket implements AutoCloseable {
409        private final ParcelFileDescriptor mPfd;
410        private final IIpSecService mService;
411        private final int mResourceId;
412        private final int mPort;
413        private final CloseGuard mCloseGuard = CloseGuard.get();
414
415        private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
416                throws ResourceUnavailableException, IOException {
417            mService = service;
418            try {
419                IpSecUdpEncapResponse result =
420                        mService.openUdpEncapsulationSocket(port, new Binder());
421                switch (result.status) {
422                    case Status.OK:
423                        break;
424                    case Status.RESOURCE_UNAVAILABLE:
425                        throw new ResourceUnavailableException(
426                                "No more Sockets may be allocated by this requester.");
427                    default:
428                        throw new RuntimeException(
429                                "Unknown status returned by IpSecService: " + result.status);
430                }
431                mResourceId = result.resourceId;
432                mPort = result.port;
433                mPfd = result.fileDescriptor;
434            } catch (RemoteException e) {
435                throw e.rethrowFromSystemServer();
436            }
437            mCloseGuard.open("constructor");
438        }
439
440        /** Access the inner UDP Encapsulation Socket */
441        public FileDescriptor getSocket() {
442            if (mPfd == null) {
443                return null;
444            }
445            return mPfd.getFileDescriptor();
446        }
447
448        /** Retrieve the port number of the inner encapsulation socket */
449        public int getPort() {
450            return mPort;
451        }
452
453        @Override
454        /**
455         * Release the resources that have been reserved for this Socket.
456         *
457         * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
458         * system. This must be done as part of cleanup following use of a socket. Failure to do so
459         * will cause the socket to count against a total allocation limit for IpSec and eventually
460         * fail due to resource limits.
461         *
462         * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
463         */
464        public void close() throws IOException {
465            try {
466                mService.closeUdpEncapsulationSocket(mResourceId);
467            } catch (RemoteException e) {
468                throw e.rethrowFromSystemServer();
469            }
470
471            try {
472                mPfd.close();
473            } catch (IOException e) {
474                Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort);
475                throw e;
476            }
477            mCloseGuard.close();
478        }
479
480        @Override
481        protected void finalize() throws Throwable {
482            if (mCloseGuard != null) {
483                mCloseGuard.warnIfOpen();
484            }
485            close();
486        }
487
488        /** @hide */
489        int getResourceId() {
490            return mResourceId;
491        }
492    };
493
494    /**
495     * Open a socket that is bound to a free UDP port on the system.
496     *
497     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
498     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
499     * Encapsulation port.
500     *
501     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
502     * socket port. Explicitly opening this port is only necessary if communication is desired on
503     * that port.
504     *
505     * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
506     *     method will bind to the specified port or fail. To retrieve the port number, call {@link
507     *     android.system.Os#getsockname(FileDescriptor)}.
508     * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
509     *     of the object.
510     */
511    // Returning a socket in this fashion that has been created and bound by the system
512    // is the only safe way to ensure that a socket is both accessible to the user and
513    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
514    // the port, which could potentially impact the traffic of the next user who binds to that
515    // socket.
516    public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
517            throws IOException, ResourceUnavailableException {
518        /*
519         * Most range checking is done in the service, but this version of the constructor expects
520         * a valid port number, and zero cannot be checked after being passed to the service.
521         */
522        if (port == 0) {
523            throw new IllegalArgumentException("Specified port must be a valid port number!");
524        }
525        return new UdpEncapsulationSocket(mService, port);
526    }
527
528    /**
529     * Open a socket that is bound to a port selected by the system.
530     *
531     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
532     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
533     * Encapsulation port.
534     *
535     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
536     * socket port. Explicitly opening this port is only necessary if communication is desired on
537     * that port.
538     *
539     * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
540     */
541    // Returning a socket in this fashion that has been created and bound by the system
542    // is the only safe way to ensure that a socket is both accessible to the user and
543    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
544    // the port, which could potentially impact the traffic of the next user who binds to that
545    // socket.
546    public UdpEncapsulationSocket openUdpEncapsulationSocket()
547            throws IOException, ResourceUnavailableException {
548        return new UdpEncapsulationSocket(mService, 0);
549    }
550
551    /**
552     * Retrieve an instance of an IpSecManager within you application context
553     *
554     * @param context the application context for this manager
555     * @hide
556     */
557    public IpSecManager(IIpSecService service) {
558        mService = checkNotNull(service, "missing service");
559    }
560}
561