blob: 84b630e30fbe7fd01c34ea981c079ee013d2f4bc [file] [log] [blame]
/*
* 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.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 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.server.vcn.util.PersistableBundleUtils;
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.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* IkeSessionParams contains all user provided configurations for negotiating an {@link IkeSession}.
*
* <p>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})
public @interface IkeOption {}
/**
* If set, the IKE library will accept any remote (server) identity, even if it does not match
* the configured remote identity
*
* <p>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.
*
* <p>@see {@link Builder#setAuthEap(X509Certificate, EapSessionConfig)}
*/
public static final int IKE_OPTION_EAP_ONLY_AUTH = 1;
/**
* If set, the IKE library will attempt to enable MOBIKE for the resulting IKE Session.
*
* <p>To support MOBIKE, callers must implement:
*
* <ul>
* <li>{@link IkeSessionCallback#onIkeSessionConnectionInfoChanged(IkeSessionConnectionInfo)}:
* this MUST migrate all IpSecTunnelInterface instances associated with this IkeSession.
* <li>{@link ChildSessionCallback#onIpSecTransformsMigrated(android.net.IpSecTransform,
* android.net.IpSecTransform)}: this MUST re-apply the migrated transforms to the
* IpSecTunnelInterface associated with this ChildSessionCallback, via {@link
* android.net.IpSecManager#applyTunnelModeTransform(
* android.net.IpSecManager.IpSecTunnelInterface, int, android.net.IpSecTransform)}.
* </ul>
*
* <p>MOBIKE support is compatible with two Network modes:
*
* <ul>
* <li><b>Caller managed:</b> The caller controls the underlying Network for the IKE Session
* at all times. The IKE Session will only change underlying Networks if the caller
* initiates it through {@link IkeSession#setNetwork(Network)}. If the caller-specified
* Network is lost, they will be notified via {@link
* IkeSessionCallback#onError(android.net.ipsec.ike.exceptions.IkeException)} with an
* {@link android.net.ipsec.ike.exceptions.IkeNetworkLostException} specifying the Network
* that was lost.
* <li><b>Platform Default:</b> The IKE Session will always track the application default
* Network. The IKE Session will start on the application default Network, and any
* subsequent changes to the default Network (after the IKE_AUTH exchange completes) will
* cause the IKE Session's underlying Network to change. If the default Network is lost
* with no replacements, the caller will be notified via {@link
* IkeSessionCallback#onError(android.net.ipsec.ike.exceptions.IkeException)} with an
* {@link android.net.ipsec.ike.exceptions.IkeNetworkLostException}. The caller can either
* wait until for a new default Network to become available or they may close the Session
* manually via {@link IkeSession#close()}. Note that the IKE Session's maximum
* retransmissions may expire while waiting for a new default Network, in which case the
* Session will automatically close.
* </ul>
*
* <p>Use of MOBIKE in the IKE Session requires the peer to also support MOBIKE.
*
* <p>If this option is set for an IKE Session, Transport-mode SAs will not be allowed in that
* Session.
*
* <p>Checking for MOBIKE use in an IKE Session is done via {@link
* IkeSessionConfiguration#isIkeExtensionEnabled(int)}.
*/
// TODO(b/175416035): update docs to @link to API for migrating IpSecTunnelInterfaces
public static final int IKE_OPTION_MOBIKE = 2;
private static final int MIN_IKE_OPTION = IKE_OPTION_ACCEPT_ANY_REMOTE_ID;
private static final int MAX_IKE_OPTION = IKE_OPTION_MOBIKE;
/** @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 static final int IKE_NATT_KEEPALIVE_DELAY_SEC_MIN = 10;
/** @hide */
@VisibleForTesting 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 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};
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 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 IS_IKE_FRAGMENT_SUPPORTED_KEY = "mIsIkeFragmentationSupported";
@NonNull private final String mServerHostname;
// @see #getNetwork for reasons of changing the annotation from @NonNull to @Nullable in Android
// S and why it is safe.
@Nullable private final Network mNetwork;
@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;
@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 boolean mIsIkeFragmentationSupported;
private IkeSessionParams(
@NonNull String serverHostname,
@NonNull Network network,
@NonNull Network callerConfiguredNetwork,
@NonNull IkeSaProposal[] proposals,
@NonNull IkeIdentification localIdentification,
@NonNull IkeIdentification remoteIdentification,
@NonNull IkeAuthConfig localAuthConfig,
@NonNull IkeAuthConfig remoteAuthConfig,
@NonNull IkeConfigAttribute[] configRequests,
@NonNull int[] retransTimeoutMsList,
@Nullable Ike3gppExtension ike3gppExtension,
long ikeOptions,
int hardLifetimeSec,
int softLifetimeSec,
int dpdDelaySec,
int nattKeepaliveDelaySec,
boolean isIkeFragmentationSupported) {
mServerHostname = serverHostname;
mNetwork = network;
mCallerConfiguredNetwork = callerConfiguredNetwork;
mSaProposals = proposals;
mLocalIdentification = localIdentification;
mRemoteIdentification = remoteIdentification;
mLocalAuthConfig = localAuthConfig;
mRemoteAuthConfig = remoteAuthConfig;
mConfigRequests = configRequests;
mRetransTimeoutMsList = retransTimeoutMsList;
mIke3gppExtension = ike3gppExtension;
mIkeOptions = ikeOptions;
mHardLifetimeSec = hardLifetimeSec;
mSoftLifetimeSec = softLifetimeSec;
mDpdDelaySec = dpdDelaySec;
mNattKeepaliveDelaySec = nattKeepaliveDelaySec;
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
*
* <p>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<IkeSaProposal> 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<ConfigAttribute> configList =
PersistableBundleUtils.toList(configBundle, ConfigAttribute::fromPersistableBundle);
for (ConfigAttribute configAttribute : configList) {
builder.addConfigRequest((IkeConfigAttribute) configAttribute);
}
builder.setRetransmissionTimeoutsMillis(in.getIntArray(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.addIkeOption(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));
// 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.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.putBoolean(IS_IKE_FRAGMENT_SUPPORTED_KEY, mIsIkeFragmentationSupported);
return result;
}
/**
* Retrieves the configured server hostname
*
* <p>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 */
@Nullable
public Network getConfiguredNetwork() {
return mCallerConfiguredNetwork;
}
/**
* Retrieves the configured or default {@link Network}
*
* <p>This method is deprecated and its annotation has been changed from @NonNull 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. Also making this method @Nullable 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.
*
* <p>For a caller that used {@link Builder#Builder(Context)} and did not set any Network,
* this method will return the default Network resolved in
* {@link IkeSessionParams.Builder#build()}. The return value of this method is only
* informational because if MOBIKE is enabled, IKE Session may switch to a different default
* Network.
*
* @hide
* @deprecated Callers should use {@link #getConfiguredNetwork}. This method is deprecated
* because its name makes it sound like it will return the actual network the session is
* running on, when it will only return the network that was configured or resolved in the
* builder.
*/
@Deprecated
@SystemApi
@NonNull
// TODO: b/163604823 Make it @Nullable
public Network getNetwork() {
return mNetwork;
}
/** Retrieves all IkeSaProposals configured */
@NonNull
public List<IkeSaProposal> getSaProposals() {
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, to = IKE_DPD_DELAY_SEC_MAX)
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 relative retransmission timeout list in milliseconds
*
* <p>@see {@link Builder#setRetransmissionTimeoutsMillis(int[])}
*/
@NonNull
public int[] getRetransmissionTimeoutsMillis() {
return mRetransTimeoutMsList;
}
/**
* 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 */
public boolean hasIkeOption(@IkeOption int ikeOption) {
return hasIkeOption(mIkeOptions, ikeOption);
}
/** @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 */
@NonNull
public List<IkeConfigRequest> getConfigurationRequests() {
return Collections.unmodifiableList(Arrays.asList(mConfigRequests));
}
/** @hide */
@Override
public int hashCode() {
return Objects.hash(
mServerHostname,
mCallerConfiguredNetwork,
mNetwork,
Arrays.hashCode(mSaProposals),
mLocalIdentification,
mRemoteIdentification,
mLocalAuthConfig,
mRemoteAuthConfig,
mIke3gppExtension,
Arrays.hashCode(mConfigRequests),
Arrays.hashCode(mRetransTimeoutMsList),
mIkeOptions,
mHardLifetimeSec,
mSoftLifetimeSec,
mDpdDelaySec,
mIsIkeFragmentationSupported);
}
/** @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)
&& Objects.equals(mNetwork, other.mNetwork)
&& 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)
&& mIkeOptions == other.mIkeOptions
&& mHardLifetimeSec == other.mHardLifetimeSec
&& mSoftLifetimeSec == other.mSoftLifetimeSec
&& mDpdDelaySec == other.mDpdDelaySec
&& mIsIkeFragmentationSupported == other.mIsIkeFragmentationSupported;
}
/** Represents an IKE session configuration request type */
public interface IkeConfigRequest {}
/** Represents an IPv4 P_CSCF request */
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 */
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<X509Certificate> mIntermediateCerts;
/** @hide */
@NonNull public final PrivateKey mPrivateKey;
/** @hide */
@VisibleForTesting
IkeAuthDigitalSignLocalConfig(
@NonNull X509Certificate clientEndCert,
@NonNull List<X509Certificate> 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<byte[]> encodedCertList =
PersistableBundleUtils.toList(certsBundle, PersistableBundleUtils::toByteArray);
List<X509Certificate> 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<byte[]> 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<X509Certificate> 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.
*
* <p>@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<IkeSaProposal> mSaProposalList = new LinkedList<>();
@NonNull private final List<IkeConfigAttribute> mConfigRequestList = new ArrayList<>();
@NonNull
private int[] mRetransTimeoutMsList =
Arrays.copyOf(
IKE_RETRANS_TIMEOUT_MS_LIST_DEFAULT,
IKE_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 final boolean mIsIkeFragmentationSupported = true;
/**
* Construct Builder
*
* <p>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.
*
* <p>This method is deprecated because it is unnecessary to try resolving a default network
* or to validate network state before IkeSession starts the packet exchanges. It will also
* makes it hard to store IkeSessionParams in a {@link android.os.Parcelable} object such as
* VcnConfig.
*
* @param context a valid {@link Context} instance.
*/
// TODO: b/163604823 Deprecate this method
public Builder(@NonNull Context context) {
this((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
}
/**
* Construct Builder
*
* @hide
*/
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;
}
/**
* 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.
*
* <p>If no {@link Network} is provided, the default Network (as per {@link
* ConnectivityManager#getActiveNetwork()}) will be used.
*
* @param network the {@link Network} that IKE Session will use.
* @return Builder this, to facilitate chaining.
*/
@NonNull
public Builder setConfiguredNetwork(@NonNull Network network) {
if (network == null) {
throw new NullPointerException("Required argument not provided");
}
mCallerConfiguredNetwork = network;
return this;
}
/**
* Behaves identically to setConfiguredNetwork.
*
* @param network the {@link Network} that IKE Session will use.
* @return Builder this, to facilitate chaining.
* @hide
* @deprecated Callers should use {@link #setConfiguredNetwork}. This method is deprecated
* because its name fail to match the corresponding getter name {@link #getNetwork()}
*/
@Deprecated
@SystemApi
@NonNull
public Builder setNetwork(@NonNull Network network) {
return setConfiguredNetwork(network);
}
/**
* Sets local IKE identification for the {@link IkeSessionParams} being built.
*
* <p>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.
*/
@NonNull
public Builder addSaProposal(@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.
*
* <p>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.
*
* <p>Callers MUST declare only one authentication method. Calling this function will
* override the previously set authentication configuration.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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}
*
* <p>Callers MUST declare only one authentication method. Calling this function will
* override the previously set authentication configuration.
*
* @see <a href="https://tools.ietf.org/html/rfc5280">RFC 5280, Internet X.509 Public Key
* Infrastructure Certificate and Certificate Revocation List (CRL) Profile</a>
* @see <a href="https://tools.ietf.org/html/rfc5998">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.
*
* <p>The public key included by the client end certificate and the private key used for
* signing MUST be a matching key pair.
*
* <p>The IKE library will use the strongest signature algorithm supported by both sides.
*
* <p>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<X509Certificate>(),
clientPrivateKey);
}
/**
* Configures the {@link IkeSession} to use public-key-signature-based authentication.
*
* <p>The public key included by the client end certificate and the private key used for
* signing MUST be a matching key pair.
*
* <p>The IKE library will use the strongest signature algorithm supported by both sides.
*
* <p>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<X509Certificate> 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.
*/
// #getConfigurationRequests is defined to retrieve PCSCF server requests
@SuppressLint("MissingGetterMatchingBuilder")
@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 {@link OsConstants.AF_INET} and {@link
* OsConstants.AF_INET6} are allowed.
* @return Builder this, to facilitate chaining.
*/
// #getConfigurationRequests is defined to retrieve PCSCF server requests
@SuppressLint("MissingGetterMatchingBuilder")
@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.
*
* <p>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 from 20 seconds to 1800 seconds, inclusive.
* @return Builder this, to facilitate chaining.
*/
@NonNull
public Builder setDpdDelaySeconds(
@IntRange(from = IKE_DPD_DELAY_SEC_MIN, to = IKE_DPD_DELAY_SEC_MAX)
int dpdDelaySeconds) {
if (dpdDelaySeconds < IKE_DPD_DELAY_SEC_MIN
|| dpdDelaySeconds > IKE_DPD_DELAY_SEC_MAX) {
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 retransmission timeout list in milliseconds.
*
* <p>Configures the retransmission by providing an array of relative retransmission
* timeouts in milliseconds, where each timeout is the waiting time before next retry,
* except the last timeout is the waiting time before terminating the IKE Session. 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 the parameters to be used for 3GPP-specific behavior during the IKE Session.
*
* <p>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.
*/
// Use #hasIkeOption instead of @getIkeOptions because #hasIkeOption allows callers to check
// the presence of one IKE option more easily
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder addIkeOption(@IkeOption int ikeOption) {
validateIkeOptionOrThrow(ikeOption);
mIkeOptions |= getOptionBitValue(ikeOption);
return this;
}
/**
* Resets (disables) the specified IKE Option.
*
* @param ikeOption the option to be disabled.
* @return Builder this, to facilitate chaining.
*/
// 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 = null;
if (mConnectivityManager != null) {
defaultOrConfiguredNetwork =
mCallerConfiguredNetwork != null
? mCallerConfiguredNetwork
: 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.");
}
}
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");
}
return new IkeSessionParams(
mServerHostname,
defaultOrConfiguredNetwork,
mCallerConfiguredNetwork,
mSaProposalList.toArray(new IkeSaProposal[0]),
mLocalIdentification,
mRemoteIdentification,
mLocalAuthConfig,
mRemoteAuthConfig,
mConfigRequestList.toArray(new IkeConfigAttribute[0]),
mRetransTimeoutMsList,
mIke3gppExtension,
mIkeOptions,
mHardLifetimeSec,
mSoftLifetimeSec,
mDpdDelaySec,
mNattKeepaliveDelaySec,
mIsIkeFragmentationSupported);
}
// TODO: add methods for supporting IKE fragmentation.
}
}