diff options
author | Arne Schwabe <arne@rfc2549.org> | 2021-01-21 12:24:06 +0100 |
---|---|---|
committer | Arne Schwabe <arne@rfc2549.org> | 2021-02-16 16:40:04 +0100 |
commit | 96db8d0bf1110f029cf5ad90bc939cd444100600 (patch) | |
tree | b2d7915f465bdeee6847e171e72a5c28e7bd9b72 /main/src | |
parent | a965f961b5dc5cba44d9a81a538c11c4394de24e (diff) |
Implement UI support for --peer-fingerprint
Diffstat (limited to 'main/src')
6 files changed, 108 insertions, 7 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index 2ccff18b..b32d17ad 100644 --- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -98,7 +98,7 @@ public class VpnProfile implements Serializable, Cloneable { public String mTLSAuthFilename; public String mClientKeyFilename; public String mCaFilename; - public boolean mUseLzo = true; + public boolean mUseLzo = false; public String mPKCS12Filename; public String mPKCS12Password; public boolean mUseTLSAuth = false; @@ -167,6 +167,8 @@ public class VpnProfile implements Serializable, Cloneable { public String mDataCiphers = ""; public boolean mBlockUnusedAddressFamilies =true; + public boolean mCheckPeerFingerprint = false; + public String mPeerFingerPrints = ""; public VpnProfile(String name) { mUuid = UUID.randomUUID(); @@ -436,7 +438,9 @@ public class VpnProfile implements Serializable, Cloneable { cfg.append("auth-user-pass\n"); case VpnProfile.TYPE_CERTIFICATES: // Ca - cfg.append(insertFileData("ca", mCaFilename)); + if (!TextUtils.isEmpty(mCaFilename)) { + cfg.append(insertFileData("ca", mCaFilename)); + } // Client Cert + Key cfg.append(insertFileData("key", mClientKeyFilename)); @@ -462,7 +466,12 @@ public class VpnProfile implements Serializable, Cloneable { String[] ks = getExternalCertificates(context); cfg.append("### From Keystore/ext auth app ####\n"); if (ks != null) { - cfg.append("<ca>\n").append(ks[0]).append("\n</ca>\n"); + if (!TextUtils.isEmpty(mCaFilename)) { + cfg.append(insertFileData("ca", mCaFilename)); + } + else if (!TextUtils.isEmpty(ks[0])) { + cfg.append("<ca>\n").append(ks[0]).append("\n</ca>\n"); + } 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"); @@ -484,6 +493,11 @@ public class VpnProfile implements Serializable, Cloneable { } } + if (mCheckPeerFingerprint) + { + cfg.append("<peer-fingerprint>\n").append(mPeerFingerPrints).append("\n</peer-fingerprint>\n"); + } + if (isUserPWAuth()) { if (mAuthRetry == AUTH_RETRY_NOINTERACT) cfg.append("auth-retry nointeract\n"); @@ -963,7 +977,7 @@ public class VpnProfile implements Serializable, Cloneable { if (mAlias == null) return R.string.no_keystore_cert_selected; } else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { - if (TextUtils.isEmpty(mCaFilename)) + if (TextUtils.isEmpty(mCaFilename) && !mCheckPeerFingerprint) return R.string.no_ca_cert_selected; } @@ -1259,7 +1273,7 @@ public class VpnProfile implements Serializable, Cloneable { return false; } - class NoCertReturnedException extends Exception { + static class NoCertReturnedException extends Exception { public NoCertReturnedException(String msg) { super(msg); } diff --git a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 6e4d8151..d25c20c0 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -568,6 +568,20 @@ public class ConfigParser { np.mCaFilename = ca.get(1); } + Vector<Vector<String>> peerfp = getAllOption("peer-fingerprint", 1, 1); + if (peerfp != null) + { + np.mCheckPeerFingerprint = true; + for (Vector<String> fp: peerfp) + { + if (fp.get(1).startsWith(VpnProfile.INLINE_TAG)) + np.mPeerFingerPrints+=fp.get(1).substring(VpnProfile.INLINE_TAG.length()) + "\n"; + else + np.mPeerFingerPrints+=fp.get(1) + "\n"; + } + } + + Vector<String> cert = getOption("cert", 1, 1); if (cert != null) { np.mClientCertFilename = cert.get(1); diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 4aa83ee9..fc4f5a74 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -42,7 +42,7 @@ <string name="duplicate_profile_name">Please enter a unique Profile Name</string> <string name="profilename">Profile Name</string> <string name="no_keystore_cert_selected">You must select a User certificate</string> - <string name="no_ca_cert_selected">You must select a CA certificate</string> + <string name="no_ca_cert_selected">You must select a CA certificate or enable peer fingerprint check</string> <string name="no_error_found">No error found</string> <string name="config_error_found">Error in Configuration</string> <string name="ipv4_format_error">Error parsing the IPv4 address</string> @@ -500,5 +500,7 @@ <string name="internal_web_view">Internal WebView</string> <string name="faq_title_ncp">Failed to negotiate cipher with server</string> <string name="faq_ncp">There are some variation of this message depending on the exact situation. They all have in common that server and client could not agree on a common cipher. The main reasons are: <ul><li> You are still relying on the fact that OpenVPN 2.4 and older allowed BF-CBC in the default configuration (if no --cipher was set). OpenVPN 2.5 does not allow it per default anymore since it is a <a href="https://community.openvpn.net/openvpn/wiki/SWEET32">broken/outdated cipher</a>.</li><li>The server runs OpenVPN 2.3 (or even older) with --enable-small (at least 4-5 year old OpenVPN)</li><li>Broken configuration (e.g., mismatching data-ciphers on client and server)</li> <p> The <a href=\"https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/cipher-negotiation.rst\">OpenVPN manual section on cipher negotiation</a> explains the different scenarios of cipher negotiation very well and what to do in these situation.<p>TP-Link devices use a at least 5 year old OpenVPN 2.3.x version (possibly older) on their devices, even in the 2019/2020 models.<p>Last but not least, there is a popular VPN provider that has a broken server that always says it is using \'BF-CBC\' because its developer thought it would be a good idea to create a proprietary cipher negotiation patch that is incompatible with standard OpenVPN.<p>In summary: all sane configurations should not get these errors. But (apart from the broken VPN provider\'s server) the client can be persuaded to still connect (fixing the sympton and not the real problem).</string> + <string name="check_peer_fingerprint">Check peer certificate fingerprint</string> + <string name="fingerprint">(Enter the SHA256 fingerprint of the server certificate(s))</string> </resources> diff --git a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt index 2983982d..fbaa4be2 100644 --- a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt +++ b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt @@ -348,4 +348,44 @@ verify-x509-name homevpn.evil.cloud name Assert.assertFalse(config.contains("key-direction")) } + @Test + @Throws(IOException::class, ConfigParser.ConfigParseError::class) + fun testPeerFingerprint() { + val conf = """ +<cert> +dummy +</cert> +cipher AES-256-GCM +client +dev-type tun +<key> +dummykey +</key> +remote home.evil.cloud 65443 udp +"""; + val fps = """ + 28:45:c7:ad:6a:c4:83:c7:a0:0a:0a:91:4b:43:e3:09:79:05:a2:ce:c2:e2:5e:c9:70:5a:2b:a4:e1:0f:97:e3 + F8:FA:6D:CF:58:65:98:5F:E0:E7:2A:B4:25:ED:2C:DD:45:7B:21:C1:B7:46:1D:46:C3:2B:1D:1D:F7:0E:43:51 + ef:5c:fc:a4:d5:59:78:14:e0:87:66:0b:53:df:e5:1e:a1:39:e0:1f:7a:ca:ca:87:4e:78:8b:45:c7:3d:af:c7 + """.trimIndent() + val fpBlock = "<peer-fingerprint>\n${fps}\n</peer-fingerprint>" + + val fpSingle = "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" + val fpSingleCmd = "peer-fingerprint ${fpSingle}\n" + + val cp = ConfigParser() + cp.parseConfig(StringReader(conf + fpBlock)) + val vp = cp.convertProfile() + + Assert.assertTrue(vp.mCheckPeerFingerprint) + Assert.assertEquals(fps.trim(), vp.mPeerFingerPrints.trim()) + + cp.parseConfig(StringReader(conf + fpBlock + "\n" + fpSingleCmd)) + val vp2 = cp.convertProfile() + Assert.assertTrue(vp2.mCheckPeerFingerprint) + Assert.assertEquals((fps + "\n" + fpSingle).trim(), vp2.mPeerFingerPrints.trim()) + + + } + } diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java index 294b33fb..0afb754d 100644 --- a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java +++ b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java @@ -20,7 +20,7 @@ import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.views.FileSelectLayout; -public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback { +public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback, CompoundButton.OnCheckedChangeListener { private static final int CHOOSE_FILE_OFFSET = 1000; private FileSelectLayout mClientCert; @@ -36,6 +36,8 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe private View mView; private EditText mProfileName; private EditText mKeyPassword; + private CheckBox mEnablePeerFingerprint; + private EditText mPeerFingerprints; private SparseArray<FileSelectLayout> fileselects = new SparseArray<>(); private Spinner mAuthRetry; @@ -68,6 +70,8 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mUseLzo = mView.findViewById(id.lzo); mType = mView.findViewById(id.type); mPKCS12Password = mView.findViewById(id.pkcs12password); + mEnablePeerFingerprint = mView.findViewById(id.enable_peer_fingerprint); + mPeerFingerprints = mView.findViewById(id.peer_fingerprint); mUserName = mView.findViewById(id.auth_username); mPassword = mView.findViewById(id.auth_password); @@ -84,6 +88,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mType.setOnItemSelectedListener(this); mAuthRetry.setOnItemSelectedListener(this); + mEnablePeerFingerprint.setOnCheckedChangeListener(this); initKeychainViews(mView); @@ -192,6 +197,8 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mPassword.setText(mProfile.mPassword); mKeyPassword.setText(mProfile.mKeyPassword); mAuthRetry.setSelection(mProfile.mAuthRetry); + mEnablePeerFingerprint.setChecked(mProfile.mCheckPeerFingerprint); + mPeerFingerprints.setText(mProfile.mPeerFingerPrints); } protected void savePreferences() { @@ -211,6 +218,8 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe mProfile.mUsername = mUserName.getText().toString(); mProfile.mKeyPassword = mKeyPassword.getText().toString(); mProfile.mAuthRetry = mAuthRetry.getSelectedItemPosition(); + mProfile.mCheckPeerFingerprint = mEnablePeerFingerprint.isChecked(); + mProfile.mPeerFingerPrints = mPeerFingerprints.getText().toString(); } @@ -228,4 +237,12 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe } + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView == mEnablePeerFingerprint) + { + mPeerFingerprints.setVisibility(isChecked ? View.VISIBLE : View.GONE); + + } + } } diff --git a/main/src/ui/res/layout/basic_settings.xml b/main/src/ui/res/layout/basic_settings.xml index 66f8b1fa..6763e52f 100644 --- a/main/src/ui/res/layout/basic_settings.xml +++ b/main/src/ui/res/layout/basic_settings.xml @@ -88,6 +88,20 @@ blinkt:showClear="true" blinkt:fileTitle="@string/ca_title" /> + <CheckBox + android:id="@+id/enable_peer_fingerprint" + style="@style/item" + android:text="@string/check_peer_fingerprint" + /> + + <EditText + android:id="@+id/peer_fingerprint" + style="@style/item" + android:inputType="textMultiLine" + android:layout_marginLeft="8dip" + android:layout_marginRight="8dip" + android:hint="@string/fingerprint" + android:visibility="gone" /> </LinearLayout> <LinearLayout |