summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java27
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java2
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java37
3 files changed, 58 insertions, 8 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java
index 32eabadf..e3175010 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java
@@ -5,18 +5,18 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.io.UnsupportedEncodingException;
+import java.net.IDN;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Locale;
public class Introducer implements Parcelable {
- private String type;
- private String address;
- private String certificate;
- private String fullyQualifiedDomainName;
- private boolean kcpEnabled;
-
- private String auth;
+ private final String type;
+ private final String address;
+ private final String certificate;
+ private final String fullyQualifiedDomainName;
+ private final boolean kcpEnabled;
+ private final String auth;
public Introducer(String type, String address, String certificate, String fullyQualifiedDomainName, boolean kcpEnabled, String auth) {
this.type = type;
@@ -94,6 +94,10 @@ public class Introducer implements Parcelable {
throw new IllegalArgumentException("FQDN not found in the introducer URL");
}
+ if (!isAscii(fqdn)) {
+ throw new IllegalArgumentException("FQDN is not ASCII: " + fqdn);
+ }
+
boolean kcp = "1".equals(uri.getQueryParameter( "kcp"));
String cert = uri.getQueryParameter( "cert");
@@ -112,6 +116,15 @@ public class Introducer implements Parcelable {
return auth;
}
+ private static boolean isAscii(String fqdn) {
+ try {
+ String asciiFQDN = IDN.toASCII(fqdn, IDN.USE_STD3_ASCII_RULES);
+ return fqdn.equals(asciiFQDN);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
public String toUrl() throws UnsupportedEncodingException {
return String.format(Locale.US, "%s://%s?fqdn=%s&kcp=%d&cert=%s&auth=%s", type, address, URLEncoder.encode(fullyQualifiedDomainName, "UTF-8"), kcpEnabled ? 1 : 0, URLEncoder.encode(certificate, "UTF-8"), URLEncoder.encode(auth, "UTF-8"));
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
index 76795616..b4ec23e6 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
@@ -983,7 +983,7 @@ public final class Provider implements Parcelable {
return introducer;
}
- public void setIntroducer(String introducerUrl) throws URISyntaxException {
+ public void setIntroducer(String introducerUrl) throws URISyntaxException, IllegalArgumentException {
this.introducer = Introducer.fromUrl(introducerUrl);
}
diff --git a/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java b/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java
index 01989cf9..b53f06fe 100644
--- a/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java
@@ -1,6 +1,7 @@
package se.leap.bitmaskclient.base.models;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import android.net.Uri;
import android.os.Build;
@@ -43,4 +44,40 @@ public class IntroducerTest {
throw new RuntimeException(e);
}
}
+
+ @Test
+ public void testFromUrl_homograph_attack() {
+ String code = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmasк.net&kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ }
+
+ @Test
+ public void testFromUrl_invalid_fqdn() {
+ String code = "obfsvpnintro://37.2.240.90:443?fqdn=file://var/wwww&kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ }
+
+ @Test
+ public void testFromUrl_missing_fqdn() {
+ String code = "obfsvpnintro://37.2.240.90:443?fqdn=&kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+
+ String code2 = "obfsvpnintro://37.2.240.90:443?kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code2));
+ }
+
+ @Test
+ public void testFromUrl_missing_cert() {
+ String code = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmask.net&kcp=0&cert=&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ }
+
+ @Test
+ public void testFromUrl_missing_auth() {
+ String code = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmask.net&kcp=0&cert=XXXXXXX&auth=";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+
+ String code2 = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmask.net&kcp=0&cert=XXXXXXX";
+ assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code2));
+ }
}