Rename app module to ui
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Standalone utilities for interacting with the system clipboard.
|
||||
*/
|
||||
|
||||
public final class ClipboardUtils {
|
||||
private ClipboardUtils() {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
public static void copyTextView(final View view) {
|
||||
if (!(view instanceof TextView))
|
||||
return;
|
||||
final CharSequence text = ((TextView) view).getText();
|
||||
if (text == null || text.length() == 0)
|
||||
return;
|
||||
final Object service = view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (!(service instanceof ClipboardManager))
|
||||
return;
|
||||
final CharSequence description = view.getContentDescription();
|
||||
((ClipboardManager) service).setPrimaryClip(ClipData.newPlainText(description, text));
|
||||
Snackbar.make(view, description + " copied to clipboard", Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.MediaColumns;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class DownloadsFileSaver {
|
||||
|
||||
public static class DownloadsFile {
|
||||
private Context context;
|
||||
private OutputStream outputStream;
|
||||
private String fileName;
|
||||
private Uri uri;
|
||||
|
||||
private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) {
|
||||
this.context = context;
|
||||
this.outputStream = outputStream;
|
||||
this.fileName = fileName;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() { return outputStream; }
|
||||
public String getFileName() { return fileName; }
|
||||
|
||||
public void delete() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
context.getContentResolver().delete(uri, null, null);
|
||||
else
|
||||
new File(fileName).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
final ContentResolver contentResolver = context.getContentResolver();
|
||||
if (overwriteExisting)
|
||||
contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), new String[]{name});
|
||||
final ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaColumns.DISPLAY_NAME, name);
|
||||
contentValues.put(MediaColumns.MIME_TYPE, mimeType);
|
||||
final Uri contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
|
||||
if (contentUri == null)
|
||||
throw new IOException(context.getString(R.string.create_downloads_file_error));
|
||||
final OutputStream contentStream = contentResolver.openOutputStream(contentUri);
|
||||
if (contentStream == null)
|
||||
throw new IOException(context.getString(R.string.create_downloads_file_error));
|
||||
@SuppressWarnings("deprecation")
|
||||
Cursor cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DATA}, null, null, null);
|
||||
String path = null;
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst())
|
||||
path = cursor.getString(0);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (path == null) {
|
||||
path = "Download/";
|
||||
cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DISPLAY_NAME}, null, null, null);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst())
|
||||
path += cursor.getString(0);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DownloadsFile(context, contentStream, path, contentUri);
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
final File file = new File(path, name);
|
||||
if (!path.isDirectory() && !path.mkdirs())
|
||||
throw new IOException(context.getString(R.string.create_output_dir_error));
|
||||
return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.backend.BackendException;
|
||||
import com.wireguard.android.util.RootShell.RootShellException;
|
||||
import com.wireguard.config.BadConfigException;
|
||||
import com.wireguard.config.BadConfigException.Location;
|
||||
import com.wireguard.config.InetEndpoint;
|
||||
import com.wireguard.config.InetNetwork;
|
||||
import com.wireguard.config.ParseException;
|
||||
import com.wireguard.crypto.Key.Format;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.crypto.KeyFormatException.Type;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java9.util.Maps;
|
||||
|
||||
public final class ErrorMessages {
|
||||
private static final Map<BadConfigException.Reason, Integer> BCE_REASON_MAP = new EnumMap<>(Maps.of(
|
||||
BadConfigException.Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key,
|
||||
BadConfigException.Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number,
|
||||
BadConfigException.Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value,
|
||||
BadConfigException.Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute,
|
||||
BadConfigException.Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section,
|
||||
BadConfigException.Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value,
|
||||
BadConfigException.Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error,
|
||||
BadConfigException.Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute,
|
||||
BadConfigException.Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section
|
||||
));
|
||||
private static final Map<BackendException.Reason, Integer> BE_REASON_MAP = new EnumMap<>(Maps.of(
|
||||
BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME, R.string.module_version_error,
|
||||
BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE, R.string.tunnel_config_error,
|
||||
BackendException.Reason.TUNNEL_MISSING_CONFIG, R.string.no_config_error,
|
||||
BackendException.Reason.VPN_NOT_AUTHORIZED, R.string.vpn_not_authorized_error,
|
||||
BackendException.Reason.UNABLE_TO_START_VPN, R.string.vpn_start_error,
|
||||
BackendException.Reason.TUN_CREATION_ERROR, R.string.tun_create_error,
|
||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE, R.string.tunnel_on_error
|
||||
));
|
||||
private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of(
|
||||
RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root,
|
||||
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error,
|
||||
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error,
|
||||
RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error,
|
||||
RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error,
|
||||
RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error
|
||||
));
|
||||
private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of(
|
||||
Format.BASE64, R.string.key_length_explanation_base64,
|
||||
Format.BINARY, R.string.key_length_explanation_binary,
|
||||
Format.HEX, R.string.key_length_explanation_hex
|
||||
));
|
||||
private static final Map<Type, Integer> KFE_TYPE_MAP = new EnumMap<>(Maps.of(
|
||||
Type.CONTENTS, R.string.key_contents_error,
|
||||
Type.LENGTH, R.string.key_length_error
|
||||
));
|
||||
private static final Map<Class, Integer> PE_CLASS_MAP = Maps.of(
|
||||
InetAddress.class, R.string.parse_error_inet_address,
|
||||
InetEndpoint.class, R.string.parse_error_inet_endpoint,
|
||||
InetNetwork.class, R.string.parse_error_inet_network,
|
||||
Integer.class, R.string.parse_error_integer
|
||||
);
|
||||
|
||||
private ErrorMessages() {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
public static String get(@Nullable final Throwable throwable) {
|
||||
final Resources resources = Application.get().getResources();
|
||||
if (throwable == null)
|
||||
return resources.getString(R.string.unknown_error);
|
||||
final Throwable rootCause = rootCause(throwable);
|
||||
final String message;
|
||||
if (rootCause instanceof BadConfigException) {
|
||||
final BadConfigException bce = (BadConfigException) rootCause;
|
||||
final String reason = getBadConfigExceptionReason(resources, bce);
|
||||
final String context = bce.getLocation() == Location.TOP_LEVEL ?
|
||||
resources.getString(R.string.bad_config_context_top_level,
|
||||
bce.getSection().getName()) :
|
||||
resources.getString(R.string.bad_config_context,
|
||||
bce.getSection().getName(),
|
||||
bce.getLocation().getName());
|
||||
final String explanation = getBadConfigExceptionExplanation(resources, bce);
|
||||
message = resources.getString(R.string.bad_config_error, reason, context) + explanation;
|
||||
} else if (rootCause instanceof BackendException) {
|
||||
final BackendException be = (BackendException) rootCause;
|
||||
message = resources.getString(BE_REASON_MAP.get(be.getReason()), be.getFormat());
|
||||
} else if (rootCause instanceof RootShellException) {
|
||||
final RootShellException rse = (RootShellException) rootCause;
|
||||
message = resources.getString(RSE_REASON_MAP.get(rse.getReason()), rse.getFormat());
|
||||
} else if (rootCause.getMessage() != null) {
|
||||
message = rootCause.getMessage();
|
||||
} else {
|
||||
final String errorType = rootCause.getClass().getSimpleName();
|
||||
message = resources.getString(R.string.generic_error, errorType);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
private static String getBadConfigExceptionExplanation(final Resources resources,
|
||||
final BadConfigException bce) {
|
||||
if (bce.getCause() instanceof KeyFormatException) {
|
||||
final KeyFormatException kfe = (KeyFormatException) bce.getCause();
|
||||
if (kfe.getType() == Type.LENGTH)
|
||||
return resources.getString(KFE_FORMAT_MAP.get(kfe.getFormat()));
|
||||
} else if (bce.getCause() instanceof ParseException) {
|
||||
final ParseException pe = (ParseException) bce.getCause();
|
||||
if (pe.getMessage() != null)
|
||||
return ": " + pe.getMessage();
|
||||
} else if (bce.getLocation() == Location.LISTEN_PORT) {
|
||||
return resources.getString(R.string.bad_config_explanation_udp_port);
|
||||
} else if (bce.getLocation() == Location.MTU) {
|
||||
return resources.getString(R.string.bad_config_explanation_positive_number);
|
||||
} else if (bce.getLocation() == Location.PERSISTENT_KEEPALIVE) {
|
||||
return resources.getString(R.string.bad_config_explanation_pka);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getBadConfigExceptionReason(final Resources resources,
|
||||
final BadConfigException bce) {
|
||||
if (bce.getCause() instanceof KeyFormatException) {
|
||||
final KeyFormatException kfe = (KeyFormatException) bce.getCause();
|
||||
return resources.getString(KFE_TYPE_MAP.get(kfe.getType()));
|
||||
} else if (bce.getCause() instanceof ParseException) {
|
||||
final ParseException pe = (ParseException) bce.getCause();
|
||||
final String type = resources.getString(PE_CLASS_MAP.containsKey(pe.getParsingClass()) ?
|
||||
PE_CLASS_MAP.get(pe.getParsingClass()) : R.string.parse_error_generic);
|
||||
return resources.getString(R.string.parse_error_reason, type, pe.getText());
|
||||
}
|
||||
return resources.getString(BCE_REASON_MAP.get(bce.getReason()), bce.getText());
|
||||
}
|
||||
|
||||
private static Throwable rootCause(final Throwable throwable) {
|
||||
Throwable cause = throwable;
|
||||
while (cause.getCause() != null) {
|
||||
if (cause instanceof BadConfigException || cause instanceof BackendException ||
|
||||
cause instanceof RootShellException)
|
||||
break;
|
||||
final Throwable nextCause = cause.getCause();
|
||||
if (nextCause instanceof RemoteException)
|
||||
break;
|
||||
cause = nextCause;
|
||||
}
|
||||
return cause;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import java9.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Helpers for logging exceptions from asynchronous tasks. These can be passed to
|
||||
* {@code CompletionStage.whenComplete()} at the end of an asynchronous future chain.
|
||||
*/
|
||||
|
||||
public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
|
||||
D(Log.DEBUG),
|
||||
E(Log.ERROR);
|
||||
|
||||
private static final String TAG = "WireGuard/" + ExceptionLoggers.class.getSimpleName();
|
||||
private final int priority;
|
||||
|
||||
ExceptionLoggers(final int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final Object result, @Nullable final Throwable throwable) {
|
||||
if (throwable != null)
|
||||
Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable));
|
||||
else if (priority <= Log.DEBUG)
|
||||
Log.println(priority, TAG, "Future completed successfully");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
|
||||
fun Context.resolveAttribute(@AttrRes attrRes: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
theme.resolveAttribute(attrRes, typedValue, true)
|
||||
return typedValue.data
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.preference.Preference;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import com.wireguard.android.activity.SettingsActivity;
|
||||
|
||||
public final class FragmentUtils {
|
||||
private FragmentUtils() {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
public static SettingsActivity getPrefActivity(final Preference preference) {
|
||||
final Context context = preference.getContext();
|
||||
if (context instanceof ContextThemeWrapper) {
|
||||
if (context instanceof SettingsActivity) {
|
||||
return ((SettingsActivity) context);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ModuleLoader {
|
||||
private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
|
||||
private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
|
||||
private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
|
||||
private static final String MODULE_NAME = "wireguard-%s.ko";
|
||||
|
||||
private final RootShell rootShell;
|
||||
private final String userAgent;
|
||||
private final File moduleDir;
|
||||
private final File tmpDir;
|
||||
|
||||
public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) {
|
||||
moduleDir = new File(context.getCacheDir(), "kmod");
|
||||
tmpDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public boolean moduleMightExist() {
|
||||
return moduleDir.exists() && moduleDir.isDirectory();
|
||||
}
|
||||
|
||||
public void loadModule() throws IOException, RootShellException {
|
||||
rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
|
||||
}
|
||||
|
||||
public static boolean isModuleLoaded() {
|
||||
return new File("/sys/module/wireguard").exists();
|
||||
}
|
||||
|
||||
private static final class Sha256Digest {
|
||||
private byte[] bytes;
|
||||
private Sha256Digest(final String hex) {
|
||||
if (hex.length() != 64)
|
||||
throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
|
||||
bytes = new byte[32];
|
||||
for (int i = 0; i < 32; ++i)
|
||||
bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) {
|
||||
final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT);
|
||||
|
||||
if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd')
|
||||
return null;
|
||||
|
||||
final String[] lines = signifyDigest.split("\n", 3);
|
||||
if (lines.length != 3)
|
||||
return null;
|
||||
if (!lines[0].startsWith("untrusted comment: "))
|
||||
return null;
|
||||
|
||||
final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT);
|
||||
if (signatureBytes == null || signatureBytes.length != 64 + 10)
|
||||
return null;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (signatureBytes[i] != publicKeyBytes[i])
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
|
||||
Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm()));
|
||||
byte[] rawPublicKeyBytes = new byte[32];
|
||||
System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32);
|
||||
signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec)));
|
||||
signature.update(lines[2].getBytes(StandardCharsets.UTF_8));
|
||||
if (!signature.verify(signatureBytes, 10, 64))
|
||||
return null;
|
||||
} catch (final Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Sha256Digest> hashes = new HashMap<>();
|
||||
for (final String line : lines[2].split("\n")) {
|
||||
final String[] components = line.split(" ", 2);
|
||||
if (components.length != 2)
|
||||
return null;
|
||||
try {
|
||||
hashes.put(components[1], new Sha256Digest(components[0]));
|
||||
} catch (final Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException {
|
||||
final List<String> output = new ArrayList<>();
|
||||
rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
|
||||
if (output.size() != 1 || output.get(0).length() != 64)
|
||||
throw new InvalidParameterException("Invalid sha256 of /proc/version");
|
||||
final String moduleName = String.format(MODULE_NAME, output.get(0));
|
||||
HttpURLConnection connection = (HttpURLConnection)new URL(MODULE_LIST_URL).openConnection();
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Hash list could not be found");
|
||||
byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
|
||||
int len;
|
||||
try (final InputStream inputStream = connection.getInputStream()) {
|
||||
len = inputStream.read(input);
|
||||
}
|
||||
if (len <= 0)
|
||||
throw new IOException("Hash list was empty");
|
||||
final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
|
||||
if (modules == null)
|
||||
throw new InvalidParameterException("The signature did not verify or invalid hash list format");
|
||||
if (!modules.containsKey(moduleName))
|
||||
return OsConstants.ENOENT;
|
||||
connection = (HttpURLConnection)new URL(String.format(MODULE_URL, moduleName)).openConnection();
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Module file could not be found, despite being on hash list");
|
||||
|
||||
tmpDir.mkdirs();
|
||||
moduleDir.mkdir();
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (final InputStream inputStream = connection.getInputStream();
|
||||
final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||
int total = 0;
|
||||
while ((len = inputStream.read(input)) > 0) {
|
||||
total += len;
|
||||
if (total > 1024 * 1024 * 15 /* 15 MiB */)
|
||||
throw new IOException("File too big");
|
||||
outputStream.write(input, 0, len);
|
||||
digest.update(input, 0, len);
|
||||
}
|
||||
outputStream.getFD().sync();
|
||||
}
|
||||
if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
|
||||
throw new IOException("Incorrect file hash");
|
||||
|
||||
if (!tempFile.renameTo(new File(moduleDir, moduleName)))
|
||||
throw new IOException("Unable to rename to final destination");
|
||||
} finally {
|
||||
if (tempFile != null)
|
||||
tempFile.delete();
|
||||
}
|
||||
return OsConstants.EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import androidx.databinding.ObservableArrayList;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.wireguard.util.Keyed;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ArrayList that allows looking up elements by some key property. As the key property must always
|
||||
* be retrievable, this list cannot hold {@code null} elements. Because this class places no
|
||||
* restrictions on the order or duplication of keys, lookup by key, as well as all list modification
|
||||
* operations, require O(n) time.
|
||||
*/
|
||||
|
||||
public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
|
||||
extends ObservableArrayList<E> implements ObservableKeyedList<K, E> {
|
||||
@Override
|
||||
public boolean add(@Nullable final E e) {
|
||||
if (e == null)
|
||||
throw new NullPointerException("Trying to add a null element");
|
||||
return super.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(final int index, @Nullable final E e) {
|
||||
if (e == null)
|
||||
throw new NullPointerException("Trying to add a null element");
|
||||
super.add(index, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(final Collection<? extends E> c) {
|
||||
if (c.contains(null))
|
||||
throw new NullPointerException("Trying to add a collection with null element(s)");
|
||||
return super.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(final int index, final Collection<? extends E> c) {
|
||||
if (c.contains(null))
|
||||
throw new NullPointerException("Trying to add a collection with null element(s)");
|
||||
return super.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAllKeys(final Collection<K> keys) {
|
||||
for (final K key : keys)
|
||||
if (!containsKey(key))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(final K key) {
|
||||
return indexOfKey(key) >= 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public E get(final K key) {
|
||||
final int index = indexOfKey(key);
|
||||
return index >= 0 ? get(index) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public E getLast(final K key) {
|
||||
final int index = lastIndexOfKey(key);
|
||||
return index >= 0 ? get(index) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOfKey(final K key) {
|
||||
final ListIterator<E> iterator = listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
final int index = iterator.nextIndex();
|
||||
if (Objects.equals(iterator.next().getKey(), key))
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOfKey(final K key) {
|
||||
final ListIterator<E> iterator = listIterator(size());
|
||||
while (iterator.hasPrevious()) {
|
||||
final int index = iterator.previousIndex();
|
||||
if (Objects.equals(iterator.previous().getKey(), key))
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(final int index, @Nullable final E e) {
|
||||
if (e == null)
|
||||
throw new NullPointerException("Trying to set a null key");
|
||||
return super.set(index, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import androidx.databinding.ObservableList;
|
||||
|
||||
import com.wireguard.util.Keyed;
|
||||
import com.wireguard.util.KeyedList;
|
||||
|
||||
/**
|
||||
* A list that is both keyed and observable.
|
||||
*/
|
||||
|
||||
public interface ObservableKeyedList<K, E extends Keyed<? extends K>>
|
||||
extends KeyedList<K, E>, ObservableList<E> {
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.wireguard.util.Keyed;
|
||||
import com.wireguard.util.SortedKeyedList;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
|
||||
/**
|
||||
* KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses
|
||||
* binary search to improve lookup and replacement times to O(log(n)). However, due to the
|
||||
* array-based nature of this class, insertion and removal of elements with anything but the largest
|
||||
* key still require O(n) time.
|
||||
*/
|
||||
|
||||
public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
|
||||
extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> {
|
||||
@Nullable private final Comparator<? super K> comparator;
|
||||
private final transient KeyList<K, E> keyList = new KeyList<>(this);
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public ObservableSortedKeyedArrayList() {
|
||||
comparator = null;
|
||||
}
|
||||
|
||||
public ObservableSortedKeyedArrayList(final Comparator<? super K> comparator) {
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
public ObservableSortedKeyedArrayList(final Collection<? extends E> c) {
|
||||
this();
|
||||
addAll(c);
|
||||
}
|
||||
|
||||
public ObservableSortedKeyedArrayList(final SortedKeyedList<K, E> other) {
|
||||
this(other.comparator());
|
||||
addAll(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(final E e) {
|
||||
final int insertionPoint = getInsertionPoint(e);
|
||||
if (insertionPoint < 0) {
|
||||
// Skipping insertion is non-destructive if the new and existing objects are the same.
|
||||
if (e == get(-insertionPoint - 1))
|
||||
return false;
|
||||
throw new IllegalArgumentException("Element with same key already exists in list");
|
||||
}
|
||||
super.add(insertionPoint, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(final int index, final E e) {
|
||||
final int insertionPoint = getInsertionPoint(e);
|
||||
if (insertionPoint < 0)
|
||||
throw new IllegalArgumentException("Element with same key already exists in list");
|
||||
if (insertionPoint != index)
|
||||
throw new IndexOutOfBoundsException("Wrong index given for element");
|
||||
super.add(index, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(final Collection<? extends E> c) {
|
||||
boolean didChange = false;
|
||||
for (final E e : c)
|
||||
if (add(e))
|
||||
didChange = true;
|
||||
return didChange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, final Collection<? extends E> c) {
|
||||
for (final E e : c)
|
||||
add(index++, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Comparator<? super K> comparator() {
|
||||
return comparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K firstKey() {
|
||||
if (isEmpty())
|
||||
// The parameter in the exception is only to shut
|
||||
// lint up, we never care for the exception message.
|
||||
throw new NoSuchElementException("Empty set");
|
||||
return get(0).getKey();
|
||||
}
|
||||
|
||||
private int getInsertionPoint(final E e) {
|
||||
if (comparator != null) {
|
||||
return -Collections.binarySearch(keyList, e.getKey(), comparator) - 1;
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
|
||||
(List<Comparable<? super K>>) keyList;
|
||||
return -Collections.binarySearch(list, e.getKey()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOfKey(final K key) {
|
||||
final int index;
|
||||
if (comparator != null) {
|
||||
index = Collections.binarySearch(keyList, key, comparator);
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
|
||||
(List<Comparable<? super K>>) keyList;
|
||||
index = Collections.binarySearch(list, key);
|
||||
}
|
||||
return index >= 0 ? index : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return keyList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOfKey(final K key) {
|
||||
// There can never be more than one element with the same key in the list.
|
||||
return indexOfKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K lastKey() {
|
||||
if (isEmpty())
|
||||
// The parameter in the exception is only to shut
|
||||
// lint up, we never care for the exception message.
|
||||
throw new NoSuchElementException("Empty set");
|
||||
return get(size() - 1).getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(final int index, final E e) {
|
||||
final int order;
|
||||
if (comparator != null) {
|
||||
order = comparator.compare(e.getKey(), get(index).getKey());
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") final Comparable<? super K> key =
|
||||
(Comparable<? super K>) e.getKey();
|
||||
order = key.compareTo(get(index).getKey());
|
||||
}
|
||||
if (order != 0) {
|
||||
// Allow replacement if the new key would be inserted adjacent to the replaced element.
|
||||
final int insertionPoint = getInsertionPoint(e);
|
||||
if (insertionPoint < index || insertionPoint > index + 1)
|
||||
throw new IndexOutOfBoundsException("Wrong index given for element");
|
||||
}
|
||||
return super.set(index, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<E> values() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final class KeyList<K, E extends Keyed<? extends K>>
|
||||
extends AbstractList<K> implements Set<K> {
|
||||
private final ObservableSortedKeyedArrayList<K, E> list;
|
||||
|
||||
private KeyList(final ObservableSortedKeyedArrayList<K, E> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K get(final int index) {
|
||||
return list.get(index).getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
public Spliterator<K> spliterator() {
|
||||
return super.spliterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import com.wireguard.util.Keyed;
|
||||
import com.wireguard.util.SortedKeyedList;
|
||||
|
||||
/**
|
||||
* A list that is both sorted/keyed and observable.
|
||||
*/
|
||||
|
||||
public interface ObservableSortedKeyedList<K, E extends Keyed<? extends K>>
|
||||
extends ObservableKeyedList<K, E>, SortedKeyedList<K, E> {
|
||||
}
|
||||
Reference in New Issue
Block a user