From 5f32be504c82e2f68ce83ebf10d728fe3772f7f8 Mon Sep 17 00:00:00 2001 From: isayan Date: Thu, 24 Jan 2019 06:51:30 +0900 Subject: [PATCH 01/38] support Allow/Disallow Application --- .gitignore | 37 -- android_app/.gitignore | 7 + android_app/Readme.md | 67 +++ android_app/androidkeystore.jks | Bin 0 -> 2120 bytes android_app/app/.gitignore | 2 + android_app/app/CMakeLists.txt | 46 +- android_app/app/build.gradle | 52 +- .../tun2http/app/ExampleInstrumentedTest.java | 26 - android_app/app/src/main/AndroidManifest.xml | 35 +- android_app/app/src/main/cpp/http.c | 20 + android_app/app/src/main/cpp/tun2http.c | 12 +- android_app/app/src/main/cpp/tun2http.h | 1 + android_app/app/src/main/cpp/tun2http.h.bak | 491 ++++++++++++++++++ .../app => tun/proxy}/MainActivity.java | 129 ++++- .../main/java/tun/proxy/MyApplication.java | 61 +++ .../tun/proxy/SimplePreferenceActivity.java | 113 ++++ .../tun/proxy/SimplePreferenceFragment.java | 204 ++++++++ .../proxy}/receiver/BootReceiver.java | 11 +- .../proxy}/service/Tun2HttpVpnService.java | 146 +++--- .../main/java/tun/utils/CertificateUtil.java | 214 ++++++++ .../tun2http/app => tun}/utils/IPUtil.java | 2 +- .../src/main/java/tun/utils/PackageUtil.java | 42 ++ .../{com/tun2http/app => tun}/utils/Util.java | 2 +- .../drawable-v24/ic_launcher_foreground.xml | 22 +- .../main/res/drawable/ic_info_black_24dp.xml | 9 + .../res/drawable/ic_launcher_background.xml | 139 +++-- .../drawable/ic_notifications_black_24dp.xml | 9 + .../main/res/drawable/ic_sync_black_24dp.xml | 9 + .../app/src/main/res/layout/activity_main.xml | 48 +- .../app/src/main/res/layout/content_main.xml | 42 ++ .../app/src/main/res/menu/menu_main.xml | 15 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 4 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 4 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3056 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5024 -> 4905 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2096 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2858 -> 2783 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4569 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7098 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6464 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10676 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9250 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15523 -> 15132 bytes .../app/src/main/res/values/colors.xml | 6 +- .../app/src/main/res/values/dimens.xml | 3 + .../app/src/main/res/values/strings.xml | 58 ++- .../app/src/main/res/values/styles.xml | 9 + .../main/res/xml/network_security_config.xml | 9 + .../app/src/main/res/xml/preferences.xml | 61 +++ .../com/tun2http/app/ExampleUnitTest.java | 17 - android_app/build.gradle | 28 +- android_app/gradle.properties | 12 +- android_app/gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +- android_app/gradlew | 72 +-- android_app/gradlew.bat | 14 +- android_app/images/Menu-Settings-sub.png | Bin 0 -> 62091 bytes android_app/images/Menu-Settings.png | Bin 0 -> 31653 bytes android_app/images/Menu.png | Bin 0 -> 598 bytes android_app/images/TunProxy.png | Bin 0 -> 17742 bytes 60 files changed, 1910 insertions(+), 403 deletions(-) delete mode 100644 .gitignore create mode 100644 android_app/.gitignore create mode 100644 android_app/Readme.md create mode 100644 android_app/androidkeystore.jks delete mode 100644 android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java create mode 100644 android_app/app/src/main/cpp/tun2http.h.bak rename android_app/app/src/main/java/{com/tun2http/app => tun/proxy}/MainActivity.java (55%) create mode 100644 android_app/app/src/main/java/tun/proxy/MyApplication.java create mode 100644 android_app/app/src/main/java/tun/proxy/SimplePreferenceActivity.java create mode 100644 android_app/app/src/main/java/tun/proxy/SimplePreferenceFragment.java rename android_app/app/src/main/java/{com/tun2http/app => tun/proxy}/receiver/BootReceiver.java (78%) rename android_app/app/src/main/java/{com/tun2http/app => tun/proxy}/service/Tun2HttpVpnService.java (87%) create mode 100644 android_app/app/src/main/java/tun/utils/CertificateUtil.java rename android_app/app/src/main/java/{com/tun2http/app => tun}/utils/IPUtil.java (99%) create mode 100644 android_app/app/src/main/java/tun/utils/PackageUtil.java rename android_app/app/src/main/java/{com/tun2http/app => tun}/utils/Util.java (98%) create mode 100644 android_app/app/src/main/res/drawable/ic_info_black_24dp.xml create mode 100644 android_app/app/src/main/res/drawable/ic_notifications_black_24dp.xml create mode 100644 android_app/app/src/main/res/drawable/ic_sync_black_24dp.xml create mode 100644 android_app/app/src/main/res/layout/content_main.xml create mode 100644 android_app/app/src/main/res/menu/menu_main.xml create mode 100644 android_app/app/src/main/res/values/dimens.xml create mode 100644 android_app/app/src/main/res/xml/network_security_config.xml create mode 100644 android_app/app/src/main/res/xml/preferences.xml delete mode 100644 android_app/app/src/test/java/com/tun2http/app/ExampleUnitTest.java create mode 100644 android_app/images/Menu-Settings-sub.png create mode 100644 android_app/images/Menu-Settings.png create mode 100644 android_app/images/Menu.png create mode 100644 android_app/images/TunProxy.png diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a307a61..0000000 --- a/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# Built application files -/*/build/ - -# Crashlytics configuations -com_crashlytics_export_strings.xml - -# Local configuration file (sdk path, etc) -local.properties - -# Gradle generated files -.gradle/ - -# Signing files -.signing/ - -# User-specific configurations -.idea/libraries/ -.idea/workspace.xml -.idea/tasks.xml -.idea/.name -.idea/compiler.xml -.idea/copyright/profiles_settings.xml -.idea/encodings.xml -.idea/misc.xml -.idea/modules.xml -.idea/scopes/scope_settings.xml -.idea/vcs.xml -*.iml - -# OS-specific files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db \ No newline at end of file diff --git a/android_app/.gitignore b/android_app/.gitignore new file mode 100644 index 0000000..cbc9ec9 --- /dev/null +++ b/android_app/.gitignore @@ -0,0 +1,7 @@ +*.iml +.gradle +.idea +.DS_Store +local.properties +/build +*.zip diff --git a/android_app/Readme.md b/android_app/Readme.md new file mode 100644 index 0000000..14506bd --- /dev/null +++ b/android_app/Readme.md @@ -0,0 +1,67 @@ +Android 通信 Proxy 設定ツール +============= +このツールは、Android の VPNService 機能を利用した Proxy 設定ツールです。 +指定したアプリからの通信のみを取得することが可能です。 + +Android 7.0 以降において、アプリはデフォルトではユーザ証明書を信頼しなくなっています。 + +* https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html + +## 使用方法 + +ユーザ証明書領域に信頼させたい Root CA がない場合はインストールします。 + +TunProxyアプリを起動すると以下の画面が起動します。 + +![Tun Proxy](images/TunProxy.png) + +* Proxy address (host:port) + * 接続先のプロキシサーバを ** IPアドレス:ポート番号 ** の形式で指定します。 + IPアドレスはIPv4形式で記載する必要があります。 + +* [Start] ボタン + * 接続を開始します。 +* [Stop] ボタン + * 接続を停止します。 + +## メニュー + +画面上部のメニューアイコン(![Menu](images/Menu.png))からアプリケーションの設定ができます。 + +### Settings + +VPNの接続設定を行います。 + +![Menu Settings](images/Menu-Settings.png) ⇒ ![Menu Settings](images/Menu-Settings-sub.png) + +Disallow Application と Allow Application の2つのモードがありますが、同時に指定することはできません。 +このためどちらのモードで動作させたいかを選択する必要があります。 +デフォルトは * Disallow Application * が選択された状態です。 + +* Disallow Application + * VPN通信から除外したいアプリを選択する。 + 選択したアプリはVPN通信を経由されなくなり、VPNを利用しない場合と同じ挙動となります。 + +* Allow Application + * VPN通信を行いたいアプリを選択する。 + 選択したアプリはVPN通信を経由するようになります。 + 選択されていないアプリは、VPNを利用しない場合と同じ挙動になります。 + なお、一つも選択されていない場合は、すべてのアプリの通信がVPNを経由します。 + +### About +アプリケーションバージョンを表示します。 + +## 動作環境 + +* Android 5.0 (API Level 21) 以降 + +## 謝辞 + +アプリ作成にあたりコードの大部分は以下のアプリをベースとして作成しました。 + +* https://github.com/MengAndy/tun2http/ + +## 開発環境 + +* JRE(JDK) 1.8以上(Oracle JRE) (http://www.oracle.com/technetwork/java/javase/downloads/index.html) +* AndroidStudio 3.2.1 (https://developer.android.com/studio/index.html) diff --git a/android_app/androidkeystore.jks b/android_app/androidkeystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..fffb1ceb96a280add18ca96f553bb1782ba776d0 GIT binary patch literal 2120 zcmbW1X*iS%8^`CFCEM7NvSb;CEWJE~AsIW_iVEXcqU=i{5ktlpdkHn!DU7|7CD}fE*`SWlsQbKnBVU00;=23}2*S#!IxdIlNuG-bknkd1DTHG8NCfI!KXA zt1WFSmpN$*KxS(9R31vj}VDB%sR$Y-v#KBoEC zE%gH=JukAoHf+m6T$`S&9;sTNDes(de!jdNK`qTgt&?<8&^k}3zI)lV5!#asu%>MD zM;o3RIl``W@s{PJG;t-gEAeA#RZ?z2(gEM?1{qiAxs23jkygQkx-=8Yr{U;|f;tf& zmpoG7L`twi+}yqjBCPT!L$UY{8>uO}(aGt&_wKdCedG7201W50j@go|+?O_sSp)py zVQ#SN5_t*?a~IYl1$Qg2yD^#C2c}yrpIU2Yq@L~Z>&g}nr~1rnxwbD#)%q4noDVUj zbV*i5P6UcP(sBuS7fv;M5@JehAHBILN5$ZtL^aF#zh6%#Z_m>=SxZ~3R+fgEM0nFm z4pnki9wv_`yVN?Ltb(zPM#BBoOg>6H$lp!sKlhgFXQ-8~-Uzo)nLtR;usA{O3ICR4 zZ~j){#V`7B;aHiQ=yyIuoU|e_0NH=vF5V3 zPY>3!DM%yL>(PLkk9lCOe75vqA;~Yzl-H$_*eWXGgX3Ej7Ei*-`iZsQm9mXbO{vyR zl*aCH6+X<0=^Nf*T7AA&!EF(;>|q=bm2ggZS>q8(W?6i}3?6IAtfh196?#bLg{ox1(q|3c}U(HB=HX-@OO@jP3R-StJl`f zO0gXgz0GgZtBIU(r!3pDP0dW5LWyUTYmlmaQNEtTP^g+!ykW$t znobwF3YAKEe7e((MZQ6;;ME}@|J5DX`8ulTrw&7zpbmr~#g1g=E#z3NpfsODi3(2g z`%X(e@)T1UPUiRLwCxK;;hZdZkdKi;{rW!=f*VxsrP(G3LXIE7Qs}Q_%~OB<(lDcT zCd$$U6IFHN#J^G&IrhM>P5}sSkLj;ryJc2-g839>D`U-^z8i0*re&yG1=$*YZHN}u#t6}K$c;F`~?!-j>Qe!v;o9n|lD(YH+PaV@WA zaYp~#D3PDSU%4TK+>@L)9?d{=a04c-JDs6o+MR0yw22P31B)xf`J$(K5$xEI59k%# zw>o5F9a5&nJb3evIQWC;{AEM73iZi5(r$+X#~?U^I7DF#7q79ys+{LAsg}u<_0Jw5ovUH)E#e8097)fk93HsG z`Ni=6@)fYnc=*Hav+pj+w&vYUwV>Di5m-?*f{oiDW+vu4>*Ty(3HRHxC_N*(`r65h zB4O9+4;7{yX4X4|&(UkL0*>z7>bz`kp^k58ROc7^MZvD8)#B}4+>!Szymbvt}8iuwG3*cYC2|TBrzvW~? zq`P-E1Z9rhR%nphuWx2wiql~Z@w?Eyh23ahDug4k)|3BO5%Gr{7F2~mU@(vjJp#$V zLJkZHK%sE>z#UhR{RE3lv|;ovI{>g93lqe!fh;FrmhcliP~)o@4iNb_F!LZRe7p!g z7;cdBZ-((O`v-XY`uPMAK;bj&mbPU z!^i;VvCIKhC>a1C-9@?7Jv8%y#b6MfHw_6sF8 zl3fxaCggj&?X=dcQ({9T-mT)Cv_5Sxiqjwtk+X3T}8XOA|ZwW1$2N!xE$*1?<{wH4w07CqBS%4^NG z(XSJ~mSdqX00K}^pa>{*T!s(_{51Sjjmk@ER!8fezM_FWl>V(n-v&$lcZkQ`9)}1( zt~-wv8?sa(>fJroa(dPA=7TGo3mQc(TzR}WO;e3XOdIT8BkoZ{QTw#6`Dc_NSH1D2 zj|Hk;*KyeoQ;P7j0x3=vc?LgmA%Z6Rjweab43-Jn%+7tO*38)j_l%gdr@s&?(D$YB zgI-Y~dRt6e{3~WtjLa819?#SyOBXVe$8)u}e(8N=`%DFWm1*$kTj^$dBSTEHrJIbr zXq2Wgh8`+!hvo_v*57@F$JV2^N*$;)0o}rFHMe|%f<`Q6Kt^GyFG TRz01anIT(_eCFK4XAJ)@M7+N7 literal 0 HcmV?d00001 diff --git a/android_app/app/.gitignore b/android_app/app/.gitignore index 796b96d..085783c 100644 --- a/android_app/app/.gitignore +++ b/android_app/app/.gitignore @@ -1 +1,3 @@ /build +/release +.externalNativeBuild diff --git a/android_app/app/CMakeLists.txt b/android_app/app/CMakeLists.txt index 8f126c2..b495d53 100644 --- a/android_app/app/CMakeLists.txt +++ b/android_app/app/CMakeLists.txt @@ -1,35 +1,35 @@ cmake_minimum_required(VERSION 3.4.1) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp) -set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/${ANDROID_ABI}") +set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/${ANDROID_ABI}") add_library( # Sets the name of the library. - tun2http + tun2http - # Sets the library as a shared library. - SHARED + # Sets the library as a shared library. + SHARED - src/main/cpp/dhcp.c - src/main/cpp/dns.c - src/main/cpp/icmp.c - src/main/cpp/ip.c - src/main/cpp/http.c - src/main/cpp/tun2http.c - src/main/cpp/session.c - src/main/cpp/tcp.c - src/main/cpp/tls.c - src/main/cpp/udp.c - src/main/cpp/util.c -) + src/main/cpp/dhcp.c + src/main/cpp/dns.c + src/main/cpp/icmp.c + src/main/cpp/ip.c + src/main/cpp/http.c + src/main/cpp/tun2http.c + src/main/cpp/session.c + src/main/cpp/tcp.c + src/main/cpp/tls.c + src/main/cpp/udp.c + src/main/cpp/util.c + ) find_library( # Sets the name of the path variable. - log-lib + log-lib - # Specifies the name of the NDK library that - # you want CMake to locate. - log ) + # Specifies the name of the NDK library that + # you want CMake to locate. + log) target_link_libraries( # Specifies the target library. - tun2http - ${log-lib} - ) \ No newline at end of file + tun2http + ${log-lib} + ) \ No newline at end of file diff --git a/android_app/app/build.gradle b/android_app/app/build.gradle index 46bd3d7..e0fc71c 100644 --- a/android_app/app/build.gradle +++ b/android_app/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 26 + compileSdkVersion 27 defaultConfig { - applicationId "com.tun2http.app" - minSdkVersion 19 - targetSdkVersion 26 - versionCode 2 - versionName '1.01' + applicationId "tun.proxy" + minSdkVersion 21 + targetSdkVersion 27 + versionCode 100007 + versionName VERSION_NAME testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -16,23 +16,57 @@ android { arguments "-DCMAKE_VERBOSE_MAKEFILE=1 -DANDROID_FUNCTION_LEVEL_LINKING=ON" } } + vectorDrawables.useSupportLibrary = true + } + signingConfigs { + debug { + } + release { + // @See gradle.properties + storeFile file(productKeyStore) + keyAlias productKeyAlias + storePassword productKeyStorePassword + keyPassword productKeyAliasPassword + } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release } } + lintOptions { + abortOnError false + } externalNativeBuild { cmake { path "CMakeLists.txt" } } - productFlavors { + applicationVariants.all { variant -> + if (variant.buildType.name.equals("release")) { + variant.outputs.each { output -> + if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) { + def list = defaultConfig.applicationId.split("\\.") + def appname = list[list.length - 1] + def versionName = defaultConfig.versionName +// output.outputFile = new File(output.outputFile.parent, "${appname}_v${versionName}.apk") + } + } + } } } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:26.1.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:27.1.1' + implementation 'com.android.support:support-v4:27.1.1' + implementation 'com.android.support:support-vector-drawable:27.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } + diff --git a/android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java b/android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java deleted file mode 100644 index 9de396e..0000000 --- a/android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.tun2http.app; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.tun2http.app", appContext.getPackageName()); - } -} diff --git a/android_app/app/src/main/AndroidManifest.xml b/android_app/app/src/main/AndroidManifest.xml index dbef8e4..a190721 100644 --- a/android_app/app/src/main/AndroidManifest.xml +++ b/android_app/app/src/main/AndroidManifest.xml @@ -1,33 +1,38 @@ + package="tun.proxy"> - - - + + + + - - + - - - + + - + + @@ -35,6 +40,16 @@ + + + + + \ No newline at end of file diff --git a/android_app/app/src/main/cpp/http.c b/android_app/app/src/main/cpp/http.c index 08111d7..f472eca 100644 --- a/android_app/app/src/main/cpp/http.c +++ b/android_app/app/src/main/cpp/http.c @@ -153,6 +153,26 @@ uint8_t *patch_http_url(uint8_t *data, size_t *data_len) { word = "OPTIONS "; } else if (pos = find_data(data, *data_len, "PATCH ")) { word = "PATCH "; + } else if (pos = find_data(data, *data_len, "HEAD ")) { + word = "HEAD "; + } else if (pos = find_data(data, *data_len, "TRACE ")) { + word = "TRACE "; + } else if (pos = find_data(data, *data_len, "PROPFIND ")) { + word = "PROPFIND "; + } else if (pos = find_data(data, *data_len, "MKCOL ")) { + word = "MKCOL "; + } else if (pos = find_data(data, *data_len, "COPY ")) { + word = "COPY "; + } else if (pos = find_data(data, *data_len, "MOVE ")) { + word = "MOVE "; + } else if (pos = find_data(data, *data_len, "LOCK ")) { + word = "LOCK "; + } else if (pos = find_data(data, *data_len, "UNLOCK ")) { + word = "UNLOCK "; + } else if (pos = find_data(data, *data_len, "LINK ")) { + word = "LINK "; + } else if (pos = find_data(data, *data_len, "UNLINK ")) { + word = "UNLINK "; } if (!pos) { diff --git a/android_app/app/src/main/cpp/tun2http.c b/android_app/app/src/main/cpp/tun2http.c index 9c50d5e..4c8d701 100644 --- a/android_app/app/src/main/cpp/tun2http.c +++ b/android_app/app/src/main/cpp/tun2http.c @@ -48,7 +48,7 @@ void JNI_OnUnload(JavaVM *vm, void *reserved) { // JNI ServiceSinkhole JNIEXPORT void JNICALL -Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1init(JNIEnv *env, jobject instance) { +Java_tun_proxy_service_Tun2HttpVpnService_jni_1init(JNIEnv *env, jobject instance) { loglevel = ANDROID_LOG_WARN; struct arguments args; @@ -72,7 +72,7 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1init(JNIEnv *env, jobject } JNIEXPORT void JNICALL -Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1start( +Java_tun_proxy_service_Tun2HttpVpnService_jni_1start( JNIEnv *env, jobject instance, jint tun, jboolean fwd53, jint rcode, jstring proxyIp, jint proxyPort) { const char *proxy_ip = (*env)->GetStringUTFChars(env, proxyIp, 0); @@ -117,7 +117,7 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1start( } JNIEXPORT void JNICALL -Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1stop( +Java_tun_proxy_service_Tun2HttpVpnService_jni_1stop( JNIEnv *env, jobject instance, jint tun) { pthread_t t = thread_id; log_android(ANDROID_LOG_WARN, "Stop tun %d thread %x", tun, t); @@ -140,13 +140,13 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1stop( } JNIEXPORT jint JNICALL -Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1get_1mtu(JNIEnv *env, jobject instance) { +Java_tun_proxy_service_Tun2HttpVpnService_jni_1get_1mtu(JNIEnv *env, jobject instance) { return get_mtu(); } JNIEXPORT void JNICALL -Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1done(JNIEnv *env, jobject instance) { +Java_tun_proxy_service_Tun2HttpVpnService_jni_1done(JNIEnv *env, jobject instance) { log_android(ANDROID_LOG_INFO, "Done"); clear(); @@ -162,7 +162,7 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1done(JNIEnv *env, jobject // JNI Util JNIEXPORT jstring JNICALL -Java_com_tun2http_app_utils_Util_jni_1getprop(JNIEnv *env, jclass type, jstring name_) { +Java_tun_utils_Util_jni_1getprop(JNIEnv *env, jclass type, jstring name_) { const char *name = (*env)->GetStringUTFChars(env, name_, 0); char value[PROP_VALUE_MAX + 1] = ""; diff --git a/android_app/app/src/main/cpp/tun2http.h b/android_app/app/src/main/cpp/tun2http.h index 129b12e..0fd3342 100644 --- a/android_app/app/src/main/cpp/tun2http.h +++ b/android_app/app/src/main/cpp/tun2http.h @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include diff --git a/android_app/app/src/main/cpp/tun2http.h.bak b/android_app/app/src/main/cpp/tun2http.h.bak new file mode 100644 index 0000000..129b12e --- /dev/null +++ b/android_app/app/src/main/cpp/tun2http.h.bak @@ -0,0 +1,491 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define EPOLL_TIMEOUT 3600 // seconds +#define EPOLL_EVENTS 20 +#define EPOLL_MIN_CHECK 100 // milliseconds + +#define ICMP4_MAXMSG (IP_MAXPACKET - 20 - 8) // bytes (socket) +#define ICMP6_MAXMSG (IPV6_MAXPACKET - 40 - 8) // bytes (socket) +#define UDP4_MAXMSG (IP_MAXPACKET - 20 - 8) // bytes (socket) +#define UDP6_MAXMSG (IPV6_MAXPACKET - 40 - 8) // bytes (socket) + +#define ICMP_TIMEOUT 15 // seconds + +#define UDP_TIMEOUT_53 15 // seconds +#define UDP_TIMEOUT_ANY 300 // seconds +#define UDP_KEEP_TIMEOUT 60 // seconds + +#define TCP_INIT_TIMEOUT 30 // seconds ~net.inet.tcp.keepinit +#define TCP_IDLE_TIMEOUT 3600 // seconds ~net.inet.tcp.keepidle +#define TCP_CLOSE_TIMEOUT 30 // seconds +#define TCP_KEEP_TIMEOUT 300 // seconds +// https://en.wikipedia.org/wiki/Maximum_segment_lifetime + +#define SESSION_LIMIT 40 // percent + +#define TCP_CONNECT_NOT_SENT -1 +#define TCP_CONNECT_SENT 0 +#define TCP_CONNECT_ESTABLISHED 1 + +#define MTU 10000 + +struct arguments { + JNIEnv *env; + jobject instance; + int tun; + jboolean fwd53; + jint rcode; + char proxyIp[128]; + int proxyPort; +}; + +struct allowed { + char raddr[INET6_ADDRSTRLEN + 1]; + uint16_t rport; // host notation +}; + +struct segment { + uint32_t seq; + uint16_t len; + uint16_t sent; + int psh; + uint8_t *data; + struct segment *next; +}; + +struct icmp_session { + time_t time; + jint uid; + int version; + + union { + __be32 ip4; // network notation + struct in6_addr ip6; + } saddr; + + union { + __be32 ip4; // network notation + struct in6_addr ip6; + } daddr; + + uint16_t id; + + uint8_t stop; +}; + +#define UDP_ACTIVE 0 +#define UDP_FINISHING 1 +#define UDP_CLOSED 2 +#define UDP_BLOCKED 3 + +struct udp_session { + time_t time; + jint uid; + int version; + uint16_t mss; + + uint64_t sent; + uint64_t received; + + union { + __be32 ip4; // network notation + struct in6_addr ip6; + } saddr; + __be16 source; // network notation + + union { + __be32 ip4; // network notation + struct in6_addr ip6; + } daddr; + __be16 dest; // network notation + + uint8_t state; +}; + +struct tcp_session { + jint uid; + time_t time; + int version; + uint16_t mss; + uint8_t recv_scale; + uint8_t send_scale; + uint32_t recv_window; // host notation, scaled + uint32_t send_window; // host notation, scaled + + uint32_t remote_seq; // confirmed bytes received, host notation + uint32_t local_seq; // confirmed bytes sent, host notation + uint32_t remote_start; + uint32_t local_start; + + uint32_t acked; // host notation + long long last_keep_alive; + + uint64_t sent; + uint64_t received; + + union { + __be32 ip4; // network notation + struct in6_addr ip6; + } saddr; + __be16 source; // network notation + + union { + __be32 ip4; // network notation + struct in6_addr ip6; + } daddr; + __be16 dest; // network notation + + uint8_t state; + struct segment *forward; + + char hostname[512]; + int connect_sent; +}; + +struct ng_session { + uint8_t protocol; + union { + struct icmp_session icmp; + struct udp_session udp; + struct tcp_session tcp; + }; + jint socket; + struct epoll_event ev; + struct ng_session *next; +}; + +// IPv6 + +struct ip6_hdr_pseudo { + struct in6_addr ip6ph_src; + struct in6_addr ip6ph_dst; + u_int32_t ip6ph_len; + u_int8_t ip6ph_zero[3]; + u_int8_t ip6ph_nxt; +} __packed; + +#define LINKTYPE_RAW 101 + +// DNS + +#define DNS_QCLASS_IN 1 +#define DNS_QTYPE_A 1 // IPv4 +#define DNS_QTYPE_AAAA 28 // IPv6 + +#define DNS_QNAME_MAX 255 +#define DNS_TTL (10 * 60) // seconds + +struct dns_header { + uint16_t id; // identification number +# if __BYTE_ORDER == __LITTLE_ENDIAN + uint16_t rd :1; // recursion desired + uint16_t tc :1; // truncated message + uint16_t aa :1; // authoritive answer + uint16_t opcode :4; // purpose of message + uint16_t qr :1; // query/response flag + uint16_t rcode :4; // response code + uint16_t cd :1; // checking disabled + uint16_t ad :1; // authenticated data + uint16_t z :1; // its z! reserved + uint16_t ra :1; // recursion available +#elif __BYTE_ORDER == __BIG_ENDIAN + uint16_t qr :1; // query/response flag + uint16_t opcode :4; // purpose of message + uint16_t aa :1; // authoritive answer + uint16_t tc :1; // truncated message + uint16_t rd :1; // recursion desired + uint16_t ra :1; // recursion available + uint16_t z :1; // its z! reserved + uint16_t ad :1; // authenticated data + uint16_t cd :1; // checking disabled + uint16_t rcode :4; // response code +# else +# error "Adjust your defines" +#endif + uint16_t q_count; // number of question entries + uint16_t ans_count; // number of answer entries + uint16_t auth_count; // number of authority entries + uint16_t add_count; // number of resource entries +} __packed; + +typedef struct dns_rr { + __be16 qname_ptr; + __be16 qtype; + __be16 qclass; + __be32 ttl; + __be16 rdlength; +} __packed; + +// DHCP + +#define DHCP_OPTION_MAGIC_NUMBER (0x63825363) + +typedef struct dhcp_packet { + uint8_t opcode; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t option_format; +} __packed; + +typedef struct dhcp_option { + uint8_t code; + uint8_t length; +} __packed; + +// Prototypes + +void handle_signal(int sig, siginfo_t *info, void *context); + +void *handle_events(void *a); + +void report_exit(const struct arguments *args, const char *fmt, ...); + +void report_error(const struct arguments *args, jint error, const char *fmt, ...); + +void check_allowed(const struct arguments *args); + +void init(const struct arguments *args); + +void clear(); + +int check_icmp_session(const struct arguments *args, + struct ng_session *s, + int sessions, int maxsessions); + +int check_udp_session(const struct arguments *args, + struct ng_session *s, + int sessions, int maxsessions); + +int check_tcp_session(const struct arguments *args, + struct ng_session *s, + int sessions, int maxsessions); + +int monitor_tcp_session(const struct arguments *args, struct ng_session *s, int epoll_fd); + +int get_icmp_timeout(const struct icmp_session *u, int sessions, int maxsessions); + +int get_udp_timeout(const struct udp_session *u, int sessions, int maxsessions); + +int get_tcp_timeout(const struct tcp_session *t, int sessions, int maxsessions); + +uint16_t get_mtu(); + +uint16_t get_default_mss(int version); + +int check_tun(const struct arguments *args, + const struct epoll_event *ev, + const int epoll_fd, + int sessions, int maxsessions); + +void check_icmp_socket(const struct arguments *args, const struct epoll_event *ev); + +void check_udp_socket(const struct arguments *args, const struct epoll_event *ev); + +int32_t get_qname(const uint8_t *data, const size_t datalen, uint16_t off, char *qname); + +void parse_dns_response(const struct arguments *args, const struct udp_session *u, + const uint8_t *data, size_t *datalen); + +uint32_t get_send_window(const struct tcp_session *cur); + +int get_receive_buffer(const struct ng_session *cur); + +uint32_t get_receive_window(const struct ng_session *cur); + +void check_tcp_socket(const struct arguments *args, + const struct epoll_event *ev, + const int epoll_fd); + +int is_lower_layer(int protocol); + +int is_upper_layer(int protocol); + +void handle_ip(const struct arguments *args, + const uint8_t *buffer, size_t length, + const int epoll_fd, + int sessions, int maxsessions); + +jboolean handle_icmp(const struct arguments *args, + const uint8_t *pkt, size_t length, + const uint8_t *payload, + int uid, + const int epoll_fd); + +int has_udp_session(const struct arguments *args, const uint8_t *pkt, const uint8_t *payload); + +jboolean handle_udp(const struct arguments *args, + const uint8_t *pkt, size_t length, + const uint8_t *payload, + int uid, + const int epoll_fd); + +int get_dns_query(const struct arguments *args, const struct udp_session *u, + const uint8_t *data, const size_t datalen, + uint16_t *qtype, uint16_t *qclass, char *qname); + +int check_domain(const struct arguments *args, const struct udp_session *u, + const uint8_t *data, const size_t datalen, + uint16_t qclass, uint16_t qtype, const char *name); + +int check_dhcp(const struct arguments *args, const struct udp_session *u, + const uint8_t *data, const size_t datalen); + +void clear_tcp_data(struct tcp_session *cur); + +jboolean handle_tcp(const struct arguments *args, + const uint8_t *pkt, size_t length, + const uint8_t *payload, + int uid, + const int epoll_fd); + +void queue_tcp(const struct arguments *args, + const struct tcphdr *tcphdr, + const char *session, struct tcp_session *cur, + const uint8_t *data, uint16_t datalen); + +int open_icmp_socket(const struct arguments *args, const struct icmp_session *cur); + +int open_udp_socket(const struct arguments *args, + const struct udp_session *cur, const struct allowed *redirect); + +int open_tcp_socket(const struct arguments *args, + const struct tcp_session *cur, const struct allowed *redirect); + +int32_t get_local_port(const int sock); + +int write_syn_ack(const struct arguments *args, struct tcp_session *cur); + +int write_ack(const struct arguments *args, struct tcp_session *cur); + +int write_data(const struct arguments *args, struct tcp_session *cur, + const uint8_t *buffer, size_t length); + +int write_fin_ack(const struct arguments *args, struct tcp_session *cur); + +void write_rst(const struct arguments *args, struct tcp_session *cur); + +void write_rst_ack(const struct arguments *args, struct tcp_session *cur); + +ssize_t write_icmp(const struct arguments *args, const struct icmp_session *cur, + uint8_t *data, size_t datalen); + +ssize_t write_udp(const struct arguments *args, const struct udp_session *cur, + uint8_t *data, size_t datalen); + +ssize_t write_tcp(const struct arguments *args, const struct tcp_session *cur, + const uint8_t *data, size_t datalen, + int syn, int ack, int fin, int rst); + +uint8_t char2nible(const char c); + +void hex2bytes(const char *hex, uint8_t *buffer); + +jint get_uid(const int version, const int protocol, + const void *saddr, const uint16_t sport, + const void *daddr, const uint16_t dport); + +jint get_uid_sub(const int version, const int protocol, + const void *saddr, const uint16_t sport, + const void *daddr, const uint16_t dport); + +int protect_socket(const struct arguments *args, int socket); + +uint16_t calc_checksum(uint16_t start, const uint8_t *buffer, size_t length); + +jobject jniGlobalRef(JNIEnv *env, jobject cls); + +jclass jniFindClass(JNIEnv *env, const char *name); + +jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature); + +jfieldID jniGetFieldID(JNIEnv *env, jclass cls, const char *name, const char *type); + +jobject jniNewObject(JNIEnv *env, jclass cls, jmethodID constructor, const char *name); + +int jniCheckException(JNIEnv *env); + +int sdk_int(JNIEnv *env); + +void log_android(int prio, const char *fmt, ...); + +void log_packet(const struct arguments *args, jobject jpacket); + +void dns_resolved(const struct arguments *args, + const char *qname, const char *aname, const char *resource, int ttl); + +jboolean is_domain_blocked(const struct arguments *args, const char *name); + +struct allowed *is_address_allowed(const struct arguments *args, jobject objPacket); + +jobject create_packet(const struct arguments *args, + jint version, + jint protocol, + const char *flags, + const char *source, + jint sport, + const char *dest, + jint dport, + const char *data, + jint uid, + jboolean allowed); + +void account_usage(const struct arguments *args, jint version, jint protocol, + const char *daddr, jint dport, jint uid, jlong sent, jlong received); + +void write_pcap_hdr(); + +void write_pcap_rec(const uint8_t *buffer, size_t len); + +void write_pcap(const void *ptr, size_t len); + +int compare_u32(uint32_t seq1, uint32_t seq2); + +const char *strstate(const int state); + +char *hex(const u_int8_t *data, const size_t len); + +int is_readable(int fd); + +int is_writable(int fd); + +long long get_ms(); diff --git a/android_app/app/src/main/java/com/tun2http/app/MainActivity.java b/android_app/app/src/main/java/tun/proxy/MainActivity.java similarity index 55% rename from android_app/app/src/main/java/com/tun2http/app/MainActivity.java rename to android_app/app/src/main/java/tun/proxy/MainActivity.java index f7baf39..ea19a17 100644 --- a/android_app/app/src/main/java/com/tun2http/app/MainActivity.java +++ b/android_app/app/src/main/java/tun/proxy/MainActivity.java @@ -1,28 +1,46 @@ -package com.tun2http.app; +package tun.proxy; -import android.app.Activity; +import android.Manifest; +import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.VpnService; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; -import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.Button; +import android.widget.CompoundButton; import android.widget.EditText; +import android.widget.Switch; + +import java.util.EnumSet; +import java.util.Map; -import com.tun2http.app.service.Tun2HttpVpnService; +import tun.proxy.service.Tun2HttpVpnService; +import tun.utils.CertificateUtil; + +public class MainActivity extends AppCompatActivity { + public static final int REQUEST_VPN = 1; + public static final int REQUEST_CERT = 2; -public class MainActivity extends Activity { Button start; Button stop; EditText hostEditText; - + MenuItem menuSetting; Handler statusHandler = new Handler(); private Tun2HttpVpnService service; @@ -31,6 +49,8 @@ public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); start = findViewById(R.id.start); stop = findViewById(R.id.stop); @@ -39,7 +59,16 @@ protected void onCreate(Bundle savedInstanceState) { start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startVpn(); +// MyApplication app = (MyApplication) getApplicationContext(); +// byte [] trust_ca = app.getTrustCA(); +// if (trust_ca != null) { +// Intent intent = CertificateUtil.trustRootCA(CertificateUtil.getCACertificate(trust_ca)); +// if (intent != null) { +// startActivityForResult(intent, REQUEST_CERT); +// } else { + startVpn(); +// } +// } } }); stop.setOnClickListener(new View.OnClickListener() { @@ -48,14 +77,58 @@ public void onClick(View v) { stopVpn(); } }); - - start.setEnabled(true); stop.setEnabled(false); loadHostPort(); + requestPermission(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.action_activity_settings); + item.setEnabled(start.isEnabled()); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_activity_settings: + Intent intent = new android.content.Intent(this, SimplePreferenceActivity.class); + startActivity(intent); + break; + case R.id.action_show_about: + new AlertDialog.Builder(this) + .setTitle(getString(R.string.app_name) + getVersionName()) + .setMessage(R.string.app_name) + .show(); + break; + default: + return super.onOptionsItemSelected(item); + } + return true; } + String getVersionName() { + PackageManager packageManager = getPackageManager(); + if (packageManager == null) { + return null; + } + + try { + return packageManager.getPackageInfo(getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder binder) { @@ -71,7 +144,6 @@ public void onServiceDisconnected(ComponentName className) { @Override protected void onResume() { super.onResume(); - start.setEnabled(false); stop.setEnabled(false); updateStatus(); @@ -89,8 +161,8 @@ boolean isRunning() { Runnable statusRunnable = new Runnable() { @Override public void run() { - updateStatus(); - statusHandler.postDelayed(statusRunnable, 1000); + updateStatus(); + statusHandler.postDelayed(statusRunnable, 1000); } }; @@ -118,17 +190,15 @@ void updateStatus() { private void stopVpn() { start.setEnabled(true); stop.setEnabled(false); - Tun2HttpVpnService.stop(this); } private void startVpn() { - Intent i = VpnService.prepare(this); if (i != null) { startActivityForResult(i, 0); } else { - onActivityResult(0, Activity.RESULT_OK, null); + onActivityResult(0, REQUEST_VPN, null); } } @@ -136,7 +206,7 @@ private void startVpn() { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (resultCode == Activity.RESULT_OK && parseAndSaveHostPort()) { + if (resultCode == REQUEST_VPN && parseAndSaveHostPort()) { start.setEnabled(false); stop.setEnabled(true); Tun2HttpVpnService.start(this); @@ -148,11 +218,11 @@ private void loadHostPort() { String proxyHost = prefs.getString(Tun2HttpVpnService.PREF_PROXY_HOST, ""); int proxyPort = prefs.getInt(Tun2HttpVpnService.PREF_PROXY_PORT, 0); - if(TextUtils.isEmpty(proxyHost)) { + if (TextUtils.isEmpty(proxyHost)) { return; } - if(proxyPort == 80) { + if (proxyPort == 80) { hostEditText.setText(proxyHost); } else { hostEditText.setText(proxyHost + ":" + proxyPort); @@ -177,7 +247,7 @@ private boolean parseAndSaveHostPort() { } } String[] ipParts = parts[0].split("\\."); - if(ipParts.length != 4) { + if (ipParts.length != 4) { hostEditText.setError(getString(R.string.enter_host)); return false; } @@ -191,4 +261,25 @@ private boolean parseAndSaveHostPort() { edit.commit(); return true; } + + private void requestPermission() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {//Can add more as per requirement + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 8000); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + switch (requestCode) { + case 8000: { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + } else { + requestPermission(); + } + return; + } + } + } + } diff --git a/android_app/app/src/main/java/tun/proxy/MyApplication.java b/android_app/app/src/main/java/tun/proxy/MyApplication.java new file mode 100644 index 0000000..05d2236 --- /dev/null +++ b/android_app/app/src/main/java/tun/proxy/MyApplication.java @@ -0,0 +1,61 @@ +package tun.proxy; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import tun.utils.CertificateUtil; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class MyApplication extends Application { + private static Context context; + + public static Context getContext() { + return context; + } + + @Override + public void onCreate() { + super.onCreate(); + context = getApplicationContext(); + } +// public byte [] getTrustCA() { +// try { +// X509Certificate cert = CertificateUtil.getCACertificate("/sdcard/", ""); +// return cert.getEncoded(); +// } catch (CertificateEncodingException e) { +// e.printStackTrace(); +// } +//// SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); +//// String trustca_preference = sharedPreferences.getString( "trusted_ca", null ); +//// if (trustca_preference != null) { +//// return CertificateUtil.decode(trustca_preference); +//// } +// return null; +// } + + public int getVPNMode() { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + String vpn_connection_mode = sharedPreferences.getString("vpn_connection_mode", String.valueOf(SimplePreferenceFragment.PackageListPreferenceFragment.VPNMode.DISALLOW.ordinal())); + return Integer.parseInt( vpn_connection_mode ); + } + + public Set getAllowedApplication() { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + Set allowed_preference = sharedPreferences.getStringSet("vpn_allowed_application", new HashSet() ); + return allowed_preference; + } + + public Set getDisallowedApplication() { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + Set disallowed_preference = sharedPreferences.getStringSet("vpn_disallowed_application", new HashSet() ); + return disallowed_preference; + } + +} diff --git a/android_app/app/src/main/java/tun/proxy/SimplePreferenceActivity.java b/android_app/app/src/main/java/tun/proxy/SimplePreferenceActivity.java new file mode 100644 index 0000000..23752fe --- /dev/null +++ b/android_app/app/src/main/java/tun/proxy/SimplePreferenceActivity.java @@ -0,0 +1,113 @@ +package tun.proxy; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +public class SimplePreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + + getFragmentManager().beginTransaction() + .replace(android.R.id.content, new SimplePreferenceFragment()).commit(); + + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, new SimplePreferenceFragment()).commit(); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } + + +} diff --git a/android_app/app/src/main/java/tun/proxy/SimplePreferenceFragment.java b/android_app/app/src/main/java/tun/proxy/SimplePreferenceFragment.java new file mode 100644 index 0000000..4ef4566 --- /dev/null +++ b/android_app/app/src/main/java/tun/proxy/SimplePreferenceFragment.java @@ -0,0 +1,204 @@ +package tun.proxy; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.view.MenuItem; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tun.utils.CertificateUtil; + +public class SimplePreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + setHasOptionsMenu(true); + + /* Allowed / Disallowed Application */ + final ListPreference pkg_selection = (ListPreference) this.findPreference("vpn_connection_mode"); + pkg_selection.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + if (preference instanceof ListPreference) { + ListPreference listPreference = (ListPreference) preference; + int index = listPreference.findIndexOfValue((String) value); + + PreferenceScreen disallow = (PreferenceScreen) findPreference("disallowed_application_list"); + PreferenceScreen allow = (PreferenceScreen) findPreference("allowed_application_list"); + disallow.setEnabled(index == 0); + allow.setEnabled(index != 0); + + + // Set the summary to reflect the new value. + preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null); + + } + return true; + } + }); + pkg_selection.setSummary(pkg_selection.getEntry()); + PreferenceScreen disallow = (PreferenceScreen) findPreference("disallowed_application_list"); + PreferenceScreen allow = (PreferenceScreen) findPreference("allowed_application_list"); + disallow.setEnabled(Integer.parseInt(pkg_selection.getValue()) == 0); + allow.setEnabled(Integer.parseInt(pkg_selection.getValue()) != 0); + + findPreference("allowed_application_list").setOnPreferenceClickListener(this); + findPreference("disallowed_application_list").setOnPreferenceClickListener(this); + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), MainActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + + // リスナー部分 + @Override + public boolean onPreferenceClick(Preference preference) { + // keyを見てクリックされたPreferenceを特定 + switch (preference.getKey()) { + case "allowed_application_list": + transitionFragment(PackageListPreferenceFragment.newInstance(PackageListPreferenceFragment.VPNMode.ALLOW)); + break; + case "disallowed_application_list": + transitionFragment(PackageListPreferenceFragment.newInstance(PackageListPreferenceFragment.VPNMode.DISALLOW)); + break; + } + return false; + } + + private void transitionFragment(PreferenceFragment nextPreferenceFragment) { + // replaceによるFragmentの切り替えと、addToBackStackで戻るボタンを押した時に前のFragmentに戻るようにする + getFragmentManager() + .beginTransaction() + .addToBackStack(null) + .replace(android.R.id.content, nextPreferenceFragment) + .commit(); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class PackageListPreferenceFragment extends PreferenceFragment { + enum VPNMode {DISALLOW, ALLOW}; + private VPNMode mode; + private PreferenceScreen mRootPreferenceScreen; + + private String pref_key[] = {"vpn_disallowed_application", "vpn_allowed_application"}; + + public static PackageListPreferenceFragment newInstance(VPNMode mode) { + PackageListPreferenceFragment fragment = new PackageListPreferenceFragment(); + fragment.mode = mode; + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + mRootPreferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity()); + setPreferenceScreen(mRootPreferenceScreen); + } + + @Override + public void onResume() { + super.onResume(); + removeAllPreferenceScreen(); + buildPackagesPreferences(); + loadSelectedPackage(); + } + + private void removeAllPreferenceScreen() { + mRootPreferenceScreen.removeAll(); + } + + private void buildPackagesPreferences() { + Context context = MyApplication.getContext().getApplicationContext(); + PackageManager pm = context.getPackageManager(); + List installedPackages = pm.getInstalledPackages(PackageManager.GET_META_DATA); + for (final PackageInfo pi : installedPackages) { + final Preference preference = buildPackagePreferences(pm, pi); + mRootPreferenceScreen.addPreference(preference); + } + } + + private Preference buildPackagePreferences(final PackageManager pm, final PackageInfo pi) { + final CheckBoxPreference p = new CheckBoxPreference(getActivity()); + p.setTitle(pi.applicationInfo.loadLabel(pm).toString()); + p.setSummary(pi.packageName); + return p; + } + + private Set getSelectedPackageSet() { + Set selected = new HashSet<>(); + for (int i = 0; i < this.mRootPreferenceScreen.getPreferenceCount(); i++) { + Preference pref = this.mRootPreferenceScreen.getPreference(i); + if ((pref instanceof CheckBoxPreference)) { + CheckBoxPreference pref_check = (CheckBoxPreference) pref; + if (pref_check.isChecked()) selected.add(pref_check.getSummary().toString()); + } + } + return selected; + } + + private void setSelectedPackageSet(Set selected) { + for (int i = 0; i < this.mRootPreferenceScreen.getPreferenceCount(); i++) { + Preference pref = this.mRootPreferenceScreen.getPreference(i); + if ((pref instanceof CheckBoxPreference)) { + CheckBoxPreference pref_check = (CheckBoxPreference) pref; + if (selected.contains(pref_check.getSummary())) + pref_check.setChecked(true); + } + } + } + + private void loadSelectedPackage() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( getActivity()); + this.getArguments(); + Set selected = prefs.getStringSet( pref_key[mode.ordinal()], new HashSet()); + setSelectedPackageSet(selected); + } + + private void storeSelectedPackageSet(final Set set) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + final SharedPreferences.Editor editor = prefs.edit(); + editor.putStringSet(pref_key[mode.ordinal()], set); + editor.commit(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + storeSelectedPackageSet(this.getSelectedPackageSet()); + startActivity(new Intent(getActivity(), SimplePreferenceActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + + } + +} diff --git a/android_app/app/src/main/java/com/tun2http/app/receiver/BootReceiver.java b/android_app/app/src/main/java/tun/proxy/receiver/BootReceiver.java similarity index 78% rename from android_app/app/src/main/java/com/tun2http/app/receiver/BootReceiver.java rename to android_app/app/src/main/java/tun/proxy/receiver/BootReceiver.java index 57aef69..01aea7e 100644 --- a/android_app/app/src/main/java/com/tun2http/app/receiver/BootReceiver.java +++ b/android_app/app/src/main/java/tun/proxy/receiver/BootReceiver.java @@ -1,4 +1,4 @@ -package com.tun2http.app.receiver; +package tun.proxy.receiver; import android.content.BroadcastReceiver; import android.content.Context; @@ -8,22 +8,21 @@ import android.preference.PreferenceManager; import android.util.Log; -import com.tun2http.app.service.Tun2HttpVpnService; - +import tun.proxy.service.Tun2HttpVpnService; public class BootReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { - if(intent != null && !Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + if (intent != null && !Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { return; } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean isRunning = prefs.getBoolean(Tun2HttpVpnService.PREF_RUNNING, false); - if(isRunning) { + if (isRunning) { Intent prepare = VpnService.prepare(context); - if(prepare == null) { + if (prepare == null) { Log.d("Tun2Http.Boot", "Starting vpn"); Tun2HttpVpnService.start(context); } else { diff --git a/android_app/app/src/main/java/com/tun2http/app/service/Tun2HttpVpnService.java b/android_app/app/src/main/java/tun/proxy/service/Tun2HttpVpnService.java similarity index 87% rename from android_app/app/src/main/java/com/tun2http/app/service/Tun2HttpVpnService.java rename to android_app/app/src/main/java/tun/proxy/service/Tun2HttpVpnService.java index ed888ff..a4d1a7d 100644 --- a/android_app/app/src/main/java/com/tun2http/app/service/Tun2HttpVpnService.java +++ b/android_app/app/src/main/java/tun/proxy/service/Tun2HttpVpnService.java @@ -1,6 +1,5 @@ -package com.tun2http.app.service; +package tun.proxy.service; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -19,42 +18,52 @@ import android.text.TextUtils; import android.util.Log; -import com.tun2http.app.R; -import com.tun2http.app.utils.IPUtil; -import com.tun2http.app.utils.Util; - import java.io.IOException; -import java.math.BigInteger; -import java.net.Inet4Address; import java.net.InetAddress; -import java.net.InterfaceAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; +import java.util.Arrays; import java.util.List; +import java.util.Set; +import tun.proxy.MyApplication; +import tun.proxy.R; public class Tun2HttpVpnService extends VpnService { - private static final String TAG = "Tun2Http.Service"; - private static final String ACTION_START = "start"; - private static final String ACTION_STOP = "stop"; public static final String PREF_PROXY_HOST = "pref_proxy_host"; public static final String PREF_PROXY_PORT = "pref_proxy_port"; public static final String PREF_RUNNING = "pref_running"; + private static final String TAG = "Tun2Http.Service"; + private static final String ACTION_START = "start"; + private static final String ACTION_STOP = "stop"; + private static volatile PowerManager.WakeLock wlInstance = null; + static { + System.loadLibrary("tun2http"); + } private Tun2HttpVpnService.Builder lastBuilder = null; private ParcelFileDescriptor vpn = null; - static { - System.loadLibrary("tun2http"); + synchronized private static PowerManager.WakeLock getLock(Context context) { + if (wlInstance == null) { + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + wlInstance = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getString(R.string.app_name) + " wakelock"); + wlInstance.setReferenceCounted(true); + } + return wlInstance; } + public static void start(Context context) { + Intent intent = new Intent(context, Tun2HttpVpnService.class); + intent.setAction(ACTION_START); + context.startService(intent); + } - private static volatile PowerManager.WakeLock wlInstance = null; + public static void stop(Context context) { + Intent intent = new Intent(context, Tun2HttpVpnService.class); + intent.setAction(ACTION_STOP); + context.startService(intent); + } private native void jni_init(); @@ -64,40 +73,13 @@ public class Tun2HttpVpnService extends VpnService { private native int jni_get_mtu(); - private native int jni_done(); - - - synchronized private static PowerManager.WakeLock getLock(Context context) { - if (wlInstance == null) { - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - wlInstance = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getString(R.string.app_name) + " wakelock"); - wlInstance.setReferenceCounted(true); - } - return wlInstance; - } + private native void jni_done(); @Override public IBinder onBind(Intent intent) { return new ServiceBinder(); } - public class ServiceBinder extends Binder { - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - // see Implementation of android.net.VpnService.Callback.onTransact() - if (code == IBinder.LAST_CALL_TRANSACTION) { - onRevoke(); - return true; - } - return super.onTransact(code, data, reply, flags); - } - - public Tun2HttpVpnService getService() { - return Tun2HttpVpnService.this; - } - } - public boolean isRunning() { return vpn != null; } @@ -122,7 +104,6 @@ private void stop() { stopForeground(true); } - @Override public void onRevoke() { Log.i(TAG, "Revoke"); @@ -158,7 +139,6 @@ private Builder getBuilder() { builder.addAddress(vpn6, 128); builder.addRoute("0.0.0.0", 0); - builder.addRoute("0:0:0:0:0:0:0:0", 0); // MTU @@ -169,7 +149,16 @@ private Builder getBuilder() { // Add list of allowed applications if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { - builder.addAllowedApplication("com.android.chrome"); + MyApplication app = (MyApplication) this.getApplication(); + if (app.getVPNMode() == 0) { + Set disallow = app.getDisallowedApplication(); + Log.d(TAG, "disallowed:" + disallow.size()); + builder.addDisallowedApplication(Arrays.asList(disallow.toArray(new String[0]))); + } else { + Set allow = app.getAllowedApplication(); + Log.d(TAG, "allowed:" + allow.size()); + builder.addAllowedApplication(Arrays.asList(allow.toArray(new String[0]))); + } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } @@ -179,7 +168,6 @@ private Builder getBuilder() { return builder; } - private void startNative(ParcelFileDescriptor vpn) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String proxyHost = prefs.getString(PREF_PROXY_HOST, ""); @@ -205,7 +193,6 @@ private void stopNative(ParcelFileDescriptor vpn) { prefs.edit().putBoolean(PREF_RUNNING, false).apply(); } - private void stopVPN(ParcelFileDescriptor pfd) { Log.i(TAG, "Stopping"); try { @@ -229,7 +216,6 @@ private void nativeError(int error, String message) { Log.w(TAG, "Native error " + error + ": " + message); } - private boolean isSupported(int protocol) { return (protocol == 1 /* ICMPv4 */ || protocol == 59 /* ICMPv6 */ || @@ -237,7 +223,6 @@ private boolean isSupported(int protocol) { protocol == 17 /* UDP */); } - @Override public void onCreate() { @@ -265,7 +250,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } - @Override public void onDestroy() { Log.i(TAG, "Destroy"); @@ -285,6 +269,23 @@ public void onDestroy() { super.onDestroy(); } + public class ServiceBinder extends Binder { + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + // see Implementation of android.net.VpnService.Callback.onTransact() + if (code == IBinder.LAST_CALL_TRANSACTION) { + onRevoke(); + return true; + } + return super.onTransact(code, data, reply, flags); + } + + public Tun2HttpVpnService getService() { + return Tun2HttpVpnService.this; + } + } + private class Builder extends VpnService.Builder { private NetworkInfo networkInfo; private int mtu; @@ -326,6 +327,25 @@ public Builder addDnsServer(InetAddress address) { return this; } + // min sdk 26 + public Builder addAllowedApplication(List packageList) throws PackageManager.NameNotFoundException { + // + for (String pkg : packageList) { + System.out.println("allowed:" + pkg); + addAllowedApplication(pkg); + } + return this; + } + + public Builder addDisallowedApplication(List packageList) throws PackageManager.NameNotFoundException { + // + for (String pkg : packageList) { + System.out.println("disallowed:" + pkg); + addDisallowedApplication(pkg); + } + return this; + } + @Override public boolean equals(Object obj) { Builder other = (Builder) obj; @@ -364,18 +384,4 @@ public boolean equals(Object obj) { return true; } } - - - public static void start(Context context) { - Intent intent = new Intent(context, Tun2HttpVpnService.class); - intent.setAction(ACTION_START); - context.startService(intent); - } - - - public static void stop(Context context) { - Intent intent = new Intent(context, Tun2HttpVpnService.class); - intent.setAction(ACTION_STOP); - context.startService(intent); - } } diff --git a/android_app/app/src/main/java/tun/utils/CertificateUtil.java b/android_app/app/src/main/java/tun/utils/CertificateUtil.java new file mode 100644 index 0000000..c9100ad --- /dev/null +++ b/android_app/app/src/main/java/tun/utils/CertificateUtil.java @@ -0,0 +1,214 @@ +package tun.utils; + +import android.content.Intent; +import android.security.KeyChain; +import android.util.Log; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CertificateUtil { + private static final String TAG = "CertificateManager"; + public enum CertificateInstallType {SYSTEM, USER} + + private final static Pattern CA_COMMON_NAME = Pattern.compile( "CN=([^,]+),?.*$" ); + private final static Pattern CA_ORGANIZATION = Pattern.compile( "O=([^,]+),?.*$" ); + + public static boolean findCAStore(String caName) { + boolean found = false; + try { + KeyStore ks = KeyStore.getInstance("AndroidCAStore"); + if (ks == null) + return false; + + ks.load(null, null); + X509Certificate rootCACert = null; + Enumeration aliases = ks.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + rootCACert = (X509Certificate) ks.getCertificate(alias); + if (rootCACert.getIssuerDN().getName().contains(caName)) { + found = true; + break; + } + } + } catch(IOException e){ + e.printStackTrace(); + } catch(KeyStoreException e){ + e.printStackTrace(); + } catch(NoSuchAlgorithmException e){ + e.printStackTrace(); + } catch(CertificateException e){ + e.printStackTrace(); + } + return found; + } + + + public static List getRootCAStore() { + final List rootCAList = new ArrayList<>(); + try { + KeyStore ks = KeyStore.getInstance( "AndroidCAStore" ); + if (ks == null) + return null; + + ks.load( null, null ); + X509Certificate rootCACert = null; + Enumeration aliases = ks.aliases(); + boolean found = false; + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + X509Certificate cert = (X509Certificate) ks.getCertificate( alias ); + System.out.println( alias + "/" + cert.getIssuerX500Principal().getName() ); + rootCAList.add( cert ); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (CertificateException e) { + e.printStackTrace(); + } + return rootCAList; + } + + public static Map getRootCAMap(EnumSet type) { + final Map rootCAMap = new HashMap<>(); + try { + KeyStore ks = KeyStore.getInstance( "AndroidCAStore" ); + if (ks == null) + return null; + + ks.load( null, null ); + X509Certificate rootCACert = null; + Enumeration aliases = ks.aliases(); + List certList = new ArrayList<>(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + X509Certificate cert = (X509Certificate) ks.getCertificate( alias ); + if (type.contains(CertificateInstallType.SYSTEM) && alias.startsWith("system:")) { + certList.add( cert ); + } + if (type.contains(CertificateInstallType.USER) && alias.startsWith("user:")) { + certList.add( cert ); + } + } + Collections.sort( certList, new Comparator() { + @Override + public int compare(X509Certificate t1, X509Certificate t2) { + String t1cn = CertificateUtil.getCommonName( t1.getIssuerX500Principal().getName() ); + String t2cn = CertificateUtil.getCommonName( t2.getIssuerX500Principal().getName() ); + return t1cn.compareToIgnoreCase( t2cn ); + } + } ); + // ソート後 + List rootCANameList = new ArrayList<>(); + List rootCAList = new ArrayList<>(); + for (X509Certificate cert : certList) { + String cn = CertificateUtil.getCommonName( cert.getIssuerX500Principal().getName() ); + if (cn.trim().isEmpty()) continue; + //String o = CertificateUtil.getOrganization( cert.getIssuerX500Principal().getName()); + rootCANameList.add( cn ); + rootCAList.add( encode( cert.getEncoded() ) ); + } + rootCAMap.put( "entry", rootCANameList.toArray( new String[0] ) ); + rootCAMap.put( "value", rootCAList.toArray( new String[0] ) ); + } catch (IOException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (CertificateException e) { + e.printStackTrace(); + } + return rootCAMap; + } + + public static String encode(byte b[]) { + return new String( b, StandardCharsets.ISO_8859_1 ); + } + + public static byte[] decode(String s) { + return s.getBytes( StandardCharsets.ISO_8859_1 ); + } + + public static Intent trustRootCA(X509Certificate cert) { + Log.d( TAG, "root CA is not yet trusted" ); + Intent intent = KeyChain.createInstallIntent(); + try { + if (findCAStore(cert.getIssuerDN().getName())) return null; + intent.putExtra( KeyChain.EXTRA_CERTIFICATE, cert.getEncoded() ); + intent.putExtra( KeyChain.EXTRA_NAME, getCommonName(cert.getIssuerDN().getName())); + } catch (CertificateEncodingException e) { + e.printStackTrace(); + } + return intent; + } + + // get the CA certificate by the path + public static X509Certificate getCACertificate(byte [] buff) { + X509Certificate ca = null; + try { + CertificateFactory cf = CertificateFactory.getInstance( "X.509" ); + ca = (X509Certificate) cf.generateCertificate( new ByteArrayInputStream( buff ) ); + } catch (CertificateException e) { + e.printStackTrace(); + } + return ca; + } + + // get the CA certificate by the path + public static X509Certificate getCACertificate(String dir, String caName) { + String CERT_FILE = dir + "/burp_export.crt"; + InputStream inStream = null; + try { + inStream = new FileInputStream( CERT_FILE ); + CertificateFactory cf = CertificateFactory.getInstance( "X.509" ); + return (X509Certificate) cf.generateCertificate( inStream ); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (CertificateException e) { + e.printStackTrace(); + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + } + } + } + return null; + } + + public static String getCommonName(String dn) { + String cn = ""; + Matcher m = CA_COMMON_NAME.matcher( dn ); + if (m.find()) { + cn = m.group( 1 ); + } + return cn; + } + + public static String getOrganization(String dn) { + String cn = ""; + Matcher m = CA_ORGANIZATION.matcher( dn ); + if (m.find()) { + cn = m.group( 1 ); + } + return cn; + } + +} diff --git a/android_app/app/src/main/java/com/tun2http/app/utils/IPUtil.java b/android_app/app/src/main/java/tun/utils/IPUtil.java similarity index 99% rename from android_app/app/src/main/java/com/tun2http/app/utils/IPUtil.java rename to android_app/app/src/main/java/tun/utils/IPUtil.java index 7ddf61f..e424c69 100644 --- a/android_app/app/src/main/java/com/tun2http/app/utils/IPUtil.java +++ b/android_app/app/src/main/java/tun/utils/IPUtil.java @@ -1,4 +1,4 @@ -package com.tun2http.app.utils; +package tun.utils; import android.util.Log; diff --git a/android_app/app/src/main/java/tun/utils/PackageUtil.java b/android_app/app/src/main/java/tun/utils/PackageUtil.java new file mode 100644 index 0000000..1bed65b --- /dev/null +++ b/android_app/app/src/main/java/tun/utils/PackageUtil.java @@ -0,0 +1,42 @@ +package tun.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import tun.proxy.MyApplication; + +import java.util.*; + + +public class PackageUtil { + + public static Map getPackageMap() { + final Map packageMap = new HashMap<>(); + Context context = MyApplication.getContext().getApplicationContext(); + PackageManager pm = context.getPackageManager(); + List installedPackages = pm.getInstalledPackages( PackageManager.GET_META_DATA ); + for (final PackageInfo pi : installedPackages) { + + } + Collections.sort( installedPackages, new Comparator() { + @Override + public int compare(PackageInfo t1, PackageInfo t2) { + String t1pkg = t1.packageName; + String t2pkg = t2.packageName; + return t1pkg.compareToIgnoreCase( t2pkg ); + } + } ); + // ソート後 + List pkgLabelList = new ArrayList<>(); + List pkgNameList = new ArrayList<>(); + for (final PackageInfo pi : installedPackages) { + pkgLabelList.add( pi.applicationInfo.loadLabel( pm ).toString() + "(" + pi.packageName + ")" ); + pkgNameList.add( pi.packageName ); + } + packageMap.put( "entry", pkgLabelList.toArray( new String[0] ) ); + packageMap.put( "value", pkgNameList.toArray( new String[0] ) ); + + return packageMap; + } + +} diff --git a/android_app/app/src/main/java/com/tun2http/app/utils/Util.java b/android_app/app/src/main/java/tun/utils/Util.java similarity index 98% rename from android_app/app/src/main/java/com/tun2http/app/utils/Util.java rename to android_app/app/src/main/java/tun/utils/Util.java index ffe3b8d..d595a34 100644 --- a/android_app/app/src/main/java/com/tun2http/app/utils/Util.java +++ b/android_app/app/src/main/java/tun/utils/Util.java @@ -1,4 +1,4 @@ -package com.tun2http.app.utils; +package tun.utils; /* This file is part of NetGuard. diff --git a/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index c3903ed..1f6bb29 100644 --- a/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,14 +1,14 @@ + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + android:strokeWidth="1" + android:strokeColor="#00000000"> + android:offset="0.0" /> + android:offset="1.0" /> @@ -29,6 +29,6 @@ android:fillColor="#FFFFFF" android:fillType="nonZero" android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" - android:strokeColor="#00000000" - android:strokeWidth="1"/> + android:strokeWidth="1" + android:strokeColor="#00000000" /> diff --git a/android_app/app/src/main/res/drawable/ic_info_black_24dp.xml b/android_app/app/src/main/res/drawable/ic_info_black_24dp.xml new file mode 100644 index 0000000..8024b5b --- /dev/null +++ b/android_app/app/src/main/res/drawable/ic_info_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/android_app/app/src/main/res/drawable/ic_launcher_background.xml b/android_app/app/src/main/res/drawable/ic_launcher_background.xml index 5713f34..0d025f9 100644 --- a/android_app/app/src/main/res/drawable/ic_launcher_background.xml +++ b/android_app/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,171 +1,170 @@ - + android:viewportWidth="108" + android:viewportHeight="108"> + android:fillColor="#008577" + android:pathData="M0,0h108v108h-108z" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> diff --git a/android_app/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/android_app/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..14f20f9 --- /dev/null +++ b/android_app/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/android_app/app/src/main/res/drawable/ic_sync_black_24dp.xml b/android_app/app/src/main/res/drawable/ic_sync_black_24dp.xml new file mode 100644 index 0000000..478aa98 --- /dev/null +++ b/android_app/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/android_app/app/src/main/res/layout/activity_main.xml b/android_app/app/src/main/res/layout/activity_main.xml index 8ed0792..298db50 100644 --- a/android_app/app/src/main/res/layout/activity_main.xml +++ b/android_app/app/src/main/res/layout/activity_main.xml @@ -1,33 +1,25 @@ - + - + -