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