diff options
author | cyBerta <cyberta@riseup.net> | 2024-12-07 03:09:01 +0100 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2024-12-11 00:09:34 +0000 |
commit | 3bcba785be239093ee469fd99efb197ca1d1f246 (patch) | |
tree | b05ffe4eb72afceca8d6651a3ff91399332a1ed1 /app | |
parent | bf75f3824596f53f0c6e9a2cfb3629da905b59d1 (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.gradle | 1 | ||||
-rw-r--r-- | app/src/main/java/de/blinkt/openvpn/VpnProfile.java | 17 | ||||
-rw-r--r-- | app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java | 5 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java | 11 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelper.java | 62 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java | 15 | ||||
-rw-r--r-- | app/src/test/java/se/leap/bitmaskclient/base/utils/PrivateKeyHelperTest.java | 13 | ||||
-rw-r--r-- | app/src/test/resources/private_PKCS8_encoded_ecdsa_key.pem | 8 | ||||
-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 |