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