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

Commit 3e238230 authored by Xia Wang's avatar Xia Wang Committed by Android Git Automerger
Browse files

am 836eff87: Merge "Add legacy VPN test framework and test cases" into klp-dev

* commit '836eff87':
  Add legacy VPN test framework and test cases
parents 88adaa72 836eff87
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := platform

LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle

# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+3 −0
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />

    <application>
        <uses-library android:name="android.test.runner" />
+223 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.settings.vpn2;

import android.os.Environment;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;

import com.android.internal.net.VpnProfile;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DEROctetString;
import com.android.org.bouncycastle.asn1.x509.BasicConstraints;

import junit.framework.Assert;

import libcore.io.Streams;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

/**
 * Certificate installer helper to extract information from a provided file
 * and install certificates to keystore.
 */
public class CertInstallerHelper {
    private static final String TAG = "CertInstallerHelper";
    /* Define a password to unlock keystore after it is reset */
    private static final String CERT_STORE_PASSWORD = "password";
    private final int mUid = KeyStore.UID_SELF;
    private PrivateKey mUserKey;  // private key
    private X509Certificate mUserCert;  // user certificate
    private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
    private KeyStore mKeyStore = KeyStore.getInstance();

    /**
     * Unlock keystore and set password
     */
    public CertInstallerHelper() {
        mKeyStore.reset();
        mKeyStore.password(CERT_STORE_PASSWORD);
    }

    private void extractCertificate(String certFile, String password) {
        InputStream in = null;
        final byte[] raw;
        java.security.KeyStore keystore = null;
        try {
            // Read .p12 file from SDCARD and extract with password
            in = new FileInputStream(new File(
                    Environment.getExternalStorageDirectory(), certFile));
            raw = Streams.readFully(in);

            keystore = java.security.KeyStore.getInstance("PKCS12");
            PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
            keystore.load(new ByteArrayInputStream(raw), passwordProtection.getPassword());

            // Install certificates and private keys
            Enumeration<String> aliases = keystore.aliases();
            if (!aliases.hasMoreElements()) {
                Assert.fail("key store failed to put in keychain");
            }
            ArrayList<String> aliasesList = Collections.list(aliases);
            // The keystore is initialized for each test case, there will
            // be only one alias in the keystore
            Assert.assertEquals(1, aliasesList.size());
            String alias = aliasesList.get(0);
            java.security.KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
            Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());

            if (entry instanceof PrivateKeyEntry) {
                Assert.assertTrue(installFrom((PrivateKeyEntry) entry));
            }
        } catch (IOException e) {
            Assert.fail("Failed to read certficate: " + e);
        } catch (KeyStoreException e) {
            Log.e(TAG, "failed to extract certificate" + e);
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "failed to extract certificate" + e);
        } catch (CertificateException e) {
            Log.e(TAG, "failed to extract certificate" + e);
        } catch (UnrecoverableEntryException e) {
            Log.e(TAG, "failed to extract certificate" + e);
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    Log.e(TAG, "close FileInputStream error: " + e);
                }
            }
        }
    }

    /**
     * Extract private keys, user certificates and ca certificates
     */
    private synchronized boolean installFrom(PrivateKeyEntry entry) {
        mUserKey = entry.getPrivateKey();
        mUserCert = (X509Certificate) entry.getCertificate();

        Certificate[] certs = entry.getCertificateChain();
        Log.d(TAG, "# certs extracted = " + certs.length);
        mCaCerts = new ArrayList<X509Certificate>(certs.length);
        for (Certificate c : certs) {
            X509Certificate cert = (X509Certificate) c;
            if (isCa(cert)) {
                mCaCerts.add(cert);
            }
        }
        Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
        return true;
    }

    private boolean isCa(X509Certificate cert) {
        try {
            byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
            if (asn1EncodedBytes == null) {
                return false;
            }
            DEROctetString derOctetString = (DEROctetString)
                    new ASN1InputStream(asn1EncodedBytes).readObject();
            byte[] octets = derOctetString.getOctets();
            ASN1Sequence sequence = (ASN1Sequence)
                    new ASN1InputStream(octets).readObject();
            return BasicConstraints.getInstance(sequence).isCA();
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * Extract certificate from the given file, and install it to keystore
     * @param name certificate name
     * @param certFile .p12 file which includes certificates
     * @param password password to extract the .p12 file
     */
    public void installCertificate(VpnProfile profile, String certFile, String password) {
        // extract private keys, certificates from the provided file
        extractCertificate(certFile, password);
        // install certificate to the keystore
        int flags = KeyStore.FLAG_ENCRYPTED;
        try {
            if (mUserKey != null) {
                Log.v(TAG, "has private key");
                String key = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
                byte[] value = mUserKey.getEncoded();

                if (!mKeyStore.importKey(key, value, mUid, flags)) {
                    Log.e(TAG, "Failed to install " + key + " as user " + mUid);
                    return;
                }
                Log.v(TAG, "install " + key + " as user " + mUid + " is successful");
            }

            if (mUserCert != null) {
                String certName = Credentials.USER_CERTIFICATE + profile.ipsecUserCert;
                byte[] certData = Credentials.convertToPem(mUserCert);

                if (!mKeyStore.put(certName, certData, mUid, flags)) {
                    Log.e(TAG, "Failed to install " + certName + " as user " + mUid);
                    return;
                }
                Log.v(TAG, "install " + certName + " as user" + mUid + " is successful.");
            }

            if (!mCaCerts.isEmpty()) {
                String caListName = Credentials.CA_CERTIFICATE + profile.ipsecCaCert;
                X509Certificate[] caCerts = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
                byte[] caListData = Credentials.convertToPem(caCerts);

                if (!mKeyStore.put(caListName, caListData, mUid, flags)) {
                    Log.e(TAG, "Failed to install " + caListName + " as user " + mUid);
                    return;
                }
                Log.v(TAG, " install " + caListName + " as user " + mUid + " is successful");
            }
        } catch (CertificateEncodingException e) {
            Log.e(TAG, "Exception while convert certificates to pem " + e);
            throw new AssertionError(e);
        } catch (IOException e) {
            Log.e(TAG, "IOException while convert to pem: " + e);
        }
    }

    public int getUid() {
        return mUid;
    }
}
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.settings.vpn2;

import com.android.internal.net.VpnProfile;

/**
 * Wrapper for VPN Profile and associated certificate files
 */
public class VpnInfo {
    // VPN Profile
    private VpnProfile mVpnProfile;
    // Certificate file in PC12 format for user certificates and private keys
    private String mCertificateFile = null;
    // Password to extract certificates from the file
    private String mPassword = null;

    public VpnInfo(VpnProfile vpnProfile, String certFile, String password) {
        mVpnProfile = vpnProfile;
        mCertificateFile = certFile;
        mPassword = password;
    }

    public VpnInfo(VpnProfile vpnProfile) {
        mVpnProfile = vpnProfile;
    }

    public void setVpnProfile(VpnProfile vpnProfile) {
        mVpnProfile = vpnProfile;
    }

    public void setCertificateFile(String certFile) {
        mCertificateFile = certFile;
    }

    public void setPassword(String password) {
        mPassword = password;
    }

    public VpnProfile getVpnProfile() {
        return mVpnProfile;
    }

    public String getCertificateFile() {
        return mCertificateFile;
    }

    public String getPassword() {
        return mPassword;
    }
}
+246 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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 com.android.settings.vpn2;

import android.util.Log;

import com.android.internal.net.VpnProfile;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

/**
 * Parse VPN profiles from an XML file
 */
public class VpnProfileParser {
    private final static String TAG = "VpnProfileParser";
    private static Map<Integer, VpnInfo> mVpnPool = new HashMap<Integer, VpnInfo>();

    static DefaultHandler mHandler = new DefaultHandler() {
        boolean name;
        boolean type;
        boolean server;
        boolean username;
        boolean password;
        boolean dnsServers;
        boolean searchDomains;
        boolean routes;
        boolean mppe;
        boolean l2tpSecret;
        boolean ipsecIdentifier;
        boolean ipsecSecret;
        boolean ipsecUserCert;
        boolean ipsecCaCert;
        boolean ipsecServerCert;
        boolean certFile;
        boolean certFilePassword;
        VpnProfile profile = null;
        VpnInfo vpnInfo = null;


        @Override
        public void startElement(String uri, String localName, String tagName,
                Attributes attributes) throws SAXException {
            if (tagName.equalsIgnoreCase("vpn")) {
                //create a new VPN profile
                profile = new VpnProfile(Long.toHexString(System.currentTimeMillis()));
                vpnInfo = new VpnInfo(profile);
            }
            if (tagName.equalsIgnoreCase("name")) {
                name = true;
            }
            if (tagName.equalsIgnoreCase("type")) {
                type = true;
            }
            if (tagName.equalsIgnoreCase("server")) {
                server = true;
            }
            if (tagName.equalsIgnoreCase("username")) {
                username = true;
            }
            if (tagName.equalsIgnoreCase("password")) {
                password = true;
            }
            if (tagName.equalsIgnoreCase("dnsServers")) {
                dnsServers = true;
            }
            if (tagName.equalsIgnoreCase("searchDomains")) {
                searchDomains = true;
            }
            if (tagName.equalsIgnoreCase("mppe")) {
                mppe = true;
            }
            if (tagName.equalsIgnoreCase("l2tpSecret")) {
                l2tpSecret = true;
            }
            if (tagName.equalsIgnoreCase("ipsecIdentifier")) {
                ipsecIdentifier = true;
            }
            if (tagName.equalsIgnoreCase("ipsecSecret")) {
                ipsecSecret = true;
            }
            if (tagName.equalsIgnoreCase("ipsecUserCert")) {
                ipsecUserCert = true;
            }
            if (tagName.equalsIgnoreCase("ipsecCaCert")) {
                ipsecCaCert = true;
            }
            if (tagName.equalsIgnoreCase("ipsecServerCert")) {
                ipsecServerCert = true;
            }
            if (tagName.equalsIgnoreCase("routes")) {
                routes = true;
            }
            if (tagName.equalsIgnoreCase("cert-file")) {
                certFile = true;
            }
            if (tagName.equalsIgnoreCase("cert-file-password")) {
                certFilePassword = true;
            }
        }

        @Override
        public void endElement(String uri, String localName, String tagName) throws SAXException {
            if (tagName.equalsIgnoreCase("vpn")) {
                mVpnPool.put(profile.type, vpnInfo);
            }
        }

        @Override
        public void characters(char ch[], int start, int length) throws SAXException {
            String strValue = new String(ch, start, length);
            if (name) {
                profile.name = strValue;
                name = false;
            }
            if (type) {
                int t = getVpnProfileType(strValue);
                if (t < 0) {
                    throw new SAXException("not a valid VPN type");
                } else {
                    profile.type = t;
                }
                type = false;
            }
            if (server) {
                profile.server = strValue;
                server = false;
            }
            if (username) {
                profile.username = strValue;
                username = false;
            }
            if (password) {
                profile.password = strValue;
                password = false;
            }
            if (dnsServers) {
                profile.dnsServers = strValue;
                dnsServers = false;
            }
            if (searchDomains) {
                profile.searchDomains = strValue;
                searchDomains = false;
            }
            if (mppe) {
                profile.mppe = Boolean.valueOf(strValue);
                mppe = false;
            }
            if (l2tpSecret) {
                profile.l2tpSecret = strValue;
                l2tpSecret = false;
            }
            if (ipsecIdentifier) {
                profile.ipsecIdentifier = strValue;
                ipsecIdentifier = false;
            }
            if (ipsecSecret) {
                profile.ipsecSecret = strValue;
                ipsecSecret = false;
            }
            if (ipsecUserCert) {
                profile.ipsecUserCert = strValue;
                ipsecUserCert = false;
            }
            if (ipsecCaCert) {
                profile.ipsecCaCert = strValue;
                ipsecCaCert = false;
            }
            if (ipsecServerCert) {
                profile.ipsecServerCert = strValue;
                ipsecServerCert = false;
            }
            if (routes) {
                profile.routes = strValue;
                routes = false;
            }
            if (certFile) {
                vpnInfo.setCertificateFile(strValue);
                certFile = false;
            }
            if (certFilePassword) {
                vpnInfo.setPassword(strValue);
                certFilePassword = false;
            }
        }

        private int getVpnProfileType(String type) {
            if (type.equalsIgnoreCase("TYPE_PPTP")) {
                return VpnProfile.TYPE_PPTP;
            } else if (type.equalsIgnoreCase("TYPE_L2TP_IPSEC_PSK")) {
                return VpnProfile.TYPE_L2TP_IPSEC_PSK;
            } else if (type.equalsIgnoreCase("TYPE_L2TP_IPSEC_RSA")) {
                return VpnProfile.TYPE_L2TP_IPSEC_RSA;
            } else if (type.equalsIgnoreCase("TYPE_IPSEC_XAUTH_PSK")) {
                return VpnProfile.TYPE_IPSEC_XAUTH_PSK;
            } else if (type.equalsIgnoreCase("TYPE_IPSEC_XAUTH_RSA")) {
                return VpnProfile.TYPE_IPSEC_XAUTH_RSA;
            } else if (type.equalsIgnoreCase("TYPE_IPSEC_HYBRID_RSA")) {
                return VpnProfile.TYPE_IPSEC_HYBRID_RSA;
            } else {
                Log.v(TAG, "Invalid VPN type: " + type);
                return -1;
            }
        }
    };

    public static Map<Integer, VpnInfo> parse(InputStream in) {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(in, mHandler);
        } catch (SAXException e) {
            Log.e(TAG, "Parse vpn profile exception: " + e.toString());
        } catch (IOException e) {
            Log.e(TAG, "Parse vpn profile exception: " + e.toString());
        } catch (ParserConfigurationException e) {
            Log.e(TAG, "Parse vpn profile exception: " + e.toString());
        } finally {
            return mVpnPool;
        }
    }
}
Loading