diff --git a/CHANGELOG b/CHANGELOG index 72fe85fb55ff57e2c5e2c167b597b9a026540d79..8e8b5dbec544b5d0edda5b44a0b840c4c030b519 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +1.5.0RC1 API-v5 beta +features: +- support new provider config API (beta) +- allow to setup provider using an invite code and via an obfuscated proxy +- new circumvention settings user interface +- Add QR code scanning for invite code +- in-app language picker +- update openvpn and openssl +- updated translations (<3<3<3 -> translators) +bugfix: +- fix support for ed25519 private VPN keys + 1.4.1 stable features: - update censorship circumvention library diff --git a/app/assets/fronts b/app/assets/fronts index 60c563337454fdd332dec8a0e1e0e77de9f62ae0..acd1cd94af720b5a2fdc53ef6896c2b5188dbf54 100644 --- a/app/assets/fronts +++ b/app/assets/fronts @@ -1,4 +1,4 @@ snowflake-target https://1098762253.rsc.cdn77.org snowflake-fronts www.cdn77.com,www.phpmyadmin.net -snowflake-stun stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 +snowflake-stun stun:stun.antisip.com:3478,stun:stun.epygi.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.mixvoip.com:3478,stun:stun.nextcloud.com:3478,stun:stun.bethesda.net:3478,stun:stun.nextcloud.com:443 utls-imitate hellorandomizedalpn \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 501292c5d8a3f79ba8f05b473f7614bd3b769359..ccd54dc94b455eb58e15069e45e5aafd7e3dad2d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,8 +32,8 @@ android { // the factor 1000 is used so that gplay users can upgrade from split apks ((current version number - 1) * 1000) + n // to extracted bundle apks, supplied by google // however we don't calculate the versionCode here, because F-Droid doesn't like that - versionCode 176000 - versionName "1.4.1" + versionCode 177000 + versionName "1.5.0RC1" compileSdk 34 minSdkVersion 21 targetSdkVersion 34 @@ -43,6 +43,9 @@ android { //Build Config Fields for default donation details + // preferred API version the client should client try in case the provider supports it + // If the provider doesn't the client falls back to lower available API versions + buildConfigField 'int', 'preferred_client_api_version', '5' //This is the default donation URL and should be set to the donation page of LEAP // and this should not be set/altered anywhere else. buildConfigField 'String', 'default_donation_url', '"https://riseuplabs.org/leap"' @@ -136,6 +139,10 @@ android { //Change the versionName as needed //versionName "0.9.9RC1" + + // preferred API version the client should client try in case the provider supports it + // If the provider doesn't the client falls back to lower available API versions + buildConfigField 'int', 'preferred_client_api_version', '5' //skip the account creation / login screen if the provider offers anonymous vpn usage, use directly the anonymous cert instead buildConfigField 'boolean', 'priotize_anonymous_usage', 'true' //allow manual gateway selection @@ -409,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' @@ -422,6 +430,11 @@ dependencies { implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' implementation 'androidx.lifecycle:lifecycle-process:2.7.0' implementation 'de.hdodenhof:circleimageview:3.1.0' + // swagger dependencies + implementation 'io.swagger:swagger-annotations:1.5.17' + implementation 'io.gsonfire:gson-fire:1.8.0' + implementation 'org.threeten:threetenbp:1.3.5' + // Start:: CameraX dependencies implementation 'com.google.mlkit:barcode-scanning:17.3.0' @@ -495,7 +508,7 @@ subprojects { afterEvaluate {project -> if (project.hasProperty("android")) { android { - compileSdkVersion 33 + compileSdkVersion 34 } } } diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 511893d71743b7801ef0d022df67eb216a4a0b6c..9e71939b222012e390176baeadfb021821248085 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 88b933eb8e8fd2922755fa0f777ef9410602cc8d..a4b5e3be71cab1684bc96a382869fdc9b5e589b4 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/io/swagger/client/JSON.java b/app/src/main/java/io/swagger/client/JSON.java new file mode 100644 index 0000000000000000000000000000000000000000..b1ca69a875764deda6c46dcfbfefaa54380ea4ed --- /dev/null +++ b/app/src/main/java/io/swagger/client/JSON.java @@ -0,0 +1,399 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonElement; +import io.gsonfire.GsonFireBuilder; +import io.gsonfire.TypeSelector; +import org.threeten.bp.LocalDate; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.format.DateTimeFormatter; + +import io.swagger.client.model.*; +import io.swagger.client.model.*; +import io.swagger.client.model.*; +import io.swagger.client.model.*; +import io.swagger.client.model.*; +import io.swagger.client.model.*; +import okio.ByteString; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Date; +import java.util.Map; +import java.util.HashMap; + +public class JSON { + private Gson gson; + private boolean isLenientOnJson = false; + private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); + private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); + private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + private ByteArrayAdapter byteArrayAdapter = new ByteArrayAdapter(); + + public static GsonBuilder createGson() { + GsonFireBuilder fireBuilder = new GsonFireBuilder() + ; + GsonBuilder builder = fireBuilder.createGsonBuilder(); + return builder; + } + + private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) { + JsonElement element = readElement.getAsJsonObject().get(discriminatorField); + if(null == element) { + throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">"); + } + return element.getAsString(); + } + + private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) { + Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue.toUpperCase()); + if(null == clazz) { + throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">"); + } + return clazz; + } + + public JSON() { + gson = createGson() + .registerTypeAdapter(Date.class, dateTypeAdapter) + .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter) + .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + .registerTypeAdapter(byte[].class, byteArrayAdapter) + .create(); + } + + /** + * Get Gson. + * + * @return Gson + */ + public Gson getGson() { + return gson; + } + + /** + * Set Gson. + * + * @param gson Gson + * @return JSON + */ + public JSON setGson(Gson gson) { + this.gson = gson; + return this; + } + + public JSON setLenientOnJson(boolean lenientOnJson) { + isLenientOnJson = lenientOnJson; + return this; + } + + /** + * Serialize the given Java object into JSON string. + * + * @param obj Object + * @return String representation of the JSON + */ + public String serialize(Object obj) { + return gson.toJson(obj); + } + + /** + * Deserialize the given JSON string to Java object. + * + * @param <T> Type + * @param body The JSON string + * @param returnType The type to deserialize into + * @return The deserialized Java object + */ + @SuppressWarnings("unchecked") + public <T> T deserialize(String body, Type returnType) { + try { + if (isLenientOnJson) { + JsonReader jsonReader = new JsonReader(new StringReader(body)); + // see https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/stream/JsonReader.html#setLenient(boolean) + jsonReader.setLenient(true); + return gson.fromJson(jsonReader, returnType); + } else { + return gson.fromJson(body, returnType); + } + } catch (JsonParseException e) { + // Fallback processing when failed to parse JSON form response body: + // return the response body string directly for the String return type; + if (returnType.equals(String.class)) + return (T) body; + else throw (e); + } + } + + /** + * Gson TypeAdapter for Byte Array type + */ + public class ByteArrayAdapter extends TypeAdapter<byte[]> { + + @Override + public void write(JsonWriter out, byte[] value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(ByteString.of(value).base64()); + } + } + + @Override + public byte[] read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String bytesAsBase64 = in.nextString(); + ByteString byteString = ByteString.decodeBase64(bytesAsBase64); + return byteString.toByteArray(); + } + } + } + + /** + * Gson TypeAdapter for JSR310 OffsetDateTime type + */ + public static class OffsetDateTimeTypeAdapter extends TypeAdapter<OffsetDateTime> { + + private DateTimeFormatter formatter; + + public OffsetDateTimeTypeAdapter() { + this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length()-5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } + } + } + + /** + * Gson TypeAdapter for JSR310 LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return LocalDate.parse(date, formatter); + } + } + } + + public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + offsetDateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + /** + * Gson TypeAdapter for java.sql.Date type + * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used + * (more efficient than SimpleDateFormat). + */ + public static class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> { + + private DateFormat dateFormat; + + public SqlDateTypeAdapter() { + } + + public SqlDateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, java.sql.Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = date.toString(); + } + out.value(value); + } + } + + @Override + public java.sql.Date read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return new java.sql.Date(dateFormat.parse(date).getTime()); + } + return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } + } + + /** + * Gson TypeAdapter for java.util.Date type + * If the dateFormat is null, ISO8601Utils will be used. + */ + public static class DateTypeAdapter extends TypeAdapter<Date> { + + private DateFormat dateFormat; + + public DateTypeAdapter() { + } + + public DateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = ISO8601Utils.format(date, true); + } + out.value(value); + } + } + + @Override + public Date read(JsonReader in) throws IOException { + try { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return dateFormat.parse(date); + } + return ISO8601Utils.parse(date, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + } + + public JSON setDateFormat(DateFormat dateFormat) { + dateTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setSqlDateFormat(DateFormat dateFormat) { + sqlDateTypeAdapter.setFormat(dateFormat); + return this; + } + +} diff --git a/app/src/main/java/io/swagger/client/model/ModelsBridge.java b/app/src/main/java/io/swagger/client/model/ModelsBridge.java new file mode 100644 index 0000000000000000000000000000000000000000..88f3575ec2b844ce28d7f26eb231be4ffe470bd4 --- /dev/null +++ b/app/src/main/java/io/swagger/client/model/ModelsBridge.java @@ -0,0 +1,428 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * ModelsBridge + */ + +public class ModelsBridge { + @SerializedName("auth") + private String auth = null; + + @SerializedName("bucket") + private String bucket = null; + + @SerializedName("experimental") + private Boolean experimental = null; + + @SerializedName("healthy") + private Boolean healthy = null; + + @SerializedName("host") + private String host = null; + + @SerializedName("ip_addr") + private String ipAddr = null; + + @SerializedName("ip6_addr") + private String ip6Addr = null; + + @SerializedName("last_seen_millis") + private Long lastSeenMillis = null; + + @SerializedName("load") + private BigDecimal load = null; + + @SerializedName("location") + private String location = null; + + @SerializedName("options") + private Map<String, Object> options = null; + + @SerializedName("overloaded") + private Boolean overloaded = null; + + @SerializedName("port") + private Integer port = null; + + @SerializedName("transport") + private String transport = null; + + @SerializedName("type") + private String type = null; + + public ModelsBridge auth(String auth) { + this.auth = auth; + return this; + } + + /** + * Any authentication method needed for connect to the bridge, `none` otherwise. + * @return auth + **/ + @ApiModelProperty(value = "Any authentication method needed for connect to the bridge, `none` otherwise.") + public String getAuth() { + return auth; + } + + public void setAuth(String auth) { + this.auth = auth; + } + + public ModelsBridge bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Bucket is a \"bucket\" tag that connotes a resource group that a user may or may not have access to. An empty bucket string implies that it is open access + * @return bucket + **/ + @ApiModelProperty(value = "Bucket is a \"bucket\" tag that connotes a resource group that a user may or may not have access to. An empty bucket string implies that it is open access") + public String getBucket() { + return bucket; + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public ModelsBridge experimental(Boolean experimental) { + this.experimental = experimental; + return this; + } + + /** + * An experimental bridge flags any bridge that, for whatever reason, is not deemed stable. The expectation is that clients have to opt-in to experimental bridges (and gateways too). + * @return experimental + **/ + @ApiModelProperty(value = "An experimental bridge flags any bridge that, for whatever reason, is not deemed stable. The expectation is that clients have to opt-in to experimental bridges (and gateways too).") + public Boolean isExperimental() { + return experimental; + } + + public void setExperimental(Boolean experimental) { + this.experimental = experimental; + } + + public ModelsBridge healthy(Boolean healthy) { + this.healthy = healthy; + return this; + } + + /** + * Healthy indicates whether this bridge can be used normally. + * @return healthy + **/ + @ApiModelProperty(value = "Healthy indicates whether this bridge can be used normally.") + public Boolean isHealthy() { + return healthy; + } + + public void setHealthy(Boolean healthy) { + this.healthy = healthy; + } + + public ModelsBridge host(String host) { + this.host = host; + return this; + } + + /** + * Host is a unique identifier for the bridge. + * @return host + **/ + @ApiModelProperty(value = "Host is a unique identifier for the bridge.") + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public ModelsBridge ipAddr(String ipAddr) { + this.ipAddr = ipAddr; + return this; + } + + /** + * IPAddr is the IPv4 address + * @return ipAddr + **/ + @ApiModelProperty(value = "IPAddr is the IPv4 address") + public String getIpAddr() { + return ipAddr; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public ModelsBridge ip6Addr(String ip6Addr) { + this.ip6Addr = ip6Addr; + return this; + } + + /** + * IP6Addr is the IPv6 address + * @return ip6Addr + **/ + @ApiModelProperty(value = "IP6Addr is the IPv6 address") + public String getIp6Addr() { + return ip6Addr; + } + + public void setIp6Addr(String ip6Addr) { + this.ip6Addr = ip6Addr; + } + + public ModelsBridge lastSeenMillis(Long lastSeenMillis) { + this.lastSeenMillis = lastSeenMillis; + return this; + } + + /** + * LastSeenMillis is a unix time in milliseconds representing the last time we received a heartbeat update from this bridge + * @return lastSeenMillis + **/ + @ApiModelProperty(value = "LastSeenMillis is a unix time in milliseconds representing the last time we received a heartbeat update from this bridge") + public Long getLastSeenMillis() { + return lastSeenMillis; + } + + public void setLastSeenMillis(Long lastSeenMillis) { + this.lastSeenMillis = lastSeenMillis; + } + + public ModelsBridge load(BigDecimal load) { + this.load = load; + return this; + } + + /** + * Load is the fractional load - but for now menshen agent is not measuring load in the bridges. + * @return load + **/ + @ApiModelProperty(value = "Load is the fractional load - but for now menshen agent is not measuring load in the bridges.") + public BigDecimal getLoad() { + return load; + } + + public void setLoad(BigDecimal load) { + this.load = load; + } + + public ModelsBridge location(String location) { + this.location = location; + return this; + } + + /** + * Location refers to the location to which this bridge points to + * @return location + **/ + @ApiModelProperty(value = "Location refers to the location to which this bridge points to") + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public ModelsBridge options(Map<String, Object> options) { + this.options = options; + return this; + } + + public ModelsBridge putOptionsItem(String key, Object optionsItem) { + if (this.options == null) { + this.options = new HashMap<String, Object>(); + } + this.options.put(key, optionsItem); + return this; + } + + /** + * Options contain the map of options that will be passed to the client. It usually contains authentication credentials. + * @return options + **/ + @ApiModelProperty(value = "Options contain the map of options that will be passed to the client. It usually contains authentication credentials.") + public Map<String, Object> getOptions() { + return options; + } + + public void setOptions(Map<String, Object> options) { + this.options = options; + } + + public ModelsBridge overloaded(Boolean overloaded) { + this.overloaded = overloaded; + return this; + } + + /** + * Overloaded should be set to true if the fractional load is above threshold. + * @return overloaded + **/ + @ApiModelProperty(value = "Overloaded should be set to true if the fractional load is above threshold.") + public Boolean isOverloaded() { + return overloaded; + } + + public void setOverloaded(Boolean overloaded) { + this.overloaded = overloaded; + } + + public ModelsBridge port(Integer port) { + this.port = port; + return this; + } + + /** + * For some protocols (like hopping) port is undefined. + * @return port + **/ + @ApiModelProperty(value = "For some protocols (like hopping) port is undefined.") + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public ModelsBridge transport(String transport) { + this.transport = transport; + return this; + } + + /** + * TCP, UDP or KCP. This was called \"protocol\" before. + * @return transport + **/ + @ApiModelProperty(value = "TCP, UDP or KCP. This was called \"protocol\" before.") + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public ModelsBridge type(String type) { + this.type = type; + return this; + } + + /** + * Type of bridge. + * @return type + **/ + @ApiModelProperty(value = "Type of bridge.") + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModelsBridge modelsBridge = (ModelsBridge) o; + return Objects.equals(this.auth, modelsBridge.auth) && + Objects.equals(this.bucket, modelsBridge.bucket) && + Objects.equals(this.experimental, modelsBridge.experimental) && + Objects.equals(this.healthy, modelsBridge.healthy) && + Objects.equals(this.host, modelsBridge.host) && + Objects.equals(this.ipAddr, modelsBridge.ipAddr) && + Objects.equals(this.ip6Addr, modelsBridge.ip6Addr) && + Objects.equals(this.lastSeenMillis, modelsBridge.lastSeenMillis) && + Objects.equals(this.load, modelsBridge.load) && + Objects.equals(this.location, modelsBridge.location) && + Objects.equals(this.options, modelsBridge.options) && + Objects.equals(this.overloaded, modelsBridge.overloaded) && + Objects.equals(this.port, modelsBridge.port) && + Objects.equals(this.transport, modelsBridge.transport) && + Objects.equals(this.type, modelsBridge.type); + } + + @Override + public int hashCode() { + return Objects.hash(auth, bucket, experimental, healthy, host, ipAddr, ip6Addr, lastSeenMillis, load, location, options, overloaded, port, transport, type); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ModelsBridge {\n"); + + sb.append(" auth: ").append(toIndentedString(auth)).append("\n"); + sb.append(" bucket: ").append(toIndentedString(bucket)).append("\n"); + sb.append(" experimental: ").append(toIndentedString(experimental)).append("\n"); + sb.append(" healthy: ").append(toIndentedString(healthy)).append("\n"); + sb.append(" host: ").append(toIndentedString(host)).append("\n"); + sb.append(" ipAddr: ").append(toIndentedString(ipAddr)).append("\n"); + sb.append(" ip6Addr: ").append(toIndentedString(ip6Addr)).append("\n"); + sb.append(" lastSeenMillis: ").append(toIndentedString(lastSeenMillis)).append("\n"); + sb.append(" load: ").append(toIndentedString(load)).append("\n"); + sb.append(" location: ").append(toIndentedString(location)).append("\n"); + sb.append(" options: ").append(toIndentedString(options)).append("\n"); + sb.append(" overloaded: ").append(toIndentedString(overloaded)).append("\n"); + sb.append(" port: ").append(toIndentedString(port)).append("\n"); + sb.append(" transport: ").append(toIndentedString(transport)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/app/src/main/java/io/swagger/client/model/ModelsEIPService.java b/app/src/main/java/io/swagger/client/model/ModelsEIPService.java new file mode 100644 index 0000000000000000000000000000000000000000..939f8aa6a5cdc3ab0b61820af0ee3db5abdd7392 --- /dev/null +++ b/app/src/main/java/io/swagger/client/model/ModelsEIPService.java @@ -0,0 +1,206 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.client.model.ModelsLocation; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * ModelsEIPService + */ + +public class ModelsEIPService { + @SerializedName("auth") + private String auth = null; + + @SerializedName("locations") + private Map<String, ModelsLocation> locations = null; + + @SerializedName("openvpn_configuration") + private Map<String, Object> openvpnConfiguration = null; + + @SerializedName("serial") + private Integer serial = null; + + @SerializedName("version") + private Integer version = null; + + public ModelsEIPService auth(String auth) { + this.auth = auth; + return this; + } + + /** + * Get auth + * @return auth + **/ + @ApiModelProperty(value = "") + public String getAuth() { + return auth; + } + + public void setAuth(String auth) { + this.auth = auth; + } + + public ModelsEIPService locations(Map<String, ModelsLocation> locations) { + this.locations = locations; + return this; + } + + public ModelsEIPService putLocationsItem(String key, ModelsLocation locationsItem) { + if (this.locations == null) { + this.locations = new HashMap<String, ModelsLocation>(); + } + this.locations.put(key, locationsItem); + return this; + } + + /** + * Get locations + * @return locations + **/ + @ApiModelProperty(value = "") + public Map<String, ModelsLocation> getLocations() { + return locations; + } + + public void setLocations(Map<String, ModelsLocation> locations) { + this.locations = locations; + } + + public ModelsEIPService openvpnConfiguration(Map<String, Object> openvpnConfiguration) { + this.openvpnConfiguration = openvpnConfiguration; + return this; + } + + public ModelsEIPService putOpenvpnConfigurationItem(String key, Object openvpnConfigurationItem) { + if (this.openvpnConfiguration == null) { + this.openvpnConfiguration = new HashMap<String, Object>(); + } + this.openvpnConfiguration.put(key, openvpnConfigurationItem); + return this; + } + + /** + * Get openvpnConfiguration + * @return openvpnConfiguration + **/ + @ApiModelProperty(value = "") + public Map<String, Object> getOpenvpnConfiguration() { + return openvpnConfiguration; + } + + public void setOpenvpnConfiguration(Map<String, Object> openvpnConfiguration) { + this.openvpnConfiguration = openvpnConfiguration; + } + + public ModelsEIPService serial(Integer serial) { + this.serial = serial; + return this; + } + + /** + * Get serial + * @return serial + **/ + @ApiModelProperty(value = "") + public Integer getSerial() { + return serial; + } + + public void setSerial(Integer serial) { + this.serial = serial; + } + + public ModelsEIPService version(Integer version) { + this.version = version; + return this; + } + + /** + * Get version + * @return version + **/ + @ApiModelProperty(value = "") + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModelsEIPService modelsEIPService = (ModelsEIPService) o; + return Objects.equals(this.auth, modelsEIPService.auth) && + Objects.equals(this.locations, modelsEIPService.locations) && + Objects.equals(this.openvpnConfiguration, modelsEIPService.openvpnConfiguration) && + Objects.equals(this.serial, modelsEIPService.serial) && + Objects.equals(this.version, modelsEIPService.version); + } + + @Override + public int hashCode() { + return Objects.hash(auth, locations, openvpnConfiguration, serial, version); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ModelsEIPService {\n"); + + sb.append(" auth: ").append(toIndentedString(auth)).append("\n"); + sb.append(" locations: ").append(toIndentedString(locations)).append("\n"); + sb.append(" openvpnConfiguration: ").append(toIndentedString(openvpnConfiguration)).append("\n"); + sb.append(" serial: ").append(toIndentedString(serial)).append("\n"); + sb.append(" version: ").append(toIndentedString(version)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/app/src/main/java/io/swagger/client/model/ModelsGateway.java b/app/src/main/java/io/swagger/client/model/ModelsGateway.java new file mode 100644 index 0000000000000000000000000000000000000000..3da72aa9c6b516e9e5b3e0947c3da76acb774215 --- /dev/null +++ b/app/src/main/java/io/swagger/client/model/ModelsGateway.java @@ -0,0 +1,371 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; +import java.math.BigDecimal; + +/** + * ModelsGateway + */ + +public class ModelsGateway { + @SerializedName("bucket") + private String bucket = null; + + @SerializedName("experimental") + private Boolean experimental = null; + + @SerializedName("healthy") + private Boolean healthy = null; + + @SerializedName("host") + private String host = null; + + @SerializedName("ip_addr") + private String ipAddr = null; + + @SerializedName("ip6_addr") + private String ip6Addr = null; + + @SerializedName("last_seen_millis") + private Long lastSeenMillis = null; + + @SerializedName("load") + private BigDecimal load = null; + + @SerializedName("location") + private String location = null; + + @SerializedName("overloaded") + private Boolean overloaded = null; + + @SerializedName("port") + private Integer port = null; + + @SerializedName("transport") + private String transport = null; + + @SerializedName("type") + private String type = null; + + public ModelsGateway bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Bucket is a \"bucket\" tag that connotes a resource group that a user may or may not have access to. An empty bucket string implies that it is open access + * @return bucket + **/ + @ApiModelProperty(value = "Bucket is a \"bucket\" tag that connotes a resource group that a user may or may not have access to. An empty bucket string implies that it is open access") + public String getBucket() { + return bucket; + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public ModelsGateway experimental(Boolean experimental) { + this.experimental = experimental; + return this; + } + + /** + * An experimental gateway flags any gateway that, for whatever reason, is not deemed stable. The expectation is that clients have to opt-in to experimental gateways (and bridges too). + * @return experimental + **/ + @ApiModelProperty(value = "An experimental gateway flags any gateway that, for whatever reason, is not deemed stable. The expectation is that clients have to opt-in to experimental gateways (and bridges too).") + public Boolean isExperimental() { + return experimental; + } + + public void setExperimental(Boolean experimental) { + this.experimental = experimental; + } + + public ModelsGateway healthy(Boolean healthy) { + this.healthy = healthy; + return this; + } + + /** + * Not used now - we could potentially flag gateways that are planned to undergo maintenance mode some time in advance. We can also automatically flag as not healthy gateways that appear not to be routing to the internet. + * @return healthy + **/ + @ApiModelProperty(value = "Not used now - we could potentially flag gateways that are planned to undergo maintenance mode some time in advance. We can also automatically flag as not healthy gateways that appear not to be routing to the internet.") + public Boolean isHealthy() { + return healthy; + } + + public void setHealthy(Boolean healthy) { + this.healthy = healthy; + } + + public ModelsGateway host(String host) { + this.host = host; + return this; + } + + /** + * Host is a unique identifier for the gateway host. It does not need to resolve, since we're not using DNS to resolve the gateways. + * @return host + **/ + @ApiModelProperty(value = "Host is a unique identifier for the gateway host. It does not need to resolve, since we're not using DNS to resolve the gateways.") + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public ModelsGateway ipAddr(String ipAddr) { + this.ipAddr = ipAddr; + return this; + } + + /** + * IPAddr is the IPv4 address + * @return ipAddr + **/ + @ApiModelProperty(value = "IPAddr is the IPv4 address") + public String getIpAddr() { + return ipAddr; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public ModelsGateway ip6Addr(String ip6Addr) { + this.ip6Addr = ip6Addr; + return this; + } + + /** + * IP6Addr is the IPv6 address + * @return ip6Addr + **/ + @ApiModelProperty(value = "IP6Addr is the IPv6 address") + public String getIp6Addr() { + return ip6Addr; + } + + public void setIp6Addr(String ip6Addr) { + this.ip6Addr = ip6Addr; + } + + public ModelsGateway lastSeenMillis(Long lastSeenMillis) { + this.lastSeenMillis = lastSeenMillis; + return this; + } + + /** + * LastSeenMillis is a unix time in milliseconds representing the last time we received a heartbeat update from this gateway + * @return lastSeenMillis + **/ + @ApiModelProperty(value = "LastSeenMillis is a unix time in milliseconds representing the last time we received a heartbeat update from this gateway") + public Long getLastSeenMillis() { + return lastSeenMillis; + } + + public void setLastSeenMillis(Long lastSeenMillis) { + this.lastSeenMillis = lastSeenMillis; + } + + public ModelsGateway load(BigDecimal load) { + this.load = load; + return this; + } + + /** + * Load is the fractional load received from the menshen agent. For the time being it is a synthethic metric that takes into account number of clients and network information for the node. + * @return load + **/ + @ApiModelProperty(value = "Load is the fractional load received from the menshen agent. For the time being it is a synthethic metric that takes into account number of clients and network information for the node.") + public BigDecimal getLoad() { + return load; + } + + public void setLoad(BigDecimal load) { + this.load = load; + } + + public ModelsGateway location(String location) { + this.location = location; + return this; + } + + /** + * Location is the canonical label for the location of the gateway. + * @return location + **/ + @ApiModelProperty(value = "Location is the canonical label for the location of the gateway.") + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public ModelsGateway overloaded(Boolean overloaded) { + this.overloaded = overloaded; + return this; + } + + /** + * Overloaded should be set to true if the fractional load is above threshold. + * @return overloaded + **/ + @ApiModelProperty(value = "Overloaded should be set to true if the fractional load is above threshold.") + public Boolean isOverloaded() { + return overloaded; + } + + public void setOverloaded(Boolean overloaded) { + this.overloaded = overloaded; + } + + public ModelsGateway port(Integer port) { + this.port = port; + return this; + } + + /** + * The (primary) port this gateway is listening on. + * @return port + **/ + @ApiModelProperty(value = "The (primary) port this gateway is listening on.") + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public ModelsGateway transport(String transport) { + this.transport = transport; + return this; + } + + /** + * TCP, UDP or KCP. This was called \"protocol\" in previous versions of the API. + * @return transport + **/ + @ApiModelProperty(value = "TCP, UDP or KCP. This was called \"protocol\" in previous versions of the API.") + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public ModelsGateway type(String type) { + this.type = type; + return this; + } + + /** + * Type is the type of gateway. The only valid type as of 2023 is openvpn. + * @return type + **/ + @ApiModelProperty(value = "Type is the type of gateway. The only valid type as of 2023 is openvpn.") + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModelsGateway modelsGateway = (ModelsGateway) o; + return Objects.equals(this.bucket, modelsGateway.bucket) && + Objects.equals(this.experimental, modelsGateway.experimental) && + Objects.equals(this.healthy, modelsGateway.healthy) && + Objects.equals(this.host, modelsGateway.host) && + Objects.equals(this.ipAddr, modelsGateway.ipAddr) && + Objects.equals(this.ip6Addr, modelsGateway.ip6Addr) && + Objects.equals(this.lastSeenMillis, modelsGateway.lastSeenMillis) && + Objects.equals(this.load, modelsGateway.load) && + Objects.equals(this.location, modelsGateway.location) && + Objects.equals(this.overloaded, modelsGateway.overloaded) && + Objects.equals(this.port, modelsGateway.port) && + Objects.equals(this.transport, modelsGateway.transport) && + Objects.equals(this.type, modelsGateway.type); + } + + @Override + public int hashCode() { + return Objects.hash(bucket, experimental, healthy, host, ipAddr, ip6Addr, lastSeenMillis, load, location, overloaded, port, transport, type); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ModelsGateway {\n"); + + sb.append(" bucket: ").append(toIndentedString(bucket)).append("\n"); + sb.append(" experimental: ").append(toIndentedString(experimental)).append("\n"); + sb.append(" healthy: ").append(toIndentedString(healthy)).append("\n"); + sb.append(" host: ").append(toIndentedString(host)).append("\n"); + sb.append(" ipAddr: ").append(toIndentedString(ipAddr)).append("\n"); + sb.append(" ip6Addr: ").append(toIndentedString(ip6Addr)).append("\n"); + sb.append(" lastSeenMillis: ").append(toIndentedString(lastSeenMillis)).append("\n"); + sb.append(" load: ").append(toIndentedString(load)).append("\n"); + sb.append(" location: ").append(toIndentedString(location)).append("\n"); + sb.append(" overloaded: ").append(toIndentedString(overloaded)).append("\n"); + sb.append(" port: ").append(toIndentedString(port)).append("\n"); + sb.append(" transport: ").append(toIndentedString(transport)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/app/src/main/java/io/swagger/client/model/ModelsLocation.java b/app/src/main/java/io/swagger/client/model/ModelsLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..adda676ba9c09e19ee2a9f9347583e2c7c62f721 --- /dev/null +++ b/app/src/main/java/io/swagger/client/model/ModelsLocation.java @@ -0,0 +1,301 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; + +/** + * ModelsLocation + */ + +public class ModelsLocation { + @SerializedName("country_code") + private String countryCode = null; + + @SerializedName("display_name") + private String displayName = null; + + @SerializedName("has_bridges") + private Boolean hasBridges = null; + + @SerializedName("healthy") + private Boolean healthy = null; + + @SerializedName("hemisphere") + private String hemisphere = null; + + @SerializedName("label") + private String label = null; + + @SerializedName("lat") + private String lat = null; + + @SerializedName("lon") + private String lon = null; + + @SerializedName("region") + private String region = null; + + @SerializedName("timezone") + private String timezone = null; + + public ModelsLocation countryCode(String countryCode) { + this.countryCode = countryCode; + return this; + } + + /** + * CountryCode is the two-character country ISO identifier (uppercase). + * @return countryCode + **/ + @ApiModelProperty(value = "CountryCode is the two-character country ISO identifier (uppercase).") + public String getCountryCode() { + return countryCode; + } + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } + + public ModelsLocation displayName(String displayName) { + this.displayName = displayName; + return this; + } + + /** + * DisplayName is the user-facing string for a given location. + * @return displayName + **/ + @ApiModelProperty(value = "DisplayName is the user-facing string for a given location.") + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public ModelsLocation hasBridges(Boolean hasBridges) { + this.hasBridges = hasBridges; + return this; + } + + /** + * Any location that has at least one bridge configured will set this to true. + * @return hasBridges + **/ + @ApiModelProperty(value = "Any location that has at least one bridge configured will set this to true.") + public Boolean isHasBridges() { + return hasBridges; + } + + public void setHasBridges(Boolean hasBridges) { + this.hasBridges = hasBridges; + } + + public ModelsLocation healthy(Boolean healthy) { + this.healthy = healthy; + return this; + } + + /** + * TODO Not used right now, but intended to signal when a location has all of their nodes overwhelmed. + * @return healthy + **/ + @ApiModelProperty(value = "TODO Not used right now, but intended to signal when a location has all of their nodes overwhelmed.") + public Boolean isHealthy() { + return healthy; + } + + public void setHealthy(Boolean healthy) { + this.healthy = healthy; + } + + public ModelsLocation hemisphere(String hemisphere) { + this.hemisphere = hemisphere; + return this; + } + + /** + * Hemisphere is a legacy label for a gateway. The rationale was once intended to be to allocate gateways for an hemisphere with certain regional \"fairness\", even if they're geographically located in a different region. We might want to set this on the Gateway or Bridge, not in the Location itself... + * @return hemisphere + **/ + @ApiModelProperty(value = "Hemisphere is a legacy label for a gateway. The rationale was once intended to be to allocate gateways for an hemisphere with certain regional \"fairness\", even if they're geographically located in a different region. We might want to set this on the Gateway or Bridge, not in the Location itself...") + public String getHemisphere() { + return hemisphere; + } + + public void setHemisphere(String hemisphere) { + this.hemisphere = hemisphere; + } + + public ModelsLocation label(String label) { + this.label = label; + return this; + } + + /** + * Label is the short representation of a location, used internally. + * @return label + **/ + @ApiModelProperty(value = "Label is the short representation of a location, used internally.") + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public ModelsLocation lat(String lat) { + this.lat = lat; + return this; + } + + /** + * Lat is the latitude for the location. + * @return lat + **/ + @ApiModelProperty(value = "Lat is the latitude for the location.") + public String getLat() { + return lat; + } + + public void setLat(String lat) { + this.lat = lat; + } + + public ModelsLocation lon(String lon) { + this.lon = lon; + return this; + } + + /** + * Lon is the longitude for the location. + * @return lon + **/ + @ApiModelProperty(value = "Lon is the longitude for the location.") + public String getLon() { + return lon; + } + + public void setLon(String lon) { + this.lon = lon; + } + + public ModelsLocation region(String region) { + this.region = region; + return this; + } + + /** + * Region is the continental region this gateway is assigned to. Not used at the moment, intended to use a label from the 7-continent model. + * @return region + **/ + @ApiModelProperty(value = "Region is the continental region this gateway is assigned to. Not used at the moment, intended to use a label from the 7-continent model.") + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public ModelsLocation timezone(String timezone) { + this.timezone = timezone; + return this; + } + + /** + * Timezone is the TZ for the location (-1, 0, +1, ...) + * @return timezone + **/ + @ApiModelProperty(value = "Timezone is the TZ for the location (-1, 0, +1, ...)") + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModelsLocation modelsLocation = (ModelsLocation) o; + return Objects.equals(this.countryCode, modelsLocation.countryCode) && + Objects.equals(this.displayName, modelsLocation.displayName) && + Objects.equals(this.hasBridges, modelsLocation.hasBridges) && + Objects.equals(this.healthy, modelsLocation.healthy) && + Objects.equals(this.hemisphere, modelsLocation.hemisphere) && + Objects.equals(this.label, modelsLocation.label) && + Objects.equals(this.lat, modelsLocation.lat) && + Objects.equals(this.lon, modelsLocation.lon) && + Objects.equals(this.region, modelsLocation.region) && + Objects.equals(this.timezone, modelsLocation.timezone); + } + + @Override + public int hashCode() { + return Objects.hash(countryCode, displayName, hasBridges, healthy, hemisphere, label, lat, lon, region, timezone); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ModelsLocation {\n"); + + sb.append(" countryCode: ").append(toIndentedString(countryCode)).append("\n"); + sb.append(" displayName: ").append(toIndentedString(displayName)).append("\n"); + sb.append(" hasBridges: ").append(toIndentedString(hasBridges)).append("\n"); + sb.append(" healthy: ").append(toIndentedString(healthy)).append("\n"); + sb.append(" hemisphere: ").append(toIndentedString(hemisphere)).append("\n"); + sb.append(" label: ").append(toIndentedString(label)).append("\n"); + sb.append(" lat: ").append(toIndentedString(lat)).append("\n"); + sb.append(" lon: ").append(toIndentedString(lon)).append("\n"); + sb.append(" region: ").append(toIndentedString(region)).append("\n"); + sb.append(" timezone: ").append(toIndentedString(timezone)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/app/src/main/java/io/swagger/client/model/ModelsProvider.java b/app/src/main/java/io/swagger/client/model/ModelsProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..34b545103a46d29473508ebb47e2d31a54bc6bea --- /dev/null +++ b/app/src/main/java/io/swagger/client/model/ModelsProvider.java @@ -0,0 +1,584 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.client.model.ModelsProviderService; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * ModelsProvider + */ + +public class ModelsProvider { + @SerializedName("api_uri") + private String apiUri = null; + + @SerializedName("api_version") + private String apiVersion = null; + + @SerializedName("api_versions") + private List<String> apiVersions = null; + + @SerializedName("ask_for_donations") + private Boolean askForDonations = null; + + @SerializedName("ca_cert_fingerprint") + private String caCertFingerprint = null; + + @SerializedName("ca_cert_uri") + private String caCertUri = null; + + @SerializedName("country_code_lookup_url") + private String countryCodeLookupUrl = null; + + @SerializedName("default_language") + private String defaultLanguage = null; + + @SerializedName("description") + private Map<String, String> description = null; + + @SerializedName("domain") + private String domain = null; + + @SerializedName("donate_period") + private String donatePeriod = null; + + @SerializedName("donate_url") + private String donateUrl = null; + + @SerializedName("info_url") + private String infoUrl = null; + + @SerializedName("languages") + private List<String> languages = null; + + @SerializedName("motd_url") + private String motdUrl = null; + + @SerializedName("name") + private Map<String, String> name = null; + + @SerializedName("service") + private ModelsProviderService service = null; + + @SerializedName("services") + private List<String> services = null; + + @SerializedName("stun_servers") + private List<String> stunServers = null; + + @SerializedName("tos_url") + private String tosUrl = null; + + public ModelsProvider apiUri(String apiUri) { + this.apiUri = apiUri; + return this; + } + + /** + * URL of the API endpoints + * @return apiUri + **/ + @ApiModelProperty(value = "URL of the API endpoints") + public String getApiUri() { + return apiUri; + } + + public void setApiUri(String apiUri) { + this.apiUri = apiUri; + } + + public ModelsProvider apiVersion(String apiVersion) { + this.apiVersion = apiVersion; + return this; + } + + /** + * oldest supported api version deprecated: kept for backwards compatibility. Replaced by api_versions. + * @return apiVersion + **/ + @ApiModelProperty(value = "oldest supported api version deprecated: kept for backwards compatibility. Replaced by api_versions.") + public String getApiVersion() { + return apiVersion; + } + + public void setApiVersion(String apiVersion) { + this.apiVersion = apiVersion; + } + + public ModelsProvider apiVersions(List<String> apiVersions) { + this.apiVersions = apiVersions; + return this; + } + + public ModelsProvider addApiVersionsItem(String apiVersionsItem) { + if (this.apiVersions == null) { + this.apiVersions = new ArrayList<String>(); + } + this.apiVersions.add(apiVersionsItem); + return this; + } + + /** + * all API versions the provider supports + * @return apiVersions + **/ + @ApiModelProperty(value = "all API versions the provider supports") + public List<String> getApiVersions() { + return apiVersions; + } + + public void setApiVersions(List<String> apiVersions) { + this.apiVersions = apiVersions; + } + + public ModelsProvider askForDonations(Boolean askForDonations) { + this.askForDonations = askForDonations; + return this; + } + + /** + * Flag indicating whether to show regularly a donation reminder + * @return askForDonations + **/ + @ApiModelProperty(value = "Flag indicating whether to show regularly a donation reminder") + public Boolean isAskForDonations() { + return askForDonations; + } + + public void setAskForDonations(Boolean askForDonations) { + this.askForDonations = askForDonations; + } + + public ModelsProvider caCertFingerprint(String caCertFingerprint) { + this.caCertFingerprint = caCertFingerprint; + return this; + } + + /** + * fingerprint of CA cert used to setup TLS sessions during VPN setup (and up to API version 3 for API communication) deprecated: kept for backwards compatibility + * @return caCertFingerprint + **/ + @ApiModelProperty(value = "fingerprint of CA cert used to setup TLS sessions during VPN setup (and up to API version 3 for API communication) deprecated: kept for backwards compatibility") + public String getCaCertFingerprint() { + return caCertFingerprint; + } + + public void setCaCertFingerprint(String caCertFingerprint) { + this.caCertFingerprint = caCertFingerprint; + } + + public ModelsProvider caCertUri(String caCertUri) { + this.caCertUri = caCertUri; + return this; + } + + /** + * URL to fetch the CA cert used to setup TLS sessions during VPN setup (and up to API version 3 for API communication) deprecated: kept for backwards compatibility + * @return caCertUri + **/ + @ApiModelProperty(value = "URL to fetch the CA cert used to setup TLS sessions during VPN setup (and up to API version 3 for API communication) deprecated: kept for backwards compatibility") + public String getCaCertUri() { + return caCertUri; + } + + public void setCaCertUri(String caCertUri) { + this.caCertUri = caCertUri; + } + + public ModelsProvider countryCodeLookupUrl(String countryCodeLookupUrl) { + this.countryCodeLookupUrl = countryCodeLookupUrl; + return this; + } + + /** + * URL of a service that returns a country code for an ip address. If empty, OONI backend is used + * @return countryCodeLookupUrl + **/ + @ApiModelProperty(value = "URL of a service that returns a country code for an ip address. If empty, OONI backend is used") + public String getCountryCodeLookupUrl() { + return countryCodeLookupUrl; + } + + public void setCountryCodeLookupUrl(String countryCodeLookupUrl) { + this.countryCodeLookupUrl = countryCodeLookupUrl; + } + + public ModelsProvider defaultLanguage(String defaultLanguage) { + this.defaultLanguage = defaultLanguage; + return this; + } + + /** + * Default language this provider uses to show infos and provider messages + * @return defaultLanguage + **/ + @ApiModelProperty(value = "Default language this provider uses to show infos and provider messages") + public String getDefaultLanguage() { + return defaultLanguage; + } + + public void setDefaultLanguage(String defaultLanguage) { + this.defaultLanguage = defaultLanguage; + } + + public ModelsProvider description(Map<String, String> description) { + this.description = description; + return this; + } + + public ModelsProvider putDescriptionItem(String key, String descriptionItem) { + if (this.description == null) { + this.description = new HashMap<String, String>(); + } + this.description.put(key, descriptionItem); + return this; + } + + /** + * Short description about the provider + * @return description + **/ + @ApiModelProperty(value = "Short description about the provider") + public Map<String, String> getDescription() { + return description; + } + + public void setDescription(Map<String, String> description) { + this.description = description; + } + + public ModelsProvider domain(String domain) { + this.domain = domain; + return this; + } + + /** + * Domain of the provider + * @return domain + **/ + @ApiModelProperty(value = "Domain of the provider") + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public ModelsProvider donatePeriod(String donatePeriod) { + this.donatePeriod = donatePeriod; + return this; + } + + /** + * Number of days until a donation reminder reappears + * @return donatePeriod + **/ + @ApiModelProperty(value = "Number of days until a donation reminder reappears") + public String getDonatePeriod() { + return donatePeriod; + } + + public void setDonatePeriod(String donatePeriod) { + this.donatePeriod = donatePeriod; + } + + public ModelsProvider donateUrl(String donateUrl) { + this.donateUrl = donateUrl; + return this; + } + + /** + * URL to the donation website + * @return donateUrl + **/ + @ApiModelProperty(value = "URL to the donation website") + public String getDonateUrl() { + return donateUrl; + } + + public void setDonateUrl(String donateUrl) { + this.donateUrl = donateUrl; + } + + public ModelsProvider infoUrl(String infoUrl) { + this.infoUrl = infoUrl; + return this; + } + + /** + * URL to general provider website + * @return infoUrl + **/ + @ApiModelProperty(value = "URL to general provider website") + public String getInfoUrl() { + return infoUrl; + } + + public void setInfoUrl(String infoUrl) { + this.infoUrl = infoUrl; + } + + public ModelsProvider languages(List<String> languages) { + this.languages = languages; + return this; + } + + public ModelsProvider addLanguagesItem(String languagesItem) { + if (this.languages == null) { + this.languages = new ArrayList<String>(); + } + this.languages.add(languagesItem); + return this; + } + + /** + * Languages the provider supports to show infos and provider messages + * @return languages + **/ + @ApiModelProperty(value = "Languages the provider supports to show infos and provider messages") + public List<String> getLanguages() { + return languages; + } + + public void setLanguages(List<String> languages) { + this.languages = languages; + } + + public ModelsProvider motdUrl(String motdUrl) { + this.motdUrl = motdUrl; + return this; + } + + /** + * URL to the message of the day service + * @return motdUrl + **/ + @ApiModelProperty(value = "URL to the message of the day service") + public String getMotdUrl() { + return motdUrl; + } + + public void setMotdUrl(String motdUrl) { + this.motdUrl = motdUrl; + } + + public ModelsProvider name(Map<String, String> name) { + this.name = name; + return this; + } + + public ModelsProvider putNameItem(String key, String nameItem) { + if (this.name == null) { + this.name = new HashMap<String, String>(); + } + this.name.put(key, nameItem); + return this; + } + + /** + * Provider name + * @return name + **/ + @ApiModelProperty(value = "Provider name") + public Map<String, String> getName() { + return name; + } + + public void setName(Map<String, String> name) { + this.name = name; + } + + public ModelsProvider service(ModelsProviderService service) { + this.service = service; + return this; + } + + /** + * Get service + * @return service + **/ + @ApiModelProperty(value = "") + public ModelsProviderService getService() { + return service; + } + + public void setService(ModelsProviderService service) { + this.service = service; + } + + public ModelsProvider services(List<String> services) { + this.services = services; + return this; + } + + public ModelsProvider addServicesItem(String servicesItem) { + if (this.services == null) { + this.services = new ArrayList<String>(); + } + this.services.add(servicesItem); + return this; + } + + /** + * List of services the provider offers, currently only openvpn + * @return services + **/ + @ApiModelProperty(value = "List of services the provider offers, currently only openvpn") + public List<String> getServices() { + return services; + } + + public void setServices(List<String> services) { + this.services = services; + } + + public ModelsProvider stunServers(List<String> stunServers) { + this.stunServers = stunServers; + return this; + } + + public ModelsProvider addStunServersItem(String stunServersItem) { + if (this.stunServers == null) { + this.stunServers = new ArrayList<String>(); + } + this.stunServers.add(stunServersItem); + return this; + } + + /** + * list of STUN servers (format: ip/hostname:port) servers to get current ip address can consist of self hosted STUN servers, public ones or a combination of both. GeolocationLookup is only done when the list of STUNServers is not empty + * @return stunServers + **/ + @ApiModelProperty(value = "list of STUN servers (format: ip/hostname:port) servers to get current ip address can consist of self hosted STUN servers, public ones or a combination of both. GeolocationLookup is only done when the list of STUNServers is not empty") + public List<String> getStunServers() { + return stunServers; + } + + public void setStunServers(List<String> stunServers) { + this.stunServers = stunServers; + } + + public ModelsProvider tosUrl(String tosUrl) { + this.tosUrl = tosUrl; + return this; + } + + /** + * URL to Terms of Service website + * @return tosUrl + **/ + @ApiModelProperty(value = "URL to Terms of Service website") + public String getTosUrl() { + return tosUrl; + } + + public void setTosUrl(String tosUrl) { + this.tosUrl = tosUrl; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModelsProvider modelsProvider = (ModelsProvider) o; + return Objects.equals(this.apiUri, modelsProvider.apiUri) && + Objects.equals(this.apiVersion, modelsProvider.apiVersion) && + Objects.equals(this.apiVersions, modelsProvider.apiVersions) && + Objects.equals(this.askForDonations, modelsProvider.askForDonations) && + Objects.equals(this.caCertFingerprint, modelsProvider.caCertFingerprint) && + Objects.equals(this.caCertUri, modelsProvider.caCertUri) && + Objects.equals(this.countryCodeLookupUrl, modelsProvider.countryCodeLookupUrl) && + Objects.equals(this.defaultLanguage, modelsProvider.defaultLanguage) && + Objects.equals(this.description, modelsProvider.description) && + Objects.equals(this.domain, modelsProvider.domain) && + Objects.equals(this.donatePeriod, modelsProvider.donatePeriod) && + Objects.equals(this.donateUrl, modelsProvider.donateUrl) && + Objects.equals(this.infoUrl, modelsProvider.infoUrl) && + Objects.equals(this.languages, modelsProvider.languages) && + Objects.equals(this.motdUrl, modelsProvider.motdUrl) && + Objects.equals(this.name, modelsProvider.name) && + Objects.equals(this.service, modelsProvider.service) && + Objects.equals(this.services, modelsProvider.services) && + Objects.equals(this.stunServers, modelsProvider.stunServers) && + Objects.equals(this.tosUrl, modelsProvider.tosUrl); + } + + @Override + public int hashCode() { + return Objects.hash(apiUri, apiVersion, apiVersions, askForDonations, caCertFingerprint, caCertUri, countryCodeLookupUrl, defaultLanguage, description, domain, donatePeriod, donateUrl, infoUrl, languages, motdUrl, name, service, services, stunServers, tosUrl); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ModelsProvider {\n"); + + sb.append(" apiUri: ").append(toIndentedString(apiUri)).append("\n"); + sb.append(" apiVersion: ").append(toIndentedString(apiVersion)).append("\n"); + sb.append(" apiVersions: ").append(toIndentedString(apiVersions)).append("\n"); + sb.append(" askForDonations: ").append(toIndentedString(askForDonations)).append("\n"); + sb.append(" caCertFingerprint: ").append(toIndentedString(caCertFingerprint)).append("\n"); + sb.append(" caCertUri: ").append(toIndentedString(caCertUri)).append("\n"); + sb.append(" countryCodeLookupUrl: ").append(toIndentedString(countryCodeLookupUrl)).append("\n"); + sb.append(" defaultLanguage: ").append(toIndentedString(defaultLanguage)).append("\n"); + sb.append(" description: ").append(toIndentedString(description)).append("\n"); + sb.append(" domain: ").append(toIndentedString(domain)).append("\n"); + sb.append(" donatePeriod: ").append(toIndentedString(donatePeriod)).append("\n"); + sb.append(" donateUrl: ").append(toIndentedString(donateUrl)).append("\n"); + sb.append(" infoUrl: ").append(toIndentedString(infoUrl)).append("\n"); + sb.append(" languages: ").append(toIndentedString(languages)).append("\n"); + sb.append(" motdUrl: ").append(toIndentedString(motdUrl)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" service: ").append(toIndentedString(service)).append("\n"); + sb.append(" services: ").append(toIndentedString(services)).append("\n"); + sb.append(" stunServers: ").append(toIndentedString(stunServers)).append("\n"); + sb.append(" tosUrl: ").append(toIndentedString(tosUrl)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/app/src/main/java/io/swagger/client/model/ModelsProviderService.java b/app/src/main/java/io/swagger/client/model/ModelsProviderService.java new file mode 100644 index 0000000000000000000000000000000000000000..ef13b60d70d42c8abab071b68f951dd243eac8b9 --- /dev/null +++ b/app/src/main/java/io/swagger/client/model/ModelsProviderService.java @@ -0,0 +1,118 @@ +/* + * Menshen API + * This is a LEAP VPN Service API + * + * OpenAPI spec version: 0.5.2 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +package io.swagger.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; + +/** + * Operational properties which describe how the provider offers the service + */ +@ApiModel(description = "Operational properties which describe how the provider offers the service") + +public class ModelsProviderService { + @SerializedName("allow_anonymous") + private Boolean allowAnonymous = null; + + @SerializedName("allow_registration") + private Boolean allowRegistration = null; + + public ModelsProviderService allowAnonymous(Boolean allowAnonymous) { + this.allowAnonymous = allowAnonymous; + return this; + } + + /** + * Flag indicating if anonymous usage without registration is allowed deprecated: kept for backwards compatibility + * @return allowAnonymous + **/ + @ApiModelProperty(value = "Flag indicating if anonymous usage without registration is allowed deprecated: kept for backwards compatibility") + public Boolean isAllowAnonymous() { + return allowAnonymous; + } + + public void setAllowAnonymous(Boolean allowAnonymous) { + this.allowAnonymous = allowAnonymous; + } + + public ModelsProviderService allowRegistration(Boolean allowRegistration) { + this.allowRegistration = allowRegistration; + return this; + } + + /** + * Flag indicating if the provider supports user registration deprecated: kept for backwards compatibility + * @return allowRegistration + **/ + @ApiModelProperty(value = "Flag indicating if the provider supports user registration deprecated: kept for backwards compatibility") + public Boolean isAllowRegistration() { + return allowRegistration; + } + + public void setAllowRegistration(Boolean allowRegistration) { + this.allowRegistration = allowRegistration; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModelsProviderService modelsProviderService = (ModelsProviderService) o; + return Objects.equals(this.allowAnonymous, modelsProviderService.allowAnonymous) && + Objects.equals(this.allowRegistration, modelsProviderService.allowRegistration); + } + + @Override + public int hashCode() { + return Objects.hash(allowAnonymous, allowRegistration); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ModelsProviderService {\n"); + + sb.append(" allowAnonymous: ").append(toIndentedString(allowAnonymous)).append("\n"); + sb.append(" allowRegistration: ").append(toIndentedString(allowRegistration)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + 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 c7e12491c1847244c8dd57597b736eded9494c8f..6b3ba348114d557d5729b43e47ce83aac2b67ba7 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/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index fb93796e19f4401c80c8e801c1fd1ef17ad50ff5..7683433200df3d4e179dc6a8ca50a0f67f4410cc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -50,6 +50,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatTextView; import androidx.core.content.ContextCompat; +import androidx.core.os.BundleCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; @@ -116,7 +117,7 @@ public class EipFragment extends Fragment implements PropertyChangeListener { Activity activity = getActivity(); if (activity != null) { if (arguments != null) { - provider = arguments.getParcelable(PROVIDER_KEY); + provider = BundleCompat.getParcelable(arguments, PROVIDER_KEY, Provider.class); if (provider == null) { handleNoProvider(activity); } else { @@ -307,7 +308,7 @@ public class EipFragment extends Fragment implements PropertyChangeListener { Log.e(TAG, "context is null when trying to start VPN"); return; } - if (!provider.getGeoipUrl().isDefault() && provider.shouldUpdateGeoIpJson()) { + if (!provider.getGeoipUrl().isEmpty() && provider.shouldUpdateGeoIpJson()) { Bundle bundle = new Bundle(); bundle.putBoolean(EIP_ACTION_START, true); bundle.putBoolean(EIP_EARLY_ROUTES, false); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java index 56b7259ee38e0890f6bb69c14b31542ba7614543..c165d19b20373dc278b719ed675122df8818a769 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java @@ -43,6 +43,7 @@ import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.os.BundleCompat; import androidx.fragment.app.ListFragment; import java.text.SimpleDateFormat; @@ -300,7 +301,7 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. // We have been called if (msg.what == MESSAGE_NEWLOG) { - LogItem logMessage = msg.getData().getParcelable("logmessage"); + LogItem logMessage = BundleCompat.getParcelable(msg.getData(), "logmessage", LogItem.class); if (addLogMessage(logMessage)) for (DataSetObserver observer : observers) { observer.onChanged(); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java index 3dbdbe6410cd260d74d843a7e306c2810d16b8d5..ca84d33038850093b29f15278939452d8381ac54 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java @@ -37,6 +37,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.os.BundleCompat; import androidx.fragment.app.DialogFragment; import org.json.JSONObject; @@ -186,7 +187,7 @@ public class MainActivityErrorDialog extends DialogFragment { return; } if (savedInstanceState.containsKey(KEY_PROVIDER)) { - this.provider = savedInstanceState.getParcelable(KEY_PROVIDER); + this.provider = BundleCompat.getParcelable(savedInstanceState, KEY_PROVIDER, Provider.class); } if (savedInstanceState.containsKey(KEY_REASON_TO_FAIL)) { this.reasonToFail = savedInstanceState.getString(KEY_REASON_TO_FAIL); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index 6c4a71d0d4a312ca5e90716adf7690e5e3cb8002..5a5d1d6e1a7ceacbc02042c5b02ffc0e47ae533a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -42,6 +42,8 @@ public interface Constants { String RESTART_ON_UPDATE = "restart_on_update"; String LAST_UPDATE_CHECK = "last_update_check"; String PREFERRED_CITY = "preferred_city"; + // ATTENTION: this key is also used in bitmask-core for persistence + String COUNTRYCODE = "COUNTRYCODE"; String USE_SNOWFLAKE = "use_snowflake"; String PREFER_UDP = "prefer_UDP"; String GATEWAY_PINNING = "gateway_pinning"; @@ -124,6 +126,10 @@ public interface Constants { String PROVIDER_MOTD_HASHES = "Constants.PROVIDER_MOTD_HASHES"; String PROVIDER_MOTD_LAST_SEEN = "Constants.PROVIDER_MOTD_LAST_SEEN"; String PROVIDER_MOTD_LAST_UPDATED = "Constants.PROVIDER_MOTD_LAST_UPDATED"; + String PROVIDER_MODELS_PROVIDER = "Constants.PROVIDER_MODELS_PROVIDER"; + String PROVIDER_MODELS_EIPSERVICE = "Constants.PROVIDER_MDOELS_EIPSERVICE"; + String PROVIDER_MODELS_GATEWAYS = "Constants.PROVIDER_MODELS_GATEWAYS"; + String PROVIDER_MODELS_BRIDGES = "Constants.PROVIDER_MODELS_BRIDGES"; //////////////////////////////////////////////// // PRESHIPPED PROVIDER CONFIG 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 c31912d91b9ad77cbfa3053659da4136fe15b3a0..671992ca02f0268e7fad0fe2ffee536ae41c7e1c 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 @@ -29,6 +29,10 @@ public class Introducer implements Parcelable { kcpEnabled = in.readByte() != 0; } + public String getFullyQualifiedDomainName() { + return fullyQualifiedDomainName; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(type); 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 ed0a9a15e7f376c3ac0c53ccf30505b30d67f48b..89cea76b1d0dd0e8f497a6a56a438ea3a6682548 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 @@ -28,6 +28,8 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGIS import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.TYPE; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDomainName; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.isNetworkUrl; import static se.leap.bitmaskclient.base.utils.PrivateKeyHelper.parsePrivateKeyFromString; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; @@ -37,23 +39,34 @@ import android.os.Parcelable; import androidx.annotation.NonNull; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.security.PrivateKey; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; import de.blinkt.openvpn.core.connection.Connection.TransportProtocol; import de.blinkt.openvpn.core.connection.Connection.TransportType; +import io.swagger.client.JSON; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsEIPService; +import io.swagger.client.model.ModelsGateway; +import io.swagger.client.model.ModelsProvider; import motd.IStringCollection; import motd.Motd; +import se.leap.bitmaskclient.BuildConfig; /** * @author Sean Leonard <meanderingcode@aetherislands.net> @@ -68,17 +81,22 @@ public final class Provider implements Parcelable { private JSONObject eipServiceJson = new JSONObject(); private JSONObject geoIpJson = new JSONObject(); private JSONObject motdJson = new JSONObject(); - private DefaultedURL mainUrl = new DefaultedURL(); - private DefaultedURL apiUrl = new DefaultedURL(); - private DefaultedURL geoipUrl = new DefaultedURL(); - private DefaultedURL motdUrl = new DefaultedURL(); + private String mainUrl = ""; + private String apiUrl = ""; + private String geoipUrl = ""; + private String motdUrl = ""; + private ModelsEIPService modelsEIPService = null; + private ModelsProvider modelsProvider = null; + private ModelsGateway[] modelsGateways = null; + private ModelsBridge[] modelsBridges = null; private String domain = ""; private String providerIp = ""; // ip of the provider main url private String providerApiIp = ""; // ip of the provider api url private String certificatePin = ""; private String certificatePinEncoding = ""; private String caCert = ""; - private String apiVersion = ""; + private int apiVersion = 3; + private int[] apiVersions = new int[0]; private String privateKeyString = ""; private transient PrivateKey privateKey = null; private String vpnCertificate = ""; @@ -96,6 +114,7 @@ public final class Provider implements Parcelable { final public static String API_URL = "api_uri", API_VERSION = "api_version", + API_VERSIONS = "api_versions", ALLOW_REGISTRATION = "allow_registration", API_RETURN_SERIAL = "serial", SERVICE = "service", @@ -117,41 +136,35 @@ public final class Provider implements Parcelable { public Provider() { } public Provider(Introducer introducer) { - this(introducer.toUrl(), null); + this("https://" + introducer.getFullyQualifiedDomainName()); this.introducer = introducer; } public Provider(String mainUrl) { - this(mainUrl, null); + this(mainUrl, null); + domain = getHostFromUrl(mainUrl); } public Provider(String mainUrl, String geoipUrl) { - try { - this.mainUrl.setUrl(new URL(mainUrl)); - } catch (MalformedURLException e) { - this.mainUrl = new DefaultedURL(); - } + setMainUrl(mainUrl); setGeoipUrl(geoipUrl); + domain = getHostFromUrl(mainUrl); } - public static Provider createCustomProvider(String mainUrl, String domain) { + public static Provider createCustomProvider(String mainUrl, String domain, Introducer introducer) { Provider p = new Provider(mainUrl); p.domain = domain; + p.introducer = introducer; return p; } public Provider(String mainUrl, String geoipUrl, String motdUrl, String providerIp, String providerApiIp) { - try { - this.mainUrl.setUrl(new URL(mainUrl)); - if (providerIp != null) { - this.providerIp = providerIp; - } - if (providerApiIp != null) { - this.providerApiIp = providerApiIp; - } - } catch (MalformedURLException e) { - e.printStackTrace(); - return; + setMainUrl(mainUrl); + if (providerIp != null) { + this.providerIp = providerIp; + } + if (providerApiIp != null) { + this.providerApiIp = providerApiIp; } setGeoipUrl(geoipUrl); setMotdUrl(motdUrl); @@ -183,14 +196,110 @@ public final class Provider implements Parcelable { } }; + public void setBridges(String bridgesJson) { + if (bridgesJson == null) { + this.modelsBridges = null; + return; + } + try { + this.modelsBridges = JSON.createGson().create().fromJson(bridgesJson, ModelsBridge[].class); + } catch (JsonSyntaxException e) { + e.printStackTrace(); + } + } + + public ModelsBridge[] getBridges() { + return this.modelsBridges; + } + + public String getBridgesJson() { + return getJsonString(modelsBridges); + } + + public void setGateways(String gatewaysJson) { + if (gatewaysJson == null) { + this.modelsGateways = null; + return; + } + try { + this.modelsGateways = JSON.createGson().create().fromJson(gatewaysJson, ModelsGateway[].class); + } catch (JsonSyntaxException e) { + e.printStackTrace(); + } + } + + public ModelsGateway[] getGateways() { + return modelsGateways; + } + + public String getGatewaysJson() { + return getJsonString(modelsGateways); + } + + public void setService(String serviceJson) { + if (serviceJson == null) { + this.modelsEIPService = null; + return; + } + try { + this.modelsEIPService = JSON.createGson().create().fromJson(serviceJson, ModelsEIPService.class); + } catch (JsonSyntaxException e) { + e.printStackTrace(); + } + } + public ModelsEIPService getService() { + return this.modelsEIPService; + } + + public String getServiceJson() { + return getJsonString(modelsEIPService); + } + + public void setModelsProvider(String json) { + if (json == null) { + this.modelsProvider = null; + return; + } + try { + this.modelsProvider = JSON.createGson().create().fromJson(json, ModelsProvider.class); + } catch (JsonSyntaxException e) { + e.printStackTrace(); + } + } + + public String getModelsProviderJson() { + return getJsonString(modelsProvider); + } + + private String getJsonString(Object model) { + if (model == null) { + return null; + } + try { + return JSON.createGson().create().toJson(model); + } catch (JsonSyntaxException e) { + e.printStackTrace(); + return null; + } + } + public boolean isConfigured() { - return !mainUrl.isDefault() && - !apiUrl.isDefault() && - hasCaCert() && - hasDefinition() && - hasVpnCertificate() && - hasEIP() && - hasPrivateKey(); + if (apiVersion < 5) { + return !mainUrl.isEmpty() && + !apiUrl.isEmpty() && + hasCaCert() && + hasDefinition() && + hasVpnCertificate() && + hasEIP() && + hasPrivateKey(); + } else { + return !mainUrl.isEmpty() && + modelsProvider != null && + modelsEIPService != null && + modelsGateways != null && + hasVpnCertificate() && + hasPrivateKey(); + } } public boolean supportsPluggableTransports() { @@ -215,42 +324,63 @@ public final class Provider implements Parcelable { } private boolean supportsTransports(Pair<TransportType, TransportProtocol>[] transportTypes) { - try { - JSONArray gatewayJsons = eipServiceJson.getJSONArray(GATEWAYS); - for (int i = 0; i < gatewayJsons.length(); i++) { - JSONArray transports = gatewayJsons.getJSONObject(i). - getJSONObject(CAPABILITIES). - getJSONArray(TRANSPORT); - for (int j = 0; j < transports.length(); j++) { - String supportedTransportType = transports.getJSONObject(j).getString(TYPE); - JSONArray transportProtocols = transports.getJSONObject(j).getJSONArray(PROTOCOLS); - for (Pair<TransportType, TransportProtocol> transportPair : transportTypes) { - for (int k = 0; k < transportProtocols.length(); k++) { - if (transportPair.first.toString().equals(supportedTransportType) && - transportPair.second.toString().equals(transportProtocols.getString(k))) { - return true; + if (apiVersion < 5) { + try { + JSONArray gatewayJsons = eipServiceJson.getJSONArray(GATEWAYS); + for (int i = 0; i < gatewayJsons.length(); i++) { + JSONArray transports = gatewayJsons.getJSONObject(i). + getJSONObject(CAPABILITIES). + getJSONArray(TRANSPORT); + for (int j = 0; j < transports.length(); j++) { + String supportedTransportType = transports.getJSONObject(j).getString(TYPE); + JSONArray transportProtocols = transports.getJSONObject(j).getJSONArray(PROTOCOLS); + for (Pair<TransportType, TransportProtocol> transportPair : transportTypes) { + for (int k = 0; k < transportProtocols.length(); k++) { + if (transportPair.first.toString().equals(supportedTransportType) && + transportPair.second.toString().equals(transportProtocols.getString(k))) { + return true; + } } } } } + } catch (Exception e) { + // ignore + } + } else { + if (modelsBridges == null) return false; + for (ModelsBridge bridge : modelsBridges) { + for (Pair<TransportType, TransportProtocol> transportPair : transportTypes) { + if (transportPair.first.toString().equals(bridge.getType()) && + transportPair.second.toString().equals(bridge.getTransport())) { + return true; + } + } } - } catch (Exception e) { - // ignore } + return false; } public String getIpForHostname(String host) { if (host != null) { - if (host.equals(mainUrl.getUrl().getHost())) { + if (host.equals(getHostFromUrl(mainUrl))) { return providerIp; - } else if (host.equals(apiUrl.getUrl().getHost())) { + } else if (host.equals(getHostFromUrl(apiUrl))) { return providerApiIp; } } return ""; } + private String getHostFromUrl(String url) { + try { + return new URL(url).getHost(); + } catch (MalformedURLException e) { + return ""; + } + } + public String getProviderApiIp() { return this.providerApiIp; } @@ -270,14 +400,21 @@ public final class Provider implements Parcelable { } public void setMainUrl(URL url) { - mainUrl.setUrl(url); + mainUrl = url.toString(); } public void setMainUrl(String url) { try { - mainUrl.setUrl(new URL(url)); + if (isNetworkUrl(url)) { + this.mainUrl = new URL(url).toString(); + } else if (isDomainName(url)){ + this.mainUrl = new URL("https://" + url).toString(); + } else { + this.mainUrl = ""; + } } catch (MalformedURLException e) { e.printStackTrace(); + this.mainUrl = ""; } } @@ -295,55 +432,54 @@ public final class Provider implements Parcelable { } public String getDomain() { - return domain; - } - - public String getMainUrlString() { - return getMainUrl().toString(); + if ((apiVersion < 5 && (domain == null || domain.isEmpty())) || + (modelsProvider == null)) { + return getHostFromUrl(mainUrl); + } + if (apiVersion < 5) { + return domain; + } + return modelsProvider.getDomain(); } - public DefaultedURL getMainUrl() { + public String getMainUrl() { return mainUrl; } - protected DefaultedURL getApiUrl() { - return apiUrl; - } - - public DefaultedURL getGeoipUrl() { + public String getGeoipUrl() { return geoipUrl; } public void setGeoipUrl(String url) { try { - this.geoipUrl.setUrl(new URL(url)); + this.geoipUrl = new URL(url).toString(); } catch (MalformedURLException e) { - this.geoipUrl = new DefaultedURL(); + this.geoipUrl = ""; } } - public DefaultedURL getMotdUrl() { + public String getMotdUrl() { return this.motdUrl; } public void setMotdUrl(String url) { try { - this.motdUrl.setUrl(new URL(url)); + this.motdUrl = new URL(url).toString(); } catch (MalformedURLException e) { - this.motdUrl = new DefaultedURL(); + this.motdUrl = ""; } } public String getApiUrlWithVersion() { - return getApiUrlString() + "/" + getApiVersion(); + return getApiUrl() + "/" + getApiVersion(); } - public String getApiUrlString() { - return getApiUrl().toString(); + public String getApiUrl() { + return apiUrl; } - public String getApiVersion() { + public int getApiVersion() { return apiVersion; } @@ -352,7 +488,7 @@ public final class Provider implements Parcelable { } public boolean hasDefinition() { - return definition != null && definition.length() > 0; + return (definition != null && definition.length() > 0) || (modelsProvider != null); } public boolean hasGeoIpJson() { @@ -377,7 +513,7 @@ public final class Provider implements Parcelable { name = definition.getJSONObject(API_TERM_NAME).getString("en"); } catch (JSONException e2) { if (mainUrl != null) { - String host = mainUrl.getDomain(); + String host = getHostFromUrl(mainUrl); name = host.substring(0, host.indexOf(".")); } } @@ -408,12 +544,25 @@ public final class Provider implements Parcelable { && !getEipServiceJson().has(ERRORS); } + public boolean hasServiceInfo() { + return modelsEIPService != null; + } + public boolean hasGatewaysInDifferentLocations() { - try { - return getEipServiceJson().getJSONObject(LOCATIONS).length() > 1; - } catch (NullPointerException | JSONException e) { - return false; + if (apiVersion >= 5) { + try { + return getService().getLocations().size() > 1; + } catch (NullPointerException e) { + return false; + } + } else { + try { + return getEipServiceJson().getJSONObject(LOCATIONS).length() > 1; + } catch (NullPointerException | JSONException e) { + return false; + } } + } @Override @@ -424,11 +573,11 @@ public final class Provider implements Parcelable { @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeString(getDomain()); - parcel.writeString(getMainUrlString()); + parcel.writeString(getMainUrl()); parcel.writeString(getProviderIp()); parcel.writeString(getProviderApiIp()); - parcel.writeString(getGeoipUrl().toString()); - parcel.writeString(getMotdUrl().toString()); + parcel.writeString(getGeoipUrl()); + parcel.writeString(getMotdUrl()); parcel.writeString(getDefinitionString()); parcel.writeString(getCaCert()); parcel.writeString(getEipServiceJsonString()); @@ -443,6 +592,13 @@ public final class Provider implements Parcelable { parcel.writeStringList(new ArrayList<>(lastMotdSeenHashes)); parcel.writeInt(shouldUpdateVpnCertificate ? 0 : 1); parcel.writeParcelable(introducer, 0); + if (apiVersion == 5) { + Gson gson = JSON.createGson().create(); + parcel.writeString(modelsProvider != null ? gson.toJson(modelsProvider) : ""); + parcel.writeString(modelsEIPService != null ? gson.toJson(modelsEIPService) : ""); + parcel.writeString(modelsBridges != null ? gson.toJson(modelsBridges) : ""); + parcel.writeString(modelsGateways != null ? gson.toJson(modelsGateways) : ""); + } } @@ -450,7 +606,7 @@ public final class Provider implements Parcelable { private Provider(Parcel in) { try { domain = in.readString(); - mainUrl.setUrl(new URL(in.readString())); + setMainUrl(in.readString()); String tmpString = in.readString(); if (!tmpString.isEmpty()) { providerIp = tmpString; @@ -461,11 +617,11 @@ public final class Provider implements Parcelable { } tmpString = in.readString(); if (!tmpString.isEmpty()) { - geoipUrl.setUrl(new URL(tmpString)); + geoipUrl = new URL(tmpString).toString(); } tmpString = in.readString(); if (!tmpString.isEmpty()) { - motdUrl.setUrl(new URL(tmpString)); + motdUrl = new URL(tmpString).toString(); } tmpString = in.readString(); if (!tmpString.isEmpty()) { @@ -505,6 +661,12 @@ public final class Provider implements Parcelable { this.lastMotdSeenHashes = new HashSet<>(lastMotdSeenHashes); this.shouldUpdateVpnCertificate = in.readInt() == 0; this.introducer = in.readParcelable(Introducer.class.getClassLoader()); + if (this.apiVersion == 5) { + this.setModelsProvider(in.readString()); + this.setService(in.readString()); + this.setBridges(in.readString()); + this.setGateways(in.readString()); + } } catch (MalformedURLException | JSONException e) { e.printStackTrace(); } @@ -516,24 +678,28 @@ public final class Provider implements Parcelable { if (o instanceof Provider) { Provider p = (Provider) o; return getDomain().equals(p.getDomain()) && - mainUrl.getDomain().equals(p.mainUrl.getDomain()) && - definition.toString().equals(p.getDefinition().toString()) && - eipServiceJson.toString().equals(p.getEipServiceJsonString()) && - geoIpJson.toString().equals(p.getGeoIpJsonString()) && - motdJson.toString().equals(p.getMotdJsonString()) && - providerIp.equals(p.getProviderIp()) && - providerApiIp.equals(p.getProviderApiIp()) && - apiUrl.equals(p.getApiUrl()) && - geoipUrl.equals(p.getGeoipUrl()) && - motdUrl.equals(p.getMotdUrl()) && - certificatePin.equals(p.getCertificatePin()) && - certificatePinEncoding.equals(p.getCertificatePinEncoding()) && - caCert.equals(p.getCaCert()) && - apiVersion.equals(p.getApiVersion()) && - privateKeyString.equals(p.getPrivateKeyString()) && - vpnCertificate.equals(p.getVpnCertificate()) && - allowAnonymous == p.allowsAnonymous() && - allowRegistered == p.allowsRegistered(); + getHostFromUrl(mainUrl).equals(getHostFromUrl(p.getMainUrl())) && + definition.toString().equals(p.getDefinition().toString()) && + eipServiceJson.toString().equals(p.getEipServiceJsonString()) && + geoIpJson.toString().equals(p.getGeoIpJsonString()) && + motdJson.toString().equals(p.getMotdJsonString()) && + providerIp.equals(p.getProviderIp()) && + providerApiIp.equals(p.getProviderApiIp()) && + apiUrl.equals(p.getApiUrl()) && + geoipUrl.equals(p.getGeoipUrl()) && + motdUrl.equals(p.getMotdUrl()) && + certificatePin.equals(p.getCertificatePin()) && + certificatePinEncoding.equals(p.getCertificatePinEncoding()) && + caCert.equals(p.getCaCert()) && + apiVersion == p.getApiVersion() && + privateKeyString.equals(p.getPrivateKeyString()) && + vpnCertificate.equals(p.getVpnCertificate()) && + allowAnonymous == p.allowsAnonymous() && + allowRegistered == p.allowsRegistered() && + Objects.equals(modelsProvider, p.modelsProvider) && + Objects.equals(modelsEIPService, p.modelsEIPService) && + Arrays.equals(modelsBridges, p.modelsBridges) && + Arrays.equals(modelsGateways, p.modelsGateways); } else return false; } @@ -551,7 +717,7 @@ public final class Provider implements Parcelable { @Override public int hashCode() { - return getMainUrlString().hashCode(); + return getMainUrl().hashCode(); } @Override @@ -561,20 +727,65 @@ public final class Provider implements Parcelable { private boolean parseDefinition(JSONObject definition) { try { + this.apiVersions = parseApiVersionsArray(); + this.apiVersion = selectPreferredApiVersion(); + this.domain = getDefinition().getString(Provider.DOMAIN); String pin = definition.getString(CA_CERT_FINGERPRINT); this.certificatePin = pin.split(":")[1].trim(); this.certificatePinEncoding = pin.split(":")[0].trim(); - this.apiUrl.setUrl(new URL(definition.getString(API_URL))); - this.allowAnonymous = definition.getJSONObject(Provider.SERVICE).getBoolean(PROVIDER_ALLOW_ANONYMOUS); - this.allowRegistered = definition.getJSONObject(Provider.SERVICE).getBoolean(PROVIDER_ALLOWED_REGISTERED); - this.apiVersion = getDefinition().getString(Provider.API_VERSION); - this.domain = getDefinition().getString(Provider.DOMAIN); + this.apiUrl = new URL(definition.getString(API_URL)).toString(); + JSONObject serviceJSONObject = definition.getJSONObject(Provider.SERVICE); + if (serviceJSONObject.has(PROVIDER_ALLOW_ANONYMOUS)) { + this.allowAnonymous = serviceJSONObject.getBoolean(PROVIDER_ALLOW_ANONYMOUS); + } + if (serviceJSONObject.has(PROVIDER_ALLOWED_REGISTERED)) { + this.allowRegistered = serviceJSONObject.getBoolean(PROVIDER_ALLOWED_REGISTERED); + } return true; - } catch (JSONException | ArrayIndexOutOfBoundsException | MalformedURLException e) { + } catch (JSONException | ArrayIndexOutOfBoundsException | MalformedURLException | NullPointerException | NumberFormatException e) { return false; } } + /** + @returns latest api version supported by client and server or the version set in 'api_version' + in case there's not a common supported version + */ + private int selectPreferredApiVersion() throws JSONException, NumberFormatException { + if (apiVersions.length == 0) { + return Integer.parseInt(getDefinition().getString(Provider.API_VERSION)); + } + + // apiVersion is a sorted Array + for (int i = apiVersions.length -1; i >= 0; i--) { + if (apiVersions[i] == BuildConfig.preferred_client_api_version || + apiVersions[i] < BuildConfig.preferred_client_api_version) { + return apiVersions[i]; + } + } + + return Integer.parseInt(getDefinition().getString(Provider.API_VERSION)); + } + + private int[] parseApiVersionsArray() { + int[] versionArray = new int[0]; + try { + JSONArray versions = getDefinition().getJSONArray(Provider.API_VERSIONS); + versionArray = new int[versions.length()]; + for (int i = 0; i < versions.length(); i++) { + try { + versionArray[i] = Integer.parseInt(versions.getString(i)); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + } catch (JSONException ignore) { + // this backend doesn't support api_versions yet + } + Arrays.sort(versionArray); + return versionArray; + } + public void setCaCert(String cert) { this.caCert = cert; } @@ -616,7 +827,7 @@ public final class Provider implements Parcelable { * @return true if last message of the day was shown more than 24h ago */ public boolean shouldShowMotdSeen() { - return !motdUrl.isDefault() && System.currentTimeMillis() - lastMotdSeen >= MOTD_TIMEOUT; + return !motdUrl.isEmpty() && System.currentTimeMillis() - lastMotdSeen >= MOTD_TIMEOUT; } /** @@ -652,7 +863,7 @@ public final class Provider implements Parcelable { } public boolean shouldUpdateMotdJson() { - return !motdUrl.isDefault() && System.currentTimeMillis() - lastMotdUpdate >= MOTD_TIMEOUT; + return !motdUrl.isEmpty() && System.currentTimeMillis() - lastMotdUpdate >= MOTD_TIMEOUT; } public void setMotdJson(@NonNull JSONObject motdJson) { @@ -709,9 +920,9 @@ public final class Provider implements Parcelable { } public boolean isDefault() { - return getMainUrl().isDefault() && - getApiUrl().isDefault() && - getGeoipUrl().isDefault() && + return getMainUrl().isEmpty() && + getApiUrl().isEmpty() && + getGeoipUrl().isEmpty() && certificatePin.isEmpty() && certificatePinEncoding.isEmpty() && caCert.isEmpty(); @@ -768,6 +979,10 @@ public final class Provider implements Parcelable { return introducer; } + public void setIntroducer(String introducerUrl) throws URISyntaxException { + this.introducer = Introducer.fromUrl(introducerUrl); + } + /** * resets everything except the main url, the providerIp and the geoip * service url (currently preseeded) @@ -777,16 +992,21 @@ public final class Provider implements Parcelable { eipServiceJson = new JSONObject(); geoIpJson = new JSONObject(); motdJson = new JSONObject(); - apiUrl = new DefaultedURL(); + apiUrl = ""; certificatePin = ""; certificatePinEncoding = ""; caCert = ""; - apiVersion = ""; + apiVersion = 3; privateKeyString = ""; vpnCertificate = ""; allowRegistered = false; allowAnonymous = false; lastGeoIpUpdate = 0L; lastEipServiceUpdate = 0L; + modelsProvider = null; + modelsGateways = null; + modelsBridges = null; + modelsEIPService = null; } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java index c2590012ea743c1f579fb33eeb27df892b360339..abd42812d1b7f8ea10a9a5b5983e398a85a12588 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java @@ -1,5 +1,14 @@ package se.leap.bitmaskclient.base.models; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; +import static se.leap.bitmaskclient.base.models.Constants.CERT; +import static se.leap.bitmaskclient.base.models.Constants.IAT_MODE; +import static se.leap.bitmaskclient.base.models.Constants.PORTS; +import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; +import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.gson.FieldNamingPolicy; @@ -7,11 +16,16 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; +import org.json.JSONArray; import org.json.JSONObject; import java.io.Serializable; +import java.util.Map; +import java.util.Vector; import de.blinkt.openvpn.core.connection.Connection; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsGateway; public class Transport implements Serializable { private final String type; @@ -32,6 +46,13 @@ public class Transport implements Serializable { this.options = options; } + public Transport(String type, String[] protocols, @Nullable String[] ports) { + this.type = type; + this.protocols = protocols; + this.ports = ports; + this.options = null; + } + public String getType() { return type; } @@ -67,6 +88,78 @@ public class Transport implements Serializable { fromJson(json.toString(), Transport.class); } + public static Transport createTransportFrom(ModelsBridge modelsBridge) { + if (modelsBridge == null) { + return null; + } + Map<String, Object> options = modelsBridge.getOptions(); + Transport.Options transportOptions = new Transport.Options((String) options.get(CERT), (String) options.get(IAT_MODE)); + Transport transport = new Transport( + modelsBridge.getType(), + new String[]{modelsBridge.getTransport()}, + new String[]{String.valueOf(modelsBridge.getPort())}, + transportOptions + ); + return transport; + } + + public static Transport createTransportFrom(ModelsGateway modelsGateway) { + if (modelsGateway == null) { + return null; + } + Transport transport = new Transport( + modelsGateway.getType(), + new String[]{modelsGateway.getTransport()}, + new String[]{String.valueOf(modelsGateway.getPort())} + ); + return transport; + } + + + @NonNull + public static Vector<Transport> createTransportsFrom(JSONObject gateway, int apiVersion) throws IllegalArgumentException { + Vector<Transport> transports = new Vector<>(); + try { + if (apiVersion >= 3) { + JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); + for (int i = 0; i < supportedTransports.length(); i++) { + Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i)); + transports.add(transport); + } + } else { + JSONObject capabilities = gateway.getJSONObject(CAPABILITIES); + JSONArray ports = capabilities.getJSONArray(PORTS); + JSONArray protocols = capabilities.getJSONArray(PROTOCOLS); + String[] portArray = new String[ports.length()]; + String[] protocolArray = new String[protocols.length()]; + for (int i = 0; i < ports.length(); i++) { + portArray[i] = String.valueOf(ports.get(i)); + } + for (int i = 0; i < protocols.length(); i++) { + protocolArray[i] = protocols.optString(i); + } + Transport transport = new Transport(OPENVPN.toString(), protocolArray, portArray); + transports.add(transport); + } + } catch (Exception e) { + throw new IllegalArgumentException(); + //throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields"); + } + return transports; + } + + public static Vector<Transport> createTransportsFrom(ModelsBridge modelsBridge) { + Vector<Transport> transports = new Vector<>(); + transports.add(Transport.createTransportFrom(modelsBridge)); + return transports; + } + + public static Vector<Transport> createTransportsFrom(ModelsGateway modelsGateway) { + Vector<Transport> transports = new Vector<>(); + transports.add(Transport.createTransportFrom(modelsGateway)); + return transports; + } + public static class Options implements Serializable { @Nullable private final String cert; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/BitmaskCoreProvider.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/BitmaskCoreProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..77cf9cf0e009bb6415115677991c86c6259eca18 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/BitmaskCoreProvider.java @@ -0,0 +1,28 @@ +package se.leap.bitmaskclient.base.utils; + +import de.blinkt.openvpn.core.NativeUtils; +import mobile.BitmaskMobile; +import mobilemodels.BitmaskMobileCore; + +public class BitmaskCoreProvider { + private static BitmaskMobileCore customMobileCore; + + /** + * Returns an empty BitmaskMobile instance, which can be currently only used to access + * bitmask-core's persistence layer API + * @return BitmaskMobileCore interface + */ + public static BitmaskMobileCore getBitmaskMobile() { + if (customMobileCore == null) { + return new BitmaskMobile(new PreferenceHelper.SharedPreferenceStore()); + } + return customMobileCore; + } + + public static void initBitmaskMobile(BitmaskMobileCore bitmaskMobileCore) { + if (!NativeUtils.isUnitTest()) { + throw new IllegalStateException("Initializing custom BitmaskMobileCore implementation outside of an unit test is not allowed"); + } + BitmaskCoreProvider.customMobileCore = bitmaskMobileCore; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java index cd5d1fca21ad0c2f564d902103e2e02d2426f5b1..0e192882cfcd197bc44d47d7e82b0d171974ce25 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java @@ -21,6 +21,8 @@ import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.os.Looper; +import android.util.Patterns; +import android.webkit.URLUtil; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -34,6 +36,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -119,6 +122,36 @@ public class ConfigHelper { return null; } + public static String parseX509CertificatesToString(ArrayList<X509Certificate> certs) { + StringBuilder sb = new StringBuilder(); + for (X509Certificate certificate : certs) { + + byte[] derCert = new byte[0]; + try { + derCert = certificate.getEncoded(); + byte[] encodedCert = Base64.encode(derCert); + String base64Cert = new String(encodedCert); + + // add cert header + sb.append("-----BEGIN CERTIFICATE-----\n"); + + // split base64 string into lines of 64 characters + int index = 0; + while (index < base64Cert.length()) { + sb.append(base64Cert.substring(index, Math.min(index + 64, base64Cert.length()))) + .append("\n"); + index += 64; + } + + // add cert footer + sb.append("-----END CERTIFICATE-----\n"); + } catch (CertificateEncodingException e) { + e.printStackTrace(); + } + } + return sb.toString().trim(); + } + public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{ Looper looper = Looper.myLooper(); if (looper != null && looper == context.getMainLooper()) { @@ -190,6 +223,13 @@ public class ConfigHelper { return matcher.matches(); } + public static boolean isNetworkUrl(String url) { + return url != null && URLUtil.isNetworkUrl(url) && Patterns.WEB_URL.matcher(url).matches(); + } + + public static boolean isDomainName(String url) { + return url != null && Patterns.DOMAIN_NAME.matcher(url).matches(); + } public static String getDomainFromMainURL(@NonNull String mainUrl) throws NullPointerException { return PublicSuffixDatabase.Companion.get().getEffectiveTldPlusOne(mainUrl).replaceFirst("http[s]?://", "").replaceFirst("/.*", ""); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/CredentialsParser.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/CredentialsParser.java new file mode 100644 index 0000000000000000000000000000000000000000..a62d548a72db8c94ab0c11d8202908edfe430e72 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/CredentialsParser.java @@ -0,0 +1,58 @@ +package se.leap.bitmaskclient.base.utils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.IOException; +import java.io.StringReader; + +import se.leap.bitmaskclient.base.models.Provider; + +public class CredentialsParser { + + public static void parseXml(String xmlString, Provider provider) throws XmlPullParserException, IOException { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser parser = factory.newPullParser(); + parser.setInput(new StringReader(xmlString)); + + String currentTag = null; + String ca = null; + String key = null; + String cert = null; + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG -> currentTag = parser.getName(); + case XmlPullParser.TEXT -> { + if (currentTag != null) { + switch (currentTag) { + case "ca" -> { + ca = parser.getText(); + ca = ca.trim(); + } + case "key" -> { + key = parser.getText(); + key = key.trim(); + } + case "cert" -> { + cert = parser.getText(); + cert = cert.trim(); + } + } + } + } + case XmlPullParser.END_TAG -> currentTag = null; + } + eventType = parser.next(); + } + + provider.setCaCert(ca); + provider.setPrivateKeyString(key); + provider.setVpnCertificate(cert); + + } +} + + diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index 694f21499af8070f4de34016290739036c652b41..25a9a3fbbea4df61355bb67c24e903f101e0c9ef 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -10,6 +10,7 @@ import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_USB; import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_WIFI; import static se.leap.bitmaskclient.base.models.Constants.ALWAYS_ON_SHOW_DIALOG; import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; +import static se.leap.bitmaskclient.base.models.Constants.COUNTRYCODE; import static se.leap.bitmaskclient.base.models.Constants.CUSTOM_PROVIDER_DOMAINS; import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER; import static se.leap.bitmaskclient.base.models.Constants.EIP_IS_ALWAYS_ON; @@ -30,6 +31,10 @@ import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY; import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_CONFIGURED; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_EIP_DEFINITION; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_BRIDGES; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_EIPSERVICE; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_GATEWAYS; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_PROVIDER; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MOTD; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MOTD_HASHES; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MOTD_LAST_SEEN; @@ -47,10 +52,12 @@ import static se.leap.bitmaskclient.base.models.Constants.USE_PORT_HOPPING; import static se.leap.bitmaskclient.base.models.Constants.USE_SNOWFLAKE; import static se.leap.bitmaskclient.base.models.Constants.USE_SYSTEM_PROXY; import static se.leap.bitmaskclient.base.models.Constants.USE_TUNNEL; +import static se.leap.bitmaskclient.base.utils.BitmaskCoreProvider.getBitmaskMobile; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; +import android.util.Base64; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -58,6 +65,8 @@ import androidx.annotation.WorkerThread; import androidx.security.crypto.EncryptedSharedPreferences; import androidx.security.crypto.MasterKey; +import com.google.gson.Gson; + import org.json.JSONException; import org.json.JSONObject; @@ -72,7 +81,11 @@ import java.util.Set; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.NativeUtils; +import io.swagger.client.JSON; +import mobile.BitmaskMobile; +import mobilemodels.BitmaskMobileCore; import se.leap.bitmaskclient.BuildConfig; +import se.leap.bitmaskclient.base.models.Introducer; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.tor.TorStatusObservable; @@ -154,7 +167,13 @@ public class PreferenceHelper { provider.setLastMotdSeen(preferences.getLong(PROVIDER_MOTD_LAST_SEEN, 0L)); provider.setLastMotdUpdate(preferences.getLong(PROVIDER_MOTD_LAST_UPDATED, 0L)); provider.setMotdLastSeenHashes(preferences.getStringSet(PROVIDER_MOTD_HASHES, new HashSet<>())); - } catch (MalformedURLException | JSONException e) { + provider.setModelsProvider(preferences.getString(PROVIDER_MODELS_PROVIDER, null)); + provider.setService(preferences.getString(PROVIDER_MODELS_EIPSERVICE, null)); + provider.setBridges(preferences.getString(PROVIDER_MODELS_BRIDGES, null)); + provider.setGateways(preferences.getString(PROVIDER_MODELS_GATEWAYS, null)); + provider.setIntroducer(getBitmaskMobile().getIntroducerURLByDomain(provider.getDomain())); + + } catch (Exception e) { e.printStackTrace(); } } @@ -199,12 +218,22 @@ public class PreferenceHelper { public static HashMap<String, Provider> getCustomProviders() { Set<String> providerDomains = getCustomProviderDomains(); HashMap<String, Provider> customProviders = new HashMap<>(); - for (String domain : providerDomains) { - String mainURL = preferences.getString(Provider.MAIN_URL + "." + domain, null); - if (mainURL != null) { - customProviders.put(mainURL, Provider.createCustomProvider(mainURL, domain)); + if (providerDomains.size() > 0) { + for (String domain : providerDomains) { + String mainURL = preferences.getString(Provider.MAIN_URL + "." + domain, null); + if (mainURL != null) { + Introducer introducer = null; + try { + introducer = Introducer.fromUrl(BitmaskCoreProvider.getBitmaskMobile().getIntroducerURLByDomain(domain)); + } catch (Exception e) { + e.printStackTrace(); + } + customProviders.put(mainURL, Provider.createCustomProvider(mainURL, domain, introducer)); + } } + } + return customProviders; } @@ -215,7 +244,7 @@ public class PreferenceHelper { SharedPreferences.Editor editor = preferences.edit(); for (Provider provider : providers) { String providerDomain = provider.getDomain(); - editor.putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()); + editor.putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrl()); newProviderDomains.add(providerDomain); } @@ -243,7 +272,7 @@ public class PreferenceHelper { putString(Provider.GEOIP_URL, provider.getGeoipUrl().toString()). putString(Provider.MOTD_URL, provider.getMotdUrl().toString()). putString(Provider.PROVIDER_API_IP, provider.getProviderApiIp()). - putString(Provider.MAIN_URL, provider.getMainUrlString()). + putString(Provider.MAIN_URL, provider.getMainUrl()). putString(Provider.KEY, provider.getDefinitionString()). putString(Provider.CA_CERT, provider.getCaCert()). putString(PROVIDER_EIP_DEFINITION, provider.getEipServiceJsonString()). @@ -252,7 +281,11 @@ public class PreferenceHelper { putString(PROVIDER_MOTD, provider.getMotdJsonString()). putStringSet(PROVIDER_MOTD_HASHES, provider.getMotdLastSeenHashes()). putLong(PROVIDER_MOTD_LAST_SEEN, provider.getLastMotdSeen()). - putLong(PROVIDER_MOTD_LAST_UPDATED, provider.getLastMotdUpdate()); + putLong(PROVIDER_MOTD_LAST_UPDATED, provider.getLastMotdUpdate()). + putString(PROVIDER_MODELS_GATEWAYS, provider.getGatewaysJson()). + putString(PROVIDER_MODELS_BRIDGES, provider.getBridgesJson()). + putString(PROVIDER_MODELS_EIPSERVICE, provider.getServiceJson()). + putString(PROVIDER_MODELS_PROVIDER, provider.getModelsProviderJson()); if (async) { editor.apply(); } else { @@ -263,9 +296,9 @@ public class PreferenceHelper { preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). putString(Provider.PROVIDER_IP + "." + providerDomain, provider.getProviderIp()). putString(Provider.PROVIDER_API_IP + "." + providerDomain, provider.getProviderApiIp()). - putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). - putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl().toString()). - putString(Provider.MOTD_URL + "." + providerDomain, provider.getMotdUrl().toString()). + putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrl()). + putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl()). + putString(Provider.MOTD_URL + "." + providerDomain, provider.getMotdUrl()). putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). @@ -450,6 +483,7 @@ public class PreferenceHelper { putBoolean(USE_SNOWFLAKE, isEnabled); if (!isEnabled) { TorStatusObservable.setProxyPort(-1); + TorStatusObservable.setSocksProxyPort(-1); } } @@ -613,6 +647,14 @@ public class PreferenceHelper { return getBoolean(ALWAYS_ON_SHOW_DIALOG, true); } + public static String getBaseCountry() { + return getString(COUNTRYCODE, null); + } + + public static void setBaseCountry(String countryCode) { + putString(COUNTRYCODE, countryCode); + } + public static String getPreferredCity() { return useObfuscationPinning() ? null : getString(PREFERRED_CITY, null); } @@ -774,4 +816,124 @@ public class PreferenceHelper { preferences.edit().clear().apply(); } } + + public static class SharedPreferenceStore implements mobilemodels.Store { + + public SharedPreferenceStore() throws IllegalArgumentException { + if (preferences == null) { + throw new IllegalStateException("Preferences not initialized."); + } + } + @Override + public void clear() throws Exception { + preferences.edit().clear().apply(); + } + + @Override + public void close() throws Exception { + + } + + @Override + public boolean contains(String s) throws Exception { + return preferences.contains(s); + } + + @Override + public boolean getBoolean(String s) { + return preferences.getBoolean(s, false); + } + + @Override + public boolean getBooleanWithDefault(String s, boolean b) { + return preferences.getBoolean(s, b); + } + + @Override + public byte[] getByteArray(String s) { + String encodedString = preferences.getString(s, ""); + try { + return Base64.decode(encodedString, Base64.DEFAULT); + } catch (IllegalArgumentException e) { + return new byte[0]; + } + } + + @Override + public byte[] getByteArrayWithDefault(String s, byte[] bytes) { + String encodedString = preferences.getString(s, ""); + try { + return Base64.decode(encodedString, Base64.DEFAULT); + } catch (IllegalArgumentException e) { + return bytes; + } + } + + @Override + public long getInt(String s) { + return preferences.getInt(s, 0); + } + + @Override + public long getIntWithDefault(String s, long l) { + return preferences.getInt(s, (int) l); + } + + @Override + public long getLong(String s) { + return preferences.getLong(s, 0L); + } + + @Override + public long getLongWithDefault(String s, long l) { + return preferences.getLong(s, l); + } + + @Override + public String getString(String s) { + return preferences.getString(s, ""); + } + + @Override + public String getStringWithDefault(String s, String s1) { + return preferences.getString(s, s1); + } + + @Override + public void open() throws Exception { + + } + + @Override + public void remove(String s) throws Exception { + preferences.edit().remove(s).apply(); + } + + @Override + public void setBoolean(String s, boolean b) { + preferences.edit().putBoolean(s, b).apply(); + } + + @Override + public void setByteArray(String s, byte[] bytes) { + String encodedString = Base64.encodeToString(bytes, Base64.DEFAULT); + preferences.edit().putString(s, encodedString).apply(); + } + + @Override + public void setInt(String s, long l) { + preferences.edit().putInt(s, (int) l).apply(); + } + + @Override + public void setLong(String s, long l) { + preferences.edit().putLong(s, l).apply(); + } + + @Override + public void setString(String s, String s1) { + preferences.edit().putString(s, s1).apply(); + } + } + } 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 7abe9416632375dccfb7d54ff706a46b0a233fd5..43af5200ee8c1fcd0787486b67c8e1ee90c1fbb0 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)); @@ -72,21 +72,39 @@ public class PrivateKeyHelper { return key; } - private EdECPrivateKey parseECPrivateKey(String ecKeyString) { - KeyFactory kf; + private PrivateKey parseECPrivateKey(String ecKeyString) { + 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 (EdECPrivateKey) 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/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java index bd626ce53bcb63e69317bf880293a6fdac57cb38..319337176567cddf84a3213c2d365a6f0422de35 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -27,6 +27,7 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETU import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.COUNTRYCODE; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; @@ -60,6 +61,7 @@ import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; +import androidx.core.os.BundleCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONObject; @@ -200,7 +202,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta switch (resultCode) { case CORRECTLY_DOWNLOADED_EIP_SERVICE: Log.d(TAG, "correctly updated service json"); - provider = resultData.getParcelable(PROVIDER_KEY); + provider = BundleCompat.getParcelable(resultData, PROVIDER_KEY, Provider.class); ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(provider); if (EipStatus.getInstance().isDisconnected()) { @@ -208,7 +210,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta } break; case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: - provider = resultData.getParcelable(PROVIDER_KEY); + provider = BundleCompat.getParcelable(resultData, PROVIDER_KEY, Provider.class); ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(provider); EipCommand.startVPN(appContext, false); @@ -218,7 +220,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta } break; case CORRECTLY_DOWNLOADED_GEOIP_JSON: - provider = resultData.getParcelable(PROVIDER_KEY); + provider = BundleCompat.getParcelable(resultData, PROVIDER_KEY, Provider.class); ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(provider); maybeStartEipService(resultData); @@ -386,6 +388,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta //setupNClostestGateway > 0: at least one failed gateway -> did the provider change it's gateways? Bundle parameters = new Bundle(); parameters.putLong(DELAY, 500); + parameters.putString(COUNTRYCODE, PreferenceHelper.getBaseCountry()); ProviderAPICommand.execute(appContext, ProviderAPI.DOWNLOAD_SERVICE_JSON, parameters, provider); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 16c9285516d77d47ee467b61b23dcc27f5e01e6a..d1ae7c43978137beb417a3d700b81684626c25dd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -20,6 +20,7 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT; import static se.leap.bitmaskclient.base.models.Constants.FULLNESS; import static se.leap.bitmaskclient.base.models.Constants.HOST; import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; import static se.leap.bitmaskclient.base.models.Constants.LOCATION; import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; import static se.leap.bitmaskclient.base.models.Constants.NAME; @@ -27,6 +28,7 @@ import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; import static se.leap.bitmaskclient.base.models.Constants.OVERLOAD; import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE; import static se.leap.bitmaskclient.base.models.Constants.VERSION; +import static se.leap.bitmaskclient.base.models.Transport.createTransportsFrom; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getExcludedApps; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningCert; @@ -47,12 +49,19 @@ import org.json.JSONObject; import java.io.IOException; import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsEIPService; +import io.swagger.client.model.ModelsGateway; +import io.swagger.client.model.ModelsLocation; +import se.leap.bitmaskclient.base.models.Transport; import se.leap.bitmaskclient.base.utils.ConfigHelper; /** @@ -71,6 +80,8 @@ public class Gateway { private JSONObject generalConfiguration; private JSONObject secrets; private JSONObject gateway; + private Vector<ModelsGateway> modelsGateways; + private Vector<ModelsBridge> modelsBridges; private JSONObject load; // the location of a gateway is its name @@ -78,6 +89,10 @@ public class Gateway { private int timezone; private int apiVersion; private Vector<VpnProfile> vpnProfiles; + private String remoteIpAddress; + private String remoteIpAddressV6; + private String host; + private String locationName; /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json @@ -94,32 +109,69 @@ public class Gateway { this.gateway = gateway; this.secrets = secrets; this.load = load; + this.apiVersion = eipDefinition.optInt(VERSION); + this.remoteIpAddress = gateway.optString(IP_ADDRESS); + this.remoteIpAddressV6 = gateway.optString(IP_ADDRESS6); + this.host = gateway.optString(HOST); + JSONObject location = getLocationInfo(gateway, eipDefinition); + this.locationName = location.optString(NAME); + this.timezone = location.optInt(TIMEZONE); + VpnConfigGenerator.Configuration configuration = getProfileConfig(Transport.createTransportsFrom(gateway, apiVersion)); + this.generalConfiguration = getGeneralConfiguration(eipDefinition); + this.name = configuration.profileName; + this.vpnProfiles = createVPNProfiles(configuration); + } + + + public Gateway(ModelsEIPService eipService, JSONObject secrets, ModelsGateway modelsGateway, int apiVersion) throws ConfigParser.ConfigParseError, NumberFormatException, JSONException, IOException { + this.apiVersion = apiVersion; + generalConfiguration = getGeneralConfiguration(eipService); + this.secrets = secrets; + this.modelsGateways = new Vector<>(); + this.modelsBridges = new Vector<>(); + this.modelsGateways.add(modelsGateway); + + this.remoteIpAddress = modelsGateway.getIpAddr(); + this.remoteIpAddressV6 = modelsGateway.getIp6Addr(); + this.host = modelsGateway.getHost(); + ModelsLocation modelsLocation = eipService.getLocations().get(modelsGateway.getLocation()); + if (modelsLocation != null) { + this.locationName = modelsLocation.getDisplayName(); + this.timezone = Integer.parseInt(modelsLocation.getTimezone()); + } else { + this.locationName = modelsGateway.getLocation(); + } + this.apiVersion = apiVersion; + VpnConfigGenerator.Configuration configuration = getProfileConfig(createTransportsFrom(modelsGateway)); + this.name = configuration.profileName; + this.vpnProfiles = createVPNProfiles(configuration); + } - apiVersion = getApiVersion(eipDefinition); - VpnConfigGenerator.Configuration configuration = getProfileConfig(eipDefinition, apiVersion); - generalConfiguration = getGeneralConfiguration(eipDefinition); - timezone = getTimezone(eipDefinition); + public Gateway(ModelsEIPService eipService, JSONObject secrets, ModelsBridge modelsBridge, int apiVersion) throws ConfigParser.ConfigParseError, JSONException, IOException { + this.apiVersion = apiVersion; + generalConfiguration = getGeneralConfiguration(eipService); + this.secrets = secrets; + this.modelsGateways = new Vector<>(); + this.modelsBridges = new Vector<>(); + this.modelsBridges.add(modelsBridge); + remoteIpAddress = modelsBridge.getIpAddr(); + host = modelsBridge.getHost(); + ModelsLocation modelsLocation = eipService.getLocations().get(modelsBridge.getLocation()); + if (modelsLocation != null) { + this.locationName = modelsLocation.getDisplayName(); + this.timezone = Integer.parseInt(modelsLocation.getTimezone()); + } else { + this.locationName = modelsBridge.getLocation(); + } this.apiVersion = apiVersion; + VpnConfigGenerator.Configuration configuration = getProfileConfig(Transport.createTransportsFrom(modelsBridge)); name = configuration.profileName; vpnProfiles = createVPNProfiles(configuration); } - private VpnConfigGenerator.Configuration getProfileConfig(JSONObject eipDefinition, int apiVersion) { - VpnConfigGenerator.Configuration config = new VpnConfigGenerator.Configuration(); - config.apiVersion = apiVersion; - config.preferUDP = getPreferUDP(); - config.experimentalTransports = allowExperimentalTransports(); - config.excludedApps = getExcludedApps(); - - config.remoteGatewayIP = config.useObfuscationPinning ? getObfuscationPinningIP() : gateway.optString(IP_ADDRESS); - config.useObfuscationPinning = useObfuscationPinning(); - config.profileName = config.useObfuscationPinning ? getObfuscationPinningGatewayLocation() : locationAsName(eipDefinition); - if (config.useObfuscationPinning) { - config.obfuscationProxyIP = getObfuscationPinningIP(); - config.obfuscationProxyPort = getObfuscationPinningPort(); - config.obfuscationProxyCert = getObfuscationPinningCert(); - config.obfuscationProxyKCP = getObfuscationPinningKCP(); - } - return config; + + + private VpnConfigGenerator.Configuration getProfileConfig(Vector<Transport> transports) { + return VpnConfigGenerator.Configuration.createProfileConfig(transports, apiVersion, remoteIpAddress, remoteIpAddressV6, locationName); } public void updateLoad(JSONObject load) { @@ -134,29 +186,33 @@ public class Gateway { } } - private int getTimezone(JSONObject eipDefinition) { - JSONObject location = getLocationInfo(eipDefinition); - return location.optInt(TIMEZONE); - } + private JSONObject getGeneralConfiguration(ModelsEIPService eipService) { + JSONObject config = new JSONObject(); + Map<String, Object> openvpnOptions = eipService.getOpenvpnConfiguration(); + Set<String> keys = openvpnOptions.keySet(); + Iterator<String> i = keys.iterator(); + while (i.hasNext()) { + try { + String key = i.next(); + Object o = openvpnOptions.get(key); + config.put(key, o); + } catch (JSONException e) { + e.printStackTrace(); + } + } - private int getApiVersion(JSONObject eipDefinition) { - return eipDefinition.optInt(VERSION); + return config; } public String getRemoteIP() { - return gateway.optString(IP_ADDRESS); + return remoteIpAddress; } public String getHost() { - return gateway.optString(HOST); + return host; } - private String locationAsName(JSONObject eipDefinition) { - JSONObject location = getLocationInfo(eipDefinition); - return location.optString(NAME); - } - - private JSONObject getLocationInfo(JSONObject eipDefinition) { + private JSONObject getLocationInfo(JSONObject gateway, JSONObject eipDefinition) { try { JSONObject locations = eipDefinition.getJSONObject(LOCATIONS); @@ -191,9 +247,8 @@ public class Gateway { */ private @NonNull Vector<VpnProfile> createVPNProfiles(VpnConfigGenerator.Configuration profileConfig) throws ConfigParser.ConfigParseError, IOException, JSONException { - VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, profileConfig); - Vector<VpnProfile> profiles = vpnConfigurationGenerator.generateVpnProfiles(); - return profiles; + VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, profileConfig); + return vpnConfigurationGenerator.generateVpnProfiles(); } public String getName() { @@ -250,8 +305,8 @@ public class Gateway { return getProfile(transportType, obfuscationTransportLayerProtocols) != null; } - public HashSet<Connection.TransportType> getSupportedTransports() { - HashSet<Connection.TransportType> transportTypes = new HashSet<>(); + public Set<Connection.TransportType> getSupportedTransports() { + Set<Connection.TransportType> transportTypes = new HashSet<>(); for (VpnProfile p : vpnProfiles) { transportTypes.add(p.getTransportType()); } @@ -277,4 +332,16 @@ public class Gateway { return new Gson().toJson(this, Gateway.class); } + public Gateway addTransport(Transport transport) { + Vector<Transport> transports = new Vector<>(); + transports.add(transport); + VpnConfigGenerator.Configuration profileConfig = getProfileConfig(transports); + try { + Vector<VpnProfile> profiles = createVPNProfiles(profileConfig); + vpnProfiles.addAll(profiles); + } catch (ConfigParser.ConfigParseError | IOException | JSONException e) { + e.printStackTrace(); + } + return this; + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 37360f81ea9868920145b7327d7a4d44773e79d4..d7f3b42ea7357741ffd86de7237d66e02954b5b2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -20,8 +20,10 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT; +import static se.leap.bitmaskclient.base.models.Constants.CERT; import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS; import static se.leap.bitmaskclient.base.models.Constants.HOST; +import static se.leap.bitmaskclient.base.models.Constants.IAT_MODE; import static se.leap.bitmaskclient.base.models.Constants.KCP; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SORTED_GATEWAYS; @@ -65,6 +67,9 @@ import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Connection.TransportType; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsEIPService; +import io.swagger.client.model.ModelsGateway; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.GatewayJson; @@ -415,84 +420,150 @@ public class GatewaysManager { return new Gson().toJson(gateways, listType); } - /** - * parse gateways from Provider's eip service - * @param provider - */ - private void parseDefaultGateways(Provider provider) { - try { - JSONObject eipDefinition = provider.getEipServiceJson(); - JSONObject secrets = secretsConfigurationFromCurrentProvider(); - JSONArray gatewaysDefined = new JSONArray(); - try { - gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); - } catch (Exception e) { - e.printStackTrace(); - } - - if (PreferenceHelper.useObfuscationPinning()) { - try { - Transport[] transports = new Transport[]{ - new Transport(OBFS4.toString(), - new String[]{getObfuscationPinningKCP() ? "kcp" : "tcp"}, - new String[]{getObfuscationPinningPort()}, - getObfuscationPinningCert())}; - GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false); - GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP( - - ), null, PINNED_OBFUSCATION_PROXY, capabilities); - Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString())); - addGateway(gateway); - } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { - e.printStackTrace(); - } - } else { - for (int i = 0; i < gatewaysDefined.length(); i++) { - try { - JSONObject gw = gatewaysDefined.getJSONObject(i); - Gateway aux = new Gateway(eipDefinition, secrets, gw); - if (gateways.get(aux.getHost()) == null) { - addGateway(aux); - } - } catch (JSONException | IOException e) { - e.printStackTrace(); - VpnStatus.logError("Unable to parse gateway config!"); - } catch (ConfigParser.ConfigParseError e) { - VpnStatus.logError("Unable to parse gateway config: " + e.getLocalizedMessage()); - } - } - } - } catch (NullPointerException npe) { - npe.printStackTrace(); - } + public void parseGatewaysV3(Provider provider) { + try { + JSONObject eipDefinition = provider.getEipServiceJson(); + JSONObject secrets = secretsConfigurationFromCurrentProvider(); + JSONArray gatewaysDefined = new JSONArray(); + try { + gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); + } catch (Exception e) { + e.printStackTrace(); + } + + if (PreferenceHelper.useObfuscationPinning()) { + try { + Transport[] transports = new Transport[]{ + new Transport(OBFS4.toString(), + new String[]{getObfuscationPinningKCP() ? "kcp" : "tcp"}, + new String[]{getObfuscationPinningPort()}, + getObfuscationPinningCert())}; + GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false); + GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP( + + ), null, PINNED_OBFUSCATION_PROXY, capabilities); + Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString())); + addGateway(gateway); + } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { + e.printStackTrace(); + } + } else { + for (int i = 0; i < gatewaysDefined.length(); i++) { + try { + JSONObject gw = gatewaysDefined.getJSONObject(i); + Gateway aux = new Gateway(eipDefinition, secrets, gw); + if (gateways.get(aux.getHost()) == null) { + addGateway(aux); + } + } catch (JSONException | IOException e) { + e.printStackTrace(); + VpnStatus.logError("Unable to parse gateway config!"); + } catch (ConfigParser.ConfigParseError e) { + VpnStatus.logError("Unable to parse gateway config: " + e.getLocalizedMessage()); + } + } + } + } catch (NullPointerException npe) { + npe.printStackTrace(); + } + + if (BuildConfig.BUILD_TYPE.equals("debug") && handleGatewayPinning()) { + return; + } + + // parse v3 menshen geoIP json variants + if (hasSortedGatewaysWithLoad(provider)) { + parseGatewaysWithLoad(provider); + } else { + parseSimpleGatewayList(provider); + } + } + + public void parseGatewaysV5(Provider provider) { + ModelsGateway[] modelsGateways = provider.getGateways(); + ModelsBridge[] modelsBridges = provider.getBridges(); + ModelsEIPService modelsEIPService = provider.getService(); + JSONObject secrets = secretsConfigurationFromCurrentProvider(); + int apiVersion = provider.getApiVersion(); + + if (PreferenceHelper.useObfuscationPinning()) { + try { + ModelsBridge modelsBridge = new ModelsBridge(); + modelsBridge.ipAddr(getObfuscationPinningIP()); + modelsBridge.port(Integer.valueOf(getObfuscationPinningPort())); + HashMap<String, Object> options = new HashMap<>(); + options.put(CERT, getObfuscationPinningCert()); + options.put(IAT_MODE, "0"); + modelsBridge.options(options); + modelsBridge.transport(getObfuscationPinningKCP() ? "kcp" : "tcp"); + modelsBridge.type(OBFS4.toString()); + modelsBridge.host(PINNED_OBFUSCATION_PROXY); + Gateway gateway = new Gateway(modelsEIPService, secrets, modelsBridge, provider.getApiVersion()); + addGateway(gateway); + } catch (NumberFormatException | ConfigParser.ConfigParseError | JSONException | + IOException e) { + e.printStackTrace(); + } + } else { + for (ModelsGateway modelsGateway : modelsGateways) { + String host = modelsGateway.getHost(); + Gateway gateway = gateways.get(host); + if (gateway == null) { + try { + addGateway(new Gateway(modelsEIPService, secrets, modelsGateway, apiVersion)); + } catch (ConfigParser.ConfigParseError | JSONException | IOException e) { + e.printStackTrace(); + } + } else { + addGateway(gateway.addTransport(Transport.createTransportFrom(modelsGateway))); + } + } + for (ModelsBridge modelsBridge : modelsBridges) { + String host = modelsBridge.getHost(); + Gateway gateway = gateways.get(host); + if (gateway == null) { + try { + addGateway(new Gateway(modelsEIPService, secrets, modelsBridge, apiVersion)); + } catch (ConfigParser.ConfigParseError | JSONException | IOException e) { + e.printStackTrace(); + } + } else { + addGateway(gateway.addTransport(Transport.createTransportFrom(modelsBridge))); + } + } + } + + if (BuildConfig.BUILD_TYPE.equals("debug")) { + handleGatewayPinning(); + } } private void parseSimpleGatewayList(Provider provider) { - try { - JSONObject geoIpJson = provider.getGeoIpJson(); - JSONArray gatewaylist = geoIpJson.getJSONArray(GATEWAYS); - - for (int i = 0; i < gatewaylist.length(); i++) { - try { - String key = gatewaylist.getString(i); - if (gateways.containsKey(key)) { - presortedList.add(gateways.get(key)); - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - } catch (NullPointerException | JSONException npe) { - Log.d(TAG, "No valid geoip json found: " + npe.getLocalizedMessage()); - } + try { + JSONObject geoIpJson = provider.getGeoIpJson(); + JSONArray gatewaylist = geoIpJson.getJSONArray(GATEWAYS); + + for (int i = 0; i < gatewaylist.length(); i++) { + try { + String key = gatewaylist.getString(i); + if (gateways.containsKey(key)) { + presortedList.add(gateways.get(key)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } catch (NullPointerException | JSONException npe) { + Log.d(TAG, "No valid geoip json found: " + npe.getLocalizedMessage()); + } } private boolean hasSortedGatewaysWithLoad(@Nullable Provider provider) { - if (provider == null) { - return false; - } - JSONObject geoIpJson = provider.getGeoIpJson(); - return geoIpJson.has(SORTED_GATEWAYS); + if (provider == null) { + return false; + } + JSONObject geoIpJson = provider.getGeoIpJson(); + return geoIpJson.has(SORTED_GATEWAYS); } private void parseGatewaysWithLoad(Provider provider) { @@ -534,30 +605,28 @@ public class GatewaysManager { } private void configureFromCurrentProvider() { - Provider provider = ProviderObservable.getInstance().getCurrentProvider(); - parseDefaultGateways(provider); - if (BuildConfig.BUILD_TYPE.equals("debug") && handleGatewayPinning()) { - return; - } - if (hasSortedGatewaysWithLoad(provider)) { - parseGatewaysWithLoad(provider); - } else { - parseSimpleGatewayList(provider); - } - + Provider provider = ProviderObservable.getInstance().getCurrentProvider(); + if (provider == null) { + return; + } + if (provider.getApiVersion() < 5) { + parseGatewaysV3(provider); + } else { + parseGatewaysV5(provider); + } } private boolean handleGatewayPinning() { - String host = PreferenceHelper.getPinnedGateway(); - if (host == null) { - return false; - } - Gateway gateway = gateways.get(host); - gateways.clear(); - if (gateway != null) { - gateways.put(host, gateway); - } - return true; + String host = PreferenceHelper.getPinnedGateway(); + if (host == null) { + return false; + } + Gateway gateway = gateways.get(host); + gateways.clear(); + if (gateway != null) { + gateways.put(host, gateway); + } + return true; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 5defa7e67d95eac69bb60afabc1d7e2e19847a96..f988dfa0174d63df4fcdf75863ecc9b7f24e69ce 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -19,22 +19,25 @@ package se.leap.bitmaskclient.eip; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; -import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; -import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; -import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; import static se.leap.bitmaskclient.base.models.Constants.KCP; -import static se.leap.bitmaskclient.base.models.Constants.PORTS; -import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.REMOTE; import static se.leap.bitmaskclient.base.models.Constants.TCP; -import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.UDP; - +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getExcludedApps; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningCert; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningGatewayLocation; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningIP; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningKCP; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningPort; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useObfuscationPinning; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -57,7 +60,6 @@ import se.leap.bitmaskclient.pluggableTransports.models.Obfs4Options; public class VpnConfigGenerator { private final JSONObject generalConfiguration; - private final JSONObject gateway; private final JSONObject secrets; Vector<Transport> transports = new Vector<>(); private final int apiVersion; @@ -69,6 +71,7 @@ public class VpnConfigGenerator { private final String obfuscationPinningCert; private final boolean obfuscationPinningKCP; private final String remoteGatewayIP; + private final String remoteGatewayIPv6; private final String profileName; private final Set<String> excludedApps; @@ -81,6 +84,7 @@ public class VpnConfigGenerator { boolean preferUDP; boolean experimentalTransports; String remoteGatewayIP = ""; + String remoteGatewayIPv6 = ""; String profileName = ""; Set<String> excludedApps = null; @@ -89,11 +93,33 @@ public class VpnConfigGenerator { String obfuscationProxyIP = ""; String obfuscationProxyPort = ""; String obfuscationProxyCert = ""; + Vector<Transport> transports = new Vector<>(); + + public static VpnConfigGenerator.Configuration createProfileConfig(Vector<Transport> transports, int apiVersion, String remoteIpAddress, String remoteIpAddressV6, String locationName) { + VpnConfigGenerator.Configuration config = new VpnConfigGenerator.Configuration(); + config.apiVersion = apiVersion; + config.preferUDP = getPreferUDP(); + config.experimentalTransports = allowExperimentalTransports(); + config.excludedApps = getExcludedApps(); + + config.remoteGatewayIP = config.useObfuscationPinning ? getObfuscationPinningIP() : remoteIpAddress; + config.remoteGatewayIPv6 = config.useObfuscationPinning ? null : remoteIpAddressV6; + config.useObfuscationPinning = useObfuscationPinning(); + config.profileName = config.useObfuscationPinning ? getObfuscationPinningGatewayLocation() : locationName; + if (config.useObfuscationPinning) { + config.obfuscationProxyIP = getObfuscationPinningIP(); + config.obfuscationProxyPort = getObfuscationPinningPort(); + config.obfuscationProxyCert = getObfuscationPinningCert(); + config.obfuscationProxyKCP = getObfuscationPinningKCP(); + } + config.transports = transports; + return config; + } } - public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, Configuration config) throws ConfigParser.ConfigParseError { + + public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, Configuration config) { this.generalConfiguration = generalConfiguration; - this.gateway = gateway; this.secrets = secrets; this.apiVersion = config.apiVersion; this.preferUDP = config.preferUDP; @@ -104,23 +130,10 @@ public class VpnConfigGenerator { this.obfuscationPinningCert = config.obfuscationProxyCert; this.obfuscationPinningKCP = config.obfuscationProxyKCP; this.remoteGatewayIP = config.remoteGatewayIP; + this.remoteGatewayIPv6 = config.remoteGatewayIPv6; + this.transports = config.transports; this.profileName = config.profileName; this.excludedApps = config.excludedApps; - checkCapabilities(); - } - - public void checkCapabilities() throws ConfigParser.ConfigParseError { - try { - if (apiVersion >= 3) { - JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); - for (int i = 0; i < supportedTransports.length(); i++) { - Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i)); - transports.add(transport); - } - } - } catch (Exception e) { - throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields"); - } } public Vector<VpnProfile> generateVpnProfiles() throws @@ -128,42 +141,28 @@ public class VpnConfigGenerator { NumberFormatException { Vector<VpnProfile> profiles = new Vector<>(); - if (apiVersion >= 3) { - for (Transport transport : transports){ - if (transport.getTransportType().isPluggableTransport()) { - Transport.Options transportOptions = transport.getOptions(); - if (!experimentalTransports && transportOptions != null && transportOptions.isExperimental()) { - continue; - } - } else if (transport.getTransportType() == OPENVPN && useObfuscationPinning) { + for (Transport transport : transports){ + if (transport.getTransportType().isPluggableTransport()) { + Transport.Options transportOptions = transport.getOptions(); + if (!experimentalTransports && transportOptions != null && transportOptions.isExperimental()) { continue; } - try { - profiles.add(createProfile(transport)); - } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { - e.printStackTrace(); - } + } else if (transport.getTransportType() == OPENVPN && useObfuscationPinning) { + continue; } - } else if (supportsOpenvpn()) { - // API v1 - TODO: let's remove support for API v1 soon try { - profiles.add(createApiv1OpenvpnProfile()); + profiles.add(createProfile(transport)); } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { e.printStackTrace(); } } + if (profiles.isEmpty()) { throw new ConfigParser.ConfigParseError("No supported transports detected."); } return profiles; } - private boolean supportsOpenvpn() { - return !useObfuscationPinning && - ((apiVersion >= 3 && getTransport(OPENVPN) != null) || - (apiVersion < 3 && !gatewayConfiguration(null).isEmpty())); - } - private String getConfigurationString(Transport transport) { return generalConfiguration() + newLine @@ -193,23 +192,8 @@ public class VpnConfigGenerator { return profile; } - @VisibleForTesting - protected VpnProfile createApiv1OpenvpnProfile() throws IOException, ConfigParser.ConfigParseError, JSONException { - String configuration = getConfigurationString(null); - ConfigParser icsOpenvpnConfigParser = new ConfigParser(); - icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); - - VpnProfile profile = icsOpenvpnConfigParser.convertProfile(OPENVPN); - profile.mName = profileName; - profile.mGatewayIp = remoteGatewayIP; - if (excludedApps != null) { - profile.mAllowedAppsVpn = new HashSet<>(excludedApps); - } - return profile; - } - - private Obfs4Options getObfs4Options(Transport transport) throws JSONException { - String ip = gateway.getString(IP_ADDRESS); + private Obfs4Options getObfs4Options(Transport transport) throws JSONException, NullPointerException { + String ip = remoteGatewayIP; if (useObfuscationPinning) { transport = new Transport(OBFS4.toString(), new String[]{obfuscationPinningKCP ? KCP : TCP}, @@ -223,9 +207,9 @@ public class VpnConfigGenerator { private String generalConfiguration() { String commonOptions = ""; try { - Iterator keys = generalConfiguration.keys(); + Iterator<String> keys = generalConfiguration.keys(); while (keys.hasNext()) { - String key = keys.next().toString(); + String key = keys.next(); commonOptions += key + " "; for (String word : String.valueOf(generalConfiguration.get(key)).split(" ")) @@ -243,32 +227,19 @@ public class VpnConfigGenerator { return commonOptions; } - private String gatewayConfiguration(@Nullable Transport transport) { + private String gatewayConfiguration(@NonNull Transport transport) { String configs = ""; StringBuilder stringBuilder = new StringBuilder(); try { - String ipAddress = null; - JSONObject capabilities = gateway.getJSONObject(CAPABILITIES); switch (apiVersion) { - default: - case 1: - case 2: - ipAddress = gateway.getString(IP_ADDRESS); - gatewayConfigApiv1(stringBuilder, ipAddress, capabilities); - break; - case 3: - case 4: - ipAddress = gateway.optString(IP_ADDRESS); - String ipAddress6 = gateway.optString(IP_ADDRESS6); - String[] ipAddresses = ipAddress6.isEmpty() ? - new String[]{ipAddress} : - new String[]{ipAddress6, ipAddress}; - if (transport == null) { - throw new NullPointerException("Transport is not allowed to be null in APIv3+"); - } + case 1, 2 -> gatewayConfigApiv1(transport, stringBuilder, remoteGatewayIP); + case 3, 4, 5 -> { + String[] ipAddresses = (remoteGatewayIPv6 == null || remoteGatewayIPv6.isEmpty()) ? + new String[]{remoteGatewayIP} : + new String[]{remoteGatewayIPv6, remoteGatewayIP}; gatewayConfigMinApiv3(transport, stringBuilder, ipAddresses); - break; + } } } catch (JSONException | NullPointerException e) { // TODO Auto-generated catch block @@ -300,15 +271,15 @@ public class VpnConfigGenerator { return null; } - private void gatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException { - int port; - String protocol; - JSONArray ports = capabilities.getJSONArray(PORTS); - JSONArray protocols = capabilities.getJSONArray(PROTOCOLS); - for (int i = 0; i < ports.length(); i++) { - port = ports.getInt(i); - for (int j = 0; j < protocols.length(); j++) { - protocol = protocols.optString(j); + private void gatewayConfigApiv1(Transport transport, StringBuilder stringBuilder, String ipAddress) throws JSONException { + if (transport == null || transport.getProtocols() == null || transport.getPorts() == null) { + VpnStatus.logError("Misconfigured provider: missing details for transport openvpn on gateway " + ipAddress); + return; + } + String[] ports = transport.getPorts(); + String[] protocols = transport.getProtocols(); + for (String port : ports) { + for (String protocol : protocols) { String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; stringBuilder.append(newRemote); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java index 751bf1dc1c09f44d6a4748761a8dba3590ee5231..63ae3731d6d5f745e88545c49edb3c4a05c5a0e9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java @@ -139,6 +139,11 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB return TorServiceCommand.getHttpTunnelPort(this); } + @Override + public int getTorSocksProxyPort() { + return TorServiceCommand.getSocksProxyPort(this); + } + @Override public boolean hasNetworkConnection() { try { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index 0d023dfccbd4a7dc8a851cf68350e86b16377caf..b370f0f6284013cd5d68edf6014a69e33b73d319 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -1,59 +1,42 @@ package se.leap.bitmaskclient.providersetup; -import static se.leap.bitmaskclient.BuildConfig.DEBUG_MODE; -import static se.leap.bitmaskclient.R.string.certificate_error; -import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; -import static se.leap.bitmaskclient.R.string.error_json_exception_user_message; -import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; import static se.leap.bitmaskclient.R.string.malformed_url; -import static se.leap.bitmaskclient.R.string.server_unreachable_message; -import static se.leap.bitmaskclient.R.string.service_is_down_error; -import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.providersetup.ProviderAPI.DELAY; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.providersetup.ProviderAPI.MISSING_NETWORK_CONNECTION; import static se.leap.bitmaskclient.providersetup.ProviderAPI.PARAMETERS; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.RECEIVER_KEY; import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION; import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT; -import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; +import static se.leap.bitmaskclient.providersetup.ProviderApiManagerV5.PROXY_HOST; +import static se.leap.bitmaskclient.providersetup.ProviderApiManagerV5.SOCKS_PROXY_SCHEME; import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_TOR_TIMEOUT; +import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_PROVIDER_JSON; import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF; -import static se.leap.bitmaskclient.tor.TorStatusObservable.getProxyPort; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import android.os.ResultReceiver; import android.util.Log; -import android.util.Pair; + +import androidx.core.content.IntentCompat; import org.jetbrains.annotations.Blocking; import org.json.JSONException; import org.json.JSONObject; -import java.io.IOException; -import java.net.ConnectException; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.net.UnknownServiceException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.TimeoutException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; - import de.blinkt.openvpn.core.VpnStatus; -import okhttp3.OkHttpClient; +import mobile.BitmaskMobile; +import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.tor.TorStatusObservable; public class ProviderApiManager extends ProviderApiManagerBase { @@ -80,11 +63,11 @@ public class ProviderApiManager extends ProviderApiManagerBase { } Provider provider = null; - if (command.getParcelableExtra(PROVIDER_KEY) != null) { - provider = command.getParcelableExtra(PROVIDER_KEY); + if (command.hasExtra(PROVIDER_KEY)) { + provider = IntentCompat.getParcelableExtra(command, PROVIDER_KEY, Provider.class); } else { //TODO: consider returning error back e.g. NO_PROVIDER - Log.e(TAG, action +" called without provider!"); + Log.e(TAG, action + " called without provider!"); return; } @@ -120,114 +103,64 @@ public class ProviderApiManager extends ProviderApiManagerBase { return; } - if (!provider.hasDefinition()) { - downloadProviderDefinition(provider); + result = downloadProviderDefinition(result, provider); + if (result.containsKey(ERRORS)) { + eventSender.sendToReceiverOrBroadcast(receiver, PROVIDER_NOK, result, provider); + return; + } } IProviderApiManager apiManager = versionedApiFactory.getProviderApiManager(provider); apiManager.handleAction(action, provider, parameters, receiver); } - private void downloadProviderDefinition(Provider provider) { + private Bundle downloadProviderDefinition(Bundle result, Provider provider) { getPersistedProviderUpdates(provider); if (provider.hasDefinition()) { - return; - } - getAndSetProviderJson(provider); - } - - private Bundle getAndSetProviderJson(Provider provider) { - Bundle result = new Bundle(); - - String providerJsonUrl = provider.getMainUrlString() + "/provider.json"; - String providerDotJsonString = fetch(providerJsonUrl, true); - - if (ConfigHelper.checkErroneousDownload(providerDotJsonString) || !isValidJson(providerDotJsonString)) { - eventSender.setErrorResult(result, malformed_url, null); return result; } - if (DEBUG_MODE) { - VpnStatus.logDebug("[API] PROVIDER JSON: " + providerDotJsonString); - } try { - JSONObject providerJson = new JSONObject(providerDotJsonString); - - if (provider.define(providerJson)) { - result.putBoolean(BROADCAST_RESULT_KEY, true); - } else { - return eventSender.setErrorResult(result, warning_corrupted_provider_details, ERROR_CORRUPTED_PROVIDER_JSON.toString()); + String providerString = fetch(provider, true); + if (ConfigHelper.checkErroneousDownload(providerString) || !isValidJson(providerString)) { + return eventSender.setErrorResult(result, malformed_url, null); } - } catch (JSONException e) { - eventSender.setErrorResult(result, providerDotJsonString); + JSONObject jsonObject = new JSONObject(providerString); + provider.define(jsonObject); + provider.setModelsProvider(providerString); + ProviderSetupObservable.updateProgress(DOWNLOADED_PROVIDER_JSON); + } catch (Exception e) { + return eventSender.setErrorResult(result, R.string.malformed_url, null); } + return result; } - - - /** - * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider. - * - */ - private String fetch(String url, boolean allowRetry) { - - JSONObject errorJson = new JSONObject(); - OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(resources); - - OkHttpClient okHttpClient = clientGenerator.initCommercialCAHttpClient(errorJson, getProxyPort()); - List<Pair<String, String>> headerArgs = new ArrayList<>(); - if (okHttpClient == null) { - return errorJson.toString(); - } - - String plainResponseBody; - + private String fetch(Provider provider, Boolean allowRetry) { + BitmaskMobile bm; try { - - plainResponseBody = ProviderApiConnector.requestStringFromServer(url, "GET", null, headerArgs, okHttpClient); - - } catch (NullPointerException npe) { - plainResponseBody = eventSender.formatErrorMessage(error_json_exception_user_message); - VpnStatus.logWarning("[API] Null response body for request " + url + ": " + npe.getLocalizedMessage()); - } catch (UnknownHostException | SocketTimeoutException e) { - plainResponseBody = eventSender.formatErrorMessage(server_unreachable_message); - VpnStatus.logWarning("[API] UnknownHostException or SocketTimeoutException for request " + url + ": " + e.getLocalizedMessage()); - } catch (MalformedURLException e) { - plainResponseBody = eventSender.formatErrorMessage(malformed_url); - VpnStatus.logWarning("[API] MalformedURLException for request " + url + ": " + e.getLocalizedMessage()); - } catch (SSLHandshakeException | SSLPeerUnverifiedException e) { - plainResponseBody = eventSender.formatErrorMessage(certificate_error); - VpnStatus.logWarning("[API] SSLHandshakeException or SSLPeerUnverifiedException for request " + url + ": " + e.getLocalizedMessage()); - } catch (ConnectException e) { - plainResponseBody = eventSender.formatErrorMessage(service_is_down_error); - VpnStatus.logWarning("[API] ConnectException for request " + url + ": " + e.getLocalizedMessage()); - } catch (IllegalArgumentException e) { - plainResponseBody = eventSender.formatErrorMessage(error_no_such_algorithm_exception_user_message); - VpnStatus.logWarning("[API] IllegalArgumentException for request " + url + ": " + e.getLocalizedMessage()); - } catch (UnknownServiceException e) { - //unable to find acceptable protocols - tlsv1.2 not enabled? - plainResponseBody = eventSender.formatErrorMessage(error_no_such_algorithm_exception_user_message); - VpnStatus.logWarning("[API] UnknownServiceException for request " + url + ": " + e.getLocalizedMessage()); - } catch (IOException e) { - plainResponseBody = eventSender.formatErrorMessage(error_io_exception_user_message); - VpnStatus.logWarning("[API] IOException for request " + url + ": " + e.getLocalizedMessage()); - } - - try { - if (allowRetry && - plainResponseBody != null && - plainResponseBody.contains(ERRORS) && - TorStatusObservable.getStatus() == OFF && - torHandler.startTorProxy() - ) { - return fetch(url, false); + bm = new BitmaskMobile(provider.getMainUrl(), new PreferenceHelper.SharedPreferenceStore()); + bm.setDebug(BuildConfig.DEBUG); + if (TorStatusObservable.isRunning() && TorStatusObservable.getSocksProxyPort() != -1) { + bm.setSocksProxy(SOCKS_PROXY_SCHEME + PROXY_HOST + ":" + TorStatusObservable.getSocksProxyPort()); + } else if (provider.hasIntroducer()) { + bm.setIntroducer(provider.getIntroducer().toUrl()); + } + return bm.getProvider(); + } catch (Exception e) { + try { + if (allowRetry && + TorStatusObservable.getStatus() == OFF && + torHandler.startTorProxy() + ) { + return fetch(provider, false); + } + } catch (InterruptedException | TimeoutException ex) { + ex.printStackTrace(); } - } catch (InterruptedException | IllegalStateException | TimeoutException e) { - e.printStackTrace(); } - return plainResponseBody; + return null; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java index ea50e7416dce018345b42a575f3ae7860aa0181f..60a41325e03a60bdc73a5228dd5e0eea6d0b06fc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -17,8 +17,10 @@ package se.leap.bitmaskclient.providersetup; -import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_BRIDGES; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_EIPSERVICE; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_GATEWAYS; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MODELS_PROVIDER; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MOTD; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MOTD_HASHES; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_MOTD_LAST_SEEN; @@ -31,30 +33,19 @@ import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP; import static se.leap.bitmaskclient.base.utils.CertificateHelper.getFingerprintFromCertificate; import static se.leap.bitmaskclient.base.utils.ConfigHelper.getDomainFromMainURL; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getFromPersistedProvider; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getLongFromPersistedProvider; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getStringSetFromPersistedProvider; -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.parsePrivateKeyFromString; import android.content.Intent; import android.content.res.Resources; -import android.os.Bundle; -import android.util.Base64; import org.json.JSONException; import org.json.JSONObject; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.Set; import java.util.concurrent.TimeoutException; @@ -62,7 +53,6 @@ import java.util.concurrent.TimeoutException; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.base.utils.PrivateKeyHelper; /** * Implements the logic of the http api calls. The methods of this class needs to be called from @@ -72,12 +62,15 @@ import se.leap.bitmaskclient.base.utils.PrivateKeyHelper; public abstract class ProviderApiManagerBase { private final static String TAG = ProviderApiManagerBase.class.getName(); + public static final String PROXY_HOST = "127.0.0.1"; + public static final String SOCKS_PROXY_SCHEME = "socks5://"; public interface ProviderApiServiceCallback { void broadcastEvent(Intent intent); boolean startTorService() throws InterruptedException, IllegalStateException, TimeoutException; void stopTorService(); int getTorHttpTunnelPort(); + int getTorSocksProxyPort(); boolean hasNetworkConnection(); void saveProvider(Provider p); } @@ -98,7 +91,6 @@ public abstract class ProviderApiManagerBase { void resetProviderDetails(Provider provider) { provider.reset(); - deleteProviderDetailsFromPreferences(provider.getDomain()); } protected boolean isValidJson(String jsonString) { @@ -143,7 +135,7 @@ public abstract class ProviderApiManagerBase { } protected void getPersistedProviderUpdates(Provider provider) { - String providerDomain = getDomainFromMainURL(provider.getMainUrlString()); + String providerDomain = getDomainFromMainURL(provider.getMainUrl()); if (hasUpdatedProviderDetails(providerDomain)) { provider.setCaCert(getPersistedProviderCA(providerDomain)); provider.define(getPersistedProviderDefinition(providerDomain)); @@ -156,6 +148,10 @@ public abstract class ProviderApiManagerBase { provider.setMotdLastSeenHashes(getPersistedMotdHashes(providerDomain)); provider.setLastMotdUpdate(getPersistedMotdLastUpdate(providerDomain)); provider.setMotdJson(getPersistedMotd(providerDomain)); + provider.setModelsProvider(getFromPersistedProvider(PROVIDER_MODELS_PROVIDER, providerDomain)); + provider.setService(getFromPersistedProvider(PROVIDER_MODELS_EIPSERVICE, providerDomain)); + provider.setGateways(getFromPersistedProvider(PROVIDER_MODELS_GATEWAYS, providerDomain)); + provider.setBridges(getFromPersistedProvider(PROVIDER_MODELS_BRIDGES, providerDomain)); } } @@ -219,43 +215,4 @@ public abstract class ProviderApiManagerBase { return PreferenceHelper.hasKey(Provider.KEY + "." + domain) && PreferenceHelper.hasKey(CA_CERT + "." + domain); } - protected Bundle loadCertificate(Provider provider, String certString) { - Bundle result = new Bundle(); - if (certString == null) { - eventSender.setErrorResult(result, vpn_certificate_is_invalid, null); - return result; - } - - try { - // API returns concatenated cert & key. Split them for OpenVPN options - String certificateString = null, keyString = null; - String[] certAndKey = certString.split("(?<=-\n)"); - for (int i = 0; i < certAndKey.length - 1; i++) { - if (certAndKey[i].contains("KEY")) { - keyString = certAndKey[i++] + certAndKey[i]; - } else if (certAndKey[i].contains("CERTIFICATE")) { - certificateString = certAndKey[i++] + certAndKey[i]; - } - } - - 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); - } - - ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(certificateString); - certificates.get(0).checkValidity(); - certificateString = Base64.encodeToString(certificates.get(0).getEncoded(), Base64.DEFAULT); - provider.setVpnCertificate( "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----"); - result.putBoolean(BROADCAST_RESULT_KEY, true); - } catch (CertificateException | NullPointerException e) { - e.printStackTrace(); - eventSender.setErrorResult(result, vpn_certificate_is_invalid, null); - } - return result; - } -} + } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerFactory.java index 5abec0afcca38708d07043c1c5e342c1c43e5638..3eae410f994335d44c7c4589489c938462fee82e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerFactory.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerFactory.java @@ -16,12 +16,10 @@ public class ProviderApiManagerFactory { } public IProviderApiManager getProviderApiManager(Provider provider) throws IllegalArgumentException { - switch (provider.getApiVersion()) { - case "5": - return new ProviderApiManagerV5(resources, callback); - default: - OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(resources); - return new ProviderApiManagerV3(resources, clientGenerator, callback); + if (provider.getApiVersion() >= 5) { + return new ProviderApiManagerV5(resources, callback); } + OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(resources); + return new ProviderApiManagerV3(resources, clientGenerator, callback); } } 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 a63899c07ff27394d1058351df36b843405cb2c9..965741f02475bc15dc46f684049be167cf3471f7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java @@ -17,7 +17,6 @@ package se.leap.bitmaskclient.providersetup; -import static android.text.TextUtils.isEmpty; import static se.leap.bitmaskclient.BuildConfig.DEBUG_MODE; import static se.leap.bitmaskclient.R.string.certificate_error; import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; @@ -29,6 +28,7 @@ import static se.leap.bitmaskclient.R.string.server_unreachable_message; import static se.leap.bitmaskclient.R.string.service_is_down_error; import static se.leap.bitmaskclient.R.string.setup_error_text; import static se.leap.bitmaskclient.R.string.setup_error_text_custom; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; import static se.leap.bitmaskclient.R.string.warning_expired_provider_cert; @@ -39,6 +39,8 @@ 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.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; import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; @@ -71,6 +73,7 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.getProxyPort; import android.content.res.Resources; import android.os.Bundle; import android.os.ResultReceiver; +import android.util.Base64; import android.util.Log; import android.util.Pair; @@ -87,7 +90,9 @@ import java.net.URL; import java.net.UnknownHostException; import java.net.UnknownServiceException; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; @@ -202,7 +207,7 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro ProviderObservable.getInstance().setProviderForDns(null); break; case DOWNLOAD_GEOIP_JSON: - if (!provider.getGeoipUrl().isDefault()) { + if (!provider.getGeoipUrl().isEmpty()) { boolean startEIP = parameters.getBoolean(EIP_ACTION_START); ProviderObservable.getInstance().setProviderForDns(provider); result = getGeoIPJson(provider); @@ -227,16 +232,14 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro public Bundle setupProvider(Provider provider, Bundle task) { Bundle currentDownload = new Bundle(); - if (isEmpty(provider.getMainUrlString()) || provider.getMainUrl().isDefault()) { + if (provider.getMainUrl().isEmpty()) { currentDownload.putBoolean(BROADCAST_RESULT_KEY, false); eventSender.setErrorResult(currentDownload, malformed_url, null); VpnStatus.logWarning("[API] MainURL String is not set. Cannot setup provider."); return currentDownload; } - getPersistedProviderUpdates(provider); currentDownload = validateProviderDetails(provider); - //provider certificate invalid if (currentDownload.containsKey(ERRORS)) { currentDownload.putParcelable(PROVIDER_KEY, provider); @@ -248,13 +251,15 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro resetProviderDetails(provider); } - currentDownload = getAndSetProviderJson(provider); - if (provider.hasDefinition() || (currentDownload.containsKey(BROADCAST_RESULT_KEY) && currentDownload.getBoolean(BROADCAST_RESULT_KEY))) { + if (!provider.hasDefinition()) { + currentDownload = getAndSetProviderJson(provider); + } + if (provider.hasDefinition()) { ProviderSetupObservable.updateProgress(DOWNLOADED_PROVIDER_JSON); if (!provider.hasCaCert()) { currentDownload = downloadCACert(provider); } - if (provider.hasCaCert() || (currentDownload.containsKey(BROADCAST_RESULT_KEY) && currentDownload.getBoolean(BROADCAST_RESULT_KEY))) { + if (provider.hasCaCert()) { ProviderSetupObservable.updateProgress(DOWNLOADED_CA_CERT); currentDownload = getAndSetEipServiceJson(provider); } @@ -274,7 +279,7 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro String providerDotJsonString; if(provider.getDefinitionString().length() == 0 || provider.getCaCert().isEmpty()) { - String providerJsonUrl = provider.getMainUrlString() + "/provider.json"; + String providerJsonUrl = provider.getMainUrl() + "/provider.json"; providerDotJsonString = downloadWithCommercialCA(providerJsonUrl, provider); } else { providerDotJsonString = downloadFromApiUrlWithProviderCA("/provider.json", provider); @@ -355,6 +360,41 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro return loadCertificate(provider, certString); } + private Bundle loadCertificate(Provider provider, String certString) { + Bundle result = new Bundle(); + if (certString == null) { + eventSender.setErrorResult(result, vpn_certificate_is_invalid, null); + return result; + } + + try { + // API returns concatenated cert & key. Split them for OpenVPN options + String certificateString = null, keyString = null; + String[] certAndKey = certString.split("(?<=-\n)"); + + for (int i = 0; i < certAndKey.length - 1; i++) { + if (certAndKey[i].contains("KEY")) { + keyString = certAndKey[i++] + certAndKey[i]; + } else if (certAndKey[i].contains("CERTIFICATE")) { + certificateString = certAndKey[i++] + certAndKey[i]; + } + } + + PrivateKey key = parsePrivateKeyFromString(keyString); + provider.setPrivateKeyString(getPEMFormattedPrivateKey(key)); + + ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(certificateString); + certificates.get(0).checkValidity(); + certificateString = Base64.encodeToString(certificates.get(0).getEncoded(), Base64.DEFAULT); + provider.setVpnCertificate( "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----"); + result.putBoolean(BROADCAST_RESULT_KEY, true); + } catch (CertificateException | NullPointerException e) { + e.printStackTrace(); + eventSender.setErrorResult(result, vpn_certificate_is_invalid, null); + } + return result; + } + /** * Fetches the geo ip Json, containing a list of gateways sorted by distance from the users current location. * Fetching is only allowed if the cache timeout of 1 h was reached, a valid geoip service URL exists and the @@ -367,13 +407,13 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro private Bundle getGeoIPJson(Provider provider) { Bundle result = new Bundle(); - if (!provider.shouldUpdateGeoIpJson() || provider.getGeoipUrl().isDefault() || VpnStatus.isVPNActive() || TorStatusObservable.getStatus() != OFF) { + if (!provider.shouldUpdateGeoIpJson() || provider.getGeoipUrl().isEmpty() || VpnStatus.isVPNActive() || TorStatusObservable.getStatus() != OFF) { result.putBoolean(BROADCAST_RESULT_KEY, false); return result; } try { - URL geoIpUrl = provider.getGeoipUrl().getUrl(); + URL geoIpUrl = new URL(provider.getGeoipUrl()); String geoipJsonString = downloadFromUrlWithProviderCA(geoIpUrl.toString(), provider, false); if (DEBUG_MODE) { @@ -389,7 +429,7 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro result.putBoolean(BROADCAST_RESULT_KEY, true); } - } catch (JSONException | NullPointerException e) { + } catch (JSONException | NullPointerException | MalformedURLException e) { result.putBoolean(BROADCAST_RESULT_KEY, false); e.printStackTrace(); } @@ -477,7 +517,7 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro * @return an empty string if it fails, the response body if not. */ private String downloadFromApiUrlWithProviderCA(String path, Provider provider) { - String baseUrl = provider.getApiUrlString(); + String baseUrl = provider.getApiUrl(); String urlString = baseUrl + path; return downloadFromUrlWithProviderCA(urlString, provider); } @@ -594,7 +634,7 @@ public class ProviderApiManagerV3 extends ProviderApiManagerBase implements IPro private boolean canConnect(Provider provider, Bundle result, int tries) { JSONObject errorJson = new JSONObject(); - String providerUrl = provider.getApiUrlString() + "/provider.json"; + String providerUrl = provider.getApiUrl() + "/provider.json"; OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), getProxyPort(), errorJson); if (okHttpClient == null) { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java index 4e698d2831de567a8ccfd3e9037ee8000e6744d6..9af14eda4e83566c746b90bee7a32ad74101b1a8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java @@ -1,24 +1,55 @@ package se.leap.bitmaskclient.providersetup; +import static android.text.TextUtils.isEmpty; +import static se.leap.bitmaskclient.R.string.malformed_url; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; +import static se.leap.bitmaskclient.R.string.warning_expired_provider_cert; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.base.models.Constants.COUNTRYCODE; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_SERVICE_JSON; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.providersetup.ProviderAPI.QUIETLY_UPDATE_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_V5_BRIDGES; +import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_V5_SERVICE_JSON; +import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_V5_GATEWAYS; import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF; import android.content.res.Resources; import android.os.Bundle; import android.os.ResultReceiver; import android.util.Log; +import androidx.annotation.Nullable; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import de.blinkt.openvpn.core.VpnStatus; +import mobile.BitmaskMobile; +import se.leap.bitmaskclient.BuildConfig; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.utils.ConfigHelper; +import se.leap.bitmaskclient.base.utils.CredentialsParser; import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.eip.EipStatus; +import se.leap.bitmaskclient.tor.TorStatusObservable; public class ProviderApiManagerV5 extends ProviderApiManagerBase implements IProviderApiManager { @@ -40,16 +71,17 @@ public class ProviderApiManagerV5 extends ProviderApiManagerBase implements IPro eventSender.sendToReceiverOrBroadcast(receiver, PROVIDER_NOK, result, provider); } break; - case DOWNLOAD_VPN_CERTIFICATE: - result = updateVpnCertificate(provider); + + case DOWNLOAD_SERVICE_JSON: + result = updateServiceInfos(provider, parameters); if (result.getBoolean(BROADCAST_RESULT_KEY)) { serviceCallback.saveProvider(provider); - ProviderSetupObservable.updateProgress(DOWNLOADED_VPN_CERTIFICATE); - eventSender.sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider); + eventSender.sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_EIP_SERVICE, result, provider); } else { - eventSender.sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider); + eventSender.sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_EIP_SERVICE, result, provider); } break; + case QUIETLY_UPDATE_VPN_CERTIFICATE: case UPDATE_INVALID_VPN_CERTIFICATE: result = updateVpnCertificate(provider); @@ -64,10 +96,220 @@ public class ProviderApiManagerV5 extends ProviderApiManagerBase implements IPro } - protected Bundle setupProvider(Provider provider, Bundle task) { - return null; + private Bundle updateServiceInfos(Provider provider, Bundle parameters) { + Bundle currentDownload = new Bundle(); + + BitmaskMobile bm; + try { + bm = new BitmaskMobile(provider.getMainUrl(), new PreferenceHelper.SharedPreferenceStore()); + } catch (IllegalStateException e) { + return eventSender.setErrorResult(currentDownload, R.string.config_error_found, null); + } + + try { + configureBaseCountryCode(bm, parameters); + } catch (Exception e) { + return eventSender.setErrorResult(currentDownload, R.string.config_error_found, null); + } + + try { + String serviceJson = bm.getService(); + provider.setService(serviceJson); + } catch (Exception e) { + return eventSender.setErrorResult(currentDownload, R.string.config_error_found, null); + } + + if (PreferenceHelper.getUseBridges()) { + try { + String bridgesJson = bm.getAllBridges("", "", "", ""); + provider.setBridges(bridgesJson); + } catch (Exception e) { + // TODO: send failed to fetch bridges event + } + } else { + try { + String gatewaysJson = bm.getAllGateways("", "", ""); + provider.setGateways(gatewaysJson); + } catch (Exception e) { + // TODO: send + return eventSender.setErrorResult(currentDownload, R.string.config_error_found, null); + + } + } + + return currentDownload; + } + protected Bundle setupProvider(Provider provider, Bundle parameters) { + Bundle currentDownload = new Bundle(); + + if (isEmpty(provider.getMainUrl()) || provider.getMainUrl().isEmpty()) { + currentDownload.putBoolean(BROADCAST_RESULT_KEY, false); + eventSender.setErrorResult(currentDownload, malformed_url, null); + VpnStatus.logWarning("[API] MainURL String is not set. Cannot setup provider."); + return currentDownload; + } + + //provider certificate invalid + if (currentDownload.containsKey(ERRORS)) { + currentDownload.putParcelable(PROVIDER_KEY, provider); + return currentDownload; + } + + BitmaskMobile bm; + try { + bm = new BitmaskMobile(provider.getMainUrl(), new PreferenceHelper.SharedPreferenceStore()); + bm.setDebug(BuildConfig.DEBUG); + if (TorStatusObservable.isRunning() && TorStatusObservable.getSocksProxyPort() != -1) { + bm.setSocksProxy(SOCKS_PROXY_SCHEME + PROXY_HOST + ":" + TorStatusObservable.getSocksProxyPort()); + } + if (provider.hasIntroducer()) { + bm.setIntroducer(provider.getIntroducer().toUrl()); + } + } catch (Exception e) { + // TODO: improve error message + return eventSender.setErrorResult(currentDownload, R.string.config_error_found, null); + } + + try { + configureBaseCountryCode(bm, parameters); + } catch (Exception e) { + return eventSender.setErrorResult(currentDownload, R.string.config_error_found, null); + } + + try { + String serviceJson = bm.getService(); + Log.d(TAG, "service Json reponse: " + serviceJson); + provider.setService(serviceJson); + ProviderSetupObservable.updateProgress(DOWNLOADED_V5_SERVICE_JSON); + } catch (Exception e) { + Log.w(TAG, "failed to fetch service.json: " + e.getMessage()); + e.printStackTrace(); + return eventSender.setErrorResult(currentDownload, R.string.error_json_exception_user_message, null); + } + + try { + // TODO: check if provider supports this API endpoint? + String gatewaysJson = bm.getAllGateways("", "", ""); + Log.d(TAG, "gateways Json reponse: " + gatewaysJson); + provider.setGateways(gatewaysJson); + ProviderSetupObservable.updateProgress(DOWNLOADED_V5_GATEWAYS); + } catch (Exception e) { + Log.w(TAG, "failed to fetch gateways: " + e.getMessage()); + e.printStackTrace(); + return eventSender.setErrorResult(currentDownload, R.string.error_json_exception_user_message, null); + } + + try { + // TODO: check if provider supports this API endpoint? + String bridgesJson = bm.getAllBridges("", "", "", ""); + Log.d(TAG, "bridges Json reponse: " + bridgesJson); + provider.setBridges(bridgesJson); + ProviderSetupObservable.updateProgress(DOWNLOADED_V5_BRIDGES); + } catch (Exception e) { + Log.w(TAG, "failed to fetch bridges: " + e.getMessage()); + e.printStackTrace(); + return eventSender.setErrorResult(currentDownload, R.string.error_json_exception_user_message, null); + } + + try { + String cert = bm.getOpenVPNCert(); + currentDownload = loadCredentials(provider, cert); + currentDownload = validateCertificateForProvider(currentDownload, provider); + ProviderSetupObservable.updateProgress(DOWNLOADED_VPN_CERTIFICATE); + } catch (Exception e) { + return eventSender.setErrorResult(currentDownload, R.string.error_json_exception_user_message, null); + } + + return currentDownload; + } + + private Bundle loadCredentials(Provider provider, String credentials) { + Bundle result = new Bundle(); + + try { + CredentialsParser.parseXml(credentials, provider); + } catch (XmlPullParserException | IOException e) { + e.printStackTrace(); + return eventSender.setErrorResult(result, vpn_certificate_is_invalid, null); + } + + result.putBoolean(BROADCAST_RESULT_KEY, true); + return result; + } + + @Nullable + private void configureBaseCountryCode(BitmaskMobile bm, Bundle parameters) throws Exception { + String cc = parameters.getString(COUNTRYCODE, null); + if (cc == null && + EipStatus.getInstance().isDisconnected() && + TorStatusObservable.getStatus() == OFF) { + try { + cc = bm.getGeolocation(); + } catch (Exception e) { + // print exception and ignore + e.printStackTrace(); + cc = ""; + } + } + bm.setCountryCode(cc); + } + + Bundle validateProviderDetails(Provider provider) { + Bundle result = new Bundle(); + result.putBoolean(BROADCAST_RESULT_KEY, false); + + if (!provider.hasDefinition()) { + return result; + } + + result = validateCertificateForProvider(result, provider); + + //invalid certificate or no certificate or unable to connect due other connectivity issues + if (result.containsKey(ERRORS) || (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) ) { + return result; + } + + result.putBoolean(BROADCAST_RESULT_KEY, true); + + return result; + } + + protected Bundle validateCertificateForProvider(Bundle result, Provider provider) { + String caCert = provider.getCaCert(); + + if (ConfigHelper.checkErroneousDownload(caCert)) { + VpnStatus.logWarning("[API] No provider cert."); + return result; + } + + ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(caCert); + if (certificates == null) { + return eventSender.setErrorResult(result, warning_corrupted_provider_cert, ERROR_INVALID_CERTIFICATE.toString()); + } + + ArrayList<X509Certificate> validCertificates = new ArrayList<>(); + int invalidCertificates = 0; + for (X509Certificate certificate : certificates) { + try { + certificate.checkValidity(); + validCertificates.add(certificate); + } catch (CertificateNotYetValidException | + CertificateExpiredException e) { + e.printStackTrace(); + invalidCertificates++; + } + } + if (validCertificates.isEmpty() && invalidCertificates > 0) { + return eventSender.setErrorResult(result, warning_expired_provider_cert, ERROR_INVALID_CERTIFICATE.toString()); + } + + provider.setCaCert(ConfigHelper.parseX509CertificatesToString(validCertificates)); + result.putParcelable(PROVIDER_KEY, provider); + result.putBoolean(BROADCAST_RESULT_KEY, true); + return result; + } protected Bundle updateVpnCertificate(Provider provider) { return null; diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiSetupBroadcastReceiver.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiSetupBroadcastReceiver.java index ee39499b9d051263ab53d849e8bae61d4f71ced6..4b8e8e185a991a27628c7f5e6167052aa4ce305e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiSetupBroadcastReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiSetupBroadcastReceiver.java @@ -18,12 +18,16 @@ package se.leap.bitmaskclient.providersetup; import static android.app.Activity.RESULT_CANCELED; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; +import androidx.core.os.BundleCompat; + import java.lang.ref.WeakReference; import se.leap.bitmaskclient.base.models.Constants; @@ -61,10 +65,10 @@ public class ProviderApiSetupBroadcastReceiver extends BroadcastReceiver { Log.d(TAG, "Broadcast resultCode: " + resultCode); Bundle resultData = intent.getParcelableExtra(Constants.BROADCAST_RESULT_KEY); - Provider handledProvider = resultData.getParcelable(Constants.PROVIDER_KEY); + Provider handledProvider = resultData == null ? null : BundleCompat.getParcelable(resultData, PROVIDER_KEY, Provider.class); if (handledProvider != null && setupInterface.getProvider() != null && - handledProvider.getMainUrlString().equalsIgnoreCase(setupInterface.getProvider().getMainUrlString())) { + handledProvider.getMainUrl().equalsIgnoreCase(setupInterface.getProvider().getMainUrl())) { switch (resultCode) { case ProviderAPI.PROVIDER_OK: setupInterface.handleProviderSetUp(handledProvider); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiTorHandler.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiTorHandler.java index b551cea0b1859621413c8768a15d2a7776f58568..3b7d424788205ab11b7ecb7834f863979574127e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiTorHandler.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiTorHandler.java @@ -29,7 +29,9 @@ public class ProviderApiTorHandler { } int port = serviceCallback.getTorHttpTunnelPort(); TorStatusObservable.setProxyPort(port); - return port != -1; + int socksPort = serviceCallback.getTorSocksProxyPort(); + TorStatusObservable.setSocksProxyPort(socksPort); + return port != -1 && socksPort != -1; } return false; } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java index 9eacae5da54db7114da219d24f60a4a6eff97494..63fbde09e06d82af063ed622d7ead5e93b98bc6b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java @@ -78,7 +78,7 @@ public class ProviderManager { private Set<String> getProviderUrlSetFromProviderSet(Set<Provider> providers) { HashSet<String> providerUrls = new HashSet<>(); for (Provider provider : providers) { - providerUrls.add(provider.getMainUrl().toString()); + providerUrls.add(provider.getMainUrl()); } return providerUrls; } @@ -152,10 +152,10 @@ public class ProviderManager { public boolean add(Provider element) { boolean addElement = element != null && - !defaultProviderURLs.contains(element.getMainUrlString()) && - !customProviders.containsKey(element.getMainUrlString()); + !defaultProviderURLs.contains(element.getMainUrl()) && + !customProviders.containsKey(element.getMainUrl()); if (addElement) { - customProviders.put(element.getMainUrlString(), element); + customProviders.put(element.getMainUrl(), element); return true; } return false; @@ -163,7 +163,7 @@ public class ProviderManager { public boolean remove(Object element) { return element instanceof Provider && - customProviders.remove(((Provider) element).getMainUrlString()) != null; + customProviders.remove(((Provider) element).getMainUrl()) != null; } public boolean addAll(Collection<? extends Provider> elements) { @@ -171,9 +171,9 @@ public class ProviderManager { boolean addedAll = true; while (iterator.hasNext()) { Provider p = (Provider) iterator.next(); - boolean containsKey = customProviders.containsKey(p.getMainUrlString()); + boolean containsKey = customProviders.containsKey(p.getMainUrl()); if (!containsKey) { - customProviders.put(p.getMainUrlString(), p); + customProviders.put(p.getMainUrl(), p); } addedAll = !containsKey && addedAll; } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java index 338a60e9838f94124a48735fafecac380bb87861..172d263658212b0f1473a320540a44b5d8c6c9af 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java @@ -24,6 +24,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.os.BundleCompat; import androidx.fragment.app.DialogFragment; import org.json.JSONObject; @@ -32,6 +33,7 @@ import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INITIAL_ACTION; @@ -136,7 +138,7 @@ public class ProviderSetupFailedDialog extends DialogFragment { break; case ERROR_NEW_URL_NO_VPN_PROVIDER: builder.setPositiveButton(R.string.retry, (dialog, id) - -> interfaceWithConfigurationWizard.addAndSelectNewProvider(provider.getMainUrlString())); + -> interfaceWithConfigurationWizard.addAndSelectNewProvider(provider.getMainUrl())); break; case ERROR_TOR_TIMEOUT: builder.setPositiveButton(R.string.retry, (dialog, id) -> { @@ -219,7 +221,7 @@ public class ProviderSetupFailedDialog extends DialogFragment { return; } if (savedInstanceState.containsKey(KEY_PROVIDER)) { - this.provider = savedInstanceState.getParcelable(KEY_PROVIDER); + this.provider = BundleCompat.getParcelable(savedInstanceState, KEY_PROVIDER, Provider.class); } if (savedInstanceState.containsKey(KEY_REASON_TO_FAIL)) { this.reasonToFail = savedInstanceState.getString(KEY_REASON_TO_FAIL); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java index 237f5bd27a19793761b589f0ae29b8d9a6edc55f..d57e1739539cfa411ff666fb7968945c72cefdfb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java @@ -38,6 +38,9 @@ public class ProviderSetupObservable { private boolean canceled = false; public static final int DOWNLOADED_PROVIDER_JSON = 20; public static final int DOWNLOADED_CA_CERT = 40; + public static final int DOWNLOADED_V5_SERVICE_JSON = 40; + public static final int DOWNLOADED_V5_GATEWAYS = 60; + public static final int DOWNLOADED_V5_BRIDGES = 80; public static final int DOWNLOADED_EIP_SERVICE_JSON = 60; public static final int DOWNLOADED_GEOIP_JSON = 80; public static final int DOWNLOADED_VPN_CERTIFICATE = 100; diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java index f45d2f2a2d3ede068f5d7a78abcf3bc6ff91f1f9..191db42bdf82c367b43ae95017452f476a696a6f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java @@ -32,6 +32,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; +import androidx.core.os.BundleCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; @@ -81,7 +82,7 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { - provider = savedInstanceState.getParcelable(EXTRA_PROVIDER); + provider = BundleCompat.getParcelable(savedInstanceState, EXTRA_PROVIDER, Provider.class); currentPosition = savedInstanceState.getInt(EXTRA_CURRENT_POSITION); switchProvider = savedInstanceState.getBoolean(EXTRA_SWITCH_PROVIDER); } @@ -173,7 +174,7 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal setupActionBar(); if (ProviderSetupObservable.isSetupRunning()) { - provider = ProviderSetupObservable.getResultData().getParcelable(PROVIDER_KEY); + provider = BundleCompat.getParcelable(ProviderSetupObservable.getResultData(), PROVIDER_KEY, Provider.class); if (provider != null) { currentPosition = adapter.getFragmentPostion(CONFIGURE_PROVIDER_FRAGMENT); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index 621cb41af271b15f8b45afe522eea00b457216a3..8b4b7ad8b7e51fdf4073b49cbef87bb82f8724df 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -40,6 +40,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; +import androidx.core.os.BundleCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -48,7 +49,9 @@ import java.beans.PropertyChangeListener; import java.util.List; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.models.Constants; import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.databinding.FConfigureProviderBinding; import se.leap.bitmaskclient.eip.EipSetupListener; import se.leap.bitmaskclient.eip.EipSetupObserver; @@ -136,7 +139,9 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Prop handleResult(ProviderSetupObservable.getResultCode(), ProviderSetupObservable.getResultData(), true); } else { ProviderSetupObservable.startSetup(); - ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); + Bundle parameters = new Bundle(); + parameters.putString(Constants.COUNTRYCODE, PreferenceHelper.getBaseCountry()); + ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, parameters, setupActivityCallback.getSelectedProvider()); } } @@ -211,34 +216,31 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Prop } private void handleResult(int resultCode, Bundle resultData, boolean resumeSetup) { - Provider provider = resultData.getParcelable(PROVIDER_KEY); + Provider provider = BundleCompat.getParcelable(resultData, PROVIDER_KEY, Provider.class); + if (ignoreProviderAPIUpdates || provider == null || (setupActivityCallback.getSelectedProvider() != null && - !setupActivityCallback.getSelectedProvider().getMainUrlString().equals(provider.getMainUrlString()))) { + !setupActivityCallback.getSelectedProvider().getMainUrl().equals(provider.getMainUrl()))) { return; } switch (resultCode) { case PROVIDER_OK: setupActivityCallback.onProviderSelected(provider); - if (provider.allowsAnonymous()) { - ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + if (provider.getApiVersion() < 5) { + if (provider.allowsAnonymous()) { + ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + } else { + // TODO: implement error message that this client only supports anonymous usage + } } else { - // TODO: implement error message that this client only supports anonymous usage + sendSuccess(resumeSetup); } break; case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: setupActivityCallback.onProviderSelected(provider); - handler.postDelayed(() -> { - if (!ProviderSetupObservable.isCanceled()) { - try { - setupActivityCallback.onConfigurationSuccess(); - } catch (NullPointerException npe) { - // callback disappeared in the meanwhile - } - } - }, resumeSetup ? 0 : 750); + sendSuccess(resumeSetup); break; case PROVIDER_NOK: case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: @@ -252,4 +254,15 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Prop } } + private void sendSuccess(boolean resumeSetup) { + handler.postDelayed(() -> { + if (!ProviderSetupObservable.isCanceled()) { + try { + setupActivityCallback.onConfigurationSuccess(); + } catch (NullPointerException npe) { + // callback disappeared in the meanwhile + } + } + }, resumeSetup ? 0 : 750); + } } \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java index 849ac681f15d3c3b3c3c5e2c9ddd332c60c7775d..c6671a9024f506c8d9cc05847c3228504eb202e0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java @@ -13,6 +13,7 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.os.BundleCompat; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.databinding.FEmptyPermissionSetupBinding; @@ -76,7 +77,7 @@ public class EmptyPermissionSetupFragment extends BaseSetupFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.vpnPermissionIntent = getArguments().getParcelable(EXTRA_VPN_INTENT); + this.vpnPermissionIntent = BundleCompat.getParcelable(getArguments(), EXTRA_VPN_INTENT, Intent.class); this.notificationPermissionAction = getArguments().getString(EXTRA_NOTIFICATION_PERMISSON_ACTION); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java index 3ac122f8c714e1b9eb19f9e38086c9e99141f006..e4302be5a82eefedbd8cbd4ad2670a0a97c33057 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -3,10 +3,8 @@ package se.leap.bitmaskclient.providersetup.fragments; import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.ADD_PROVIDER; import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.INVITE_CODE_PROVIDER; -import android.Manifest; import android.app.Activity; import android.content.Intent; -import android.content.pm.PackageManager; import android.graphics.Typeface; import android.os.Bundle; import android.text.Editable; @@ -20,19 +18,17 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import java.util.ArrayList; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.providersetup.activities.scanner.ScannerActivity; import se.leap.bitmaskclient.base.models.Introducer; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.ViewHelper; import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; +import se.leap.bitmaskclient.providersetup.activities.scanner.ScannerActivity; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel; import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory; diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java index 00117336de5112ce53cbe2d91090b359a36012d7..fe21c6b37d1e27c431bcc98d3916cdfef09d29be 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -1,11 +1,12 @@ package se.leap.bitmaskclient.providersetup.fragments.viewmodel; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDomainName; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.isNetworkUrl; + import android.content.Context; import android.content.res.AssetManager; import android.text.InputType; -import android.util.Patterns; import android.view.View; -import android.webkit.URLUtil; import androidx.lifecycle.ViewModel; @@ -51,7 +52,7 @@ public class ProviderSelectionViewModel extends ViewModel { public boolean isValidConfig() { if (selected == ADD_PROVIDER) { - return customUrl != null && (Patterns.DOMAIN_NAME.matcher(customUrl).matches() || (URLUtil.isNetworkUrl(customUrl) && Patterns.WEB_URL.matcher(customUrl).matches())); + return isNetworkUrl(customUrl) || isDomainName(customUrl); } if (selected == INVITE_CODE_PROVIDER) { try { @@ -121,7 +122,7 @@ public class ProviderSelectionViewModel extends ViewModel { } public String getCustomUrl() { - if (customUrl != null && Patterns.DOMAIN_NAME.matcher(customUrl).matches()) { + if (isDomainName(customUrl)) { return "https://" + customUrl; } return customUrl; diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java index abc029ff277384b40c11417dc564e50a169c5619..4c6ddaba15e37c8ae7d8a4f1b6219e8d38b19776 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java @@ -134,6 +134,21 @@ public class TorServiceCommand { return -1; } + @WorkerThread + public static int getSocksProxyPort(Context context) { + try { + TorServiceConnection torServiceConnection = initTorServiceConnection(context); + if (torServiceConnection != null) { + int tunnelPort = torServiceConnection.getService().getSocksPort(); + torServiceConnection.close(); + return tunnelPort; + } + } catch (InterruptedException | IllegalStateException e) { + e.printStackTrace(); + } + return -1; + } + private static boolean isNotCancelled() { return !TorStatusObservable.isCancelled(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java index b1ad608496f3da0bfdf5add62e77ecdb39bf000f..5a49fda2460419151f6b4164bf2930c0abf6c24d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java @@ -95,6 +95,7 @@ public class TorStatusObservable { private String lastTorLog = ""; private String lastSnowflakeLog = ""; private int port = -1; + private int socksPort = -1; private int bootstrapPercent = -1; private int retrySnowflakeRendezVous = 0; private final Vector<String> lastLogs = new Vector<>(100); @@ -321,6 +322,15 @@ public class TorStatusObservable { return getInstance().port; } + public static void setSocksProxyPort(int port) { + getInstance().socksPort = port; + instance.notifyObservers(); + } + + public static int getSocksProxyPort() { + return getInstance().socksPort; + } + @Nullable public static String getLastTorLog() { diff --git a/app/src/test/java/io/swagger/client/JSONTest.java b/app/src/test/java/io/swagger/client/JSONTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5baa7e790dd327268b318193def4f5c79aa9eda1 --- /dev/null +++ b/app/src/test/java/io/swagger/client/JSONTest.java @@ -0,0 +1,31 @@ +package io.swagger.client; + +import org.junit.Test; + +import java.io.IOException; +import static org.junit.Assert.*; + +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; + +import com.google.gson.JsonSyntaxException; + +import de.blinkt.openvpn.core.ConfigParser; +import io.swagger.client.model.ModelsProvider; +import se.leap.bitmaskclient.testutils.TestSetupHelper; + +public class JSONTest { + + @Test + public void testProviderJsonParsing_testBackwardsCompatibility_v4() throws IOException { + String boblove = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.net.json")); + ModelsProvider p = JSON.createGson().create().fromJson(boblove, ModelsProvider.class); + assertNotNull(p); + assertEquals("riseup.net", p.getDomain()); + } + + @Test + public void testProvidingNull() throws IOException { + String p = JSON.createGson().create().toJson(null); + assertEquals("null", p); + } +} diff --git a/app/src/test/java/se/leap/bitmaskclient/base/models/ProviderTest.java b/app/src/test/java/se/leap/bitmaskclient/base/models/ProviderTest.java index ee6cd30f598c9853641a847cfefc25a5f2c078d6..dc35ecb9fd3dc9b5d77bab251f4e7305fb4b9e15 100644 --- a/app/src/test/java/se/leap/bitmaskclient/base/models/ProviderTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/base/models/ProviderTest.java @@ -5,8 +5,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import android.os.Build; + import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import se.leap.bitmaskclient.base.utils.BuildConfigHelper; import se.leap.bitmaskclient.testutils.MockHelper; @@ -16,6 +21,8 @@ import se.leap.bitmaskclient.testutils.TestSetupHelper; * Created by cyberta on 12.02.18. */ +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class ProviderTest { @Before diff --git a/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java index 6e0ceb56a65704bb00ff63af98b4bd93a8fccf37..c638716514702db8c15d6280d1baae49bf492c92 100644 --- a/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java @@ -1,6 +1,7 @@ package se.leap.bitmaskclient.base.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -11,6 +12,12 @@ import org.junit.runner.RunWith; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import se.leap.bitmaskclient.testutils.TestSetupHelper; + @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(DataProviderRunner.class) public class ConfigHelperTest { @@ -57,4 +64,19 @@ public class ConfigHelperTest { assertEquals("domain.co.uk", ConfigHelper.getDomainFromMainURL("https://subdomain.domain.co.uk")); assertEquals("domain.co.uk", ConfigHelper.getDomainFromMainURL("https://domain.co.uk")); } + + @Test + public void testParseX509CertificatesFromString() throws IOException { + ArrayList<X509Certificate> certs = ConfigHelper.parseX509CertificatesFromString(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("updated_cert.pem"))); + assertTrue(certs != null); + } + + @Test + public void testParseX509CertificatesToString() throws IOException { + String certsString = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("updated_cert.pem")); + ArrayList<X509Certificate> certs = ConfigHelper.parseX509CertificatesFromString(certsString); + String parsedCerts = ConfigHelper.parseX509CertificatesToString(certs); + assertEquals(certsString, parsedCerts); + } + } \ No newline at end of file diff --git a/app/src/test/java/se/leap/bitmaskclient/base/utils/CredentialsParserTest.java b/app/src/test/java/se/leap/bitmaskclient/base/utils/CredentialsParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..55a9e94f84695be2fb24efb7e947eba8de5b7ecc --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/base/utils/CredentialsParserTest.java @@ -0,0 +1,57 @@ +package se.leap.bitmaskclient.base.utils; + +import static org.junit.Assert.assertEquals; + +import android.os.Build; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.testutils.TestSetupHelper; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.P}) +public class CredentialsParserTest { + + @Test + public void testCertificateResponse() throws IOException, XmlPullParserException { + String ed25519_creds = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ed25519_credentials.pem")); + Provider provider = new Provider("https://demo.bitmask.net"); + CredentialsParser.parseXml(ed25519_creds, provider); + assertEquals( + "-----BEGIN PRIVATE KEY-----\n" + + "MC4CAQAwBQYDK2VwBCIEIF+HZvpSdhnTbYeT635bT2+IU4FbW3EWlHuUnXvhb10m\n" + + "-----END PRIVATE KEY-----", provider.getPrivateKeyString()); + assertEquals( + "-----BEGIN CERTIFICATE-----\n" + + "MIIBgzCCASigAwIBAgIRALD3Z4SsobpcU7tcC0r9JOQwCgYIKoZIzj0EAwIwNzE1\n" + + "MDMGA1UEAwwsUHJvdmlkZXIgUm9vdCBDQSAoY2xpZW50IGNlcnRpZmljYXRlcyBv\n" + + "bmx5ISkwHhcNMjQxMTA1MTU0MjU0WhcNMjQxMTI5MTU0MjU0WjAUMRIwEAYDVQQD\n" + + "EwlVTkxJTUlURUQwKjAFBgMrZXADIQC5QkZAcpkQ3Rm54gN5iLEU1Zp1w+patXVT\n" + + "W9GRXmFz+6NnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMC\n" + + "MB0GA1UdDgQWBBRMxeMW4vqGK7FBkDt2+8upfkK1kzAfBgNVHSMEGDAWgBS0pVQs\n" + + "1wnvNYG0AnmkxUcLOw+BLDAKBggqhkjOPQQDAgNJADBGAiEAg112+zWMm9qrPTvK\n" + + "99IMa+wbeNzZLSoN9xewf5rxOX0CIQCvMi08JcajsAJ9Dg6YAQgpmFdb35HDCzve\n" + + "lhkTCWJpgQ==\n" + + "-----END CERTIFICATE-----", provider.getVpnCertificate()); + assertEquals( + "-----BEGIN CERTIFICATE-----\n" + + "MIIBozCCAUigAwIBAgIBATAKBggqhkjOPQQDAjA3MTUwMwYDVQQDDCxQcm92aWRl\n" + + "ciBSb290IENBIChjbGllbnQgY2VydGlmaWNhdGVzIG9ubHkhKTAeFw0yNDEwMjMx\n" + + "MjA0MjRaFw0yOTEwMjMxMjA5MjRaMDcxNTAzBgNVBAMMLFByb3ZpZGVyIFJvb3Qg\n" + + "Q0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMFkwEwYHKoZIzj0CAQYIKoZI\n" + + "zj0DAQcDQgAEMImwbNTDrXMeWfyTb2TMNzXNr79OsKjLDdZWqVT0iHMI8apo2P4H\n" + + "eXCHVGjS2Z+jpyI1u9ic3igThsKEmdZMSKNFMEMwDgYDVR0PAQH/BAQDAgKkMBIG\n" + + "A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFLSlVCzXCe81gbQCeaTFRws7D4Es\n" + + "MAoGCCqGSM49BAMCA0kAMEYCIQCw88nXg/vs/KgGqH1uPs9oZkOxucVn/ZEznYzg\n" + + "szLhtAIhAPY32oHwmj3yHO9H2Jp7x0CoHuu1fKd9fQTBvEEbi7o9\n" + + "-----END CERTIFICATE-----", provider.getCaCert()); + } + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/base/utils/PreferenceHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/base/utils/PreferenceHelperTest.java index e03fccff7243510b5a2bc7ccae6550e5f01259a4..1b093f62ff523e437d77fe0230a99b7f505e9fbe 100644 --- a/app/src/test/java/se/leap/bitmaskclient/base/utils/PreferenceHelperTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/base/utils/PreferenceHelperTest.java @@ -1,5 +1,13 @@ package se.leap.bitmaskclient.base.utils; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_EIP_DEFINITION; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + import android.content.SharedPreferences; import org.junit.Before; @@ -8,15 +16,6 @@ import org.junit.Test; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.testutils.MockSharedPreferences; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_EIP_DEFINITION; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.preferUDP; -import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; - /** * Created by cyberta on 17.01.18. */ 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 5ad9d2e791119056c221d7d9d1b25aed153c5f42..5b1d45546881a52550ecadefeac38b5f369e2581 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/java/se/leap/bitmaskclient/eip/GatewayTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewayTest.java index 66c139b2686c852b8c88a268fa681ec66fd8fb75..88e58cd8698a29e1e454ce35fbd879ea716cdbdf 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/GatewayTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewayTest.java @@ -10,6 +10,7 @@ import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider; import android.content.Context; import android.content.SharedPreferences; +import android.os.Build; import androidx.annotation.Nullable; @@ -17,8 +18,11 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.IOException; import java.util.Arrays; @@ -35,6 +39,8 @@ import se.leap.bitmaskclient.base.utils.TimezoneHelper; import se.leap.bitmaskclient.testutils.MockSharedPreferences; import se.leap.bitmaskclient.testutils.TestSetupHelper; +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class GatewayTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java index b79c34ae1acf624c20418224a8169bc00b68e44a..a9a7362861a7077aa7e27ca3ab8a6aad3db47840 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java @@ -20,6 +20,7 @@ import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider; import android.content.Context; import android.content.SharedPreferences; +import android.os.Build; import androidx.annotation.Nullable; @@ -27,8 +28,11 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.IOException; import java.util.ArrayList; @@ -49,6 +53,8 @@ import se.leap.bitmaskclient.testutils.TestSetupHelper; /** * Created by cyberta on 09.10.17. */ +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class GatewaysManagerTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -114,7 +120,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "37.218.247.60"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); VpnProfile profile = createProfile(configGenerator, OBFS4); assertNotNull(profile); assertEquals(0, gatewaysManager.getPosition(profile)); @@ -132,7 +139,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "37.218.247.60"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); VpnProfile profile = createProfile(configGenerator, OPENVPN); assertNotNull(profile); assertEquals(0, gatewaysManager.getPosition(profile)); @@ -149,7 +157,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "37.218.247.60"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); assertThrows(ConfigParser.ConfigParseError.class, () -> createProfile(configGenerator, OBFS4)); } @@ -164,7 +173,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "37.218.247.60"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); VpnProfile profile = createProfile(configGenerator, OBFS4); assertEquals(2, gatewaysManager.getPosition(profile)); @@ -181,7 +191,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "37.218.247.60"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); VpnProfile profile = createProfile(configGenerator, OPENVPN); assertEquals(2, gatewaysManager.getPosition(profile)); @@ -198,7 +209,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "37.218.247.61"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); VpnProfile profile = createProfile(configGenerator, OBFS4); assertEquals(-1, gatewaysManager.getPosition(profile)); @@ -215,7 +227,8 @@ public class GatewaysManagerTest { VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); configuration.apiVersion = 3; configuration.remoteGatewayIP = "3.21.247.89"; - VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, gateway1, configuration); + configuration.transports = Transport.createTransportsFrom(gateway1, 3); + VpnConfigGenerator configGenerator = new VpnConfigGenerator(provider.getDefinition(), secrets, configuration); VpnProfile profile = createProfile(configGenerator, OBFS4); assertEquals(1, gatewaysManager.getPosition(profile)); diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java index 5343c4669751f5d4976c2a29ee6270c3cb4ca6c5..327e208580e4ac209b20d4756d5e31610609b189 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java @@ -8,17 +8,24 @@ import static org.mockito.Mockito.when; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; +import static se.leap.bitmaskclient.base.models.Transport.createTransportsFrom; +import static se.leap.bitmaskclient.eip.VpnConfigGenerator.Configuration.createProfileConfig; import android.content.Context; import android.content.SharedPreferences; +import android.os.Build; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.File; -import java.util.HashMap; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; @@ -26,6 +33,7 @@ import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.models.Transport; import se.leap.bitmaskclient.base.utils.BuildConfigHelper; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.base.utils.PrivateKeyHelper; @@ -36,6 +44,8 @@ import se.leap.bitmaskclient.testutils.TestSetupHelper; /** * Created by cyberta on 03.10.17. */ +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class VpnConfigGeneratorTest { Context context; @@ -55,7 +65,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -151,7 +161,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -247,7 +257,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -343,7 +353,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -439,7 +449,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -535,7 +545,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -631,7 +641,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -727,7 +737,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -829,7 +839,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -931,7 +941,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -1042,7 +1052,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -1145,7 +1155,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -1246,7 +1256,7 @@ public class VpnConfigGeneratorTest { "management-hold\n" + "\n" + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + - "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "setenv IV_PLAT_VER \"28 9 ROBO Android unknown robolectric\"\n" + "machine-readable-output\n" + "allow-recursive-routing\n" + "ifconfig-nowarn\n" + @@ -1375,9 +1385,8 @@ public class VpnConfigGeneratorTest { @Test public void testGenerateVpnProfile_v1_tcp_udp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_tcp_udp.json"))); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 1; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 1), 1, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertEquals(expectedVPNConfig_v1_tcp_udp.trim(), getVpnProfile(vpnProfiles, OPENVPN).getConfigFile(context, false).trim()); @@ -1386,9 +1395,8 @@ public class VpnConfigGeneratorTest { @Test public void testGenerateVpnProfile_v1_udp_tcp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_udp_tcp.json"))); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 1; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 1), 1, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertEquals(expectedVPNConfig_v1_udp_tcp.trim(), getVpnProfile(vpnProfiles, OPENVPN).getConfigFile(context, false).trim()); @@ -1397,9 +1405,9 @@ public class VpnConfigGeneratorTest { @Test public void testGenerateVpnProfile_v2_tcp_udp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_tcp_udp.json"))); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 2; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 2), 2, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertEquals(expectedVPNConfig_v1_tcp_udp.trim(), getVpnProfile(vpnProfiles, OPENVPN).getConfigFile(context, false).trim()); @@ -1408,9 +1416,8 @@ public class VpnConfigGeneratorTest { @Test public void testGenerateVpnProfile_v2_udp_tcp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_udp_tcp.json"))); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 2; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 2), 2, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertEquals(expectedVPNConfig_v1_udp_tcp.trim(), getVpnProfile(vpnProfiles, OPENVPN).getConfigFile(context, false).trim()); @@ -1421,9 +1428,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4() throws Exception { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(false); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo.bitmask.eip-service-obfsvpn1.0.0.json"))).getJSONArray("gateways").getJSONObject(0); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1435,9 +1441,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4_obfsvpn() throws Exception { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo.bitmask.eip-service-obfsvpn1.0.0.json"))).getJSONArray("gateways").getJSONObject(0); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1448,9 +1453,9 @@ public class VpnConfigGeneratorTest { @Test public void testGenerateVpnProfile_v3_ovpn_tcp_udp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_pt_tcp_udp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + configuration.preferUDP = false; + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1461,9 +1466,8 @@ public class VpnConfigGeneratorTest { @Test public void testGenerateVpnProfile_v3_ovpn_udp_tcp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_pt_udp_tcp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1476,9 +1480,8 @@ public class VpnConfigGeneratorTest { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_pt_udp_tcp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); //delete "data-ciphers" from config to test if the resulting openvpn config file will contain the default value taken from "cipher" flag generalConfig.put("data-ciphers", null); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1490,9 +1493,9 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v4_ovpn_tcp_udp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 4; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 4), 4, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + configuration.preferUDP = false; + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1504,9 +1507,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v4_ovpn_udp_tcp() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_udp_tcp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_udp_tcp.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 4; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 4), 4, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1518,9 +1520,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_ipv6only_allowOpenvpnIPv6Only() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OPENVPN)); } @@ -1529,9 +1530,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4IPv6_skip() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); } @@ -1543,9 +1543,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4IPv4AndIPv6_skipIPv6() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv4ipv6.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv4ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1560,9 +1559,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4udp_skip() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udp.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udp.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1575,9 +1573,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4TCP_openvpnTCP_skip() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_tcp2.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_tcp2.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1587,9 +1584,8 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_v3_obfs4UDPAndTCP_skipUDP() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udptcp.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udptcp.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1600,10 +1596,9 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_preferUDP_firstRemotesUDP() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/multiport_tcpudp_eip-service.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/multiport_tcpudp_eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.preferUDP = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1615,9 +1610,9 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_testNewCiphers() throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp_new_ciphers.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp_new_ciphers.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 4; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 4), 4, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); + configuration.preferUDP = false; + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); System.out.println(getVpnProfile(vpnProfiles, OPENVPN).getConfigFile(context, false)); assertEquals(expectedVPNConfig_v4_ovpn_tcp_udp_new_ciphers.trim(), getVpnProfile(vpnProfiles, OPENVPN).getConfigFile(context, false).trim()); @@ -1627,11 +1622,10 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfileExperimentalTransportsEnabled () throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_kcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_kcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; configuration.preferUDP = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4) && ((Obfs4Connection)getVpnProfile(vpnProfiles, OBFS4).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("kcp")); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1641,10 +1635,9 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_experimentalTransportsEnabled_KCPMisconfiguredWithUDP_SkippingObfsKCP () throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_kcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_kcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse(containsKey(vpnProfiles, OBFS4)); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1662,7 +1655,8 @@ public class VpnConfigGeneratorTest { configuration.obfuscationProxyCert = "asdfasdf"; configuration.obfuscationProxyKCP = true; configuration.remoteGatewayIP = "1.2.3.4"; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + configuration.transports = createTransportsFrom(gateway, 3); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue("has openvpn profile", containsKey(vpnProfiles, OPENVPN)); assertTrue("has obfs4 profile", containsKey(vpnProfiles, OBFS4)); @@ -1682,7 +1676,8 @@ public class VpnConfigGeneratorTest { configuration.obfuscationProxyIP = "5.6.7.8"; configuration.obfuscationProxyCert = "asdfasdf"; configuration.remoteGatewayIP = "1.2.3.4"; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + configuration.transports = createTransportsFrom(gateway, 3); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse("has openvpn profile", containsKey(vpnProfiles, OPENVPN)); assertTrue("has obfs4 profile", containsKey(vpnProfiles, OBFS4)); @@ -1702,8 +1697,9 @@ public class VpnConfigGeneratorTest { configuration.obfuscationProxyIP = "5.6.7.8"; configuration.obfuscationProxyCert = "asdfasdf"; configuration.remoteGatewayIP = "1.2.3.4"; + configuration.transports = createTransportsFrom(gateway, 3); - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertFalse("has openvpn profile", containsKey(vpnProfiles, OPENVPN)); assertTrue("has no obfs4 profile", containsKey(vpnProfiles, OBFS4)); @@ -1714,10 +1710,9 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_obfs4hop_tcp () throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4_HOP) && ((Obfs4Connection)getVpnProfile(vpnProfiles, OBFS4_HOP).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("tcp")); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1727,10 +1722,9 @@ public class VpnConfigGeneratorTest { public void testGenerateVpnProfile_obfs4hop_kcp () throws Exception { gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_kcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_kcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4_HOP) && ((Obfs4Connection)getVpnProfile(vpnProfiles, OBFS4_HOP).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("kcp")); assertTrue(containsKey(vpnProfiles, OPENVPN)); @@ -1741,10 +1735,9 @@ public class VpnConfigGeneratorTest { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt_portHopping.eip-service.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt_portHopping.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); System.out.println(getVpnProfile(vpnProfiles, OBFS4_HOP).getConfigFile(context, false)); assertEquals(expectedVPNConfig_hopping_pt_portHopping.trim(), getVpnProfile(vpnProfiles, OBFS4_HOP).getConfigFile(context, false).trim()); @@ -1755,10 +1748,9 @@ public class VpnConfigGeneratorTest { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt_portHopping.eip-service.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt_portHopping.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); System.out.println(getVpnProfile(vpnProfiles, OBFS4_HOP).getConfigFile(context, false)); assertEquals(expectedVPNConfig_hopping_pt_portHopping.trim(), getVpnProfile(vpnProfiles, OBFS4_HOP).getConfigFile(context, false).trim()); @@ -1768,10 +1760,9 @@ public class VpnConfigGeneratorTest { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt.eip-service.json"))).getJSONArray("gateways").getJSONObject(1); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4)); assertTrue(((Obfs4Connection)getVpnProfile(vpnProfiles, OBFS4).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("tcp")); @@ -1783,10 +1774,9 @@ public class VpnConfigGeneratorTest { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt.eip-service.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4_HOP)); assertTrue(((Obfs4Connection)getVpnProfile(vpnProfiles, OBFS4_HOP).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("tcp")); @@ -1799,10 +1789,9 @@ public class VpnConfigGeneratorTest { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt.eip-service.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = false; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Exception exception = null; try { vpnConfigGenerator.generateVpnProfiles(); @@ -1817,10 +1806,9 @@ public class VpnConfigGeneratorTest { BuildConfigHelper buildConfigHelper = MockHelper.mockBuildConfigHelper(true); gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt_portHopping.eip-service.json"))).getJSONArray("gateways").getJSONObject(2); generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("decoupled_pt_portHopping.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); - VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration(); - configuration.apiVersion = 3; + VpnConfigGenerator.Configuration configuration = createProfileConfig(createTransportsFrom(gateway, 3), 3, gateway.optString(IP_ADDRESS), gateway.optString(IP_ADDRESS6), ""); configuration.experimentalTransports = true; - vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, configuration); Vector<VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); assertTrue(containsKey(vpnProfiles, OBFS4_HOP)); assertTrue(((Obfs4Connection)getVpnProfile(vpnProfiles, OBFS4_HOP).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("tcp")); diff --git a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerTest.java index 2978edc7c8171bb8d73ea221d19b5fb44a519652..adbcf8cb863bd4ee83c83e05c13f3c6bf6e73a25 100644 --- a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerTest.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; @@ -40,6 +41,9 @@ import androidx.annotation.Nullable; import org.json.JSONException; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.IOException; import java.security.NoSuchAlgorithmException; @@ -55,6 +59,8 @@ import se.leap.bitmaskclient.base.utils.PrivateKeyHelper; import se.leap.bitmaskclient.testutils.MockSharedPreferences; import se.leap.bitmaskclient.tor.TorStatusObservable; +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class ProviderApiManagerTest { private Resources mockResources; @@ -116,6 +122,11 @@ public class ProviderApiManagerTest { return 8118; } + @Override + public int getTorSocksProxyPort() { + return 9050; + } + @Override public boolean hasNetworkConnection() { return hasNetworkConnection; @@ -153,7 +164,7 @@ public class ProviderApiManagerTest { Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_TOR_TIMEOUT\",\"initalAction\":\"setUpProvider\",\"errors\":\"Starting bridges failed. Do you want to retry or continue with an unobfuscated secure connection to configure Bitmask?\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Starting bridges failed. Do you want to retry or continue with an unobfuscated secure connection to configure Bitmask?\",\"errorId\":\"ERROR_TOR_TIMEOUT\",\"initalAction\":\"setUpProvider\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); Intent providerApiCommand = new Intent(); diff --git a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3Test.java b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3Test.java index 646d32d8f3c31c27e48a7cf422472073bb7e55bd..5751d1c5100b2c2efa89c05d0a29db26e4ec2efa 100644 --- a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3Test.java +++ b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3Test.java @@ -47,8 +47,8 @@ import static se.leap.bitmaskclient.testutils.MockHelper.mockCertificateHelper; import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator; import static se.leap.bitmaskclient.testutils.MockHelper.mockContext; import static se.leap.bitmaskclient.testutils.MockHelper.mockPreferenceHelper; -import static se.leap.bitmaskclient.testutils.MockHelper.mockProviderApiConnector; import static se.leap.bitmaskclient.testutils.MockHelper.mockPrivateKeyHelper; +import static se.leap.bitmaskclient.testutils.MockHelper.mockProviderApiConnector; import static se.leap.bitmaskclient.testutils.MockHelper.mockResources; import static se.leap.bitmaskclient.testutils.MockHelper.mockResultReceiver; import static se.leap.bitmaskclient.testutils.TestSetupHelper.getConfiguredProvider; @@ -60,6 +60,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; @@ -68,6 +69,9 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.IOException; import java.security.NoSuchAlgorithmException; @@ -87,7 +91,8 @@ import se.leap.bitmaskclient.tor.TorStatusObservable; /** * Created by cyberta on 04.01.18. */ - +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class ProviderApiManagerV3Test { private Resources mockResources; @@ -149,6 +154,11 @@ public class ProviderApiManagerV3Test { return 8118; } + @Override + public int getTorSocksProxyPort() { + return 9050; + } + @Override public boolean hasNetworkConnection() { return hasNetworkConnection; @@ -245,7 +255,7 @@ public class ProviderApiManagerV3Test { providerApiManager = new ProviderApiManagerV3(mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_CERTIFICATE_PINNING\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); providerApiManager.handleAction(SET_UP_PROVIDER, provider, new Bundle(), mockResultReceiver(PROVIDER_NOK, expectedResult)); @@ -262,7 +272,7 @@ public class ProviderApiManagerV3Test { Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_CERTIFICATE_PINNING\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); providerApiManager.handleAction(SET_UP_PROVIDER, provider, new Bundle(), mockResultReceiver(PROVIDER_NOK, expectedResult)); @@ -279,7 +289,7 @@ public class ProviderApiManagerV3Test { Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_CERTIFICATE_PINNING\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); providerApiManager.handleAction(SET_UP_PROVIDER, provider, new Bundle(), mockResultReceiver(PROVIDER_NOK, expectedResult)); @@ -296,7 +306,7 @@ public class ProviderApiManagerV3Test { Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_INVALID_CERTIFICATE\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); providerApiManager.handleAction(SET_UP_PROVIDER, provider, new Bundle(), mockResultReceiver(PROVIDER_NOK, expectedResult)); @@ -313,7 +323,7 @@ public class ProviderApiManagerV3Test { Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_INVALID_CERTIFICATE\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); providerApiManager.handleAction(SET_UP_PROVIDER, provider, new Bundle(), mockResultReceiver(PROVIDER_NOK, expectedResult)); @@ -329,7 +339,7 @@ public class ProviderApiManagerV3Test { providerApiManager = new ProviderApiManagerV3(mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_INVALID_CERTIFICATE\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); Intent providerApiCommand = new Intent(); @@ -339,15 +349,15 @@ public class ProviderApiManagerV3Test { @Test public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { - Provider provider = new Provider("https://riseup.net"); - PreferenceHelper preferenceHelper = mockPreferenceHelper(getConfiguredProvider()); + Provider provider = getConfiguredProvider(); + PreferenceHelper preferenceHelper = mockPreferenceHelper(provider); CertificateHelper certHelper = mockCertificateHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); ProviderApiConnector mockedApiConnector = mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE); providerApiManager = new ProviderApiManagerV3(mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = new Bundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\",\"errorId\":\"ERROR_INVALID_CERTIFICATE\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); providerApiManager.handleAction(SET_UP_PROVIDER, provider, new Bundle(), mockResultReceiver(PROVIDER_NOK, expectedResult)); @@ -356,11 +366,11 @@ public class ProviderApiManagerV3Test { @Test public void test_handleIntentSetupProvider_preseededProviderAndCA_failedConfiguration() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { - Provider provider = getConfiguredProvider(); + Provider provider = getProvider(null, null, null, null, null, "riseup_net_invalid_config.json", null, null); PreferenceHelper preferenceHelper = mockPreferenceHelper(provider); CertificateHelper certHelper = mockCertificateHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); - ProviderApiConnector mockedApiConnector = mockProviderApiConnector(ERROR_CASE_MICONFIGURED_PROVIDER); + ProviderApiConnector mockedApiConnector = mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManagerV3(mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -375,8 +385,8 @@ public class ProviderApiManagerV3Test { @Test public void test_handleIntentSetupProvider_preseededCustomProviderAndCA_failedConfiguration() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { - Provider provider = getConfiguredProvider(); - PreferenceHelper preferenceHelper = mockPreferenceHelper(provider); + Provider provider = new Provider("riseup.net"); + PreferenceHelper preferenceHelper = new PreferenceHelper(new MockSharedPreferences()); ProviderApiConnector mockedApiConnector = mockProviderApiConnector(ERROR_CASE_MICONFIGURED_PROVIDER); CertificateHelper certHelper = mockCertificateHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); @@ -549,7 +559,7 @@ public class ProviderApiManagerV3Test { @Test public void test_handleIntentSetupProvider_TorFallback_SecondTryHappyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException { - Provider provider = getConfiguredProviderAPIv4(); + Provider provider = new Provider("riseup.net"); PreferenceHelper preferenceHelper = mockPreferenceHelper(provider); CertificateHelper certHelper = mockCertificateHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); ProviderApiConnector mockedApiConnector = mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK); diff --git a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java index 925d2464d31ac712575423a0b2b3d612668f4def..b6a2becd8d54c3f741ad4f52e08fc182a1ef0081 100644 --- a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java @@ -9,26 +9,36 @@ import static org.mockito.Mockito.when; import android.content.SharedPreferences; import android.content.res.AssetManager; +import android.os.Build; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.InputStream; import java.util.HashSet; import java.util.Set; +import mobile.BitmaskMobile; +import mobilemodels.BitmaskMobileCore; import se.leap.bitmaskclient.base.models.Constants; import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.utils.BitmaskCoreProvider; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.testutils.MockHelper; import se.leap.bitmaskclient.testutils.MockSharedPreferences; +import se.leap.bitmaskclient.testutils.TestSetupHelper; /** * Created by cyberta on 20.02.18. */ +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) public class ProviderManagerTest { private AssetManager assetManager; @@ -40,6 +50,7 @@ public class ProviderManagerTest { @Before public void setup() throws Exception { assetManager = mock(AssetManager.class); + BitmaskCoreProvider.initBitmaskMobile(TestSetupHelper.getCustomBitmaskCore()); when(assetManager.open(anyString())).thenAnswer(new Answer<InputStream>() { @Override @@ -67,7 +78,7 @@ public class ProviderManagerTest { preferenceHelper = new PreferenceHelper(mockSharedPrefs); HashSet<Provider> customProviders = new HashSet<>(); - customProviders.add(Provider.createCustomProvider("https://leapcolombia.org", "leapcolombia.org")); + customProviders.add(Provider.createCustomProvider("https://leapcolombia.org", "leapcolombia.org", null)); PreferenceHelper.setCustomProviders(customProviders); } @@ -162,7 +173,7 @@ public class ProviderManagerTest { providerManager.setAddDummyEntry(true); providerManager.clear(); assertEquals("1 providers", 1, providerManager.providers().size()); - assertEquals("provider is dummy element", "https://example.net", providerManager.get(0).getMainUrlString()); + assertEquals("provider is dummy element", "", providerManager.get(0).getMainUrl()); } @Test @@ -195,8 +206,8 @@ public class ProviderManagerTest { providerManager.add(secondCustomProvider); providerManager.saveCustomProviders(); Set<String> providerSet = mockSharedPrefs.getStringSet(Constants.CUSTOM_PROVIDER_DOMAINS, new HashSet<>()); - assertEquals("persist was called twice", 2, providerSet.size()); - assertEquals("PreferenceHelper has 2 providers", 2, PreferenceHelper.getCustomProviders().size()); + assertEquals("persist was called twice", 3, providerSet.size()); + assertEquals("PreferenceHelper has 2 providers", 3, PreferenceHelper.getCustomProviders().size()); } diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java index 5434e7bdece4b175f93244b439dc849551fc6f38..c89d08cbea66cecbab9d239232b136f1b4e22934 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java @@ -259,9 +259,9 @@ public class MockHelper { sharedPreferences.edit(). putString(Provider.PROVIDER_IP + "." + providerDomain, provider.getProviderIp()). putString(Provider.PROVIDER_API_IP + "." + providerDomain, provider.getProviderApiIp()). - putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). - putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl().toString()). - putString(Provider.MOTD_URL + "." + providerDomain, provider.getMotdUrl().toString()). + putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrl()). + putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl()). + putString(Provider.MOTD_URL + "." + providerDomain, provider.getMotdUrl()). putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java index 72791ba65929a9d8e1a698c503b4b165ce5d4862..974202030d69a5aa68cecbd1d00371cfeffe731d 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import mobilemodels.BitmaskMobileCore; import se.leap.bitmaskclient.base.models.Provider; /** @@ -105,4 +106,83 @@ public class TestSetupHelper { return null; } + public static BitmaskMobileCore getCustomBitmaskCore() { + return new BitmaskMobileCore() { + @Override + public String getAllBridges(String s, String s1, String s2, String s3) throws Exception { + return null; + } + + @Override + public String getAllGateways(String s, String s1, String s2) throws Exception { + return null; + } + + @Override + public String getBestBridge() throws Exception { + return null; + } + + @Override + public String getBestGateway() throws Exception { + return null; + } + + @Override + public String getGeolocation() throws Exception { + return null; + } + + @Override + public String getIntroducerURLByDomain(String s) throws Exception { + return null; + } + + @Override + public String getOpenVPNCert() throws Exception { + return null; + } + + @Override + public String getProvider() throws Exception { + return null; + } + + @Override + public String getService() throws Exception { + return null; + } + + @Override + public void setCountryCode(String s) { + + } + + @Override + public void setDebug(boolean b) { + + } + + @Override + public void setIntroducer(String s) throws Exception { + + } + + @Override + public void setResolveWithDoH(boolean b) { + + } + + @Override + public void setSocksProxy(String s) { + + } + + @Override + public void setUseTls(boolean b) { + + } + }; + } + } diff --git a/app/src/test/resources/ed25519_credentials.pem b/app/src/test/resources/ed25519_credentials.pem new file mode 100644 index 0000000000000000000000000000000000000000..1c10ed282e5517fe46e59035075ee50ed991b484 --- /dev/null +++ b/app/src/test/resources/ed25519_credentials.pem @@ -0,0 +1,31 @@ +<key> +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIF+HZvpSdhnTbYeT635bT2+IU4FbW3EWlHuUnXvhb10m +-----END PRIVATE KEY----- +</key> +<cert> +-----BEGIN CERTIFICATE----- +MIIBgzCCASigAwIBAgIRALD3Z4SsobpcU7tcC0r9JOQwCgYIKoZIzj0EAwIwNzE1 +MDMGA1UEAwwsUHJvdmlkZXIgUm9vdCBDQSAoY2xpZW50IGNlcnRpZmljYXRlcyBv +bmx5ISkwHhcNMjQxMTA1MTU0MjU0WhcNMjQxMTI5MTU0MjU0WjAUMRIwEAYDVQQD +EwlVTkxJTUlURUQwKjAFBgMrZXADIQC5QkZAcpkQ3Rm54gN5iLEU1Zp1w+patXVT +W9GRXmFz+6NnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMC +MB0GA1UdDgQWBBRMxeMW4vqGK7FBkDt2+8upfkK1kzAfBgNVHSMEGDAWgBS0pVQs +1wnvNYG0AnmkxUcLOw+BLDAKBggqhkjOPQQDAgNJADBGAiEAg112+zWMm9qrPTvK +99IMa+wbeNzZLSoN9xewf5rxOX0CIQCvMi08JcajsAJ9Dg6YAQgpmFdb35HDCzve +lhkTCWJpgQ== +-----END CERTIFICATE----- +</cert> +<ca> +-----BEGIN CERTIFICATE----- +MIIBozCCAUigAwIBAgIBATAKBggqhkjOPQQDAjA3MTUwMwYDVQQDDCxQcm92aWRl +ciBSb290IENBIChjbGllbnQgY2VydGlmaWNhdGVzIG9ubHkhKTAeFw0yNDEwMjMx +MjA0MjRaFw0yOTEwMjMxMjA5MjRaMDcxNTAzBgNVBAMMLFByb3ZpZGVyIFJvb3Qg +Q0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAEMImwbNTDrXMeWfyTb2TMNzXNr79OsKjLDdZWqVT0iHMI8apo2P4H +eXCHVGjS2Z+jpyI1u9ic3igThsKEmdZMSKNFMEMwDgYDVR0PAQH/BAQDAgKkMBIG +A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFLSlVCzXCe81gbQCeaTFRws7D4Es +MAoGCCqGSM49BAMCA0kAMEYCIQCw88nXg/vs/KgGqH1uPs9oZkOxucVn/ZEznYzg +szLhtAIhAPY32oHwmj3yHO9H2Jp7x0CoHuu1fKd9fQTBvEEbi7o9 +-----END CERTIFICATE----- +</ca> 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 0000000000000000000000000000000000000000..568783a160cacdbdaedac9f03b7e26f1bc579945 --- /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 similarity index 100% rename from app/src/test/resources/private_ed25519_key.pem rename to app/src/test/resources/private_PKCS8_encoded_ed25519_key.pem diff --git a/bitmask-core-android b/bitmask-core-android index 29720d3bfdf1a1a60f7f86f33584da2e2b2a4b9e..8b802cc791d1d913c03fe57c251d3eb40a6a59a2 160000 --- a/bitmask-core-android +++ b/bitmask-core-android @@ -1 +1 @@ -Subproject commit 29720d3bfdf1a1a60f7f86f33584da2e2b2a4b9e +Subproject commit 8b802cc791d1d913c03fe57c251d3eb40a6a59a2 diff --git a/ics-openvpn b/ics-openvpn index 14d655f2936be93b15c09278e91367f4e43890c8..b1c21e7e1fbc0d09e3d121b89651482a0bb02efd 160000 --- a/ics-openvpn +++ b/ics-openvpn @@ -1 +1 @@ -Subproject commit 14d655f2936be93b15c09278e91367f4e43890c8 +Subproject commit b1c21e7e1fbc0d09e3d121b89651482a0bb02efd diff --git a/scripts/buildSwaggerClient.sh b/scripts/buildSwaggerClient.sh new file mode 100755 index 0000000000000000000000000000000000000000..b7a6837884ffc25eeb799c921943bc09b650b9b1 --- /dev/null +++ b/scripts/buildSwaggerClient.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +SWAGGER_MENSHEN_COMMIT=8c93c8208256097b2f1d18f2395755320b9743ee +SCRIPT_DIR=$(dirname "$0") +BASE_DIR="$SCRIPT_DIR/.." + +GREEN='\033[0;32m' +NC='\033[0m' + +echo -e "${GREEN}Creating swagger build directory${NC}" +mkdir -p ${BASE_DIR}/app/build/swagger + +echo -e "${GREEN}Fetching swagger specification for menshen commit ${SWAGGER_MENSHEN_COMMIT}${NC}" +if [[ -z $MENSHEN_SWAGGER_YAML ]]; then + curl https://0xacab.org/leap/menshen/-/raw/${SWAGGER_MENSHEN_COMMIT}/api/swagger.yaml > ${BASE_DIR}/app/build/swagger/swagger.yaml +else + cp $MENSHEN_SWAGGER_YAML ${BASE_DIR}/app/build/swagger/swagger.yaml +fi + +echo -e "${GREEN}Pulling swagger-codegen docker image${NC}" +docker pull swaggerapi/swagger-codegen-cli +cd $BASE_DIR + +echo -e "${GREEN}Running swagger-codegen-cli in docker${NC}" +docker run --rm --user $(id -u):$(id -g) -v ${PWD}:/bitmask_android swaggerapi/swagger-codegen-cli generate \ + -i /bitmask_android/app/build/swagger/swagger.yaml \ + -l java \ + -o /bitmask_android/app/build/swagger \ + -DuseAndroidMavenGradlePlugin=true \ + -DhideGenerationTimestamp=true + +echo -e "${GREEN}Copying generated model classes${NC}" +cp -r app/build/swagger/src/main/java/io/swagger/client/model/* app/src/main/java/io/swagger/client/model/. +cp app/build/swagger/src/main/java/io/swagger/client/JSON.java app/src/main/java/io/swagger/client/. + +echo -e "${GREEN}Cleanup${NC}" +rm -r app/build/swagger/