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