Merge branch 'dev' into wireguard_embedded
42
.gitignore
vendored
|
@ -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
|
@ -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/
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
TEMPLATE = subdirs
|
||||
SUBDIRS = client service platform
|
||||
SUBDIRS = client
|
||||
|
||||
!ios:!android {
|
||||
SUBDIRS += service platform
|
||||
}
|
||||
|
|
1
client/3rd/wireguard-tools
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 7a321ce808ef9cec1f45cce92befcc9e170d3aa9
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
client/android/lib/wireguard/arm64-v8a/libwg-go.so
Normal file
BIN
client/android/lib/wireguard/arm64-v8a/libwg-quick.so
Normal file
BIN
client/android/lib/wireguard/arm64-v8a/libwg.so
Normal file
BIN
client/android/lib/wireguard/armeabi-v7a/libwg-go.so
Normal file
BIN
client/android/lib/wireguard/armeabi-v7a/libwg-quick.so
Normal file
BIN
client/android/lib/wireguard/armeabi-v7a/libwg.so
Normal file
BIN
client/android/lib/wireguard/x86/libwg-go.so
Normal file
BIN
client/android/lib/wireguard/x86/libwg-quick.so
Normal file
BIN
client/android/lib/wireguard/x86/libwg.so
Normal file
BIN
client/android/lib/wireguard/x86_64/libwg-go.so
Normal file
BIN
client/android/lib/wireguard/x86_64/libwg-quick.so
Normal file
BIN
client/android/lib/wireguard/x86_64/libwg.so
Normal file
11
client/android/res/drawable/ic_amnezia_round.xml
Normal 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>
|
15
client/android/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
10
client/android/res/drawable/ic_logo_on.xml
Normal 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>
|
4
client/android/res/drawable/splash_background.xml
Normal 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>
|
5
client/android/res/mipmap-anydpi-v26/vpnicon.xml
Normal 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>
|
5
client/android/res/mipmap-anydpi-v26/vpnicon_round.xml
Normal 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>
|
BIN
client/android/res/mipmap-hdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
client/android/res/mipmap-hdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
client/android/res/mipmap-hdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
client/android/res/mipmap-mdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 1,009 B |
BIN
client/android/res/mipmap-mdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
client/android/res/mipmap-mdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
client/android/res/mipmap-xhdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
client/android/res/mipmap-xhdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
client/android/res/mipmap-xhdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
client/android/res/mipmap-xxhdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
client/android/res/mipmap-xxhdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
client/android/res/mipmap-xxhdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 7 KiB |
BIN
client/android/res/mipmap-xxxhdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
client/android/res/mipmap-xxxhdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
client/android/res/mipmap-xxxhdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
10
client/android/res/values/style.xml
Normal 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>
|
4
client/android/res/values/vpnicon_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="vpnicon_background">#000000</color>
|
||||
</resources>
|
|
@ -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);
|
||||
}
|
||||
}
|
57
client/android/src/com/wireguard/config/Attribute.java
Normal 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;
|
||||
}
|
||||
}
|
116
client/android/src/com/wireguard/config/BadConfigException.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
218
client/android/src/com/wireguard/config/Config.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
73
client/android/src/com/wireguard/config/InetAddresses.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
client/android/src/com/wireguard/config/InetEndpoint.java
Normal 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;
|
||||
}
|
||||
}
|
77
client/android/src/com/wireguard/config/InetNetwork.java
Normal 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;
|
||||
}
|
||||
}
|
394
client/android/src/com/wireguard/config/Interface.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
46
client/android/src/com/wireguard/config/ParseException.java
Normal 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;
|
||||
}
|
||||
}
|
306
client/android/src/com/wireguard/config/Peer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
497
client/android/src/com/wireguard/crypto/Curve25519.java
Normal 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);
|
||||
}
|
||||
}
|
2435
client/android/src/com/wireguard/crypto/Ed25519.java
Normal file
283
client/android/src/com/wireguard/crypto/Key.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
52
client/android/src/com/wireguard/crypto/KeyPair.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
BIN
client/android/src/debug/res/mipmap-hdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
client/android/src/debug/res/mipmap-mdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
client/android/src/debug/res/mipmap-xhdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 7.5 KiB |
BIN
client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 12 KiB |
BIN
client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="vpnicon_background">#000000</color>
|
||||
</resources>
|
115
client/android/src/org/amnezia/vpn/NotificationUtil.kt
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
35
client/android/src/org/amnezia/vpn/Prefs.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
72
client/android/src/org/amnezia/vpn/VPNLogger.kt
Normal 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")
|
||||
}
|
||||
}
|
309
client/android/src/org/amnezia/vpn/VPNService.kt
Normal 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?
|
||||
}
|
||||
}
|
170
client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
Normal 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
|
||||
}
|
||||
}
|
189
client/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java
Normal 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);
|
||||
}
|
||||
}
|
33
client/android/src/org/amnezia/vpn/qt/VPNActivity.java
Normal 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();
|
||||
}
|
18
client/android/src/org/amnezia/vpn/qt/VPNApplication.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
37
client/android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt
Normal 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)
|
||||
}
|
44
client/android/wireguard/CMakeLists.txt
Normal 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)
|
52
client/android/wireguard/libwg-go/Makefile
Normal 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:
|
227
client/android/wireguard/libwg-go/api-android.go
Normal 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() {}
|
10
client/android/wireguard/libwg-go/go.mod
Normal 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
|
||||
)
|
21
client/android/wireguard/libwg-go/go.sum
Normal 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=
|
|
@ -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
|
||||
|
70
client/android/wireguard/libwg-go/jni.c
Normal 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;
|
||||
}
|
71
client/android/wireguard/ndk-compat/compat.c
Normal 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
|
15
client/android/wireguard/ndk-compat/compat.h
Normal 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
|
|
@ -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
|
||||
|
|
66
client/configurators/ikev2_configurator.cpp
Normal 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();
|
||||
}
|
||||
|
30
client/configurators/ikev2_configurator.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|