diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index 81d03f07d0be8..afa63039b6f70 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -70,6 +70,15 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; private static final String EMPTY_CERT = ""; + /** @hide */ + public static final List DEFAULT_ALGORITHMS = + Collections.unmodifiableList(Arrays.asList( + IpSecAlgorithm.CRYPT_AES_CBC, + IpSecAlgorithm.AUTH_HMAC_SHA256, + IpSecAlgorithm.AUTH_HMAC_SHA384, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.AUTH_CRYPT_AES_GCM)); + @NonNull private final String mServerAddr; @NonNull private final String mUserIdentity; @@ -172,7 +181,56 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { throw new IllegalArgumentException("Invalid auth method set"); } - VpnProfile.validateAllowedAlgorithms(mAllowedAlgorithms); + validateAllowedAlgorithms(mAllowedAlgorithms); + } + + /** + * Validates that the allowed algorithms are a valid set for IPsec purposes + * + *

In order for the algorithm list to be a valid set, it must contain at least one algorithm + * that provides Authentication, and one that provides Encryption. Authenticated Encryption with + * Associated Data (AEAD) algorithms are counted as providing Authentication and Encryption. + * + * @param allowedAlgorithms The list to be validated + */ + private static void validateAllowedAlgorithms(@NonNull List algorithmNames) { + VpnProfile.validateAllowedAlgorithms(algorithmNames); + + // First, make sure no insecure algorithms were proposed. + if (algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_MD5) + || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA1)) { + throw new IllegalArgumentException("Algorithm not supported for IKEv2 VPN profiles"); + } + + // Validate that some valid combination (AEAD or AUTH + CRYPT) is present + if (hasAeadAlgorithms(algorithmNames) || hasNormalModeAlgorithms(algorithmNames)) { + return; + } + + throw new IllegalArgumentException("Algorithm set missing support for Auth, Crypt or both"); + } + + /** + * Checks if the provided list has AEAD algorithms + * + * @hide + */ + public static boolean hasAeadAlgorithms(@NonNull List algorithmNames) { + return algorithmNames.contains(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); + } + + /** + * Checks the provided list has acceptable (non-AEAD) authentication and encryption algorithms + * + * @hide + */ + public static boolean hasNormalModeAlgorithms(@NonNull List algorithmNames) { + final boolean hasCrypt = algorithmNames.contains(IpSecAlgorithm.CRYPT_AES_CBC); + final boolean hasAuth = algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA256) + || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA384) + || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA512); + + return hasCrypt && hasAuth; } /** Retrieves the server address string. */ @@ -559,7 +617,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @Nullable private X509Certificate mUserCert; @Nullable private ProxyInfo mProxyInfo; - @NonNull private List mAllowedAlgorithms = new ArrayList<>(); + @NonNull private List mAllowedAlgorithms = DEFAULT_ALGORITHMS; private boolean mIsBypassable = false; private boolean mIsMetered = true; private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; @@ -756,7 +814,19 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** * Sets the allowable set of IPsec algorithms * - *

A list of allowed IPsec algorithms as defined in {@link IpSecAlgorithm} + *

If set, this will constrain the set of algorithms that the IPsec tunnel will use for + * integrity verification and encryption to the provided list. + * + *

The set of allowed IPsec algorithms is defined in {@link IpSecAlgorithm}. Adding of + * algorithms that are considered insecure (such as AUTH_HMAC_MD5 and AUTH_HMAC_SHA1) is not + * permitted, and will result in an IllegalArgumentException being thrown. + * + *

The provided algorithm list must contain at least one algorithm that provides + * Authentication, and one that provides Encryption. Authenticated Encryption with + * Associated Data (AEAD) algorithms provide both Authentication and Encryption. + * + *

By default, this profile will use any algorithm defined in {@link IpSecAlgorithm}, + * with the exception of those considered insecure (as described above). * * @param algorithmNames the list of supported IPsec algorithms * @return this {@link Builder} object to facilitate chaining of method calls @@ -765,7 +835,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @NonNull public Builder setAllowedAlgorithms(@NonNull List algorithmNames) { checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames"); - VpnProfile.validateAllowedAlgorithms(algorithmNames); + validateAllowedAlgorithms(algorithmNames); mAllowedAlgorithms = algorithmNames; return this; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 5ecaf6a148aaa..093e906da65e4 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1955,6 +1955,7 @@ public class Vpn { profile.ipsecCaCert = caCert; // Start VPN profile + profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS); startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); return; case VpnProfile.TYPE_IKEV2_IPSEC_PSK: @@ -1963,6 +1964,7 @@ public class Vpn { Ikev2VpnProfile.encodeForIpsecSecret(profile.ipsecSecret.getBytes()); // Start VPN profile + profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS); startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); return; case VpnProfile.TYPE_L2TP_IPSEC_PSK: @@ -2359,7 +2361,7 @@ public class Vpn { final IkeSessionParams ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams(mContext, mProfile, network); final ChildSessionParams childSessionParams = - VpnIkev2Utils.buildChildSessionParams(); + VpnIkev2Utils.buildChildSessionParams(mProfile.getAllowedAlgorithms()); // TODO: Remove the need for adding two unused addresses with // IPsec tunnels. diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java index 3da304c07910e..228966cbee5b3 100644 --- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java +++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java @@ -24,7 +24,6 @@ import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16; import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8; import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96; -import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96; import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128; import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192; import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256; @@ -39,6 +38,7 @@ import android.content.Context; import android.net.Ikev2VpnProfile; import android.net.InetAddresses; import android.net.IpPrefix; +import android.net.IpSecAlgorithm; import android.net.IpSecTransform; import android.net.Network; import android.net.RouteInfo; @@ -83,6 +83,8 @@ import java.util.List; * @hide */ public class VpnIkev2Utils { + private static final String TAG = VpnIkev2Utils.class.getSimpleName(); + static IkeSessionParams buildIkeSessionParams( @NonNull Context context, @NonNull Ikev2VpnProfile profile, @NonNull Network network) { final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity()); @@ -103,11 +105,11 @@ public class VpnIkev2Utils { return ikeOptionsBuilder.build(); } - static ChildSessionParams buildChildSessionParams() { + static ChildSessionParams buildChildSessionParams(List allowedAlgorithms) { final TunnelModeChildSessionParams.Builder childOptionsBuilder = new TunnelModeChildSessionParams.Builder(); - for (final ChildSaProposal childProposal : getChildSaProposals()) { + for (final ChildSaProposal childProposal : getChildSaProposals(allowedAlgorithms)) { childOptionsBuilder.addSaProposal(childProposal); } @@ -144,7 +146,7 @@ public class VpnIkev2Utils { } private static List getIkeSaProposals() { - // TODO: filter this based on allowedAlgorithms + // TODO: Add ability to filter this when IKEv2 API is made Public API final List proposals = new ArrayList<>(); // Encryption Algorithms: Currently only AES_CBC is supported. @@ -160,7 +162,6 @@ public class VpnIkev2Utils { normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192); normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128); normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96); - normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96); // Add AEAD options final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder(); @@ -187,38 +188,59 @@ public class VpnIkev2Utils { return proposals; } - private static List getChildSaProposals() { - // TODO: filter this based on allowedAlgorithms + /** Builds a child SA proposal based on the allowed IPsec algorithms */ + private static List getChildSaProposals(List allowedAlgorithms) { final List proposals = new ArrayList<>(); // Add non-AEAD options - final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder(); + if (Ikev2VpnProfile.hasNormalModeAlgorithms(allowedAlgorithms)) { + final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder(); - // Encryption Algorithms: Currently only AES_CBC is supported. - normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256); - normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192); - normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128); + // Encryption Algorithms: + // AES-CBC is currently the only supported encryption algorithm. + normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256); + normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192); + normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128); - // Authentication/Integrity Algorithms - normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256); - normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192); - normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128); - normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96); + // Authentication/Integrity Algorithms: + // Guaranteed by Ikev2VpnProfile constructor to contain at least one of these. + if (allowedAlgorithms.contains(IpSecAlgorithm.AUTH_HMAC_SHA512)) { + normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256); + } + if (allowedAlgorithms.contains(IpSecAlgorithm.AUTH_HMAC_SHA384)) { + normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192); + } + if (allowedAlgorithms.contains(IpSecAlgorithm.AUTH_HMAC_SHA256)) { + normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128); + } + + ChildSaProposal proposal = normalModeBuilder.build(); + if (proposal.getIntegrityAlgorithms().isEmpty()) { + // Should be impossible; Verified in Ikev2VpnProfile. + Log.wtf(TAG, "Missing integrity algorithm when buildling Child SA proposal"); + } else { + proposals.add(normalModeBuilder.build()); + } + } // Add AEAD options - final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder(); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128); - aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128); + if (Ikev2VpnProfile.hasAeadAlgorithms(allowedAlgorithms)) { + final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder(); + + // AES-GCM is currently the only supported AEAD algorithm + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128); + aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128); + + proposals.add(aeadBuilder.build()); + } - proposals.add(normalModeBuilder.build()); - proposals.add(aeadBuilder.build()); return proposals; } diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java index 2273bc61225c2..ada5494efd60f 100644 --- a/tests/net/java/android/net/Ikev2VpnProfileTest.java +++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java @@ -40,7 +40,10 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.security.auth.x500.X500Principal; @@ -106,6 +109,7 @@ public class Ikev2VpnProfileTest { assertTrue(profile.isBypassable()); assertTrue(profile.isMetered()); assertEquals(TEST_MTU, profile.getMaxMtu()); + assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms()); } @Test @@ -159,6 +163,78 @@ public class Ikev2VpnProfileTest { assertNull(profile.getUserCert()); } + @Test + public void testBuildWithAllowedAlgorithmsAead() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List allowedAlgorithms = Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildWithAllowedAlgorithmsNormal() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List allowedAlgorithms = + Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA512, IpSecAlgorithm.CRYPT_AES_CBC); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testSetAllowedAlgorithmsEmptyList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setAllowedAlgorithms(new ArrayList<>()); + fail("Expected exception due to no valid algorithm set"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInvalidList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256)); + fail("Expected exception due to missing encryption"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC)); + fail("Expected exception due to missing authentication"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + } + @Test public void testBuildNoAuthMethodSet() throws Exception { final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();