/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.ipsec.ike; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static com.android.internal.net.ipsec.ike.utils.IkeCertUtils.certificateFromByteArray; import static com.android.internal.net.ipsec.ike.utils.IkeCertUtils.privateKeyFromByteArray; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.Context; import android.net.ConnectivityManager; import android.net.Network; import android.net.eap.EapSessionConfig; import android.net.ipsec.ike.ike3gpp.Ike3gppExtension; import android.os.PersistableBundle; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute; import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Pcscf; import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Pcscf; import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.IkeConfigAttribute; import com.android.internal.net.ipsec.ike.message.IkePayload; import com.android.modules.utils.build.SdkLevel; import com.android.server.vcn.util.PersistableBundleUtils; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.security.interfaces.RSAKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; /** * IkeSessionParams contains all user provided configurations for negotiating an {@link IkeSession}. * *

Note that all negotiated configurations will be reused during rekey including SA Proposal and * lifetime. */ public final class IkeSessionParams { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({IKE_AUTH_METHOD_PSK, IKE_AUTH_METHOD_PUB_KEY_SIGNATURE, IKE_AUTH_METHOD_EAP}) public @interface IkeAuthMethod {} // Constants to describe user configured authentication methods. /** @hide */ public static final int IKE_AUTH_METHOD_PSK = 1; /** @hide */ public static final int IKE_AUTH_METHOD_PUB_KEY_SIGNATURE = 2; /** @hide */ public static final int IKE_AUTH_METHOD_EAP = 3; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({AUTH_DIRECTION_LOCAL, AUTH_DIRECTION_REMOTE, AUTH_DIRECTION_BOTH}) public @interface AuthDirection {} // Constants to describe which side (local and/or remote) the authentication configuration will // be used. /** @hide */ public static final int AUTH_DIRECTION_LOCAL = 1; /** @hide */ public static final int AUTH_DIRECTION_REMOTE = 2; /** @hide */ public static final int AUTH_DIRECTION_BOTH = 3; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ IKE_OPTION_ACCEPT_ANY_REMOTE_ID, IKE_OPTION_EAP_ONLY_AUTH, IKE_OPTION_MOBIKE, IKE_OPTION_FORCE_PORT_4500, IKE_OPTION_INITIAL_CONTACT, IKE_OPTION_REKEY_MOBILITY, IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION, IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES, IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF }) public @interface IkeOption {} /** * If set, the IKE library will accept any remote (server) identity, even if it does not match * the configured remote identity * *

See {@link Builder#setRemoteIdentification(IkeIdentification)} */ public static final int IKE_OPTION_ACCEPT_ANY_REMOTE_ID = 0; /** * If set, and EAP has been configured as the authentication method, the IKE library will * request that the remote (also) use an EAP-only authentication flow. * *

@see {@link Builder#setAuthEap(X509Certificate, EapSessionConfig)} */ public static final int IKE_OPTION_EAP_ONLY_AUTH = 1; /** * If set, the IKE Session will attempt to handle IP address changes using RFC4555 MOBIKE. * *

Upon IP address changes (including Network changes), the IKE session will initiate an RFC * 4555 MOBIKE procedure, migrating both this IKE Session and associated IPsec Transforms to the * new local and remote address pair. * *

The IKE library will first attempt to enable MOBIKE to handle the changes of underlying * network and addresses. For callers targeting SDK {@link android.os.Build.VERSION_CODES#S_V2} * and earlier, this option will implicitly enable the support for rekey-based mobility, and * thus if the server does not support MOBIKE, the IKE Session will try migration by rekeying * all associated IPsec SAs. This rekey-based mobility feature is not best-practice and has * technical issues; accordingly, it will no longer be enabled for callers targeting SDK {@link * android.os.Build.VERSION_CODES#TIRAMISU} and above. * *

Checking whether or not MOBIKE is supported by both the IKE library and the server in an * IKE Session is done via {@link IkeSessionConfiguration#isIkeExtensionEnabled(int)}. * *

It is recommended that IKE_OPTION_MOBIKE be enabled unless precluded for compatibility * reasons. * *

If this option is set for an IKE Session, Transport-mode SAs will not be allowed in that * Session. * *

Callers that need to perform migration of IPsec transforms and tunnels MUST implement * migration specific methods in {@link IkeSessionCallback} and {@link ChildSessionCallback}. */ public static final int IKE_OPTION_MOBIKE = 2; /** * Configures the IKE session to always send to port 4500. * *

If set, the IKE Session will be initiated and maintained exclusively using * destination port 4500, regardless of the presence of NAT. Otherwise, the IKE Session will * be initiated on destination port 500; then, if either a NAT is detected or both MOBIKE * and NAT-T are supported by the peer, it will proceed on port 4500. */ public static final int IKE_OPTION_FORCE_PORT_4500 = 3; /** * If set, the IKE library will send INITIAL_CONTACT notification to the peers. * *

If this option is set, the INITIAL_CONTACT notification payload is sent in IKE_AUTH. The * client can use this option to assert to the peer that this IKE SA is the only IKE SA * currently active between the authenticated identities. * *

@see "https://tools.ietf.org/html/rfc7296#section-2.4" RFC 7296, Internet Key Exchange * Protocol Version 2 (IKEv2) * *

@see {@link Builder#addIkeOption(int)} */ public static final int IKE_OPTION_INITIAL_CONTACT = 4; /** * If set, the IKE Session will attempt to handle IP address changes by rekeying with new * addresses. * *

Upon IP address changes (including Network changes), the IKE session will initiate a * standard rekey Child procedure using the new local address to replace the existing associated * IPsec transforms with new transforms tied to the new addresses. At the same time the IKE * library will notify the remote of the address change and implicitly migrate itself to the new * address. * *

This capability is NOT negotiated; it is the responsibility of the caller to ensure that * the remote supports rekey-based mobility. Failure to do so may lead to increased disruption * during mobility events. * *

This option may be set together with {@link #IKE_OPTION_MOBIKE} as a fallback. If both * {@link #IKE_OPTION_MOBIKE} and {@link #IKE_OPTION_REKEY_MOBILITY} are set: * *

* *

For callers targeting SDK {@link android.os.Build.VERSION_CODES#S_V2} or earlier, setting * {@link #IKE_OPTION_MOBIKE} will implicitly set {@link #IKE_OPTION_REKEY_MOBILITY}. * *

If this option is set for an IKE Session, Transport-mode SAs will not be allowed in that * Session. * *

Callers that need to perform migration of IPsec transforms and tunnels MUST implement * migration specific methods in {@link IkeSessionCallback} and {@link ChildSessionCallback}. * * @see {@link IKE_OPTION_MOBIKE} * @see {@link IkeSession#setNetwork(Network)} * @hide */ @SystemApi public static final int IKE_OPTION_REKEY_MOBILITY = 5; /** * If set, IKE Session will automatically select address families. * *

IP address families often have different performance characteristics on any given network. * For example, IPv6 ESP may not be hardware-accelerated by middleboxes, or completely * black-holed. This option allows the IKE session to automatically select based on the IP * address family it perceives as the most likely to work well. * * @hide */ public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6; /** * If set, the IKE session will select the NATT keepalive timers automatically. * *

NATT keepalive timers will be selected and adjusted based on the underlying network * configurations, and updated as underlying network configurations change. * * @hide */ public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7; /** * If set, the IKE session will start the NATT keepalive with a power optimization flag. * *

IKE session will start the keepalive with {@link SocketKeepalive#FLAG_AUTOMATIC_ON_OFF}. * The system will automatically disable keepalives when no TCP connections are open on the * network that is associated with the IKE session. * *

For callers relying on long-lived UDP port mappings through the IPsec layer, this flag * should never be used since the keepalive may be stopped unexpectedly. * *

This option applies to only hardware keepalive. When keepalive switches to software * keepalive because of errors on hardware keepalive, this option may be ignored. * * @hide */ // TODO(b/269200616): Move software keepalive mechanism to other place with the required // permission to get TCP socket status via netlink commands to also get benefit from this // option. @SystemApi public static final int IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF = 8; private static final int MIN_IKE_OPTION = IKE_OPTION_ACCEPT_ANY_REMOTE_ID; private static final int MAX_IKE_OPTION = IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF; /** * Automatically choose the IP version for ESP packets. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int ESP_IP_VERSION_AUTO = 0; /** * Use IPv4 for ESP packets. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int ESP_IP_VERSION_IPV4 = 4; /** * Use IPv6 for ESP packets. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int ESP_IP_VERSION_IPV6 = 6; // IP version to store in mEspIpVersion. /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ ESP_IP_VERSION_AUTO, ESP_IP_VERSION_IPV4, ESP_IP_VERSION_IPV6, }) public @interface EspIpVersion {} /** * Automatically choose the encapsulation type for ESP packets. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int ESP_ENCAP_TYPE_AUTO = 0; /** * Do not encapsulate ESP packets in transport layer protocol. * * Under this encapsulation type, the IKE Session will send NAT detection only when it is * performing mobility update from an environment with a NAT, as an attempt to stop using * UDP encapsulation for the ESP packets. If IKE Session still detects a NAT in this case, * the IKE Session will be terminated. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int ESP_ENCAP_TYPE_NONE = -1; /** * Encapsulate ESP packets in UDP. * * Under this encapsulation type, the IKE Session will send NAT detection and fake a local * NAT. In this case the IKE Session will always encapsulate ESP packets in UDP as long as * the server also supports NAT traversal. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int ESP_ENCAP_TYPE_UDP = 17; // Encap type to store in mEspEncapType. /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ ESP_ENCAP_TYPE_AUTO, ESP_ENCAP_TYPE_NONE, ESP_ENCAP_TYPE_UDP, }) public @interface EspEncapType {} /** * Automatically choose the keepalive interval. * * This constant can be passed to * {@link com.android.internal.net.ipsec.ike.IkeSessionStateMachine#setNetwork} to signify * that the keepalive delay should be deduced automatically from the underlying network. * * @see #getNattKeepAliveDelaySeconds * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int NATT_KEEPALIVE_INTERVAL_AUTO = -1; /** * Setting timer to this value will disable the Dead Peer Detection(DPD). * *

@see {@link Builder#setDpdDelaySeconds} */ @FlaggedApi("com.android.ipsec.flags.dpd_disable_api") public static final int IKE_DPD_DELAY_SEC_DISABLED = Integer.MAX_VALUE; /** @hide */ public static final SparseArray IP_VERSION_TO_STR; static { IP_VERSION_TO_STR = new SparseArray<>(); IP_VERSION_TO_STR.put(ESP_IP_VERSION_AUTO, "AUTO"); IP_VERSION_TO_STR.put(ESP_IP_VERSION_IPV4, "IPV4"); IP_VERSION_TO_STR.put(ESP_IP_VERSION_IPV6, "IPV6"); } /** @hide */ public static final SparseArray ENCAP_TYPE_TO_STR; static { ENCAP_TYPE_TO_STR = new SparseArray<>(); ENCAP_TYPE_TO_STR.put(ESP_ENCAP_TYPE_NONE, "NONE"); ENCAP_TYPE_TO_STR.put(ESP_ENCAP_TYPE_AUTO, "AUTO"); ENCAP_TYPE_TO_STR.put(ESP_ENCAP_TYPE_UDP, "UDP"); } /** @hide */ @VisibleForTesting static final int IKE_HARD_LIFETIME_SEC_MINIMUM = 300; // 5 minutes /** @hide */ @VisibleForTesting static final int IKE_HARD_LIFETIME_SEC_MAXIMUM = 86400; // 24 hours /** @hide */ @VisibleForTesting static final int IKE_HARD_LIFETIME_SEC_DEFAULT = 14400; // 4 hours /** @hide */ @VisibleForTesting static final int IKE_SOFT_LIFETIME_SEC_MINIMUM = 120; // 2 minutes /** @hide */ @VisibleForTesting static final int IKE_SOFT_LIFETIME_SEC_DEFAULT = 7200; // 2 hours /** @hide */ @VisibleForTesting static final int IKE_LIFETIME_MARGIN_SEC_MINIMUM = (int) TimeUnit.MINUTES.toSeconds(1L); /** @hide */ @VisibleForTesting static final int IKE_DPD_DELAY_SEC_MIN = 20; /** @hide */ @VisibleForTesting static final int IKE_DPD_DELAY_SEC_MAX = 1800; // 30 minutes /** @hide */ @VisibleForTesting static final int IKE_DPD_DELAY_SEC_DEFAULT = 120; // 2 minutes /** @hide */ @VisibleForTesting public static final int IKE_NATT_KEEPALIVE_DELAY_SEC_MIN = 10; /** @hide */ @VisibleForTesting public static final int IKE_NATT_KEEPALIVE_DELAY_SEC_MAX = 3600; /** @hide */ @VisibleForTesting static final int IKE_NATT_KEEPALIVE_DELAY_SEC_DEFAULT = 10; /** @hide */ @VisibleForTesting static final int DSCP_MIN = 0; /** @hide */ @VisibleForTesting static final int DSCP_MAX = 63; /** @hide */ @VisibleForTesting static final int DSCP_DEFAULT = 0; /** @hide */ @VisibleForTesting static final int IKE_RETRANS_TIMEOUT_MS_MIN = 500; /** @hide */ @VisibleForTesting static final int IKE_RETRANS_TIMEOUT_MS_MAX = (int) TimeUnit.MINUTES.toMillis(30L); /** @hide */ @VisibleForTesting static final int IKE_RETRANS_MAX_ATTEMPTS_MAX = 10; /** @hide */ @VisibleForTesting static final int[] IKE_RETRANS_TIMEOUT_MS_LIST_DEFAULT = new int[] {500, 1000, 2000, 4000, 8000}; /** @hide */ @VisibleForTesting static final int LIVENESS_RETRANS_TIMEOUT_MS_MIN = 500; /** @hide */ @VisibleForTesting static final int LIVENESS_RETRANS_TIMEOUT_MS_MAX = 30000; /** @hide */ @VisibleForTesting static final int LIVENESS_RETRANS_TIMEOUT_MS_TOTAL = 30000; /** @hide */ @VisibleForTesting static final int LIVENESS_RETRANS_MAX_ATTEMPTS_MAX = 10; /** @hide */ @VisibleForTesting static final int[] LIVENESS_RETRANS_TIMEOUT_MS_LIST_DEFAULT = new int[] {500, 1000, 2000, 4000, 8000}; private static final String SERVER_HOST_NAME_KEY = "mServerHostname"; private static final String SA_PROPOSALS_KEY = "mSaProposals"; private static final String LOCAL_ID_KEY = "mLocalIdentification"; private static final String REMOTE_ID_KEY = "mRemoteIdentification"; private static final String LOCAL_AUTH_KEY = "mLocalAuthConfig"; private static final String REMOTE_AUTH_KEY = "mRemoteAuthConfig"; private static final String CONFIG_ATTRIBUTES_KEY = "mConfigRequests"; private static final String RETRANS_TIMEOUTS_KEY = "mRetransTimeoutMsList"; private static final String LIVENESS_RETRANS_TIMEOUTS_KEY = "mLivenessRetransTimeoutMsList"; private static final String IKE_OPTIONS_KEY = "mIkeOptions"; private static final String HARD_LIFETIME_SEC_KEY = "mHardLifetimeSec"; private static final String SOFT_LIFETIME_SEC_KEY = "mSoftLifetimeSec"; private static final String DPD_DELAY_SEC_KEY = "mDpdDelaySec"; private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "mNattKeepaliveDelaySec"; private static final String DSCP_KEY = "mDscp"; private static final String IS_IKE_FRAGMENT_SUPPORTED_KEY = "mIsIkeFragmentationSupported"; private static final String IP_VERSION_KEY = "mIpVersion"; private static final String ENCAP_TYPE_KEY = "mEncapType"; @NonNull private final String mServerHostname; // @see #getNetwork for reasons of changing the annotation from @NonNull to @Nullable in SDK S. // Do not include mDefaultOrConfiguredNetwork in #hashCode or #equal because when it represents // configured network, it always has the same value as mCallerConfiguredNetwork. When it // represents a default network it can only reflects the device status at the IkeSessionParams // creation time. Since the actually default network may change after IkeSessionParams is // constructed, depending on mDefaultOrConfiguredNetwork in #hashCode and #equal to decide // if this object equals to another object does not make sense. @Nullable private final Network mDefaultOrConfiguredNetwork; @Nullable private final Network mCallerConfiguredNetwork; @NonNull private final IkeSaProposal[] mSaProposals; @NonNull private final IkeIdentification mLocalIdentification; @NonNull private final IkeIdentification mRemoteIdentification; @NonNull private final IkeAuthConfig mLocalAuthConfig; @NonNull private final IkeAuthConfig mRemoteAuthConfig; @NonNull private final IkeConfigAttribute[] mConfigRequests; @NonNull private final int[] mRetransTimeoutMsList; @NonNull private final int[] mLivenessRetransTimeoutMsList; @Nullable private final Ike3gppExtension mIke3gppExtension; private final long mIkeOptions; private final int mHardLifetimeSec; private final int mSoftLifetimeSec; private final int mDpdDelaySec; private final int mNattKeepaliveDelaySec; private final int mDscp; @EspIpVersion private final int mIpVersion; @EspEncapType private final int mEncapType; private final boolean mIsIkeFragmentationSupported; private IkeSessionParams( @NonNull String serverHostname, @NonNull Network defaultOrConfiguredNetwork, @NonNull Network callerConfiguredNetwork, @NonNull IkeSaProposal[] proposals, @NonNull IkeIdentification localIdentification, @NonNull IkeIdentification remoteIdentification, @NonNull IkeAuthConfig localAuthConfig, @NonNull IkeAuthConfig remoteAuthConfig, @NonNull IkeConfigAttribute[] configRequests, @NonNull int[] retransTimeoutMsList, @NonNull int[] livenessRetransTimeoutMsList, @Nullable Ike3gppExtension ike3gppExtension, long ikeOptions, int hardLifetimeSec, int softLifetimeSec, int dpdDelaySec, int nattKeepaliveDelaySec, int dscp, @EspIpVersion int espIpVersion, @EspEncapType int espEncapType, boolean isIkeFragmentationSupported) { mServerHostname = serverHostname; mDefaultOrConfiguredNetwork = defaultOrConfiguredNetwork; mCallerConfiguredNetwork = callerConfiguredNetwork; mSaProposals = proposals; mLocalIdentification = localIdentification; mRemoteIdentification = remoteIdentification; mLocalAuthConfig = localAuthConfig; mRemoteAuthConfig = remoteAuthConfig; mConfigRequests = configRequests; mRetransTimeoutMsList = retransTimeoutMsList; mLivenessRetransTimeoutMsList = livenessRetransTimeoutMsList; mIke3gppExtension = ike3gppExtension; mIkeOptions = ikeOptions; mHardLifetimeSec = hardLifetimeSec; mSoftLifetimeSec = softLifetimeSec; mDpdDelaySec = dpdDelaySec; mNattKeepaliveDelaySec = nattKeepaliveDelaySec; mDscp = dscp; mIpVersion = espIpVersion; mEncapType = espEncapType; mIsIkeFragmentationSupported = isIkeFragmentationSupported; } private static void validateIkeOptionOrThrow(@IkeOption int ikeOption) { if (ikeOption < MIN_IKE_OPTION || ikeOption > MAX_IKE_OPTION) { throw new IllegalArgumentException("Invalid IKE Option: " + ikeOption); } } private static long getOptionBitValue(int ikeOption) { return 1 << ikeOption; } /** * Constructs this object by deserializing a PersistableBundle * *

Constructed IkeSessionParams is guaranteed to be valid, as checked by the * IkeSessionParams.Builder * * @hide */ @NonNull public static IkeSessionParams fromPersistableBundle(@NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); IkeSessionParams.Builder builder = new IkeSessionParams.Builder(); builder.setServerHostname(in.getString(SERVER_HOST_NAME_KEY)); PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY); Objects.requireNonNull(in, "SA Proposals is null"); List saProposals = PersistableBundleUtils.toList(proposalBundle, IkeSaProposal::fromPersistableBundle); for (IkeSaProposal proposal : saProposals) { builder.addSaProposal(proposal); } builder.setLocalIdentification( IkeIdentification.fromPersistableBundle(in.getPersistableBundle(LOCAL_ID_KEY))); builder.setRemoteIdentification( IkeIdentification.fromPersistableBundle(in.getPersistableBundle(REMOTE_ID_KEY))); builder.setAuth( IkeAuthConfig.fromPersistableBundle(in.getPersistableBundle(LOCAL_AUTH_KEY)), IkeAuthConfig.fromPersistableBundle(in.getPersistableBundle(REMOTE_AUTH_KEY))); PersistableBundle configBundle = in.getPersistableBundle(CONFIG_ATTRIBUTES_KEY); Objects.requireNonNull(configBundle, "configBundle is null"); List configList = PersistableBundleUtils.toList(configBundle, ConfigAttribute::fromPersistableBundle); for (ConfigAttribute configAttribute : configList) { builder.addConfigRequest((IkeConfigAttribute) configAttribute); } builder.setRetransmissionTimeoutsMillis(in.getIntArray(RETRANS_TIMEOUTS_KEY)); builder.setLivenessRetransmissionTimeoutsMillis( in.getIntArray(LIVENESS_RETRANS_TIMEOUTS_KEY)); long ikeOptions = in.getLong(IKE_OPTIONS_KEY); for (int option = MIN_IKE_OPTION; option <= MAX_IKE_OPTION; option++) { if (hasIkeOption(ikeOptions, option)) { builder.addIkeOptionInternal(option); } else { builder.removeIkeOption(option); } } builder.setLifetimeSeconds( in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY)); builder.setDpdDelaySeconds(in.getInt(DPD_DELAY_SEC_KEY)); builder.setNattKeepAliveDelaySeconds(in.getInt(NATT_KEEPALIVE_DELAY_SEC_KEY)); builder.setIpVersion(in.getInt(IP_VERSION_KEY)); builder.setEncapType(in.getInt(ENCAP_TYPE_KEY)); // Fragmentation policy is not configurable. IkeSessionParams will always be constructed to // support fragmentation. if (!in.getBoolean(IS_IKE_FRAGMENT_SUPPORTED_KEY)) { throw new IllegalArgumentException("Invalid fragmentation policy"); } return builder.build(); } /** * Serializes this object to a PersistableBundle * * @hide */ @NonNull public PersistableBundle toPersistableBundle() { if (mCallerConfiguredNetwork != null || mIke3gppExtension != null) { throw new IllegalStateException( "Cannot convert a IkeSessionParams with a caller configured network or with" + " 3GPP extension enabled"); } final PersistableBundle result = new PersistableBundle(); result.putString(SERVER_HOST_NAME_KEY, mServerHostname); PersistableBundle saProposalBundle = PersistableBundleUtils.fromList( Arrays.asList(mSaProposals), IkeSaProposal::toPersistableBundle); result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle); result.putPersistableBundle(LOCAL_ID_KEY, mLocalIdentification.toPersistableBundle()); result.putPersistableBundle(REMOTE_ID_KEY, mRemoteIdentification.toPersistableBundle()); result.putPersistableBundle(LOCAL_AUTH_KEY, mLocalAuthConfig.toPersistableBundle()); result.putPersistableBundle(REMOTE_AUTH_KEY, mRemoteAuthConfig.toPersistableBundle()); PersistableBundle configAttributeBundle = PersistableBundleUtils.fromList( Arrays.asList(mConfigRequests), ConfigAttribute::toPersistableBundle); result.putPersistableBundle(CONFIG_ATTRIBUTES_KEY, configAttributeBundle); result.putIntArray(RETRANS_TIMEOUTS_KEY, mRetransTimeoutMsList); result.putIntArray(LIVENESS_RETRANS_TIMEOUTS_KEY, mLivenessRetransTimeoutMsList); result.putLong(IKE_OPTIONS_KEY, mIkeOptions); result.putInt(HARD_LIFETIME_SEC_KEY, mHardLifetimeSec); result.putInt(SOFT_LIFETIME_SEC_KEY, mSoftLifetimeSec); result.putInt(DPD_DELAY_SEC_KEY, mDpdDelaySec); result.putInt(NATT_KEEPALIVE_DELAY_SEC_KEY, mNattKeepaliveDelaySec); result.putInt(DSCP_KEY, mDscp); result.putBoolean(IS_IKE_FRAGMENT_SUPPORTED_KEY, mIsIkeFragmentationSupported); result.putInt(IP_VERSION_KEY, mIpVersion); result.putInt(ENCAP_TYPE_KEY, mEncapType); return result; } /** * Retrieves the configured server hostname * *

The configured server hostname will be resolved during IKE Session creation. */ @NonNull public String getServerHostname() { return mServerHostname; } /** * Retrieves the configured {@link Network}, or null if was not set * *

This getter is for internal use. Not matter {@link Builder#Builder(Context)} or {@link * Builder#Builder()} is used, this method will always return null if no Network was set by the * caller. * * @hide */ @Nullable public Network getConfiguredNetwork() { return mCallerConfiguredNetwork; } // This method was first released as a @NonNull System APi and has been changed to @Nullable // since Android S. This method needs to be @Nullable because a new Builder constructor {@link // Builder#Builder() was added in Android S, and by using the new constructor the return value // of this method will be null if no network was set. // For apps that are using a null-safe language, making this method @Nullable will break // compilation, and apps need to update their code. For apps that are not using null-safe // language, making this change will not break the backwards compatibility because for any app // that uses the deprecated constructor {@link Builder#Builder(Context)}, the return value of // this method is still guaranteed to be non-null. /** * Retrieves the configured {@link Network}, or null if was not set. * *

@see {@link Builder#setNetwork(Network)} */ @Nullable public Network getNetwork() { return mDefaultOrConfiguredNetwork; } /** * Retrieves all IkeSaProposals configured * * @deprecated Callers should use {@link #getIkeSaProposals()}. This method is deprecated * because its name does not match the return type. * @hide */ @Deprecated @SystemApi @NonNull public List getSaProposals() { return getIkeSaProposals(); } /** Retrieves all IkeSaProposals configured */ @NonNull public List getIkeSaProposals() { return Arrays.asList(mSaProposals); } /** @hide */ public IkeSaProposal[] getSaProposalsInternal() { return mSaProposals; } /** Retrieves the local (client) identity */ @NonNull public IkeIdentification getLocalIdentification() { return mLocalIdentification; } /** Retrieves the required remote (server) identity */ @NonNull public IkeIdentification getRemoteIdentification() { return mRemoteIdentification; } /** Retrieves the local (client) authentication configuration */ @NonNull public IkeAuthConfig getLocalAuthConfig() { return mLocalAuthConfig; } /** Retrieves the remote (server) authentication configuration */ @NonNull public IkeAuthConfig getRemoteAuthConfig() { return mRemoteAuthConfig; } /** Retrieves hard lifetime in seconds */ // Use "second" because smaller unit won't make sense to describe a rekey interval. @SuppressLint("MethodNameUnits") @IntRange(from = IKE_HARD_LIFETIME_SEC_MINIMUM, to = IKE_HARD_LIFETIME_SEC_MAXIMUM) public int getHardLifetimeSeconds() { return mHardLifetimeSec; } /** Retrieves soft lifetime in seconds */ // Use "second" because smaller unit does not make sense to a rekey interval. @SuppressLint("MethodNameUnits") @IntRange(from = IKE_SOFT_LIFETIME_SEC_MINIMUM, to = IKE_HARD_LIFETIME_SEC_MAXIMUM) public int getSoftLifetimeSeconds() { return mSoftLifetimeSec; } /** Retrieves the Dead Peer Detection(DPD) delay in seconds */ // Use "second" because smaller unit does not make sense to a DPD delay. @SuppressLint("MethodNameUnits") @IntRange(from = IKE_DPD_DELAY_SEC_MIN) public int getDpdDelaySeconds() { return mDpdDelaySec; } /** Retrieves the Network Address Translation Traversal (NATT) keepalive delay in seconds */ // Use "second" because smaller unit does not make sense for a NATT Keepalive delay. @SuppressLint("MethodNameUnits") @IntRange(from = IKE_NATT_KEEPALIVE_DELAY_SEC_MIN, to = IKE_NATT_KEEPALIVE_DELAY_SEC_MAX) public int getNattKeepAliveDelaySeconds() { return mNattKeepaliveDelaySec; } /** * Retrieves the DSCP field of IKE packets. * * @hide */ @SystemApi @IntRange(from = DSCP_MIN, to = DSCP_MAX) public int getDscp() { return mDscp; } /** * Retrieves the IP version. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @EspIpVersion public int getIpVersion() { return mIpVersion; } /** * Retrieves the encap type. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @EspEncapType public int getEncapType() { return mEncapType; } /** * Retrieves the relative retransmission timeout list in milliseconds * *

@see {@link Builder#setRetransmissionTimeoutsMillis(int[])} */ @NonNull public int[] getRetransmissionTimeoutsMillis() { return mRetransTimeoutMsList; } /** * Retrieves the relative retransmission timeout list for configuring on-demand liveness checks * in milliseconds. * *

The on-demand liveness check uses the returned list of liveness retransmission timeouts * set from {@link Builder#setLivenessRetransmissionTimeoutsMillis} or uses the default value of * {0.5s, 1s, 2s, 4s, 8s} if no override is defined. * *

@see {@link Builder#setLivenessRetransmissionTimeoutsMillis} for more information about * how the list is structured. * * @hide */ @SystemApi @FlaggedApi("com.android.ipsec.flags.liveness_check_api") @NonNull public int[] getLivenessRetransmissionTimeoutsMillis() { return mLivenessRetransTimeoutMsList; } /** * Retrieves the configured Ike3gppExtension, or null if it was not set. * * @hide */ @SystemApi @Nullable public Ike3gppExtension getIke3gppExtension() { return mIke3gppExtension; } private static boolean hasIkeOption(long ikeOptionsRecord, @IkeOption int ikeOption) { validateIkeOptionOrThrow(ikeOption); return (ikeOptionsRecord & getOptionBitValue(ikeOption)) != 0; } /** * Checks if the given IKE Session negotiation option is set * * @param ikeOption the option to check. * @throws IllegalArgumentException if the provided option is invalid. */ public boolean hasIkeOption(@IkeOption int ikeOption) { return hasIkeOption(mIkeOptions, ikeOption); } /** * Return all the enabled IKE Options * * @return A Set of enabled IKE options that have been added using {@link * Builder#addIkeOption(int)} */ @FlaggedApi("com.android.ipsec.flags.enabled_ike_options_api") @NonNull @IkeOption public Set getIkeOptions() { final Set result = new HashSet<>(); long ikeOptionBits = mIkeOptions; int optionValue = 0; while (ikeOptionBits > 0) { if ((ikeOptionBits & 1) == 1) { result.add(optionValue); } ikeOptionBits >>>= 1; optionValue++; } return result; } /** @hide */ public long getHardLifetimeMsInternal() { return TimeUnit.SECONDS.toMillis((long) mHardLifetimeSec); } /** @hide */ public long getSoftLifetimeMsInternal() { return TimeUnit.SECONDS.toMillis((long) mSoftLifetimeSec); } /** @hide */ public boolean isIkeFragmentationSupported() { return mIsIkeFragmentationSupported; } /** @hide */ public IkeConfigAttribute[] getConfigurationAttributesInternal() { return mConfigRequests; } /** * Retrieves the list of Configuration Requests * * @hide */ @SystemApi @NonNull public List getConfigurationRequests() { return Collections.unmodifiableList(Arrays.asList(mConfigRequests)); } /** @hide */ @Override public int hashCode() { return Objects.hash( mServerHostname, mCallerConfiguredNetwork, Arrays.hashCode(mSaProposals), mLocalIdentification, mRemoteIdentification, mLocalAuthConfig, mRemoteAuthConfig, mIke3gppExtension, Arrays.hashCode(mConfigRequests), Arrays.hashCode(mRetransTimeoutMsList), Arrays.hashCode(mLivenessRetransTimeoutMsList), mIkeOptions, mHardLifetimeSec, mSoftLifetimeSec, mDpdDelaySec, mNattKeepaliveDelaySec, mDscp, mIsIkeFragmentationSupported, mIpVersion, mEncapType); } /** @hide */ @Override public boolean equals(Object o) { if (!(o instanceof IkeSessionParams)) { return false; } IkeSessionParams other = (IkeSessionParams) o; return mServerHostname.equals(other.mServerHostname) && Objects.equals(mCallerConfiguredNetwork, other.mCallerConfiguredNetwork) && Arrays.equals(mSaProposals, other.mSaProposals) && mLocalIdentification.equals(other.mLocalIdentification) && mRemoteIdentification.equals(other.mRemoteIdentification) && mLocalAuthConfig.equals(other.mLocalAuthConfig) && mRemoteAuthConfig.equals(other.mRemoteAuthConfig) && Objects.equals(mIke3gppExtension, other.mIke3gppExtension) && Arrays.equals(mConfigRequests, other.mConfigRequests) && Arrays.equals(mRetransTimeoutMsList, other.mRetransTimeoutMsList) && Arrays.equals(mLivenessRetransTimeoutMsList, other.mLivenessRetransTimeoutMsList) && mIkeOptions == other.mIkeOptions && mHardLifetimeSec == other.mHardLifetimeSec && mSoftLifetimeSec == other.mSoftLifetimeSec && mDpdDelaySec == other.mDpdDelaySec && mNattKeepaliveDelaySec == other.mNattKeepaliveDelaySec && mDscp == other.mDscp && mIsIkeFragmentationSupported == other.mIsIkeFragmentationSupported && mIpVersion == other.mIpVersion && mEncapType == other.mEncapType; } /** * Represents an IKE session configuration request type * * @hide */ @SystemApi public interface IkeConfigRequest {} /** * Represents an IPv4 P_CSCF request * * @hide */ @SystemApi public interface ConfigRequestIpv4PcscfServer extends IkeConfigRequest { /** * Retrieves the requested IPv4 P_CSCF server address * * @return The requested P_CSCF server address, or null if no specific P_CSCF server was * requested */ @Nullable Inet4Address getAddress(); } /** * Represents an IPv6 P_CSCF request * * @hide */ @SystemApi public interface ConfigRequestIpv6PcscfServer extends IkeConfigRequest { /** * Retrieves the requested IPv6 P_CSCF server address * * @return The requested P_CSCF server address, or null if no specific P_CSCF server was * requested */ @Nullable Inet6Address getAddress(); } /** This class contains common information of an IKEv2 authentication configuration. */ public abstract static class IkeAuthConfig { private static final String AUTH_METHOD_KEY = "mAuthMethod"; private static final String AUTH_DIRECTION_KEY = "mAuthDirection"; /** @hide */ @IkeAuthMethod public final int mAuthMethod; /** @hide */ @AuthDirection public final int mAuthDirection; /** @hide */ IkeAuthConfig(@IkeAuthMethod int authMethod, @AuthDirection int authDirection) { mAuthMethod = authMethod; mAuthDirection = authDirection; } /** * Constructs this object by deserializing a PersistableBundle * * @hide */ @NonNull public static IkeAuthConfig fromPersistableBundle(PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); int authMethod = in.getInt(AUTH_METHOD_KEY); switch (authMethod) { case IKE_AUTH_METHOD_PSK: return IkeAuthPskConfig.fromPersistableBundle(in); case IKE_AUTH_METHOD_PUB_KEY_SIGNATURE: switch (in.getInt(AUTH_DIRECTION_KEY)) { case AUTH_DIRECTION_LOCAL: return IkeAuthDigitalSignLocalConfig.fromPersistableBundle(in); case AUTH_DIRECTION_REMOTE: return IkeAuthDigitalSignRemoteConfig.fromPersistableBundle(in); default: throw new IllegalArgumentException( "Digital-signature-based auth configuration with invalid" + " direction: " + in.getInt(AUTH_DIRECTION_KEY)); } case IKE_AUTH_METHOD_EAP: return IkeAuthEapConfig.fromPersistableBundle(in); default: throw new IllegalArgumentException("Invalid Auth Method: " + authMethod); } } /** * Serializes this object to a PersistableBundle * * @hide */ @NonNull protected PersistableBundle toPersistableBundle() { final PersistableBundle result = new PersistableBundle(); result.putInt(AUTH_METHOD_KEY, mAuthMethod); result.putInt(AUTH_DIRECTION_KEY, mAuthDirection); return result; } @Override public int hashCode() { return Objects.hash(mAuthMethod, mAuthDirection); } @Override public boolean equals(Object o) { if (!(o instanceof IkeAuthConfig)) { return false; } IkeAuthConfig other = (IkeAuthConfig) o; return mAuthMethod == other.mAuthMethod && mAuthDirection == other.mAuthDirection; } } /** * This class represents the configuration to support IKEv2 pre-shared-key-based authentication * of local or remote side. */ public static class IkeAuthPskConfig extends IkeAuthConfig { private static final String PSK_KEY = "mPsk"; /** @hide */ @NonNull public final byte[] mPsk; /** @hide */ @VisibleForTesting IkeAuthPskConfig(byte[] psk) { super(IKE_AUTH_METHOD_PSK, AUTH_DIRECTION_BOTH); mPsk = psk; } /** * Constructs this object by deserializing a PersistableBundle * * @hide */ @NonNull public static IkeAuthPskConfig fromPersistableBundle(@NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); PersistableBundle pskBundle = in.getPersistableBundle(PSK_KEY); Objects.requireNonNull(in, "PSK bundle is null"); return new IkeAuthPskConfig(PersistableBundleUtils.toByteArray(pskBundle)); } /** * Serializes this object to a PersistableBundle * * @hide */ @Override @NonNull public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); result.putPersistableBundle(PSK_KEY, PersistableBundleUtils.fromByteArray(mPsk)); return result; } /** Retrieves the pre-shared key */ @NonNull public byte[] getPsk() { return Arrays.copyOf(mPsk, mPsk.length); } @Override public int hashCode() { return Objects.hash(super.hashCode(), Arrays.hashCode(mPsk)); } @Override public boolean equals(Object o) { if (!super.equals(o) || !(o instanceof IkeAuthPskConfig)) { return false; } return Arrays.equals(mPsk, ((IkeAuthPskConfig) o).mPsk); } } /** * This class represents the configuration to support IKEv2 public-key-signature-based * authentication of the remote side. */ public static class IkeAuthDigitalSignRemoteConfig extends IkeAuthConfig { private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY"; /** @hide */ @Nullable public final TrustAnchor mTrustAnchor; /** * If a certificate is provided, it MUST be the root CA used by the remote (server), or * authentication will fail. If no certificate is provided, any root CA in the system's * truststore is considered acceptable. * * @hide */ @VisibleForTesting IkeAuthDigitalSignRemoteConfig(@Nullable X509Certificate caCert) { super(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE, AUTH_DIRECTION_REMOTE); if (caCert == null) { mTrustAnchor = null; } else { // The name constraints extension, defined in RFC 5280, indicates a name space // within which all subject names in subsequent certificates in a certification path // MUST be located. mTrustAnchor = new TrustAnchor(caCert, null /*nameConstraints*/); // TODO: Investigate if we need to support the name constraints extension. } } /** * Constructs this object by deserializing a PersistableBundle * * @hide */ @NonNull public static IkeAuthDigitalSignRemoteConfig fromPersistableBundle( @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); PersistableBundle trustCertBundle = in.getPersistableBundle(TRUST_CERT_KEY); X509Certificate caCert = null; if (trustCertBundle != null) { byte[] encodedCert = PersistableBundleUtils.toByteArray(trustCertBundle); caCert = certificateFromByteArray(encodedCert); } return new IkeAuthDigitalSignRemoteConfig(caCert); } /** * Serializes this object to a PersistableBundle * * @hide */ @Override @NonNull public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); try { if (mTrustAnchor != null) { result.putPersistableBundle( TRUST_CERT_KEY, PersistableBundleUtils.fromByteArray( mTrustAnchor.getTrustedCert().getEncoded())); } } catch (CertificateEncodingException e) { throw new IllegalArgumentException("Fail to encode the certificate"); } return result; } /** Retrieves the provided CA certificate for validating the remote certificate(s) */ @Nullable public X509Certificate getRemoteCaCert() { if (mTrustAnchor == null) return null; return mTrustAnchor.getTrustedCert(); } @Override public int hashCode() { // Use #getTrustedCert() because TrustAnchor does not override #hashCode() return Objects.hash( super.hashCode(), (mTrustAnchor == null) ? null : mTrustAnchor.getTrustedCert()); } @Override public boolean equals(Object o) { if (!super.equals(o) || !(o instanceof IkeAuthDigitalSignRemoteConfig)) { return false; } IkeAuthDigitalSignRemoteConfig other = (IkeAuthDigitalSignRemoteConfig) o; if (mTrustAnchor == null && other.mTrustAnchor == null) { return true; } // Compare #getTrustedCert() because TrustAnchor does not override #equals(Object) return mTrustAnchor != null && other.mTrustAnchor != null && Objects.equals( mTrustAnchor.getTrustedCert(), other.mTrustAnchor.getTrustedCert()); } } /** * This class represents the configuration to support IKEv2 public-key-signature-based * authentication of the local side. */ public static class IkeAuthDigitalSignLocalConfig extends IkeAuthConfig { private static final String END_CERT_KEY = "mEndCert"; private static final String INTERMEDIATE_CERTS_KEY = "mIntermediateCerts"; private static final String PRIVATE_KEY_KEY = "mPrivateKey"; /** @hide */ @NonNull public final X509Certificate mEndCert; /** @hide */ @NonNull public final List mIntermediateCerts; /** @hide */ @NonNull public final PrivateKey mPrivateKey; /** @hide */ @VisibleForTesting IkeAuthDigitalSignLocalConfig( @NonNull X509Certificate clientEndCert, @NonNull List clientIntermediateCerts, @NonNull PrivateKey privateKey) { super(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE, AUTH_DIRECTION_LOCAL); mEndCert = clientEndCert; mIntermediateCerts = clientIntermediateCerts; mPrivateKey = privateKey; } /** * Constructs this object by deserializing a PersistableBundle * * @hide */ @NonNull public static IkeAuthDigitalSignLocalConfig fromPersistableBundle( @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); PersistableBundle endCertBundle = in.getPersistableBundle(END_CERT_KEY); Objects.requireNonNull(endCertBundle, "End cert not provided"); byte[] encodedCert = PersistableBundleUtils.toByteArray(endCertBundle); X509Certificate endCert = certificateFromByteArray(encodedCert); PersistableBundle certsBundle = in.getPersistableBundle(INTERMEDIATE_CERTS_KEY); Objects.requireNonNull(certsBundle, "Intermediate certs not provided"); List encodedCertList = PersistableBundleUtils.toList(certsBundle, PersistableBundleUtils::toByteArray); List certList = new ArrayList<>(encodedCertList.size()); for (byte[] encoded : encodedCertList) { certList.add(certificateFromByteArray(encoded)); } PersistableBundle privateKeyBundle = in.getPersistableBundle(PRIVATE_KEY_KEY); Objects.requireNonNull(privateKeyBundle, "PrivateKey bundle is null"); PrivateKey privateKey = privateKeyFromByteArray(PersistableBundleUtils.toByteArray(privateKeyBundle)); Objects.requireNonNull(privateKeyBundle, "PrivateKey is null"); return new IkeAuthDigitalSignLocalConfig(endCert, certList, privateKey); } /** * Serializes this object to a PersistableBundle * * @hide */ @Override @NonNull public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); try { result.putPersistableBundle( END_CERT_KEY, PersistableBundleUtils.fromByteArray(mEndCert.getEncoded())); List encodedCertList = new ArrayList<>(mIntermediateCerts.size()); for (X509Certificate cert : mIntermediateCerts) { encodedCertList.add(cert.getEncoded()); } PersistableBundle certsBundle = PersistableBundleUtils.fromList( encodedCertList, PersistableBundleUtils::fromByteArray); result.putPersistableBundle(INTERMEDIATE_CERTS_KEY, certsBundle); } catch (CertificateEncodingException e) { throw new IllegalArgumentException("Fail to encode certificate"); } // TODO: b/170670506 Consider putting PrivateKey in Android KeyStore result.putPersistableBundle( PRIVATE_KEY_KEY, PersistableBundleUtils.fromByteArray(mPrivateKey.getEncoded())); return result; } /** Retrieves the client end certificate */ @NonNull public X509Certificate getClientEndCertificate() { return mEndCert; } /** Retrieves the intermediate certificates */ @NonNull public List getIntermediateCertificates() { return mIntermediateCerts; } /** Retrieves the private key */ @NonNull public PrivateKey getPrivateKey() { return mPrivateKey; } @Override public int hashCode() { return Objects.hash(super.hashCode(), mEndCert, mIntermediateCerts, mPrivateKey); } @Override public boolean equals(Object o) { if (!super.equals(o) || !(o instanceof IkeAuthDigitalSignLocalConfig)) { return false; } IkeAuthDigitalSignLocalConfig other = (IkeAuthDigitalSignLocalConfig) o; return mEndCert.equals(other.mEndCert) && mIntermediateCerts.equals(other.mIntermediateCerts) && mPrivateKey.equals(other.mPrivateKey); } } /** * This class represents the configuration to support EAP authentication of the local side. * *

@see {@link IkeSessionParams.Builder#setAuthEap(X509Certificate, EapSessionConfig)} */ public static class IkeAuthEapConfig extends IkeAuthConfig { private static final String EAP_CONFIG_KEY = "mEapConfig"; /** @hide */ @NonNull public final EapSessionConfig mEapConfig; /** @hide */ @VisibleForTesting IkeAuthEapConfig(EapSessionConfig eapConfig) { super(IKE_AUTH_METHOD_EAP, AUTH_DIRECTION_LOCAL); mEapConfig = eapConfig; } /** * Constructs this object by deserializing a PersistableBundle * * @hide */ @NonNull public static IkeAuthEapConfig fromPersistableBundle(@NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle null"); PersistableBundle eapBundle = in.getPersistableBundle(EAP_CONFIG_KEY); Objects.requireNonNull(in, "EAP Config bundle is null"); EapSessionConfig eapConfig = EapSessionConfig.fromPersistableBundle(eapBundle); Objects.requireNonNull(eapConfig, "EAP Config is null"); return new IkeAuthEapConfig(eapConfig); } /** * Serializes this object to a PersistableBundle * * @hide */ @Override @NonNull public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); result.putPersistableBundle(EAP_CONFIG_KEY, mEapConfig.toPersistableBundle()); return result; } /** Retrieves EAP configuration */ @NonNull public EapSessionConfig getEapConfig() { return mEapConfig; } @Override public int hashCode() { return Objects.hash(super.hashCode(), mEapConfig); } @Override public boolean equals(Object o) { if (!super.equals(o) || !(o instanceof IkeAuthEapConfig)) { return false; } return mEapConfig.equals(((IkeAuthEapConfig) o).mEapConfig); } } /** This class can be used to incrementally construct a {@link IkeSessionParams}. */ public static final class Builder { // This field has changed from @NonNull to @Nullable since Android S. It has to be @Nullable // because the new constructor #Builder() will not need and will not able to get a // ConnectivityManager instance anymore. Making it @Nullable does not break the backwards // compatibility because if apps use the old constructor #Builder(Context), the Builder and // the IkeSessionParams built from it will still work in the old way. @see #Builder(Context) @Nullable private ConnectivityManager mConnectivityManager; @NonNull private final List mSaProposalList = new LinkedList<>(); @NonNull private final List mConfigRequestList = new ArrayList<>(); @NonNull private int[] mRetransTimeoutMsList = Arrays.copyOf( IKE_RETRANS_TIMEOUT_MS_LIST_DEFAULT, IKE_RETRANS_TIMEOUT_MS_LIST_DEFAULT.length); @NonNull private int[] mLivenessRetransTimeoutMsList = Arrays.copyOf( LIVENESS_RETRANS_TIMEOUT_MS_LIST_DEFAULT, LIVENESS_RETRANS_TIMEOUT_MS_LIST_DEFAULT.length); @NonNull private String mServerHostname; @Nullable private Network mCallerConfiguredNetwork; @Nullable private IkeIdentification mLocalIdentification; @Nullable private IkeIdentification mRemoteIdentification; @Nullable private IkeAuthConfig mLocalAuthConfig; @Nullable private IkeAuthConfig mRemoteAuthConfig; @Nullable private Ike3gppExtension mIke3gppExtension; private long mIkeOptions = 0; private int mHardLifetimeSec = IKE_HARD_LIFETIME_SEC_DEFAULT; private int mSoftLifetimeSec = IKE_SOFT_LIFETIME_SEC_DEFAULT; private int mDpdDelaySec = IKE_DPD_DELAY_SEC_DEFAULT; private int mNattKeepaliveDelaySec = IKE_NATT_KEEPALIVE_DELAY_SEC_DEFAULT; private int mDscp = DSCP_DEFAULT; private final boolean mIsIkeFragmentationSupported = true; @EspIpVersion private int mIpVersion = ESP_IP_VERSION_AUTO; @EspEncapType private int mEncapType = ESP_ENCAP_TYPE_AUTO; /** * Construct Builder * *

This constructor is deprecated since Android S. Apps that use this constructor can * still expect {@link #build()} to throw if no configured or default network was found. But * apps that use {@link #Builder()} MUST NOT expect that behavior anymore. * *

For a caller that used this constructor and did not set any Network, {@link * IkeSessionParams#getNetwork()} will return the default Network resolved in {@link * IkeSessionParams.Builder#build()}. This return value is only informational because if * MOBIKE is enabled, IKE Session may switch to a different default Network. * * @param context a valid {@link Context} instance. * @deprecated Callers should use {@link #Builder()}.This method is deprecated because it is * unnecessary to try resolving a default network or to validate network is connected * before {@link IkeSession} starts the setup process. * @hide */ @Deprecated @SystemApi public Builder(@NonNull Context context) { this((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)); } /** * Construct Builder */ public Builder() {} /** @hide */ // TODO: b/178389011 This constructor should be removed when #Builder(Context) can be safely // removed. See #Builder(Context) for reasons. @VisibleForTesting public Builder(ConnectivityManager connectManager) { mConnectivityManager = connectManager; } /** * Construct Builder from the {@link IkeSessionParams} object. * * @param ikeSessionParams the object this Builder will be constructed with. */ public Builder(@NonNull IkeSessionParams ikeSessionParams) { mSaProposalList.addAll(ikeSessionParams.getSaProposals()); mConfigRequestList.addAll(Arrays.asList(ikeSessionParams.mConfigRequests)); int[] retransmissionTimeouts = ikeSessionParams.getRetransmissionTimeoutsMillis(); mRetransTimeoutMsList = Arrays.copyOf(retransmissionTimeouts, retransmissionTimeouts.length); int[] livenessretransmissionTimeouts = ikeSessionParams.mLivenessRetransTimeoutMsList; if (livenessretransmissionTimeouts != null) { mLivenessRetransTimeoutMsList = Arrays.copyOf( livenessretransmissionTimeouts, livenessretransmissionTimeouts.length); } mServerHostname = ikeSessionParams.getServerHostname(); mCallerConfiguredNetwork = ikeSessionParams.getConfiguredNetwork(); mLocalIdentification = ikeSessionParams.getLocalIdentification(); mRemoteIdentification = ikeSessionParams.getRemoteIdentification(); mLocalAuthConfig = ikeSessionParams.getLocalAuthConfig(); mRemoteAuthConfig = ikeSessionParams.getRemoteAuthConfig(); mIke3gppExtension = ikeSessionParams.getIke3gppExtension(); mHardLifetimeSec = ikeSessionParams.getHardLifetimeSeconds(); mSoftLifetimeSec = ikeSessionParams.getSoftLifetimeSeconds(); mDpdDelaySec = ikeSessionParams.getDpdDelaySeconds(); mNattKeepaliveDelaySec = ikeSessionParams.getNattKeepAliveDelaySeconds(); mDscp = ikeSessionParams.getDscp(); mIpVersion = ikeSessionParams.getIpVersion(); mEncapType = ikeSessionParams.getEncapType(); mIkeOptions = ikeSessionParams.mIkeOptions; if (!ikeSessionParams.mIsIkeFragmentationSupported) { throw new IllegalStateException( "mIsIkeFragmentationSupported should never be false"); } } /** * Sets the server hostname for the {@link IkeSessionParams} being built. * * @param serverHostname the hostname of the IKE server, such as "ike.android.com". * @return Builder this, to facilitate chaining. */ @NonNull public Builder setServerHostname(@NonNull String serverHostname) { Objects.requireNonNull(serverHostname, "Required argument not provided"); mServerHostname = serverHostname; return this; } /** * Sets the {@link Network} for the {@link IkeSessionParams} being built. * *

If no {@link Network} is provided, the default Network (as per {@link * ConnectivityManager#getActiveNetwork()}) will be used when constructing an {@link * IkeSession}. * * @param network the {@link Network} that IKE Session will use, or {@code null} to clear * the previously set {@link Network} * @return Builder this, to facilitate chaining. */ @NonNull public Builder setNetwork(@Nullable Network network) { mCallerConfiguredNetwork = network; return this; } /** * Sets local IKE identification for the {@link IkeSessionParams} being built. * *

It is not allowed to use KEY ID together with digital-signature-based authentication * as per RFC 7296. * * @param identification the local IKE identification. * @return Builder this, to facilitate chaining. */ @NonNull public Builder setLocalIdentification(@NonNull IkeIdentification identification) { if (identification == null) { throw new NullPointerException("Required argument not provided"); } mLocalIdentification = identification; return this; } /** * Sets remote IKE identification for the {@link IkeSessionParams} being built. * * @param identification the remote IKE identification. * @return Builder this, to facilitate chaining. */ @NonNull public Builder setRemoteIdentification(@NonNull IkeIdentification identification) { if (identification == null) { throw new NullPointerException("Required argument not provided"); } mRemoteIdentification = identification; return this; } /** * Adds an IKE SA proposal to the {@link IkeSessionParams} being built. * * @param proposal IKE SA proposal. * @return Builder this, to facilitate chaining. * @deprecated Callers should use {@link #addIkeSaProposal(IkeSaProposal)}. This method is * deprecated because its name does not match the input type. * @hide */ @Deprecated @SystemApi @NonNull public Builder addSaProposal(@NonNull IkeSaProposal proposal) { return addIkeSaProposal(proposal); } /** * Adds an IKE SA proposal to the {@link IkeSessionParams} being built. * * @param proposal IKE SA proposal. * @return Builder this, to facilitate chaining. */ @NonNull public Builder addIkeSaProposal(@NonNull IkeSaProposal proposal) { if (proposal == null) { throw new NullPointerException("Required argument not provided"); } if (proposal.getProtocolId() != IkePayload.PROTOCOL_ID_IKE) { throw new IllegalArgumentException( "Expected IKE SA Proposal but received Child SA proposal"); } mSaProposalList.add(proposal); return this; } /** * Configures authentication for IKE Session. Internal use only. * * @hide */ @NonNull private Builder setAuth(IkeAuthConfig local, IkeAuthConfig remote) { mLocalAuthConfig = local; mRemoteAuthConfig = remote; return this; } /** * Configures the {@link IkeSession} to use pre-shared-key-based authentication. * *

Both client and server MUST be authenticated using the provided shared key. IKE * authentication will fail if the remote peer tries to use other authentication methods. * *

Callers MUST declare only one authentication method. Calling this function will * override the previously set authentication configuration. * *

Callers SHOULD NOT use this if any other authentication methods can be used; PSK-based * authentication is generally considered insecure. * * @param sharedKey the shared key. * @return Builder this, to facilitate chaining. */ // #getLocalAuthConfig and #getRemoveAuthConfig are defined to retrieve // authentication configurations @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAuthPsk(@NonNull byte[] sharedKey) { if (sharedKey == null) { throw new NullPointerException("Required argument not provided"); } return setAuth(new IkeAuthPskConfig(sharedKey), new IkeAuthPskConfig(sharedKey)); } /** * Configures the {@link IkeSession} to use EAP authentication. * *

Not all EAP methods provide mutual authentication. As such EAP MUST be used in * conjunction with a public-key-signature-based authentication of the remote server, unless * EAP-Only authentication is enabled. * *

Callers may enable EAP-Only authentication by setting {@link * #IKE_OPTION_EAP_ONLY_AUTH}, which will make IKE library request the remote to use * EAP-Only authentication. The remote may opt to reject the request, at which point the * received certificates and authentication payload WILL be validated with the provided root * CA or system's truststore as usual. Only safe EAP methods as listed in RFC 5998 will be * accepted for EAP-Only authentication. * *

If {@link #IKE_OPTION_EAP_ONLY_AUTH} is set, callers MUST configure EAP as the * authentication method and all EAP methods set in EAP Session configuration MUST be safe * methods that are accepted for EAP-Only authentication. Otherwise callers will get an * exception when building the {@link IkeSessionParams} * *

Callers MUST declare only one authentication method. Calling this function will * override the previously set authentication configuration. * * @see RFC 5280, Internet X.509 Public Key * Infrastructure Certificate and Certificate Revocation List (CRL) Profile * @see RFC 5998, An Extension for EAP-Only * Authentication in IKEv2 * @param serverCaCert the CA certificate for validating the received server certificate(s). * If a certificate is provided, it MUST be the root CA used by the server, or * authentication will fail. If no certificate is provided, any root CA in the system's * truststore is considered acceptable. * @return Builder this, to facilitate chaining. */ // TODO(b/151667921): Consider also supporting configuring EAP method that is not accepted // by EAP-Only when {@link #IKE_OPTION_EAP_ONLY_AUTH} is set // MissingGetterMatchingBuilder: #getLocalAuthConfig and #getRemoveAuthConfig are defined to // retrieve authentication configurations @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAuthEap( @Nullable X509Certificate serverCaCert, @NonNull EapSessionConfig eapConfig) { if (eapConfig == null) { throw new NullPointerException("Required argument not provided"); } return setAuth( new IkeAuthEapConfig(eapConfig), new IkeAuthDigitalSignRemoteConfig(serverCaCert)); } /** * Configures the {@link IkeSession} to use public-key-signature-based authentication. * *

The public key included by the client end certificate and the private key used for * signing MUST be a matching key pair. * *

The IKE library will use the strongest signature algorithm supported by both sides. * *

Currenly only RSA digital signature is supported. * * @param serverCaCert the CA certificate for validating the received server certificate(s). * If a certificate is provided, it MUST be the root CA used by the server, or * authentication will fail. If no certificate is provided, any root CA in the system's * truststore is considered acceptable. * @param clientEndCert the end certificate for remote server to verify the locally * generated signature. * @param clientPrivateKey private key to generate outbound digital signature. The {@link * PrivateKey} MUST be an instance of {@link RSAKey}. * @return Builder this, to facilitate chaining. */ // #getLocalAuthConfig and #getRemoveAuthConfig are defined to retrieve // authentication configurations @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAuthDigitalSignature( @Nullable X509Certificate serverCaCert, @NonNull X509Certificate clientEndCert, @NonNull PrivateKey clientPrivateKey) { return setAuthDigitalSignature( serverCaCert, clientEndCert, new LinkedList(), clientPrivateKey); } /** * Configures the {@link IkeSession} to use public-key-signature-based authentication. * *

The public key included by the client end certificate and the private key used for * signing MUST be a matching key pair. * *

The IKE library will use the strongest signature algorithm supported by both sides. * *

Currenly only RSA digital signature is supported. * * @param serverCaCert the CA certificate for validating the received server certificate(s). * If a null value is provided, IKE library will try all default CA certificates stored * in Android system to do the validation. Otherwise, it will only use the provided CA * certificate. * @param clientEndCert the end certificate for remote server to verify locally generated * signature. * @param clientIntermediateCerts intermediate certificates for the remote server to * validate the end certificate. * @param clientPrivateKey private key to generate outbound digital signature. The {@link * PrivateKey} MUST be an instance of {@link RSAKey}. * @return Builder this, to facilitate chaining. */ // #getLocalAuthConfig and #getRemoveAuthConfig are defined to retrieve // authentication configurations @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAuthDigitalSignature( @Nullable X509Certificate serverCaCert, @NonNull X509Certificate clientEndCert, @NonNull List clientIntermediateCerts, @NonNull PrivateKey clientPrivateKey) { if (clientEndCert == null || clientIntermediateCerts == null || clientPrivateKey == null) { throw new NullPointerException("Required argument not provided"); } if (!(clientPrivateKey instanceof RSAKey)) { throw new IllegalArgumentException("Unsupported private key type"); } IkeAuthConfig localConfig = new IkeAuthDigitalSignLocalConfig( clientEndCert, clientIntermediateCerts, clientPrivateKey); IkeAuthConfig remoteConfig = new IkeAuthDigitalSignRemoteConfig(serverCaCert); return setAuth(localConfig, remoteConfig); } /** * Adds a configuration request. Internal use only. * * @hide */ @NonNull private Builder addConfigRequest(IkeConfigAttribute configReq) { mConfigRequestList.add(configReq); return this; } /** * Adds a specific internal P_CSCF server request to the {@link IkeSessionParams} being * built. * * @param address the requested P_CSCF address. * @return Builder this, to facilitate chaining. * @hide */ // #getConfigurationRequests is defined to retrieve PCSCF server requests @SuppressLint("MissingGetterMatchingBuilder") @SystemApi @NonNull public Builder addPcscfServerRequest(@NonNull InetAddress address) { if (address == null) { throw new NullPointerException("Required argument not provided"); } if (address instanceof Inet4Address) { return addConfigRequest(new ConfigAttributeIpv4Pcscf((Inet4Address) address)); } else if (address instanceof Inet6Address) { return addConfigRequest(new ConfigAttributeIpv6Pcscf((Inet6Address) address)); } else { throw new IllegalArgumentException("Invalid address family"); } } /** * Adds a internal P_CSCF server request to the {@link IkeSessionParams} being built. * * @param addressFamily the address family. Only {@code AF_INET} and {@code AF_INET6} are * allowed. * @return Builder this, to facilitate chaining. * @hide */ // #getConfigurationRequests is defined to retrieve PCSCF server requests @SuppressLint("MissingGetterMatchingBuilder") @SystemApi @NonNull public Builder addPcscfServerRequest(int addressFamily) { if (addressFamily == AF_INET) { return addConfigRequest(new ConfigAttributeIpv4Pcscf()); } else if (addressFamily == AF_INET6) { return addConfigRequest(new ConfigAttributeIpv6Pcscf()); } else { throw new IllegalArgumentException("Invalid address family: " + addressFamily); } } /** * Sets hard and soft lifetimes. * *

Lifetimes will not be negotiated with the remote IKE server. * * @param hardLifetimeSeconds number of seconds after which IKE SA will expire. Defaults to * 14400 seconds (4 hours). MUST be a value from 300 seconds (5 minutes) to 86400 * seconds (24 hours), inclusive. * @param softLifetimeSeconds number of seconds after which IKE SA will request rekey. * Defaults to 7200 seconds (2 hours). MUST be at least 120 seconds (2 minutes), and at * least 60 seconds (1 minute) shorter than the hard lifetime. * @return Builder this, to facilitate chaining. */ // #getHardLifetimeSeconds and #getSoftLifetimeSeconds are defined for callers to retrieve // the lifetimes @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setLifetimeSeconds( @IntRange(from = IKE_HARD_LIFETIME_SEC_MINIMUM, to = IKE_HARD_LIFETIME_SEC_MAXIMUM) int hardLifetimeSeconds, @IntRange(from = IKE_SOFT_LIFETIME_SEC_MINIMUM, to = IKE_HARD_LIFETIME_SEC_MAXIMUM) int softLifetimeSeconds) { if (hardLifetimeSeconds < IKE_HARD_LIFETIME_SEC_MINIMUM || hardLifetimeSeconds > IKE_HARD_LIFETIME_SEC_MAXIMUM || softLifetimeSeconds < IKE_SOFT_LIFETIME_SEC_MINIMUM || hardLifetimeSeconds - softLifetimeSeconds < IKE_LIFETIME_MARGIN_SEC_MINIMUM) { throw new IllegalArgumentException("Invalid lifetime value"); } mHardLifetimeSec = hardLifetimeSeconds; mSoftLifetimeSec = softLifetimeSeconds; return this; } /** * Sets the Dead Peer Detection(DPD) delay in seconds. * * @param dpdDelaySeconds number of seconds after which IKE SA will initiate DPD if no * inbound cryptographically protected IKE message was received. Defaults to 120 * seconds. MUST be a value greater than or equal to than 20 seconds. Setting the value * to {@link IkeSessionParams#IKE_DPD_DELAY_SEC_DISABLED} will disable DPD. * @return Builder this, to facilitate chaining. */ @NonNull public Builder setDpdDelaySeconds( @IntRange(from = IKE_DPD_DELAY_SEC_MIN) int dpdDelaySeconds) { if (dpdDelaySeconds < IKE_DPD_DELAY_SEC_MIN) { throw new IllegalArgumentException("Invalid DPD delay value"); } mDpdDelaySec = dpdDelaySeconds; return this; } /** * Sets the Network Address Translation Traversal (NATT) keepalive delay in seconds. * * @param nattKeepaliveDelaySeconds number of seconds between keepalive packet * transmissions. Defaults to 10 seconds. MUST be a value from 10 seconds to 3600 * seconds, inclusive. * @return Builder this, to facilitate chaining. */ @NonNull public Builder setNattKeepAliveDelaySeconds( @IntRange( from = IKE_NATT_KEEPALIVE_DELAY_SEC_MIN, to = IKE_NATT_KEEPALIVE_DELAY_SEC_MAX) int nattKeepaliveDelaySeconds) { if (nattKeepaliveDelaySeconds < IKE_NATT_KEEPALIVE_DELAY_SEC_MIN || nattKeepaliveDelaySeconds > IKE_NATT_KEEPALIVE_DELAY_SEC_MAX) { throw new IllegalArgumentException("Invalid NATT keepalive delay value"); } mNattKeepaliveDelaySec = nattKeepaliveDelaySeconds; return this; } /** * Sets the DSCP field of the IKE packets. * *

Differentiated services code point (DSCP) is a 6-bit field in the IP header that is * used for packet classification and prioritization. The DSCP field is encoded in the 6 * higher order bits of the Type of Service (ToS) in IPv4 header, or the traffic class (TC) * field in IPv6 header. * *

Any 6-bit values (0 to 63) are acceptable, whether IANA-defined, or * implementation-specific values. * * @see RFC 2474, Definition of the * Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers * @see * Differentiated Services Field Codepoints (DSCP) * @param dscp the dscp value. Defaults to 0. * @return Builder this, to facilitate chaining. * @hide */ @SystemApi @NonNull public Builder setDscp(@IntRange(from = DSCP_MIN, to = DSCP_MAX) int dscp) { if (dscp < DSCP_MIN || dscp > DSCP_MAX) { throw new IllegalArgumentException("Invalid DSCP value"); } mDscp = dscp; return this; } /** * Sets the IP version to use for ESP packets. * * @param ipVersion the IP version to use. * @return the {@code Builder} to facilitate chaining. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public Builder setIpVersion(@EspIpVersion int ipVersion) { if (ESP_IP_VERSION_AUTO != ipVersion && ESP_IP_VERSION_IPV4 != ipVersion && ESP_IP_VERSION_IPV6 != ipVersion) { throw new IllegalArgumentException("Invalid IP version : " + ipVersion); } mIpVersion = ipVersion; return this; } /** * Sets the encapsulation type to use for ESP packets. * * @param encapType the IP version to use. * @return the {@code Builder} to facilitate chaining. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public Builder setEncapType(@EspEncapType int encapType) { if (ESP_ENCAP_TYPE_AUTO != encapType && ESP_ENCAP_TYPE_NONE != encapType && ESP_ENCAP_TYPE_UDP != encapType) { throw new IllegalArgumentException("Invalid encap type : " + encapType); } mEncapType = encapType; return this; } /** * Sets the retransmission timeout list in milliseconds. * *

Configures the retransmission by providing an array of relative retransmission * timeouts in milliseconds. After sending out a request and before receiving the response, * the IKE Session will iterate through the array and wait for the relative timeout before * the next retry. If the last timeout is exceeded, the IKE Session will be terminated. * *

Each element in the array MUST be a value from 500 ms to 1800000 ms (30 minutes). The * length of the array MUST NOT exceed 10. This retransmission timeout list defaults to * {0.5s, 1s, 2s, 4s, 8s} * * @param retransTimeoutMillisList the array of relative retransmission timeout in * milliseconds. * @return Builder this, to facilitate chaining. */ @NonNull public Builder setRetransmissionTimeoutsMillis(@NonNull int[] retransTimeoutMillisList) { boolean isValid = true; if (retransTimeoutMillisList == null || retransTimeoutMillisList.length == 0 || retransTimeoutMillisList.length > IKE_RETRANS_MAX_ATTEMPTS_MAX) { isValid = false; } for (int t : retransTimeoutMillisList) { if (t < IKE_RETRANS_TIMEOUT_MS_MIN || t > IKE_RETRANS_TIMEOUT_MS_MAX) { isValid = false; } } if (!isValid) throw new IllegalArgumentException("Invalid retransmission timeout list"); mRetransTimeoutMsList = retransTimeoutMillisList; return this; } /** * Sets a list of retransmission timeouts in milliseconds for performing on-demand liveness * checks. * *

Provides the user the ability to set an array of relative retransmission timeouts for * on-demand liveness checks in milliseconds. After sending out a request and before * receiving the response, the IKE Session will iterate through the array and wait for the * relative timeout before the next retry. If the last timeout is exceeded, the IKE Session * will be terminated. * *

Each element in the array MUST be a value from 500 ms to 30000 ms. The length of the * array MUST NOT exceed 10. The total retransmission timeouts MUST NOT exceed 30000 ms. * This retransmission timeout list defaults to {0.5s, 1s, 2s, 4s, 8s}. * * @param retransTimeoutMillisList the array of relative retransmission timeout in * milliseconds for checking peer's liveness. * @return Builder this, to facilitate chaining. * @hide */ @SystemApi @FlaggedApi("com.android.ipsec.flags.liveness_check_api") @NonNull public Builder setLivenessRetransmissionTimeoutsMillis( @NonNull int[] retransTimeoutMillisList) { boolean isValid = true; int totalTimeoutMs = 0; if (retransTimeoutMillisList == null || retransTimeoutMillisList.length == 0 || retransTimeoutMillisList.length > LIVENESS_RETRANS_MAX_ATTEMPTS_MAX) { isValid = false; } for (int t : retransTimeoutMillisList) { totalTimeoutMs += t; if (t < LIVENESS_RETRANS_TIMEOUT_MS_MIN || t > LIVENESS_RETRANS_TIMEOUT_MS_MAX) { isValid = false; } } if (totalTimeoutMs > LIVENESS_RETRANS_TIMEOUT_MS_TOTAL) { isValid = false; } if (!isValid) { throw new IllegalArgumentException("Invalid liveness retransmission timeout list."); } mLivenessRetransTimeoutMsList = retransTimeoutMillisList; return this; } /** * Sets the parameters to be used for 3GPP-specific behavior during the IKE Session. * *

Setting the Ike3gppExtension also enables support for non-configurable payloads, such * as the Notify - BACKOFF_TIMER payload. * * @see 3GPP ETSI TS 24.302: Access to the 3GPP Evolved Packet Core (EPC) via non-3GPP * access networks * @param ike3gppExtension the Ike3gppExtension to use for this IKE Session. * @return Builder this, to facilitate chaining. * @hide */ @SystemApi @NonNull public Builder setIke3gppExtension(@NonNull Ike3gppExtension ike3gppExtension) { Objects.requireNonNull(ike3gppExtension, "ike3gppExtension must not be null"); mIke3gppExtension = ike3gppExtension; return this; } /** * Sets the specified IKE Option as enabled. * * @param ikeOption the option to be enabled. * @return Builder this, to facilitate chaining. * @throws IllegalArgumentException if the provided option is invalid. */ @NonNull public Builder addIkeOption(@IkeOption int ikeOption) { return addIkeOptionInternal(ikeOption); } /** @hide */ @NonNull public Builder addIkeOptionInternal(@IkeOption int ikeOption) { validateIkeOptionOrThrow(ikeOption); if (ikeOption == IKE_OPTION_MOBIKE || ikeOption == IKE_OPTION_REKEY_MOBILITY) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException("Mobility only supported for S/S+"); } else if (!SdkLevel.isAtLeastT() && ikeOption == IKE_OPTION_MOBIKE) { // Automatically enable IKE_OPTION_REKEY_MOBILITY if S <= SDK < T for // compatibility mIkeOptions |= getOptionBitValue(IKE_OPTION_REKEY_MOBILITY); } } mIkeOptions |= getOptionBitValue(ikeOption); return this; } /** * Resets (disables) the specified IKE Option. * * @param ikeOption the option to be disabled. * @return Builder this, to facilitate chaining. * @throws IllegalArgumentException if the provided option is invalid. */ // Use #removeIkeOption instead of #clearIkeOption because "clear" sounds indicating // clearing all enabled IKE options @SuppressLint("BuilderSetStyle") @NonNull public Builder removeIkeOption(@IkeOption int ikeOption) { validateIkeOptionOrThrow(ikeOption); mIkeOptions &= ~getOptionBitValue(ikeOption); return this; } /** * Validates and builds the {@link IkeSessionParams}. * * @return IkeSessionParams the validated IkeSessionParams. */ @NonNull public IkeSessionParams build() { if (mSaProposalList.isEmpty()) { throw new IllegalArgumentException("IKE SA proposal not found"); } // TODO: b/178389011 This code block should be removed when // IkeSessionParams#getNetwork() and #Builder(Context) can be safely removed. This block // makes sure if the Builder is constructed with the deprecated constructor // #Builder(Context), #build() still works in the same way and will throw exception when // there is no configured or default network. Network defaultOrConfiguredNetwork = mCallerConfiguredNetwork; if (mConnectivityManager != null && defaultOrConfiguredNetwork == null) { defaultOrConfiguredNetwork = mConnectivityManager.getActiveNetwork(); if (defaultOrConfiguredNetwork == null) { throw new IllegalArgumentException("Network not found"); } } if (mServerHostname == null || mLocalIdentification == null || mRemoteIdentification == null || mLocalAuthConfig == null || mRemoteAuthConfig == null) { throw new IllegalArgumentException("Necessary parameter missing."); } if ((mIkeOptions & getOptionBitValue(IKE_OPTION_EAP_ONLY_AUTH)) != 0) { if (!(mLocalAuthConfig instanceof IkeAuthEapConfig)) { throw new IllegalArgumentException( "If IKE_OPTION_EAP_ONLY_AUTH is set," + " eap authentication needs to be configured."); } IkeAuthEapConfig ikeAuthEapConfig = (IkeAuthEapConfig) mLocalAuthConfig; if (!ikeAuthEapConfig.getEapConfig().areAllMethodsEapOnlySafe()) { throw new IllegalArgumentException( "Only EAP-only safe method allowed" + " when using EAP-only option."); } } // as of today, the device_identity feature is only implemented for EAP-AKA if ((mIke3gppExtension != null && mIke3gppExtension.getIke3gppParams().getMobileDeviceIdentity() != null)) { if (!(mLocalAuthConfig instanceof IkeAuthEapConfig) || ((IkeAuthEapConfig) mLocalAuthConfig).getEapConfig().getEapAkaConfig() == null) { throw new IllegalArgumentException( "If device identity is set in Ike3gppParams, then EAP-KA MUST be" + " configured as an acceptable authentication method"); } } if (mLocalAuthConfig.mAuthMethod == IKE_AUTH_METHOD_PUB_KEY_SIGNATURE && mLocalIdentification.idType == IkeIdentification.ID_TYPE_KEY_ID) { throw new IllegalArgumentException( "It is not allowed to use KEY_ID as local ID when local authentication" + " method is digital-signature-based"); } if ((mIpVersion == ESP_IP_VERSION_IPV4 && mEncapType == ESP_ENCAP_TYPE_NONE) || (mIpVersion == ESP_IP_VERSION_IPV6 && mEncapType == ESP_ENCAP_TYPE_UDP)) { throw new UnsupportedOperationException("Sending packets with IPv4 ESP or IPv6 UDP" + " are not supported"); } return new IkeSessionParams( mServerHostname, defaultOrConfiguredNetwork, mCallerConfiguredNetwork, mSaProposalList.toArray(new IkeSaProposal[0]), mLocalIdentification, mRemoteIdentification, mLocalAuthConfig, mRemoteAuthConfig, mConfigRequestList.toArray(new IkeConfigAttribute[0]), mRetransTimeoutMsList, mLivenessRetransTimeoutMsList, mIke3gppExtension, mIkeOptions, mHardLifetimeSec, mSoftLifetimeSec, mDpdDelaySec, mNattKeepaliveDelaySec, mDscp, mIpVersion, mEncapType, mIsIkeFragmentationSupported); } // TODO: add methods for supporting IKE fragmentation. } /** * Dumps the state of {@link IkeSessionParams} * * @param pw {@link PrintWriter} to write the state of the object. * @param prefix prefix for indentation * @hide */ public void dump(PrintWriter pw, String prefix) { // Please make sure that the dump is thread-safe // so the client won't get a crash or exception when adding codes to the dump. pw.println("------------------------------"); pw.println("IkeSessionParams:"); pw.println(prefix + "Caller configured network: " + mCallerConfiguredNetwork); pw.println(prefix + "Dpd Delay timer in secs: " + mDpdDelaySec); pw.println(prefix + "Dscp: " + mDscp); pw.println(prefix + "Esp ip version: " + IP_VERSION_TO_STR.get(mIpVersion)); pw.println(prefix + "Esp encap type: " + ENCAP_TYPE_TO_STR.get(mEncapType)); pw.println(prefix + "Force port4500 status: " + hasIkeOption(IKE_OPTION_FORCE_PORT_4500)); pw.println(prefix + "Hard life time in secs: " + mHardLifetimeSec); pw.println( prefix + "Liveness retransmission timer in millis : " + Arrays.toString(mLivenessRetransTimeoutMsList)); pw.println(prefix + "Nat keep alive delay in secs: " + mNattKeepaliveDelaySec); pw.println(prefix + "Soft life time in secs: " + mSoftLifetimeSec); pw.println(prefix + "Remote host name: " + mServerHostname); pw.println( prefix + "Retransmission timer in millis : " + Arrays.toString(mRetransTimeoutMsList)); for (IkeSaProposal saProposal : getIkeSaProposals()) { pw.println(); pw.println(prefix + "IkeSaProposal:"); pw.println( prefix + "Encryption algorithm: " + saProposal.getEncryptionAlgorithms().toString()); pw.println( prefix + "Integrity algorithm: " + saProposal.getIntegrityAlgorithms().toString()); pw.println(prefix + "Dh Group algorithm: " + saProposal.getDhGroups().toString()); pw.println( prefix + "Prf algorithm: " + saProposal.getPseudorandomFunctions().toString()); } pw.println("------------------------------"); pw.println(); } }