IpSecTransform.java revision 5cd64cc8012243c656875be6b84456caeabd60f5
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 android.net.IpSecManager.INVALID_RESOURCE_ID; 19 20import static com.android.internal.util.Preconditions.checkNotNull; 21 22import android.annotation.IntDef; 23import android.annotation.NonNull; 24import android.annotation.RequiresPermission; 25import android.annotation.SystemApi; 26import android.content.Context; 27import android.os.Binder; 28import android.os.Handler; 29import android.os.IBinder; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.util.Log; 33 34import com.android.internal.annotations.VisibleForTesting; 35import com.android.internal.util.Preconditions; 36 37import dalvik.system.CloseGuard; 38 39import java.io.IOException; 40import java.lang.annotation.Retention; 41import java.lang.annotation.RetentionPolicy; 42import java.net.InetAddress; 43 44/** 45 * This class represents a transform, which roughly corresponds to an IPsec Security Association. 46 * 47 * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} 48 * object encapsulates the properties and state of an IPsec security association. That includes, 49 * but is not limited to, algorithm choice, key material, and allocated system resources. 50 * 51 * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the 52 * Internet Protocol</a> 53 */ 54public final class IpSecTransform implements AutoCloseable { 55 private static final String TAG = "IpSecTransform"; 56 57 /** @hide */ 58 public static final int MODE_TRANSPORT = 0; 59 60 /** @hide */ 61 public static final int MODE_TUNNEL = 1; 62 63 /** @hide */ 64 public static final int ENCAP_NONE = 0; 65 66 /** 67 * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP 68 * header and payload. This prevents traffic from being interpreted as ESP or IKEv2. 69 * 70 * @hide 71 */ 72 public static final int ENCAP_ESPINUDP_NON_IKE = 1; 73 74 /** 75 * IPsec traffic will be encapsulated within UDP as per 76 * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>. 77 * 78 * @hide 79 */ 80 public static final int ENCAP_ESPINUDP = 2; 81 82 /** @hide */ 83 @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE}) 84 @Retention(RetentionPolicy.SOURCE) 85 public @interface EncapType {} 86 87 /** @hide */ 88 @VisibleForTesting 89 public IpSecTransform(Context context, IpSecConfig config) { 90 mContext = context; 91 mConfig = new IpSecConfig(config); 92 mResourceId = INVALID_RESOURCE_ID; 93 } 94 95 private IIpSecService getIpSecService() { 96 IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); 97 if (b == null) { 98 throw new RemoteException("Failed to connect to IpSecService") 99 .rethrowAsRuntimeException(); 100 } 101 102 return IIpSecService.Stub.asInterface(b); 103 } 104 105 /** 106 * Checks the result status and throws an appropriate exception if the status is not Status.OK. 107 */ 108 private void checkResultStatus(int status) 109 throws IOException, IpSecManager.ResourceUnavailableException, 110 IpSecManager.SpiUnavailableException { 111 switch (status) { 112 case IpSecManager.Status.OK: 113 return; 114 // TODO: Pass Error string back from bundle so that errors can be more specific 115 case IpSecManager.Status.RESOURCE_UNAVAILABLE: 116 throw new IpSecManager.ResourceUnavailableException( 117 "Failed to allocate a new IpSecTransform"); 118 case IpSecManager.Status.SPI_UNAVAILABLE: 119 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); 120 // Fall through 121 default: 122 throw new IllegalStateException( 123 "Failed to Create a Transform with status code " + status); 124 } 125 } 126 127 private IpSecTransform activate() 128 throws IOException, IpSecManager.ResourceUnavailableException, 129 IpSecManager.SpiUnavailableException { 130 synchronized (this) { 131 try { 132 IIpSecService svc = getIpSecService(); 133 IpSecTransformResponse result = svc.createTransform(mConfig, new Binder()); 134 int status = result.status; 135 checkResultStatus(status); 136 mResourceId = result.resourceId; 137 Log.d(TAG, "Added Transform with Id " + mResourceId); 138 mCloseGuard.open("build"); 139 } catch (RemoteException e) { 140 throw e.rethrowAsRuntimeException(); 141 } 142 } 143 144 return this; 145 } 146 147 /** 148 * Equals method used for testing 149 * 150 * @hide 151 */ 152 @VisibleForTesting 153 public static boolean equals(IpSecTransform lhs, IpSecTransform rhs) { 154 if (lhs == null || rhs == null) return (lhs == rhs); 155 return IpSecConfig.equals(lhs.getConfig(), rhs.getConfig()) 156 && lhs.mResourceId == rhs.mResourceId; 157 } 158 159 /** 160 * Deactivate this {@code IpSecTransform} and free allocated resources. 161 * 162 * <p>Deactivating a transform while it is still applied to a socket will result in errors on 163 * that socket. Make sure to remove transforms by calling {@link 164 * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a 165 * socket will not deactivate it (because one transform may be applied to multiple sockets). 166 * 167 * <p>It is safe to call this method on a transform that has already been deactivated. 168 */ 169 public void close() { 170 Log.d(TAG, "Removing Transform with Id " + mResourceId); 171 172 // Always safe to attempt cleanup 173 if (mResourceId == INVALID_RESOURCE_ID) { 174 mCloseGuard.close(); 175 return; 176 } 177 try { 178 IIpSecService svc = getIpSecService(); 179 svc.deleteTransform(mResourceId); 180 stopNattKeepalive(); 181 } catch (RemoteException e) { 182 throw e.rethrowAsRuntimeException(); 183 } finally { 184 mResourceId = INVALID_RESOURCE_ID; 185 mCloseGuard.close(); 186 } 187 } 188 189 /** Check that the transform was closed properly. */ 190 @Override 191 protected void finalize() throws Throwable { 192 if (mCloseGuard != null) { 193 mCloseGuard.warnIfOpen(); 194 } 195 close(); 196 } 197 198 /* Package */ 199 IpSecConfig getConfig() { 200 return mConfig; 201 } 202 203 private final IpSecConfig mConfig; 204 private int mResourceId; 205 private final Context mContext; 206 private final CloseGuard mCloseGuard = CloseGuard.get(); 207 private ConnectivityManager.PacketKeepalive mKeepalive; 208 private Handler mCallbackHandler; 209 private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = 210 new ConnectivityManager.PacketKeepaliveCallback() { 211 212 @Override 213 public void onStarted() { 214 synchronized (this) { 215 mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted()); 216 } 217 } 218 219 @Override 220 public void onStopped() { 221 synchronized (this) { 222 mKeepalive = null; 223 mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped()); 224 } 225 } 226 227 @Override 228 public void onError(int error) { 229 synchronized (this) { 230 mKeepalive = null; 231 mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error)); 232 } 233 } 234 }; 235 236 private NattKeepaliveCallback mUserKeepaliveCallback; 237 238 /** @hide */ 239 @VisibleForTesting 240 public int getResourceId() { 241 return mResourceId; 242 } 243 244 /** 245 * A callback class to provide status information regarding a NAT-T keepalive session 246 * 247 * <p>Use this callback to receive status information regarding a NAT-T keepalive session 248 * by registering it when calling {@link #startNattKeepalive}. 249 * 250 * @hide 251 */ 252 @SystemApi 253 public static class NattKeepaliveCallback { 254 /** The specified {@code Network} is not connected. */ 255 public static final int ERROR_INVALID_NETWORK = 1; 256 /** The hardware does not support this request. */ 257 public static final int ERROR_HARDWARE_UNSUPPORTED = 2; 258 /** The hardware returned an error. */ 259 public static final int ERROR_HARDWARE_ERROR = 3; 260 261 /** The requested keepalive was successfully started. */ 262 public void onStarted() {} 263 /** The keepalive was successfully stopped. */ 264 public void onStopped() {} 265 /** An error occurred. */ 266 public void onError(int error) {} 267 } 268 269 /** 270 * Start a NAT-T keepalive session for the current transform. 271 * 272 * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides 273 * a power efficient mechanism of sending NAT-T packets at a specified interval. 274 * 275 * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status 276 * information about the requested NAT-T keepalive session. 277 * @param intervalSeconds the interval between NAT-T keepalives being sent. The 278 * the allowed range is between 20 and 3600 seconds. 279 * @param handler a handler on which to post callbacks when received. 280 * 281 * @hide 282 */ 283 @SystemApi 284 @RequiresPermission(anyOf = { 285 android.Manifest.permission.NETWORK_STACK, 286 android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD 287 }) 288 public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback, 289 int intervalSeconds, @NonNull Handler handler) throws IOException { 290 checkNotNull(userCallback); 291 if (intervalSeconds < 20 || intervalSeconds > 3600) { 292 throw new IllegalArgumentException("Invalid NAT-T keepalive interval"); 293 } 294 checkNotNull(handler); 295 if (mResourceId == INVALID_RESOURCE_ID) { 296 throw new IllegalStateException( 297 "Packet keepalive cannot be started for an inactive transform"); 298 } 299 300 synchronized (mKeepaliveCallback) { 301 if (mKeepaliveCallback != null) { 302 throw new IllegalStateException("Keepalive already active"); 303 } 304 305 mUserKeepaliveCallback = userCallback; 306 ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( 307 Context.CONNECTIVITY_SERVICE); 308 mKeepalive = cm.startNattKeepalive( 309 mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback, 310 NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()), 311 4500, // FIXME urgently, we need to get the port number from the Encap socket 312 NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress())); 313 mCallbackHandler = handler; 314 } 315 } 316 317 /** 318 * Stop an ongoing NAT-T keepalive session. 319 * 320 * Calling this API will request that an ongoing NAT-T keepalive session be terminated. 321 * If this API is not called when a Transform is closed, the underlying NAT-T session will 322 * be terminated automatically. 323 * 324 * @hide 325 */ 326 @SystemApi 327 @RequiresPermission(anyOf = { 328 android.Manifest.permission.NETWORK_STACK, 329 android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD 330 }) 331 public void stopNattKeepalive() { 332 synchronized (mKeepaliveCallback) { 333 if (mKeepalive == null) { 334 Log.e(TAG, "No active keepalive to stop"); 335 return; 336 } 337 mKeepalive.stop(); 338 } 339 } 340 341 /** This class is used to build {@link IpSecTransform} objects. */ 342 public static class Builder { 343 private Context mContext; 344 private IpSecConfig mConfig; 345 346 /** 347 * Set the encryption algorithm. 348 * 349 * <p>Encryption is mutually exclusive with authenticated encryption. 350 * 351 * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. 352 */ 353 @NonNull 354 public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { 355 // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. 356 Preconditions.checkNotNull(algo); 357 mConfig.setEncryption(algo); 358 return this; 359 } 360 361 /** 362 * Set the authentication (integrity) algorithm. 363 * 364 * <p>Authentication is mutually exclusive with authenticated encryption. 365 * 366 * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. 367 */ 368 @NonNull 369 public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { 370 // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. 371 Preconditions.checkNotNull(algo); 372 mConfig.setAuthentication(algo); 373 return this; 374 } 375 376 /** 377 * Set the authenticated encryption algorithm. 378 * 379 * <p>The Authenticated Encryption (AE) class of algorithms are also known as 380 * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode 381 * algorithms (as referred to in 382 * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). 383 * 384 * <p>Authenticated encryption is mutually exclusive with encryption and authentication. 385 * 386 * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to 387 * be applied. 388 */ 389 @NonNull 390 public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { 391 Preconditions.checkNotNull(algo); 392 mConfig.setAuthenticatedEncryption(algo); 393 return this; 394 } 395 396 /** 397 * Add UDP encapsulation to an IPv4 transform. 398 * 399 * <p>This allows IPsec traffic to pass through a NAT. 400 * 401 * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec 402 * ESP Packets</a> 403 * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, 404 * NAT Traversal of IKEv2</a> 405 * @param localSocket a socket for sending and receiving encapsulated traffic 406 * @param remotePort the UDP port number of the remote host that will send and receive 407 * encapsulated traffic. In the case of IKEv2, this should be port 4500. 408 */ 409 @NonNull 410 public IpSecTransform.Builder setIpv4Encapsulation( 411 @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { 412 Preconditions.checkNotNull(localSocket); 413 mConfig.setEncapType(ENCAP_ESPINUDP); 414 if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { 415 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); 416 } 417 mConfig.setEncapSocketResourceId(localSocket.getResourceId()); 418 mConfig.setEncapRemotePort(remotePort); 419 return this; 420 } 421 422 /** 423 * Build a transport mode {@link IpSecTransform}. 424 * 425 * <p>This builds and activates a transport mode transform. Note that an active transform 426 * will not affect any network traffic until it has been applied to one or more sockets. 427 * 428 * @see IpSecManager#applyTransportModeTransform 429 * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use 430 * this transform; this address must belong to the Network used by all sockets that 431 * utilize this transform; if provided, then only traffic originating from the 432 * specified source address will be processed. 433 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 434 * traffic 435 * @throws IllegalArgumentException indicating that a particular combination of transform 436 * properties is invalid 437 * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms 438 * are active 439 * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI 440 * collides with an existing transform 441 * @throws IOException indicating other errors 442 */ 443 @NonNull 444 public IpSecTransform buildTransportModeTransform( 445 @NonNull InetAddress sourceAddress, 446 @NonNull IpSecManager.SecurityParameterIndex spi) 447 throws IpSecManager.ResourceUnavailableException, 448 IpSecManager.SpiUnavailableException, IOException { 449 Preconditions.checkNotNull(sourceAddress); 450 Preconditions.checkNotNull(spi); 451 if (spi.getResourceId() == INVALID_RESOURCE_ID) { 452 throw new IllegalArgumentException("Invalid SecurityParameterIndex"); 453 } 454 mConfig.setMode(MODE_TRANSPORT); 455 mConfig.setSourceAddress(sourceAddress.getHostAddress()); 456 mConfig.setSpiResourceId(spi.getResourceId()); 457 // FIXME: modifying a builder after calling build can change the built transform. 458 return new IpSecTransform(mContext, mConfig).activate(); 459 } 460 461 /** 462 * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some 463 * parameters have interdependencies that are checked at build time. 464 * 465 * @param sourceAddress the {@link InetAddress} that provides the source address for this 466 * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} 467 * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. 468 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 469 * traffic 470 * @throws IllegalArgumentException indicating that a particular combination of transform 471 * properties is invalid. 472 * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms 473 * are active 474 * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI 475 * collides with an existing transform 476 * @throws IOException indicating other errors 477 * @hide 478 */ 479 @SystemApi 480 @NonNull 481 @RequiresPermission(android.Manifest.permission.NETWORK_STACK) 482 public IpSecTransform buildTunnelModeTransform( 483 @NonNull InetAddress sourceAddress, 484 @NonNull IpSecManager.SecurityParameterIndex spi) 485 throws IpSecManager.ResourceUnavailableException, 486 IpSecManager.SpiUnavailableException, IOException { 487 Preconditions.checkNotNull(sourceAddress); 488 Preconditions.checkNotNull(spi); 489 if (spi.getResourceId() == INVALID_RESOURCE_ID) { 490 throw new IllegalArgumentException("Invalid SecurityParameterIndex"); 491 } 492 mConfig.setMode(MODE_TUNNEL); 493 mConfig.setSourceAddress(sourceAddress.getHostAddress()); 494 mConfig.setSpiResourceId(spi.getResourceId()); 495 return new IpSecTransform(mContext, mConfig).activate(); 496 } 497 498 /** 499 * Create a new IpSecTransform.Builder. 500 * 501 * @param context current context 502 */ 503 public Builder(@NonNull Context context) { 504 Preconditions.checkNotNull(context); 505 mContext = context; 506 mConfig = new IpSecConfig(); 507 } 508 } 509} 510