summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2023-07-19 10:23:01 +0200
committercyBerta <cyberta@riseup.net>2023-07-19 10:23:01 +0200
commit33216d22493fa413996a49df2b1ab1def47f9fa0 (patch)
treee27233d61082a674a62ab339913c0c6780b94438 /app/src/main/java/de/blinkt/openvpn/VpnProfile.java
parent7e55cd7e6c93c0a6613cbf09036b0c6e559b5e8a (diff)
Update source code for external key managment based on ics-openvpn (some relevant commits: 5e7b841c8d5111e6b63e74944903a168939ca723 a6de5a9e4d8d757414c5e2f94eb806be9216dda3 9e704d04dc7f2f93bddf85d371772340fa5af0b1 4466103d770c353cfb8d4ea08093560ba28d58b8 b9ac2b15eac3e5e5f9dc89c948ec8278e2e7c1f9 3cb8f44a92471e43589a80067380d7b262c18c20)
Diffstat (limited to 'app/src/main/java/de/blinkt/openvpn/VpnProfile.java')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java187
1 files changed, 158 insertions, 29 deletions
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);