/* * 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 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 com.android.internal.annotations.VisibleForTesting; 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 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.TrustAnchor; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; 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}. * *
Note that all negotiated configurations will be reused during rekey including SA Proposal and * lifetime. * * @hide */ @SystemApi 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({IKE_OPTION_ACCEPT_ANY_REMOTE_ID, IKE_OPTION_EAP_ONLY_AUTH}) 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; private static final int MIN_IKE_OPTION = IKE_OPTION_ACCEPT_ANY_REMOTE_ID; private static final int MAX_IKE_OPTION = IKE_OPTION_EAP_ONLY_AUTH; /** @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_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}; @NonNull private final String mServerHostname; @NonNull private final Network mNetwork; @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; private final long mIkeOptions; private final int mHardLifetimeSec; private final int mSoftLifetimeSec; private final int mDpdDelaySec; private final boolean mIsIkeFragmentationSupported; private IkeSessionParams( @NonNull String serverHostname, @NonNull Network network, @NonNull IkeSaProposal[] proposals, @NonNull IkeIdentification localIdentification, @NonNull IkeIdentification remoteIdentification, @NonNull IkeAuthConfig localAuthConfig, @NonNull IkeAuthConfig remoteAuthConfig, @NonNull IkeConfigAttribute[] configRequests, @NonNull int[] retransTimeoutMsList, long ikeOptions, int hardLifetimeSec, int softLifetimeSec, int dpdDelaySec, boolean isIkeFragmentationSupported) { mServerHostname = serverHostname; mNetwork = network; mSaProposals = proposals; mLocalIdentification = localIdentification; mRemoteIdentification = remoteIdentification; mLocalAuthConfig = localAuthConfig; mRemoteAuthConfig = remoteAuthConfig; mConfigRequests = configRequests; mRetransTimeoutMsList = retransTimeoutMsList; mIkeOptions = ikeOptions; mHardLifetimeSec = hardLifetimeSec; mSoftLifetimeSec = softLifetimeSec; mDpdDelaySec = dpdDelaySec; 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; } /** * 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} */
@NonNull
public Network getNetwork() {
return mNetwork;
}
/** Retrieves all ChildSaProposals configured */
@NonNull
public List @see {@link Builder#setRetransmissionTimeoutsMillis(int[])}
*/
public int[] getRetransmissionTimeoutsMillis() {
return mRetransTimeoutMsList;
}
/** Checks if the given IKE Session negotiation option is set */
public boolean hasIkeOption(@IkeOption int ikeOption) {
validateIkeOptionOrThrow(ikeOption);
return (mIkeOptions & getOptionBitValue(ikeOption)) != 0;
}
/** @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 @see {@link IkeSessionParams.Builder#setAuthEap(X509Certificate, EapSessionConfig)}
*/
public static class IkeAuthEapConfig extends IkeAuthConfig {
/** @hide */
@NonNull public final EapSessionConfig mEapConfig;
private IkeAuthEapConfig(EapSessionConfig eapConfig) {
super(IKE_AUTH_METHOD_EAP);
mEapConfig = eapConfig;
}
/** Retrieves EAP configuration */
@NonNull
public EapSessionConfig getEapConfig() {
return mEapConfig;
}
}
/** This class can be used to incrementally construct a {@link IkeSessionParams}. */
public static final class Builder {
@NonNull private final ConnectivityManager mConnectivityManager;
@NonNull private final List 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 setNetwork(@NonNull Network network) {
if (network == null) {
throw new NullPointerException("Required argument not provided");
}
mNetwork = 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.
*/
@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 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.
*/
@NonNull
public Builder setAuthPsk(@NonNull byte[] sharedKey) {
if (sharedKey == null) {
throw new NullPointerException("Required argument not provided");
}
mLocalAuthConfig = new IkeAuthPskConfig(sharedKey);
mRemoteAuthConfig = new IkeAuthPskConfig(sharedKey);
return this;
}
/**
* 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
@NonNull
public Builder setAuthEap(
@Nullable X509Certificate serverCaCert, @NonNull EapSessionConfig eapConfig) {
if (eapConfig == null) {
throw new NullPointerException("Required argument not provided");
}
mLocalAuthConfig = new IkeAuthEapConfig(eapConfig);
mRemoteAuthConfig = new IkeAuthDigitalSignRemoteConfig(serverCaCert);
return this;
}
/**
* 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. Only {@link
* RSAPrivateKey} is supported.
* @return Builder this, to facilitate chaining.
*/
@NonNull
public Builder setAuthDigitalSignature(
@Nullable X509Certificate serverCaCert,
@NonNull X509Certificate clientEndCert,
@NonNull PrivateKey clientPrivateKey) {
return setAuthDigitalSignature(
serverCaCert,
clientEndCert,
new LinkedList 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. Only {@link
* RSAPrivateKey} is supported.
* @return Builder this, to facilitate chaining.
*/
@NonNull
public Builder setAuthDigitalSignature(
@Nullable X509Certificate serverCaCert,
@NonNull X509Certificate clientEndCert,
@NonNull List 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.
*/
@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 retransmission timeout list in milliseconds.
*
* 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 specified IKE Option as enabled.
*
* @param ikeOption the option to be enabled.
* @return Builder this, to facilitate chaining.
*/
@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.
*/
@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");
}
Network network = mNetwork != null ? mNetwork : mConnectivityManager.getActiveNetwork();
if (network == 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,
network,
mSaProposalList.toArray(new IkeSaProposal[0]),
mLocalIdentification,
mRemoteIdentification,
mLocalAuthConfig,
mRemoteAuthConfig,
mConfigRequestList.toArray(new IkeConfigAttribute[0]),
mRetransTimeoutMsList,
mIkeOptions,
mHardLifetimeSec,
mSoftLifetimeSec,
mDpdDelaySec,
mIsIkeFragmentationSupported);
}
// TODO: add methods for supporting IKE fragmentation.
}
}