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