summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2024-12-07 03:09:01 +0100
committercyberta <cyberta@riseup.net>2024-12-11 00:09:34 +0000
commit3bcba785be239093ee469fd99efb197ca1d1f246 (patch)
treeb05ffe4eb72afceca8d6651a3ff91399332a1ed1 /app
parentbf75f3824596f53f0c6e9a2cfb3629da905b59d1 (diff)
Fix support for ed25519 private VPN keys, add signing capabilities for ed25519 in VpnProfile, so that such a key can be passed with OpenVPNs management-external-key option on runtime
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle1
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java17
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java5
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java11
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelper.java62
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java15
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelperTest.java13
-rw-r--r--app/src/test/resources/private_PKCS8_encoded_ecdsa_key.pem8
-rw-r--r--app/src/test/resources/private_PKCS8_encoded_ed25519_key.pem (renamed from app/src/test/resources/private_ed25519_key.pem)0
9 files changed, 88 insertions, 44 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 5a3f0551..f2d3b93c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -416,6 +416,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.10.0'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
+ implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'androidx.legacy:legacy-support-core-utils:1.0.0'
implementation 'androidx.annotation:annotation:1.7.0'
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 511893d7..9e71939b 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -43,6 +43,7 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
@@ -475,7 +476,11 @@ public class VpnProfile implements Serializable, Cloneable {
// Client Cert + Key
cfg.append(insertFileData("cert", mClientCertFilename));
mPrivateKey = ProviderObservable.getInstance().getCurrentProvider().getPrivateKey();
- cfg.append("management-external-key nopadding pkcs1 pss digest\n");
+ if (mPrivateKey.getAlgorithm().equalsIgnoreCase("RSA")) {
+ cfg.append("management-external-key nopadding pkcs1 pss digest\n");
+ } else {
+ cfg.append("management-external-key\n");
+ }
break;
case VpnProfile.TYPE_USERPASS_PKCS12:
@@ -1280,7 +1285,9 @@ public class VpnProfile implements Serializable, Cloneable {
return signed_bytes;
}
} catch
- (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | SignatureException | InvalidAlgorithmParameterException
+ (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException |
+ BadPaddingException | NoSuchPaddingException | SignatureException |
+ InvalidAlgorithmParameterException | NoSuchProviderException
e) {
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
return null;
@@ -1326,11 +1333,13 @@ public class VpnProfile implements Serializable, Cloneable {
return hashtype;
}
- private byte[] doDigestSign(PrivateKey privkey, byte[] data, OpenVPNManagement.SignaturePadding padding, String hashalg, String saltlen) throws SignatureException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
+ private byte[] doDigestSign(PrivateKey privkey, byte[] data, OpenVPNManagement.SignaturePadding padding, String hashalg, String saltlen) throws SignatureException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchProviderException {
/* RSA */
Signature sig = null;
- if (privkey.getAlgorithm().equals("EC")) {
+ if (privkey.getAlgorithm().equals("Ed25519")) {
+ sig = Signature.getInstance("Ed25519", "BC");
+ } else if (privkey.getAlgorithm().equals("EC")) {
if (hashalg.equals(""))
hashalg = "NONE";
/* e.g. SHA512withECDSA */
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 88b933eb..a4b5e3be 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -272,12 +272,13 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
}
private void processCommand(String command) {
- //Log.i(TAG, "Line from managment" + command);
+ Log.i(TAG, "Line from managment " + command);
if (command.startsWith(">") && command.contains(":")) {
String[] parts = command.split(":", 2);
String cmd = parts[0].substring(1);
String argument = parts[1];
+ Log.d(">>>>", "CMD: "+ cmd + "argument: " + argument);
switch (cmd) {
@@ -735,7 +736,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
String[] arguments = argument.split(",");
// NC9t8IkYrjAQcCzc85zN0H5TvwfAUDwYkR4j2ga6fGw=,RSA_PKCS1_PSS_PADDING,hashalg=SHA256,saltlen=digest
-
+ // ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRMUyAxLjMsIGNsaWVudCBIoXJ0aWZpY2F0ZVZlcmlmeQCvvTk69HvSHUhM27ghCCSgzHds1Bdsm4MyVGxlgDIJbnDj+G5Y1YxXajqy6E/G1GA=,ED25519,data=message
SignaturePadding padding = SignaturePadding.NO_PADDING;
String saltlen="";
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java
index c7e12491..6b3ba348 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java
@@ -35,8 +35,10 @@ import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.multidex.MultiDexApplication;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.conscrypt.Conscrypt;
+import java.security.Provider;
import java.security.Security;
import se.leap.bitmaskclient.BuildConfig;
@@ -70,7 +72,14 @@ public class BitmaskApp extends MultiDexApplication implements DefaultLifecycleO
super.onCreate();
// Normal app init code...*/
PRNGFixes.apply();
- Security.insertProviderAt(Conscrypt.newProvider(), 1);
+ final Provider provider = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
+ // Replace Android's own BC provider
+ if (!provider.getClass().equals(BouncyCastleProvider.class)) {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ Security.insertProviderAt(new BouncyCastleProvider(), 1);
+ }
+ Security.insertProviderAt(Conscrypt.newProvider(), 2);
+
preferenceHelper = new PreferenceHelper(this);
providerObservable = ProviderObservable.getInstance();
providerObservable.updateProvider(getSavedProviderFromSharedPreferences());
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelper.java
index eb4d6956..43af5200 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelper.java
@@ -1,6 +1,8 @@
package se.leap.bitmaskclient.base.utils;
-import android.os.Build;
+import static android.util.Base64.encodeToString;
+
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -11,7 +13,6 @@ import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
-import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
@@ -20,13 +21,16 @@ import de.blinkt.openvpn.core.NativeUtils;
public class PrivateKeyHelper {
+ public static final String TAG = PrivateKeyHelper.class.getSimpleName();
+
public static final String RSA = "RSA";
public static final String ED_25519 = "Ed25519";
+ public static final String ECDSA = "ECDSA";
public static final String RSA_KEY_BEGIN = "-----BEGIN RSA PRIVATE KEY-----\n";
public static final String RSA_KEY_END = "-----END RSA PRIVATE KEY-----";
- public static final String ED_25519_KEY_BEGIN = "-----BEGIN PRIVATE KEY-----\n";
- public static final String ED_25519_KEY_END = "-----END PRIVATE KEY-----";
+ public static final String EC_KEY_BEGIN = "-----BEGIN PRIVATE KEY-----\n";
+ public static final String EC_KEY_END = "-----END PRIVATE KEY-----";
public interface PrivateKeyHelperInterface {
@@ -43,7 +47,7 @@ public class PrivateKeyHelper {
}
if (privateKeyString.contains(RSA_KEY_BEGIN)) {
return parseRsaKeyFromString(privateKeyString);
- } else if (privateKeyString.contains(ED_25519_KEY_BEGIN)) {
+ } else if (privateKeyString.contains(EC_KEY_BEGIN)) {
return parseECPrivateKey(privateKeyString);
} else {
return null;
@@ -54,11 +58,7 @@ public class PrivateKeyHelper {
RSAPrivateKey key;
try {
KeyFactory kf;
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- kf = KeyFactory.getInstance(RSA, "BC");
- } else {
- kf = KeyFactory.getInstance(RSA);
- }
+ kf = KeyFactory.getInstance(RSA, "BC");
rsaKeyString = rsaKeyString.replaceFirst(RSA_KEY_BEGIN, "").replaceFirst(RSA_KEY_END, "");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString));
@@ -73,20 +73,38 @@ public class PrivateKeyHelper {
}
private PrivateKey parseECPrivateKey(String ecKeyString) {
- KeyFactory kf;
+ String base64 = ecKeyString.replace(EC_KEY_BEGIN, "").replace(EC_KEY_END, "");
+ byte[] keyBytes = Base64.decode(base64);
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+ String errMsg;
try {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- kf = KeyFactory.getInstance(ED_25519, "BC");
- } else {
- kf = KeyFactory.getInstance(ED_25519);
- }
- ecKeyString = ecKeyString.replaceFirst(ED_25519_KEY_BEGIN, "").replaceFirst(ED_25519_KEY_END, "");
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(ecKeyString));
- return kf.generatePrivate(keySpec);
- } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
- e.printStackTrace();
- return null;
+ KeyFactory keyFactory = KeyFactory.getInstance(ED_25519, "BC");
+ return keyFactory.generatePrivate(keySpec);
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
+ errMsg = e.toString();
+ }
+
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, "BC");
+ return keyFactory.generatePrivate(keySpec);
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
+ errMsg += "\n" + e.toString();
+ Log.e(TAG, errMsg);
}
+ return null;
+ }
+ }
+
+ public static String getPEMFormattedPrivateKey(PrivateKey key) throws NullPointerException {
+ if (key == null) {
+ throw new NullPointerException("Private key was null.");
+ }
+ String keyString = encodeToString(key.getEncoded(), android.util.Base64.DEFAULT);
+
+ if (key instanceof RSAPrivateKey) {
+ return (RSA_KEY_BEGIN + keyString + RSA_KEY_END);
+ } else {
+ return EC_KEY_BEGIN + keyString + EC_KEY_END;
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java
index b2c1aa10..965741f0 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java
@@ -39,10 +39,7 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICA
import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask;
import static se.leap.bitmaskclient.base.utils.CertificateHelper.getFingerprintFromCertificate;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString;
-import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.ED_25519_KEY_BEGIN;
-import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.ED_25519_KEY_END;
-import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.RSA_KEY_BEGIN;
-import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.RSA_KEY_END;
+import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.getPEMFormattedPrivateKey;
import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.parsePrivateKeyFromString;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_GEOIP_JSON;
@@ -99,10 +96,8 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.List;
-import java.util.StringTokenizer;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.SSLHandshakeException;
@@ -386,13 +381,7 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro
}
PrivateKey key = parsePrivateKeyFromString(keyString);
- keyString = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
-
- if (key instanceof RSAPrivateKey) {
- provider.setPrivateKeyString(RSA_KEY_BEGIN + keyString + RSA_KEY_END);
- } else {
- provider.setPrivateKeyString(ED_25519_KEY_BEGIN + keyString + ED_25519_KEY_END);
- }
+ provider.setPrivateKeyString(getPEMFormattedPrivateKey(key));
ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(certificateString);
certificates.get(0).checkValidity();
diff --git a/app/src/test/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelperTest.java
index 5ad9d2e7..5b1d4554 100644
--- a/app/src/test/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelperTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelperTest.java
@@ -12,13 +12,14 @@ import org.robolectric.annotation.Config;
import java.io.IOException;
import java.security.PrivateKey;
+import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.RSAPrivateKey;
import se.leap.bitmaskclient.testutils.TestSetupHelper;
@RunWith(RobolectricTestRunner.class)
-@Config(sdk = {Build.VERSION_CODES.P, Build.VERSION_CODES.O})
+@Config(sdk = {Build.VERSION_CODES.UPSIDE_DOWN_CAKE, Build.VERSION_CODES.P, Build.VERSION_CODES.O, Build.VERSION_CODES.N})
public class PrivateKeyHelperTest {
@Test
@@ -31,9 +32,17 @@ public class PrivateKeyHelperTest {
@Test
public void parsePrivateKeyFromString_testEd25519() throws IOException {
- String ed25519_key = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("private_ed25519_key.pem"));
+ String ed25519_key = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("private_PKCS8_encoded_ed25519_key.pem"));
PrivateKey pk = PrivateKeyHelper.parsePrivateKeyFromString(ed25519_key);
assertNotNull(pk);
assertTrue(pk instanceof EdECPrivateKey);
}
+
+ @Test
+ public void parsePrivateKeyFromString_testEcDSA() throws IOException {
+ String ed_dsa_key = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("private_PKCS8_encoded_ecdsa_key.pem"));
+ PrivateKey pk = PrivateKeyHelper.parsePrivateKeyFromString(ed_dsa_key);
+ assertNotNull(pk);
+ assertTrue(pk instanceof ECPrivateKey);
+ }
} \ No newline at end of file
diff --git a/app/src/test/resources/private_PKCS8_encoded_ecdsa_key.pem b/app/src/test/resources/private_PKCS8_encoded_ecdsa_key.pem
new file mode 100644
index 00000000..568783a1
--- /dev/null
+++ b/app/src/test/resources/private_PKCS8_encoded_ecdsa_key.pem
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAQHgofCij0Tdc8aO5
+lNnxhjXiU2Z+84/vz0RpCdoZt0H8ytLb5AOUOaPMu5gNGC2SssTkJhGc/dDX7jdw
+8/GEQQ2hgYkDgYYABABnVEIseHS5WQ/8J3x//uTaU9G5d3HR/dOo+RI7PLizxj8p
+pLKptfPDLTWHMujqE5yPe4GYnU2S1KMws853VBTucwF4AVz1sxYMEpFcYUys+Xr1
+JyTDsxA/o4yeV4ZcuqaofNFYUL6YRFjXg7UAlUPp0s6ongQzJ0/10wGDbUI7vR0I
+Lg==
+-----END PRIVATE KEY----- \ No newline at end of file
diff --git a/app/src/test/resources/private_ed25519_key.pem b/app/src/test/resources/private_PKCS8_encoded_ed25519_key.pem
index eac4d4db..eac4d4db 100644
--- a/app/src/test/resources/private_ed25519_key.pem
+++ b/app/src/test/resources/private_PKCS8_encoded_ed25519_key.pem