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