diff options
author | Yan Yan <evitayan@google.com> | 2021-03-10 02:31:36 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-03-10 02:31:36 +0000 |
commit | a8d51aac6643cabbcf725ebc59dfc2e021c7d16c (patch) | |
tree | 5269bff8445aa5c02d41f3bdc166f96f8487d514 | |
parent | dd010bff7925f6c9950b1b4af8b6a4bf774f580a (diff) | |
parent | f8bce7ede4fdd5948528be781db3003c033e4c7e (diff) |
Merge changes from topic "ike-params"
* changes:
Support converting VcnControlPlaneIkeConfig to/from PersistableBundle
Support converting IKE Options to/from PersistableBundle
Support converting IkeConfigRequest to/from PersistableBundle
Support converting IkeAuthEapConfig to/from PersistableBundle
Support converting IkeAuthDigitalSignConfig to/from PersistableBundle
Support converting IkeAuthConfig to/from PersistableBundle
Support converting IkeSessionParams to/from PersistableBundle
8 files changed, 821 insertions, 31 deletions
diff --git a/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java index de086f63b14d..22d7faf2fe18 100644 --- a/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java +++ b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java @@ -19,11 +19,13 @@ package android.net.vcn; import static android.net.vcn.VcnControlPlaneConfig.CONFIG_TYPE_IKE; import android.annotation.NonNull; -import android.annotation.Nullable; import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.net.vcn.persistablebundleutils.IkeSessionParamsUtils; +import android.net.vcn.persistablebundleutils.TunnelModeChildSessionParamsUtils; import android.os.PersistableBundle; import android.util.ArraySet; +import android.util.Log; import java.util.Objects; @@ -38,14 +40,11 @@ import java.util.Objects; public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig { private static final String TAG = VcnControlPlaneIkeConfig.class.getSimpleName(); - // STOPSHIP: b/163604823 Make mIkeParams and mChildParams @NonNull when it is supported to - // construct mIkeParams and mChildParams from PersistableBundles. - private static final String IKE_PARAMS_KEY = "mIkeParams"; - @Nullable private final IkeSessionParams mIkeParams; + @NonNull private final IkeSessionParams mIkeParams; private static final String CHILD_PARAMS_KEY = "mChildParams"; - @Nullable private final TunnelModeChildSessionParams mChildParams; + @NonNull private final TunnelModeChildSessionParams mChildParams; private static final ArraySet<String> BUNDLE_KEY_SET = new ArraySet<>(); @@ -80,11 +79,19 @@ public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig { final PersistableBundle ikeParamsBundle = in.getPersistableBundle(IKE_PARAMS_KEY); final PersistableBundle childParamsBundle = in.getPersistableBundle(CHILD_PARAMS_KEY); - // STOPSHIP: b/163604823 Support constructing mIkeParams and mChildParams from - // PersistableBundles. + Objects.requireNonNull(ikeParamsBundle, "IKE Session Params was null"); + Objects.requireNonNull(childParamsBundle, "Child Session Params was null"); + + mIkeParams = IkeSessionParamsUtils.fromPersistableBundle(ikeParamsBundle); + mChildParams = TunnelModeChildSessionParamsUtils.fromPersistableBundle(childParamsBundle); + + for (String key : in.keySet()) { + if (!BUNDLE_KEY_SET.contains(key)) { + Log.w(TAG, "Found an unexpected key in the PersistableBundle: " + key); + } + } - mIkeParams = null; - mChildParams = null; + validate(); } private void validate() { @@ -101,9 +108,11 @@ public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig { @NonNull public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); - - // STOPSHIP: b/163604823 Support converting mIkeParams and mChildParams to - // PersistableBundles. + result.putPersistableBundle( + IKE_PARAMS_KEY, IkeSessionParamsUtils.toPersistableBundle(mIkeParams)); + result.putPersistableBundle( + CHILD_PARAMS_KEY, + TunnelModeChildSessionParamsUtils.toPersistableBundle(mChildParams)); return result; } @@ -134,10 +143,9 @@ public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig { VcnControlPlaneIkeConfig other = (VcnControlPlaneIkeConfig) o; - // STOPSHIP: b/163604823 Also check mIkeParams and mChildParams when it is supported to - // construct mIkeParams and mChildParams from PersistableBundles. They are not checked - // now so that VcnGatewayConnectionConfigTest and VcnConfigTest can pass. - return super.equals(o); + return super.equals(o) + && Objects.equals(mIkeParams, other.mIkeParams) + && Objects.equals(mChildParams, other.mChildParams); } /** @hide */ diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java index b6036b4a6fd1..35b318687773 100644 --- a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/CertUtils.java @@ -18,18 +18,24 @@ package android.net.vcn.persistablebundleutils; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Objects; /** - * CertUtils provides utility methods for constructing Certificate. + * CertUtils provides utility methods for constructing Certificate and PrivateKey. * * @hide */ public class CertUtils { private static final String CERT_TYPE_X509 = "X.509"; + private static final String PRIVATE_KEY_TYPE_RSA = "RSA"; /** Decodes an ASN.1 DER encoded Certificate */ public static X509Certificate certificateFromByteArray(byte[] derEncoded) { @@ -43,4 +49,18 @@ public class CertUtils { throw new IllegalArgumentException("Fail to decode certificate", e); } } + + /** Decodes a PKCS#8 encoded RSA private key */ + public static RSAPrivateKey privateKeyFromByteArray(byte[] pkcs8Encoded) { + Objects.requireNonNull(pkcs8Encoded, "pkcs8Encoded was null"); + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8Encoded); + + try { + KeyFactory keyFactory = KeyFactory.getInstance(PRIVATE_KEY_TYPE_RSA); + + return (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalArgumentException("Fail to decode PrivateKey", e); + } + } } diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java new file mode 100644 index 000000000000..9d3462cb0b2e --- /dev/null +++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2021 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.vcn.persistablebundleutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; +import android.net.eap.EapSessionConfig; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv4PcscfServer; +import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv6PcscfServer; +import android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig; +import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig; +import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig; +import android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig; +import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig; +import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.net.InetAddress; +import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Abstract utility class to convert IkeSessionParams to/from PersistableBundle. + * + * @hide + */ +@VisibleForTesting(visibility = Visibility.PRIVATE) +public final class IkeSessionParamsUtils { + private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY"; + private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY"; + private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY"; + private static final String REMOTE_ID_KEY = "REMOTE_ID_KEY"; + private static final String LOCAL_AUTH_KEY = "LOCAL_AUTH_KEY"; + private static final String REMOTE_AUTH_KEY = "REMOTE_AUTH_KEY"; + private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY"; + private static final String RETRANS_TIMEOUTS_KEY = "RETRANS_TIMEOUTS_KEY"; + private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY"; + private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY"; + private static final String DPD_DELAY_SEC_KEY = "DPD_DELAY_SEC_KEY"; + private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY"; + private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY"; + + private static final Set<Integer> IKE_OPTIONS = new ArraySet<>(); + + static { + IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID); + IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH); + IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE); + } + + /** Serializes an IkeSessionParams to a PersistableBundle. */ + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull IkeSessionParams params) { + if (params.getConfiguredNetwork() != null || params.getIke3gppExtension() != 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, params.getServerHostname()); + + final PersistableBundle saProposalBundle = + PersistableBundleUtils.fromList( + params.getSaProposals(), IkeSaProposalUtils::toPersistableBundle); + result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle); + + result.putPersistableBundle( + LOCAL_ID_KEY, + IkeIdentificationUtils.toPersistableBundle(params.getLocalIdentification())); + result.putPersistableBundle( + REMOTE_ID_KEY, + IkeIdentificationUtils.toPersistableBundle(params.getRemoteIdentification())); + + result.putPersistableBundle( + LOCAL_AUTH_KEY, AuthConfigUtils.toPersistableBundle(params.getLocalAuthConfig())); + result.putPersistableBundle( + REMOTE_AUTH_KEY, AuthConfigUtils.toPersistableBundle(params.getRemoteAuthConfig())); + + final List<ConfigRequest> reqList = new ArrayList<>(); + for (IkeConfigRequest req : params.getConfigurationRequests()) { + reqList.add(new ConfigRequest(req)); + } + final PersistableBundle configReqListBundle = + PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle); + result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle); + + result.putIntArray(RETRANS_TIMEOUTS_KEY, params.getRetransmissionTimeoutsMillis()); + result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds()); + result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds()); + result.putInt(DPD_DELAY_SEC_KEY, params.getDpdDelaySeconds()); + result.putInt(NATT_KEEPALIVE_DELAY_SEC_KEY, params.getNattKeepAliveDelaySeconds()); + + final List<Integer> enabledIkeOptions = new ArrayList<>(); + for (int option : IKE_OPTIONS) { + if (params.hasIkeOption(option)) { + enabledIkeOptions.add(option); + } + } + + final int[] optionArray = enabledIkeOptions.stream().mapToInt(i -> i).toArray(); + result.putIntArray(IKE_OPTIONS_KEY, optionArray); + + return result; + } + + /** Constructs an IkeSessionParams by deserializing a PersistableBundle. */ + @NonNull + public static IkeSessionParams fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle is null"); + + final 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 was null"); + List<IkeSaProposal> saProposals = + PersistableBundleUtils.toList( + proposalBundle, IkeSaProposalUtils::fromPersistableBundle); + for (IkeSaProposal proposal : saProposals) { + builder.addSaProposal(proposal); + } + + builder.setLocalIdentification( + IkeIdentificationUtils.fromPersistableBundle( + in.getPersistableBundle(LOCAL_ID_KEY))); + builder.setRemoteIdentification( + IkeIdentificationUtils.fromPersistableBundle( + in.getPersistableBundle(REMOTE_ID_KEY))); + + AuthConfigUtils.setBuilderByReadingPersistableBundle( + in.getPersistableBundle(LOCAL_AUTH_KEY), + in.getPersistableBundle(REMOTE_AUTH_KEY), + builder); + + builder.setRetransmissionTimeoutsMillis(in.getIntArray(RETRANS_TIMEOUTS_KEY)); + 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)); + + final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY); + Objects.requireNonNull(configReqListBundle, "Config request list was null"); + final List<ConfigRequest> reqList = + PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new); + for (ConfigRequest req : reqList) { + switch (req.type) { + case ConfigRequest.IPV4_P_CSCF_ADDRESS: + if (req.address == null) { + builder.addPcscfServerRequest(AF_INET); + } else { + builder.addPcscfServerRequest(req.address); + } + break; + case ConfigRequest.IPV6_P_CSCF_ADDRESS: + if (req.address == null) { + builder.addPcscfServerRequest(AF_INET6); + } else { + builder.addPcscfServerRequest(req.address); + } + break; + default: + throw new IllegalArgumentException( + "Unrecognized config request type: " + req.type); + } + } + + // Clear IKE Options that are by default enabled + for (int option : IKE_OPTIONS) { + builder.removeIkeOption(option); + } + + final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY); + for (int option : optionArray) { + builder.addIkeOption(option); + } + + return builder.build(); + } + + private static final class AuthConfigUtils { + private static final int IKE_AUTH_METHOD_PSK = 1; + private static final int IKE_AUTH_METHOD_PUB_KEY_SIGNATURE = 2; + private static final int IKE_AUTH_METHOD_EAP = 3; + + private static final String AUTH_METHOD_KEY = "AUTH_METHOD_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle(@NonNull IkeAuthConfig authConfig) { + if (authConfig instanceof IkeAuthPskConfig) { + IkeAuthPskConfig config = (IkeAuthPskConfig) authConfig; + return IkeAuthPskConfigUtils.toPersistableBundle( + config, createPersistableBundle(IKE_AUTH_METHOD_PSK)); + } else if (authConfig instanceof IkeAuthDigitalSignLocalConfig) { + IkeAuthDigitalSignLocalConfig config = (IkeAuthDigitalSignLocalConfig) authConfig; + return IkeAuthDigitalSignConfigUtils.toPersistableBundle( + config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE)); + } else if (authConfig instanceof IkeAuthDigitalSignRemoteConfig) { + IkeAuthDigitalSignRemoteConfig config = (IkeAuthDigitalSignRemoteConfig) authConfig; + return IkeAuthDigitalSignConfigUtils.toPersistableBundle( + config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE)); + } else if (authConfig instanceof IkeAuthEapConfig) { + IkeAuthEapConfig config = (IkeAuthEapConfig) authConfig; + return IkeAuthEapConfigUtils.toPersistableBundle( + config, createPersistableBundle(IKE_AUTH_METHOD_EAP)); + } else { + throw new IllegalStateException("Invalid IkeAuthConfig subclass"); + } + } + + private static PersistableBundle createPersistableBundle(int type) { + final PersistableBundle result = new PersistableBundle(); + result.putInt(AUTH_METHOD_KEY, type); + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle localAuthBundle, + @NonNull PersistableBundle remoteAuthBundle, + @NonNull IkeSessionParams.Builder builder) { + Objects.requireNonNull(localAuthBundle, "localAuthBundle was null"); + Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null"); + + final int localMethodType = localAuthBundle.getInt(AUTH_METHOD_KEY); + final int remoteMethodType = remoteAuthBundle.getInt(AUTH_METHOD_KEY); + switch (localMethodType) { + case IKE_AUTH_METHOD_PSK: + if (remoteMethodType != IKE_AUTH_METHOD_PSK) { + throw new IllegalArgumentException( + "Expect remote auth method to be PSK based, but was " + + remoteMethodType); + } + IkeAuthPskConfigUtils.setBuilderByReadingPersistableBundle( + localAuthBundle, remoteAuthBundle, builder); + return; + case IKE_AUTH_METHOD_PUB_KEY_SIGNATURE: + if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) { + throw new IllegalArgumentException( + "Expect remote auth method to be digital signature based, but was " + + remoteMethodType); + } + IkeAuthDigitalSignConfigUtils.setBuilderByReadingPersistableBundle( + localAuthBundle, remoteAuthBundle, builder); + return; + case IKE_AUTH_METHOD_EAP: + if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) { + throw new IllegalArgumentException( + "When using EAP for local authentication, expect remote auth" + + " method to be digital signature based, but was " + + remoteMethodType); + } + IkeAuthEapConfigUtils.setBuilderByReadingPersistableBundle( + localAuthBundle, remoteAuthBundle, builder); + return; + default: + throw new IllegalArgumentException( + "Invalid EAP method type " + localMethodType); + } + } + } + + private static final class IkeAuthPskConfigUtils { + private static final String PSK_KEY = "PSK_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle( + @NonNull IkeAuthPskConfig config, @NonNull PersistableBundle result) { + result.putPersistableBundle( + PSK_KEY, PersistableBundleUtils.fromByteArray(config.getPsk())); + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle localAuthBundle, + @NonNull PersistableBundle remoteAuthBundle, + @NonNull IkeSessionParams.Builder builder) { + Objects.requireNonNull(localAuthBundle, "localAuthBundle was null"); + Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null"); + + final PersistableBundle localPskBundle = localAuthBundle.getPersistableBundle(PSK_KEY); + final PersistableBundle remotePskBundle = + remoteAuthBundle.getPersistableBundle(PSK_KEY); + Objects.requireNonNull(localAuthBundle, "Local PSK was null"); + Objects.requireNonNull(remoteAuthBundle, "Remote PSK was null"); + + final byte[] localPsk = PersistableBundleUtils.toByteArray(localPskBundle); + final byte[] remotePsk = PersistableBundleUtils.toByteArray(remotePskBundle); + if (!Arrays.equals(localPsk, remotePsk)) { + throw new IllegalArgumentException("Local PSK and remote PSK are different"); + } + builder.setAuthPsk(localPsk); + } + } + + private static class IkeAuthDigitalSignConfigUtils { + private static final String END_CERT_KEY = "END_CERT_KEY"; + private static final String INTERMEDIATE_CERTS_KEY = "INTERMEDIATE_CERTS_KEY"; + private static final String PRIVATE_KEY_KEY = "PRIVATE_KEY_KEY"; + private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle( + @NonNull IkeAuthDigitalSignLocalConfig config, @NonNull PersistableBundle result) { + try { + result.putPersistableBundle( + END_CERT_KEY, + PersistableBundleUtils.fromByteArray( + config.getClientEndCertificate().getEncoded())); + + final List<X509Certificate> certList = config.getIntermediateCertificates(); + final List<byte[]> encodedCertList = new ArrayList<>(certList.size()); + for (X509Certificate cert : certList) { + encodedCertList.add(cert.getEncoded()); + } + + final 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(config.getPrivateKey().getEncoded())); + return result; + } + + @NonNull + public static PersistableBundle toPersistableBundle( + @NonNull IkeAuthDigitalSignRemoteConfig config, @NonNull PersistableBundle result) { + try { + X509Certificate caCert = config.getRemoteCaCert(); + if (caCert != null) { + result.putPersistableBundle( + TRUST_CERT_KEY, + PersistableBundleUtils.fromByteArray(caCert.getEncoded())); + } + } catch (CertificateEncodingException e) { + throw new IllegalArgumentException("Fail to encode the certificate"); + } + + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle localAuthBundle, + @NonNull PersistableBundle remoteAuthBundle, + @NonNull IkeSessionParams.Builder builder) { + Objects.requireNonNull(localAuthBundle, "localAuthBundle was null"); + Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null"); + + // Deserialize localAuth + final PersistableBundle endCertBundle = + localAuthBundle.getPersistableBundle(END_CERT_KEY); + Objects.requireNonNull(endCertBundle, "End cert was null"); + final byte[] encodedCert = PersistableBundleUtils.toByteArray(endCertBundle); + final X509Certificate endCert = CertUtils.certificateFromByteArray(encodedCert); + + final PersistableBundle certsBundle = + localAuthBundle.getPersistableBundle(INTERMEDIATE_CERTS_KEY); + Objects.requireNonNull(certsBundle, "Intermediate certs was null"); + final List<byte[]> encodedCertList = + PersistableBundleUtils.toList(certsBundle, PersistableBundleUtils::toByteArray); + final List<X509Certificate> certList = new ArrayList<>(encodedCertList.size()); + for (byte[] encoded : encodedCertList) { + certList.add(CertUtils.certificateFromByteArray(encoded)); + } + + final PersistableBundle privateKeyBundle = + localAuthBundle.getPersistableBundle(PRIVATE_KEY_KEY); + Objects.requireNonNull(privateKeyBundle, "PrivateKey bundle was null"); + final PrivateKey privateKey = + CertUtils.privateKeyFromByteArray( + PersistableBundleUtils.toByteArray(privateKeyBundle)); + + // Deserialize remoteAuth + final PersistableBundle trustCertBundle = + remoteAuthBundle.getPersistableBundle(TRUST_CERT_KEY); + + X509Certificate caCert = null; + if (trustCertBundle != null) { + final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle); + caCert = CertUtils.certificateFromByteArray(encodedCaCert); + } + + builder.setAuthDigitalSignature(caCert, endCert, certList, privateKey); + } + } + + private static final class IkeAuthEapConfigUtils { + private static final String EAP_CONFIG_KEY = "EAP_CONFIG_KEY"; + + @NonNull + public static PersistableBundle toPersistableBundle( + @NonNull IkeAuthEapConfig config, @NonNull PersistableBundle result) { + result.putPersistableBundle( + EAP_CONFIG_KEY, + EapSessionConfigUtils.toPersistableBundle(config.getEapConfig())); + return result; + } + + public static void setBuilderByReadingPersistableBundle( + @NonNull PersistableBundle localAuthBundle, + @NonNull PersistableBundle remoteAuthBundle, + @NonNull IkeSessionParams.Builder builder) { + // Deserialize localAuth + final PersistableBundle eapBundle = + localAuthBundle.getPersistableBundle(EAP_CONFIG_KEY); + Objects.requireNonNull(eapBundle, "EAP Config was null"); + final EapSessionConfig eapConfig = + EapSessionConfigUtils.fromPersistableBundle(eapBundle); + + // Deserialize remoteAuth + final PersistableBundle trustCertBundle = + remoteAuthBundle.getPersistableBundle( + IkeAuthDigitalSignConfigUtils.TRUST_CERT_KEY); + + X509Certificate serverCaCert = null; + if (trustCertBundle != null) { + final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle); + serverCaCert = CertUtils.certificateFromByteArray(encodedCaCert); + } + builder.setAuthEap(serverCaCert, eapConfig); + } + } + + private static final class ConfigRequest { + private static final int IPV4_P_CSCF_ADDRESS = 1; + private static final int IPV6_P_CSCF_ADDRESS = 2; + + private static final String TYPE_KEY = "type"; + private static final String ADDRESS_KEY = "address"; + + public final int type; + + // Null when it is an empty request + @Nullable public final InetAddress address; + + ConfigRequest(IkeConfigRequest config) { + if (config instanceof ConfigRequestIpv4PcscfServer) { + type = IPV4_P_CSCF_ADDRESS; + address = ((ConfigRequestIpv4PcscfServer) config).getAddress(); + } else if (config instanceof ConfigRequestIpv6PcscfServer) { + type = IPV6_P_CSCF_ADDRESS; + address = ((ConfigRequestIpv6PcscfServer) config).getAddress(); + } else { + throw new IllegalStateException("Unknown TunnelModeChildConfigRequest"); + } + } + + ConfigRequest(PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + type = in.getInt(TYPE_KEY); + + String addressStr = in.getString(ADDRESS_KEY); + if (addressStr == null) { + address = null; + } else { + address = InetAddresses.parseNumericAddress(addressStr); + } + } + + @NonNull + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(TYPE_KEY, type); + if (address != null) { + result.putString(ADDRESS_KEY, address.getHostAddress()); + } + + return result; + } + } +} diff --git a/tests/vcn/assets/client-end-cert.pem b/tests/vcn/assets/client-end-cert.pem new file mode 100644 index 000000000000..e82da85c50ab --- /dev/null +++ b/tests/vcn/assets/client-end-cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDaDCCAlCgAwIBAgIIcorRI3n29E4wDQYJKoZIhvcNAQELBQAwQTELMAkGA1UE +BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF3R3by5jYS50ZXN0LmFu +ZHJvaWQubmV0MB4XDTIwMDQxNDA1MDM0OVoXDTIzMDQxNDA1MDM0OVowRTELMAkG +A1UEBhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxJDAiBgNVBAMTG2NsaWVudC50ZXN0 +LmlrZS5hbmRyb2lkLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AK/cK+sIaiQlJYvy5+Dq70sJbgR7PO1uS2qkLRP7Wb3z5SNvz94nQvZRrFn1AFIE +CpfESh5kUF6gJe7t7NR3mpQ98iEosCRBMDJT8qB+EeHiL4wkrmCE9sYMTyvaApRc +6Qzozn/9kKma7Qpj/25AvoPluTERqhZ6AQ77BJeb6FNOAoO1Aoe9GJuB1xmRxjRw +D0mwusL+ciQ/7uKlsFP5VO5XqACcohXSerzO8jcD9necBvka3SDepqqzn1K0NPRC +25fMmS5kSjddKtKOif7w2NI3OpVsmP3kHv66If73VURsy0lgXPYyKkq8lAMrtmXG +R7svFGPbEl+Swkpr3b+dzF8CAwEAAaNgMF4wHwYDVR0jBBgwFoAUcqSu1uRYT/DL +bLoDNUz38nGvCKQwJgYDVR0RBB8wHYIbY2xpZW50LnRlc3QuaWtlLmFuZHJvaWQu +bmV0MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQCa53tK +I9RM9/MutZ5KNG2Gfs2cqaPyv8ZRhs90HDWZhkFVu7prywJAxOd2hxxHPsvgurio +4bKAxnT4EXevgz5YoCbj2TPIL9TdFYh59zZ97XXMxk+SRdypgF70M6ETqKPs3hDP +ZRMMoHvvYaqaPvp4StSBX9A44gSyjHxVYJkrjDZ0uffKg5lFL5IPvqfdmSRSpGab +SyGTP4OLTy0QiNV3pBsJGdl0h5BzuTPR9OTl4xgeqqBQy2bDjmfJBuiYyCSCkPi7 +T3ohDYCymhuSkuktHPNG1aKllUJaw0tuZuNydlgdAveXPYfM36uvK0sfd9qr9pAy +rmkYV2MAWguFeckh +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/tests/vcn/assets/client-private-key.key b/tests/vcn/assets/client-private-key.key new file mode 100644 index 000000000000..22736e98e030 --- /dev/null +++ b/tests/vcn/assets/client-private-key.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCv3CvrCGokJSWL +8ufg6u9LCW4EezztbktqpC0T+1m98+Ujb8/eJ0L2UaxZ9QBSBAqXxEoeZFBeoCXu +7ezUd5qUPfIhKLAkQTAyU/KgfhHh4i+MJK5ghPbGDE8r2gKUXOkM6M5//ZCpmu0K +Y/9uQL6D5bkxEaoWegEO+wSXm+hTTgKDtQKHvRibgdcZkcY0cA9JsLrC/nIkP+7i +pbBT+VTuV6gAnKIV0nq8zvI3A/Z3nAb5Gt0g3qaqs59StDT0QtuXzJkuZEo3XSrS +jon+8NjSNzqVbJj95B7+uiH+91VEbMtJYFz2MipKvJQDK7Zlxke7LxRj2xJfksJK +a92/ncxfAgMBAAECggEAQztaMvW5lm35J8LKsWs/5qEJRX9T8LWs8W0oqq36Riub +G2wgvR6ndAIPcSjAYZqX7iOl7m6NZ0+0kN63HxdGqovwKIskpAekBGmhpYftED1n +zh0r6UyMB3UnQ22KdOv8UOokIDxxdNX8728BdUYdT9Ggdkj5jLRB+VcwD0IUlNvo +zzTpURV9HEd87uiLqd4AAHXSI0lIHI5U43z24HI/J6/YbYHT3Rlh6CIa/LuwO6vL +gFkgqg0/oy6yJtjrHtzNVA67F0UaH62hR4YFgbC0d955SJnDidWOv/0j2DMpfdCc +9kFAcPwUSyykvUSLnGIKWSG4D+6gzIeAeUx4oO7kMQKBgQDVNRkX8AGTHyLg+NXf +spUWWcodwVioXl30Q7h6+4bt8OI61UbhQ7wX61wvJ1cySpa2KOYa2UdagQVhGhhL +ADu363R77uXF/jZgzVfmjjyJ2nfDqRgHWRTlSkuq/jCOQCz7VIPHRZg5WL/9D4ms +TAqMjpzqeMfFZI+w4/+xpcJIuQKBgQDTKBy+ZuerWrVT9icWKvLU58o5EVj/2yFy +GJvKm+wRAAX2WzjNnR4HVd4DmMREVz1BPYby0j5gqjvtDsxYYu39+NT7JvMioLLK +QPj+7k5geYgNqVgCxB1vP89RhY2X1RLrN9sTXOodgFPeXOQWNYITkGp3eQpx4nTJ ++K/al3oB1wKBgAjnc8nVIyuyxDEjE0OJYMKTM2a0uXAmqMPXxC+Wq5bqVXhhidlE +i+lv0eTCPtkB1nN7F8kNQ/aaps/cWCFhvBy9P5shagUvzbOTP9WIIS0cq53HRRKh +fMbqqGhWv05hjb9dUzeSR341n6cA7B3++v3Nwu3j52vt/DZF/1q68nc5AoGAS0SU +ImbKE/GsizZGLoe2sZ/CHN+LKwCwhlwxRGKaHmE0vuE7eUeVSaYZEo0lAPtb8WJ+ +NRYueASWgeTxgFwbW5mUScZTirdfo+rPFwhZVdhcYApKPgosN9i2DOgfVcz1BnWN +mPRY25U/0BaqkyQVruWeneG+kGPZn5kPDktKiVcCgYEAkzwU9vCGhm7ZVALvx/zR +wARz2zsL9ImBc0P4DK1ld8g90FEnHrEgeI9JEwz0zFHOCMLwlk7kG0Xev7vfjZ7G +xSqtQYOH33Qp6rtBOgdt8hSyDFvakvDl6bqhAw52gelO3MTpAB1+ZsfZ5gFx13Jf +idNFcaIrC52PtZIH7QCzdDY= +-----END PRIVATE KEY-----
\ No newline at end of file diff --git a/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java index 36f5e41462e8..2333718d0cab 100644 --- a/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java @@ -99,6 +99,13 @@ public class VcnControlPlaneIkeConfigTest { } @Test + public void testPersistableBundle() { + final VcnControlPlaneIkeConfig config = buildTestConfig(); + + assertEquals(config, new VcnControlPlaneIkeConfig(config.toPersistableBundle())); + } + + @Test public void testConstructConfigWithoutIkeParams() { try { new VcnControlPlaneIkeConfig(null, CHILD_PARAMS); diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java new file mode 100644 index 000000000000..546d957d417e --- /dev/null +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2021 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.vcn.persistablebundleutils; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.telephony.TelephonyManager.APPTYPE_USIM; + +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.eap.EapSessionConfig; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeSessionParams; +import android.os.PersistableBundle; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.org.bouncycastle.util.io.pem.PemObject; +import com.android.internal.org.bouncycastle.util.io.pem.PemReader; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IkeSessionParamsUtilsTest { + private static IkeSessionParams.Builder createBuilderMinimum() { + final InetAddress serverAddress = InetAddresses.parseNumericAddress("192.0.2.100"); + + return new IkeSessionParams.Builder() + .setServerHostname(serverAddress.getHostAddress()) + .addSaProposal(SaProposalUtilsTest.buildTestIkeSaProposal()) + .setLocalIdentification(new IkeFqdnIdentification("client.test.android.net")) + .setRemoteIdentification(new IkeFqdnIdentification("server.test.android.net")) + .setAuthPsk("psk".getBytes()); + } + + private static void verifyPersistableBundleEncodeDecodeIsLossless(IkeSessionParams params) { + final PersistableBundle bundle = IkeSessionParamsUtils.toPersistableBundle(params); + final IkeSessionParams result = IkeSessionParamsUtils.fromPersistableBundle(bundle); + + assertEquals(result, params); + } + + @Test + public void testEncodeRecodeParamsWithLifetimes() throws Exception { + final int hardLifetime = (int) TimeUnit.HOURS.toSeconds(20L); + final int softLifetime = (int) TimeUnit.HOURS.toSeconds(10L); + final IkeSessionParams params = + createBuilderMinimum().setLifetimeSeconds(hardLifetime, softLifetime).build(); + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithDpdDelay() throws Exception { + final int dpdDelay = (int) TimeUnit.MINUTES.toSeconds(10L); + final IkeSessionParams params = createBuilderMinimum().setDpdDelaySeconds(dpdDelay).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithNattKeepalive() throws Exception { + final int nattKeepAliveDelay = (int) TimeUnit.MINUTES.toSeconds(5L); + final IkeSessionParams params = + createBuilderMinimum().setNattKeepAliveDelaySeconds(nattKeepAliveDelay).build(); + + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithRetransmissionTimeouts() throws Exception { + final int[] retransmissionTimeout = new int[] {500, 500, 500, 500, 500, 500}; + final IkeSessionParams params = + createBuilderMinimum() + .setRetransmissionTimeoutsMillis(retransmissionTimeout) + .build(); + + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithConfigRequests() throws Exception { + final Inet4Address ipv4Address = + (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.100"); + final Inet6Address ipv6Address = + (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1"); + + final IkeSessionParams params = + createBuilderMinimum() + .addPcscfServerRequest(AF_INET) + .addPcscfServerRequest(AF_INET6) + .addPcscfServerRequest(ipv4Address) + .addPcscfServerRequest(ipv6Address) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithAuthPsk() throws Exception { + final IkeSessionParams params = createBuilderMinimum().setAuthPsk("psk".getBytes()).build(); + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithIkeOptions() throws Exception { + final IkeSessionParams params = + createBuilderMinimum() + .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID) + .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + private static InputStream openAssetsFile(String fileName) throws Exception { + return InstrumentationRegistry.getContext().getResources().getAssets().open(fileName); + } + + private static X509Certificate createCertFromPemFile(String fileName) throws Exception { + final CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) factory.generateCertificate(openAssetsFile(fileName)); + } + + private static RSAPrivateKey createRsaPrivateKeyFromKeyFile(String fileName) throws Exception { + final PemObject pemObject = + new PemReader(new InputStreamReader(openAssetsFile(fileName))).readPemObject(); + return (RSAPrivateKey) CertUtils.privateKeyFromByteArray(pemObject.getContent()); + } + + @Test + public void testEncodeRecodeParamsWithDigitalSignAuth() throws Exception { + final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem"); + final X509Certificate clientEndCert = createCertFromPemFile("client-end-cert.pem"); + final RSAPrivateKey clientPrivateKey = + createRsaPrivateKeyFromKeyFile("client-private-key.key"); + + final IkeSessionParams params = + createBuilderMinimum() + .setAuthDigitalSignature(serverCaCert, clientEndCert, clientPrivateKey) + .build(); + verifyPersistableBundleEncodeDecodeIsLossless(params); + } + + @Test + public void testEncodeRecodeParamsWithEapAuth() throws Exception { + final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem"); + + final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII); + final int subId = 1; + final EapSessionConfig eapConfig = + new EapSessionConfig.Builder() + .setEapIdentity(eapId) + .setEapSimConfig(subId, APPTYPE_USIM) + .setEapAkaConfig(subId, APPTYPE_USIM) + .build(); + + final IkeSessionParams params = + createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build(); + verifyPersistableBundleEncodeDecodeIsLossless(params); + } +} diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java index 8ae8692b4f75..664044a9e7d4 100644 --- a/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/SaProposalUtilsTest.java @@ -32,21 +32,25 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class SaProposalUtilsTest { + /** Package private so that IkeSessionParamsUtilsTest can use it */ + static IkeSaProposal buildTestIkeSaProposal() { + return new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_3DES, SaProposal.KEY_LEN_UNUSED) + .addEncryptionAlgorithm( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .addDhGroup(SaProposal.DH_GROUP_3072_BIT_MODP) + .build(); + } + @Test public void testPersistableBundleEncodeDecodeIsLosslessIkeProposal() throws Exception { - final IkeSaProposal proposal = - new IkeSaProposal.Builder() - .addEncryptionAlgorithm( - SaProposal.ENCRYPTION_ALGORITHM_3DES, SaProposal.KEY_LEN_UNUSED) - .addEncryptionAlgorithm( - SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) - .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) - .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128) - .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC) - .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256) - .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) - .addDhGroup(SaProposal.DH_GROUP_3072_BIT_MODP) - .build(); + final IkeSaProposal proposal = buildTestIkeSaProposal(); final PersistableBundle bundle = IkeSaProposalUtils.toPersistableBundle(proposal); final SaProposal resultProposal = IkeSaProposalUtils.fromPersistableBundle(bundle); |