summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl33
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java187
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java14
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java15
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java6
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java30
6 files changed, 248 insertions, 37 deletions
diff --git a/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl b/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
index c6db965b..1f77b15f 100644
--- a/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
+++ b/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
@@ -1,16 +1,16 @@
// ExternalCertificateProvider.aidl
package de.blinkt.openvpn.api;
-
/*
* This is very simple interface that is specialised to have only the minimal set of crypto
* operation that are needed for OpenVPN to authenticate with an external certificate
*/
interface ExternalCertificateProvider {
/**
+ * @deprecated use {@link #getSignedDataWithExtra} instead
* Requests signing the data with RSA/ECB/PKCS1PADDING
* for RSA certficate and with NONEwithECDSA for EC certificates
- * @parm alias the parameter that
+ * @param alias user certificate identifier
*/
byte[] getSignedData(in String alias, in byte[] data);
@@ -36,4 +36,33 @@ interface ExternalCertificateProvider {
*
*/
Bundle getCertificateMetaData(in String alias);
+
+ /**
+ * Requests signing the data with RSA/ECB/nopadding, RSA/ECB/PKCS1PADDING or PKCS1PSSPADDING
+ * for RSA certficate and with NONEwithECDSA for EC certificates
+ * @param alias user certificate identifier
+ * @param data the data to be signed
+ * @param extra additional information.
+ * Should contain the following keys:
+ * <ul>
+ * <li>int key "de.blinkt.openvpn.api.RSA_PADDING_TYPE", may be set as:
+ * <ul>
+ * <li>0 - for RSA/ECB/nopadding
+ * <li>1 - for RSA/ECB/PKCS1PADDING
+ * <li>2 - for PKCS1PSSPADDING
+ * </ul>
+ * <li>string key "de.blinkt.openvpn.api.SALTLEN", may be set as:
+ * <ul>
+ * <li>"digest" - use the same salt size as the hash to sign
+ * <li>"max" - use maximum possible saltlen which is '(nbits-1)/8 - hlen - 2'. Here
+ * 'nbits' is the number of bits in the key modulus and 'hlen' is the size in octets of
+ * the hash. See: RFC 8017 sec 8.1.1 and 9.1.1.
+ * </ul>
+ * <li>boolean key "de.blinkt.openvpn.api.NEEDS_DIGEST", indicating that the data should be
+ * hashed before signing or not
+ * <li>string key "de.blinkt.openvpn.api.DIGEST", the short common digest algorithm name to
+ * use (such as SHA256, SHA224, etc.)
+ * </ul>
+ */
+ byte[] getSignedDataWithExtra(in String alias, in byte[] data, in Bundle extra);
}
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 9baac195..9f722dfe 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -15,6 +15,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.Bundle;
import android.preference.PreferenceManager;
import android.security.KeyChain;
import android.security.KeyChainException;
@@ -37,7 +38,9 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
+import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
@@ -45,6 +48,9 @@ import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
import java.util.Collection;
import java.util.HashSet;
import java.util.Locale;
@@ -58,6 +64,7 @@ import javax.crypto.NoSuchPaddingException;
import de.blinkt.openvpn.core.ExtAuthHelper;
import de.blinkt.openvpn.core.NativeUtils;
+import de.blinkt.openvpn.core.OpenVPNManagement;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.OrbotHelper;
import de.blinkt.openvpn.core.PasswordCache;
@@ -104,6 +111,10 @@ public class VpnProfile implements Serializable, Cloneable {
private static final long serialVersionUID = 7085688938959334563L;
private static final int AUTH_RETRY_NONE_KEEP = 1;
private static final int AUTH_RETRY_INTERACT = 3;
+ private static final String EXTRA_RSA_PADDING_TYPE = "de.blinkt.openvpn.api.RSA_PADDING_TYPE";
+ private static final String EXTRA_SALTLEN = "de.blinkt.openvpn.api.SALTLEN";
+ private static final String EXTRA_NEEDS_DIGEST = "de.blinkt.openvpn.api.NEEDS_DIGEST";
+ private static final String EXTRA_DIGEST = "de.blinkt.openvpn.api.DIGEST";
public static String DEFAULT_DNS1 = "8.8.8.8";
public static String DEFAULT_DNS2 = "8.8.4.4";
// variable named wrong and should haven beeen transient
@@ -458,7 +469,7 @@ public class VpnProfile implements Serializable, Cloneable {
if (!TextUtils.isEmpty(ks[1]))
cfg.append("<extra-certs>\n").append(ks[1]).append("\n</extra-certs>\n");
cfg.append("<cert>\n").append(ks[2]).append("\n</cert>\n");
- cfg.append("management-external-key nopadding\n");
+ cfg.append("management-external-key nopadding pkcs1 pss digest\n");
} else {
cfg.append(context.getString(R.string.keychain_access)).append("\n");
}
@@ -1148,13 +1159,14 @@ public class VpnProfile implements Serializable, Cloneable {
}
@Nullable
- public String getSignedData(Context c, String b64data, boolean pkcs1padding) {
+ public String getSignedData(Context c, String b64data, OpenVPNManagement.SignaturePadding padding, String saltlen, String hashalg, boolean needDigest) {
byte[] data = Base64.decode(b64data, Base64.DEFAULT);
byte[] signed_bytes;
- if (mAuthenticationType == TYPE_EXTERNAL_APP)
- signed_bytes = getExtAppSignedData(c, data);
- else
- signed_bytes = getKeyChainSignedData(data, pkcs1padding);
+ if (mAuthenticationType == TYPE_EXTERNAL_APP) {
+ signed_bytes = getExtAppSignedData(c, data, padding, saltlen, hashalg, needDigest);
+ } else {
+ signed_bytes = getKeyChainSignedData(data, padding, saltlen, hashalg, needDigest);
+ }
if (signed_bytes != null)
return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
@@ -1162,19 +1174,41 @@ public class VpnProfile implements Serializable, Cloneable {
return null;
}
- private byte[] getExtAppSignedData(Context c, byte[] data) {
+ private byte[] getExtAppSignedData(Context c, byte[] data, OpenVPNManagement.SignaturePadding padding, String saltlen, String hashalg, boolean needDigest)
+ {
+
+ Bundle extra = new Bundle();
+ RsaPaddingType paddingType;
+ switch (padding) {
+ case RSA_PKCS1_PADDING:
+ paddingType = RsaPaddingType.PKCS1_PADDING;
+ break;
+ case NO_PADDING:
+ paddingType = RsaPaddingType.NO_PADDING;
+ break;
+ case RSA_PKCS1_PSS_PADDING:
+ paddingType = RsaPaddingType.RSAPSS_PADDING;
+ break;
+ default:
+ paddingType = RsaPaddingType.NO_PADDING;
+ }
+
+ extra.putInt(EXTRA_RSA_PADDING_TYPE, paddingType.ordinal());
+ extra.putString(EXTRA_SALTLEN, saltlen);
+ extra.putString(EXTRA_DIGEST, hashalg);
+ extra.putBoolean(EXTRA_NEEDS_DIGEST, needDigest);
+
if (TextUtils.isEmpty(mExternalAuthenticator))
return null;
try {
- return ExtAuthHelper.signData(c, mExternalAuthenticator, mAlias, data);
+ return ExtAuthHelper.signData(c, mExternalAuthenticator, mAlias, data, extra);
} catch (KeyChainException | InterruptedException e) {
VpnStatus.logError(R.string.error_extapp_sign, mExternalAuthenticator, e.getClass().toString(), e.getLocalizedMessage());
return null;
}
}
- private byte[] getKeyChainSignedData(byte[] data, boolean pkcs1padding) {
-
+ private byte[] getKeyChainSignedData(byte[] data, OpenVPNManagement.SignaturePadding padding, String saltlen, String hashalg, boolean needDigest) {
PrivateKey privkey = getKeystoreKey();
try {
@@ -1182,36 +1216,121 @@ public class VpnProfile implements Serializable, Cloneable {
String keyalgorithm = privkey.getAlgorithm();
byte[] signed_bytes;
- if (keyalgorithm.equals("EC")) {
- Signature signer = Signature.getInstance("NONEwithECDSA");
-
- signer.initSign(privkey);
- signer.update(data);
- signed_bytes = signer.sign();
-
+ if (needDigest || keyalgorithm.equals("EC")) {
+ return doDigestSign(privkey, data, padding, hashalg, saltlen);
} else {
- /* ECB is perfectly fine in this special case, since we are using it for
- the public/private part in the TLS exchange
- */
- Cipher signer;
- if (pkcs1padding)
- signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
- else
- signer = Cipher.getInstance("RSA/ECB/NoPadding");
-
+ /* ECB is perfectly fine in this special case, since we are using it for
+ the public/private part in the TLS exchange */
+ Cipher signer = null;
+ switch (padding) {
+ case RSA_PKCS1_PADDING:
+ signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+ break;
+ case NO_PADDING:
+ signer = Cipher.getInstance("RSA/ECB/NoPadding");
+ break;
+ case RSA_PKCS1_PSS_PADDING:
+ throw new NoSuchPaddingException("Cannot do PKCS1 PSS padding without also doing the digest");
+ }
signer.init(Cipher.ENCRYPT_MODE, privkey);
signed_bytes = signer.doFinal(data);
+
+ return signed_bytes;
}
- return signed_bytes;
- } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException
- | BadPaddingException | NoSuchPaddingException | SignatureException e) {
+ } catch
+ (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | SignatureException | InvalidAlgorithmParameterException
+ e) {
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
return null;
}
}
+ private byte[] addPSSPadding(PrivateKey privkey, String digest, byte[] data) throws NoSuchAlgorithmException {
+ /* For < API 23, add padding ourselves */
+ int hashtype = getHashtype(digest);
+
+ MessageDigest msgDigest = MessageDigest.getInstance(digest);
+ byte[] hash = msgDigest.digest(data);
+
+ /* MSBits = (BN_num_bits(rsa->n) - 1) & 0x7; */
+ int numbits = ((RSAPrivateKey) privkey).getModulus().bitLength();
+
+ int MSBits = (numbits - 1) & 0x7;
+
+ return NativeUtils.addRssPssPadding(hashtype, MSBits, numbits/8, hash);
+ }
+
+ private int getHashtype(String digest) throws NoSuchAlgorithmException {
+ int hashtype = 0;
+ switch (digest) {
+ case "SHA1":
+ hashtype = 1;
+ break;
+ case "SHA224":
+ hashtype = 2;
+ break;
+ case "SHA256":
+ hashtype = 3;
+ break;
+ case "SHA384":
+ hashtype = 4;
+ break;
+ case "SHA512":
+ hashtype = 5;
+ break;
+ default:
+ throw new NoSuchAlgorithmException("Unknown digest algorithm: " + digest);
+ }
+ return hashtype;
+ }
+
+ private byte[] doDigestSign(PrivateKey privkey, byte[] data, OpenVPNManagement.SignaturePadding padding, String hashalg, String saltlen) throws SignatureException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
+ /* RSA */
+ Signature sig = null;
+
+ if (privkey.getAlgorithm().equals("EC")) {
+ if (hashalg.equals(""))
+ hashalg = "NONE";
+ /* e.g. SHA512withECDSA */
+ hashalg = hashalg + "withECDSA";
+ sig = Signature.getInstance(hashalg.toUpperCase(Locale.ROOT));
+ } else if (padding == OpenVPNManagement.SignaturePadding.RSA_PKCS1_PSS_PADDING) {
+ /* https://developer.android.com/training/articles/keystore#SupportedSignatures */
+ if (!"digest".equals(saltlen))
+ throw new SignatureException("PSS signing requires saltlen=digest");
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ data = addPSSPadding(privkey, hashalg, data);
+ return getKeyChainSignedData(data, OpenVPNManagement.SignaturePadding.NO_PADDING, "none", "none", false);
+ }
+
+ sig = Signature.getInstance(hashalg + "withRSA/PSS");
+
+ PSSParameterSpec pssspec = null;
+ switch (hashalg) {
+ case "SHA256":
+ pssspec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
+ break;
+ case "SHA512":
+ pssspec = new PSSParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 64, 1);
+ break;
+ case "SHA384":
+ pssspec = new PSSParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, 48, 1);
+ break;
+ }
+ sig.setParameter(pssspec);
+ } else if (padding == OpenVPNManagement.SignaturePadding.RSA_PKCS1_PADDING) {
+ sig = Signature.getInstance(hashalg + "withRSA");
+ }
+
+ sig.initSign(privkey);
+ sig.update(data);
+ return sig.sign();
+ }
+
+
private boolean usesExtraProxyOptions() {
if (mUseCustomConfig && mCustomConfigOptions != null && mCustomConfigOptions.contains("http-proxy-option "))
return true;
@@ -1222,6 +1341,16 @@ public class VpnProfile implements Serializable, Cloneable {
return false;
}
+ /**
+ * The order of elements is important!
+ */
+ private enum RsaPaddingType {
+ NO_PADDING,
+ PKCS1_PADDING,
+ RSAPSS_PADDING
+ }
+
+
class NoCertReturnedException extends Exception {
public NoCertReturnedException(String msg) {
super(msg);
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java b/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
index a62a4c62..d102dce2 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
@@ -108,15 +108,23 @@ public class ExtAuthHelper {
public static byte[] signData(@NonNull Context context,
@NonNull String extAuthPackageName,
@NonNull String alias,
- @NonNull byte[] data
+ @NonNull byte[] data,
+ @NonNull Bundle extra
) throws KeyChainException, InterruptedException
{
- try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) {
+ try (ExternalAuthProviderConnection authProviderConnection =
+ bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) {
ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService();
- return externalAuthProvider.getSignedData(alias, data);
+
+ byte[] result = externalAuthProvider.getSignedDataWithExtra(alias, data, extra);
+ // When the desired method is not implemented, a default implementation is called, returning null
+ if (result == null)
+ result = externalAuthProvider.getSignedData(alias, data);
+
+ return result;
} catch (RemoteException e) {
throw new KeyChainException(e);
diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
index f769b38e..818564c7 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
@@ -29,6 +29,21 @@ public class NativeUtils {
private static native String getJNIAPI();
+ static boolean rsspssloaded = false;
+
+ public static byte[] addRssPssPadding(int hashtype, int MSBits, int rsa_size, byte[] from)
+ {
+ if (!rsspssloaded) {
+ rsspssloaded = true;
+ System.loadLibrary("rsapss");
+ }
+
+ return rsapss(hashtype, MSBits, rsa_size, from);
+ }
+
+ private static native byte[] rsapss(int hashtype, int MSBits, int rsa_size, byte[] from);
+
+
public final static int[] openSSLlengths = {
16, 64, 256, 1024, 8 * 1024, 16 * 1024
};
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
index ef17e98b..02e4eca9 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java
@@ -16,6 +16,12 @@ public interface OpenVPNManagement {
screenOff,
}
+ enum SignaturePadding {
+ RSA_PKCS1_PSS_PADDING,
+ RSA_PKCS1_PADDING,
+ NO_PADDING
+ }
+
int mBytecountInterval = 2;
void reconnect();
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
index a02e7e27..88b933eb 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -194,7 +194,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
// Closing one of the two sockets also closes the other
//mServerSocketLocal.close();
- managmentCommand("version 2\n");
+ managmentCommand("version 3\n");
while (true) {
@@ -730,9 +730,33 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
releaseHold();
}
- private void processSignCommand(String b64data) {
+ private void processSignCommand(String argument) {
- String signed_string = mProfile.getSignedData(mOpenVPNService, b64data, false);
+ String[] arguments = argument.split(",");
+
+ // NC9t8IkYrjAQcCzc85zN0H5TvwfAUDwYkR4j2ga6fGw=,RSA_PKCS1_PSS_PADDING,hashalg=SHA256,saltlen=digest
+
+
+ SignaturePadding padding = SignaturePadding.NO_PADDING;
+ String saltlen="";
+ String hashalg="";
+ boolean needsDigest = false;
+
+ for (int i=1;i < arguments.length;i++) {
+ String arg = arguments[i];
+ if(arg.equals("RSA_PKCS1_PADDING"))
+ padding = SignaturePadding.RSA_PKCS1_PADDING;
+ else if (arg.equals("RSA_PKCS1_PSS_PADDING"))
+ padding = SignaturePadding.RSA_PKCS1_PSS_PADDING;
+ else if (arg.startsWith("saltlen="))
+ saltlen= arg.substring(8);
+ else if (arg.startsWith("hashalg="))
+ hashalg = arg.substring(8);
+ else if (arg.equals("data=message"))
+ needsDigest = true;
+ }
+
+ String signed_string = mProfile.getSignedData(mOpenVPNService, arguments[0], padding, saltlen, hashalg, needsDigest);
if (signed_string == null) {
managmentCommand("pk-sig\n");