Merge branch 'dev' into wireguard_embedded

This commit is contained in:
pokamest 2021-10-13 16:57:03 +03:00
commit ba8755a6d4
150 changed files with 8320 additions and 8490 deletions

42
.gitignore vendored
View file

@ -43,4 +43,46 @@ CMakeLists.txt.user*
# tmp files
*.*~
######################### Android
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Android Profiling
*.hprof

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "3rd/QtSsh"]
path = 3rd/QtSsh
url = https://github.com/amnezia-vpn/QtSsh.git
[submodule "client/3rd/wireguard-tools"]
path = client/3rd/wireguard-tools
url = https://github.com/WireGuard/wireguard-tools/

View file

@ -1,3 +1,6 @@
TEMPLATE = subdirs
SUBDIRS = client service platform
SUBDIRS = client
!ios:!android {
SUBDIRS += service platform
}

@ -0,0 +1 @@
Subproject commit 7a321ce808ef9cec1f45cce92befcc9e170d3aa9

View file

@ -1,8 +1,13 @@
<?xml version="1.0"?>
<manifest package="org.qtproject.example" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<manifest package="org.amnezia.vpn" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
@ -77,6 +82,19 @@
<!-- extract android style -->
</activity>
<service android:name=".VPNService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<service android:name="org.amnezia.vpn.qt.VPNPermissionHelper"
android:process=":QtOnlyProcess"
android:permission="android.permission.BIND_VPN_SERVICE">
</service>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>

View file

@ -1,23 +1,52 @@
buildscript {
ext{
kotlin_version = "1.4.30-M1"
// for libwg
appcompatVersion = '1.1.0'
annotationsVersion = '1.0.1'
databindingVersion = '3.3.1'
jsr305Version = '3.0.2'
streamsupportVersion = '1.7.0'
threetenabpVersion = '1.1.1'
groupName = 'org.amnezia.vpn'
}
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
repositories {
google()
jcenter()
mavenCentral()
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.android.installreferrer:installreferrer:2.2'
implementation 'com.android.billingclient:billing-ktx:4.0.0'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha02"
implementation "androidx.security:security-crypto:1.1.0-alpha03"
implementation "androidx.security:security-identity-credential:1.0.0-alpha02"
implementation 'com.adjust.sdk:adjust-android:4.28.2'
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.10"
}
android {
@ -38,6 +67,10 @@ android {
buildToolsVersion '28.0.3'
dexOptions {
javaMaxHeapSize "3g"
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
@ -71,7 +104,34 @@ android {
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
minSdkVersion = 24
targetSdkVersion = qtTargetSdkVersion
}
buildTypes {
release {
// That would enable treeshaking and remove java code that is just called from qt
minifyEnabled false
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
}
}
}
debug {
//applicationIdSuffix ".debug"
//versionNameSuffix "-debug"
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
}
}
}
}
// externalNativeBuild {
// cmake {
// path 'wireguard/CMakeLists.txt'
// }
// }
}

View file

@ -9,3 +9,15 @@ org.gradle.jvmargs=-Xmx2048m
# build with the same inputs. However, over time, the cache size will
# grow. Uncomment the following line to enable it.
#org.gradle.caching=true
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.bundle.enableUncompressedNativeLibs=false
androidBuildToolsVersion=30.0.2
androidCompileSdkVersion=30
org.gradle.caching=true
org.gradle.parallel=true

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,5.8C11.3372,5.8 10.8,6.3373 10.8,7C10.8,7.6627 11.3372,8.2 12,8.2C12.6627,8.2 13.2,7.6627 13.2,7C13.2,6.3373 12.6627,5.8 12,5.8ZM9.2,7C9.2,5.4536 10.4536,4.2 12,4.2C13.5463,4.2 14.8,5.4536 14.8,7C14.8,8.5464 13.5463,9.8 12,9.8C11.5177,9.8 11.064,9.6781 10.6679,9.4634L9.4634,10.6679C9.5543,10.8357 9.6286,11.0138 9.684,11.2H14.3159C14.6601,10.0434 15.7315,9.2 17,9.2C18.5463,9.2 19.8,10.4536 19.8,12C19.8,13.5464 18.5463,14.8 17,14.8C16.5177,14.8 16.064,14.6781 15.6679,14.4634L14.4634,15.6679C14.6781,16.0641 14.8,16.5178 14.8,17C14.8,18.5464 13.5463,19.8 12,19.8C10.4536,19.8 9.2,18.5464 9.2,17C9.2,15.4536 10.4536,14.2 12,14.2C12.4822,14.2 12.9359,14.3219 13.332,14.5365L14.5365,13.3321C14.4456,13.1643 14.3713,12.9862 14.3159,12.8H9.684C9.3398,13.9566 8.2684,14.8 6.9999,14.8C5.4535,14.8 4.2,13.5464 4.2,12C4.2,10.4536 5.4535,9.2 6.9999,9.2C7.4822,9.2 7.9359,9.3219 8.332,9.5365L9.5365,8.3321C9.3218,7.9359 9.2,7.4822 9.2,7ZM10.8,17C10.8,16.3373 11.3372,15.8 12,15.8C12.6627,15.8 13.2,16.3373 13.2,17C13.2,17.6627 12.6627,18.2 12,18.2C11.3372,18.2 10.8,17.6627 10.8,17ZM6.9999,10.8C6.3372,10.8 5.8,11.3373 5.8,12C5.8,12.6627 6.3372,13.2 6.9999,13.2C7.6627,13.2 8.2,12.6627 8.2,12C8.2,11.3373 7.6627,10.8 6.9999,10.8ZM15.8,12C15.8,11.3373 16.3372,10.8 17,10.8C17.6627,10.8 18.2,11.3373 18.2,12C18.2,12.6627 17.6627,13.2 17,13.2C16.3372,13.2 15.8,12.6627 15.8,12Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="1.2226415"
android:scaleY="1.2226415"
android:translateX="27.101887"
android:translateY="27.101887">
<path
android:pathData="M22,4.95C20.1775,4.95 18.7,6.4275 18.7,8.25C18.7,10.0725 20.1775,11.55 22,11.55C23.8225,11.55 25.3,10.0725 25.3,8.25C25.3,6.4275 23.8225,4.95 22,4.95ZM14.3,8.25C14.3,3.9974 17.7474,0.55 22,0.55C26.2526,0.55 29.7,3.9974 29.7,8.25C29.7,12.5026 26.2526,15.95 22,15.95C20.6739,15.95 19.4261,15.6148 18.3368,15.0245L15.0245,18.3368C15.2745,18.7981 15.4787,19.2879 15.6311,19.8H28.3689C29.3155,16.6194 32.2619,14.3 35.75,14.3C40.0026,14.3 43.45,17.7474 43.45,22C43.45,26.2526 40.0026,29.7 35.75,29.7C34.4239,29.7 33.1761,29.3648 32.0868,28.7745L28.7745,32.0868C29.3648,33.1761 29.7,34.4239 29.7,35.75C29.7,40.0026 26.2526,43.45 22,43.45C17.7474,43.45 14.3,40.0026 14.3,35.75C14.3,31.4974 17.7474,28.05 22,28.05C23.3261,28.05 24.5738,28.3852 25.6632,28.9755L28.9755,25.6632C28.7255,25.2019 28.5213,24.7121 28.3689,24.2H15.6311C14.6845,27.3806 11.7381,29.7 8.25,29.7C3.9974,29.7 0.55,26.2526 0.55,22C0.55,17.7474 3.9974,14.3 8.25,14.3C9.5761,14.3 10.8238,14.6352 11.9132,15.2255L15.2255,11.9132C14.6352,10.8238 14.3,9.5761 14.3,8.25ZM18.7,35.75C18.7,33.9275 20.1775,32.45 22,32.45C23.8225,32.45 25.3,33.9275 25.3,35.75C25.3,37.5725 23.8225,39.05 22,39.05C20.1775,39.05 18.7,37.5725 18.7,35.75ZM8.25,18.7C6.4275,18.7 4.95,20.1775 4.95,22C4.95,23.8225 6.4275,25.3 8.25,25.3C10.0725,25.3 11.55,23.8225 11.55,22C11.55,20.1775 10.0725,18.7 8.25,18.7ZM32.45,22C32.45,20.1775 33.9275,18.7 35.75,18.7C37.5725,18.7 39.05,20.1775 39.05,22C39.05,23.8225 37.5725,25.3 35.75,25.3C33.9275,25.3 32.45,23.8225 32.45,22Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</group>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M11,3.8535C10.2353,3.8535 9.6154,4.4734 9.6154,5.2381C9.6154,6.0028 10.2353,6.6227 11,6.6227C11.7647,6.6227 12.3846,6.0028 12.3846,5.2381C12.3846,4.4734 11.7647,3.8535 11,3.8535ZM7.7692,5.2381C7.7692,3.4538 9.2157,2.0073 11,2.0073C12.7843,2.0073 14.2308,3.4538 14.2308,5.2381C14.2308,7.0224 12.7843,8.4689 11,8.4689C10.4436,8.4689 9.92,8.3282 9.4629,8.0805L8.0732,9.4703C8.1781,9.6639 8.2638,9.8694 8.3277,10.0842H13.6722C14.0694,8.7497 15.3057,7.7766 16.7692,7.7766C18.5535,7.7766 20,9.223 20,11.0073C20,12.7916 18.5535,14.2381 16.7692,14.2381C16.2128,14.2381 15.6893,14.0975 15.2322,13.8498L13.8424,15.2395C13.9718,15.4783 14.072,15.7352 14.1382,16.0054H12.1501C11.9016,15.6354 11.4792,15.3919 11,15.3919C10.2353,15.3919 9.6153,16.0119 9.6153,16.7766C9.6153,17.1482 9.7617,17.4856 10,17.7343V19.8496C8.7051,19.4285 7.7692,18.2118 7.7692,16.7766C7.7692,14.9923 9.2156,13.5458 11,13.5458C11.5564,13.5458 12.0799,13.6864 12.537,13.9341L13.9268,12.5444C13.8219,12.3508 13.7362,12.1453 13.6722,11.9304H8.3277C7.9306,13.2649 6.6943,14.2381 5.2307,14.2381C3.4464,14.2381 1.9999,12.7916 1.9999,11.0073C1.9999,9.223 3.4464,7.7766 5.2307,7.7766C5.7871,7.7766 6.3106,7.9172 6.7677,8.1649L8.1575,6.7751C7.9099,6.318 7.7692,5.7945 7.7692,5.2381ZM5.2307,9.6227C4.466,9.6227 3.8461,10.2426 3.8461,11.0073C3.8461,11.772 4.466,12.392 5.2307,12.392C5.9954,12.392 6.6154,11.772 6.6154,11.0073C6.6154,10.2426 5.9954,9.6227 5.2307,9.6227ZM15.3846,11.0073C15.3846,10.2426 16.0045,9.6227 16.7692,9.6227C17.5339,9.6227 18.1538,10.2426 18.1538,11.0073C18.1538,11.772 17.5339,12.392 16.7692,12.392C16.0045,12.392 15.3846,11.772 15.3846,11.0073ZM13.664,21.0073L11,18.1662L12.332,16.7557L13.664,18.1762L16.6586,15.005L18,16.4256L13.664,21.0073Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,4 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/black" />
<item android:drawable="@drawable/ic_launcher_foreground" android:gravity="center" android:width="200dp" android:height="200dp"/>
</layer-list>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/vpnicon_background"/>
<foreground android:drawable="@mipmap/vpnicon_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/vpnicon_background"/>
<foreground android:drawable="@mipmap/vpnicon_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="AppTheme.Splash" parent="AppTheme">
<item name="android:windowBackground">@drawable/splash_background</item>
</style>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="vpnicon_background">#000000</color>
</resources>

View file

@ -0,0 +1,93 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
public final class SharedLibraryLoader {
private static final String TAG = "WireGuard/SharedLibraryLoader";
private SharedLibraryLoader() {}
public static boolean extractLibrary(
final Context context, final String libName, final File destination) throws IOException {
final Collection<String> apks = new HashSet<>();
Log.d(TAG, "Loading Lib ->" + libName);
if (context.getApplicationInfo().sourceDir != null)
apks.add(context.getApplicationInfo().sourceDir);
if (context.getApplicationInfo().splitSourceDirs != null)
apks.addAll(Arrays.asList(context.getApplicationInfo().splitSourceDirs));
for (final String abi : Build.SUPPORTED_ABIS) {
for (final String apk : apks) {
try (final ZipFile zipFile = new ZipFile(new File(apk), ZipFile.OPEN_READ)) {
final String mappedLibName = System.mapLibraryName(libName);
final String libZipPath =
"lib" + File.separatorChar + abi + File.separatorChar + mappedLibName;
final ZipEntry zipEntry = zipFile.getEntry(libZipPath);
if (zipEntry == null)
continue;
Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + destination.getAbsolutePath());
try (final FileOutputStream out = new FileOutputStream(destination);
final InputStream in = zipFile.getInputStream(zipEntry)) {
int len;
final byte[] buffer = new byte[1024 * 32];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.getFD().sync();
}
}
return true;
}
}
return false;
}
public static void loadSharedLibrary(final Context context, final String libName) {
Throwable noAbiException;
try {
System.loadLibrary(libName);
return;
} catch (final UnsatisfiedLinkError e) {
Log.d(TAG, "Failed to load library normally, so attempting to extract from apk", e);
noAbiException = e;
}
File f = null;
try {
f = File.createTempFile("lib", ".so", context.getCodeCacheDir());
if (extractLibrary(context, libName, f)) {
System.load(f.getAbsolutePath());
return;
}
} catch (final Exception e) {
Log.d(TAG, "Failed to load library apk:/" + libName, e);
noAbiException = e;
} finally {
if (f != null)
// noinspection ResultOfMethodCallIgnored
f.delete();
}
if (noAbiException instanceof RuntimeException)
throw(RuntimeException) noAbiException;
throw new RuntimeException(noAbiException);
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import java.util.Iterator;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Attribute {
private static final Pattern LINE_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*([^\\s#][^#]*)");
private static final Pattern LIST_SEPARATOR = Pattern.compile("\\s*,\\s*");
private final String key;
private final String value;
private Attribute(final String key, final String value) {
this.key = key;
this.value = value;
}
public static String join(final Iterable<?> values) {
final Iterator<?> it = values.iterator();
if (!it.hasNext()) {
return "";
}
final StringBuilder sb = new StringBuilder();
sb.append(it.next());
while (it.hasNext()) {
sb.append(", ");
sb.append(it.next());
}
return sb.toString();
}
public static Optional<Attribute> parse(final CharSequence line) {
final Matcher matcher = LINE_PATTERN.matcher(line);
if (!matcher.matches())
return Optional.empty();
return Optional.of(new Attribute(matcher.group(1), matcher.group(2)));
}
public static String[] split(final CharSequence value) {
return LIST_SEPARATOR.split(value);
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import com.wireguard.crypto.KeyFormatException;
import androidx.annotation.Nullable;
public class BadConfigException extends Exception {
private final Location location;
private final Reason reason;
private final Section section;
@Nullable private final CharSequence text;
private BadConfigException(final Section section, final Location location, final Reason reason,
@Nullable final CharSequence text, @Nullable final Throwable cause) {
super(cause);
this.section = section;
this.location = location;
this.reason = reason;
this.text = text;
}
public BadConfigException(final Section section, final Location location, final Reason reason,
@Nullable final CharSequence text) {
this(section, location, reason, text, null);
}
public BadConfigException(
final Section section, final Location location, final KeyFormatException cause) {
this(section, location, Reason.INVALID_KEY, null, cause);
}
public BadConfigException(final Section section, final Location location,
@Nullable final CharSequence text, final NumberFormatException cause) {
this(section, location, Reason.INVALID_NUMBER, text, cause);
}
public BadConfigException(
final Section section, final Location location, final ParseException cause) {
this(section, location, Reason.INVALID_VALUE, cause.getText(), cause);
}
public Location getLocation() {
return location;
}
public Reason getReason() {
return reason;
}
public Section getSection() {
return section;
}
@Nullable
public CharSequence getText() {
return text;
}
public enum Location {
TOP_LEVEL(""),
ADDRESS("Address"),
ALLOWED_IPS("AllowedIPs"),
DNS("DNS"),
ENDPOINT("Endpoint"),
EXCLUDED_APPLICATIONS("ExcludedApplications"),
INCLUDED_APPLICATIONS("IncludedApplications"),
LISTEN_PORT("ListenPort"),
MTU("MTU"),
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
PRE_SHARED_KEY("PresharedKey"),
PRIVATE_KEY("PrivateKey"),
PUBLIC_KEY("PublicKey");
private final String name;
Location(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public enum Reason {
INVALID_KEY,
INVALID_NUMBER,
INVALID_VALUE,
MISSING_ATTRIBUTE,
MISSING_SECTION,
SYNTAX_ERROR,
UNKNOWN_ATTRIBUTE,
UNKNOWN_SECTION
}
public enum Section {
CONFIG("Config"),
INTERFACE("Interface"),
PEER("Peer");
private final String name;
Section(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}

View file

@ -0,0 +1,218 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import com.wireguard.config.BadConfigException.Location;
import com.wireguard.config.BadConfigException.Reason;
import com.wireguard.config.BadConfigException.Section;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import androidx.annotation.Nullable;
/**
* Represents the contents of a wg-quick configuration file, made up of one or more "Interface"
* sections (combined together), and zero or more "Peer" sections (treated individually).
* <p>
* Instances of this class are immutable.
*/
public final class Config {
private final Interface interfaze;
private final List<Peer> peers;
private Config(final Builder builder) {
interfaze = Objects.requireNonNull(builder.interfaze, "An [Interface] section is required");
// Defensively copy to ensure immutability even if the Builder is reused.
peers = Collections.unmodifiableList(new ArrayList<>(builder.peers));
}
/**
* Parses an series of "Interface" and "Peer" sections into a {@code Config}. Throws
* {@link BadConfigException} if the input is not well-formed or contains data that cannot
* be parsed.
*
* @param stream a stream of UTF-8 text that is interpreted as a WireGuard configuration
* @return a {@code Config} instance representing the supplied configuration
*/
public static Config parse(final InputStream stream) throws IOException, BadConfigException {
return parse(new BufferedReader(new InputStreamReader(stream)));
}
/**
* Parses an series of "Interface" and "Peer" sections into a {@code Config}. Throws
* {@link BadConfigException} if the input is not well-formed or contains data that cannot
* be parsed.
*
* @param reader a BufferedReader of UTF-8 text that is interpreted as a WireGuard configuration
* @return a {@code Config} instance representing the supplied configuration
*/
public static Config parse(final BufferedReader reader) throws IOException, BadConfigException {
final Builder builder = new Builder();
final Collection<String> interfaceLines = new ArrayList<>();
final Collection<String> peerLines = new ArrayList<>();
boolean inInterfaceSection = false;
boolean inPeerSection = false;
boolean seenInterfaceSection = false;
@Nullable String line;
while ((line = reader.readLine()) != null) {
final int commentIndex = line.indexOf('#');
if (commentIndex != -1)
line = line.substring(0, commentIndex);
line = line.trim();
if (line.isEmpty())
continue;
if (line.startsWith("[")) {
// Consume all [Peer] lines read so far.
if (inPeerSection) {
builder.parsePeer(peerLines);
peerLines.clear();
}
if ("[Interface]".equalsIgnoreCase(line)) {
inInterfaceSection = true;
inPeerSection = false;
seenInterfaceSection = true;
} else if ("[Peer]".equalsIgnoreCase(line)) {
inInterfaceSection = false;
inPeerSection = true;
} else {
throw new BadConfigException(
Section.CONFIG, Location.TOP_LEVEL, Reason.UNKNOWN_SECTION, line);
}
} else if (inInterfaceSection) {
interfaceLines.add(line);
} else if (inPeerSection) {
peerLines.add(line);
} else {
throw new BadConfigException(
Section.CONFIG, Location.TOP_LEVEL, Reason.UNKNOWN_SECTION, line);
}
}
if (inPeerSection)
builder.parsePeer(peerLines);
if (!seenInterfaceSection)
throw new BadConfigException(
Section.CONFIG, Location.TOP_LEVEL, Reason.MISSING_SECTION, null);
// Combine all [Interface] sections in the file.
builder.parseInterface(interfaceLines);
return builder.build();
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Config))
return false;
final Config other = (Config) obj;
return interfaze.equals(other.interfaze) && peers.equals(other.peers);
}
/**
* Returns the interface section of the configuration.
*
* @return the interface configuration
*/
public Interface getInterface() {
return interfaze;
}
/**
* Returns a list of the configuration's peer sections.
*
* @return a list of {@link Peer}s
*/
public List<Peer> getPeers() {
return peers;
}
@Override
public int hashCode() {
return 31 * interfaze.hashCode() + peers.hashCode();
}
/**
* Converts the {@code Config} into a string suitable for debugging purposes. The {@code Config}
* is identified by its interface's public key and the number of peers it has.
*
* @return a concise single-line identifier for the {@code Config}
*/
@Override
public String toString() {
return "(Config " + interfaze + " (" + peers.size() + " peers))";
}
/**
* Converts the {@code Config} into a string suitable for use as a {@code wg-quick}
* configuration file.
*
* @return the {@code Config} represented as one [Interface] and zero or more [Peer] sections
*/
public String toWgQuickString() {
final StringBuilder sb = new StringBuilder();
sb.append("[Interface]\n").append(interfaze.toWgQuickString());
for (final Peer peer : peers) sb.append("\n[Peer]\n").append(peer.toWgQuickString());
return sb.toString();
}
/**
* Serializes the {@code Config} for use with the WireGuard cross-platform userspace API.
*
* @return the {@code Config} represented as a series of "key=value" lines
*/
public String toWgUserspaceString() {
final StringBuilder sb = new StringBuilder();
sb.append(interfaze.toWgUserspaceString());
sb.append("replace_peers=true\n");
for (final Peer peer : peers) sb.append(peer.toWgUserspaceString());
return sb.toString();
}
@SuppressWarnings("UnusedReturnValue")
public static final class Builder {
// Defaults to an empty set.
private final ArrayList<Peer> peers = new ArrayList<>();
// No default; must be provided before building.
@Nullable private Interface interfaze;
public Builder addPeer(final Peer peer) {
peers.add(peer);
return this;
}
public Builder addPeers(final Collection<Peer> peers) {
this.peers.addAll(peers);
return this;
}
public Config build() {
if (interfaze == null)
throw new IllegalArgumentException("An [Interface] section is required");
return new Config(this);
}
public Builder parseInterface(final Iterable<? extends CharSequence> lines)
throws BadConfigException {
return setInterface(Interface.parse(lines));
}
public Builder parsePeer(final Iterable<? extends CharSequence> lines)
throws BadConfigException {
return addPeer(Peer.parse(lines));
}
public Builder setInterface(final Interface interfaze) {
this.interfaze = interfaze;
return this;
}
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
import androidx.annotation.Nullable;
/**
* Utility methods for creating instances of {@link InetAddress}.
*/
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]?))$");
static {
Method m = null;
try {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q)
// noinspection JavaReflectionMemberAccess
m = InetAddress.class.getMethod("parseNumericAddress", String.class);
} catch (final Exception ignored) {
}
PARSER_METHOD = m;
}
private InetAddresses() {}
/**
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
*
* @param address a string representing the IP address
* @return an instance of {@link Inet4Address} or {@link Inet6Address}, as appropriate
*/
public static InetAddress parse(final String address) throws ParseException {
if (address.isEmpty())
throw new ParseException(InetAddress.class, address, "Empty address");
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q)
return android.net.InetAddresses.parseNumericAddress(address);
else if (PARSER_METHOD != null)
return (InetAddress) PARSER_METHOD.invoke(null, address);
else
throw new NoSuchMethodException("parseNumericAddress");
} catch (final IllegalArgumentException e) {
throw new ParseException(InetAddress.class, address, e);
} catch (final Exception e) {
final Throwable cause = e.getCause();
// Re-throw parsing exceptions with the original type, as callers might try to catch
// them. On the other hand, callers cannot be expected to handle reflection failures.
if (cause instanceof IllegalArgumentException)
throw new ParseException(InetAddress.class, address, cause);
try {
if (WONT_TOUCH_RESOLVER.matcher(address).matches())
return InetAddress.getByName(address);
else
throw new ParseException(InetAddress.class, address, "Not an IP address");
} catch (final UnknownHostException f) {
throw new ParseException(InetAddress.class, address, f);
}
}
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
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;
/**
* An external endpoint (host and port) used to connect to a WireGuard {@link Peer}.
* <p>
* Instances of this class are externally immutable.
*/
public final class InetEndpoint {
private static final Pattern BARE_IPV6 = Pattern.compile("^[^\\[\\]]*:[^\\[\\]]*");
private static final Pattern FORBIDDEN_CHARACTERS = Pattern.compile("[/?#]");
private final String host;
private final boolean isResolved;
private final Object lock = new Object();
private final int port;
private Instant lastResolution = Instant.EPOCH;
@Nullable private InetEndpoint resolved;
private InetEndpoint(final String host, final boolean isResolved, final int port) {
this.host = host;
this.isResolved = isResolved;
this.port = port;
}
public static InetEndpoint parse(final String endpoint) throws ParseException {
if (FORBIDDEN_CHARACTERS.matcher(endpoint).find())
throw new ParseException(InetEndpoint.class, endpoint, "Forbidden characters");
final URI uri;
try {
uri = new URI("wg://" + endpoint);
} catch (final URISyntaxException e) {
throw new ParseException(InetEndpoint.class, endpoint, e);
}
if (uri.getPort() < 0 || uri.getPort() > 65535)
throw new ParseException(InetEndpoint.class, endpoint, "Missing/invalid port number");
try {
InetAddresses.parse(uri.getHost());
// Parsing ths host as a numeric address worked, so we don't need to do DNS lookups.
return new InetEndpoint(uri.getHost(), true, uri.getPort());
} catch (final ParseException ignored) {
// Failed to parse the host as a numeric address, so it must be a DNS hostname/FQDN.
return new InetEndpoint(uri.getHost(), false, uri.getPort());
}
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof InetEndpoint))
return false;
final InetEndpoint other = (InetEndpoint) obj;
return host.equals(other.host) && port == other.port;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
/**
* Generate an {@code InetEndpoint} instance with the same port and the host resolved using DNS
* to a numeric address. If the host is already numeric, the existing instance may be returned.
* Because this function may perform network I/O, it must not be called from the main thread.
*
* @return the resolved endpoint, or {@link Optional#empty()}
*/
public Optional<InetEndpoint> getResolved() {
if (isResolved)
return Optional.of(this);
synchronized (lock) {
// TODO(zx2c4): Implement a real timeout mechanism using DNS TTL
if (Duration.between(lastResolution, Instant.now()).toMinutes() > 1) {
try {
// Prefer v4 endpoints over v6 to work around DNS64 and IPv6 NAT issues.
final InetAddress[] candidates = InetAddress.getAllByName(host);
InetAddress address = candidates[0];
for (final InetAddress candidate : candidates) {
if (candidate instanceof Inet4Address) {
address = candidate;
break;
}
}
resolved = new InetEndpoint(address.getHostAddress(), true, port);
lastResolution = Instant.now();
} catch (final UnknownHostException e) {
resolved = null;
}
}
return Optional.ofNullable(resolved);
}
}
@Override
public int hashCode() {
return host.hashCode() ^ port;
}
@Override
public String toString() {
final boolean isBareIpv6 = isResolved && BARE_IPV6.matcher(host).matches();
return (isBareIpv6 ? '[' + host + ']' : host) + ':' + port;
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import java.net.Inet4Address;
import java.net.InetAddress;
/**
* An Internet network, denoted by its address and netmask
* <p>
* Instances of this class are immutable.
*/
public final class InetNetwork {
private final InetAddress address;
private final int mask;
private InetNetwork(final InetAddress address, final int mask) {
this.address = address;
this.mask = mask;
}
public static InetNetwork parse(final String network) throws ParseException {
final int slash = network.lastIndexOf('/');
final String maskString;
final int rawMask;
final String rawAddress;
if (slash >= 0) {
maskString = network.substring(slash + 1);
try {
rawMask = Integer.parseInt(maskString, 10);
} catch (final NumberFormatException ignored) {
throw new ParseException(Integer.class, maskString);
}
rawAddress = network.substring(0, slash);
} else {
maskString = "";
rawMask = -1;
rawAddress = network;
}
final InetAddress address = InetAddresses.parse(rawAddress);
final int maxMask = (address instanceof Inet4Address) ? 32 : 128;
if (rawMask > maxMask)
throw new ParseException(InetNetwork.class, maskString, "Invalid network mask");
final int mask = rawMask >= 0 ? rawMask : maxMask;
return new InetNetwork(address, mask);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof InetNetwork))
return false;
final InetNetwork other = (InetNetwork) obj;
return address.equals(other.address) && mask == other.mask;
}
public InetAddress getAddress() {
return address;
}
public int getMask() {
return mask;
}
@Override
public int hashCode() {
return address.hashCode() ^ mask;
}
@Override
public String toString() {
return address.getHostAddress() + '/' + mask;
}
}

View file

@ -0,0 +1,394 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import com.wireguard.config.BadConfigException.Location;
import com.wireguard.config.BadConfigException.Reason;
import com.wireguard.config.BadConfigException.Section;
import com.wireguard.crypto.Key;
import com.wireguard.crypto.KeyFormatException;
import com.wireguard.crypto.KeyPair;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Collections;
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;
/**
* Represents the configuration for a WireGuard interface (an [Interface] block). Interfaces must
* have a private key (used to initialize a {@code KeyPair}), and may optionally have several other
* attributes.
* <p>
* Instances of this class are immutable.
*/
public final class Interface {
private static final int MAX_UDP_PORT = 65535;
private static final int MIN_UDP_PORT = 0;
private final Set<InetNetwork> addresses;
private final Set<InetAddress> dnsServers;
private final Set<String> excludedApplications;
private final Set<String> includedApplications;
private final KeyPair keyPair;
private final Optional<Integer> listenPort;
private final Optional<Integer> mtu;
private Interface(final Builder builder) {
// 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));
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");
listenPort = builder.listenPort;
mtu = builder.mtu;
}
/**
* Parses an series of "KEY = VALUE" lines into an {@code Interface}. Throws
* {@link ParseException} if the input is not well-formed or contains unknown attributes.
*
* @param lines An iterable sequence of lines, containing at least a private key attribute
* @return An {@code Interface} with all of the attributes from {@code lines} set
*/
public static Interface parse(final Iterable<? extends CharSequence> lines)
throws BadConfigException {
final Builder builder = new Builder();
for (final CharSequence line : lines) {
final Attribute attribute =
Attribute.parse(line).orElseThrow(()
-> new BadConfigException(Section.INTERFACE,
Location.TOP_LEVEL, Reason.SYNTAX_ERROR, line));
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
case "address":
builder.parseAddresses(attribute.getValue());
break;
case "dns":
builder.parseDnsServers(attribute.getValue());
break;
case "excludedapplications":
builder.parseExcludedApplications(attribute.getValue());
break;
case "includedapplications":
builder.parseIncludedApplications(attribute.getValue());
break;
case "listenport":
builder.parseListenPort(attribute.getValue());
break;
case "mtu":
builder.parseMtu(attribute.getValue());
break;
case "privatekey":
builder.parsePrivateKey(attribute.getValue());
break;
default:
throw new BadConfigException(
Section.INTERFACE, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
}
}
return builder.build();
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Interface))
return false;
final Interface other = (Interface) obj;
return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers)
&& excludedApplications.equals(other.excludedApplications)
&& includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair)
&& listenPort.equals(other.listenPort) && mtu.equals(other.mtu);
}
/**
* Returns the set of IP addresses assigned to the interface.
*
* @return a set of {@link InetNetwork}s
*/
public Set<InetNetwork> getAddresses() {
// The collection is already immutable.
return addresses;
}
/**
* Returns the set of DNS servers associated with the interface.
*
* @return a set of {@link InetAddress}es
*/
public Set<InetAddress> getDnsServers() {
// The collection is already immutable.
return dnsServers;
}
/**
* Returns the set of applications excluded from using the interface.
*
* @return a set of package names
*/
public Set<String> getExcludedApplications() {
// The collection is already immutable.
return excludedApplications;
}
/**
* Returns the set of applications included exclusively for using the interface.
*
* @return a set of package names
*/
public Set<String> getIncludedApplications() {
// The collection is already immutable.
return includedApplications;
}
/**
* Returns the public/private key pair used by the interface.
*
* @return a key pair
*/
public KeyPair getKeyPair() {
return keyPair;
}
/**
* Returns the UDP port number that the WireGuard interface will listen on.
*
* @return a UDP port number, or {@code Optional.empty()} if none is configured
*/
public Optional<Integer> getListenPort() {
return listenPort;
}
/**
* Returns the MTU used for the WireGuard interface.
*
* @return the MTU, or {@code Optional.empty()} if none is configured
*/
public Optional<Integer> getMtu() {
return mtu;
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + addresses.hashCode();
hash = 31 * hash + dnsServers.hashCode();
hash = 31 * hash + excludedApplications.hashCode();
hash = 31 * hash + includedApplications.hashCode();
hash = 31 * hash + keyPair.hashCode();
hash = 31 * hash + listenPort.hashCode();
hash = 31 * hash + mtu.hashCode();
return hash;
}
/**
* Converts the {@code Interface} into a string suitable for debugging purposes. The {@code
* Interface} is identified by its public key and (if set) the port used for its UDP socket.
*
* @return A concise single-line identifier for the {@code Interface}
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("(Interface ");
sb.append(keyPair.getPublicKey().toBase64());
listenPort.ifPresent(lp -> sb.append(" @").append(lp));
sb.append(')');
return sb.toString();
}
/**
* Converts the {@code Interface} into a string suitable for inclusion in a {@code wg-quick}
* configuration file.
*
* @return The {@code Interface} represented as a series of "Key = Value" lines
*/
public String toWgQuickString() {
final StringBuilder sb = new StringBuilder();
if (!addresses.isEmpty())
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
if (!dnsServers.isEmpty()) {
final List<String> dnsServerStrings =
dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
}
if (!excludedApplications.isEmpty())
sb.append("ExcludedApplications = ")
.append(Attribute.join(excludedApplications))
.append('\n');
if (!includedApplications.isEmpty())
sb.append("IncludedApplications = ")
.append(Attribute.join(includedApplications))
.append('\n');
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
return sb.toString();
}
/**
* Serializes the {@code Interface} for use with the WireGuard cross-platform userspace API.
* Note that not all attributes are included in this representation.
*
* @return the {@code Interface} represented as a series of "KEY=VALUE" lines
*/
public String toWgUserspaceString() {
final StringBuilder sb = new StringBuilder();
sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n');
listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n'));
return sb.toString();
}
@SuppressWarnings("UnusedReturnValue")
public static final class Builder {
// Defaults to an empty set.
private final Set<InetNetwork> addresses = new LinkedHashSet<>();
// Defaults to an empty set.
private final Set<InetAddress> dnsServers = 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<>();
// No default; must be provided before building.
@Nullable private KeyPair keyPair;
// Defaults to not present.
private Optional<Integer> listenPort = Optional.empty();
// Defaults to not present.
private Optional<Integer> mtu = Optional.empty();
public Builder addAddress(final InetNetwork address) {
addresses.add(address);
return this;
}
public Builder addAddresses(final Collection<InetNetwork> addresses) {
this.addresses.addAll(addresses);
return this;
}
public Builder addDnsServer(final InetAddress dnsServer) {
dnsServers.add(dnsServer);
return this;
}
public Builder addDnsServers(final Collection<? extends InetAddress> dnsServers) {
this.dnsServers.addAll(dnsServers);
return this;
}
public Interface build() throws BadConfigException {
if (keyPair == null)
throw new BadConfigException(
Section.INTERFACE, Location.PRIVATE_KEY, Reason.MISSING_ATTRIBUTE, null);
if (!includedApplications.isEmpty() && !excludedApplications.isEmpty())
throw new BadConfigException(
Section.INTERFACE, Location.INCLUDED_APPLICATIONS, Reason.INVALID_KEY, null);
return new Interface(this);
}
public Builder excludeApplication(final String application) {
excludedApplications.add(application);
return this;
}
public Builder excludeApplications(final Collection<String> applications) {
excludedApplications.addAll(applications);
return this;
}
public Builder includeApplication(final String application) {
includedApplications.add(application);
return this;
}
public Builder includeApplications(final Collection<String> applications) {
includedApplications.addAll(applications);
return this;
}
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException {
try {
for (final String address : Attribute.split(addresses))
addAddress(InetNetwork.parse(address));
return this;
} catch (final ParseException e) {
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e);
}
}
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
try {
for (final String dnsServer : Attribute.split(dnsServers))
addDnsServer(InetAddresses.parse(dnsServer));
return this;
} catch (final ParseException e) {
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
}
}
public Builder parseExcludedApplications(final CharSequence apps) {
return excludeApplications(List.of(Attribute.split(apps)));
}
public Builder parseIncludedApplications(final CharSequence apps) {
return includeApplications(List.of(Attribute.split(apps)));
}
public Builder parseListenPort(final String listenPort) throws BadConfigException {
try {
return setListenPort(Integer.parseInt(listenPort));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e);
}
}
public Builder parseMtu(final String mtu) throws BadConfigException {
try {
return setMtu(Integer.parseInt(mtu));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e);
}
}
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
try {
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
} catch (final KeyFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e);
}
}
public Builder setKeyPair(final KeyPair keyPair) {
this.keyPair = keyPair;
return this;
}
public Builder setListenPort(final int listenPort) throws BadConfigException {
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT)
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE,
String.valueOf(listenPort));
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort);
return this;
}
public Builder setMtu(final int mtu) throws BadConfigException {
if (mtu < 0)
throw new BadConfigException(
Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE, String.valueOf(mtu));
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
return this;
}
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import androidx.annotation.Nullable;
/**
*
*/
public class ParseException extends Exception {
private final Class<?> parsingClass;
private final CharSequence text;
public ParseException(final Class<?> parsingClass, final CharSequence text,
@Nullable final String message, @Nullable final Throwable cause) {
super(message, cause);
this.parsingClass = parsingClass;
this.text = text;
}
public ParseException(
final Class<?> parsingClass, final CharSequence text, @Nullable final String message) {
this(parsingClass, text, message, null);
}
public ParseException(
final Class<?> parsingClass, final CharSequence text, @Nullable final Throwable cause) {
this(parsingClass, text, null, cause);
}
public ParseException(final Class<?> parsingClass, final CharSequence text) {
this(parsingClass, text, null, null);
}
public Class<?> getParsingClass() {
return parsingClass;
}
public CharSequence getText() {
return text;
}
}

View file

@ -0,0 +1,306 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import com.wireguard.config.BadConfigException.Location;
import com.wireguard.config.BadConfigException.Reason;
import com.wireguard.config.BadConfigException.Section;
import com.wireguard.crypto.Key;
import com.wireguard.crypto.KeyFormatException;
import java.util.Collection;
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;
/**
* Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key,
* and may optionally have several other attributes.
* <p>
* Instances of this class are immutable.
*/
public final class Peer {
private final Set<InetNetwork> allowedIps;
private final Optional<InetEndpoint> endpoint;
private final Optional<Integer> persistentKeepalive;
private final Optional<Key> preSharedKey;
private final Key publicKey;
private Peer(final Builder builder) {
// Defensively copy to ensure immutability even if the Builder is reused.
allowedIps = Collections.unmodifiableSet(new LinkedHashSet<>(builder.allowedIps));
endpoint = builder.endpoint;
persistentKeepalive = builder.persistentKeepalive;
preSharedKey = builder.preSharedKey;
publicKey = Objects.requireNonNull(builder.publicKey, "Peers must have a public key");
}
/**
* Parses an series of "KEY = VALUE" lines into a {@code Peer}. Throws {@link ParseException} if
* the input is not well-formed or contains unknown attributes.
*
* @param lines an iterable sequence of lines, containing at least a public key attribute
* @return a {@code Peer} with all of its attributes set from {@code lines}
*/
public static Peer parse(final Iterable<? extends CharSequence> lines) throws BadConfigException {
final Builder builder = new Builder();
for (final CharSequence line : lines) {
final Attribute attribute =
Attribute.parse(line).orElseThrow(()
-> new BadConfigException(Section.PEER,
Location.TOP_LEVEL, Reason.SYNTAX_ERROR, line));
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
case "allowedips":
builder.parseAllowedIPs(attribute.getValue());
break;
case "endpoint":
builder.parseEndpoint(attribute.getValue());
break;
case "persistentkeepalive":
builder.parsePersistentKeepalive(attribute.getValue());
break;
case "presharedkey":
builder.parsePreSharedKey(attribute.getValue());
break;
case "publickey":
builder.parsePublicKey(attribute.getValue());
break;
default:
throw new BadConfigException(
Section.PEER, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
}
}
return builder.build();
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Peer))
return false;
final Peer other = (Peer) obj;
return allowedIps.equals(other.allowedIps) && endpoint.equals(other.endpoint)
&& persistentKeepalive.equals(other.persistentKeepalive)
&& preSharedKey.equals(other.preSharedKey) && publicKey.equals(other.publicKey);
}
/**
* Returns the peer's set of allowed IPs.
*
* @return the set of allowed IPs
*/
public Set<InetNetwork> getAllowedIps() {
// The collection is already immutable.
return allowedIps;
}
/**
* Returns the peer's endpoint.
*
* @return the endpoint, or {@code Optional.empty()} if none is configured
*/
public Optional<InetEndpoint> getEndpoint() {
return endpoint;
}
/**
* Returns the peer's persistent keepalive.
*
* @return the persistent keepalive, or {@code Optional.empty()} if none is configured
*/
public Optional<Integer> getPersistentKeepalive() {
return persistentKeepalive;
}
/**
* Returns the peer's pre-shared key.
*
* @return the pre-shared key, or {@code Optional.empty()} if none is configured
*/
public Optional<Key> getPreSharedKey() {
return preSharedKey;
}
/**
* Returns the peer's public key.
*
* @return the public key
*/
public Key getPublicKey() {
return publicKey;
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + allowedIps.hashCode();
hash = 31 * hash + endpoint.hashCode();
hash = 31 * hash + persistentKeepalive.hashCode();
hash = 31 * hash + preSharedKey.hashCode();
hash = 31 * hash + publicKey.hashCode();
return hash;
}
/**
* Converts the {@code Peer} into a string suitable for debugging purposes. The {@code Peer} is
* identified by its public key and (if known) its endpoint.
*
* @return a concise single-line identifier for the {@code Peer}
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("(Peer ");
sb.append(publicKey.toBase64());
endpoint.ifPresent(ep -> sb.append(" @").append(ep));
sb.append(')');
return sb.toString();
}
/**
* Converts the {@code Peer} into a string suitable for inclusion in a {@code wg-quick}
* configuration file.
*
* @return the {@code Peer} represented as a series of "Key = Value" lines
*/
public String toWgQuickString() {
final StringBuilder sb = new StringBuilder();
if (!allowedIps.isEmpty())
sb.append("AllowedIPs = ").append(Attribute.join(allowedIps)).append('\n');
endpoint.ifPresent(ep -> sb.append("Endpoint = ").append(ep).append('\n'));
persistentKeepalive.ifPresent(
pk -> sb.append("PersistentKeepalive = ").append(pk).append('\n'));
preSharedKey.ifPresent(psk -> sb.append("PreSharedKey = ").append(psk.toBase64()).append('\n'));
sb.append("PublicKey = ").append(publicKey.toBase64()).append('\n');
return sb.toString();
}
/**
* Serializes the {@code Peer} for use with the WireGuard cross-platform userspace API. Note
* that not all attributes are included in this representation.
*
* @return the {@code Peer} represented as a series of "key=value" lines
*/
public String toWgUserspaceString() {
final StringBuilder sb = new StringBuilder();
// The order here is important: public_key signifies the beginning of a new peer.
sb.append("public_key=").append(publicKey.toHex()).append('\n');
for (final InetNetwork allowedIp : allowedIps)
sb.append("allowed_ip=").append(allowedIp).append('\n');
endpoint.flatMap(InetEndpoint::getResolved)
.ifPresent(ep -> sb.append("endpoint=").append(ep).append('\n'));
persistentKeepalive.ifPresent(
pk -> sb.append("persistent_keepalive_interval=").append(pk).append('\n'));
preSharedKey.ifPresent(psk -> sb.append("preshared_key=").append(psk.toHex()).append('\n'));
return sb.toString();
}
@SuppressWarnings("UnusedReturnValue")
public static final class Builder {
// See wg(8)
private static final int MAX_PERSISTENT_KEEPALIVE = 65535;
// Defaults to an empty set.
private final Set<InetNetwork> allowedIps = new LinkedHashSet<>();
// Defaults to not present.
private Optional<InetEndpoint> endpoint = Optional.empty();
// Defaults to not present.
private Optional<Integer> persistentKeepalive = Optional.empty();
// Defaults to not present.
private Optional<Key> preSharedKey = Optional.empty();
// No default; must be provided before building.
@Nullable private Key publicKey;
public Builder addAllowedIp(final InetNetwork allowedIp) {
allowedIps.add(allowedIp);
return this;
}
public Builder addAllowedIps(final Collection<InetNetwork> allowedIps) {
this.allowedIps.addAll(allowedIps);
return this;
}
public Peer build() throws BadConfigException {
if (publicKey == null)
throw new BadConfigException(
Section.PEER, Location.PUBLIC_KEY, Reason.MISSING_ATTRIBUTE, null);
return new Peer(this);
}
public Builder parseAllowedIPs(final CharSequence allowedIps) throws BadConfigException {
try {
for (final String allowedIp : Attribute.split(allowedIps))
addAllowedIp(InetNetwork.parse(allowedIp));
return this;
} catch (final ParseException e) {
throw new BadConfigException(Section.PEER, Location.ALLOWED_IPS, e);
}
}
public Builder parseEndpoint(final String endpoint) throws BadConfigException {
try {
return setEndpoint(InetEndpoint.parse(endpoint));
} catch (final ParseException e) {
throw new BadConfigException(Section.PEER, Location.ENDPOINT, e);
}
}
public Builder parsePersistentKeepalive(final String persistentKeepalive)
throws BadConfigException {
try {
return setPersistentKeepalive(Integer.parseInt(persistentKeepalive));
} catch (final NumberFormatException e) {
throw new BadConfigException(
Section.PEER, Location.PERSISTENT_KEEPALIVE, persistentKeepalive, e);
}
}
public Builder parsePreSharedKey(final String preSharedKey) throws BadConfigException {
try {
return setPreSharedKey(Key.fromBase64(preSharedKey));
} catch (final KeyFormatException e) {
throw new BadConfigException(Section.PEER, Location.PRE_SHARED_KEY, e);
}
}
public Builder parsePublicKey(final String publicKey) throws BadConfigException {
try {
return setPublicKey(Key.fromBase64(publicKey));
} catch (final KeyFormatException e) {
throw new BadConfigException(Section.PEER, Location.PUBLIC_KEY, e);
}
}
public Builder setEndpoint(final InetEndpoint endpoint) {
this.endpoint = Optional.of(endpoint);
return this;
}
public Builder setPersistentKeepalive(final int persistentKeepalive) throws BadConfigException {
if (persistentKeepalive < 0 || persistentKeepalive > MAX_PERSISTENT_KEEPALIVE)
throw new BadConfigException(Section.PEER, Location.PERSISTENT_KEEPALIVE,
Reason.INVALID_VALUE, String.valueOf(persistentKeepalive));
this.persistentKeepalive =
persistentKeepalive == 0 ? Optional.empty() : Optional.of(persistentKeepalive);
return this;
}
public Builder setPreSharedKey(final Key preSharedKey) {
this.preSharedKey = Optional.of(preSharedKey);
return this;
}
public Builder setPublicKey(final Key publicKey) {
this.publicKey = publicKey;
return this;
}
}
}

View file

@ -0,0 +1,497 @@
/*
* Copyright © 2016 Southern Storm Software, Pty Ltd.
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
import java.util.Arrays;
import androidx.annotation.Nullable;
/**
* Implementation of Curve25519 ECDH.
* <p>
* This implementation was imported to WireGuard from noise-java:
* https://github.com/rweather/noise-java
* <p>
* This implementation is based on that from arduinolibs:
* https://github.com/rweather/arduinolibs
* <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({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"})
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 final int[] A;
private final int[] AA;
private final int[] B;
private final int[] BB;
private final int[] C;
private final int[] CB;
private final int[] D;
private final int[] DA;
private final int[] E;
private final long[] t1;
private final int[] t2;
private final int[] x_1;
private final int[] x_2;
private final int[] x_3;
private final int[] z_2;
private final int[] z_3;
/**
* Constructs the temporary state holder for Curve25519 evaluation.
*/
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];
}
/**
* 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, final int[] x, final int[] y) {
select = -select;
for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
final int 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(final byte[] result, final int offset, final byte[] privateKey,
@Nullable final byte[] publicKey) {
final 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) {
final int bit = (index * 8) % 26;
final int word = (index * 8) / 26;
final 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) {
final int bit = (index * 8) % 26;
final 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();
}
}
/**
* 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 static void sub(final int[] result, final int[] x, final int[] y) {
int index;
int 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;
}
/**
* 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(final int[] result, final int[] x, final int[] y) {
int carry = x[0] + y[0];
result[0] = carry & 0x03FFFFFF;
for (int 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.
*/
private void destroy() {
// Destroy all temporary variables.
Arrays.fill(x_1, 0);
Arrays.fill(x_2, 0);
Arrays.fill(x_3, 0);
Arrays.fill(z_2, 0);
Arrays.fill(z_3, 0);
Arrays.fill(A, 0);
Arrays.fill(B, 0);
Arrays.fill(C, 0);
Arrays.fill(D, 0);
Arrays.fill(E, 0);
Arrays.fill(AA, 0);
Arrays.fill(BB, 0);
Arrays.fill(DA, 0);
Arrays.fill(CB, 0);
Arrays.fill(t1, 0L);
Arrays.fill(t2, 0);
}
/**
* Evaluates the curve for every bit in a secret key.
*
* @param s The 32-byte secret key.
*/
private void evalCurve(final byte[] s) {
int sposn = 31;
int sbit = 6;
int svalue = s[sposn] | 0x40;
int swap = 0;
// 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".
while (true) {
// Conditional swaps on entry to this bit but only if we
// didn't swap on the previous bit.
final int 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);
// 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;
}
}
// 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.
*/
private void mul(final int[] result, final int[] x, final int[] y) {
// Multiply the two numbers to create the intermediate result.
long v = x[0];
for (int i = 0; i < NUM_LIMBS_255BIT; ++i) {
t1[i] = v * y[i];
}
for (int i = 1; i < NUM_LIMBS_255BIT; ++i) {
v = x[i];
for (int j = 0; j < (NUM_LIMBS_255BIT - 1); ++j) {
t1[i + j] += v * y[j];
}
t1[i + NUM_LIMBS_255BIT - 1] = v * y[NUM_LIMBS_255BIT - 1];
}
// Propagate carries and convert back into 26-bit words.
v = t1[0];
t2[0] = ((int) v) & 0x03FFFFFF;
for (int i = 1; i < NUM_LIMBS_510BIT; ++i) {
v = (v >> 26) + t1[i];
t2[i] = ((int) v) & 0x03FFFFFF;
}
// Reduce the result modulo 2^255 - 19.
reduce(result, t2, NUM_LIMBS_255BIT);
}
/**
* Multiplies a number by the a24 constant, modulo 2^255 - 19.
*
* @param result The result.
* @param x The number to multiply by a24.
*/
private void mulA24(final int[] result, final int[] x) {
final long a24 = 121665;
long carry = 0;
for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
carry += a24 * x[index];
t2[index] = ((int) carry) & 0x03FFFFFF;
carry >>= 26;
}
t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF;
reduce(result, t2, 1);
}
/**
* Raise x to the power of (2^250 - 1).
*
* @param result The result. Must not overlap with x.
* @param x The argument.
*/
private void pow250(final int[] result, final int[] x) {
// The big-endian hexadecimal expansion of (2^250 - 1) is:
// 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
//
// The naive implementation needs to do 2 multiplications per 1 bit and
// 1 multiplication per 0 bit. We can improve upon this by creating a
// pattern 0000000001 ... 0000000001. If we square and multiply the
// pattern by itself we can turn the pattern into the partial results
// 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc.
// This averages out to about 1.1 multiplications per 1 bit instead of 2.
// Build a pattern of 250 bits in length of repeated copies of 0000000001.
square(A, x);
for (int j = 0; j < 9; ++j) square(A, A);
mul(result, A, x);
for (int i = 0; i < 23; ++i) {
for (int j = 0; j < 10; ++j) square(A, A);
mul(result, result, A);
}
// Multiply bit-shifted versions of the 0000000001 pattern into
// the result to "fill in" the gaps in the pattern.
square(A, result);
mul(result, result, A);
for (int j = 0; j < 8; ++j) {
square(A, A);
mul(result, result, A);
}
}
/**
* Computes the reciprocal of a number modulo 2^255 - 19.
*
* @param result The result. Must not overlap with x.
* @param x The argument.
*/
private void recip(final int[] result, final 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
// Start with the 250 upper bits of the expansion of (p - 2).
pow250(result, x);
// Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest.
square(result, result);
square(result, result);
mul(result, result, x);
square(result, result);
square(result, result);
mul(result, result, x);
square(result, result);
mul(result, result, x);
}
/**
* 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(final int[] result, final int[] x, final int size) {
// 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.
int carry = 0;
int limb = x[NUM_LIMBS_255BIT - 1] >> 21;
x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
for (int 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 (int index = size; index < NUM_LIMBS_255BIT; ++index) {
carry += x[index];
x[index] = carry & 0x03FFFFFF;
carry >>= 26;
}
}
// 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 (int 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);
}
/**
* Reduces a number modulo 2^255 - 19 where it is known that the
* number can be reduced with only 1 trial subtraction.
*
* @param x The number to reduce, and the result.
*/
private void reduceQuick(final int[] x) {
// 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.
int carry = 19;
for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
carry += x[index];
t2[index] = carry & 0x03FFFFFF;
carry >>= 26;
}
// 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.
final int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01);
final int nmask = ~mask;
t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
for (int index = 0; index < NUM_LIMBS_255BIT; ++index)
x[index] = (x[index] & nmask) | (t2[index] & mask);
}
/**
* Squares a number modulo 2^255 - 19.
*
* @param result The result.
* @param x The number to square.
*/
private void square(final int[] result, final int[] x) {
mul(result, x, x);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,283 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
import com.wireguard.crypto.KeyFormatException.Type;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
/**
* Represents a WireGuard public or private key. This class uses specialized constant-time base64
* and hexadecimal codec implementations that resist side-channel attacks.
* <p>
* Instances of this class are immutable.
*/
@SuppressWarnings("MagicNumber")
public final class Key {
private final byte[] key;
/**
* Constructs an object encapsulating the supplied key.
*
* @param key an array of bytes containing a binary key. Callers of this constructor are
* responsible for ensuring that the array is of the correct length.
*/
private Key(final byte[] key) {
// Defensively copy to ensure immutability.
this.key = Arrays.copyOf(key, key.length);
}
/**
* Decodes a single 4-character base64 chunk to an integer in constant time.
*
* @param src an array of at least 4 characters in base64 format
* @param srcOffset the offset of the beginning of the chunk in {@code src}
* @return the decoded 3-byte integer, or some arbitrary integer value if the input was not
* valid base64
*/
private static int decodeBase64(final char[] src, final int srcOffset) {
int val = 0;
for (int i = 0; i < 4; ++i) {
final char c = src[i + srcOffset];
val |= (-1 + ((((('A' - 1) - c) & (c - ('Z' + 1))) >>> 8) & (c - 64))
+ ((((('a' - 1) - c) & (c - ('z' + 1))) >>> 8) & (c - 70))
+ ((((('0' - 1) - c) & (c - ('9' + 1))) >>> 8) & (c + 5))
+ ((((('+' - 1) - c) & (c - ('+' + 1))) >>> 8) & 63)
+ ((((('/' - 1) - c) & (c - ('/' + 1))) >>> 8) & 64))
<< (18 - 6 * i);
}
return val;
}
/**
* Encodes a single 4-character base64 chunk from 3 consecutive bytes in constant time.
*
* @param src an array of at least 3 bytes
* @param srcOffset the offset of the beginning of the chunk in {@code src}
* @param dest an array of at least 4 characters
* @param destOffset the offset of the beginning of the chunk in {@code dest}
*/
private static void encodeBase64(
final byte[] src, final int srcOffset, final char[] dest, final int destOffset) {
final byte[] input = {
(byte) ((src[srcOffset] >>> 2) & 63),
(byte) ((src[srcOffset] << 4 | ((src[1 + srcOffset] & 0xff) >>> 4)) & 63),
(byte) ((src[1 + srcOffset] << 2 | ((src[2 + srcOffset] & 0xff) >>> 6)) & 63),
(byte) ((src[2 + srcOffset]) & 63),
};
for (int i = 0; i < 4; ++i) {
dest[i + destOffset] =
(char) (input[i] + 'A' + (((25 - input[i]) >>> 8) & 6) - (((51 - input[i]) >>> 8) & 75)
- (((61 - input[i]) >>> 8) & 15) + (((62 - input[i]) >>> 8) & 3));
}
}
/**
* Decodes a WireGuard public or private key from its base64 string representation. This
* function throws a {@link KeyFormatException} if the source string is not well-formed.
*
* @param str the base64 string representation of a WireGuard key
* @return the decoded key encapsulated in an immutable container
*/
public static Key fromBase64(final String str) throws KeyFormatException {
final char[] input = str.toCharArray();
if (input.length != Format.BASE64.length || input[Format.BASE64.length - 1] != '=')
throw new KeyFormatException(Format.BASE64, Type.LENGTH);
final byte[] key = new byte[Format.BINARY.length];
int i;
int ret = 0;
for (i = 0; i < key.length / 3; ++i) {
final int val = decodeBase64(input, i * 4);
ret |= val >>> 31;
key[i * 3] = (byte) ((val >>> 16) & 0xff);
key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
key[i * 3 + 2] = (byte) (val & 0xff);
}
final char[] endSegment = {
input[i * 4],
input[i * 4 + 1],
input[i * 4 + 2],
'A',
};
final int val = decodeBase64(endSegment, 0);
ret |= (val >>> 31) | (val & 0xff);
key[i * 3] = (byte) ((val >>> 16) & 0xff);
key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
if (ret != 0)
throw new KeyFormatException(Format.BASE64, Type.CONTENTS);
return new Key(key);
}
/**
* Wraps a WireGuard public or private key in an immutable container. This function throws a
* {@link KeyFormatException} if the source data is not the correct length.
*
* @param bytes an array of bytes containing a WireGuard key in binary format
* @return the key encapsulated in an immutable container
*/
public static Key fromBytes(final byte[] bytes) throws KeyFormatException {
if (bytes.length != Format.BINARY.length)
throw new KeyFormatException(Format.BINARY, Type.LENGTH);
return new Key(bytes);
}
/**
* Decodes a WireGuard public or private key from its hexadecimal string representation. This
* function throws a {@link KeyFormatException} if the source string is not well-formed.
*
* @param str the hexadecimal string representation of a WireGuard key
* @return the decoded key encapsulated in an immutable container
*/
public static Key fromHex(final String str) throws KeyFormatException {
final char[] input = str.toCharArray();
if (input.length != Format.HEX.length)
throw new KeyFormatException(Format.HEX, Type.LENGTH);
final byte[] key = new byte[Format.BINARY.length];
int ret = 0;
for (int i = 0; i < key.length; ++i) {
int c;
int cNum;
int cNum0;
int cAlpha;
int cAlpha0;
int cVal;
final int cAcc;
c = input[i * 2];
cNum = c ^ 48;
cNum0 = ((cNum - 10) >>> 8) & 0xff;
cAlpha = (c & ~32) - 55;
cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff;
ret |= ((cNum0 | cAlpha0) - 1) >>> 8;
cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha);
cAcc = cVal * 16;
c = input[i * 2 + 1];
cNum = c ^ 48;
cNum0 = ((cNum - 10) >>> 8) & 0xff;
cAlpha = (c & ~32) - 55;
cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff;
ret |= ((cNum0 | cAlpha0) - 1) >>> 8;
cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha);
key[i] = (byte) (cAcc | cVal);
}
if (ret != 0)
throw new KeyFormatException(Format.HEX, Type.CONTENTS);
return new Key(key);
}
/**
* Generates a private key using the system's {@link SecureRandom} number generator.
*
* @return a well-formed random private key
*/
static Key generatePrivateKey() {
final SecureRandom secureRandom = new SecureRandom();
final byte[] privateKey = new byte[Format.BINARY.getLength()];
secureRandom.nextBytes(privateKey);
privateKey[0] &= 248;
privateKey[31] &= 127;
privateKey[31] |= 64;
return new Key(privateKey);
}
/**
* Generates a public key from an existing private key.
*
* @param privateKey a private key
* @return a well-formed public key that corresponds to the supplied private key
*/
static Key generatePublicKey(final Key privateKey) {
final byte[] publicKey = new byte[Format.BINARY.getLength()];
Curve25519.eval(publicKey, 0, privateKey.getBytes(), null);
return new Key(publicKey);
}
@Override
public boolean equals(final Object obj) {
if (obj == this)
return true;
if (obj == null || obj.getClass() != getClass())
return false;
final Key other = (Key) obj;
return MessageDigest.isEqual(key, other.key);
}
/**
* Returns the key as an array of bytes.
*
* @return an array of bytes containing the raw binary key
*/
public byte[] getBytes() {
// Defensively copy to ensure immutability.
return Arrays.copyOf(key, key.length);
}
@Override
public int hashCode() {
int ret = 0;
for (int i = 0; i < key.length / 4; ++i)
ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16)
+ (key[i * 4 + 3] >> 24);
return ret;
}
/**
* Encodes the key to base64.
*
* @return a string containing the encoded key
*/
public String toBase64() {
final char[] output = new char[Format.BASE64.length];
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[Format.BASE64.length - 1] = '=';
return new String(output);
}
/**
* Encodes the key to hexadecimal ASCII characters.
*
* @return a string containing the encoded key
*/
public String toHex() {
final char[] output = new char[Format.HEX.length];
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));
}
return new String(output);
}
/**
* The supported formats for encoding a WireGuard key.
*/
public enum Format {
BASE64(44),
BINARY(32),
HEX(64);
private final int length;
Format(final int length) {
this.length = length;
}
public int getLength() {
return length;
}
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
/**
* An exception thrown when attempting to parse an invalid key (too short, too long, or byte
* data inappropriate for the format). The format being parsed can be accessed with the
* {@link #getFormat} method.
*/
public final class KeyFormatException extends Exception {
private final Key.Format format;
private final Type type;
KeyFormatException(final Key.Format format, final Type type) {
this.format = format;
this.type = type;
}
public Key.Format getFormat() {
return format;
}
public Type getType() {
return type;
}
public enum Type { CONTENTS, LENGTH }
}

View file

@ -0,0 +1,52 @@
/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.crypto;
/**
* Represents a Curve25519 key pair as used by WireGuard.
* <p>
* Instances of this class are immutable.
*/
public class KeyPair {
private final Key privateKey;
private final Key publicKey;
/**
* Creates a key pair using a newly-generated private key.
*/
public KeyPair() {
this(Key.generatePrivateKey());
}
/**
* Creates a key pair using an existing private key.
*
* @param privateKey a private key, used to derive the public key
*/
public KeyPair(final Key privateKey) {
this.privateKey = privateKey;
publicKey = Key.generatePublicKey(privateKey);
}
/**
* Returns the private key from the key pair.
*
* @return the private key
*/
public Key getPrivateKey() {
return privateKey;
}
/**
* Returns the public key from the key pair.
*
* @return the public key
*/
public Key getPublicKey() {
return publicKey;
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/vpnicon_background"/>
<foreground android:drawable="@mipmap/vpnicon_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/vpnicon_background"/>
<foreground android:drawable="@mipmap/vpnicon_round"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="vpnicon_background">#000000</color>
</resources>

View file

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Parcel
import androidx.core.app.NotificationCompat
import org.json.JSONObject
object NotificationUtil {
var sCurrentContext: Context? = null
private var sNotificationBuilder: NotificationCompat.Builder? = null
const val NOTIFICATION_CHANNEL_ID = "com.amnezia.vpnNotification"
const val CONNECTED_NOTIFICATION_ID = 1337
const val tag = "NotificationUtil"
/**
* Updates the current shown notification from a
* Parcel - Gets called from AndroidController.cpp
*/
fun update(data: Parcel) {
// [data] is here a json containing the noification content
val buffer = data.createByteArray()
val json = buffer?.let { String(it) }
val content = JSONObject(json)
update(content.getString("title"), content.getString("message"))
}
/**
* Updates the current shown notification
*/
fun update(heading: String, message: String) {
if (sCurrentContext == null) return
val notificationManager: NotificationManager =
sCurrentContext?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
sNotificationBuilder?.let {
it.setContentTitle(heading)
.setContentText(message)
notificationManager.notify(CONNECTED_NOTIFICATION_ID, it.build())
}
}
/**
* Saves the default translated "connected" notification, in case the vpn gets started
* without the app.
*/
fun saveFallBackMessage(data: Parcel, context: Context) {
// [data] is here a json containing the notification content
val buffer = data.createByteArray()
val json = buffer?.let { String(it) }
val content = JSONObject(json)
val prefs = Prefs.get(context)
prefs.edit()
.putString("fallbackNotificationHeader", content.getString("title"))
.putString("fallbackNotificationMessage", content.getString("message"))
.apply()
Log.v(tag, "Saved new fallback message -> ${content.getString("title")}")
}
/*
* Creates a new Notification using the current set of Strings
* Shows the notification in the given {context}
*/
fun show(service: VPNService) {
sNotificationBuilder = NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
sCurrentContext = service
val notificationManager: NotificationManager =
sCurrentContext?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// From Oreo on we need to have a "notification channel" to post to.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "vpn"
val descriptionText = " "
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
notificationManager.createNotificationChannel(channel)
}
// In case we do not have gotten a message to show from the Frontend
// try to populate the notification with a translated Fallback message
val prefs = Prefs.get(service)
val message =
"" + prefs.getString("fallbackNotificationMessage", "Running in the Background")
val header = "" + prefs.getString("fallbackNotificationHeader", "Mozilla VPN")
// Create the Intent that Should be Fired if the User Clicks the notification
val mainActivityName = "org.amnezia.vpn.qt.VPNActivity"
val activity = Class.forName(mainActivityName)
val intent = Intent(service, activity)
val pendingIntent = PendingIntent.getActivity(service, 0, intent, 0)
// Build our notification
sNotificationBuilder?.let {
it.setSmallIcon(org.amnezia.vpn.R.drawable.ic_amnezia_round)
.setContentTitle(header)
.setContentText(message)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
service.startForeground(CONNECTED_NOTIFICATION_ID, it.build())
}
}
}

View file

@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
object Prefs {
// Opens and returns an instance of EncryptedSharedPreferences
fun get(context: Context): SharedPreferences {
try {
val mainKey = MasterKey.Builder(context.applicationContext)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPrefsFile = "com.amnezia.vpn.secure.prefs"
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
context.applicationContext,
sharedPrefsFile,
mainKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
return sharedPreferences
} catch (e: Exception) {
Log.e("Android-Prefs", "Getting Encryption Storage failed, plaintext fallback")
return context.getSharedPreferences("com.amnezia.vpn.prefrences", Context.MODE_PRIVATE)
}
}
}

View file

@ -0,0 +1,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn
import android.content.Context
import java.io.File
import java.time.LocalDateTime
import android.util.Log as nativeLog
/*
* Drop in replacement for android.util.Log
* Also stores a copy of all logs in tmp/mozilla_deamon_logs.txt
*/
class Log {
val LOG_MAX_FILE_SIZE = 204800
private var file: File
private constructor(context: Context) {
val tempDIR = context.cacheDir
file = File(tempDIR, "mozilla_deamon_logs.txt")
if (file.length() > LOG_MAX_FILE_SIZE) {
file.writeText("")
}
}
companion object {
var instance: Log? = null
fun init(ctx: Context) {
if (instance == null) {
instance = Log(ctx)
}
}
fun i(tag: String, message: String) {
instance?.write("[info] - ($tag) - $message")
if (!BuildConfig.DEBUG) { return; }
nativeLog.i(tag, message)
}
fun v(tag: String, message: String) {
instance?.write("($tag) - $message")
if (!BuildConfig.DEBUG) { return; }
nativeLog.v(tag, message)
}
fun e(tag: String, message: String) {
instance?.write("[error] - ($tag) - $message")
if (!BuildConfig.DEBUG) { return; }
nativeLog.e(tag, message)
}
// Only Prints && Loggs when in debug, noop in release.
fun sensitive(tag: String, message: String?) {
if (!BuildConfig.DEBUG) { return; }
if (message == null) { return; }
e(tag, message)
}
fun getContent(): String? {
return try {
instance?.file?.readText()
} catch (e: Exception) {
"=== Failed to read Daemon Logs === \n ${e.localizedMessage} "
}
}
fun clearFile() {
instance?.file?.writeText("")
}
}
private fun write(message: String) {
//LocalDateTime.now()
//file.appendText("[${LocalDateTime.now()}] $message \n")
}
}

View file

@ -0,0 +1,309 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.system.OsConstants
import com.wireguard.android.util.SharedLibraryLoader
import com.wireguard.config.*
import com.wireguard.crypto.Key
import org.json.JSONObject
class VPNService : android.net.VpnService() {
private val tag = "VPNService"
private var mBinder: VPNServiceBinder = VPNServiceBinder(this)
private var mConfig: JSONObject? = null
private var mConnectionTime: Long = 0
private var mAlreadyInitialised = false
private var currentTunnelHandle = -1
fun init() {
if (mAlreadyInitialised) {
return
}
Log.init(this)
SharedLibraryLoader.loadSharedLibrary(this, "wg-go")
Log.i(tag, "loaded lib")
Log.e(tag, "Wireguard Version ${wgVersion()}")
mAlreadyInitialised = true
}
override fun onUnbind(intent: Intent?): Boolean {
if (!isUp) {
// If the Qt Client got closed while we were not connected
// we do not need to stay as a foreground service.
stopForeground(true)
}
return super.onUnbind(intent)
}
/**
* EntryPoint for the Service, gets Called when AndroidController.cpp
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
*/
override fun onBind(intent: Intent?): IBinder? {
Log.v(tag, "Got Bind request")
init()
return mBinder
}
/**
* Might be the entryPoint if the Service gets Started via an
* Service Intent: Might be from Always-On-Vpn from Settings
* or from Booting the device and having "connect on boot" enabled.
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
init()
intent?.let {
if (intent.getBooleanExtra("startOnly", false)) {
Log.i(tag, "Start only!")
return super.onStartCommand(intent, flags, startId)
}
}
// This start is from always-on
if (this.mConfig == null) {
// We don't have tunnel to turn on - Try to create one with last config the service got
val prefs = Prefs.get(this)
val lastConfString = prefs.getString("lastConf", "")
if (lastConfString.isNullOrEmpty()) {
// We have nothing to connect to -> Exit
Log.e(
tag,
"VPN service was triggered without defining a Server or having a tunnel"
)
return super.onStartCommand(intent, flags, startId)
}
this.mConfig = JSONObject(lastConfString)
}
turnOn(this.mConfig!!)
return super.onStartCommand(intent, flags, startId)
}
// Invoked when the application is revoked.
// At this moment, the VPN interface is already deactivated by the system.
override fun onRevoke() {
this.turnOff()
super.onRevoke()
}
var connectionTime: Long = 0
get() {
return mConnectionTime
}
var isUp: Boolean
get() {
return currentTunnelHandle >= 0
}
private set(value) {
if (value) {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "")
mConnectionTime = System.currentTimeMillis()
return
}
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0
}
val status: JSONObject
get() {
val deviceIpv4: String = ""
return JSONObject().apply {
putOpt("rx_bytes", getConfigValue("rx_bytes"))
putOpt("tx_bytes", getConfigValue("tx_bytes"))
putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway"))
putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address"))
}
}
/*
* Checks if the VPN Permission is given.
* If the permission is given, returns true
* Requests permission and returns false if not.
*/
fun checkPermissions(): Boolean {
// See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service
// Call Prepare, if we get an Intent back, we dont have the VPN Permission
// from the user. So we need to pass this to our main Activity and exit here.
val intent = prepare(this)
if (intent == null) {
Log.e(tag, "VPN Permission Already Present")
return true
}
Log.e(tag, "Requesting VPN Permission")
return false
}
fun turnOn(json: JSONObject) {
Log.sensitive(tag, json.toString())
val wireguard_conf = buildWireugardConfig(json)
if (!checkPermissions()) {
Log.e(tag, "turn on was called without no permissions present!")
isUp = false
return
}
Log.i(tag, "Permission okay")
if (currentTunnelHandle != -1) {
Log.e(tag, "Tunnel already up")
// Turn the tunnel down because this might be a switch
wgTurnOff(currentTunnelHandle)
}
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
val builder = Builder()
setupBuilder(wireguard_conf, builder)
builder.setSession("mvpn0")
builder.establish().use { tun ->
if (tun == null)return
Log.i(tag, "Go backend " + wgVersion())
currentTunnelHandle = wgTurnOn("mvpn0", tun.detachFd(), wgConfig)
}
if (currentTunnelHandle < 0) {
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
isUp = false
return
}
protect(wgGetSocketV4(currentTunnelHandle))
protect(wgGetSocketV6(currentTunnelHandle))
mConfig = json
isUp = true
// Store the config in case the service gets
// asked boot vpn from the OS
val prefs = Prefs.get(this)
prefs.edit()
.putString("lastConf", json.toString())
.apply()
NotificationUtil.show(this) // Go foreground
}
fun turnOff() {
Log.v(tag, "Try to disable tunnel")
wgTurnOff(currentTunnelHandle)
currentTunnelHandle = -1
stopForeground(false)
isUp = false
}
/**
* Configures an Android VPN Service Tunnel
* with a given Wireguard Config
*/
private fun setupBuilder(config: Config, builder: Builder) {
// Setup Split tunnel
for (excludedApplication in config.`interface`.excludedApplications)
builder.addDisallowedApplication(excludedApplication)
// Device IP
for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask)
// DNS
for (addr in config.`interface`.dnsServers) builder.addDnsServer(addr.hostAddress)
// Add All routes the VPN may route tos
for (peer in config.peers) {
for (addr in peer.allowedIps) {
builder.addRoute(addr.address, addr.mask)
}
}
builder.allowFamily(OsConstants.AF_INET)
builder.allowFamily(OsConstants.AF_INET6)
builder.setMtu(config.`interface`.mtu.orElse(1280))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
builder.setBlocking(true)
}
/**
* Gets config value for {key} from the Current
* running Wireguard tunnel
*/
private fun getConfigValue(key: String): String? {
if (!isUp) {
return null
}
val config = wgGetConfig(currentTunnelHandle) ?: return null
val lines = config.split("\n")
for (line in lines) {
val parts = line.split("=")
val k = parts.first()
val value = parts.last()
if (key == k) {
return value
}
}
return null
}
/**
* Create a Wireguard [Config] from a [json] string -
* The [json] will be created in AndroidController.cpp
*/
private fun buildWireugardConfig(obj: JSONObject): Config {
val confBuilder = Config.Builder()
val jServer = obj.getJSONObject("server")
val peerBuilder = Peer.Builder()
val ep =
InetEndpoint.parse(jServer.getString("ipv4AddrIn") + ":" + jServer.getString("port"))
peerBuilder.setEndpoint(ep)
peerBuilder.setPublicKey(Key.fromBase64(jServer.getString("publicKey")))
val jAllowedIPList = obj.getJSONArray("allowedIPs")
if (jAllowedIPList.length() == 0) {
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
peerBuilder.addAllowedIp(internet)
} else {
(0 until jAllowedIPList.length()).toList().forEach {
val network = InetNetwork.parse(jAllowedIPList.getString(it))
peerBuilder.addAllowedIp(network)
}
}
confBuilder.addPeer(peerBuilder.build())
val privateKey = obj.getJSONObject("keys").getString("privateKey")
val jDevice = obj.getJSONObject("device")
val ifaceBuilder = Interface.Builder()
ifaceBuilder.parsePrivateKey(privateKey)
ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv4Address")))
ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv6Address")))
ifaceBuilder.addDnsServer(InetNetwork.parse(obj.getString("dns")).address)
val jExcludedApplication = obj.getJSONArray("excludedApps")
(0 until jExcludedApplication.length()).toList().forEach {
val appName = jExcludedApplication.get(it).toString()
ifaceBuilder.excludeApplication(appName)
}
confBuilder.setInterface(ifaceBuilder.build())
return confBuilder.build()
}
companion object {
@JvmStatic
fun startService(c: Context) {
c.applicationContext.startService(
Intent(c.applicationContext, VPNService::class.java).apply {
putExtra("startOnly", true)
}
)
}
@JvmStatic
private external fun wgGetConfig(handle: Int): String?
@JvmStatic
private external fun wgGetSocketV4(handle: Int): Int
@JvmStatic
private external fun wgGetSocketV6(handle: Int): Int
@JvmStatic
private external fun wgTurnOff(handle: Int)
@JvmStatic
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
@JvmStatic
private external fun wgVersion(): String?
}
}

View file

@ -0,0 +1,170 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn
import android.os.Binder
import android.os.DeadObjectException
import android.os.IBinder
import android.os.Parcel
import com.wireguard.config.*
import org.json.JSONObject
import java.lang.Exception
class VPNServiceBinder(service: VPNService) : Binder() {
private val mService = service
private val tag = "VPNServiceBinder"
private var mListener: IBinder? = null
private var mResumeConfig: JSONObject? = null
/**
* The codes this Binder does accept in [onTransact]
*/
object ACTIONS {
const val activate = 1
const val deactivate = 2
const val registerEventListener = 3
const val requestStatistic = 4
const val requestGetLog = 5
const val requestCleanupLog = 6
const val resumeActivate = 7
const val setNotificationText = 8
const val setFallBackNotification = 9
}
/**
* Gets called when the VPNServiceBinder gets a request from a Client.
* The [code] determines what action is requested. - see [ACTIONS]
* [data] may contain a utf-8 encoded json string with optional args or is null.
* [reply] is a pointer to a buffer in the clients memory, to reply results.
* we use this to send result data.
*
* returns true if the [code] was accepted
*/
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
Log.i(tag, "GOT TRANSACTION $code")
when (code) {
ACTIONS.activate -> {
try {
Log.i(tag, "Activiation Requested, parsing Config")
// [data] is here a json containing the wireguard conf
val buffer = data.createByteArray()
val json = buffer?.let { String(it) }
val config = JSONObject(json)
Log.v(tag, "Stored new Tunnel config in Service")
if (!mService.checkPermissions()) {
mResumeConfig = config
// The Permission prompt was already
// send, in case it's accepted we will
// receive ACTIONS.resumeActivate
return true
}
this.mService.turnOn(config)
} catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
dispatchEvent(EVENTS.activationError, e.localizedMessage)
}
return true
}
ACTIONS.resumeActivate -> {
// [data] is empty
// Activate the current tunnel
try {
mResumeConfig?.let { this.mService.turnOn(it) }
} catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
}
return true
}
ACTIONS.deactivate -> {
// [data] here is empty
this.mService.turnOff()
return true
}
ACTIONS.registerEventListener -> {
// [data] contains the Binder that we need to dispatch the Events
val binder = data.readStrongBinder()
mListener = binder
val obj = JSONObject()
obj.put("connected", mService.isUp)
obj.put("time", mService.connectionTime)
dispatchEvent(EVENTS.init, obj.toString())
return true
}
ACTIONS.requestStatistic -> {
dispatchEvent(EVENTS.statisticUpdate, mService.status.toString())
return true
}
ACTIONS.requestGetLog -> {
// Grabs all the Logs and dispatch new Log Event
dispatchEvent(EVENTS.backendLogs, Log.getContent())
return true
}
ACTIONS.requestCleanupLog -> {
Log.clearFile()
return true
}
ACTIONS.setNotificationText -> {
NotificationUtil.update(data)
return true
}
ACTIONS.setFallBackNotification -> {
NotificationUtil.saveFallBackMessage(data, mService)
return true
}
IBinder.LAST_CALL_TRANSACTION -> {
Log.e(tag, "The OS Requested to shut down the VPN")
this.mService.turnOff()
return true
}
else -> {
Log.e(tag, "Received invalid bind request \t Code -> $code")
// If we're hitting this there is probably something wrong in the client.
return false
}
}
return false
}
/**
* Dispatches an Event to all registered Binders
* [code] the Event that happened - see [EVENTS]
* To register an Eventhandler use [onTransact] with
* [ACTIONS.registerEventListener]
*/
fun dispatchEvent(code: Int, payload: String?) {
try {
mListener?.let {
if (it.isBinderAlive) {
val data = Parcel.obtain()
data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
it.transact(code, data, Parcel.obtain(), 0)
}
}
} catch (e: DeadObjectException) {
// If the QT Process is killed (not just inactive)
// we cant access isBinderAlive, so nothing to do here.
}
}
/**
* The codes we Are Using in case of [dispatchEvent]
*/
object EVENTS {
const val init = 0
const val connected = 1
const val disconnected = 2
const val statisticUpdate = 3
const val backendLogs = 4
const val activationError = 5
}
}

View file

@ -0,0 +1,189 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn.qt;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.Manifest.permission;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
// Gets used by /platforms/android/androidAppListProvider.cpp
public class PackageManagerHelper {
final static String TAG = "PackageManagerHelper";
final static int MIN_CHROME_VERSION = 65;
final static List<String> CHROME_BROWSERS = Arrays.asList(
new String[] {"com.google.android.webview", "com.android.webview", "com.google.chrome"});
private static String getAllAppNames(Context ctx) {
JSONObject output = new JSONObject();
PackageManager pm = ctx.getPackageManager();
List<String> browsers = getBrowserIDs(pm);
List<PackageInfo> packs = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
// Do not add ourselves and System Apps to the list, unless it might be a browser
if ((!isSystemPackage(p,pm) || browsers.contains(p.packageName))
&& !isSelf(p)) {
String appid = p.packageName;
String appName = p.applicationInfo.loadLabel(pm).toString();
try {
output.put(appid, appName);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
return output.toString();
}
private static Drawable getAppIcon(Context ctx, String id) {
try {
return ctx.getPackageManager().getApplicationIcon(id);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return new ColorDrawable(Color.TRANSPARENT);
}
private static boolean isSystemPackage(PackageInfo pkgInfo, PackageManager pm) {
if( (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0){
// no system app
return false;
}
// For Systems Packages there are Cases where we want to add it anyway:
// Has the use Internet permission (otherwise makes no sense)
// Had at least 1 update (this means it's probably on any AppStore)
// Has a a launch activity (has a ui and is not just a system service)
if(!usesInternet(pkgInfo)){
return true;
}
if(!hadUpdate(pkgInfo)){
return true;
}
if(pm.getLaunchIntentForPackage(pkgInfo.packageName) == null){
// If there is no way to launch this from a homescreen, def a sys package
return true;
}
return false;
}
private static boolean isSelf(PackageInfo pkgInfo) {
return pkgInfo.packageName.equals("org.amnezia.vpn")
|| pkgInfo.packageName.equals("org.amnezia.vpn.debug");
}
private static boolean usesInternet(PackageInfo pkgInfo){
if(pkgInfo.requestedPermissions == null){
return false;
}
for(int i=0; i < pkgInfo.requestedPermissions.length; i++) {
String permission = pkgInfo.requestedPermissions[i];
if(Manifest.permission.INTERNET.equals(permission)){
return true;
}
}
return false;
}
private static boolean hadUpdate(PackageInfo pkgInfo){
return pkgInfo.lastUpdateTime > pkgInfo.firstInstallTime;
}
// Returns List of all Packages that can classify themselves as browsers
private static List<String> getBrowserIDs(PackageManager pm) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.mozilla.org/"));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that
// are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set
// in the intent filter
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
List<String> browsers = new ArrayList<String>();
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo info = resolveInfos.get(i);
String browserID = info.activityInfo.packageName;
browsers.add(browserID);
}
return browsers;
}
// Gets called in AndroidAuthenticationListener;
public static boolean isWebViewSupported(Context ctx) {
Log.v(TAG, "Checking if installed Webview is compatible with FxA");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// The default Webview is able do to FXA
return true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PackageInfo pi = WebView.getCurrentWebViewPackage();
if (CHROME_BROWSERS.contains(pi.packageName)) {
return isSupportedChromeBrowser(pi);
}
return isNotAncientBrowser(pi);
}
// Before O the webview is hardcoded, but we dont know which package it is.
// Check if com.google.android.webview is installed
PackageManager pm = ctx.getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo("com.google.android.webview", 0);
return isSupportedChromeBrowser(pi);
} catch (PackageManager.NameNotFoundException e) {
}
// Otherwise check com.android.webview
try {
PackageInfo pi = pm.getPackageInfo("com.android.webview", 0);
return isSupportedChromeBrowser(pi);
} catch (PackageManager.NameNotFoundException e) {
}
Log.e(TAG, "Android System WebView is not found");
// Giving up :(
return false;
}
private static boolean isSupportedChromeBrowser(PackageInfo pi) {
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
Log.d(TAG, "version name: " + pi.versionName);
Log.d(TAG, "version code: " + pi.versionCode);
try {
String versionCode = pi.versionName.split(Pattern.quote(" "))[0];
String majorVersion = versionCode.split(Pattern.quote("."))[0];
int version = Integer.parseInt(majorVersion);
return version >= MIN_CHROME_VERSION;
} catch (Exception e) {
Log.e(TAG, "Failed to check Chrome Version Code " + pi.versionName);
return false;
}
}
private static boolean isNotAncientBrowser(PackageInfo pi) {
// Not a google chrome - So the version name is worthless
// Lets just make sure the WebView
// used is not ancient ==> Was updated in at least the last 365 days
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
Log.d(TAG, "version name: " + pi.versionName);
Log.d(TAG, "version code: " + pi.versionCode);
double oneYearInMillis = 31536000000L;
return pi.lastUpdateTime > (System.currentTimeMillis() - oneYearInMillis);
}
}

View file

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn.qt;
import android.view.KeyEvent;
public class VPNActivity extends org.qtproject.qt5.android.bindings.QtActivity {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
try {
if (!handleBackButton()) {
// Move the activity into paused state if back button was pressed
moveTaskToBack(true);
}
} catch (Exception e) {
}
}
// Returns true if MVPN has handled the back button
native boolean handleBackButton();
}

View file

@ -0,0 +1,18 @@
package org.amnezia.vpn.qt;
import android.app.Activity;
import android.os.Bundle;
import org.amnezia.vpn.BuildConfig;
public class VPNApplication extends org.qtproject.qt5.android.bindings.QtApplication {
private static VPNApplication instance;
@Override
public void onCreate() {
super.onCreate();
VPNApplication.instance = this;
}
}

View file

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn.qt
import android.content.Context
import android.content.Intent
class VPNPermissionHelper : android.net.VpnService() {
/**
* This small service does nothing else then checking if the vpn permission
* is present and prompting if not.
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val intent = prepare(this.applicationContext)
if (intent != null) {
startActivityForResult(intent)
}
return START_NOT_STICKY
}
companion object {
@JvmStatic
fun startService(c: Context) {
val appC = c.applicationContext
appC.startService(Intent(appC, VPNPermissionHelper::class.java))
}
}
/**
* Fetches the Global QTAndroidActivity and calls startActivityForResult with the given intent
* Is used to request the VPN-Permission, if not given.
* Actually Implemented in src/platforms/android/AndroidController.cpp
*/
external fun startActivityForResult(i: Intent)
}

View file

@ -0,0 +1,44 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message("PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}")
set( CMAKE_WG_TOOLS_DIR ../../../../desktop-client/client/3rd/wireguard-tools )
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)
# Work around https://github.com/android-ndk/ndk/issues/602
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
add_executable(libwg-quick.so ${CMAKE_WG_TOOLS_DIR}/src/wg-quick/android.c ndk-compat/compat.c)
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
target_link_libraries(libwg-quick.so -ldl)
file(GLOB WG_SOURCES ${CMAKE_WG_TOOLS_DIR}/src/*.c ndk-compat/compat.c)
add_executable(libwg.so ${WG_SOURCES})
target_include_directories(libwg.so PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_WG_TOOLS_DIR}/src/uapi/linux/" "${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_WG_TOOLS_DIR}/src/")
target_compile_options(libwg.so PUBLIC -O3 -std=gnu11 -D_GNU_SOURCE -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND make
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
ANDROID_C_COMPILER=${ANDROID_C_COMPILER}
ANDROID_TOOLCHAIN_ROOT=${ANDROID_TOOLCHAIN_ROOT}
ANDROID_LLVM_TRIPLE=${ANDROID_LLVM_TRIPLE}
ANDROID_SYSROOT=${ANDROID_SYSROOT}
ANDROID_PACKAGE_NAME=${ANDROID_PACKAGE_NAME}
GRADLE_USER_HOME=${GRADLE_USER_HOME}
CFLAGS=${CMAKE_C_FLAGS}\ -Wno-unused-command-line-argument
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ -fuse-ld=gold
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
BUILDDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../generated-src
)
# Hack to make it actually build as part of the default target
add_dependencies(libwg.so libwg-go.so)

View file

@ -0,0 +1,52 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
BUILDDIR ?= $(CURDIR)/build
DESTDIR ?= $(CURDIR)/out
NDK_GO_ARCH_MAP_x86 := 386
NDK_GO_ARCH_MAP_x86_64 := amd64
NDK_GO_ARCH_MAP_arm := arm
NDK_GO_ARCH_MAP_arm64 := arm64
NDK_GO_ARCH_MAP_mips := mipsx
NDK_GO_ARCH_MAP_mips64 := mips64x
CLANG_FLAGS := --target=$(ANDROID_LLVM_TRIPLE) --gcc-toolchain=$(ANDROID_TOOLCHAIN_ROOT) --sysroot=$(ANDROID_SYSROOT)
export CGO_CFLAGS := $(CLANG_FLAGS) $(CFLAGS)
export CGO_LDFLAGS := $(CLANG_FLAGS) $(LDFLAGS) -Wl,-soname=libwg-go.so
export CC := $(ANDROID_C_COMPILER)
export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
export GOOS := android
export CGO_ENABLED := 1
GO_VERSION := 1.16
GO_PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m))
GO_TARBALL := go$(GO_VERSION).$(GO_PLATFORM).tar.gz
GO_HASH_darwin-amd64 := 6000a9522975d116bf76044967d7e69e04e982e9625330d9a539a8b45395f9a8
GO_HASH_darwin-arm64 := 4dac57c00168d30bbd02d95131d5de9ca88e04f2c5a29a404576f30ae9b54810
GO_HASH_linux-amd64 := 013a489ebb3e24ef3d915abe5b94c3286c070dfe0818d5bca8108f1d6e8440d2
default: $(DESTDIR)/libwg-go.so
$(GRADLE_USER_HOME)/caches/golang/$(GO_TARBALL):
mkdir -p "$(dir $@)"
flock "$@.lock" -c ' \
[ -f "$@" ] && exit 0; \
curl -o "$@.tmp" "https://dl.google.com/go/$(GO_TARBALL)" && \
echo "$(GO_HASH_$(GO_PLATFORM)) $@.tmp" | sha256sum -c && \
mv "$@.tmp" "$@"'
$(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_TARBALL)
mkdir -p "$(dir $@)"
flock "$@.lock" -c ' \
[ -f "$@" ] && exit 0; \
tar -C "$(dir $@)" --strip-components=1 -xzf "$^" && \
patch -p1 -f -N -r- -d "$(dir $@)" < goruntime-boottime-over-monotonic.diff && \
touch "$@"'
$(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH)
$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -trimpath -o "$@" -buildmode c-shared
.DELETE_ON_ERROR:

View file

@ -0,0 +1,227 @@
/* SPDX-License-Identifier: Apache-2.0
*
* Copyright (C) 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
package main
// #cgo LDFLAGS: -llog
// #include <android/log.h>
import "C"
import (
"fmt"
"math"
"net"
"os"
"os/signal"
"runtime"
"runtime/debug"
"strings"
"unsafe"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/ipc"
"golang.zx2c4.com/wireguard/tun"
)
type AndroidLogger struct {
level C.int
tag *C.char
}
func cstring(s string) *C.char {
b, err := unix.BytePtrFromString(s)
if err != nil {
b := [1]C.char{}
return &b[0]
}
return (*C.char)(unsafe.Pointer(b))
}
func (l AndroidLogger) Printf(format string, args ...interface{}) {
C.__android_log_write(l.level, l.tag, cstring(fmt.Sprintf(format, args...)))
}
type TunnelHandle struct {
device *device.Device
uapi net.Listener
}
var tunnelHandles map[int32]TunnelHandle
func init() {
tunnelHandles = make(map[int32]TunnelHandle)
signals := make(chan os.Signal)
signal.Notify(signals, unix.SIGUSR2)
go func() {
buf := make([]byte, os.Getpagesize())
for {
select {
case <-signals:
n := runtime.Stack(buf, true)
if n == len(buf) {
n--
}
buf[n] = 0
C.__android_log_write(C.ANDROID_LOG_ERROR, cstring("WireGuard/GoBackend/Stacktrace"), (*C.char)(unsafe.Pointer(&buf[0])))
}
}
}()
}
//export wgTurnOn
func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
tag := cstring("WireGuard/GoBackend/" + interfaceName)
logger := &device.Logger{
Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf,
}
tun, name, err := tun.CreateUnmonitoredTUNFromFD(int(tunFd))
if err != nil {
unix.Close(int(tunFd))
logger.Errorf("CreateUnmonitoredTUNFromFD: %v", err)
return -1
}
logger.Verbosef("Attaching to interface %v", name)
device := device.NewDevice(tun, conn.NewStdNetBind(), logger)
err = device.IpcSet(settings)
if err != nil {
unix.Close(int(tunFd))
logger.Errorf("IpcSet: %v", err)
return -1
}
device.DisableSomeRoamingForBrokenMobileSemantics()
var uapi net.Listener
uapiFile, err := ipc.UAPIOpen(name)
if err != nil {
logger.Errorf("UAPIOpen: %v", err)
} else {
uapi, err = ipc.UAPIListen(name, uapiFile)
if err != nil {
uapiFile.Close()
logger.Errorf("UAPIListen: %v", err)
} else {
go func() {
for {
conn, err := uapi.Accept()
if err != nil {
return
}
go device.IpcHandle(conn)
}
}()
}
}
err = device.Up()
if err != nil {
logger.Errorf("Unable to bring up device: %v", err)
uapiFile.Close()
device.Close()
return -1
}
logger.Verbosef("Device started")
var i int32
for i = 0; i < math.MaxInt32; i++ {
if _, exists := tunnelHandles[i]; !exists {
break
}
}
if i == math.MaxInt32 {
logger.Errorf("Unable to find empty handle")
uapiFile.Close()
device.Close()
return -1
}
tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi}
return i
}
//export wgTurnOff
func wgTurnOff(tunnelHandle int32) {
handle, ok := tunnelHandles[tunnelHandle]
if !ok {
return
}
delete(tunnelHandles, tunnelHandle)
if handle.uapi != nil {
handle.uapi.Close()
}
handle.device.Close()
}
//export wgGetSocketV4
func wgGetSocketV4(tunnelHandle int32) int32 {
handle, ok := tunnelHandles[tunnelHandle]
if !ok {
return -1
}
bind, _ := handle.device.Bind().(conn.PeekLookAtSocketFd)
if bind == nil {
return -1
}
fd, err := bind.PeekLookAtSocketFd4()
if err != nil {
return -1
}
return int32(fd)
}
//export wgGetSocketV6
func wgGetSocketV6(tunnelHandle int32) int32 {
handle, ok := tunnelHandles[tunnelHandle]
if !ok {
return -1
}
bind, _ := handle.device.Bind().(conn.PeekLookAtSocketFd)
if bind == nil {
return -1
}
fd, err := bind.PeekLookAtSocketFd6()
if err != nil {
return -1
}
return int32(fd)
}
//export wgGetConfig
func wgGetConfig(tunnelHandle int32) *C.char {
handle, ok := tunnelHandles[tunnelHandle]
if !ok {
return nil
}
settings, err := handle.device.IpcGet()
if err != nil {
return nil
}
return C.CString(settings)
}
//export wgVersion
func wgVersion() *C.char {
info, ok := debug.ReadBuildInfo()
if !ok {
return C.CString("unknown")
}
for _, dep := range info.Deps {
if dep.Path == "golang.zx2c4.com/wireguard" {
parts := strings.Split(dep.Version, "-")
if len(parts) == 3 && len(parts[2]) == 12 {
return C.CString(parts[2][:7])
}
return C.CString(dep.Version)
}
}
return C.CString("unknown")
}
func main() {}

View file

@ -0,0 +1,10 @@
module golang.zx2c4.com/wireguard/android
go 1.16
require (
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d // indirect
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43
golang.zx2c4.com/wireguard v0.0.0-20210222142647-219296a1e787
)

View file

@ -0,0 +1,21 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wireguard v0.0.0-20210222142647-219296a1e787 h1:zrctiUlt4hD1sgxBYrG5CAjobVhpdxnUXy+qyWWLR1w=
golang.zx2c4.com/wireguard v0.0.0-20210222142647-219296a1e787/go.mod h1:LofpIKqPJNvHiwKXuzsBshJCTe7IgRAz3iizquljFDk=

View file

@ -0,0 +1,161 @@
From b83553d9f260ba20c6faaa52e6fe6f74309eb41a Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Mon, 22 Feb 2021 02:36:03 +0100
Subject: [PATCH] runtime: use CLOCK_BOOTTIME in nanotime on Linux
This makes timers account for having expired while a computer was
asleep, which is quite common on mobile devices. Note that BOOTTIME is
identical to MONOTONIC, except that it takes into account time spent
in suspend. In Linux 4.17, the kernel will actually make MONOTONIC act
like BOOTTIME anyway, so this switch will additionally unify the
timer behavior across kernels.
BOOTTIME was introduced into Linux 2.6.39-rc1 with 70a08cca1227d in
2011.
Fixes #24595
Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
---
src/runtime/sys_linux_386.s | 4 ++--
src/runtime/sys_linux_amd64.s | 2 +-
src/runtime/sys_linux_arm.s | 4 ++--
src/runtime/sys_linux_arm64.s | 4 ++--
src/runtime/sys_linux_mips64x.s | 2 +-
src/runtime/sys_linux_mipsx.s | 2 +-
src/runtime/sys_linux_ppc64x.s | 2 +-
src/runtime/sys_linux_s390x.s | 2 +-
8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
index 1e3a834812..78b6021fc7 100644
--- a/src/runtime/sys_linux_386.s
+++ b/src/runtime/sys_linux_386.s
@@ -337,13 +337,13 @@ noswitch:
LEAL 8(SP), BX // &ts (struct timespec)
MOVL BX, 4(SP)
- MOVL $1, 0(SP) // CLOCK_MONOTONIC
+ MOVL $7, 0(SP) // CLOCK_BOOTTIME
CALL AX
JMP finish
fallback:
MOVL $SYS_clock_gettime, AX
- MOVL $1, BX // CLOCK_MONOTONIC
+ MOVL $7, BX // CLOCK_BOOTTIME
LEAL 8(SP), CX
INVOKE_SYSCALL
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
index 37cb8dad03..e8b730bcaa 100644
--- a/src/runtime/sys_linux_amd64.s
+++ b/src/runtime/sys_linux_amd64.s
@@ -302,7 +302,7 @@ noswitch:
SUBQ $16, SP // Space for results
ANDQ $~15, SP // Align for C code
- MOVL $1, DI // CLOCK_MONOTONIC
+ MOVL $7, DI // CLOCK_BOOTTIME
LEAQ 0(SP), SI
MOVQ runtime·vdsoClockgettimeSym(SB), AX
CMPQ AX, $0
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
index 475f52344c..bb567abcf4 100644
--- a/src/runtime/sys_linux_arm.s
+++ b/src/runtime/sys_linux_arm.s
@@ -11,7 +11,7 @@
#include "textflag.h"
#define CLOCK_REALTIME 0
-#define CLOCK_MONOTONIC 1
+#define CLOCK_BOOTTIME 7
// for EABI, as we don't support OABI
#define SYS_BASE 0x0
@@ -366,7 +366,7 @@ noswitch:
SUB $24, R13 // Space for results
BIC $0x7, R13 // Align for C code
- MOVW $CLOCK_MONOTONIC, R0
+ MOVW $CLOCK_BOOTTIME, R0
MOVW $8(R13), R1 // timespec
MOVW runtime·vdsoClockgettimeSym(SB), R2
CMP $0, R2
diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s
index 198a5bacef..9715387f36 100644
--- a/src/runtime/sys_linux_arm64.s
+++ b/src/runtime/sys_linux_arm64.s
@@ -13,7 +13,7 @@
#define AT_FDCWD -100
#define CLOCK_REALTIME 0
-#define CLOCK_MONOTONIC 1
+#define CLOCK_BOOTTIME 7
#define SYS_exit 93
#define SYS_read 63
@@ -319,7 +319,7 @@ noswitch:
BIC $15, R1
MOVD R1, RSP
- MOVW $CLOCK_MONOTONIC, R0
+ MOVW $CLOCK_BOOTTIME, R0
MOVD runtime·vdsoClockgettimeSym(SB), R2
CBZ R2, fallback
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
index c3e9f37694..e3879acd38 100644
--- a/src/runtime/sys_linux_mips64x.s
+++ b/src/runtime/sys_linux_mips64x.s
@@ -312,7 +312,7 @@ noswitch:
AND $~15, R1 // Align for C code
MOVV R1, R29
- MOVW $1, R4 // CLOCK_MONOTONIC
+ MOVW $7, R4 // CLOCK_BOOTTIME
MOVV $0(R29), R5
MOVV runtime·vdsoClockgettimeSym(SB), R25
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
index fab2ab3892..f9af103594 100644
--- a/src/runtime/sys_linux_mipsx.s
+++ b/src/runtime/sys_linux_mipsx.s
@@ -238,7 +238,7 @@ TEXT runtime·walltime1(SB),NOSPLIT,$8-12
RET
TEXT runtime·nanotime1(SB),NOSPLIT,$8-8
- MOVW $1, R4 // CLOCK_MONOTONIC
+ MOVW $7, R4 // CLOCK_BOOTTIME
MOVW $4(R29), R5
MOVW $SYS_clock_gettime, R2
SYSCALL
diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s
index fd69ee70a5..ff6bc8355b 100644
--- a/src/runtime/sys_linux_ppc64x.s
+++ b/src/runtime/sys_linux_ppc64x.s
@@ -249,7 +249,7 @@ fallback:
JMP finish
TEXT runtime·nanotime1(SB),NOSPLIT,$16-8
- MOVD $1, R3 // CLOCK_MONOTONIC
+ MOVD $7, R3 // CLOCK_BOOTTIME
MOVD R1, R15 // R15 is unchanged by C code
MOVD g_m(g), R21 // R21 = m
diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s
index c15a1d5364..f52c4d5098 100644
--- a/src/runtime/sys_linux_s390x.s
+++ b/src/runtime/sys_linux_s390x.s
@@ -207,7 +207,7 @@ TEXT runtime·walltime1(SB),NOSPLIT,$16
RET
TEXT runtime·nanotime1(SB),NOSPLIT,$16
- MOVW $1, R2 // CLOCK_MONOTONIC
+ MOVW $7, R2 // CLOCK_BOOTTIME
MOVD $tp-16(SP), R3
MOVW $SYS_clock_gettime, R1
SYSCALL
--
2.30.1

View file

@ -0,0 +1,70 @@
/* SPDX-License-Identifier: Apache-2.0
*
* Copyright © 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights
* Reserved.
*/
#include <jni.h>
#include <stdlib.h>
#include <string.h>
struct go_string {
const char* str;
long n;
};
extern int wgTurnOn(struct go_string ifname, int tun_fd,
struct go_string settings);
extern void wgTurnOff(int handle);
extern int wgGetSocketV4(int handle);
extern int wgGetSocketV6(int handle);
extern char* wgGetConfig(int handle);
extern char* wgVersion();
JNIEXPORT jint JNICALL Java_org_amnezia_vpn_VPNService_wgTurnOn(
JNIEnv* env, jclass c, jstring ifname, jint tun_fd, jstring settings) {
const char* ifname_str = (*env)->GetStringUTFChars(env, ifname, 0);
size_t ifname_len = (*env)->GetStringUTFLength(env, ifname);
const char* settings_str = (*env)->GetStringUTFChars(env, settings, 0);
size_t settings_len = (*env)->GetStringUTFLength(env, settings);
int ret =
wgTurnOn((struct go_string){.str = ifname_str, .n = ifname_len}, tun_fd,
(struct go_string){.str = settings_str, .n = settings_len});
(*env)->ReleaseStringUTFChars(env, ifname, ifname_str);
(*env)->ReleaseStringUTFChars(env, settings, settings_str);
return ret;
}
JNIEXPORT void JNICALL Java_org_amnezia_vpn_VPNService_wgTurnOff(
JNIEnv* env, jclass c, jint handle) {
wgTurnOff(handle);
}
JNIEXPORT jint JNICALL Java_org_amnezia_vpn_VPNService_wgGetSocketV4(
JNIEnv* env, jclass c, jint handle) {
return wgGetSocketV4(handle);
}
JNIEXPORT jint JNICALL Java_org_amnezia_vpn_VPNService_wgGetSocketV6(
JNIEnv* env, jclass c, jint handle) {
return wgGetSocketV6(handle);
}
JNIEXPORT jstring JNICALL Java_org_amnezia_vpn_VPNService_wgGetConfig(
JNIEnv* env, jclass c, jint handle) {
jstring ret;
char* config = wgGetConfig(handle);
if (!config) return NULL;
ret = (*env)->NewStringUTF(env, config);
free(config);
return ret;
}
JNIEXPORT jstring JNICALL
Java_org_amnezia_vpn_VPNService_wgVersion(JNIEnv* env, jclass c) {
jstring ret;
char* version = wgVersion();
if (!version) return NULL;
ret = (*env)->NewStringUTF(env, version);
free(version);
return ret;
}

View file

@ -0,0 +1,71 @@
/* SPDX-License-Identifier: BSD
*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
*
*/
#define FILE_IS_EMPTY
#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
# undef FILE_IS_EMPTY
# include <stdio.h>
# include <stdlib.h>
ssize_t getdelim(char** buf, size_t* bufsiz, int delimiter, FILE* fp) {
char *ptr, *eptr;
if (*buf == NULL || *bufsiz == 0) {
*bufsiz = BUFSIZ;
if ((*buf = malloc(*bufsiz)) == NULL) return -1;
}
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
int c = fgetc(fp);
if (c == -1) {
if (feof(fp)) {
ssize_t diff = (ssize_t)(ptr - *buf);
if (diff != 0) {
*ptr = '\0';
return diff;
}
}
return -1;
}
*ptr++ = c;
if (c == delimiter) {
*ptr = '\0';
return ptr - *buf;
}
if (ptr + 2 >= eptr) {
char* nbuf;
size_t nbufsiz = *bufsiz * 2;
ssize_t d = ptr - *buf;
if ((nbuf = realloc(*buf, nbufsiz)) == NULL) return -1;
*buf = nbuf;
*bufsiz = nbufsiz;
eptr = nbuf + nbufsiz;
ptr = nbuf + d;
}
}
}
ssize_t getline(char** buf, size_t* bufsiz, FILE* fp) {
return getdelim(buf, bufsiz, '\n', fp);
}
#endif
#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
# undef FILE_IS_EMPTY
# include <string.h>
char* strchrnul(const char* s, int c) {
char* x = strchr(s, c);
if (!x) return (char*)s + strlen(s);
return x;
}
#endif
#ifdef FILE_IS_EMPTY
# undef FILE_IS_EMPTY
static char ____x __attribute__((unused));
#endif

View file

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: BSD
*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
*
*/
#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
# include <stdio.h>
ssize_t getdelim(char** buf, size_t* bufsiz, int delimiter, FILE* fp);
ssize_t getline(char** buf, size_t* bufsiz, FILE* fp);
#endif
#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
char* strchrnul(const char* s, int c);
#endif

View file

@ -19,6 +19,7 @@ include ("3rd/SortFilterProxyModel/SortFilterProxyModel.pri")
HEADERS += \
../ipc/ipc.h \
configurators/cloak_configurator.h \
configurators/ikev2_configurator.h \
configurators/shadowsocks_configurator.h \
configurators/ssh_configurator.h \
configurators/vpn_configurator.h \
@ -34,6 +35,7 @@ HEADERS += \
debug.h \
defines.h \
managementserver.h \
protocols/ikev2_vpn_protocol.h \
protocols/openvpnovercloakprotocol.h \
protocols/protocols_defs.h \
protocols/shadowsocksvpnprotocol.h \
@ -73,6 +75,7 @@ HEADERS += \
SOURCES += \
configurators/cloak_configurator.cpp \
configurators/ikev2_configurator.cpp \
configurators/shadowsocks_configurator.cpp \
configurators/ssh_configurator.cpp \
configurators/vpn_configurator.cpp \
@ -87,6 +90,7 @@ SOURCES += \
debug.cpp \
main.cpp \
managementserver.cpp \
protocols/ikev2_vpn_protocol.cpp \
protocols/openvpnovercloakprotocol.cpp \
protocols/protocols_defs.cpp \
protocols/shadowsocksvpnprotocol.cpp \
@ -122,9 +126,6 @@ SOURCES += \
protocols/vpnprotocol.cpp \
protocols/openvpnprotocol.cpp \
FORMS += \
ui/server_widget.ui
RESOURCES += \
resources.qrc
@ -174,10 +175,37 @@ macx {
LIBS += -framework Cocoa -framework ApplicationServices -framework CoreServices -framework Foundation -framework AppKit -framework Security
}
android {
QT += androidextras
INCLUDEPATH += platforms/android
HEADERS += protocols/android_vpnprotocol.h \
SOURCES += protocols/android_vpnprotocol.cpp \
DISTFILES += \
android/AndroidManifest.xml \
android/build.gradle \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
for (abi, ANDROID_ABIS): {
ANDROID_EXTRA_LIBS += $$PWD/android/lib/wireguard/$${abi}/libwg.so
ANDROID_EXTRA_LIBS += $$PWD/android/lib/wireguard/$${abi}/libwg-go.so
ANDROID_EXTRA_LIBS += $$PWD/android/lib/wireguard/$${abi}/libwg-quick.so
}
}
REPC_REPLICA += ../ipc/ipc_interface.rep
!ios: REPC_REPLICA += ../ipc/ipc_process_interface.rep
DISTFILES += \
android/AndroidManifest.xml
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android

View file

@ -0,0 +1,66 @@
#include "ikev2_configurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QDebug>
#include <QTemporaryFile>
#include <QJsonDocument>
#include "sftpdefs.h"
#include "core/server_defs.h"
#include "containers/containers_defs.h"
#include "core/scripts_registry.h"
#include "utils.h"
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode)
{
Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName;
connData.clientId = Utils::getRandomString(16);
connData.password = Utils::getRandomString(16);
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\
"-S -c \"IKEv2 VPN CA\" -n \"%1\" "\
"-s \"O=IKEv2 VPN,CN=%1\" "\
"-k rsa -g 3072 -v 120 "\
"-d sql:/etc/ipsec.d -t \",,\" "\
"--keyUsage digitalSignature,keyEncipherment "\
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId);
ErrorCode e = ServerController::runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
.arg(connData.password)
.arg(connData.clientId)
.arg(certFileName);
e = ServerController::runContainerScript(credentials, container, scriptExportCert);
connData.cert = ServerController::getTextFileFromContainer(container, credentials, certFileName, &e);
qDebug() << "Ikev2Configurator::ConnectionData cert size:" << connData.cert.size();
return connData;
}
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode && *errorCode) {
return "";
}
QJsonObject config;
config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId;
config[config_key::cert] = QString(connData.cert.toBase64());
config[config_key::password] = connData.password;
return QJsonDocument(config).toJson();
}

View file

@ -0,0 +1,30 @@
#ifndef IKEV2_CONFIGURATOR_H
#define IKEV2_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "core/defs.h"
#include "core/servercontroller.h"
class Ikev2Configurator
{
public:
struct ConnectionData {
QByteArray cert; // p12 client cert
QString clientId;
QString password; // certificate password
QString host; // host ip
};
static QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
private:
static ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode = nullptr);
};
#endif // IKEV2_CONFIGURATOR_H

View file

@ -5,6 +5,7 @@
#include <QTemporaryDir>
#include <QDebug>
#include <QTemporaryFile>
#include <QJsonObject>
#include "core/server_defs.h"
#include "containers/containers_defs.h"
@ -242,16 +243,14 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
config.replace("block-outside-dns", "");
#endif
//qDebug().noquote() << config;
return config;
QJsonObject jConfig;
jConfig[config_key::config] = config;
return QJsonDocument(jConfig).toJson();
}
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config)
{
// TODO replace DNS if it already set
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (m_settings().routeMode() != Settings::VpnAllSites) {
config.replace("redirect-gateway def1 bypass-dhcp", "");
}
@ -277,9 +276,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config)
QString OpenVpnConfigurator::processConfigWithExportSettings(QString config)
{
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if(!config.contains("redirect-gateway def1 bypass-dhcp")) {
config.append("redirect-gateway def1 bypass-dhcp\n");
}
@ -308,5 +304,5 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
QStringList scriptList {script_import, script_sign};
QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container));
return ServerController::runScript(ServerController::sshParams(credentials), script);
return ServerController::runScript(credentials, script);
}

View file

@ -2,7 +2,8 @@
#include "openvpn_configurator.h"
#include "cloak_configurator.h"
#include "shadowsocks_configurator.h"
//#include "wireguard_configurator.h"
#include "wireguard_configurator.h"
#include "ikev2_configurator.h"
#include <QFile>
#include <QJsonObject>
@ -10,6 +11,11 @@
#include "containers/containers_defs.h"
Settings &VpnConfigurator::m_settings()
{
static Settings s;
return s;
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode)
@ -24,14 +30,39 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
case Protocol::Cloak:
return CloakConfigurator::genCloakConfig(credentials, container, containerConfig, errorCode);
// case Protocol::WireGuard:
// return WireGuardConfigurator::genWireGuardConfig(credentials, container, containerConfig, errorCode);
case Protocol::WireGuard:
return WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
case Protocol::Ikev2:
return Ikev2Configurator::genIkev2Config(credentials, container, containerConfig, errorCode);
default:
return "";
}
}
QString VpnConfigurator::processConfigWithLocalSettings(DockerContainer container, Protocol proto, QString config)
{
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (proto == Protocol::OpenVpn) {
return OpenVpnConfigurator::processConfigWithLocalSettings(config);
}
return config;
}
QString VpnConfigurator::processConfigWithExportSettings(DockerContainer container, Protocol proto, QString config)
{
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (proto == Protocol::OpenVpn) {
return OpenVpnConfigurator::processConfigWithExportSettings(config);
}
return config;
}
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut)
{

View file

@ -7,6 +7,7 @@
#include "settings.h"
#include "core/servercontroller.h"
// Retrieve connection settings from server
class VpnConfigurator
{
public:
@ -14,9 +15,14 @@ public:
static QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode = nullptr);
static QString processConfigWithLocalSettings(DockerContainer container, Protocol proto, QString config);
static QString processConfigWithExportSettings(DockerContainer container, Protocol proto, QString config);
// workaround for containers which is not support normal configaration
static void updateContainerConfigAfterInstallation(DockerContainer container,
QJsonObject &containerConfig, const QString &stdOut);
static Settings &m_settings();
};
#endif // VPN_CONFIGURATOR_H

Some files were not shown because too many files have changed in this diff Show more