diff options
Diffstat (limited to 'app/src')
22 files changed, 893 insertions, 919 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2496e6be..b81b0fcc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,8 +35,8 @@ <activity android:name=".activity.SettingsActivity" android:label="@string/settings" - android:theme="@style/SettingsTheme" - android:parentActivityName=".activity.MainActivity" /> + android:parentActivityName=".activity.MainActivity" + android:theme="@style/SettingsTheme" /> <activity android:name=".activity.TunnelCreatorActivity" @@ -58,11 +58,12 @@ <service android:name=".backend.GoBackend$VpnService" - android:permission="android.permission.BIND_VPN_SERVICE" > + android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> <action android:name="android.net.VpnService" /> </intent-filter> - <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON" + <meta-data + android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON" android:value="false" /> </service> diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java index c1b21373..f3cf0887 100644 --- a/app/src/main/java/com/wireguard/android/Application.java +++ b/app/src/main/java/com/wireguard/android/Application.java @@ -55,11 +55,11 @@ public class Application extends android.app.Application { public interface ApplicationComponent { AsyncWorker getAsyncWorker(); + Class getBackendType(); + ToolsInstaller getToolsInstaller(); TunnelManager getTunnelManager(); - - Class getBackendType(); } @Qualifier diff --git a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java index e48b6d88..fb5e7a09 100644 --- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java @@ -23,11 +23,6 @@ import java.util.List; */ public class SettingsActivity extends AppCompatActivity { - @FunctionalInterface - public interface PermissionRequestCallback { - void done(String[] permissions, int[] grantResults); - } - private HashMap<Integer, PermissionRequestCallback> permissionRequestCallbacks = new HashMap<>(); private int permissionRequestCounter = 0; @@ -49,15 +44,6 @@ public class SettingsActivity extends AppCompatActivity { } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - final PermissionRequestCallback f = permissionRequestCallbacks.get(requestCode); - if (f != null) { - permissionRequestCallbacks.remove(requestCode); - f.done(permissions, grantResults); - } - } - - @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) { @@ -78,6 +64,20 @@ public class SettingsActivity extends AppCompatActivity { } } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + final PermissionRequestCallback f = permissionRequestCallbacks.get(requestCode); + if (f != null) { + permissionRequestCallbacks.remove(requestCode); + f.done(permissions, grantResults); + } + } + + @FunctionalInterface + public interface PermissionRequestCallback { + void done(String[] permissions, int[] grantResults); + } + public static class SettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(final Bundle savedInstanceState, final String key) { diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java index 8201b116..519f7427 100644 --- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -27,48 +27,20 @@ import java9.util.concurrent.CompletableFuture; public final class GoBackend implements Backend { private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName(); + private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); static { System.loadLibrary("wg-go"); } + private Context context; private Tunnel currentTunnel; private int currentTunnelHandle = -1; - private Context context; - public GoBackend(Context context) { this.context = context; } - private void startVpnService() { - context.startService(new Intent(context, VpnService.class)); - } - - public static class VpnService extends android.net.VpnService { - @Override - public void onCreate() { - vpnService.complete(this); - super.onCreate(); - } - - @Override - public void onDestroy() { - for (final Tunnel tunnel : Application.getComponent().getTunnelManager().getTunnels()) { - if (tunnel != null && tunnel.getState() != State.DOWN) - tunnel.setState(State.DOWN); - } - vpnService = vpnService.newIncompleteFuture(); - super.onDestroy(); - } - - public Builder getBuilder() { - return new Builder(); - } - } - - private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); - private static native int wgGetSocketV4(int handle); private static native int wgGetSocketV6(int handle); @@ -219,4 +191,30 @@ public final class GoBackend implements Backend { currentTunnelHandle = -1; } } + + private void startVpnService() { + context.startService(new Intent(context, VpnService.class)); + } + + public static class VpnService extends android.net.VpnService { + public Builder getBuilder() { + return new Builder(); + } + + @Override + public void onCreate() { + vpnService.complete(this); + super.onCreate(); + } + + @Override + public void onDestroy() { + for (final Tunnel tunnel : Application.getComponent().getTunnelManager().getTunnels()) { + if (tunnel != null && tunnel.getState() != State.DOWN) + tunnel.setState(State.DOWN); + } + vpnService = vpnService.newIncompleteFuture(); + super.onDestroy(); + } + } } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java index 8d0ae4cf..01fa594b 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java @@ -20,6 +20,10 @@ import com.wireguard.config.Config; public class TunnelDetailFragment extends BaseFragment { private TunnelDetailFragmentBinding binding; + private void onConfigLoaded(final String name, final Config config) { + binding.setConfig(new Config.Observable(config, name)); + } + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -46,10 +50,6 @@ public class TunnelDetailFragment extends BaseFragment { super.onDestroyView(); } - private void onConfigLoaded(final String name, final Config config) { - binding.setConfig(new Config.Observable(config, name)); - } - @Override public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) { if (binding == null) diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java index b7467b14..a3760ed9 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java @@ -3,8 +3,6 @@ package com.wireguard.android.fragment; import android.app.Activity; import android.content.Context; import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.Snackbar; @@ -41,6 +39,24 @@ public class TunnelEditorFragment extends BaseFragment { binding.setConfig(new Config.Observable(config, name)); } + private void onConfigSaved(final Tunnel savedTunnel, final Config config, + final Throwable throwable) { + final String message; + if (throwable == null) { + message = getString(R.string.config_save_success, savedTunnel.getName()); + Log.d(TAG, message); + onFinished(); + } else { + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + message = getString(R.string.config_save_error, savedTunnel.getName(), error); + Log.e(TAG, message, throwable); + if (binding != null) { + final CoordinatorLayout container = binding.mainContainer; + Snackbar.make(container, message, Snackbar.LENGTH_LONG).show(); + } + } + } + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -48,40 +64,6 @@ public class TunnelEditorFragment extends BaseFragment { } @Override - public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) { - tunnel = newTunnel; - if (binding == null) - return; - binding.setConfig(new Config.Observable(null, null)); - if (tunnel != null) - tunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(tunnel.getName(), a)); - } - - @Override - public void onSaveInstanceState(@NonNull final Bundle outState) { - outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig()); - outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName()); - super.onSaveInstanceState(outState); - } - - @Override - public void onViewStateRestored(final Bundle savedInstanceState) { - if (savedInstanceState == null) { - onSelectedTunnelChanged(null, getSelectedTunnel()); - } else { - tunnel = getSelectedTunnel(); - Config.Observable config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG); - String originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME); - if (tunnel != null && !tunnel.getName().equals(originalName)) - onSelectedTunnelChanged(null, tunnel); - else - binding.setConfig(config); - } - - super.onViewStateRestored(savedInstanceState); - } - - @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.config_editor, menu); } @@ -159,22 +141,21 @@ public class TunnelEditorFragment extends BaseFragment { } } - private void onConfigSaved(final Tunnel savedTunnel, final Config config, - final Throwable throwable) { - final String message; - if (throwable == null) { - message = getString(R.string.config_save_success, savedTunnel.getName()); - Log.d(TAG, message); - onFinished(); - } else { - final String error = ExceptionLoggers.unwrap(throwable).getMessage(); - message = getString(R.string.config_save_error, savedTunnel.getName(), error); - Log.e(TAG, message, throwable); - if (binding != null) { - final CoordinatorLayout container = binding.mainContainer; - Snackbar.make(container, message, Snackbar.LENGTH_LONG).show(); - } - } + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig()); + outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName()); + super.onSaveInstanceState(outState); + } + + @Override + public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) { + tunnel = newTunnel; + if (binding == null) + return; + binding.setConfig(new Config.Observable(null, null)); + if (tunnel != null) + tunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(tunnel.getName(), a)); } private void onTunnelCreated(final Tunnel newTunnel, final Throwable throwable) { @@ -214,4 +195,21 @@ public class TunnelEditorFragment extends BaseFragment { } } } + + @Override + public void onViewStateRestored(final Bundle savedInstanceState) { + if (savedInstanceState == null) { + onSelectedTunnelChanged(null, getSelectedTunnel()); + } else { + tunnel = getSelectedTunnel(); + Config.Observable config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG); + String originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME); + if (tunnel != null && !tunnel.getName().equals(originalName)) + onSelectedTunnelChanged(null, tunnel); + else + binding.setConfig(config); + } + + super.onViewStateRestored(savedInstanceState); + } } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java index 719960af..5291d8fb 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -27,7 +27,6 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.TextView; import com.wireguard.android.Application; import com.wireguard.android.Application.ApplicationComponent; @@ -44,7 +43,6 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.zip.ZipEntry; @@ -70,6 +68,14 @@ public class TunnelListFragment extends BaseFragment { private TunnelListFragmentBinding binding; private TunnelManager tunnelManager; + public boolean collapseActionMenu() { + if (binding.createMenu.isExpanded()) { + binding.createMenu.collapse(); + return true; + } + return false; + } + private void importTunnel(final Uri uri) { final Activity activity = getActivity(); if (activity == null) @@ -199,14 +205,6 @@ public class TunnelListFragment extends BaseFragment { super.onDestroyView(); } - public boolean collapseActionMenu() { - if (binding.createMenu.isExpanded()) { - binding.createMenu.collapse(); - return true; - } - return false; - } - public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) { startActivity(new Intent(getActivity(), TunnelCreatorActivity.class)); if (binding != null) @@ -254,7 +252,7 @@ public class TunnelListFragment extends BaseFragment { if (tunnels.size() == 1 && throwables.isEmpty()) message = getString(R.string.import_success, tunnels.get(0).getName()); else if (tunnels.isEmpty() && throwables.size() == 1) - /* Use the exception message from above. */; + /* Use the exception message from above. */ ; else if (throwables.isEmpty()) message = getString(R.string.import_total_success, tunnels.size()); else if (!throwables.isEmpty()) diff --git a/app/src/main/java/com/wireguard/android/model/TunnelManager.java b/app/src/main/java/com/wireguard/android/model/TunnelManager.java index e4667c82..72980f5e 100644 --- a/app/src/main/java/com/wireguard/android/model/TunnelManager.java +++ b/app/src/main/java/com/wireguard/android/model/TunnelManager.java @@ -128,15 +128,6 @@ public final class TunnelManager extends BaseObservable { return tunnels; } - public void refreshTunnelStates() { - asyncWorker.supplyAsync(backend::enumerate) - .thenAccept(running -> { - for (final Tunnel tunnel : tunnels) - tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN); - }) - .whenComplete(ExceptionLoggers.E); - } - public void onCreate() { asyncWorker.supplyAsync(configStore::enumerate) .thenAcceptBoth(asyncWorker.supplyAsync(backend::enumerate), this::onTunnelsLoaded) @@ -151,6 +142,15 @@ public final class TunnelManager extends BaseObservable { setLastUsedTunnel(tunnels.get(lastUsedName)); } + public void refreshTunnelStates() { + asyncWorker.supplyAsync(backend::enumerate) + .thenAccept(running -> { + for (final Tunnel tunnel : tunnels) + tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN); + }) + .whenComplete(ExceptionLoggers.E); + } + public CompletionStage<Void> restoreState() { if (!preferences.getBoolean(KEY_RESTORE_ON_BOOT, false)) return CompletableFuture.completedFuture(null); diff --git a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java index 421be1df..b10d8388 100644 --- a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java @@ -6,9 +6,9 @@ import android.content.pm.PackageManager; import android.os.Environment; import android.support.design.widget.Snackbar; import android.support.v7.preference.Preference; -import android.view.ContextThemeWrapper; import android.util.AttributeSet; import android.util.Log; +import android.view.ContextThemeWrapper; import com.wireguard.android.Application; import com.wireguard.android.Application.ApplicationComponent; @@ -25,7 +25,6 @@ import java.io.FileOutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -50,19 +49,6 @@ public class ZipExporterPreference extends Preference { tunnelManager = applicationComponent.getTunnelManager(); } - @Override - public CharSequence getSummary() { - if (exportedFilePath == null) - return getContext().getString(R.string.export_summary); - else - return getContext().getString(R.string.export_success, exportedFilePath); - } - - @Override - public CharSequence getTitle() { - return getContext().getString(R.string.zip_exporter_title); - } - private void exportZip() { List<Tunnel> tunnels = new ArrayList<>(tunnelManager.getTunnels()); List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size()); @@ -124,9 +110,22 @@ public class ZipExporterPreference extends Preference { } @Override + public CharSequence getSummary() { + if (exportedFilePath == null) + return getContext().getString(R.string.export_summary); + else + return getContext().getString(R.string.export_success, exportedFilePath); + } + + @Override + public CharSequence getTitle() { + return getContext().getString(R.string.zip_exporter_title); + } + + @Override protected void onClick() { getPrefActivity(this).ensurePermissions( - new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, (permissions, granted) -> { if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) exportZip(); diff --git a/app/src/main/java/com/wireguard/config/Attribute.java b/app/src/main/java/com/wireguard/config/Attribute.java index 5ecd21f4..2d8070ac 100644 --- a/app/src/main/java/com/wireguard/config/Attribute.java +++ b/app/src/main/java/com/wireguard/config/Attribute.java @@ -30,6 +30,7 @@ enum Attribute { private static final Map<String, Attribute> KEY_MAP; private static final Pattern SEPARATOR_PATTERN = Pattern.compile("\\s|="); + private static Method parseNumericAddressMethod; static { KEY_MAP = new HashMap<>(Attribute.values().length); @@ -38,6 +39,14 @@ enum Attribute { } } + static { + try { + parseNumericAddressMethod = InetAddress.class.getMethod("parseNumericAddress", new Class[]{String.class}); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + private final Pattern pattern; private final String token; @@ -46,44 +55,35 @@ enum Attribute { this.token = token; } - public static Attribute match(final CharSequence line) { - return KEY_MAP.get(SEPARATOR_PATTERN.split(line)[0]); - } - public static <T> String listToString(final List<T> list) { return TextUtils.join(", ", list); } - public static String[] stringToList(final String string) { - if (string == null) - return new String[0]; - return string.trim().split("\\s*,\\s*"); - } - - private static Method parseNumericAddressMethod; - static { - try { - parseNumericAddressMethod = InetAddress.class.getMethod("parseNumericAddress", new Class[]{String.class}); - } catch (Exception e) { - throw new RuntimeException(e); - } + public static Attribute match(final CharSequence line) { + return KEY_MAP.get(SEPARATOR_PATTERN.split(line)[0]); } public static InetAddress parseIPString(final String address) { if (address == null || address.isEmpty()) throw new IllegalArgumentException("Empty address"); try { - return (InetAddress)parseNumericAddressMethod.invoke(null, new Object[]{address}); + return (InetAddress) parseNumericAddressMethod.invoke(null, new Object[]{address}); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { if (e.getCause() instanceof IllegalArgumentException) - throw (IllegalArgumentException)e.getCause(); + throw (IllegalArgumentException) e.getCause(); else throw new IllegalArgumentException(e.getCause()); } } + public static String[] stringToList(final String string) { + if (string == null) + return new String[0]; + return string.trim().split("\\s*,\\s*"); + } + public String composeWith(final Object value) { return String.format("%s = %s%n", token, value); } diff --git a/app/src/main/java/com/wireguard/config/Config.java b/app/src/main/java/com/wireguard/config/Config.java index 6330ff5e..7e6ff77c 100644 --- a/app/src/main/java/com/wireguard/config/Config.java +++ b/app/src/main/java/com/wireguard/config/Config.java @@ -1,7 +1,5 @@ package com.wireguard.config; -import com.android.databinding.library.baseAdapters.BR; - import android.databinding.BaseObservable; import android.databinding.Bindable; import android.databinding.ObservableArrayList; @@ -9,6 +7,8 @@ import android.databinding.ObservableList; import android.os.Parcel; import android.os.Parcelable; +import com.android.databinding.library.baseAdapters.BR; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -22,90 +22,6 @@ import java.util.List; */ public class Config { - public static class Observable extends BaseObservable implements Parcelable { - private String name; - private Interface.Observable observableInterface; - private ObservableList<Peer.Observable> observablePeers; - - public Observable(Config parent, String name) { - this.name = name; - loadData(parent); - } - - public void loadData(Config parent) { - this.observableInterface = new Interface.Observable(parent == null ? null : parent.interfaceSection); - this.observablePeers = new ObservableArrayList<>(); - if (parent != null) { - for (Peer peer : parent.getPeers()) - this.observablePeers.add(new Peer.Observable(peer)); - } - } - - public void commitData(Config parent) { - this.observableInterface.commitData(parent.interfaceSection); - List<Peer> newPeers = new ArrayList<>(this.observablePeers.size()); - for (Peer.Observable observablePeer : this.observablePeers) { - Peer peer = new Peer(); - observablePeer.commitData(peer); - newPeers.add(peer); - } - parent.peers = newPeers; - notifyChange(); - } - - @Bindable - public String getName() { - return name == null ? "" : name; - } - - public void setName(String name) { - this.name = name; - notifyPropertyChanged(BR.name); - } - - @Bindable - public Interface.Observable getInterfaceSection() { - return observableInterface; - } - - @Bindable - public ObservableList<Peer.Observable> getPeers() { - return observablePeers; - } - - - public static final Creator<Observable> CREATOR = new Creator<Observable>() { - @Override - public Observable createFromParcel(final Parcel in) { - return new Observable(in); - } - - @Override - public Observable[] newArray(final int size) { - return new Observable[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeString(name); - dest.writeParcelable(observableInterface, flags); - dest.writeTypedList(observablePeers); - } - - private Observable(final Parcel in) { - name = in.readString(); - observableInterface = in.readParcelable(Interface.Observable.class.getClassLoader()); - observablePeers = new ObservableArrayList<>(); - in.readTypedList(observablePeers, Peer.Observable.CREATOR); - } - } - private final Interface interfaceSection = new Interface(); private List<Peer> peers = new ArrayList<>(); @@ -157,4 +73,86 @@ public class Config { sb.append('\n').append(peer); return sb.toString(); } + + public static class Observable extends BaseObservable implements Parcelable { + public static final Creator<Observable> CREATOR = new Creator<Observable>() { + @Override + public Observable createFromParcel(final Parcel in) { + return new Observable(in); + } + + @Override + public Observable[] newArray(final int size) { + return new Observable[size]; + } + }; + private String name; + private Interface.Observable observableInterface; + private ObservableList<Peer.Observable> observablePeers; + + public Observable(Config parent, String name) { + this.name = name; + loadData(parent); + } + + private Observable(final Parcel in) { + name = in.readString(); + observableInterface = in.readParcelable(Interface.Observable.class.getClassLoader()); + observablePeers = new ObservableArrayList<>(); + in.readTypedList(observablePeers, Peer.Observable.CREATOR); + } + + public void commitData(Config parent) { + this.observableInterface.commitData(parent.interfaceSection); + List<Peer> newPeers = new ArrayList<>(this.observablePeers.size()); + for (Peer.Observable observablePeer : this.observablePeers) { + Peer peer = new Peer(); + observablePeer.commitData(peer); + newPeers.add(peer); + } + parent.peers = newPeers; + notifyChange(); + } + + @Override + public int describeContents() { + return 0; + } + + @Bindable + public Interface.Observable getInterfaceSection() { + return observableInterface; + } + + @Bindable + public String getName() { + return name == null ? "" : name; + } + + @Bindable + public ObservableList<Peer.Observable> getPeers() { + return observablePeers; + } + + public void loadData(Config parent) { + this.observableInterface = new Interface.Observable(parent == null ? null : parent.interfaceSection); + this.observablePeers = new ObservableArrayList<>(); + if (parent != null) { + for (Peer peer : parent.getPeers()) + this.observablePeers.add(new Peer.Observable(peer)); + } + } + + public void setName(String name) { + this.name = name; + notifyPropertyChanged(BR.name); + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + dest.writeString(name); + dest.writeParcelable(observableInterface, flags); + dest.writeTypedList(observablePeers); + } + } } diff --git a/app/src/main/java/com/wireguard/config/Interface.java b/app/src/main/java/com/wireguard/config/Interface.java index 758b528d..ff69ccaf 100644 --- a/app/src/main/java/com/wireguard/config/Interface.java +++ b/app/src/main/java/com/wireguard/config/Interface.java @@ -17,158 +17,34 @@ import java.util.List; */ public class Interface { - public static class Observable extends BaseObservable implements Parcelable { - private String addresses; - private String dnses; - private String publicKey; - private String privateKey; - private String listenPort; - private String mtu; - - public Observable(Interface parent) { - if (parent != null) - loadData(parent); - } - - public void loadData(Interface parent) { - this.addresses = parent.getAddressString(); - this.dnses = parent.getDnsString(); - this.publicKey = parent.getPublicKey(); - this.privateKey = parent.getPrivateKey(); - this.listenPort = parent.getListenPortString(); - this.mtu = parent.getMtuString(); - } - - public void commitData(Interface parent) { - parent.setAddressString(this.addresses); - parent.setDnsString(this.dnses); - parent.setPrivateKey(this.privateKey); - parent.setListenPortString(this.listenPort); - parent.setMtuString(this.mtu); - loadData(parent); - notifyChange(); - } - - @Bindable - public String getAddresses() { - return addresses; - } - - public void setAddresses(String addresses) { - this.addresses = addresses; - notifyPropertyChanged(BR.addresses); - } - - @Bindable - public String getDnses() { - return dnses; - } - - public void setDnses(String dnses) { - this.dnses = dnses; - notifyPropertyChanged(BR.dnses); - } - - @Bindable - public String getPublicKey() { - return publicKey; - } - - @Bindable - public String getPrivateKey() { - return privateKey; - } - - public void setPrivateKey(String privateKey) { - this.privateKey = privateKey; - - try { - this.publicKey = new Keypair(privateKey).getPublicKey(); - } catch (IllegalArgumentException ignored) { - this.publicKey = ""; - } - - notifyPropertyChanged(BR.privateKey); - notifyPropertyChanged(BR.publicKey); - } - - public void generateKeypair() { - Keypair keypair = new Keypair(); - privateKey = keypair.getPrivateKey(); - publicKey = keypair.getPublicKey(); - notifyPropertyChanged(BR.privateKey); - notifyPropertyChanged(BR.publicKey); - } - - @Bindable - public String getListenPort() { - return listenPort; - } - - public void setListenPort(String listenPort) { - this.listenPort = listenPort; - notifyPropertyChanged(BR.listenPort); - } - - @Bindable - public String getMtu() { - return mtu; - } - - public void setMtu(String mtu) { - this.mtu = mtu; - notifyPropertyChanged(BR.mtu); - } - - - public static final Creator<Observable> CREATOR = new Creator<Observable>() { - @Override - public Observable createFromParcel(final Parcel in) { - return new Observable(in); - } - - @Override - public Observable[] newArray(final int size) { - return new Observable[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeString(addresses); - dest.writeString(dnses); - dest.writeString(publicKey); - dest.writeString(privateKey); - dest.writeString(listenPort); - dest.writeString(mtu); - } - - private Observable(final Parcel in) { - addresses = in.readString(); - dnses = in.readString(); - publicKey = in.readString(); - privateKey = in.readString(); - listenPort = in.readString(); - mtu = in.readString(); - } - } - private List<IPCidr> addressList; private List<InetAddress> dnsList; private Keypair keypair; private int listenPort; private int mtu; - public Interface() { addressList = new LinkedList<>(); dnsList = new LinkedList<>(); } + private void addAddresses(String[] addresses) { + if (addresses != null && addresses.length > 0) { + for (final String addr : addresses) { + if (addr.isEmpty()) + throw new IllegalArgumentException("Address is empty"); + this.addressList.add(new IPCidr(addr)); + } + } + } + + private void addDnses(String[] dnses) { + if (dnses != null && dnses.length > 0) { + for (final String dns : dnses) { + this.dnsList.add(Attribute.parseIPString(dns)); + } + } + } + private String getAddressString() { if (addressList.isEmpty()) return null; @@ -179,6 +55,12 @@ public class Interface { return addressList.toArray(new IPCidr[addressList.size()]); } + private String getDnsString() { + if (dnsList.isEmpty()) + return null; + return Attribute.listToString(getDnsStrings()); + } + private List<String> getDnsStrings() { List<String> strings = new LinkedList<>(); for (final InetAddress addr : dnsList) @@ -186,12 +68,6 @@ public class Interface { return strings; } - private String getDnsString() { - if (dnsList.isEmpty()) - return null; - return Attribute.listToString(getDnsStrings()); - } - public InetAddress[] getDnses() { return dnsList.toArray(new InetAddress[dnsList.size()]); } @@ -244,29 +120,11 @@ public class Interface { throw new IllegalArgumentException(line); } - private void addAddresses(String[] addresses) { - if (addresses != null && addresses.length > 0) { - for (final String addr : addresses) { - if (addr.isEmpty()) - throw new IllegalArgumentException("Address is empty"); - this.addressList.add(new IPCidr(addr)); - } - } - } - private void setAddressString(final String addressString) { this.addressList.clear(); addAddresses(Attribute.stringToList(addressString)); } - private void addDnses(String[] dnses) { - if (dnses != null && dnses.length > 0) { - for (final String dns : dnses) { - this.dnsList.add(Attribute.parseIPString(dns)); - } - } - } - private void setDnsString(final String dnsString) { this.dnsList.clear(); addDnses(Attribute.stringToList(dnsString)); @@ -318,4 +176,143 @@ public class Interface { sb.append(Attribute.PRIVATE_KEY.composeWith(keypair.getPrivateKey())); return sb.toString(); } + + public static class Observable extends BaseObservable implements Parcelable { + public static final Creator<Observable> CREATOR = new Creator<Observable>() { + @Override + public Observable createFromParcel(final Parcel in) { + return new Observable(in); + } + + @Override + public Observable[] newArray(final int size) { + return new Observable[size]; + } + }; + private String addresses; + private String dnses; + private String listenPort; + private String mtu; + private String privateKey; + private String publicKey; + + public Observable(Interface parent) { + if (parent != null) + loadData(parent); + } + + private Observable(final Parcel in) { + addresses = in.readString(); + dnses = in.readString(); + publicKey = in.readString(); + privateKey = in.readString(); + listenPort = in.readString(); + mtu = in.readString(); + } + + public void commitData(Interface parent) { + parent.setAddressString(this.addresses); + parent.setDnsString(this.dnses); + parent.setPrivateKey(this.privateKey); + parent.setListenPortString(this.listenPort); + parent.setMtuString(this.mtu); + loadData(parent); + notifyChange(); + } + + @Override + public int describeContents() { + return 0; + } + + public void generateKeypair() { + Keypair keypair = new Keypair(); + privateKey = keypair.getPrivateKey(); + publicKey = keypair.getPublicKey(); + notifyPropertyChanged(BR.privateKey); + notifyPropertyChanged(BR.publicKey); + } + + @Bindable + public String getAddresses() { + return addresses; + } + + @Bindable + public String getDnses() { + return dnses; + } + + @Bindable + public String getListenPort() { + return listenPort; + } + + @Bindable + public String getMtu() { + return mtu; + } + + @Bindable + public String getPrivateKey() { + return privateKey; + } + + @Bindable + public String getPublicKey() { + return publicKey; + } + + public void loadData(Interface parent) { + this.addresses = parent.getAddressString(); + this.dnses = parent.getDnsString(); + this.publicKey = parent.getPublicKey(); + this.privateKey = parent.getPrivateKey(); + this.listenPort = parent.getListenPortString(); + this.mtu = parent.getMtuString(); + } + + public void setAddresses(String addresses) { + this.addresses = addresses; + notifyPropertyChanged(BR.addresses); + } + + public void setDnses(String dnses) { + this.dnses = dnses; + notifyPropertyChanged(BR.dnses); + } + + public void setListenPort(String listenPort) { + this.listenPort = listenPort; + notifyPropertyChanged(BR.listenPort); + } + + public void setMtu(String mtu) { + this.mtu = mtu; + notifyPropertyChanged(BR.mtu); + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + + try { + this.publicKey = new Keypair(privateKey).getPublicKey(); + } catch (IllegalArgumentException ignored) { + this.publicKey = ""; + } + + notifyPropertyChanged(BR.privateKey); + notifyPropertyChanged(BR.publicKey); + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + dest.writeString(addresses); + dest.writeString(dnses); + dest.writeString(publicKey); + dest.writeString(privateKey); + dest.writeString(listenPort); + dest.writeString(mtu); + } + } } diff --git a/app/src/main/java/com/wireguard/config/Peer.java b/app/src/main/java/com/wireguard/config/Peer.java index 327365b3..9f71089f 100644 --- a/app/src/main/java/com/wireguard/config/Peer.java +++ b/app/src/main/java/com/wireguard/config/Peer.java @@ -22,136 +22,26 @@ import java.util.Locale; */ public class Peer { - public static class Observable extends BaseObservable implements Parcelable { - private String allowedIPs; - private String endpoint; - private String persistentKeepalive; - private String preSharedKey; - private String publicKey; - - public Observable(Peer parent) { - loadData(parent); - } - public static Observable newInstance() { - return new Observable(new Peer()); - } - - public void loadData(Peer parent) { - this.allowedIPs = parent.getAllowedIPsString(); - this.endpoint = parent.getEndpointString(); - this.persistentKeepalive = parent.getPersistentKeepaliveString(); - this.preSharedKey = parent.getPreSharedKey(); - this.publicKey = parent.getPublicKey(); - } - - public void commitData(Peer parent) { - parent.setAllowedIPsString(this.allowedIPs); - parent.setEndpointString(this.endpoint); - parent.setPersistentKeepaliveString(this.persistentKeepalive); - parent.setPreSharedKey(this.preSharedKey); - parent.setPublicKey(this.publicKey); - if (parent.getPublicKey() == null) - throw new IllegalArgumentException("Peer public key may not be empty"); - loadData(parent); - notifyChange(); - } - - @Bindable - public String getAllowedIPs() { - return allowedIPs; - } - - public void setAllowedIPs(String allowedIPs) { - this.allowedIPs = allowedIPs; - notifyPropertyChanged(BR.allowedIPs); - } - - @Bindable - public String getEndpoint() { - return endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - notifyPropertyChanged(BR.endpoint); - } - - @Bindable - public String getPersistentKeepalive() { - return persistentKeepalive; - } - - public void setPersistentKeepalive(String persistentKeepalive) { - this.persistentKeepalive = persistentKeepalive; - notifyPropertyChanged(BR.persistentKeepalive); - } - - @Bindable - public String getPreSharedKey() { - return preSharedKey; - } - - public void setPreSharedKey(String preSharedKey) { - this.preSharedKey = preSharedKey; - notifyPropertyChanged(BR.preSharedKey); - } - - @Bindable - public String getPublicKey() { - return publicKey; - } - - public void setPublicKey(String publicKey) { - this.publicKey = publicKey; - notifyPropertyChanged(BR.publicKey); - } - - - public static final Creator<Observable> CREATOR = new Creator<Observable>() { - @Override - public Observable createFromParcel(final Parcel in) { - return new Observable(in); - } - - @Override - public Observable[] newArray(final int size) { - return new Observable[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeString(allowedIPs); - dest.writeString(endpoint); - dest.writeString(persistentKeepalive); - dest.writeString(preSharedKey); - dest.writeString(publicKey); - } - - private Observable(final Parcel in) { - allowedIPs = in.readString(); - endpoint = in.readString(); - persistentKeepalive = in.readString(); - preSharedKey = in.readString(); - publicKey = in.readString(); - } - } - private List<IPCidr> allowedIPsList; private InetSocketAddress endpoint; private int persistentKeepalive; private String preSharedKey; private String publicKey; - public Peer() { allowedIPsList = new LinkedList<>(); } + private void addAllowedIPs(String[] allowedIPs) { + if (allowedIPs != null && allowedIPs.length > 0) { + for (final String allowedIP : allowedIPs) { + this.allowedIPsList.add(new IPCidr(allowedIP)); + } + } + } + + public IPCidr[] getAllowedIPs() { + return allowedIPsList.toArray(new IPCidr[allowedIPsList.size()]); + } private String getAllowedIPsString() { if (allowedIPsList.isEmpty()) @@ -159,10 +49,6 @@ public class Peer { return Attribute.listToString(allowedIPsList); } - public IPCidr[] getAllowedIPs() { - return allowedIPsList.toArray(new IPCidr[allowedIPsList.size()]); - } - public InetSocketAddress getEndpoint() { return endpoint; } @@ -173,24 +59,6 @@ public class Peer { return String.format(Locale.getDefault(), "%s:%d", endpoint.getHostString(), endpoint.getPort()); } - public String getResolvedEndpointString() throws UnknownHostException { - if (endpoint == null) - throw new UnknownHostException("{empty}"); - if (endpoint.isUnresolved()) - endpoint = new InetSocketAddress(endpoint.getHostString(), endpoint.getPort()); - if (endpoint.isUnresolved()) - throw new UnknownHostException(endpoint.getHostString()); - if (endpoint.getAddress() instanceof Inet6Address) - return String.format(Locale.getDefault(), - "[%s]:%d", - endpoint.getAddress().getHostAddress(), - endpoint.getPort()); - return String.format(Locale.getDefault(), - "%s:%d", - endpoint.getAddress().getHostAddress(), - endpoint.getPort()); - } - public int getPersistentKeepalive() { return persistentKeepalive; } @@ -209,6 +77,24 @@ public class Peer { return publicKey; } + public String getResolvedEndpointString() throws UnknownHostException { + if (endpoint == null) + throw new UnknownHostException("{empty}"); + if (endpoint.isUnresolved()) + endpoint = new InetSocketAddress(endpoint.getHostString(), endpoint.getPort()); + if (endpoint.isUnresolved()) + throw new UnknownHostException(endpoint.getHostString()); + if (endpoint.getAddress() instanceof Inet6Address) + return String.format(Locale.getDefault(), + "[%s]:%d", + endpoint.getAddress().getHostAddress(), + endpoint.getPort()); + return String.format(Locale.getDefault(), + "%s:%d", + endpoint.getAddress().getHostAddress(), + endpoint.getPort()); + } + public void parse(final String line) { final Attribute key = Attribute.match(line); if (key == Attribute.ALLOWED_IPS) @@ -225,14 +111,6 @@ public class Peer { throw new IllegalArgumentException(line); } - private void addAllowedIPs(String[] allowedIPs) { - if (allowedIPs != null && allowedIPs.length > 0) { - for (final String allowedIP : allowedIPs) { - this.allowedIPsList.add(new IPCidr(allowedIP)); - } - } - } - private void setAllowedIPsString(final String allowedIPsString) { this.allowedIPsList.clear(); addAllowedIPs(Attribute.stringToList(allowedIPsString)); @@ -246,7 +124,7 @@ public class Peer { if (endpoint != null && !endpoint.isEmpty()) { InetSocketAddress constructedEndpoint; if (endpoint.indexOf('/') != -1 || endpoint.indexOf('?') != -1 || endpoint.indexOf('#') != -1) - throw new IllegalArgumentException("Forbidden characters in endpoint"); + throw new IllegalArgumentException("Forbidden characters in endpoint"); URI uri; try { uri = new URI("wg://" + endpoint); @@ -301,4 +179,123 @@ public class Peer { sb.append(Attribute.PUBLIC_KEY.composeWith(publicKey)); return sb.toString(); } + + public static class Observable extends BaseObservable implements Parcelable { + public static final Creator<Observable> CREATOR = new Creator<Observable>() { + @Override + public Observable createFromParcel(final Parcel in) { + return new Observable(in); + } + + @Override + public Observable[] newArray(final int size) { + return new Observable[size]; + } + }; + private String allowedIPs; + private String endpoint; + private String persistentKeepalive; + private String preSharedKey; + private String publicKey; + + public Observable(Peer parent) { + loadData(parent); + } + + private Observable(final Parcel in) { + allowedIPs = in.readString(); + endpoint = in.readString(); + persistentKeepalive = in.readString(); + preSharedKey = in.readString(); + publicKey = in.readString(); + } + + public static Observable newInstance() { + return new Observable(new Peer()); + } + + public void commitData(Peer parent) { + parent.setAllowedIPsString(this.allowedIPs); + parent.setEndpointString(this.endpoint); + parent.setPersistentKeepaliveString(this.persistentKeepalive); + parent.setPreSharedKey(this.preSharedKey); + parent.setPublicKey(this.publicKey); + if (parent.getPublicKey() == null) + throw new IllegalArgumentException("Peer public key may not be empty"); + loadData(parent); + notifyChange(); + } + + @Override + public int describeContents() { + return 0; + } + + @Bindable + public String getAllowedIPs() { + return allowedIPs; + } + + @Bindable + public String getEndpoint() { + return endpoint; + } + + @Bindable + public String getPersistentKeepalive() { + return persistentKeepalive; + } + + @Bindable + public String getPreSharedKey() { + return preSharedKey; + } + + @Bindable + public String getPublicKey() { + return publicKey; + } + + public void loadData(Peer parent) { + this.allowedIPs = parent.getAllowedIPsString(); + this.endpoint = parent.getEndpointString(); + this.persistentKeepalive = parent.getPersistentKeepaliveString(); + this.preSharedKey = parent.getPreSharedKey(); + this.publicKey = parent.getPublicKey(); + } + + public void setAllowedIPs(String allowedIPs) { + this.allowedIPs = allowedIPs; + notifyPropertyChanged(BR.allowedIPs); + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + notifyPropertyChanged(BR.endpoint); + } + + public void setPersistentKeepalive(String persistentKeepalive) { + this.persistentKeepalive = persistentKeepalive; + notifyPropertyChanged(BR.persistentKeepalive); + } + + public void setPreSharedKey(String preSharedKey) { + this.preSharedKey = preSharedKey; + notifyPropertyChanged(BR.preSharedKey); + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + notifyPropertyChanged(BR.publicKey); + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + dest.writeString(allowedIPs); + dest.writeString(endpoint); + dest.writeString(persistentKeepalive); + dest.writeString(preSharedKey); + dest.writeString(publicKey); + } + } } diff --git a/app/src/main/java/com/wireguard/crypto/Curve25519.java b/app/src/main/java/com/wireguard/crypto/Curve25519.java index 5d27c3e3..0956c4fb 100644 --- a/app/src/main/java/com/wireguard/crypto/Curve25519.java +++ b/app/src/main/java/com/wireguard/crypto/Curve25519.java @@ -26,16 +26,16 @@ import java.util.Arrays; /** * Implementation of the Curve25519 elliptic curve algorithm. - * + * <p> * This implementation is based on that from arduinolibs: * https://github.com/rweather/arduinolibs - * + * <p> * This implementation is copied verbatim from noise-java: * https://github.com/rweather/noise-java - * + * <p> * Differences in this version are due to using 26-bit limbs for the * representation instead of the 8/16/32-bit limbs in the original. - * + * <p> * References: http://cr.yp.to/ecdh.html, RFC 7748 */ @SuppressWarnings("ALL") @@ -44,47 +44,148 @@ 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; - private int[] x_1; - private int[] x_2; - private int[] x_3; - private int[] z_2; - private int[] z_3; private int[] A; + private int[] AA; private int[] B; + private int[] BB; private int[] C; + private int[] CB; private int[] D; - private int[] E; - private int[] AA; - private int[] BB; private int[] DA; - private int[] CB; + private int[] E; private long[] t1; private int[] t2; + private int[] x_1; + private int[] x_2; + private int[] x_3; + private int[] z_2; + private int[] z_3; /** * Constructs the temporary state holder for Curve25519 evaluation. */ - private Curve25519() - { + private Curve25519() { // Allocate memory for all of the temporary variables we will need. - x_1 = new int [NUM_LIMBS_255BIT]; - x_2 = new int [NUM_LIMBS_255BIT]; - x_3 = new int [NUM_LIMBS_255BIT]; - z_2 = new int [NUM_LIMBS_255BIT]; - z_3 = new int [NUM_LIMBS_255BIT]; - A = new int [NUM_LIMBS_255BIT]; - B = new int [NUM_LIMBS_255BIT]; - C = new int [NUM_LIMBS_255BIT]; - D = new int [NUM_LIMBS_255BIT]; - E = new int [NUM_LIMBS_255BIT]; - AA = new int [NUM_LIMBS_255BIT]; - BB = new int [NUM_LIMBS_255BIT]; - DA = new int [NUM_LIMBS_255BIT]; - CB = new int [NUM_LIMBS_255BIT]; - t1 = new long [NUM_LIMBS_510BIT]; - t2 = new int [NUM_LIMBS_510BIT]; + x_1 = new int[NUM_LIMBS_255BIT]; + x_2 = new int[NUM_LIMBS_255BIT]; + x_3 = new int[NUM_LIMBS_255BIT]; + z_2 = new int[NUM_LIMBS_255BIT]; + z_3 = new int[NUM_LIMBS_255BIT]; + A = new int[NUM_LIMBS_255BIT]; + B = new int[NUM_LIMBS_255BIT]; + C = new int[NUM_LIMBS_255BIT]; + D = new int[NUM_LIMBS_255BIT]; + E = new int[NUM_LIMBS_255BIT]; + AA = new int[NUM_LIMBS_255BIT]; + BB = new int[NUM_LIMBS_255BIT]; + DA = new int[NUM_LIMBS_255BIT]; + CB = new int[NUM_LIMBS_255BIT]; + t1 = new long[NUM_LIMBS_510BIT]; + t2 = new int[NUM_LIMBS_510BIT]; } + /** + * Conditional swap of two values. + * + * @param select Set to 1 to swap, 0 to leave as-is. + * @param x The first value. + * @param y The second value. + */ + private static void cswap(int select, int[] x, int[] y) { + int dummy; + select = -select; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { + dummy = select & (x[index] ^ y[index]); + x[index] ^= dummy; + y[index] ^= dummy; + } + } + + /** + * Evaluates the Curve25519 curve. + * + * @param result Buffer to place the result of the evaluation into. + * @param offset Offset into the result buffer. + * @param privateKey The private key to use in the evaluation. + * @param publicKey The public key to use in the evaluation, or null + * if the base point of the curve should be used. + */ + public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey) { + Curve25519 state = new Curve25519(); + try { + // Unpack the public key value. If null, use 9 as the base point. + Arrays.fill(state.x_1, 0); + if (publicKey != null) { + // Convert the input value from little-endian into 26-bit limbs. + for (int index = 0; index < 32; ++index) { + int bit = (index * 8) % 26; + int word = (index * 8) / 26; + int value = publicKey[index] & 0xFF; + if (bit <= (26 - 8)) { + state.x_1[word] |= value << bit; + } else { + state.x_1[word] |= value << bit; + state.x_1[word] &= 0x03FFFFFF; + state.x_1[word + 1] |= value >> (26 - bit); + } + } + + // Just in case, we reduce the number modulo 2^255 - 19 to + // make sure that it is in range of the field before we start. + // This eliminates values between 2^255 - 19 and 2^256 - 1. + state.reduceQuick(state.x_1); + state.reduceQuick(state.x_1); + } else { + state.x_1[0] = 9; + } + + // Initialize the other temporary variables. + Arrays.fill(state.x_2, 0); // x_2 = 1 + state.x_2[0] = 1; + Arrays.fill(state.z_2, 0); // z_2 = 0 + System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1 + Arrays.fill(state.z_3, 0); // z_3 = 1 + state.z_3[0] = 1; + + // Evaluate the curve for every bit of the private key. + state.evalCurve(privateKey); + + // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19. + state.recip(state.z_3, state.z_2); + state.mul(state.x_2, state.x_2, state.z_3); + + // Convert x_2 into little-endian in the result buffer. + for (int index = 0; index < 32; ++index) { + int bit = (index * 8) % 26; + int word = (index * 8) / 26; + if (bit <= (26 - 8)) + result[offset + index] = (byte) (state.x_2[word] >> bit); + else + result[offset + index] = (byte) ((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit))); + } + } finally { + // Clean up all temporary state before we exit. + state.destroy(); + } + } + + /** + * Adds two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to add. + * @param y The second number to add. + */ + private void add(int[] result, int[] x, int[] y) { + int index, carry; + carry = x[0] + y[0]; + result[0] = carry & 0x03FFFFFF; + for (index = 1; index < NUM_LIMBS_255BIT; ++index) { + carry = (carry >> 26) + x[index] + y[index]; + result[index] = carry & 0x03FFFFFF; + } + reduceQuick(result); + } /** * Destroy all sensitive data in this object. @@ -105,107 +206,82 @@ public final class Curve25519 { Arrays.fill(BB, 0); Arrays.fill(DA, 0); Arrays.fill(CB, 0); - Arrays.fill(t1, 0L); - Arrays.fill(t2, 0); + Arrays.fill(t1, 0L); + Arrays.fill(t2, 0); } /** - * Reduces a number modulo 2^255 - 19 where it is known that the - * number can be reduced with only 1 trial subtraction. + * Evaluates the curve for every bit in a secret key. * - * @param x The number to reduce, and the result. + * @param s The 32-byte secret key. */ - private void reduceQuick(int[] x) - { - int index, carry; - - // Perform a trial subtraction of (2^255 - 19) from "x" which is - // equivalent to adding 19 and subtracting 2^255. We add 19 here; - // the subtraction of 2^255 occurs in the next step. - carry = 19; - for (index = 0; index < NUM_LIMBS_255BIT; ++index) { - carry += x[index]; - t2[index] = carry & 0x03FFFFFF; - carry >>= 26; - } + private void evalCurve(byte[] s) { + int sposn = 31; + int sbit = 6; + int svalue = s[sposn] | 0x40; + int swap = 0; + int select; - // If there was a borrow, then the original "x" is the correct answer. - // If there was no borrow, then "t2" is the correct answer. Select the - // correct answer but do it in a way that instruction timing will not - // reveal which value was selected. Borrow will occur if bit 21 of - // "t2" is zero. Turn the bit into a selection mask. - int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01); - int nmask = ~mask; - t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; - for (index = 0; index < NUM_LIMBS_255BIT; ++index) - x[index] = (x[index] & nmask) | (t2[index] & mask); - } + // Iterate over all 255 bits of "s" from the highest to the lowest. + // We ignore the high bit of the 256-bit representation of "s". + for (; ; ) { + // Conditional swaps on entry to this bit but only if we + // didn't swap on the previous bit. + select = (svalue >> sbit) & 0x01; + swap ^= select; + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + swap = select; - /** - * Reduce a number modulo 2^255 - 19. - * - * @param result The result. - * @param x The value to be reduced. This array will be - * modified during the reduction. - * @param size The number of limbs in the high order half of x. - */ - private void reduce(int[] result, int[] x, int size) - { - int index, limb, carry; + // Evaluate the curve. + add(A, x_2, z_2); // A = x_2 + z_2 + square(AA, A); // AA = A^2 + sub(B, x_2, z_2); // B = x_2 - z_2 + square(BB, B); // BB = B^2 + sub(E, AA, BB); // E = AA - BB + add(C, x_3, z_3); // C = x_3 + z_3 + sub(D, x_3, z_3); // D = x_3 - z_3 + mul(DA, D, A); // DA = D * A + mul(CB, C, B); // CB = C * B + add(x_3, DA, CB); // x_3 = (DA + CB)^2 + square(x_3, x_3); + sub(z_3, DA, CB); // z_3 = x_1 * (DA - CB)^2 + square(z_3, z_3); + mul(z_3, z_3, x_1); + mul(x_2, AA, BB); // x_2 = AA * BB + mulA24(z_2, E); // z_2 = E * (AA + a24 * E) + add(z_2, z_2, AA); + mul(z_2, z_2, E); - // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will - // either produce the answer we want or it will produce a - // value of the form "answer + j * (2^255 - 19)". There are - // 5 left-over bits in the top-most limb of the bottom half. - carry = 0; - limb = x[NUM_LIMBS_255BIT - 1] >> 21; - x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; - for (index = 0; index < size; ++index) { - limb += x[NUM_LIMBS_255BIT + index] << 5; - carry += (limb & 0x03FFFFFF) * 19 + x[index]; - x[index] = carry & 0x03FFFFFF; - limb >>= 26; - carry >>= 26; - } - if (size < NUM_LIMBS_255BIT) { - // The high order half of the number is short; e.g. for mulA24(). - // Propagate the carry through the rest of the low order part. - for (index = size; index < NUM_LIMBS_255BIT; ++index) { - carry += x[index]; - x[index] = carry & 0x03FFFFFF; - carry >>= 26; + // Move onto the next lower bit of "s". + if (sbit > 0) { + --sbit; + } else if (sposn == 0) { + break; + } else if (sposn == 1) { + --sposn; + svalue = s[sposn] & 0xF8; + sbit = 7; + } else { + --sposn; + svalue = s[sposn]; + sbit = 7; } } - // The "j" value may still be too large due to the final carry-out. - // We must repeat the reduction. If we already have the answer, - // then this won't do any harm but we must still do the calculation - // to preserve the overall timing. The "j" value will be between - // 0 and 19, which means that the carry we care about is in the - // top 5 bits of the highest limb of the bottom half. - carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19; - x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; - for (index = 0; index < NUM_LIMBS_255BIT; ++index) { - carry += x[index]; - result[index] = carry & 0x03FFFFFF; - carry >>= 26; - } - - // At this point "x" will either be the answer or it will be the - // answer plus (2^255 - 19). Perform a trial subtraction to - // complete the reduction process. - reduceQuick(result); + // Final conditional swaps. + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); } /** * Multiplies two numbers modulo 2^255 - 19. * * @param result The result. - * @param x The first number to multiply. - * @param y The second number to multiply. + * @param x The first number to multiply. + * @param y The second number to multiply. */ - private void mul(int[] result, int[] x, int[] y) - { + private void mul(int[] result, int[] x, int[] y) { int i, j; // Multiply the two numbers to create the intermediate result. @@ -223,10 +299,10 @@ public final class Curve25519 { // Propagate carries and convert back into 26-bit words. v = t1[0]; - t2[0] = ((int)v) & 0x03FFFFFF; + t2[0] = ((int) v) & 0x03FFFFFF; for (i = 1; i < NUM_LIMBS_510BIT; ++i) { v = (v >> 26) + t1[i]; - t2[i] = ((int)v) & 0x03FFFFFF; + t2[i] = ((int) v) & 0x03FFFFFF; } // Reduce the result modulo 2^255 - 19. @@ -234,113 +310,31 @@ public final class Curve25519 { } /** - * Squares a number modulo 2^255 - 19. - * - * @param result The result. - * @param x The number to square. - */ - private void square(int[] result, int[] x) - { - mul(result, x, x); - } - - /** * Multiplies a number by the a24 constant, modulo 2^255 - 19. * * @param result The result. - * @param x The number to multiply by a24. + * @param x The number to multiply by a24. */ - private void mulA24(int[] result, int[] x) - { + private void mulA24(int[] result, int[] x) { long a24 = 121665; long carry = 0; int index; for (index = 0; index < NUM_LIMBS_255BIT; ++index) { carry += a24 * x[index]; - t2[index] = ((int)carry) & 0x03FFFFFF; + t2[index] = ((int) carry) & 0x03FFFFFF; carry >>= 26; } - t2[NUM_LIMBS_255BIT] = ((int)carry) & 0x03FFFFFF; + t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF; reduce(result, t2, 1); } /** - * Adds two numbers modulo 2^255 - 19. - * - * @param result The result. - * @param x The first number to add. - * @param y The second number to add. - */ - private void add(int[] result, int[] x, int[] y) - { - int index, carry; - carry = x[0] + y[0]; - result[0] = carry & 0x03FFFFFF; - for (index = 1; index < NUM_LIMBS_255BIT; ++index) { - carry = (carry >> 26) + x[index] + y[index]; - result[index] = carry & 0x03FFFFFF; - } - reduceQuick(result); - } - - /** - * Subtracts two numbers modulo 2^255 - 19. - * - * @param result The result. - * @param x The first number to subtract. - * @param y The second number to subtract. - */ - private void sub(int[] result, int[] x, int[] y) - { - int index, borrow; - - // Subtract y from x to generate the intermediate result. - borrow = 0; - for (index = 0; index < NUM_LIMBS_255BIT; ++index) { - borrow = x[index] - y[index] - ((borrow >> 26) & 0x01); - result[index] = borrow & 0x03FFFFFF; - } - - // If we had a borrow, then the result has gone negative and we - // have to add 2^255 - 19 to the result to make it positive again. - // The top bits of "borrow" will be all 1's if there is a borrow - // or it will be all 0's if there was no borrow. Easiest is to - // conditionally subtract 19 and then mask off the high bits. - borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19); - result[0] = borrow & 0x03FFFFFF; - for (index = 1; index < NUM_LIMBS_255BIT; ++index) { - borrow = result[index] - ((borrow >> 26) & 0x01); - result[index] = borrow & 0x03FFFFFF; - } - result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; - } - - /** - * Conditional swap of two values. - * - * @param select Set to 1 to swap, 0 to leave as-is. - * @param x The first value. - * @param y The second value. - */ - private static void cswap(int select, int[] x, int[] y) - { - int dummy; - select = -select; - for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { - dummy = select & (x[index] ^ y[index]); - x[index] ^= dummy; - y[index] ^= dummy; - } - } - - /** * Raise x to the power of (2^250 - 1). * * @param result The result. Must not overlap with x. - * @param x The argument. + * @param x The argument. */ - private void pow250(int[] result, int[] x) - { + private void pow250(int[] result, int[] x) { int i, j; // The big-endian hexadecimal expansion of (2^250 - 1) is: @@ -378,10 +372,9 @@ public final class Curve25519 { * Computes the reciprocal of a number modulo 2^255 - 19. * * @param result The result. Must not overlap with x. - * @param x The argument. + * @param x The argument. */ - private void recip(int[] result, int[] x) - { + private void recip(int[] result, int[] x) { // The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19. // The big-endian hexadecimal expansion of (p - 2) is: // 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB @@ -400,136 +393,129 @@ public final class Curve25519 { } /** - * Evaluates the curve for every bit in a secret key. + * Reduce a number modulo 2^255 - 19. * - * @param s The 32-byte secret key. + * @param result The result. + * @param x The value to be reduced. This array will be + * modified during the reduction. + * @param size The number of limbs in the high order half of x. */ - private void evalCurve(byte[] s) - { - int sposn = 31; - int sbit = 6; - int svalue = s[sposn] | 0x40; - int swap = 0; - int select; - - // Iterate over all 255 bits of "s" from the highest to the lowest. - // We ignore the high bit of the 256-bit representation of "s". - for (;;) { - // Conditional swaps on entry to this bit but only if we - // didn't swap on the previous bit. - select = (svalue >> sbit) & 0x01; - swap ^= select; - cswap(swap, x_2, x_3); - cswap(swap, z_2, z_3); - swap = select; - - // Evaluate the curve. - add(A, x_2, z_2); // A = x_2 + z_2 - square(AA, A); // AA = A^2 - sub(B, x_2, z_2); // B = x_2 - z_2 - square(BB, B); // BB = B^2 - sub(E, AA, BB); // E = AA - BB - add(C, x_3, z_3); // C = x_3 + z_3 - sub(D, x_3, z_3); // D = x_3 - z_3 - mul(DA, D, A); // DA = D * A - mul(CB, C, B); // CB = C * B - add(x_3, DA, CB); // x_3 = (DA + CB)^2 - square(x_3, x_3); - sub(z_3, DA, CB); // z_3 = x_1 * (DA - CB)^2 - square(z_3, z_3); - mul(z_3, z_3, x_1); - mul(x_2, AA, BB); // x_2 = AA * BB - mulA24(z_2, E); // z_2 = E * (AA + a24 * E) - add(z_2, z_2, AA); - mul(z_2, z_2, E); + private void reduce(int[] result, int[] x, int size) { + int index, limb, carry; - // Move onto the next lower bit of "s". - if (sbit > 0) { - --sbit; - } else if (sposn == 0) { - break; - } else if (sposn == 1) { - --sposn; - svalue = s[sposn] & 0xF8; - sbit = 7; - } else { - --sposn; - svalue = s[sposn]; - sbit = 7; + // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will + // either produce the answer we want or it will produce a + // value of the form "answer + j * (2^255 - 19)". There are + // 5 left-over bits in the top-most limb of the bottom half. + carry = 0; + limb = x[NUM_LIMBS_255BIT - 1] >> 21; + x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (index = 0; index < size; ++index) { + limb += x[NUM_LIMBS_255BIT + index] << 5; + carry += (limb & 0x03FFFFFF) * 19 + x[index]; + x[index] = carry & 0x03FFFFFF; + limb >>= 26; + carry >>= 26; + } + if (size < NUM_LIMBS_255BIT) { + // The high order half of the number is short; e.g. for mulA24(). + // Propagate the carry through the rest of the low order part. + for (index = size; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + x[index] = carry & 0x03FFFFFF; + carry >>= 26; } } - // Final conditional swaps. - cswap(swap, x_2, x_3); - cswap(swap, z_2, z_3); + // The "j" value may still be too large due to the final carry-out. + // We must repeat the reduction. If we already have the answer, + // then this won't do any harm but we must still do the calculation + // to preserve the overall timing. The "j" value will be between + // 0 and 19, which means that the carry we care about is in the + // top 5 bits of the highest limb of the bottom half. + carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19; + x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + result[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + + // At this point "x" will either be the answer or it will be the + // answer plus (2^255 - 19). Perform a trial subtraction to + // complete the reduction process. + reduceQuick(result); } /** - * Evaluates the Curve25519 curve. + * Reduces a number modulo 2^255 - 19 where it is known that the + * number can be reduced with only 1 trial subtraction. * - * @param result Buffer to place the result of the evaluation into. - * @param offset Offset into the result buffer. - * @param privateKey The private key to use in the evaluation. - * @param publicKey The public key to use in the evaluation, or null - * if the base point of the curve should be used. + * @param x The number to reduce, and the result. */ - public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey) - { - Curve25519 state = new Curve25519(); - try { - // Unpack the public key value. If null, use 9 as the base point. - Arrays.fill(state.x_1, 0); - if (publicKey != null) { - // Convert the input value from little-endian into 26-bit limbs. - for (int index = 0; index < 32; ++index) { - int bit = (index * 8) % 26; - int word = (index * 8) / 26; - int value = publicKey[index] & 0xFF; - if (bit <= (26 - 8)) { - state.x_1[word] |= value << bit; - } else { - state.x_1[word] |= value << bit; - state.x_1[word] &= 0x03FFFFFF; - state.x_1[word + 1] |= value >> (26 - bit); - } - } + private void reduceQuick(int[] x) { + int index, carry; - // Just in case, we reduce the number modulo 2^255 - 19 to - // make sure that it is in range of the field before we start. - // This eliminates values between 2^255 - 19 and 2^256 - 1. - state.reduceQuick(state.x_1); - state.reduceQuick(state.x_1); - } else { - state.x_1[0] = 9; - } + // Perform a trial subtraction of (2^255 - 19) from "x" which is + // equivalent to adding 19 and subtracting 2^255. We add 19 here; + // the subtraction of 2^255 occurs in the next step. + carry = 19; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + t2[index] = carry & 0x03FFFFFF; + carry >>= 26; + } - // Initialize the other temporary variables. - Arrays.fill(state.x_2, 0); // x_2 = 1 - state.x_2[0] = 1; - Arrays.fill(state.z_2, 0); // z_2 = 0 - System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1 - Arrays.fill(state.z_3, 0); // z_3 = 1 - state.z_3[0] = 1; + // If there was a borrow, then the original "x" is the correct answer. + // If there was no borrow, then "t2" is the correct answer. Select the + // correct answer but do it in a way that instruction timing will not + // reveal which value was selected. Borrow will occur if bit 21 of + // "t2" is zero. Turn the bit into a selection mask. + int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01); + int nmask = ~mask; + t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) + x[index] = (x[index] & nmask) | (t2[index] & mask); + } - // Evaluate the curve for every bit of the private key. - state.evalCurve(privateKey); + /** + * Squares a number modulo 2^255 - 19. + * + * @param result The result. + * @param x The number to square. + */ + private void square(int[] result, int[] x) { + mul(result, x, x); + } - // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19. - state.recip(state.z_3, state.z_2); - state.mul(state.x_2, state.x_2, state.z_3); + /** + * Subtracts two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to subtract. + * @param y The second number to subtract. + */ + private void sub(int[] result, int[] x, int[] y) { + int index, borrow; - // Convert x_2 into little-endian in the result buffer. - for (int index = 0; index < 32; ++index) { - int bit = (index * 8) % 26; - int word = (index * 8) / 26; - if (bit <= (26 - 8)) - result[offset + index] = (byte)(state.x_2[word] >> bit); - else - result[offset + index] = (byte)((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit))); - } - } finally { - // Clean up all temporary state before we exit. - state.destroy(); + // Subtract y from x to generate the intermediate result. + borrow = 0; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + borrow = x[index] - y[index] - ((borrow >> 26) & 0x01); + result[index] = borrow & 0x03FFFFFF; } + + // If we had a borrow, then the result has gone negative and we + // have to add 2^255 - 19 to the result to make it positive again. + // The top bits of "borrow" will be all 1's if there is a borrow + // or it will be all 0's if there was no borrow. Easiest is to + // conditionally subtract 19 and then mask off the high bits. + borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19); + result[0] = borrow & 0x03FFFFFF; + for (index = 1; index < NUM_LIMBS_255BIT; ++index) { + borrow = result[index] - ((borrow >> 26) & 0x01); + result[index] = borrow & 0x03FFFFFF; + } + result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; } } diff --git a/app/src/main/java/com/wireguard/crypto/KeyEncoding.java b/app/src/main/java/com/wireguard/crypto/KeyEncoding.java index 92e03230..ba867668 100644 --- a/app/src/main/java/com/wireguard/crypto/KeyEncoding.java +++ b/app/src/main/java/com/wireguard/crypto/KeyEncoding.java @@ -13,10 +13,10 @@ public final class KeyEncoding { public static final int KEY_LENGTH_HEX = 64; private static final String KEY_LENGTH_BASE64_EXCEPTION_MESSAGE = "WireGuard base64 keys must be 44 characters encoding 32 bytes"; - private static final String KEY_LENGTH_HEX_EXCEPTION_MESSAGE = - "WireGuard hex keys must be 64 characters encoding 32 bytes"; private static final String KEY_LENGTH_EXCEPTION_MESSAGE = "WireGuard keys must be 32 bytes"; + private static final String KEY_LENGTH_HEX_EXCEPTION_MESSAGE = + "WireGuard hex keys must be 64 characters encoding 32 bytes"; private KeyEncoding() { // Prevent instantiation. @@ -82,23 +82,6 @@ public final class KeyEncoding { return key; } - public static String keyToBase64(final byte[] key) { - final char[] output = new char[KEY_LENGTH_BASE64]; - if (key.length != KEY_LENGTH) - throw new IllegalArgumentException(KEY_LENGTH_EXCEPTION_MESSAGE); - int i; - for (i = 0; i < KEY_LENGTH / 3; ++i) - encodeBase64(key, i * 3, output, i * 4); - final byte[] endSegment = { - key[i * 3], - key[i * 3 + 1], - 0, - }; - encodeBase64(endSegment, 0, output, i * 4); - output[KEY_LENGTH_BASE64 - 1] = '='; - return new String(output); - } - public static byte[] keyFromHex(final String str) { final char[] input = str.toCharArray(); final byte[] key = new byte[KEY_LENGTH]; @@ -110,7 +93,7 @@ public final class KeyEncoding { for (int i = 0; i < KEY_LENGTH_HEX; ++i) { c = input[i]; c_num = c ^ 48; - c_num0 = (c_num - 10) >>8; + c_num0 = (c_num - 10) >> 8; c_alpha = (c & ~32) - 55; c_alpha0 = ((c_alpha - 10) ^ (c_alpha - 16)) >> 8; if ((c_num0 | c_alpha0) == 0) @@ -119,19 +102,36 @@ public final class KeyEncoding { if (state == 0) c_acc = c_val * 16; else - key[i / 2] = (byte)(c_acc | c_val); + key[i / 2] = (byte) (c_acc | c_val); state = ~state; } return key; } + public static String keyToBase64(final byte[] key) { + final char[] output = new char[KEY_LENGTH_BASE64]; + if (key.length != KEY_LENGTH) + throw new IllegalArgumentException(KEY_LENGTH_EXCEPTION_MESSAGE); + int i; + for (i = 0; i < KEY_LENGTH / 3; ++i) + encodeBase64(key, i * 3, output, i * 4); + final byte[] endSegment = { + key[i * 3], + key[i * 3 + 1], + 0, + }; + encodeBase64(endSegment, 0, output, i * 4); + output[KEY_LENGTH_BASE64 - 1] = '='; + return new String(output); + } + public static String keyToHex(final byte[] key) { final char[] output = new char[KEY_LENGTH_HEX]; if (key.length != KEY_LENGTH) throw new IllegalArgumentException(KEY_LENGTH_EXCEPTION_MESSAGE); for (int i = 0; i < KEY_LENGTH; ++i) { - output[i * 2] = (char)(87 + (key[i] >> 4 & 0xf) + ((((key[i] >> 4 & 0xf) - 10) >> 8) & ~38)); - output[i * 2 + 1] = (char)(87 + (key[i] & 0xf) + ((((key[i] & 0xf) - 10) >> 8) & ~38)); + output[i * 2] = (char) (87 + (key[i] >> 4 & 0xf) + ((((key[i] >> 4 & 0xf) - 10) >> 8) & ~38)); + output[i * 2 + 1] = (char) (87 + (key[i] & 0xf) + ((((key[i] & 0xf) - 10) >> 8) & ~38)); } return new String(output); } diff --git a/app/src/main/res/layout/tunnel_editor_fragment.xml b/app/src/main/res/layout/tunnel_editor_fragment.xml index 964aff5c..70f376af 100644 --- a/app/src/main/res/layout/tunnel_editor_fragment.xml +++ b/app/src/main/res/layout/tunnel_editor_fragment.xml @@ -114,11 +114,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/public_key_label" + android:contentDescription="@string/public_key_description" android:ellipsize="end" android:focusable="false" android:hint="@string/hint_generated" android:maxLines="1" - android:contentDescription="@string/public_key_description" android:onClick="@{ClipboardUtils::copyTextView}" android:text="@{config.interfaceSection.publicKey}" /> diff --git a/app/src/main/res/menu/config_editor.xml b/app/src/main/res/menu/config_editor.xml index bb58f6c9..dd0137df 100644 --- a/app/src/main/res/menu/config_editor.xml +++ b/app/src/main/res/menu/config_editor.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:android="http://schemas.android.com/apk/res/android"> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_action_save" android:alphabeticShortcut="s" diff --git a/app/src/main/res/menu/main_activity.xml b/app/src/main/res/menu/main_activity.xml index 54211ec4..ce593296 100644 --- a/app/src/main/res/menu/main_activity.xml +++ b/app/src/main/res/menu/main_activity.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:android="http://schemas.android.com/apk/res/android"> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_settings" android:alphabeticShortcut="s" diff --git a/app/src/main/res/menu/tunnel_detail.xml b/app/src/main/res/menu/tunnel_detail.xml index 8e2c2eb9..2834a661 100644 --- a/app/src/main/res/menu/tunnel_detail.xml +++ b/app/src/main/res/menu/tunnel_detail.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:android="http://schemas.android.com/apk/res/android"> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_action_edit" android:alphabeticShortcut="e" diff --git a/app/src/main/res/menu/tunnel_list_action_mode.xml b/app/src/main/res/menu/tunnel_list_action_mode.xml index 6d175573..6b03cd19 100644 --- a/app/src/main/res/menu/tunnel_list_action_mode.xml +++ b/app/src/main/res/menu/tunnel_list_action_mode.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:android="http://schemas.android.com/apk/res/android"> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_action_delete" android:alphabeticShortcut="d" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a2a79cf9..f028d2b6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<resources xmlns:tools="http://schemas.android.com/tools"> +<resources> <plurals name="delete_error"> <item quantity="one">Unable to delete %d tunnel: %s</item> <item quantity="other">Unable to delete %d tunnels: %s</item> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 37957a7b..1fda6aa6 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -2,9 +2,11 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar" /> + <style name="SettingsTheme" parent="AppTheme"> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> </style> + <style name="fab_label" parent="android:TextAppearance.DeviceDefault.Inverse"> <item name="android:background">@drawable/fab_label_background</item> </style> |