/*
* Copyright (c) 2012-2014 Arne Schwabe
* Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
*/
package de.blinkt.openvpn;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.R;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.preference.PreferenceManager;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.text.TextUtils;
import android.util.Base64;
import org.spongycastle.util.io.pem.PemObject;
import org.spongycastle.util.io.pem.PemWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Locale;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.Future;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import de.blinkt.openvpn.core.NativeUtils;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.X509Utils;
public class VpnProfile implements Serializable {
// Note that this class cannot be moved to core where it belongs since
// the profile loading depends on it being here
// The Serializable documentation mentions that class name change are possible
// but the how is unclear
//
transient public static final long MAX_EMBED_FILE_SIZE = 2048*1024; // 2048kB
// Don't change this, not all parts of the program use this constant
public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
public static final String INLINE_TAG = "[[INLINE]]";
public static final String DISPLAYNAME_TAG = "[[NAME]]";
private static final String MININONPIEVPN = "nopievpn";
private static final String MINIPIEVPN = "pievpn";
private static final long serialVersionUID = 7085688938959334563L;
private static final String OVPNCONFIGFILE = "android.conf";
public static final int MAXLOGLEVEL = 4;
public static final int CURRENT_PROFILE_VERSION = 2;
public static final int DEFAULT_MSSFIX_SIZE = 1450;
public static String DEFAULT_DNS1 = "8.8.8.8";
public static String DEFAULT_DNS2 = "8.8.4.4";
public transient String mTransientPW = null;
public transient String mTransientPCKS12PW = null;
public static final int TYPE_CERTIFICATES = 0;
public static final int TYPE_PKCS12 = 1;
public static final int TYPE_KEYSTORE = 2;
public static final int TYPE_USERPASS = 3;
public static final int TYPE_STATICKEYS = 4;
public static final int TYPE_USERPASS_CERTIFICATES = 5;
public static final int TYPE_USERPASS_PKCS12 = 6;
public static final int TYPE_USERPASS_KEYSTORE = 7;
public static final int X509_VERIFY_TLSREMOTE = 0;
public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
public static final int X509_VERIFY_TLSREMOTE_DN = 2;
public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
// variable named wrong and should haven beeen transient
// but needs to keep wrong name to guarante loading of old
// profiles
public transient boolean profileDleted = false;
public int mAuthenticationType = TYPE_KEYSTORE;
public String mName;
public String mAlias;
public String mClientCertFilename;
public String mTLSAuthDirection = "";
public String mTLSAuthFilename;
public String mClientKeyFilename;
public String mCaFilename;
public boolean mUseLzo = true;
public String mServerPort = "1194";
public boolean mUseUdp = true;
public String mPKCS12Filename;
public String mPKCS12Password;
public boolean mUseTLSAuth = false;
public String mServerName = "openvpn.blinkt.de";
public String mDNS1 = DEFAULT_DNS1;
public String mDNS2 = DEFAULT_DNS2;
public String mIPv4Address;
public String mIPv6Address;
public boolean mOverrideDNS = false;
public String mSearchDomain = "blinkt.de";
public boolean mUseDefaultRoute = true;
public boolean mUsePull = true;
public String mCustomRoutes;
public boolean mCheckRemoteCN = true;
public boolean mExpectTLSCert = false;
public String mRemoteCN = "";
public String mPassword = "";
public String mUsername = "";
public boolean mRoutenopull = false;
public boolean mUseRandomHostname = false;
public boolean mUseFloat = false;
public boolean mUseCustomConfig = false;
public String mCustomConfigOptions = "";
public String mVerb = "1"; //ignored
public String mCipher = "";
public boolean mNobind = false;
public boolean mUseDefaultRoutev6 = true;
public String mCustomRoutesv6 = "";
public String mKeyPassword = "";
public boolean mPersistTun = false;
public String mConnectRetryMax = "5";
public String mConnectRetry = "5";
public boolean mUserEditable = true;
public String mAuth = "";
public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
private transient PrivateKey mPrivateKey;
// Public attributes, since I got mad with getter/setter
// set members to default values
private UUID mUuid;
public boolean mAllowLocalLAN;
private int mProfileVersion;
public String mExcludedRoutes;
public String mExcludedRoutesv6;
public int mMssFix =0; // -1 is default,
public VpnProfile(String name) {
mUuid = UUID.randomUUID();
mName = name;
mProfileVersion = CURRENT_PROFILE_VERSION;
}
public static String getMiniVPNExecutableName()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
return VpnProfile.MINIPIEVPN;
else
return VpnProfile.MININONPIEVPN;
}
public static String[] replacePieWithNoPie(String[] mArgv)
{
mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN);
return mArgv;
}
public static String openVpnEscape(String unescaped) {
if (unescaped == null)
return null;
String escapedString = unescaped.replace("\\", "\\\\");
escapedString = escapedString.replace("\"", "\\\"");
escapedString = escapedString.replace("\n", "\\n");
if (escapedString.equals(unescaped) && !escapedString.contains(" ") &&
!escapedString.contains("#") && !escapedString.contains(";")
&& !escapedString.equals(""))
return unescaped;
else
return '"' + escapedString + '"';
}
public void clearDefaults() {
mServerName = "unknown";
mUsePull = false;
mUseLzo = false;
mUseDefaultRoute = false;
mUseDefaultRoutev6 = false;
mExpectTLSCert = false;
mCheckRemoteCN = false;
mPersistTun = false;
mAllowLocalLAN = true;
mMssFix = 0;
}
public UUID getUUID() {
return mUuid;
}
public String getName() {
if (mName==null)
return "No profile name";
return mName;
}
public void upgradeProfile(){
if(mProfileVersion< 2) {
/* default to the behaviour the OS used */
mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT;
}
mProfileVersion= CURRENT_PROFILE_VERSION;
}
public String getConfigFile(Context context, boolean configForOvpn3) {
File cacheDir = context.getCacheDir();
String cfg = "";
// Enable management interface
cfg += "# Enables connection to GUI\n";
cfg += "management ";
cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
cfg += " unix\n";
cfg += "management-client\n";
// Not needed, see updated man page in 2.3
//cfg += "management-signal\n";
cfg += "management-query-passwords\n";
cfg += "management-hold\n\n";
if (!configForOvpn3)
cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)));
cfg += "machine-readable-output\n";
// Users are confused by warnings that are misleading...
cfg += "ifconfig-nowarn\n";
boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);
if (useTLSClient && mUsePull)
cfg += "client\n";
else if (mUsePull)
cfg += "pull\n";
else if (useTLSClient)
cfg += "tls-client\n";
//cfg += "verb " + mVerb + "\n";
cfg += "verb " + MAXLOGLEVEL + "\n";
if (mConnectRetryMax == null) {
mConnectRetryMax = "5";
}
if (!mConnectRetryMax.equals("-1"))
cfg += "connect-retry-max " + mConnectRetryMax + "\n";
if (mConnectRetry == null)
mConnectRetry = "5";
cfg += "connect-retry " + mConnectRetry + "\n";
cfg += "resolv-retry 60\n";
// We cannot use anything else than tun
cfg += "dev tun\n";
// Server Address
cfg += "remote ";
cfg += mServerName;
cfg += " ";
cfg += mServerPort;
if (mUseUdp)
cfg += " udp\n";
else
cfg += " tcp-client\n";
switch (mAuthenticationType) {
case VpnProfile.TYPE_USERPASS_CERTIFICATES:
cfg += "auth-user-pass\n";
case VpnProfile.TYPE_CERTIFICATES:
// Ca
cfg += insertFileData("ca", mCaFilename);
// Client Cert + Key
cfg += insertFileData("key", mClientKeyFilename);
cfg += insertFileData("cert", mClientCertFilename);
break;
case VpnProfile.TYPE_USERPASS_PKCS12:
cfg += "auth-user-pass\n";
case VpnProfile.TYPE_PKCS12:
cfg += insertFileData("pkcs12", mPKCS12Filename);
break;
case VpnProfile.TYPE_USERPASS_KEYSTORE:
cfg += "auth-user-pass\n";
case VpnProfile.TYPE_KEYSTORE:
if (!configForOvpn3) {
String[] ks = getKeyStoreCertificates(context);
cfg += "### From Keystore ####\n";
if (ks != null) {
cfg += "\n" + ks[0] + "\n\n";
if (ks[1] != null)
cfg += "\n" + ks[1] + "\n\n";
cfg += "\n" + ks[2] + "\n\n";
cfg += "management-external-key\n";
} else {
cfg += context.getString(R.string.keychain_access) + "\n";
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
if (!mAlias.matches("^[a-zA-Z0-9]$"))
cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n";
}
}
break;
case VpnProfile.TYPE_USERPASS:
cfg += "auth-user-pass\n";
cfg += insertFileData("ca", mCaFilename);
}
if (mUseLzo) {
cfg += "comp-lzo\n";
}
if (mUseTLSAuth) {
if (mAuthenticationType == TYPE_STATICKEYS)
cfg += insertFileData("secret", mTLSAuthFilename);
else
cfg += insertFileData("tls-auth", mTLSAuthFilename);
if (!TextUtils.isEmpty(mTLSAuthDirection)) {
cfg += "key-direction ";
cfg += mTLSAuthDirection;
cfg += "\n";
}
}
if (!mUsePull) {
if (!TextUtils.isEmpty(mIPv4Address))
cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
if (!TextUtils.isEmpty(mIPv6Address))
cfg += "ifconfig-ipv6 " + mIPv6Address + "\n";
}
if (mUsePull && mRoutenopull)
cfg += "route-nopull\n";
String routes = "";
if (mUseDefaultRoute)
routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n";
else
{
for (String route : getCustomRoutes(mCustomRoutes)) {
routes += "route " + route + " vpn_gateway\n";
}
for (String route: getCustomRoutes(mExcludedRoutes)) {
routes += "route " + route + " net_gateway\n";
}
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mAllowLocalLAN)
cfg+="redirect-private block-local\n";
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mAllowLocalLAN)
cfg+="redirect-private unblock-local\n";
if (mUseDefaultRoutev6)
cfg += "route-ipv6 ::/0\n";
else
for (String route : getCustomRoutesv6(mCustomRoutesv6)) {
routes += "route-ipv6 " + route + "\n";
}
cfg += routes;
if (mOverrideDNS || !mUsePull) {
if (!TextUtils.isEmpty(mDNS1))
cfg += "dhcp-option DNS " + mDNS1 + "\n";
if (!TextUtils.isEmpty(mDNS2))
cfg += "dhcp-option DNS " + mDNS2 + "\n";
if (!TextUtils.isEmpty(mSearchDomain))
cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";
}
if (mMssFix !=0){
if (mMssFix!=1450)
cfg+=String.format("mssfix %d\n", mMssFix, Locale.US);
else
cfg+="mssfix\n";
}
if (mNobind)
cfg += "nobind\n";
// Authentication
if (mAuthenticationType != TYPE_STATICKEYS) {
if (mCheckRemoteCN) {
if (mRemoteCN == null || mRemoteCN.equals(""))
cfg += "verify-x509-name " + mServerName + " name\n";
else
switch (mX509AuthType) {
// 2.2 style x509 checks
case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING:
cfg += "compat-names no-remapping\n";
case X509_VERIFY_TLSREMOTE:
cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
break;
case X509_VERIFY_TLSREMOTE_RDN:
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n";
break;
case X509_VERIFY_TLSREMOTE_RDN_PREFIX:
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n";
break;
case X509_VERIFY_TLSREMOTE_DN:
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
break;
}
}
if (mExpectTLSCert)
cfg += "remote-cert-tls server\n";
}
if (!TextUtils.isEmpty(mCipher)) {
cfg += "cipher " + mCipher + "\n";
}
if (!TextUtils.isEmpty(mAuth)) {
cfg += "auth " + mAuth + "\n";
}
// Obscure Settings dialog
if (mUseRandomHostname)
cfg += "#my favorite options :)\nremote-random-hostname\n";
if (mUseFloat)
cfg += "float\n";
if (mPersistTun) {
cfg += "persist-tun\n";
cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n";
cfg += "preresolve\n";
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
if (usesystemproxy) {
cfg += "# Use system proxy setting\n";
cfg += "management-query-proxy\n";
}
if (mUseCustomConfig) {
cfg += "# Custom configuration options\n";
cfg += "# You are on your on own here :)\n";
cfg += mCustomConfigOptions;
cfg += "\n";
}
return cfg;
}
public String getVersionEnvString(Context c) {
String version = "unknown";
try {
PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
version = packageinfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
VpnStatus.logException(e);
}
return String.format(Locale.US, "%s %s", c.getPackageName(), version);
}
//! Put inline data inline and other data as normal escaped filename
private String insertFileData(String cfgentry, String filedata) {
if (filedata == null) {
// TODO: generate good error
return String.format("%s %s\n", cfgentry, "missing");
} else if (isEmbedded(filedata)) {
String dataWithOutHeader = getEmbeddedContent(filedata);
return String.format(Locale.ENGLISH, "<%s>\n%s\n%s>\n", cfgentry, dataWithOutHeader, cfgentry);
} else {
return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
}
}
private Collection getCustomRoutes(String routes) {
Vector cidrRoutes = new Vector();
if (routes == null) {
// No routes set, return empty vector
return cidrRoutes;
}
for (String route : routes.split("[\n \t]")) {
if (!route.equals("")) {
String cidrroute = cidrToIPAndNetmask(route);
if (cidrroute == null)
return null;
cidrRoutes.add(cidrroute);
}
}
return cidrRoutes;
}
private Collection getCustomRoutesv6(String routes) {
Vector cidrRoutes = new Vector();
if (routes == null) {
// No routes set, return empty vector
return cidrRoutes;
}
for (String route : routes.split("[\n \t]")) {
if (!route.equals("")) {
cidrRoutes.add(route);
}
}
return cidrRoutes;
}
private String cidrToIPAndNetmask(String route) {
String[] parts = route.split("/");
// No /xx, assume /32 as netmask
if (parts.length == 1)
parts = (route + "/32").split("/");
if (parts.length != 2)
return null;
int len;
try {
len = Integer.parseInt(parts[1]);
} catch (NumberFormatException ne) {
return null;
}
if (len < 0 || len > 32)
return null;
long nm = 0xffffffffl;
nm = (nm << (32 - len)) & 0xffffffffl;
String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff);
return parts[0] + " " + netmask;
}
private String[] buildOpenvpnArgv(File cacheDir) {
Vector args = new Vector();
// Add fixed paramenters
//args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
args.add(cacheDir.getAbsolutePath() + "/" + getMiniVPNExecutableName());
args.add("--config");
args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE);
return args.toArray(new String[args.size()]);
}
public Intent prepareStartService(Context context) {
Intent intent = getStartServiceIntent(context);
if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
if (getKeyStoreCertificates(context) == null)
return null;
}
try {
FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE);
cfg.write(getConfigFile(context, false));
cfg.flush();
cfg.close();
} catch (IOException e) {
VpnStatus.logException(e);
}
return intent;
}
public Intent getStartServiceIntent(Context context) {
String prefix = context.getPackageName();
Intent intent = new Intent(context, OpenVPNService.class);
intent.putExtra(prefix + ".ARGV", buildOpenvpnArgv(context.getCacheDir()));
intent.putExtra(prefix + ".profileUUID", mUuid.toString());
ApplicationInfo info = context.getApplicationInfo();
intent.putExtra(prefix + ".nativelib", info.nativeLibraryDir);
return intent;
}
public String[] getKeyStoreCertificates(Context context) {
return getKeyStoreCertificates(context, 5);
}
public static String getDisplayName(String embeddedFile) {
int start = DISPLAYNAME_TAG.length();
int end = embeddedFile.indexOf(INLINE_TAG);
return embeddedFile.substring(start,end);
}
public static String getEmbeddedContent(String data)
{
if (!data.contains(INLINE_TAG))
return data;
int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
return data.substring(start);
}
public static boolean isEmbedded(String data) {
if (data==null)
return false;
if(data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
return true;
else
return false;
}
public void checkForRestart(final Context context) {
/* This method is called when OpenVPNService is restarted */
if ((mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE)
&& mPrivateKey==null) {
new Thread( new Runnable() {
@Override
public void run() {
getKeyStoreCertificates(context);
}
}).start();
}
}
class NoCertReturnedException extends Exception {
public NoCertReturnedException (String msg) {
super(msg);
}
}
synchronized String[] getKeyStoreCertificates(Context context,int tries) {
PrivateKey privateKey = null;
X509Certificate[] caChain;
Exception exp;
try {
privateKey = KeyChain.getPrivateKey(context, mAlias);
mPrivateKey = privateKey;
String keystoreChain = null;
caChain = KeyChain.getCertificateChain(context, mAlias);
if(caChain == null)
throw new NoCertReturnedException("No certificate returned from Keystore");
if (caChain.length <= 1 && TextUtils.isEmpty(mCaFilename)) {
VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert));
} else {
StringWriter ksStringWriter = new StringWriter();
PemWriter pw = new PemWriter(ksStringWriter);
for (int i = 1; i < caChain.length; i++) {
X509Certificate cert = caChain[i];
pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
}
pw.close();
keystoreChain = ksStringWriter.toString();
}
String caout = null;
if (!TextUtils.isEmpty(mCaFilename)) {
try {
Certificate cacert = X509Utils.getCertificateFromFile(mCaFilename);
StringWriter caoutWriter = new StringWriter();
PemWriter pw = new PemWriter(caoutWriter);
pw.writeObject(new PemObject("CERTIFICATE", cacert.getEncoded()));
pw.close();
caout= caoutWriter.toString();
} catch (Exception e) {
VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage());
}
}
StringWriter certout = new StringWriter();
if (caChain.length >= 1) {
X509Certificate usercert = caChain[0];
PemWriter upw = new PemWriter(certout);
upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded()));
upw.close();
}
String user = certout.toString();
String ca, extra;
if(caout==null) {
ca =keystoreChain;
extra=null;
} else {
ca = caout;
extra=keystoreChain;
}
return new String[]{ca, extra, user};
} catch (InterruptedException e) {
exp=e;
} catch (FileNotFoundException e) {
exp=e;
} catch (CertificateException e) {
exp=e;
} catch (IOException e) {
exp=e;
} catch (KeyChainException e) {
exp=e;
} catch (NoCertReturnedException e) {
exp =e;
} catch (IllegalArgumentException e) {
exp =e;
} catch (AssertionError e) {
if (tries ==0)
return null;
VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying",e.getLocalizedMessage()));
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
VpnStatus.logException(e1);
}
return getKeyStoreCertificates(context, tries-1);
}
exp.printStackTrace();
VpnStatus.logError(R.string.keyChainAccessError, exp.getLocalizedMessage());
VpnStatus.logError(R.string.keychain_access);
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
if (!mAlias.matches("^[a-zA-Z0-9]$")) {
VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug);
}
}
return null;
}
//! Return an error if somethign is wrong
public int checkProfile(Context context) {
if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
if (mAlias == null)
return R.string.no_keystore_cert_selected;
}
if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) {
if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null)
return R.string.ipv4_format_error;
}
if (!mUseDefaultRoute && (getCustomRoutes(mCustomRoutes) == null || getCustomRoutes(mExcludedRoutes) ==null))
return R.string.custom_route_format_error;
// Everything okay
return R.string.no_error_found;
}
//! Openvpn asks for a "Private Key", this should be pkcs12 key
//
public String getPasswordPrivateKey() {
if (mTransientPCKS12PW != null) {
String pwcopy = mTransientPCKS12PW;
mTransientPCKS12PW = null;
return pwcopy;
}
switch (mAuthenticationType) {
case TYPE_PKCS12:
case TYPE_USERPASS_PKCS12:
return mPKCS12Password;
case TYPE_CERTIFICATES:
case TYPE_USERPASS_CERTIFICATES:
return mKeyPassword;
case TYPE_USERPASS:
case TYPE_STATICKEYS:
default:
return null;
}
}
public boolean isUserPWAuth() {
switch (mAuthenticationType) {
case TYPE_USERPASS:
case TYPE_USERPASS_CERTIFICATES:
case TYPE_USERPASS_KEYSTORE:
case TYPE_USERPASS_PKCS12:
return true;
default:
return false;
}
}
public boolean requireTLSKeyPassword() {
if (TextUtils.isEmpty(mClientKeyFilename))
return false;
String data = "";
if (isEmbedded(mClientKeyFilename))
data = mClientKeyFilename;
else {
char[] buf = new char[2048];
FileReader fr;
try {
fr = new FileReader(mClientKeyFilename);
int len = fr.read(buf);
while (len > 0) {
data += new String(buf, 0, len);
len = fr.read(buf);
}
fr.close();
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
}
}
if (data.contains("Proc-Type: 4,ENCRYPTED"))
return true;
else if (data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----"))
return true;
else
return false;
}
public int needUserPWInput(boolean ignoreTransient) {
if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) &&
(mPKCS12Password == null || mPKCS12Password.equals(""))) {
if (ignoreTransient || mTransientPCKS12PW == null)
return R.string.pkcs12_file_encryption_key;
}
if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
if (requireTLSKeyPassword() && TextUtils.isEmpty(mKeyPassword))
if (ignoreTransient || mTransientPCKS12PW == null) {
return R.string.private_key_password;
}
}
if (isUserPWAuth() &&
(TextUtils.isEmpty(mUsername) ||
(TextUtils.isEmpty(mPassword) && (mTransientPW == null || ignoreTransient)))) {
return R.string.password;
}
return 0;
}
public String getPasswordAuth() {
if (mTransientPW != null) {
String pwcopy = mTransientPW;
mTransientPW = null;
return pwcopy;
} else {
return mPassword;
}
}
// Used by the Array Adapter
@Override
public String toString() {
return mName;
}
public String getUUIDString() {
return mUuid.toString();
}
public PrivateKey getKeystoreKey() {
return mPrivateKey;
}
public String getSignedData(String b64data) {
PrivateKey privkey = getKeystoreKey();
Exception err;
byte[] data = Base64.decode(b64data, Base64.DEFAULT);
// The Jelly Bean *evil* Hack
// 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
return processSignJellyBeans(privkey, data);
}
try {
/* ECB is perfectly fine in this special case, since we are using it for
the public/private part in the TLS exchange
*/
@SuppressLint("GetInstance")
Cipher rsaSigner = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
rsaSigner.init(Cipher.ENCRYPT_MODE, privkey);
byte[] signed_bytes = rsaSigner.doFinal(data);
return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
} catch (NoSuchAlgorithmException e) {
err = e;
} catch (InvalidKeyException e) {
err = e;
} catch (NoSuchPaddingException e) {
err = e;
} catch (IllegalBlockSizeException e) {
err = e;
} catch (BadPaddingException e) {
err = e;
}
VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage());
return null;
}
private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
Exception err;
try {
Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
getKey.setAccessible(true);
// Real object type is OpenSSLKey
Object opensslkey = getKey.invoke(privkey);
getKey.setAccessible(false);
Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext");
// integer pointer to EVP_pkey
getPkeyContext.setAccessible(true);
int pkey = (Integer) getPkeyContext.invoke(opensslkey);
getPkeyContext.setAccessible(false);
// 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
} catch (NoSuchMethodException e) {
err = e;
} catch (IllegalArgumentException e) {
err = e;
} catch (IllegalAccessException e) {
err = e;
} catch (InvocationTargetException e) {
err = e;
} catch (InvalidKeyException e) {
err = e;
}
VpnStatus.logError(R.string.error_rsa_sign, err.getClass().toString(), err.getLocalizedMessage());
return null;
}
}