diff options
Diffstat (limited to 'tunnel/src/main/java')
24 files changed, 306 insertions, 279 deletions
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Backend.java b/tunnel/src/main/java/com/wireguard/android/backend/Backend.java index 4c18d98b..224d5849 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/Backend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/Backend.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -30,6 +30,7 @@ public interface Backend { * * @param tunnel The tunnel to examine the state of. * @return The state of the tunnel. + * @throws Exception Exception raised when retrieving tunnel's state. */ Tunnel.State getState(Tunnel tunnel) throws Exception; @@ -39,6 +40,7 @@ public interface Backend { * * @param tunnel The tunnel to retrieve statistics for. * @return The statistics for the tunnel. + * @throws Exception Exception raised when retrieving statistics. */ Statistics getStatistics(Tunnel tunnel) throws Exception; @@ -46,7 +48,7 @@ public interface Backend { * Determine version of underlying backend. * * @return The version of the backend. - * @throws Exception + * @throws Exception Exception raised while retrieving version. */ String getVersion() throws Exception; @@ -59,6 +61,7 @@ public interface Backend { * {@code TOGGLE}. * @param config The configuration for this tunnel, may be null if state is {@code DOWN}. * @return The updated state of the tunnel. + * @throws Exception Exception raised while changing state. */ Tunnel.State setState(Tunnel tunnel, Tunnel.State state, @Nullable Config config) throws Exception; } diff --git a/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java b/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java index 5552b3ee..94f7b098 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/BackendException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2020 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,24 +7,47 @@ package com.wireguard.android.backend; import com.wireguard.util.NonNullForAll; +/** + * A subclass of {@link Exception} that encapsulates the reasons for a failure originating in + * implementations of {@link Backend}. + */ @NonNullForAll public final class BackendException extends Exception { private final Object[] format; private final Reason reason; + /** + * Public constructor for BackendException. + * + * @param reason The {@link Reason} which caused this exception to be thrown + * @param format Format string values used when converting exceptions to user-facing strings. + */ public BackendException(final Reason reason, final Object... format) { this.reason = reason; this.format = format; } + /** + * Get the format string values associated with the instance. + * + * @return Array of {@link Object} for string formatting purposes + */ public Object[] getFormat() { return format; } + /** + * Get the reason for this exception. + * + * @return Associated {@link Reason} for this exception. + */ public Reason getReason() { return reason; } + /** + * Enum class containing all known reasons for why a {@link BackendException} might be thrown. + */ public enum Reason { UNKNOWN_KERNEL_MODULE_NAME, WG_QUICK_CONFIG_ERROR_CODE, @@ -32,6 +55,7 @@ public final class BackendException extends Exception { VPN_NOT_AUTHORIZED, UNABLE_TO_START_VPN, TUN_CREATION_ERROR, - GO_ACTIVATION_ERROR_CODE + GO_ACTIVATION_ERROR_CODE, + DNS_RESOLUTION_FAILURE, } } diff --git a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java index 9fafc918..6b66f2c5 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -16,6 +16,7 @@ import com.wireguard.android.backend.BackendException.Reason; import com.wireguard.android.backend.Tunnel.State; import com.wireguard.android.util.SharedLibraryLoader; import com.wireguard.config.Config; +import com.wireguard.config.InetEndpoint; import com.wireguard.config.InetNetwork; import com.wireguard.config.Peer; import com.wireguard.crypto.Key; @@ -23,6 +24,7 @@ import com.wireguard.crypto.KeyFormatException; import com.wireguard.util.NonNullForAll; import java.net.InetAddress; +import java.time.Instant; import java.util.Collections; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -34,8 +36,13 @@ import java.util.concurrent.TimeoutException; import androidx.annotation.Nullable; import androidx.collection.ArraySet; +/** + * Implementation of {@link Backend} that uses the wireguard-go userspace implementation to provide + * WireGuard tunnels. + */ @NonNullForAll public final class GoBackend implements Backend { + private static final int DNS_RESOLUTION_RETRIES = 10; private static final String TAG = "WireGuard/GoBackend"; @Nullable private static AlwaysOnCallback alwaysOnCallback; private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>(); @@ -44,16 +51,27 @@ public final class GoBackend implements Backend { @Nullable private Tunnel currentTunnel; private int currentTunnelHandle = -1; + /** + * Public constructor for GoBackend. + * + * @param context An Android {@link Context} + */ public GoBackend(final Context context) { SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); this.context = context; } + /** + * Set a {@link AlwaysOnCallback} to be invoked when {@link VpnService} is started by the + * system's Always-On VPN mode. + * + * @param cb Callback to be invoked + */ public static void setAlwaysOnCallback(final AlwaysOnCallback cb) { alwaysOnCallback = cb; } - private static native String wgGetConfig(int handle); + @Nullable private static native String wgGetConfig(int handle); private static native int wgGetSocketV4(int handle); @@ -65,6 +83,11 @@ public final class GoBackend implements Backend { private static native String wgVersion(); + /** + * Method to get the names of running tunnels. + * + * @return A set of string values denoting names of running tunnels. + */ @Override public Set<String> getRunningTunnelNames() { if (currentTunnel != null) { @@ -75,27 +98,42 @@ public final class GoBackend implements Backend { return Collections.emptySet(); } + /** + * Get the associated {@link State} for a given {@link Tunnel}. + * + * @param tunnel The tunnel to examine the state of. + * @return {@link State} associated with the given tunnel. + */ @Override public State getState(final Tunnel tunnel) { return currentTunnel == tunnel ? State.UP : State.DOWN; } + /** + * Get the associated {@link Statistics} for a given {@link Tunnel}. + * + * @param tunnel The tunnel to retrieve statistics for. + * @return {@link Statistics} associated with the given tunnel. + */ @Override public Statistics getStatistics(final Tunnel tunnel) { final Statistics stats = new Statistics(); - if (tunnel != currentTunnel) { + if (tunnel != currentTunnel || currentTunnelHandle == -1) return stats; - } final String config = wgGetConfig(currentTunnelHandle); + if (config == null) + return stats; Key key = null; long rx = 0; long tx = 0; + long latestHandshakeMSec = 0; for (final String line : config.split("\\n")) { if (line.startsWith("public_key=")) { if (key != null) - stats.add(key, rx, tx); + stats.add(key, rx, tx, latestHandshakeMSec); rx = 0; tx = 0; + latestHandshakeMSec = 0; try { key = Key.fromHex(line.substring(11)); } catch (final KeyFormatException ignored) { @@ -117,18 +155,49 @@ public final class GoBackend implements Backend { } catch (final NumberFormatException ignored) { tx = 0; } + } else if (line.startsWith("last_handshake_time_sec=")) { + if (key == null) + continue; + try { + latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000; + } catch (final NumberFormatException ignored) { + latestHandshakeMSec = 0; + } + } else if (line.startsWith("last_handshake_time_nsec=")) { + if (key == null) + continue; + try { + latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000; + } catch (final NumberFormatException ignored) { + latestHandshakeMSec = 0; + } } } if (key != null) - stats.add(key, rx, tx); + stats.add(key, rx, tx, latestHandshakeMSec); return stats; } + /** + * Get the version of the underlying wireguard-go library. + * + * @return {@link String} value of the version of the wireguard-go library. + */ @Override public String getVersion() { return wgVersion(); } + /** + * Change the state of a given {@link Tunnel}, optionally applying a given {@link Config}. + * + * @param tunnel The tunnel to control the state of. + * @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or + * {@code TOGGLE}. + * @param config The configuration for this tunnel, may be null if state is {@code DOWN}. + * @return {@link State} of the tunnel after state changes are applied. + * @throws Exception Exception raised while changing tunnel state. + */ @Override public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception { final State originalState = getState(tunnel); @@ -167,8 +236,10 @@ public final class GoBackend implements Backend { throw new BackendException(Reason.VPN_NOT_AUTHORIZED); final VpnService service; - if (!vpnService.isDone()) - startVpnService(); + if (!vpnService.isDone()) { + Log.d(TAG, "Requesting to start VpnService"); + context.startService(new Intent(context, VpnService.class)); + } try { service = vpnService.get(2, TimeUnit.SECONDS); @@ -184,6 +255,25 @@ public final class GoBackend implements Backend { return; } + + dnsRetry: for (int i = 0; i < DNS_RESOLUTION_RETRIES; ++i) { + // Pre-resolve IPs so they're cached when building the userspace string + for (final Peer peer : config.getPeers()) { + final InetEndpoint ep = peer.getEndpoint().orElse(null); + if (ep == null) + continue; + if (ep.getResolved().orElse(null) == null) { + if (i < DNS_RESOLUTION_RETRIES - 1) { + Log.w(TAG, "DNS host \"" + ep.getHost() + "\" failed to resolve; trying again"); + Thread.sleep(1000); + continue dnsRetry; + } else + throw new BackendException(Reason.DNS_RESOLUTION_FAILURE, ep.getHost()); + } + } + break; + } + // Build config final String goConfig = config.toWgUserspaceString(); @@ -203,6 +293,9 @@ public final class GoBackend implements Backend { for (final InetAddress addr : config.getInterface().getDnsServers()) builder.addDnsServer(addr.getHostAddress()); + for (final String dnsSearchDomain : config.getInterface().getDnsSearchDomains()) + builder.addSearchDomain(dnsSearchDomain); + boolean sawDefaultRoute = false; for (final Peer peer : config.getPeers()) { for (final InetNetwork addr : peer.getAllowedIps()) { @@ -229,7 +322,7 @@ public final class GoBackend implements Backend { try (final ParcelFileDescriptor tun = builder.establish()) { if (tun == null) throw new BackendException(Reason.TUN_CREATION_ERROR); - Log.d(TAG, "Go backend v" + wgVersion()); + Log.d(TAG, "Go backend " + wgVersion()); currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig); } if (currentTunnelHandle < 0) @@ -245,21 +338,23 @@ public final class GoBackend implements Backend { Log.w(TAG, "Tunnel already down"); return; } - - wgTurnOff(currentTunnelHandle); + int handleToClose = currentTunnelHandle; currentTunnel = null; currentTunnelHandle = -1; currentConfig = null; + wgTurnOff(handleToClose); + try { + vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf(); + } catch (final TimeoutException ignored) { } } tunnel.onStateChange(state); } - private void startVpnService() { - Log.d(TAG, "Requesting to start VpnService"); - context.startService(new Intent(context, VpnService.class)); - } - + /** + * Callback for {@link GoBackend} that is invoked when {@link VpnService} is started by the + * system's Always-On VPN mode. + */ public interface AlwaysOnCallback { void alwaysOnTriggered(); } @@ -293,6 +388,9 @@ public final class GoBackend implements Backend { } } + /** + * {@link android.net.VpnService} implementation for {@link GoBackend} + */ public static class VpnService extends android.net.VpnService { @Nullable private GoBackend owner; diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java index b4e01e76..d5d41c5f 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/Statistics.java @@ -1,64 +1,101 @@ /* - * Copyright © 2020 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.wireguard.android.backend; import android.os.SystemClock; -import android.util.Pair; import com.wireguard.crypto.Key; import com.wireguard.util.NonNullForAll; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import androidx.annotation.Nullable; + +/** + * Class representing transfer statistics for a {@link Tunnel} instance. + */ @NonNullForAll public class Statistics { - private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>(); + public record PeerStats(long rxBytes, long txBytes, long latestHandshakeEpochMillis) { } + private final Map<Key, PeerStats> stats = new HashMap<>(); private long lastTouched = SystemClock.elapsedRealtime(); Statistics() { } - void add(final Key key, final long rx, final long tx) { - peerBytes.put(key, Pair.create(rx, tx)); + /** + * Add a peer and its current stats to the internal map. + * + * @param key A WireGuard public key bound to a particular peer + * @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by + * the provided {@link Key}. This value is in bytes + * @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by + * the provided {@link Key}. This value is in bytes. + * @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer} + * referenced by the provided {@link Key}. The value is in epoch milliseconds. + */ + void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) { + stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake)); lastTouched = SystemClock.elapsedRealtime(); } + /** + * Check if the statistics are stale, indicating the need for the {@link Backend} to update them. + * + * @return boolean indicating if the current statistics instance has stale values. + */ public boolean isStale() { return SystemClock.elapsedRealtime() - lastTouched > 900; } - public long peerRx(final Key peer) { - if (!peerBytes.containsKey(peer)) - return 0; - return peerBytes.get(peer).first; - } - - public long peerTx(final Key peer) { - if (!peerBytes.containsKey(peer)) - return 0; - return peerBytes.get(peer).second; + /** + * Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key} + * + * @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}. + * @return a {@link PeerStats} representing various statistics about this peer. + */ + @Nullable + public PeerStats peer(final Key peer) { + return stats.get(peer); } + /** + * Get the list of peers being tracked by this instance. + * + * @return An array of {@link Key} instances representing WireGuard + * {@link com.wireguard.config.Peer}s + */ public Key[] peers() { - return peerBytes.keySet().toArray(new Key[0]); + return stats.keySet().toArray(new Key[0]); } + /** + * Get the total received traffic by all the peers being tracked by this instance + * + * @return a long representing the number of bytes received by the peers being tracked. + */ public long totalRx() { long rx = 0; - for (final Pair<Long, Long> val : peerBytes.values()) { - rx += val.first; + for (final PeerStats val : stats.values()) { + rx += val.rxBytes; } return rx; } + /** + * Get the total transmitted traffic by all the peers being tracked by this instance + * + * @return a long representing the number of bytes transmitted by the peers being tracked. + */ public long totalTx() { long tx = 0; - for (final Pair<Long, Long> val : peerBytes.values()) { - tx += val.second; + for (final PeerStats val : stats.values()) { + tx += val.txBytes; } return tx; } diff --git a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java index a9761929..dbc91c27 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/Tunnel.java @@ -1,5 +1,5 @@ /* - * Copyright © 2020 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -36,11 +36,20 @@ public interface Tunnel { */ void onStateChange(State newState); + /** + * Enum class to represent all possible states of a {@link Tunnel}. + */ enum State { DOWN, TOGGLE, UP; + /** + * Get the state of a {@link Tunnel} + * + * @param running boolean indicating if the tunnel is running. + * @return State of the tunnel based on whether or not it is running. + */ public static State of(final boolean running) { return running ? UP : DOWN; } diff --git a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java index d4d1f152..87fdf6e5 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -20,6 +20,7 @@ import com.wireguard.util.NonNullForAll; import java.io.File; import java.io.FileOutputStream; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,11 +33,10 @@ import java.util.Objects; import java.util.Set; import androidx.annotation.Nullable; -import java9.util.stream.Collectors; -import java9.util.stream.Stream; /** - * WireGuard backend that uses {@code wg-quick} to implement tunnel configuration. + * Implementation of {@link Backend} that uses the kernel module and {@code wg-quick} to provide + * WireGuard tunnels. */ @NonNullForAll @@ -54,6 +54,10 @@ public final class WgQuickBackend implements Backend { this.toolsInstaller = toolsInstaller; } + public static boolean hasKernelSupport() { + return new File("/sys/module/wireguard").exists(); + } + @Override public Set<String> getRunningTunnelNames() { final List<String> output = new ArrayList<>(); @@ -67,7 +71,7 @@ public final class WgQuickBackend implements Backend { return Collections.emptySet(); } // wg puts all interface names on the same line. Split them into separate elements. - return Stream.of(output.get(0).split(" ")).collect(Collectors.toUnmodifiableSet()); + return Set.of(output.get(0).split(" ")); } @Override @@ -80,17 +84,17 @@ public final class WgQuickBackend implements Backend { final Statistics stats = new Statistics(); final Collection<String> output = new ArrayList<>(); try { - if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0) + if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0) return stats; } catch (final Exception ignored) { return stats; } for (final String line : output) { final String[] parts = line.split("\\t"); - if (parts.length != 3) + if (parts.length != 8) continue; try { - stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2])); + stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000); } catch (final Exception ignored) { } } 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 8f7749e1..00000000 --- a/tunnel/src/main/java/com/wireguard/android/util/ModuleLoader.java +++ /dev/null @@ -1,189 +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.util.NonNullForAll; - -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 androidx.annotation.Nullable; - -@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 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 static boolean isModuleLoaded() { - return new File("/sys/module/wireguard").exists(); - } - - 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()) { - 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); - 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; - } - - 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 boolean moduleMightExist() { - return moduleDir.exists() && moduleDir.isDirectory(); - } - - @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 { - final EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); - final Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm())); - final 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; - } - - 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); - } - } -} diff --git a/tunnel/src/main/java/com/wireguard/android/util/RootShell.java b/tunnel/src/main/java/com/wireguard/android/util/RootShell.java index 6fe89a83..5839ec67 100644 --- a/tunnel/src/main/java/com/wireguard/android/util/RootShell.java +++ b/tunnel/src/main/java/com/wireguard/android/util/RootShell.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -43,8 +43,11 @@ public class RootShell { public RootShell(final Context context) { localBinaryDir = new File(context.getCodeCacheDir(), "bin"); localTemporaryDir = new File(context.getCacheDir(), "tmp"); - preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n", - context.getPackageName(), localBinaryDir, localTemporaryDir); + final String packageName = context.getPackageName(); + if (packageName.contains("'")) + throw new RuntimeException("Impossibly invalid package name contains a single quote"); + preamble = String.format("export CALLING_PACKAGE='%s' PATH=\"%s:$PATH\" TMPDIR='%s'; magisk --sqlite \"UPDATE policies SET notification=0, logging=0 WHERE uid=%d\" >/dev/null 2>&1; id -u\n", + packageName, localBinaryDir, localTemporaryDir, android.os.Process.myUid()); } private static boolean isExecutableInPath(final String name) { diff --git a/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java b/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java index 6b2c6142..98e3e63d 100644 --- a/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java +++ b/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java index 37f00a37..d3882498 100644 --- a/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java +++ b/tunnel/src/main/java/com/wireguard/android/util/ToolsInstaller.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -143,9 +143,9 @@ public final class ToolsInstaller { extract(); final StringBuilder script = new StringBuilder("set -ex; "); - script.append("trap 'rm -rf /data/adb/moduleswireguard' INT TERM EXIT; "); + script.append("trap 'rm -rf /data/adb/modules/wireguard' INT TERM EXIT; "); script.append(String.format("rm -rf /data/adb/modules/wireguard/; mkdir -p /data/adb/modules/wireguard%s; ", INSTALL_DIR)); - script.append("printf 'name=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; "); + script.append("printf 'id=wireguard\nname=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; "); script.append("touch /data/adb/modules/wireguard/auto_mount; "); for (final String name : EXECUTABLES) { final File destination = new File("/data/adb/modules/wireguard" + INSTALL_DIR, name); diff --git a/tunnel/src/main/java/com/wireguard/config/Attribute.java b/tunnel/src/main/java/com/wireguard/config/Attribute.java index 659f7cf4..c43750d7 100644 --- a/tunnel/src/main/java/com/wireguard/config/Attribute.java +++ b/tunnel/src/main/java/com/wireguard/config/Attribute.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -8,11 +8,10 @@ package com.wireguard.config; import com.wireguard.util.NonNullForAll; import java.util.Iterator; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java9.util.Optional; - @NonNullForAll public final class Attribute { private static final Pattern LINE_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*([^\\s#][^#]*)"); diff --git a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java index d1e3c627..e70418c9 100644 --- a/tunnel/src/main/java/com/wireguard/config/BadConfigException.java +++ b/tunnel/src/main/java/com/wireguard/config/BadConfigException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/config/Config.java b/tunnel/src/main/java/com/wireguard/config/Config.java index a183a75a..21e45f5d 100644 --- a/tunnel/src/main/java/com/wireguard/config/Config.java +++ b/tunnel/src/main/java/com/wireguard/config/Config.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java index 573c522d..165a702b 100644 --- a/tunnel/src/main/java/com/wireguard/config/InetAddresses.java +++ b/tunnel/src/main/java/com/wireguard/config/InetAddresses.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -23,6 +23,7 @@ import androidx.annotation.Nullable; public final class InetAddresses { @Nullable private static final Method PARSER_METHOD; private static final Pattern WONT_TOUCH_RESOLVER = Pattern.compile("^(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?)|((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$"); + private static final Pattern VALID_HOSTNAME = Pattern.compile("^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\\.?$"); static { Method m = null; @@ -39,6 +40,16 @@ public final class InetAddresses { } /** + * Determines whether input is a valid DNS hostname. + * + * @param maybeHostname a string that is possibly a DNS hostname + * @return whether or not maybeHostname is a valid DNS hostname + */ + public static boolean isHostname(final CharSequence maybeHostname) { + return VALID_HOSTNAME.matcher(maybeHostname).matches(); + } + + /** * Parses a numeric IPv4 or IPv6 address without performing any DNS lookups. * * @param address a string representing the IP address diff --git a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java index 6eff4a1b..c0ef433e 100644 --- a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java +++ b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,18 +7,17 @@ package com.wireguard.config; import com.wireguard.util.NonNullForAll; -import org.threeten.bp.Duration; -import org.threeten.bp.Instant; - import java.net.Inet4Address; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; import java.util.regex.Pattern; import androidx.annotation.Nullable; -import java9.util.Optional; /** diff --git a/tunnel/src/main/java/com/wireguard/config/InetNetwork.java b/tunnel/src/main/java/com/wireguard/config/InetNetwork.java index 7fd12577..84aea82b 100644 --- a/tunnel/src/main/java/com/wireguard/config/InetNetwork.java +++ b/tunnel/src/main/java/com/wireguard/config/InetNetwork.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/config/Interface.java b/tunnel/src/main/java/com/wireguard/config/Interface.java index c49357f7..53ca9111 100644 --- a/tunnel/src/main/java/com/wireguard/config/Interface.java +++ b/tunnel/src/main/java/com/wireguard/config/Interface.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -20,13 +20,11 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import androidx.annotation.Nullable; -import java9.util.Lists; -import java9.util.Optional; -import java9.util.stream.Collectors; -import java9.util.stream.StreamSupport; /** * Represents the configuration for a WireGuard interface (an [Interface] block). Interfaces must @@ -42,6 +40,7 @@ public final class Interface { private final Set<InetNetwork> addresses; private final Set<InetAddress> dnsServers; + private final Set<String> dnsSearchDomains; private final Set<String> excludedApplications; private final Set<String> includedApplications; private final KeyPair keyPair; @@ -52,6 +51,7 @@ public final class Interface { // Defensively copy to ensure immutability even if the Builder is reused. addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses)); dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers)); + dnsSearchDomains = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsSearchDomains)); excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications)); includedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications)); keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key"); @@ -110,6 +110,7 @@ public final class Interface { final Interface other = (Interface) obj; return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers) + && dnsSearchDomains.equals(other.dnsSearchDomains) && excludedApplications.equals(other.excludedApplications) && includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair) @@ -138,6 +139,16 @@ public final class Interface { } /** + * Returns the set of DNS search domains associated with the interface. + * + * @return a set of strings + */ + public Set<String> getDnsSearchDomains() { + // The collection is already immutable. + return dnsSearchDomains; + } + + /** * Returns the set of applications excluded from using the interface. * * @return a set of package names @@ -223,9 +234,8 @@ public final class Interface { if (!addresses.isEmpty()) sb.append("Address = ").append(Attribute.join(addresses)).append('\n'); if (!dnsServers.isEmpty()) { - final List<String> dnsServerStrings = StreamSupport.stream(dnsServers) - .map(InetAddress::getHostAddress) - .collect(Collectors.toUnmodifiableList()); + final List<String> dnsServerStrings = dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList()); + dnsServerStrings.addAll(dnsSearchDomains); sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n'); } if (!excludedApplications.isEmpty()) @@ -258,6 +268,8 @@ public final class Interface { // Defaults to an empty set. private final Set<InetAddress> dnsServers = new LinkedHashSet<>(); // Defaults to an empty set. + private final Set<String> dnsSearchDomains = new LinkedHashSet<>(); + // Defaults to an empty set. private final Set<String> excludedApplications = new LinkedHashSet<>(); // Defaults to an empty set. private final Set<String> includedApplications = new LinkedHashSet<>(); @@ -288,6 +300,16 @@ public final class Interface { return this; } + public Builder addDnsSearchDomain(final String dnsSearchDomain) { + dnsSearchDomains.add(dnsSearchDomain); + return this; + } + + public Builder addDnsSearchDomains(final Collection<String> dnsSearchDomains) { + this.dnsSearchDomains.addAll(dnsSearchDomains); + return this; + } + public Interface build() throws BadConfigException { if (keyPair == null) throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, @@ -330,8 +352,15 @@ public final class Interface { public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException { try { - for (final String dnsServer : Attribute.split(dnsServers)) - addDnsServer(InetAddresses.parse(dnsServer)); + for (final String dnsServer : Attribute.split(dnsServers)) { + try { + addDnsServer(InetAddresses.parse(dnsServer)); + } catch (final ParseException e) { + if (e.getParsingClass() != InetAddress.class || !InetAddresses.isHostname(dnsServer)) + throw e; + addDnsSearchDomain(dnsServer); + } + } return this; } catch (final ParseException e) { throw new BadConfigException(Section.INTERFACE, Location.DNS, e); @@ -339,11 +368,11 @@ public final class Interface { } public Builder parseExcludedApplications(final CharSequence apps) { - return excludeApplications(Lists.of(Attribute.split(apps))); + return excludeApplications(List.of(Attribute.split(apps))); } public Builder parseIncludedApplications(final CharSequence apps) { - return includeApplications(Lists.of(Attribute.split(apps))); + return includeApplications(List.of(Attribute.split(apps))); } public Builder parseListenPort(final String listenPort) throws BadConfigException { diff --git a/tunnel/src/main/java/com/wireguard/config/ParseException.java b/tunnel/src/main/java/com/wireguard/config/ParseException.java index 289f1120..ff430e6b 100644 --- a/tunnel/src/main/java/com/wireguard/config/ParseException.java +++ b/tunnel/src/main/java/com/wireguard/config/ParseException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/config/Peer.java b/tunnel/src/main/java/com/wireguard/config/Peer.java index 984dbe9c..b308a937 100644 --- a/tunnel/src/main/java/com/wireguard/config/Peer.java +++ b/tunnel/src/main/java/com/wireguard/config/Peer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -17,10 +17,10 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.Set; import androidx.annotation.Nullable; -import java9.util.Optional; /** * Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key, diff --git a/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java b/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java index 10f51351..c9a592f2 100644 --- a/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java +++ b/tunnel/src/main/java/com/wireguard/crypto/Curve25519.java @@ -1,6 +1,6 @@ /* * Copyright © 2016 Southern Storm Software, Pty Ltd. - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,7 @@ import java.util.Arrays; import androidx.annotation.Nullable; /** - * Implementation of the Curve25519 elliptic curve algorithm. + * Implementation of Curve25519 ECDH. * <p> * This implementation was imported to WireGuard from noise-java: * https://github.com/rweather/noise-java @@ -28,7 +28,7 @@ import androidx.annotation.Nullable; */ @SuppressWarnings({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"}) @NonNullForAll -final class Curve25519 { +public final class Curve25519 { // Numbers modulo 2^255 - 19 are broken up into ten 26-bit words. private static final int NUM_LIMBS_255BIT = 10; private static final int NUM_LIMBS_510BIT = 20; diff --git a/tunnel/src/main/java/com/wireguard/crypto/Key.java b/tunnel/src/main/java/com/wireguard/crypto/Key.java index c11688d5..1aff6701 100644 --- a/tunnel/src/main/java/com/wireguard/crypto/Key.java +++ b/tunnel/src/main/java/com/wireguard/crypto/Key.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java b/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java index 8608fc36..b1503f29 100644 --- a/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java +++ b/tunnel/src/main/java/com/wireguard/crypto/KeyFormatException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java b/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java index 22c21734..85f94ca5 100644 --- a/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java +++ b/tunnel/src/main/java/com/wireguard/crypto/KeyPair.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java b/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java index eb5b683f..a6598fd8 100644 --- a/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java +++ b/tunnel/src/main/java/com/wireguard/util/NonNullForAll.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ |