summaryrefslogtreecommitdiffhomepage
path: root/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java')
-rw-r--r--tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java215
1 files changed, 0 insertions, 215 deletions
diff --git a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java b/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java
deleted file mode 100644
index d1ad9e16..00000000
--- a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright © 2019-2020 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 com.wireguard.crypto.Ed25519;
-import com.wireguard.util.NonNullForAll;
-
-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.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import androidx.annotation.Nullable;
-
-/**
- * Class that implements the logic for downloading and loading signed, prebuilt modules for
- * WireGuard into the running kernel.
- */
-@NonNullForAll
-@SuppressWarnings("MagicNumber")
-public class ModuleLoader {
- private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
- private static final String MODULE_NAME = "wireguard-%s.ko";
- private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
- private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
- private final File moduleDir;
- private final RootShell rootShell;
- private final File tmpDir;
- private final String userAgent;
-
- /**
- * Public constructor for ModuleLoader
- *
- * @param context A {@link Context} instance.
- * @param rootShell A {@link RootShell} instance used to run elevated commands required for module
- * loading.
- * @param userAgent A {@link String} that represents the User-Agent string used for connections
- * to the upstream server.
- */
- 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;
- }
-
- /**
- * Check whether a WireGuard module is already loaded into the kernel.
- *
- * @return boolean indicating if WireGuard is already enabled in the kernel.
- */
- public static boolean isModuleLoaded() {
- return new File("/sys/module/wireguard").exists();
- }
-
- /**
- * Download the correct WireGuard module for the device
- *
- * @return {@link OsConstants}.EXIT_SUCCESS if everything succeeds, ENOENT otherwise.
- * @throws IOException if the remote hash list was not found or empty.
- * @throws RootShellException if {@link RootShell} has a failure executing elevated commands.
- * @throws NoSuchAlgorithmException if SHA256 algorithm is not available in device JDK.
- */
- 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");
- final byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
- int len;
- try (final InputStream inputStream = connection.getInputStream()) {
- int offset = 0;
- while (input.length - offset > 0 && (len = inputStream.read(input, offset, input.length - offset)) > 0) {
- offset += len;
- }
- len = offset;
- }
- 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);
- final 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;
- }
-
- /**
- * Load the downloaded module. ModuleLoader#download must be called before this.
- *
- * @throws IOException if {@link RootShell} has a failure executing elevated commands.
- * @throws RootShellException if {@link RootShell} has a failure executing elevated commands.
- */
- public void loadModule() throws IOException, RootShellException {
- rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
- }
-
- /**
- * Check if the module might already exist in the app's data.
- *
- * @return boolean indicating whether downloadable module might exist already.
- */
- public boolean moduleMightExist() {
- return moduleDir.exists() && moduleDir.isDirectory();
- }
-
- @Nullable
- private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) {
- 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;
-
- 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;
- }
- publicKeyBytes = Arrays.copyOfRange(publicKeyBytes, 10, 10 + 32);
- signatureBytes = Arrays.copyOfRange(signatureBytes, 10, 10 + 64);
- if (!Ed25519.verify(lines[2].getBytes(StandardCharsets.UTF_8), signatureBytes, publicKeyBytes))
- return null;
-
- final Map<String, Sha256Digest> hashes = new HashMap<>();
- for (final String line : lines[2].split("\n")) {
- final String[] components = line.split(" {2}", 2);
- if (components.length != 2)
- return null;
- try {
- hashes.put(components[1], new Sha256Digest(components[0]));
- } catch (final Exception ignored) {
- return null;
- }
- }
- return hashes;
- }
-
- private static final class Sha256Digest {
- private final 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);
- }
- }
-}