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.Flags.IPSEC_TRANSFORM_STATE; 19 import static android.net.IpSecManager.INVALID_RESOURCE_ID; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.FlaggedApi; 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.RequiresFeature; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SystemApi; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.os.Binder; 32 import android.os.OutcomeReceiver; 33 import android.os.RemoteException; 34 import android.os.ServiceSpecificException; 35 import android.util.Log; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import dalvik.system.CloseGuard; 40 41 import java.io.IOException; 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.net.InetAddress; 45 import java.util.Objects; 46 import java.util.concurrent.Executor; 47 48 /** 49 * This class represents a transform, which roughly corresponds to an IPsec Security Association. 50 * 51 * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} 52 * object encapsulates the properties and state of an IPsec security association. That includes, 53 * but is not limited to, algorithm choice, key material, and allocated system resources. 54 * 55 * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the 56 * Internet Protocol</a> 57 */ 58 public final class IpSecTransform implements AutoCloseable { 59 private static final String TAG = "IpSecTransform"; 60 61 /** @hide */ 62 public static final int MODE_TRANSPORT = 0; 63 64 /** @hide */ 65 public static final int MODE_TUNNEL = 1; 66 67 /** @hide */ 68 public static final int ENCAP_NONE = 0; 69 70 /** 71 * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP 72 * header and payload. This prevents traffic from being interpreted as ESP or IKEv2. 73 * 74 * @hide 75 */ 76 public static final int ENCAP_ESPINUDP_NON_IKE = 1; 77 78 /** 79 * IPsec traffic will be encapsulated within UDP as per 80 * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>. 81 * 82 * @hide 83 */ 84 public static final int ENCAP_ESPINUDP = 2; 85 86 /** @hide */ 87 @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE}) 88 @Retention(RetentionPolicy.SOURCE) 89 public @interface EncapType {} 90 91 /** @hide */ 92 @VisibleForTesting IpSecTransform(Context context, IpSecConfig config)93 public IpSecTransform(Context context, IpSecConfig config) { 94 mContext = context; 95 mConfig = new IpSecConfig(config); 96 mResourceId = INVALID_RESOURCE_ID; 97 } 98 getIpSecManager(Context context)99 private IpSecManager getIpSecManager(Context context) { 100 return context.getSystemService(IpSecManager.class); 101 } 102 /** 103 * Checks the result status and throws an appropriate exception if the status is not Status.OK. 104 */ checkResultStatus(int status)105 private void checkResultStatus(int status) 106 throws IOException, IpSecManager.ResourceUnavailableException, 107 IpSecManager.SpiUnavailableException { 108 switch (status) { 109 case IpSecManager.Status.OK: 110 return; 111 // TODO: Pass Error string back from bundle so that errors can be more specific 112 case IpSecManager.Status.RESOURCE_UNAVAILABLE: 113 throw new IpSecManager.ResourceUnavailableException( 114 "Failed to allocate a new IpSecTransform"); 115 case IpSecManager.Status.SPI_UNAVAILABLE: 116 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); 117 // Fall through 118 default: 119 throw new IllegalStateException( 120 "Failed to Create a Transform with status code " + status); 121 } 122 } 123 activate()124 private IpSecTransform activate() 125 throws IOException, IpSecManager.ResourceUnavailableException, 126 IpSecManager.SpiUnavailableException { 127 synchronized (mLock) { 128 try { 129 IpSecTransformResponse result = getIpSecManager(mContext).createTransform( 130 mConfig, new Binder(), mContext.getOpPackageName()); 131 int status = result.status; 132 checkResultStatus(status); 133 mResourceId = result.resourceId; 134 Log.d(TAG, "Added Transform with Id " + mResourceId); 135 mCloseGuard.open("close"); 136 } catch (ServiceSpecificException e) { 137 throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e); 138 } 139 } 140 141 return this; 142 } 143 144 /** 145 * Standard equals. 146 */ equals(@ullable Object other)147 public boolean equals(@Nullable Object other) { 148 if (this == other) return true; 149 if (!(other instanceof IpSecTransform)) return false; 150 final IpSecTransform rhs = (IpSecTransform) other; 151 return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId; 152 } 153 154 /** 155 * Deactivate this {@code IpSecTransform} and free allocated resources. 156 * 157 * <p>Deactivating a transform while it is still applied to a socket will result in errors on 158 * that socket. Make sure to remove transforms by calling {@link 159 * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a 160 * socket will not deactivate it (because one transform may be applied to multiple sockets). 161 * 162 * <p>It is safe to call this method on a transform that has already been deactivated. 163 */ close()164 public void close() { 165 Log.d(TAG, "Removing Transform with Id " + mResourceId); 166 167 synchronized(mLock) { 168 // Always safe to attempt cleanup 169 if (mResourceId == INVALID_RESOURCE_ID) { 170 mCloseGuard.close(); 171 return; 172 } 173 174 try { 175 getIpSecManager(mContext).deleteTransform(mResourceId); 176 } catch (Exception e) { 177 // On close we swallow all random exceptions since failure to close is not 178 // actionable by the user. 179 Log.e(TAG, "Failed to close " + this + ", Exception=" + e); 180 } finally { 181 mResourceId = INVALID_RESOURCE_ID; 182 mCloseGuard.close(); 183 } 184 } 185 } 186 187 /** Check that the transform was closed properly. */ 188 @Override finalize()189 protected void finalize() throws Throwable { 190 if (mCloseGuard != null) { 191 mCloseGuard.warnIfOpen(); 192 } 193 close(); 194 } 195 196 /* Package */ getConfig()197 IpSecConfig getConfig() { 198 return mConfig; 199 } 200 201 private final IpSecConfig mConfig; 202 private final Object mLock = new Object(); 203 private int mResourceId; // Partly guarded by mLock to ensure basic safety, not correctness 204 private final Context mContext; 205 private final CloseGuard mCloseGuard = CloseGuard.get(); 206 207 /** @hide */ 208 @VisibleForTesting getResourceId()209 public int getResourceId() { 210 synchronized(mLock) { 211 return mResourceId; 212 } 213 } 214 215 /** 216 * Retrieve the current state of this IpSecTransform. 217 * 218 * @param executor The {@link Executor} on which to call the supplied callback. 219 * @param callback Callback that's called after the transform state is ready or when an error 220 * occurs. 221 * @see IpSecTransformState 222 */ 223 @FlaggedApi(IPSEC_TRANSFORM_STATE) requestIpSecTransformState( @allbackExecutor @onNull Executor executor, @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback)224 public void requestIpSecTransformState( 225 @CallbackExecutor @NonNull Executor executor, 226 @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) { 227 Objects.requireNonNull(executor); 228 Objects.requireNonNull(callback); 229 230 // TODO: Consider adding check to prevent DDoS attack. 231 232 try { 233 IpSecTransformState ipSecTransformState; 234 synchronized(mLock) { 235 ipSecTransformState = getIpSecManager(mContext).getTransformState(mResourceId); 236 } 237 executor.execute( 238 () -> { 239 callback.onResult(ipSecTransformState); 240 }); 241 } catch (IllegalStateException e) { 242 executor.execute( 243 () -> { 244 callback.onError(e); 245 }); 246 } catch (RemoteException e) { 247 executor.execute( 248 () -> { 249 callback.onError(e.rethrowFromSystemServer()); 250 }); 251 } 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 /** This class is used to build {@link IpSecTransform} objects. */ 279 public static class Builder { 280 private Context mContext; 281 private IpSecConfig mConfig; 282 283 /** 284 * Set the encryption algorithm. 285 * 286 * <p>Encryption is mutually exclusive with authenticated encryption. 287 * 288 * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. 289 */ 290 @NonNull setEncryption(@onNull IpSecAlgorithm algo)291 public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { 292 // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. 293 Objects.requireNonNull(algo); 294 mConfig.setEncryption(algo); 295 return this; 296 } 297 298 /** 299 * Set the authentication (integrity) algorithm. 300 * 301 * <p>Authentication is mutually exclusive with authenticated encryption. 302 * 303 * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. 304 */ 305 @NonNull setAuthentication(@onNull IpSecAlgorithm algo)306 public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { 307 // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. 308 Objects.requireNonNull(algo); 309 mConfig.setAuthentication(algo); 310 return this; 311 } 312 313 /** 314 * Set the authenticated encryption algorithm. 315 * 316 * <p>The Authenticated Encryption (AE) class of algorithms are also known as 317 * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode 318 * algorithms (as referred to in 319 * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). 320 * 321 * <p>Authenticated encryption is mutually exclusive with encryption and authentication. 322 * 323 * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to 324 * be applied. 325 */ 326 @NonNull setAuthenticatedEncryption(@onNull IpSecAlgorithm algo)327 public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { 328 Objects.requireNonNull(algo); 329 mConfig.setAuthenticatedEncryption(algo); 330 return this; 331 } 332 333 /** 334 * Add UDP encapsulation to an IPv4 transform. 335 * 336 * <p>This allows IPsec traffic to pass through a NAT. 337 * 338 * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec 339 * ESP Packets</a> 340 * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, 341 * NAT Traversal of IKEv2</a> 342 * @param localSocket a socket for sending and receiving encapsulated traffic 343 * @param remotePort the UDP port number of the remote host that will send and receive 344 * encapsulated traffic. In the case of IKEv2, this should be port 4500. 345 */ 346 @NonNull setIpv4Encapsulation( @onNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort)347 public IpSecTransform.Builder setIpv4Encapsulation( 348 @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { 349 Objects.requireNonNull(localSocket); 350 mConfig.setEncapType(ENCAP_ESPINUDP); 351 if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { 352 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); 353 } 354 mConfig.setEncapSocketResourceId(localSocket.getResourceId()); 355 mConfig.setEncapRemotePort(remotePort); 356 return this; 357 } 358 359 /** 360 * Build a transport mode {@link IpSecTransform}. 361 * 362 * <p>This builds and activates a transport mode transform. Note that an active transform 363 * will not affect any network traffic until it has been applied to one or more sockets. 364 * 365 * @see IpSecManager#applyTransportModeTransform 366 * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use 367 * this transform; this address must belong to the Network used by all sockets that 368 * utilize this transform; if provided, then only traffic originating from the 369 * specified source address will be processed. 370 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 371 * traffic 372 * @throws IllegalArgumentException indicating that a particular combination of transform 373 * properties is invalid 374 * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms 375 * are active 376 * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI 377 * collides with an existing transform 378 * @throws IOException indicating other errors 379 */ 380 @NonNull buildTransportModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)381 public IpSecTransform buildTransportModeTransform( 382 @NonNull InetAddress sourceAddress, 383 @NonNull IpSecManager.SecurityParameterIndex spi) 384 throws IpSecManager.ResourceUnavailableException, 385 IpSecManager.SpiUnavailableException, IOException { 386 Objects.requireNonNull(sourceAddress); 387 Objects.requireNonNull(spi); 388 if (spi.getResourceId() == INVALID_RESOURCE_ID) { 389 throw new IllegalArgumentException("Invalid SecurityParameterIndex"); 390 } 391 mConfig.setMode(MODE_TRANSPORT); 392 mConfig.setSourceAddress(sourceAddress.getHostAddress()); 393 mConfig.setSpiResourceId(spi.getResourceId()); 394 // FIXME: modifying a builder after calling build can change the built transform. 395 return new IpSecTransform(mContext, mConfig).activate(); 396 } 397 398 /** 399 * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some 400 * parameters have interdependencies that are checked at build time. 401 * 402 * @param sourceAddress the {@link InetAddress} that provides the source address for this 403 * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} 404 * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. 405 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 406 * traffic 407 * @throws IllegalArgumentException indicating that a particular combination of transform 408 * properties is invalid. 409 * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms 410 * are active 411 * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI 412 * collides with an existing transform 413 * @throws IOException indicating other errors 414 * @hide 415 */ 416 @SystemApi 417 @NonNull 418 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) 419 @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) buildTunnelModeTransform( @onNull InetAddress sourceAddress, @NonNull IpSecManager.SecurityParameterIndex spi)420 public IpSecTransform buildTunnelModeTransform( 421 @NonNull InetAddress sourceAddress, 422 @NonNull IpSecManager.SecurityParameterIndex spi) 423 throws IpSecManager.ResourceUnavailableException, 424 IpSecManager.SpiUnavailableException, IOException { 425 Objects.requireNonNull(sourceAddress); 426 Objects.requireNonNull(spi); 427 if (spi.getResourceId() == INVALID_RESOURCE_ID) { 428 throw new IllegalArgumentException("Invalid SecurityParameterIndex"); 429 } 430 mConfig.setMode(MODE_TUNNEL); 431 mConfig.setSourceAddress(sourceAddress.getHostAddress()); 432 mConfig.setSpiResourceId(spi.getResourceId()); 433 return new IpSecTransform(mContext, mConfig).activate(); 434 } 435 436 /** 437 * Create a new IpSecTransform.Builder. 438 * 439 * @param context current context 440 */ Builder(@onNull Context context)441 public Builder(@NonNull Context context) { 442 Objects.requireNonNull(context); 443 mContext = context; 444 mConfig = new IpSecConfig(); 445 } 446 } 447 448 @Override toString()449 public String toString() { 450 return new StringBuilder() 451 .append("IpSecTransform{resourceId=") 452 .append(mResourceId) 453 .append("}") 454 .toString(); 455 } 456 } 457