Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94e1c08a authored by Benedict Wong's avatar Benedict Wong Committed by Automerger Merge Worker
Browse files

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

Merge "Add filtering for IPsec algorithms in IKEv2 VPNs" am: 7e2fe6ee am: 331c9243 am: bc035214 am: c2542125 am: bd109853

Change-Id: I60426f87a51f46bbcddf6da911a8df0db03fc0f8
parents c34974e2 bd109853
Loading
Loading
Loading
Loading
+74 −4
Original line number Diff line number Diff line
@@ -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;
+3 −1
Original line number Diff line number Diff line
@@ -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.
+52 −30
Original line number Diff line number Diff line
@@ -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,26 +188,46 @@ 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
        if (Ikev2VpnProfile.hasNormalModeAlgorithms(allowedAlgorithms)) {
            final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();

        // Encryption Algorithms: Currently only AES_CBC is supported.
            // 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
            // 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);
        normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
            }

            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
        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);
@@ -217,8 +238,9 @@ public class VpnIkev2Utils {
            aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
            aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);

        proposals.add(normalModeBuilder.build());
            proposals.add(aeadBuilder.build());
        }

        return proposals;
    }

+76 −0
Original line number Diff line number Diff line
@@ -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();