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