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