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