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 * Standard equals. 155 */ equals(Object other)156 public boolean equals(Object other) { 157 if (this == other) return true; 158 if (!(other instanceof IpSecTransform)) return false; 159 final IpSecTransform rhs = (IpSecTransform) other; 160 return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId; 161 } 162 163 /** 164 * Deactivate this {@code IpSecTransform} and free allocated resources. 165 * 166 * <p>Deactivating a transform while it is still applied to a socket will result in errors on 167 * that socket. Make sure to remove transforms by calling {@link 168 * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a 169 * socket will not deactivate it (because one transform may be applied to multiple sockets). 170 * 171 * <p>It is safe to call this method on a transform that has already been deactivated. 172 */ close()173 public void close() { 174 Log.d(TAG, "Removing Transform with Id " + mResourceId); 175 176 // Always safe to attempt cleanup 177 if (mResourceId == INVALID_RESOURCE_ID) { 178 mCloseGuard.close(); 179 return; 180 } 181 try { 182 IIpSecService svc = getIpSecService(); 183 svc.deleteTransform(mResourceId); 184 stopNattKeepalive(); 185 } catch (RemoteException e) { 186 throw e.rethrowAsRuntimeException(); 187 } catch (Exception e) { 188 // On close we swallow all random exceptions since failure to close is not 189 // actionable by the user. 190 Log.e(TAG, "Failed to close " + this + ", Exception=" + e); 191 } finally { 192 mResourceId = INVALID_RESOURCE_ID; 193 mCloseGuard.close(); 194 } 195 } 196 197 /** Check that the transform was closed properly. */ 198 @Override finalize()199 protected void finalize() throws Throwable { 200 if (mCloseGuard != null) { 201 mCloseGuard.warnIfOpen(); 202 } 203 close(); 204 } 205 206 /* Package */ getConfig()207 IpSecConfig getConfig() { 208 return mConfig; 209 } 210 211 private final IpSecConfig mConfig; 212 private int mResourceId; 213 private final Context mContext; 214 private final CloseGuard mCloseGuard = CloseGuard.get(); 215 private ConnectivityManager.PacketKeepalive mKeepalive; 216 private Handler mCallbackHandler; 217 private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = 218 new ConnectivityManager.PacketKeepaliveCallback() { 219 220 @Override 221 public void onStarted() { 222 synchronized (this) { 223 mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted()); 224 } 225 } 226 227 @Override 228 public void onStopped() { 229 synchronized (this) { 230 mKeepalive = null; 231 mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped()); 232 } 233 } 234 235 @Override 236 public void onError(int error) { 237 synchronized (this) { 238 mKeepalive = null; 239 mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error)); 240 } 241 } 242 }; 243 244 private NattKeepaliveCallback mUserKeepaliveCallback; 245 246 /** @hide */ 247 @VisibleForTesting getResourceId()248 public int getResourceId() { 249 return mResourceId; 250 } 251 252 /** 253 * A callback class to provide status information regarding a NAT-T keepalive session 254 * 255 * <p>Use this callback to receive status information regarding a NAT-T keepalive session 256 * by registering it when calling {@link #startNattKeepalive}. 257 * 258 * @hide 259 */ 260 public static class NattKeepaliveCallback { 261 /** The specified {@code Network} is not connected. */ 262 public static final int ERROR_INVALID_NETWORK = 1; 263 /** The hardware does not support this request. */ 264 public static final int ERROR_HARDWARE_UNSUPPORTED = 2; 265 /** The hardware returned an error. */ 266 public static final int ERROR_HARDWARE_ERROR = 3; 267 268 /** The requested keepalive was successfully started. */ onStarted()269 public void onStarted() {} 270 /** The keepalive was successfully stopped. */ onStopped()271 public void onStopped() {} 272 /** An error occurred. */ onError(int error)273 public void onError(int error) {} 274 } 275 276 /** 277 * Start a NAT-T keepalive session for the current transform. 278 * 279 * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides 280 * a power efficient mechanism of sending NAT-T packets at a specified interval. 281 * 282 * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status 283 * information about the requested NAT-T keepalive session. 284 * @param intervalSeconds the interval between NAT-T keepalives being sent. The 285 * the allowed range is between 20 and 3600 seconds. 286 * @param handler a handler on which to post callbacks when received. 287 * 288 * @hide 289 */ 290 @RequiresPermission(anyOf = { 291 android.Manifest.permission.MANAGE_IPSEC_TUNNELS, 292 android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD 293 }) startNattKeepalive(@onNull NattKeepaliveCallback userCallback, int intervalSeconds, @NonNull Handler handler)294 public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback, 295 int intervalSeconds, @NonNull Handler handler) throws IOException { 296 checkNotNull(userCallback); 297 if (intervalSeconds < 20 || intervalSeconds > 3600) { 298 throw new IllegalArgumentException("Invalid NAT-T keepalive interval"); 299 } 300 checkNotNull(handler); 301 if (mResourceId == INVALID_RESOURCE_ID) { 302 throw new IllegalStateException( 303 "Packet keepalive cannot be started for an inactive transform"); 304 } 305 306 synchronized (mKeepaliveCallback) { 307 if (mKeepaliveCallback != null) { 308 throw new IllegalStateException("Keepalive already active"); 309 } 310 311 mUserKeepaliveCallback = userCallback; 312 ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( 313 Context.CONNECTIVITY_SERVICE); 314 mKeepalive = cm.startNattKeepalive( 315 mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback, 316 NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()), 317 4500, // FIXME urgently, we need to get the port number from the Encap socket 318 NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress())); 319 mCallbackHandler = handler; 320 } 321 } 322 323 /** 324 * Stop an ongoing NAT-T keepalive session. 325 * 326 * Calling this API will request that an ongoing NAT-T keepalive session be terminated. 327 * If this API is not called when a Transform is closed, the underlying NAT-T session will 328 * be terminated automatically. 329 * 330 * @hide 331 */ 332 @RequiresPermission(anyOf = { 333 android.Manifest.permission.MANAGE_IPSEC_TUNNELS, 334 android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD 335 }) stopNattKeepalive()336 public void stopNattKeepalive() { 337 synchronized (mKeepaliveCallback) { 338 if (mKeepalive == null) { 339 Log.e(TAG, "No active keepalive to stop"); 340 return; 341 } 342 mKeepalive.stop(); 343 } 344 } 345 346 /** This class is used to build {@link IpSecTransform} objects. */ 347 public static class Builder { 348 private Context mContext; 349 private IpSecConfig mConfig; 350 351 /** 352 * Set the encryption algorithm. 353 * 354 * <p>Encryption is mutually exclusive with authenticated encryption. 355 * 356 * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. 357 */ 358 @NonNull setEncryption(@onNull IpSecAlgorithm algo)359 public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { 360 // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. 361 Preconditions.checkNotNull(algo); 362 mConfig.setEncryption(algo); 363 return this; 364 } 365 366 /** 367 * Set the authentication (integrity) algorithm. 368 * 369 * <p>Authentication is mutually exclusive with authenticated encryption. 370 * 371 * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. 372 */ 373 @NonNull setAuthentication(@onNull IpSecAlgorithm algo)374 public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { 375 // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. 376 Preconditions.checkNotNull(algo); 377 mConfig.setAuthentication(algo); 378 return this; 379 } 380 381 /** 382 * Set the authenticated encryption algorithm. 383 * 384 * <p>The Authenticated Encryption (AE) class of algorithms are also known as 385 * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode 386 * algorithms (as referred to in 387 * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). 388 * 389 * <p>Authenticated encryption is mutually exclusive with encryption and authentication. 390 * 391 * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to 392 * be applied. 393 */ 394 @NonNull setAuthenticatedEncryption(@onNull IpSecAlgorithm algo)395 public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { 396 Preconditions.checkNotNull(algo); 397 mConfig.setAuthenticatedEncryption(algo); 398 return this; 399 } 400 401 /** 402 * Add UDP encapsulation to an IPv4 transform. 403 * 404 * <p>This allows IPsec traffic to pass through a NAT. 405 * 406 * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec 407 * ESP Packets</a> 408 * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, 409 * NAT Traversal of IKEv2</a> 410 * @param localSocket a socket for sending and receiving encapsulated traffic 411 * @param remotePort the UDP port number of the remote host that will send and receive 412 * encapsulated traffic. In the case of IKEv2, this should be port 4500. 413 */ 414 @NonNull setIpv4Encapsulation( @onNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort)415 public IpSecTransform.Builder setIpv4Encapsulation( 416 @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { 417 Preconditions.checkNotNull(localSocket); 418 mConfig.setEncapType(ENCAP_ESPINUDP); 419 if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { 420 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); 421 } 422 mConfig.setEncapSocketResourceId(localSocket.getResourceId()); 423 mConfig.setEncapRemotePort(remotePort); 424 return this; 425 } 426 427 /** 428 * Build a transport mode {@link IpSecTransform}. 429 * 430 * <p>This builds and activates a transport mode transform. Note that an active transform 431 * will not affect any network traffic until it has been applied to one or more sockets. 432 * 433 * @see IpSecManager#applyTransportModeTransform 434 * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use 435 * this transform; this address must belong to the Network used by all sockets that 436 * utilize this transform; if provided, then only traffic originating from the 437 * specified source address will be processed. 438 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 439 * traffic 440 * @throws IllegalArgumentException indicating that a particular combination of transform 441 * properties is invalid 442 * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms 443 * are active 444 * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI 445 * collides with an existing transform 446 * @throws IOException indicating other errors 447 */ 448 @NonNull buildTransportModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)449 public IpSecTransform buildTransportModeTransform( 450 @NonNull InetAddress sourceAddress, 451 @NonNull IpSecManager.SecurityParameterIndex spi) 452 throws IpSecManager.ResourceUnavailableException, 453 IpSecManager.SpiUnavailableException, IOException { 454 Preconditions.checkNotNull(sourceAddress); 455 Preconditions.checkNotNull(spi); 456 if (spi.getResourceId() == INVALID_RESOURCE_ID) { 457 throw new IllegalArgumentException("Invalid SecurityParameterIndex"); 458 } 459 mConfig.setMode(MODE_TRANSPORT); 460 mConfig.setSourceAddress(sourceAddress.getHostAddress()); 461 mConfig.setSpiResourceId(spi.getResourceId()); 462 // FIXME: modifying a builder after calling build can change the built transform. 463 return new IpSecTransform(mContext, mConfig).activate(); 464 } 465 466 /** 467 * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some 468 * parameters have interdependencies that are checked at build time. 469 * 470 * @param sourceAddress the {@link InetAddress} that provides the source address for this 471 * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} 472 * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. 473 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 474 * traffic 475 * @throws IllegalArgumentException indicating that a particular combination of transform 476 * properties is invalid. 477 * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms 478 * are active 479 * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI 480 * collides with an existing transform 481 * @throws IOException indicating other errors 482 * @hide 483 */ 484 @SystemApi 485 @NonNull 486 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) 487 @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) buildTunnelModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)488 public IpSecTransform buildTunnelModeTransform( 489 @NonNull InetAddress sourceAddress, 490 @NonNull IpSecManager.SecurityParameterIndex spi) 491 throws IpSecManager.ResourceUnavailableException, 492 IpSecManager.SpiUnavailableException, IOException { 493 Preconditions.checkNotNull(sourceAddress); 494 Preconditions.checkNotNull(spi); 495 if (spi.getResourceId() == INVALID_RESOURCE_ID) { 496 throw new IllegalArgumentException("Invalid SecurityParameterIndex"); 497 } 498 mConfig.setMode(MODE_TUNNEL); 499 mConfig.setSourceAddress(sourceAddress.getHostAddress()); 500 mConfig.setSpiResourceId(spi.getResourceId()); 501 return new IpSecTransform(mContext, mConfig).activate(); 502 } 503 504 /** 505 * Create a new IpSecTransform.Builder. 506 * 507 * @param context current context 508 */ Builder(@onNull Context context)509 public Builder(@NonNull Context context) { 510 Preconditions.checkNotNull(context); 511 mContext = context; 512 mConfig = new IpSecConfig(); 513 } 514 } 515 516 @Override toString()517 public String toString() { 518 return new StringBuilder() 519 .append("IpSecTransform{resourceId=") 520 .append(mResourceId) 521 .append("}") 522 .toString(); 523 } 524 } 525