Merge "Add filtering for IPsec algorithms in IKEv2 VPNs" am: 7e2fe6eeee

Change-Id: Ib3e7bea4ad17eaf0b64bc157ed682c672a17f79a
This commit is contained in:
Benedict Wong
2020-04-28 17:59:07 +00:00
committed by Automerger Merge Worker
4 changed files with 204 additions and 34 deletions

View File

@@ -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<String> 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
*
* <p>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<String> 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<String> 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<String> 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<String> mAllowedAlgorithms = new ArrayList<>();
@NonNull private List<String> 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
*
* <p>A list of allowed IPsec algorithms as defined in {@link IpSecAlgorithm}
* <p>If set, this will constrain the set of algorithms that the IPsec tunnel will use for
* integrity verification and encryption to the provided list.
*
* <p>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.
*
* <p>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.
*
* <p>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<String> algorithmNames) {
checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames");
VpnProfile.validateAllowedAlgorithms(algorithmNames);
validateAllowedAlgorithms(algorithmNames);
mAllowedAlgorithms = algorithmNames;
return this;

View File

@@ -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.

View File

@@ -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<String> 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<IkeSaProposal> getIkeSaProposals() {
// TODO: filter this based on allowedAlgorithms
// TODO: Add ability to filter this when IKEv2 API is made Public API
final List<IkeSaProposal> 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<ChildSaProposal> getChildSaProposals() {
// TODO: filter this based on allowedAlgorithms
/** Builds a child SA proposal based on the allowed IPsec algorithms */
private static List<ChildSaProposal> getChildSaProposals(List<String> allowedAlgorithms) {
final List<ChildSaProposal> 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;
}

View File

@@ -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<String> 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<String> 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<String> 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<String> 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();