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