/* * 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 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 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 @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 @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 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 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 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();
}
}