diff options
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)); + } } |