From 0869f9a016d890511e14392deb99c791167208e9 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 7 Sep 2020 18:52:33 +0900 Subject: [PATCH] Use lots of regex to add http or https for XMR explorer API endpoint * If Tor *.onion hostname, use HTTP with Tor proxy * If 127.0.0.1 or localhost, use HTTP without Tor proxy * If LAN address or *.local FQDN, use HTTP without Tor proxy * If any other FQDN hostname, use HTTPS with Tor proxy --- .../trade/txproof/xmr/XmrTxProofRequest.java | 15 +- .../settings/preferences/PreferencesView.java | 38 +++- .../main/java/bisq/desktop/util/GUIUtil.java | 121 ++++++++++ .../java/bisq/desktop/util/GUIUtilTest.java | 214 ++++++++++++++++++ 4 files changed, 381 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index 325538acb67..eb62b09468f 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -163,10 +163,19 @@ public String toString() { this.model = model; httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); - httpClient.setBaseUrl("http://" + model.getServiceAddress()); - if (model.getServiceAddress().matches("^192.*|^localhost.*")) { - log.info("Ignoring Socks5 proxy for local net address: {}", model.getServiceAddress()); + + // localhost, LAN address, or *.local FQDN starts with http://, don't use Tor + if (model.getServiceAddress().regionMatches(0, "http:", 0, 5)) { + httpClient.setBaseUrl(model.getServiceAddress()); httpClient.setIgnoreSocks5Proxy(true); + // any non-onion FQDN starts with https://, use Tor + } else if (model.getServiceAddress().regionMatches(0, "https:", 0, 6)) { + httpClient.setBaseUrl(model.getServiceAddress()); + httpClient.setIgnoreSocks5Proxy(false); + // it's a raw onion so add http:// and use Tor proxy + } else { + httpClient.setBaseUrl("http://" + model.getServiceAddress()); + httpClient.setIgnoreSocks5Proxy(false); } terminated = false; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index e521dc12526..f9c803be1e9 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -95,6 +95,7 @@ import java.io.File; import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -672,12 +673,41 @@ private void initializeAutoConfirmOptions() { autoConfServiceAddressListener = (observable, oldValue, newValue) -> { if (!newValue.equals(oldValue)) { - List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); + + RegexValidator onionRegex = GUIUtil.onionAddressRegexValidator(); + RegexValidator localhostRegex = GUIUtil.localhostAddressRegexValidator(); + RegexValidator localnetRegex = GUIUtil.localnetAddressRegexValidator(); + + List serviceAddressesRaw = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); + // revert to default service providers when user empties the list - if (serviceAddresses.size() == 1 && serviceAddresses.get(0).isEmpty()) { - serviceAddresses = preferences.getDefaultXmrTxProofServices(); + if (serviceAddressesRaw.size() == 1 && serviceAddressesRaw.get(0).isEmpty()) { + serviceAddressesRaw = preferences.getDefaultXmrTxProofServices(); } - preferences.setAutoConfServiceAddresses("XMR", serviceAddresses); + + // we must always communicate with XMR explorer API securely + // if *.onion hostname, we use Tor normally + // if localhost, LAN address, or *.local FQDN we use HTTP without Tor + // otherwise we enforce https:// for any clearnet FQDN hostname + List serviceAddressesParsed = new ArrayList(); + serviceAddressesRaw.forEach((addr) -> { + addr = addr.replaceAll("http://", "").replaceAll("https://", ""); + if (onionRegex.validate(addr).isValid) { + log.info("Using Tor for onion hostname: {}", addr); + serviceAddressesParsed.add(addr); + } else if (localhostRegex.validate(addr).isValid) { + log.info("Using HTTP without Tor for Loopback address: {}", addr); + serviceAddressesParsed.add("http://" + addr); + } else if (localnetRegex.validate(addr).isValid) { + log.info("Using HTTP without Tor for LAN address: {}", addr); + serviceAddressesParsed.add("http://" + addr); + } else { + log.info("Using HTTPS with Tor for Clearnet address: {}", addr); + serviceAddressesParsed.add("https://" + addr); + } + }); + + preferences.setAutoConfServiceAddresses("XMR", serviceAddressesParsed); } }; diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 375347939a2..5d4d4406ae4 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -1161,6 +1161,127 @@ public static RegexValidator addressRegexValidator() { return regexValidator; } + // checks if valid tor onion hostname with optional port at the end + public static RegexValidator onionAddressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + String onionV2RegexPattern = String.format("[a-zA-Z2-7]{16}\\.onion(?:\\:%1$s)?", portRegexPattern); + String onionV3RegexPattern = String.format("[a-zA-Z2-7]{56}\\.onion(?:\\:%1$s)?", portRegexPattern); + regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)),\\s*)*(?:(?:%1$s)|(?:%2$s))*$", + onionV2RegexPattern, onionV3RegexPattern)); + return regexValidator; + } + + // checks if localhost address, with optional port at the end + public static RegexValidator localhostAddressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + + // match 0 ~ 65535 + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + + // match 127/8 (127.0.0.0 ~ 127.255.255.255) + String localhostIpv4RegexPattern = String.format( + "(?:127\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){2}" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match ::/64 with optional port at the end, i.e. ::1 or [::1]:8081 + String localhostIpv6RegexPattern = "(:((:[0-9a-fA-F]{1,4}){1,4}|:)|)"; + localhostIpv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", localhostIpv6RegexPattern, portRegexPattern); + + // match *.local + String localhostFqdnRegexPattern = String.format("(localhost(?:\\:%1$s)?)", portRegexPattern); + + regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)|(?:%3$s)),\\s*)*(?:(?:%1$s)|(?:%2$s)|(?:%3$s))*$", + localhostIpv4RegexPattern, localhostIpv6RegexPattern, localhostFqdnRegexPattern)); + + return regexValidator; + } + + // checks if local area network address, with optional port at the end + public static RegexValidator localnetAddressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + + // match 0 ~ 65535 + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + + // match 10/8 (10.0.0.0 ~ 10.255.255.255) + String localnetIpv4RegexPatternA = String.format( + "(?:10\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){2}" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match 172.16/12 (172.16.0.0 ~ 172.31.255.255) + String localnetIpv4RegexPatternB = String.format( + "(?:172\\.)" + + "(?:(?:1[6-9]|2[0-9]|[3][0-1])\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match 192.168/16 (192.168.0.0 ~ 192.168.255.255) + String localnetIpv4RegexPatternC = String.format( + "(?:192\\.)" + + "(?:168\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match 169.254/15 (169.254.0.0 ~ 169.255.255.255) + String autolocalIpv4RegexPattern = String.format( + "(?:169\\.)" + + "(?:(?:254|255)\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match fc00::/7 (fc00:: ~ fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff) + String localnetIpv6RegexPattern = "(" + + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){6}[0-9a-fA-F]{1,4}|" + // fd00:2:3:4:5:6:7:8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,7}:|" + // fd00:: fd00:2:3:4:5:6:7:: + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,6}:[0-9a-fA-F]{1,4}|" + // fd00::8 fd00:2:3:4:5:6::8 fd00:2:3:4:5:6::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,5}(:[0-9a-fA-F]{1,4}){1,1}|" + // fd00::7:8 fd00:2:3:4:5::7:8 fd00:2:3:4:5::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,4}(:[0-9a-fA-F]{1,4}){1,2}|" + // fd00::7:8 fd00:2:3:4:5::7:8 fd00:2:3:4:5::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,3}(:[0-9a-fA-F]{1,4}){1,3}|" + // fd00::6:7:8 fd00:2:3:4::6:7:8 fd00:2:3:4::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,2}(:[0-9a-fA-F]{1,4}){1,4}|" + // fd00::5:6:7:8 fd00:2:3::5:6:7:8 fd00:2:3::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,1}(:[0-9a-fA-F]{1,4}){1,5}|" + // fd00::4:5:6:7:8 fd00:2::4:5:6:7:8 fd00:2::8 + "([fF][cCdD][0-9a-fA-F]{2}:)(:[0-9a-fA-F]{1,4}){1,6}" + // fd00::3:4:5:6:7:8 fd00::3:4:5:6:7:8 fd00::8 + ")"; + + // match fe80::/10 (fe80:: ~ febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff) + String autolocalIpv6RegexPattern = "("+ + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){6}[0-9a-fA-F]{1,4}|" + // fe80:2:3:4:5:6:7:8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,7}:|" + // fe80:: fe80:2:3:4:5:6:7:: + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,6}:[0-9a-fA-F]{1,4}|" + // fe80::8 fe80:2:3:4:5:6::8 fe80:2:3:4:5:6::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,5}(:[0-9a-fA-F]{1,4}){1,1}|" + // fe80::7:8 fe80:2:3:4:5::7:8 fe80:2:3:4:5::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,4}(:[0-9a-fA-F]{1,4}){1,2}|" + // fe80::7:8 fe80:2:3:4:5::7:8 fe80:2:3:4:5::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,3}(:[0-9a-fA-F]{1,4}){1,3}|" + // fe80::6:7:8 fe80:2:3:4::6:7:8 fe80:2:3:4::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,2}(:[0-9a-fA-F]{1,4}){1,4}|" + // fe80::5:6:7:8 fe80:2:3::5:6:7:8 fe80:2:3::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,1}(:[0-9a-fA-F]{1,4}){1,5}|" + // fe80::4:5:6:7:8 fe80:2::4:5:6:7:8 fe80:2::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)(:[0-9a-fA-F]{1,4}){1,6}" + // fe80::3:4:5:6:7:8 fe80::3:4:5:6:7:8 fe80::8 + ")"; + + // allow for brackets with optional port at the end + localnetIpv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", localnetIpv6RegexPattern, portRegexPattern); + + // allow for brackets with optional port at the end + autolocalIpv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", autolocalIpv6RegexPattern, portRegexPattern); + + // match *.local + String localFqdnRegexPattern = String.format("(((?!-)[a-zA-Z0-9-]{1,63}(?