IpSecManager.java revision f1dad26972dceac86edfc42bc87753b7ad8ad54f
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     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
197     * @return the reserved SecurityParameterIndex
198     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
199     *     for this user
200     * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
201     */
202    public SecurityParameterIndex reserveSecurityParameterIndex(
203            int direction, InetAddress remoteAddress, int requestedSpi)
204            throws SpiUnavailableException, ResourceUnavailableException {
205        return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
206    }
207
208    /**
209     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
210     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
211     * transform. For security reasons, attempts to send traffic to any IP address other than the
212     * address associated with that transform will throw an IOException. In addition, if the
213     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
214     * send() or receive() until the transform is removed from the socket by calling {@link
215     * #removeTransportModeTransform(Socket, IpSecTransform)};
216     *
217     * @param socket a stream socket
218     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
219     */
220    public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
221            throws IOException {
222        applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
223    }
224
225    /**
226     * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
227     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
228     * transform. For security reasons, attempts to send traffic to any IP address other than the
229     * address associated with that transform will throw an IOException. In addition, if the
230     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
231     * send() or receive() until the transform is removed from the socket by calling {@link
232     * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
233     *
234     * @param socket a datagram socket
235     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
236     */
237    public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
238            throws IOException {
239        applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
240    }
241
242    /* Call down to activate a transform */
243    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
244        try {
245            mService.applyTransportModeTransform(pfd, transform.getResourceId());
246        } catch (RemoteException e) {
247            throw e.rethrowFromSystemServer();
248        }
249    }
250
251    /**
252     * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
253     * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to
254     * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic).
255     * Applications should probably not use this API directly. Instead, they should use {@link
256     * VpnService} to provide VPN capability in a more generic fashion.
257     *
258     * @param net a {@link Network} that will be tunneled via IP Sec.
259     * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
260     * @hide
261     */
262    public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
263
264    /**
265     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
266     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
267     * communication in the clear in the event socket reuse is desired. This operation will succeed
268     * regardless of the underlying state of a transform. If a transform is removed, communication
269     * on all sockets to which that transform was applied will fail until this method is called.
270     *
271     * @param socket a socket that previously had a transform applied to it.
272     * @param transform the IPsec Transform that was previously applied to the given socket
273     */
274    public void removeTransportModeTransform(Socket socket, IpSecTransform transform) {
275        removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
276    }
277
278    /**
279     * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
280     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
281     * communication in the clear in the event socket reuse is desired. This operation will succeed
282     * regardless of the underlying state of a transform. If a transform is removed, communication
283     * on all sockets to which that transform was applied will fail until this method is called.
284     *
285     * @param socket a socket that previously had a transform applied to it.
286     * @param transform the IPsec Transform that was previously applied to the given socket
287     */
288    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) {
289        removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
290    }
291
292    /* Call down to activate a transform */
293    private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
294        try {
295            mService.removeTransportModeTransform(pfd, transform.getResourceId());
296        } catch (RemoteException e) {
297            throw e.rethrowFromSystemServer();
298        }
299    }
300
301    /**
302     * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
303     * cleanup if a tunneled Network experiences a change in default route. The Network will drop
304     * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
305     * lost, all traffic will drop.
306     *
307     * @param net a network that currently has transform applied to it.
308     * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
309     *     network
310     * @hide
311     */
312    public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
313
314    /**
315     * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
316     * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
317     *
318     * <p>The socket provided by this class cannot be re-bound or closed via the inner
319     * FileDescriptor. Instead, disposing of this socket requires a call to close().
320     */
321    public static final class UdpEncapsulationSocket implements AutoCloseable {
322        private final FileDescriptor mFd;
323        private final IIpSecService mService;
324        private final CloseGuard mCloseGuard = CloseGuard.get();
325
326        private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
327                throws ResourceUnavailableException {
328            mService = service;
329            mCloseGuard.open("constructor");
330            // TODO: go down to the kernel and get a socket on the specified
331            mFd = new FileDescriptor();
332        }
333
334        private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException {
335            mService = service;
336            mCloseGuard.open("constructor");
337            // TODO: go get a random socket on a random port
338            mFd = new FileDescriptor();
339        }
340
341        /** Access the inner UDP Encapsulation Socket */
342        public FileDescriptor getSocket() {
343            return mFd;
344        }
345
346        /** Retrieve the port number of the inner encapsulation socket */
347        public int getPort() {
348            return 0; // TODO get the port number from the Socket;
349        }
350
351        @Override
352        /**
353         * Release the resources that have been reserved for this Socket.
354         *
355         * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
356         * system. This must be done as part of cleanup following use of a socket. Failure to do so
357         * will cause the socket to count against a total allocation limit for IpSec and eventually
358         * fail due to resource limits.
359         *
360         * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
361         */
362        public void close() {
363            // TODO: Go close the socket
364            mCloseGuard.close();
365        }
366
367        @Override
368        protected void finalize() throws Throwable {
369            if (mCloseGuard != null) {
370                mCloseGuard.warnIfOpen();
371            }
372
373            close();
374        }
375    };
376
377    /**
378     * Open a socket that is bound to a free UDP port on the system.
379     *
380     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
381     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
382     * Encapsulation port.
383     *
384     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
385     * socket port. Explicitly opening this port is only necessary if communication is desired on
386     * that port.
387     *
388     * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
389     *     method will bind to the specified port or fail. To retrieve the port number, call {@link
390     *     android.system.Os#getsockname(FileDescriptor)}.
391     * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
392     *     of the object.
393     */
394    // Returning a socket in this fashion that has been created and bound by the system
395    // is the only safe way to ensure that a socket is both accessible to the user and
396    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
397    // the port, which could potentially impact the traffic of the next user who binds to that
398    // socket.
399    public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
400            throws IOException, ResourceUnavailableException {
401        // Temporary code
402        return new UdpEncapsulationSocket(mService, port);
403    }
404
405    /**
406     * Open a socket that is bound to a port selected by the system.
407     *
408     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
409     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
410     * Encapsulation port.
411     *
412     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
413     * socket port. Explicitly opening this port is only necessary if communication is desired on
414     * that port.
415     *
416     * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
417     */
418    // Returning a socket in this fashion that has been created and bound by the system
419    // is the only safe way to ensure that a socket is both accessible to the user and
420    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
421    // the port, which could potentially impact the traffic of the next user who binds to that
422    // socket.
423    public UdpEncapsulationSocket openUdpEncapsulationSocket()
424            throws IOException, ResourceUnavailableException {
425        // Temporary code
426        return new UdpEncapsulationSocket(mService);
427    }
428
429    /**
430     * Retrieve an instance of an IpSecManager within you application context
431     *
432     * @param context the application context for this manager
433     * @hide
434     */
435    public IpSecManager(IIpSecService service) {
436        mService = checkNotNull(service, "missing service");
437    }
438}
439