mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
289 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93a9e4bfc8 | |||
| de7d43da19 | |||
| b86356b0cc | |||
| f6d7552b58 | |||
| 5bd88ac2e9 | |||
| 94fa5b59f3 | |||
| 7169480999 | |||
| c44ce0d77c | |||
| 7fd71a8408 | |||
| 68db721089 | |||
| a180e12bdf | |||
| f3a4a1b1be | |||
| 6977a8ecbc | |||
| d00f64e6ad | |||
| d5b3da6ba3 | |||
| c245318339 | |||
| b3b0fec2e1 | |||
| 9d571a4c71 | |||
| f283858490 | |||
| 76fe203767 | |||
| b9a47f2f50 | |||
| 27cb17c640 | |||
| ef8fb89eb3 | |||
| f1b045f8a8 | |||
| 050066132b | |||
| 2a6e6a1e24 | |||
| 92689d084c | |||
| 00f314039d | |||
| fcb75e837d | |||
| 9fbea76b74 | |||
| b3ff120bcf | |||
| 9dea98f020 | |||
| c4701d4e7a | |||
| 48903ca3a1 | |||
| 0c9fd4aef4 | |||
| b2af2e46ac | |||
| efc76a0683 | |||
| c4a553c166 | |||
| 69a00b0252 | |||
| 4257c08b43 | |||
| c9e5b92f79 | |||
| 99818c2ad8 | |||
| 99e3afabad | |||
| d3339a7f3a | |||
| 678bfffe49 | |||
| 728b48044c | |||
| 7ccbfa48bc | |||
| 83460bc29b | |||
| c28e1b468a | |||
| abd7fdd19c | |||
| 2b1ec9c693 | |||
| 19fcddfdaf | |||
| 0bca78eca9 | |||
| 68046a0b7c | |||
| d19017f87b | |||
| 46536bc60a | |||
| 6a424e9858 | |||
| 8afe50cd87 | |||
| 48980c486e | |||
| 5f6cd282d3 | |||
| 95121c06e2 | |||
| c2b17c128d | |||
| eda24765e7 | |||
| 35e0e146e6 | |||
| a5254ac238 | |||
| 517b5e5ca6 | |||
| cfeb6cbffd | |||
| c128ba981c | |||
| a1ca994c8b | |||
| 52c12940c4 | |||
| 25d759374c | |||
| e9250afd2b | |||
| eb83086d5c | |||
| 9398e0e695 | |||
| 915c8f46c5 | |||
| ec132ac96c | |||
| 101838404e | |||
| db3164223a | |||
| 5a7b5d34fb | |||
| 9420333c76 | |||
| f6403fe82e | |||
| c55b025eee | |||
| fc6fc26148 | |||
| 48b43ee102 | |||
| e091020692 | |||
| 07baf0ed65 | |||
| 42d3d9b98a | |||
| 389c1f5327 | |||
| 703b9137e0 | |||
| b183a3b232 | |||
| f163f0fc1d | |||
| 3b49d5ca59 | |||
| 236e5ca2e3 | |||
| 2f6e28b980 | |||
| 46d96a8887 | |||
| 56221881da | |||
| 3f55f6a629 | |||
| 7c8ae9c311 | |||
| b173dcaa17 | |||
| da5fe1d766 | |||
| a15ea0e8a1 | |||
| fbbba648c4 | |||
| f79bfa9d2e | |||
| 3011a0e306 | |||
| 76640311ab | |||
| e707471b04 | |||
| 6425700d1c | |||
| 36045c6694 | |||
| 52ecd6899b | |||
| 49a6a9ed76 | |||
| 4869429eb6 | |||
| 956dd6e37a | |||
| 665a2911be | |||
| 1cfa4e0630 | |||
| 5bda624576 | |||
| d1f0560595 | |||
| df07fc1b1f | |||
| 8ca31e0c90 | |||
| f1c6067485 | |||
| ca04c63f5e | |||
| 89cdd2bece | |||
| 73d7dfa54f | |||
| 0a5b54a2e4 | |||
| e43aa02a5b | |||
| c3fb62a6ab | |||
| 62f3a339b7 | |||
| 767b14b37a | |||
| e7fa160c9c | |||
| 7350d79c50 | |||
| 86f08554cd | |||
| a741186c21 | |||
| 6acaab0ffa | |||
| 212e9b3a91 | |||
| 2bff37efae | |||
| b88ab8e432 | |||
| 48f6cf904e | |||
| 367789bda2 | |||
| d06924c59d | |||
| 2db99715b1 | |||
| 9688a8e52d | |||
| b2c429f74d | |||
| 6ea6ab1bd9 | |||
| c5aa070bf4 | |||
| d67201ede9 | |||
| 4323fb2063 | |||
| 6d5452b8ee | |||
| 569d63ef0f | |||
| ea910ba300 | |||
| 1c1e74d06f | |||
| 5dc16c06f1 | |||
| 4efaf20a1c | |||
| 9d96b1cd13 | |||
| 1d721ffb9a | |||
| 2130131a9d | |||
| e0b091b474 | |||
| 8547de82ea | |||
| aa871bd1c9 | |||
| 23806e1def | |||
| 31867993ce | |||
| 7b7a922d92 | |||
| 09bd958d8d | |||
| 576e2226fe | |||
| 1533270e4e | |||
| e7b25719e4 | |||
| 7183e8541c | |||
| 9e71e64cbd | |||
| 4f3bae4a9a | |||
| 990059f8a6 | |||
| af55af5e76 | |||
| 82d96a9691 | |||
| 9f3f215452 | |||
| 2dfc6a87b8 | |||
| 7261a86c48 | |||
| 2946dd2278 | |||
| 5065262aac | |||
| 4685d3b543 | |||
| 7a389e8755 | |||
| 4e5daf22a3 | |||
| 3bf9c10d7d | |||
| 2e175cb9fc | |||
| 823c1b5d3a | |||
| 92bc1a6f09 | |||
| d511220f8b | |||
| 923e358aaa | |||
| 92b19eccf6 | |||
| 5358aaeb00 | |||
| e31a2066c0 | |||
| 928c4f18c9 | |||
| 628e22869d | |||
| c9cd860654 | |||
| 17984adae5 | |||
| 5601bc4fdf | |||
| e14681801e | |||
| f106b4d367 | |||
| 74802f30ed | |||
| d63bf15011 | |||
| 60de146f03 | |||
| c4f32eed31 | |||
| 2c9067b0de | |||
| 6844a2375b | |||
| 7b838e77a0 | |||
| 694e781beb | |||
| 399a8c6d28 | |||
| dce08b3ecc | |||
| 2763da960f | |||
| d4fff4af3c | |||
| f0903c32f3 | |||
| ea8875478e | |||
| 4c08e9f3bc | |||
| e8736102bf | |||
| 371cadcc02 | |||
| c3805195af | |||
| 2ef267bc44 | |||
| 02a98b9d68 | |||
| 94bae4b859 | |||
| 425acc5f8b | |||
| bb87c0838d | |||
| 1542adba82 | |||
| 3aa8a46f6e | |||
| 1f08d78b43 | |||
| 268adfb0a1 | |||
| c681611102 | |||
| 4fc2a23f49 | |||
| 23f4a6ec8e | |||
| 504862c2b8 | |||
| a22a9448ca | |||
| 862e83ddf5 | |||
| 8735eee662 | |||
| ff82cf5dc4 | |||
| 8648790583 | |||
| b881d92a80 | |||
| 7ad7f31e4d | |||
| 138e6f70a4 | |||
| 6f94f4646a | |||
| 4a01d2cf20 | |||
| 8948601caa | |||
| aa92ccd06d | |||
| 253ae75795 | |||
| 87cb5f620a | |||
| 46cd740a84 | |||
| 76e5039578 | |||
| c6b131aa4c | |||
| 5e72bf945c | |||
| eebf7eccec | |||
| 168c293bfe | |||
| aae3cdcac1 | |||
| 96566f04ee | |||
| fff15fffe2 | |||
| 4e5a03e7f1 | |||
| 7571bbc36e | |||
| db4a1a62e5 | |||
| 581773ce03 | |||
| 46058f614e | |||
| 9cab51fb00 | |||
| 918be16372 | |||
| 175477d31f | |||
| cd70b7e619 | |||
| 22011e263e | |||
| 88a2b9a07a | |||
| 248f487d4e | |||
| 572ef09296 | |||
| 03078236ab | |||
| b39a0a1d94 | |||
| e94fc688ba | |||
| 558f613acc | |||
| d800a95a1d | |||
| b8f100d4fa | |||
| 51618fb882 | |||
| 14f537ba76 | |||
| 3458ed78d7 | |||
| 4bc571f609 | |||
| ee61f842e5 | |||
| 758b25947c | |||
| b036c38981 | |||
| eab2b8e45a | |||
| dfdec2bf4b | |||
| 843156cf1b | |||
| 01413e5a4c | |||
| 743304c359 | |||
| 6c5d590169 | |||
| a1e68f5506 | |||
| 424315b17f | |||
| b83e74427e | |||
| 8fefae0325 | |||
| ede633ea03 | |||
| b4469064a2 | |||
| 393e289784 | |||
| d18423ee8c | |||
| 73516c28af |
@@ -0,0 +1,39 @@
|
|||||||
|
BasedOnStyle: WebKit
|
||||||
|
AccessModifierOffset: '-4'
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveMacros: 'true'
|
||||||
|
AlignTrailingComments: 'true'
|
||||||
|
AllowAllArgumentsOnNextLine: 'true'
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: 'true'
|
||||||
|
AllowShortBlocksOnASingleLine: 'false'
|
||||||
|
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||||
|
AllowShortEnumsOnASingleLine: 'false'
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AlwaysBreakTemplateDeclarations: 'No'
|
||||||
|
BreakBeforeBinaryOperators: NonAssignment
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterEnum: false
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterObjCDeclaration: false
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
ColumnLimit: '120'
|
||||||
|
CommentPragmas: '"^!|^:"'
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
|
||||||
|
ConstructorInitializerIndentWidth: '4'
|
||||||
|
ContinuationIndentWidth: '8'
|
||||||
|
IndentPPDirectives: BeforeHash
|
||||||
|
NamespaceIndentation: All
|
||||||
|
PenaltyExcessCharacter: '10'
|
||||||
|
PointerAlignment: Right
|
||||||
|
SortIncludes: 'true'
|
||||||
|
SpaceAfterTemplateKeyword: 'false'
|
||||||
|
Standard: Auto
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/client/3rd
|
||||||
|
/client/3rd-prebuild
|
||||||
|
/client/android
|
||||||
|
/client/cmake
|
||||||
|
/client/core/serialization
|
||||||
|
/client/daemon
|
||||||
|
/client/fonts
|
||||||
|
/client/images
|
||||||
|
/client/ios
|
||||||
|
/client/mozilla
|
||||||
|
/client/platforms/dummy
|
||||||
|
/client/platforms/linux
|
||||||
|
/client/platforms/macos
|
||||||
|
/client/platforms/windows
|
||||||
|
/client/server_scripts
|
||||||
|
/client/translations
|
||||||
|
/deploy
|
||||||
|
/docs
|
||||||
|
/metadata
|
||||||
|
/service/src
|
||||||
@@ -10,12 +10,16 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-Linux-Ubuntu:
|
Build-Linux-Ubuntu:
|
||||||
name: 'Build-Linux-Ubuntu'
|
runs-on: ubuntu-22.04
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
@@ -75,13 +79,17 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-Windows:
|
Build-Windows:
|
||||||
name: Build-Windows
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
BUILD_ARCH: 64
|
BUILD_ARCH: 64
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
@@ -137,13 +145,17 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-iOS:
|
Build-iOS:
|
||||||
name: 'Build-iOS'
|
|
||||||
runs-on: macos-13
|
runs-on: macos-13
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
CC: cc
|
CC: cc
|
||||||
CXX: c++
|
CXX: c++
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
@@ -178,7 +190,7 @@ jobs:
|
|||||||
- name: 'Install go'
|
- name: 'Install go'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.24'
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: 'Setup gomobile'
|
- name: 'Setup gomobile'
|
||||||
@@ -205,7 +217,11 @@ jobs:
|
|||||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
|
||||||
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
|
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
|
||||||
export PATH=$PATH:~/go/bin
|
export PATH=$PATH:~/go/bin
|
||||||
sh deploy/build_ios.sh
|
sh deploy/build_ios.sh | \
|
||||||
|
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
|
||||||
|
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
|
||||||
|
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
|
||||||
|
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
|
||||||
env:
|
env:
|
||||||
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
||||||
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||||
@@ -228,19 +244,23 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-MacOS:
|
Build-MacOS:
|
||||||
name: 'Build-MacOS'
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||||
QT_VERSION: 6.4.3
|
QT_VERSION: 6.4.3
|
||||||
QIF_VERSION: 4.6
|
QIF_VERSION: 4.6
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '14.3.1'
|
xcode-version: '15.4.0'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
@@ -293,28 +313,33 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-Android:
|
Build-Android:
|
||||||
name: 'Build-Android'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.7.3
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'gcc_64'
|
arch: 'linux_gcc_64'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_x86_64 Qt'
|
- name: 'Install android_x86_64 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
@@ -322,10 +347,11 @@ jobs:
|
|||||||
arch: 'android_x86_64'
|
arch: 'android_x86_64'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_x86 Qt'
|
- name: 'Install android_x86 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
@@ -333,10 +359,11 @@ jobs:
|
|||||||
arch: 'android_x86'
|
arch: 'android_x86'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_armv7 Qt'
|
- name: 'Install android_armv7 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
@@ -344,10 +371,11 @@ jobs:
|
|||||||
arch: 'android_armv7'
|
arch: 'android_armv7'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_arm64_v8a Qt'
|
- name: 'Install android_arm64_v8a Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
@@ -355,7 +383,8 @@ jobs:
|
|||||||
arch: 'android_arm64_v8a'
|
arch: 'android_arm64_v8a'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Grant execute permission for qt-cmake'
|
- name: 'Grant execute permission for qt-cmake'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -439,3 +468,21 @@ jobs:
|
|||||||
path: deploy/build/AmneziaVPN-release.aab
|
path: deploy/build/AmneziaVPN-release.aab
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
Extra:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Search a corresponding PR
|
||||||
|
uses: octokit/request-action@v2.x
|
||||||
|
id: pull_request
|
||||||
|
with:
|
||||||
|
route: GET /repos/${{ github.repository }}/pulls
|
||||||
|
head: ${{ github.repository_owner }}:${{ github.ref_name }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Add PR link to build summary
|
||||||
|
if: ${{ fromJSON(steps.pull_request.outputs.data)[0].number != '' }}
|
||||||
|
run: |
|
||||||
|
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
QT_VERSION: 6.4.1
|
QT_VERSION: 6.4.1
|
||||||
QIF_VERSION: 4.5
|
QIF_VERSION: 4.5
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|||||||
@@ -1,64 +1,41 @@
|
|||||||
name: 'Upload a new version'
|
name: 'Upload a new version'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
tags:
|
inputs:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
RELEASE_VERSION:
|
||||||
|
description: 'Release version (e.g. 1.2.3.4)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload:
|
Upload-S3:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: upload
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout CMakeLists.txt
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref_name }}
|
ref: ${{ inputs.RELEASE_VERSION }}
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
|
deploy/deploy_s3.sh
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
- name: Verify git tag
|
- name: Verify git tag
|
||||||
run: |
|
run: |
|
||||||
GIT_TAG=${{ github.ref_name }}
|
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
||||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||||
|
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
|
||||||
else
|
else
|
||||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
- name: Setup Rclone
|
||||||
uses: robinraju/release-downloader@v1.8
|
uses: AnimMouse/setup-rclone@v1
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.ref_name }}
|
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
|
||||||
out-file-path: ${{ github.ref_name }}
|
|
||||||
|
|
||||||
- name: Upload beta version
|
- name: Send dist to S3
|
||||||
uses: jakejarvis/s3-sync-action@master
|
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
|
||||||
if: contains(github.event.base_ref, 'dev')
|
|
||||||
with:
|
|
||||||
args: --include "AmneziaVPN*" --delete
|
|
||||||
env:
|
|
||||||
AWS_S3_BUCKET: updates
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
||||||
SOURCE_DIR: ${{ github.ref_name }}
|
|
||||||
DEST_DIR: beta/${{ github.ref_name }}
|
|
||||||
|
|
||||||
- name: Upload stable version
|
|
||||||
uses: jakejarvis/s3-sync-action@master
|
|
||||||
if: contains(github.event.base_ref, 'master')
|
|
||||||
with:
|
|
||||||
args: --include "AmneziaVPN*" --delete
|
|
||||||
env:
|
|
||||||
AWS_S3_BUCKET: updates
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
||||||
SOURCE_DIR: ${{ github.ref_name }}
|
|
||||||
DEST_DIR: stable/${{ github.ref_name }}
|
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,3 @@
|
|||||||
[submodule "client/3rd/OpenVPNAdapter"]
|
|
||||||
path = client/3rd/OpenVPNAdapter
|
|
||||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
|
|
||||||
[submodule "client/3rd/qtkeychain"]
|
[submodule "client/3rd/qtkeychain"]
|
||||||
path = client/3rd/qtkeychain
|
path = client/3rd/qtkeychain
|
||||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||||
@@ -13,3 +10,6 @@
|
|||||||
[submodule "client/3rd/amneziawg-apple"]
|
[submodule "client/3rd/amneziawg-apple"]
|
||||||
path = client/3rd/amneziawg-apple
|
path = client/3rd/amneziawg-apple
|
||||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||||
|
[submodule "client/3rd/QSimpleCrypto"]
|
||||||
|
path = client/3rd/QSimpleCrypto
|
||||||
|
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.6.1.0
|
project(${PROJECT} VERSION 4.8.6.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 56)
|
set(APP_ANDROID_VERSION_CODE 2083)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -1,47 +1,51 @@
|
|||||||
# Amnezia VPN
|
# Amnezia VPN
|
||||||
## _The best client for self-hosted VPN_
|
|
||||||
|
### _The best client for self-hosted VPN_
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||||
|
|
||||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<br>
|
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||||
|
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
|
[](https://amnezia.org)
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
|
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_Linux_4.6.0.3.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
|
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.6.0.3"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
|
|
||||||
|
|
||||||
<br>
|
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
|
> [!TIP]
|
||||||
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
|
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
|
||||||
|
|
||||||
|
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||||
|
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||||
|
|
||||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||||
|
|
||||||
<br>
|
<br/>
|
||||||
|
|
||||||
|
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
- Very easy to use - enter your IP address, SSH login, password and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||||
- OpenVPN, Shadowsocks, WireGuard, and IKEv2 protocols support.
|
- Classic VPN-protocols: OpenVPN, WireGuard and IKEv2 protocols.
|
||||||
- Masking VPN with OpenVPN over Cloak plugin
|
- Protocols with traffic Masking (Obfuscation): OpenVPN over [Cloak](https://github.com/cbeuw/Cloak) plugin, Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||||
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
|
- Split tunneling support - add any sites to the client to enable VPN only for them or add Apps (only for Android and Desktop).
|
||||||
- Windows, MacOS, Linux, Android, iOS releases.
|
- Windows, MacOS, Linux, Android, iOS releases.
|
||||||
|
- Support for AmneziaWG protocol configuration on [Keenetic beta firmware](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [https://amnezia.org](https://amnezia.org) - project website
|
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||||
|
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||||
|
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
|
|
||||||
@@ -181,10 +185,11 @@ GPL v3.0
|
|||||||
|
|
||||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
|
|
||||||
|
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||||
|
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
This project is tested with BrowserStack.
|
This project is tested with BrowserStack.
|
||||||
|
|||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
# Amnezia VPN
|
||||||
|
|
||||||
|
### _Лучший клиент для создания VPN на собственном сервере_
|
||||||
|
|
||||||
|
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||||
|
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||||
|
|
||||||
|
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||||
|
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||||
|
|
||||||
|
[](https://amnezia.org)
|
||||||
|
|
||||||
|
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||||
|
|
||||||
|
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||||
|
|
||||||
|
|
||||||
|
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||||
|
|
||||||
|
## Особенности
|
||||||
|
|
||||||
|
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
|
||||||
|
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||||
|
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||||
|
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||||
|
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||||
|
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||||
|
|
||||||
|
## Ссылки
|
||||||
|
|
||||||
|
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||||
|
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||||
|
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||||
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||||
|
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||||
|
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||||
|
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||||
|
|
||||||
|
## Технологии
|
||||||
|
|
||||||
|
AmneziaVPN использует несколько проектов с открытым исходным кодом:
|
||||||
|
|
||||||
|
- [OpenSSL](https://www.openssl.org/)
|
||||||
|
- [OpenVPN](https://openvpn.net/)
|
||||||
|
- [Shadowsocks](https://shadowsocks.org/)
|
||||||
|
- [Qt](https://www.qt.io/)
|
||||||
|
- [LibSsh](https://libssh.org)
|
||||||
|
- и другие...
|
||||||
|
|
||||||
|
## Проверка исходного кода
|
||||||
|
После клонирования репозитория обязательно загрузите все подмодули.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
Хотите внести свой вклад? Добро пожаловать!
|
||||||
|
|
||||||
|
### Помощь с переводами
|
||||||
|
|
||||||
|
Загрузите самые актуальные файлы перевода.
|
||||||
|
|
||||||
|
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
|
||||||
|
|
||||||
|
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
|
||||||
|
|
||||||
|
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
|
||||||
|
|
||||||
|
### Сборка исходного кода и деплой
|
||||||
|
Проверьте папку deploy для скриптов сборки.
|
||||||
|
|
||||||
|
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||||
|
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||||
|
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||||
|
- MacOS
|
||||||
|
- iOS
|
||||||
|
- Модуль совместимости с Qt 5
|
||||||
|
- Qt Shader Tools
|
||||||
|
- Дополнительные библиотеки:
|
||||||
|
- Qt Image Formats
|
||||||
|
- Qt Multimedia
|
||||||
|
- Qt Remote Objects
|
||||||
|
|
||||||
|
|
||||||
|
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
|
||||||
|
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH=$PATH:~/go/bin
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
gomobile init
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Соберите проект:
|
||||||
|
```bash
|
||||||
|
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
||||||
|
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
||||||
|
export QT_IOS_BIN=$QT_BIN_DIR
|
||||||
|
export PATH=$PATH:~/go/bin
|
||||||
|
mkdir build-ios
|
||||||
|
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||||
|
```
|
||||||
|
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
|
||||||
|
|
||||||
|
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
|
||||||
|
```bash
|
||||||
|
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||||
|
|
||||||
|
Если сборка завершится с ошибкой:
|
||||||
|
```
|
||||||
|
make: ***
|
||||||
|
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||||
|
Error 1
|
||||||
|
```
|
||||||
|
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||||
|
|
||||||
|
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
|
||||||
|
```
|
||||||
|
arch -arm64 brew install cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
|
||||||
|
|
||||||
|
|
||||||
|
## Как собрать Android-приложение
|
||||||
|
Сборка тестировалась на MacOS. Требования:
|
||||||
|
- JDK 11
|
||||||
|
- Android SDK 33
|
||||||
|
- CMake 3.25.0
|
||||||
|
|
||||||
|
Установите QT, QT Creator и Android Studio.
|
||||||
|
Настройте QT Creator:
|
||||||
|
|
||||||
|
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
|
||||||
|
- Укажите путь к JDK 11.
|
||||||
|
- Укажите путь к Android SDK (`$ANDROID_HOME`)
|
||||||
|
|
||||||
|
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
|
||||||
|
|
||||||
|
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
|
||||||
|
|
||||||
|
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
|
||||||
|
|
||||||
|
### Разработка Android-компонентов
|
||||||
|
|
||||||
|
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
|
||||||
|
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
|
||||||
|
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
|
||||||
|
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
GPL v3.0
|
||||||
|
|
||||||
|
## Донаты
|
||||||
|
|
||||||
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
|
|
||||||
|
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||||
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||||
|
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||||
|
|
||||||
|
## Благодарности
|
||||||
|
|
||||||
|
Этот проект тестируется с помощью BrowserStack.
|
||||||
|
Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.
|
||||||
+1
-1
Submodule client/3rd-prebuilt updated: c38a587fcd...efad1a5b5c
Vendored
-1
Submodule client/3rd/OpenVPNAdapter deleted from dea6040996
+1
Submodule client/3rd/QSimpleCrypto added at c99b33f0e0
@@ -1,20 +0,0 @@
|
|||||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QAead.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QBlockCipher.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QCryptoError.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QRsa.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QSimpleCrypto_global.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509Store.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QAead.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QBlockCipher.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QCryptoError.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QRsa.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509Store.cpp
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
INCLUDEPATH += $$PWD
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
$$PWD/include/QAead.h \
|
|
||||||
$$PWD/include/QBlockCipher.h \
|
|
||||||
$$PWD/include/QCryptoError.h \
|
|
||||||
$$PWD/include/QRsa.h \
|
|
||||||
$$PWD/include/QSimpleCrypto_global.h \
|
|
||||||
$$PWD/include/QX509.h \
|
|
||||||
$$PWD/include/QX509Store.h
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
$$PWD/sources/QAead.cpp \
|
|
||||||
$$PWD/sources/QBlockCipher.cpp \
|
|
||||||
$$PWD/sources/QCryptoError.cpp \
|
|
||||||
$$PWD/sources/QRsa.cpp \
|
|
||||||
$$PWD/sources/QX509.cpp \
|
|
||||||
$$PWD/sources/QX509Store.cpp
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QAEAD_H
|
|
||||||
#define QAEAD_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/aes.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QAead {
|
|
||||||
public:
|
|
||||||
QAead();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encryptAesGcm - Function encrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decryptAesGcm - Function decrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted
|
|
||||||
/// \param key - AES key
|
|
||||||
/// \param iv - Initialization vector
|
|
||||||
/// \param tag - Authorization tag
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encryptAesCcm - Function encrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decryptAesCcm - Function decrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QAEAD_H
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QBLOCKCIPHER_H
|
|
||||||
#define QBLOCKCIPHER_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/aes.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QBlockCipher {
|
|
||||||
|
|
||||||
#define Aes128Rounds 10
|
|
||||||
#define Aes192Rounds 12
|
|
||||||
#define Aes256Rounds 14
|
|
||||||
|
|
||||||
public:
|
|
||||||
QBlockCipher();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief generateRandomBytes - Function generates random bytes by size.
|
|
||||||
/// \param size - Size of generated bytes.
|
|
||||||
/// \return Returns random bytes.
|
|
||||||
///
|
|
||||||
QByteArray generateRandomBytes(const int& size);
|
|
||||||
QByteArray generateSecureRandomBytes(const int& size);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Encryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
|
||||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Decryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
|
||||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QBLOCKCIPHER_H
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef QCRYPTOERROR_H
|
|
||||||
#define QCRYPTOERROR_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
/// TODO: Add Special error code for each error.
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QCryptoError : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QCryptoError(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setError - Sets error information
|
|
||||||
/// \param errorCode - Error code.
|
|
||||||
/// \param errorSummary - Error summary.
|
|
||||||
///
|
|
||||||
inline void setError(const quint8 errorCode, const QString& errorSummary)
|
|
||||||
{
|
|
||||||
m_currentErrorCode = errorCode;
|
|
||||||
m_errorSummary = errorSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief lastError - Returns last error.
|
|
||||||
/// \return Returns eror ID and error Text.
|
|
||||||
///
|
|
||||||
inline QPair<quint8, QString> lastError() const
|
|
||||||
{
|
|
||||||
return QPair<quint8, QString>(m_currentErrorCode, m_errorSummary);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
quint8 m_currentErrorCode;
|
|
||||||
QString m_errorSummary;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // QCRYPTOERROR_H
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QRSA_H
|
|
||||||
#define QRSA_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QRsa {
|
|
||||||
|
|
||||||
#define PublicEncrypt 0
|
|
||||||
#define PrivateEncrypt 1
|
|
||||||
#define PublicDecrypt 2
|
|
||||||
#define PrivateDecrypt 3
|
|
||||||
|
|
||||||
public:
|
|
||||||
QRsa();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
|
||||||
/// \param bits - RSA key size.
|
|
||||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
|
||||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
RSA* generateRsaKeys(const int& bits, const int& rsaBigNumber);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief savePublicKey - Saves to file RSA public key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param publicKeyFileName - Public key file name.
|
|
||||||
///
|
|
||||||
void savePublicKey(RSA *rsa, const QByteArray& publicKeyFileName);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief savePrivateKey - Saves to file RSA private key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param privateKeyFileName - Private key file name.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
///
|
|
||||||
void savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password = "", const EVP_CIPHER* cipher = nullptr);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief getPublicKeyFromFile - Gets RSA public key from a file.
|
|
||||||
/// \param filePath - File path to public key file.
|
|
||||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* getPublicKeyFromFile(const QByteArray& filePath);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief getPrivateKeyFromFile - Gets RSA private key from a file.
|
|
||||||
/// \param filePath - File path to private key file.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password = "");
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encrypt - Encrypt data with RSA algorithm.
|
|
||||||
/// \param plaintext - Text that must be encrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
|
||||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encrypt(QByteArray plainText, RSA* rsa, const int& encryptType = PublicEncrypt, const int& padding = RSA_PKCS1_PADDING);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decrypt - Decrypt data with RSA algorithm.
|
|
||||||
/// \param cipherText - Text that must be decrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
|
||||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return - Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType = PrivateDecrypt, const int& padding = RSA_PKCS1_PADDING);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QRSA_H
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#ifndef QSIMPLECRYPTO_GLOBAL_H
|
|
||||||
#define QSIMPLECRYPTO_GLOBAL_H
|
|
||||||
|
|
||||||
#include <QtCore/qglobal.h>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#define QSIMPLECRYPTO_EXPORT
|
|
||||||
|
|
||||||
#endif // QSIMPLECRYPTO_GLOBAL_H
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QX509_H
|
|
||||||
#define QX509_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QMap>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/x509.h>
|
|
||||||
#include <openssl/x509_vfy.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QX509 {
|
|
||||||
|
|
||||||
#define oneYear 31536000L
|
|
||||||
#define x509LastVersion 2
|
|
||||||
|
|
||||||
public:
|
|
||||||
QX509();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
|
||||||
/// \param fileName - File path to certificate.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* loadCertificateFromFile(const QByteArray& fileName);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
|
||||||
/// \param endCertificate - Certificate that will be signed
|
|
||||||
/// \param caCertificate - CA certificate that will sign end certificate
|
|
||||||
/// \param caPrivateKey - CA certificate private key
|
|
||||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
|
||||||
///
|
|
||||||
X509* signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName = "");
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
|
||||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
|
||||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
|
||||||
///
|
|
||||||
X509* verifyCertificate(X509* x509, X509_STORE* store);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
|
||||||
/// \param rsa - OpenSSL RSA.
|
|
||||||
/// \param additionalData - Certificate information.
|
|
||||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
|
||||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
|
||||||
/// \param serialNumber - X509 certificate serial number.
|
|
||||||
/// \param version - X509 certificate version.
|
|
||||||
/// \param notBefore - X509 start date.
|
|
||||||
/// \param notAfter - X509 end date.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
|
||||||
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
|
||||||
const long& serialNumber = 1, const long& version = x509LastVersion,
|
|
||||||
const long& notBefore = 0, const long& notAfter = oneYear);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QX509_H
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QX509STORE_H
|
|
||||||
#define QX509STORE_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/x509_vfy.h>
|
|
||||||
#include <openssl/x509v3.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QX509Store {
|
|
||||||
public:
|
|
||||||
QX509Store();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief addCertificateToStore
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param x509 - OpenSSL X509.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool addCertificateToStore(X509_STORE* store, X509* x509);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief addLookup
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setCertificateDepth
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setDepth(X509_STORE* store, const int& depth);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setFlag(X509_STORE* store, const unsigned long& flag);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setPurpose(X509_STORE* store, const int& purpose);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setTrust
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setTrust(X509_STORE* store, const int& trust);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setDefaultPaths
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setDefaultPaths(X509_STORE* store);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
|
||||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param file - Qt QFile that will be loaded.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool loadLocations(X509_STORE* store, const QFile& file);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileInfo - Qt QFileInfo.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool loadLocations(X509_STORE* store, const QFileInfo& fileInfo);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // QX509STORE_H
|
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QAead.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QAead::QAead()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::encryptAesGcm - Function encrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (encryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int plainTextLength = data.size();
|
|
||||||
int cipherTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize cipherText. Here encrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
|
||||||
if (cipherText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize encryption operation. */
|
|
||||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Check if aad need to be used */
|
|
||||||
// if (aad.length() > 0) {
|
|
||||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
// if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
// throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
|
||||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the encryption. Normally cipher text bytes may be written at
|
|
||||||
* this stage, but this does not occur in GCM mode
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Get tag */
|
|
||||||
// if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
// throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::decryptAesGcm - Function decrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted
|
|
||||||
/// \param key - AES key
|
|
||||||
/// \param iv - Initialization vector
|
|
||||||
/// \param tag - Authorization tag
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (decryptionCipher.get() == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int cipherTextLength = data.size();
|
|
||||||
int plainTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize plainText. Here decrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize decryption operation. */
|
|
||||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Check if aad need to be used */
|
|
||||||
// if (aad.length() > 0) {
|
|
||||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
// if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
// throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be decrypted, and obtain the plain text output.
|
|
||||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
|
||||||
// if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
// throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the decryption. A positive return value indicates success,
|
|
||||||
* anything else is a failure - the plain text is not trustworthy.
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::encryptAesCcm - Function encrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (encryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int plainTextLength = data.size();
|
|
||||||
int cipherTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize cipherText. Here encrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
|
||||||
if (cipherText.get() == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize encryption operation. */
|
|
||||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set tag length */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if aad need to be used */
|
|
||||||
if (aad.length() > 0) {
|
|
||||||
/* Provide the total plain text length */
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, nullptr, plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
|
||||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the encryption. Normally ciphertext bytes may be written at
|
|
||||||
* this stage, but this does not occur in GCM mode
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get tag */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::decryptAesCcm - Function decrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (decryptionCipher.get() == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int cipherTextLength = data.size();
|
|
||||||
int plainTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize plainText. Here decrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize decryption operation. */
|
|
||||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if aad need to be used */
|
|
||||||
if (aad.length() > 0) {
|
|
||||||
/* Provide the total ciphertext length */
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, nullptr, cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
|
||||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the decryption. A positive return value indicates success,
|
|
||||||
* anything else is a failure - the plaintext is not trustworthy.
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QBlockCipher.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher::QBlockCipher()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QBlockCipher::generateRandomBytes - Function generates random bytes by size.
|
|
||||||
/// \param size - Size of generated bytes.
|
|
||||||
/// \return Returns random bytes.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::generateRandomBytes(const int& size)
|
|
||||||
{
|
|
||||||
unsigned char arr[sizeof(size)];
|
|
||||||
RAND_bytes(arr, sizeof(size));
|
|
||||||
|
|
||||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::generateSecureRandomBytes(const int &size)
|
|
||||||
{
|
|
||||||
unsigned char arr[sizeof(size)];
|
|
||||||
RAND_priv_bytes(arr, sizeof(size));
|
|
||||||
|
|
||||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Encryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::encryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv,
|
|
||||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (encryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reinterpret values for multi use */
|
|
||||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
|
||||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int cipherTextLength(data.size() + AES_BLOCK_SIZE);
|
|
||||||
int finalLength = 0;
|
|
||||||
|
|
||||||
/* Initialize cipcherText. Here encrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[cipherTextLength]() };
|
|
||||||
if (cipherText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug here
|
|
||||||
// /* Start encryption with password based encryption routine */
|
|
||||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<unsigned char*>(salt.data()), reinterpret_cast<unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
|
||||||
// throw std::runtime_error("Couldn't start encryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Initialize encryption operation. */
|
|
||||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
|
||||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
|
||||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finalize the encryption. Normally ciphertext bytes may be written at this stage */
|
|
||||||
if (!EVP_EncryptFinal(encryptionCipher.get(), cipherText.get() + cipherTextLength, &finalLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength + finalLength);
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Decryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::decryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv,
|
|
||||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (decryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reinterpret values for multi use */
|
|
||||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
|
||||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int plainTextLength(data.size());
|
|
||||||
int finalLength = 0;
|
|
||||||
|
|
||||||
/* Initialize plainText. Here decrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[plainTextLength + AES_BLOCK_SIZE]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for \'plainText\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug here
|
|
||||||
// /* Start encryption with password based encryption routine */
|
|
||||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<const unsigned char*>(salt.data()), reinterpret_cast<const unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
|
||||||
// throw std::runtime_error("Couldn't start decryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Initialize decryption operation. */
|
|
||||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
|
||||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
|
||||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the decryption. A positive return value indicates success,
|
|
||||||
* anything else is a failure - the plaintext is not trustworthy.
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptFinal(decryptionCipher.get(), plainText.get() + plainTextLength, &finalLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength + finalLength);
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
|
||||||
return QByteArray(exception.what());
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#include "include/QCryptoError.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QCryptoError::QCryptoError(QObject* parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QRsa.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QRsa::QRsa()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
|
||||||
/// \param bits - RSA key size.
|
|
||||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
|
||||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
RSA* QSimpleCrypto::QRsa::generateRsaKeys(const int& bits, const int& rsaBigNumber)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize big number */
|
|
||||||
std::unique_ptr<BIGNUM, void (*)(BIGNUM*)> bigNumber { BN_new(), BN_free };
|
|
||||||
if (bigNumber == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'bigNumber\'. BN_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set big number */
|
|
||||||
if (!BN_set_word(bigNumber.get(), rsaBigNumber)) {
|
|
||||||
throw std::runtime_error("Couldn't set bigNumber. BN_set_word(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize RSA */
|
|
||||||
RSA* rsa = nullptr;
|
|
||||||
if (!(rsa = RSA_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize x509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generate key pair and store it in RSA */
|
|
||||||
if (!RSA_generate_key_ex(rsa, bits, bigNumber.get(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't generate RSA. RSA_generate_key_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return rsa;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::savePublicKey - Saves to file RSA public key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param publicKeyFileName - Public key file name.
|
|
||||||
///
|
|
||||||
void QSimpleCrypto::QRsa::savePublicKey(RSA* rsa, const QByteArray& publicKeyFileName)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(publicKeyFileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (bioPublicKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'bioPublicKey\'. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write public key on file */
|
|
||||||
if (!PEM_write_bio_RSA_PUBKEY(bioPublicKey.get(), rsa)) {
|
|
||||||
throw std::runtime_error("Couldn't save public key. PEM_write_bio_RSAPublicKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::savePrivateKey - Saves to file RSA private key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param privateKeyFileName - Private key file name.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
///
|
|
||||||
void QSimpleCrypto::QRsa::savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(privateKeyFileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (bioPrivateKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write private key to file */
|
|
||||||
if (!PEM_write_bio_RSAPrivateKey(bioPrivateKey.get(), rsa, cipher, reinterpret_cast<unsigned char*>(password.data()), password.size(), nullptr, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't save private key. PEM_write_bio_RSAPrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::getPublicKeyFromFile - Gets RSA public key from a file.
|
|
||||||
/// \param filePath - File path to public key file.
|
|
||||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* QSimpleCrypto::QRsa::getPublicKeyFromFile(const QByteArray& filePath)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
|
||||||
if (bioPublicKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize bioPublicKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize EVP_PKEY */
|
|
||||||
EVP_PKEY* keyStore = nullptr;
|
|
||||||
if (!(keyStore = EVP_PKEY_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write private key to file */
|
|
||||||
if (!PEM_read_bio_PUBKEY(bioPublicKey.get(), &keyStore, nullptr, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyStore;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::getPrivateKeyFromFile - Gets RSA private key from a file.
|
|
||||||
/// \param filePath - File path to private key file.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* QSimpleCrypto::QRsa::getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
|
||||||
if (bioPrivateKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize EVP_PKEY */
|
|
||||||
EVP_PKEY* keyStore = nullptr;
|
|
||||||
if (!(keyStore = EVP_PKEY_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write private key to file */
|
|
||||||
if (!PEM_read_bio_PrivateKey(bioPrivateKey.get(), &keyStore, nullptr, (void*)password.data())) {
|
|
||||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyStore;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::encrypt - Encrypt data with RSA algorithm.
|
|
||||||
/// \param plaintext - Text that must be encrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
|
||||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QRsa::encrypt(QByteArray plainText, RSA* rsa, const int& encryptType, const int& padding)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize array. Here encrypted data will be saved */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[RSA_size(rsa)]() };
|
|
||||||
if (cipherText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Result of encryption operation */
|
|
||||||
short int result = 0;
|
|
||||||
|
|
||||||
/* Execute encryption operation */
|
|
||||||
if (encryptType == PublicDecrypt) {
|
|
||||||
result = RSA_public_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
|
||||||
} else if (encryptType == PrivateDecrypt) {
|
|
||||||
result = RSA_private_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for result */
|
|
||||||
if (result <= -1) {
|
|
||||||
throw std::runtime_error("Couldn't encrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get encrypted data */
|
|
||||||
const QByteArray& encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), RSA_size(rsa));
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return "";
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::decrypt - Decrypt data with RSA algorithm.
|
|
||||||
/// \param cipherText - Text that must be decrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
|
||||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return - Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QRsa::decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType, const int& padding)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize array. Here decrypted data will be saved */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherText.size()]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'plainText'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Result of decryption operation */
|
|
||||||
short int result = 0;
|
|
||||||
|
|
||||||
/* Execute decryption operation */
|
|
||||||
if (decryptType == PublicDecrypt) {
|
|
||||||
result = RSA_public_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
|
||||||
} else if (decryptType == PrivateDecrypt) {
|
|
||||||
result = RSA_private_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for result */
|
|
||||||
if (result <= -1) {
|
|
||||||
throw std::runtime_error("Couldn't decrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get decrypted data */
|
|
||||||
const QByteArray& decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()));
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return "";
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QX509.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QX509::QX509()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
|
||||||
/// \param fileName - File path to certificate.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::loadCertificateFromFile(const QByteArray& fileName)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize X509 */
|
|
||||||
X509* x509 = nullptr;
|
|
||||||
if (!(x509 = X509_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "r+"), BIO_free_all };
|
|
||||||
if (certFile == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read file */
|
|
||||||
if (!PEM_read_bio_X509(certFile.get(), &x509, nullptr, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't read certificate file from disk. PEM_read_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
|
||||||
/// \param endCertificate - Certificate that will be signed
|
|
||||||
/// \param caCertificate - CA certificate that will sign end certificate
|
|
||||||
/// \param caPrivateKey - CA certificate private key
|
|
||||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Set issuer to CA's subject. */
|
|
||||||
if (!X509_set_issuer_name(endCertificate, X509_get_subject_name(caCertificate))) {
|
|
||||||
throw std::runtime_error("Couldn't set issuer name for X509. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sign the certificate with key. */
|
|
||||||
if (!X509_sign(endCertificate, caPrivateKey, EVP_sha256())) {
|
|
||||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write certificate file on disk. If needed */
|
|
||||||
if (!fileName.isEmpty()) {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (certFile == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write file on disk */
|
|
||||||
if (!PEM_write_bio_X509(certFile.get(), endCertificate)) {
|
|
||||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return endCertificate;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
|
||||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
|
||||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize X509_STORE_CTX */
|
|
||||||
std::unique_ptr<X509_STORE_CTX, void (*)(X509_STORE_CTX*)> ctx { X509_STORE_CTX_new(), X509_STORE_CTX_free };
|
|
||||||
if (ctx == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set up CTX for a subsequent verification operation */
|
|
||||||
if (!X509_STORE_CTX_init(ctx.get(), store, x509, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509_STORE_CTX. X509_STORE_CTX_init(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify X509 */
|
|
||||||
if (!X509_verify_cert(ctx.get())) {
|
|
||||||
throw std::runtime_error("Couldn't verify cert. X509_verify_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
|
||||||
/// \param rsa - OpenSSL RSA.
|
|
||||||
/// \param additionalData - Certificate information.
|
|
||||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
|
||||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
|
||||||
/// \param serialNumber - X509 certificate serial number.
|
|
||||||
/// \param version - X509 certificate version.
|
|
||||||
/// \param notBefore - X509 start date.
|
|
||||||
/// \param notAfter - X509 end date.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
|
||||||
const QByteArray& certificateFileName, const EVP_MD* md,
|
|
||||||
const long& serialNumber, const long& version,
|
|
||||||
const long& notBefore, const long& notAfter)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize X509 */
|
|
||||||
X509* x509 = nullptr;
|
|
||||||
if (!(x509 = X509_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize EVP_PKEY */
|
|
||||||
std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> keyStore { EVP_PKEY_new(), EVP_PKEY_free };
|
|
||||||
if (keyStore == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sign rsa key */
|
|
||||||
if (!EVP_PKEY_assign_RSA(keyStore.get(), rsa)) {
|
|
||||||
throw std::runtime_error("Couldn't assign rsa. EVP_PKEY_assign_RSA(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate serial number. */
|
|
||||||
if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)) {
|
|
||||||
throw std::runtime_error("Couldn't set serial number. ASN1_INTEGER_set(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate version */
|
|
||||||
if (!X509_set_version(x509, version)) {
|
|
||||||
throw std::runtime_error("Couldn't set version. X509_set_version(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate creation and expiration date */
|
|
||||||
X509_gmtime_adj(X509_get_notBefore(x509), notBefore);
|
|
||||||
X509_gmtime_adj(X509_get_notAfter(x509), notAfter);
|
|
||||||
|
|
||||||
/* Set certificate public key */
|
|
||||||
if (!X509_set_pubkey(x509, keyStore.get())) {
|
|
||||||
throw std::runtime_error("Couldn't set public key. X509_set_pubkey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize X509_NAME */
|
|
||||||
X509_NAME* x509Name = X509_get_subject_name(x509);
|
|
||||||
if (x509Name == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509_NAME. X509_NAME(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add additional data to certificate */
|
|
||||||
QMapIterator<QByteArray, QByteArray> certificateInformationList(additionalData);
|
|
||||||
while (certificateInformationList.hasNext()) {
|
|
||||||
/* Read next item in list */
|
|
||||||
certificateInformationList.next();
|
|
||||||
|
|
||||||
/* Set additional data */
|
|
||||||
if (!X509_NAME_add_entry_by_txt(x509Name, certificateInformationList.key().data(), MBSTRING_UTF8, reinterpret_cast<const unsigned char*>(certificateInformationList.value().data()), -1, -1, 0)) {
|
|
||||||
throw std::runtime_error("Couldn't set additional information. X509_NAME_add_entry_by_txt(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate info */
|
|
||||||
if (!X509_set_issuer_name(x509, x509Name)) {
|
|
||||||
throw std::runtime_error("Couldn't set issuer name. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sign certificate */
|
|
||||||
if (!X509_sign(x509, keyStore.get(), md)) {
|
|
||||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write certificate file on disk. If needed */
|
|
||||||
if (!certificateFileName.isEmpty()) {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(certificateFileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (certFile == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write file on disk */
|
|
||||||
if (!PEM_write_bio_X509(certFile.get(), x509)) {
|
|
||||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QX509Store.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QX509Store::QX509Store()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::addCertificateToStore
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param x509 - OpenSSL X509.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::addCertificateToStore(X509_STORE* store, X509* x509)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_add_cert(store, x509)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add certificate to X509_STORE. X509_STORE_add_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::addLookup
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_add_lookup(store, method)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add lookup to X509_STORE. X509_STORE_add_lookup(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setCertificateDepth
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setDepth(X509_STORE* store, const int& depth)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_depth(store, depth)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set depth for X509_STORE. X509_STORE_set_depth(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setFlag(X509_STORE* store, const unsigned long& flag)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_flags(store, flag)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set flag for X509_STORE. X509_STORE_set_flags(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setPurpose(X509_STORE* store, const int& purpose)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_purpose(store, purpose)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set purpose for X509_STORE. X509_STORE_set_purpose(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setTrust
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setTrust(X509_STORE* store, const int& trust)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_trust(store, trust)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set trust for X509_STORE. X509_STORE_set_trust(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setDefaultPaths
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setDefaultPaths(X509_STORE* store)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_default_paths(store)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set default paths for X509_STORE. X509_STORE_set_default_paths(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
|
||||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_load_locations(store, fileName, dirPath)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param file - Qt QFile that will be loaded.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFile& file)
|
|
||||||
{
|
|
||||||
/* Initialize QFileInfo to read information about file */
|
|
||||||
QFileInfo info(file);
|
|
||||||
|
|
||||||
if (!X509_STORE_load_locations(store, info.fileName().toLocal8Bit(), info.absoluteDir().path().toLocal8Bit())) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileInfo - Qt QFileInfo.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFileInfo& fileInfo)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_load_locations(store, fileInfo.fileName().toLocal8Bit(), fileInfo.absoluteDir().path().toLocal8Bit())) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS
|
|
||||||
Core Network
|
|
||||||
)
|
|
||||||
set(LIBS ${LIBS} Qt6::Core Qt6::Network)
|
|
||||||
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
if(MSVC)
|
|
||||||
set(LIBS ${LIBS} Advapi32.lib)
|
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
|
||||||
set(LIBS ${LIBS} advapi32)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
#include <QtCore/QElapsedTimer>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QSharedMemory>
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
|
||||||
* if another instance already exists
|
|
||||||
* @param argc
|
|
||||||
* @param argv
|
|
||||||
* @param allowSecondary Whether to enable secondary instance support
|
|
||||||
* @param options Optional flags to toggle specific behaviour
|
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
|
||||||
*/
|
|
||||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
|
|
||||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
// On Android and iOS since the library is not supported fallback to
|
|
||||||
// standard QApplication behaviour by simply returning at this point.
|
|
||||||
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Store the current mode of the program
|
|
||||||
d->options = options;
|
|
||||||
|
|
||||||
// Add any unique user data
|
|
||||||
if ( ! userData.isEmpty() )
|
|
||||||
d->addAppData( userData );
|
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory
|
|
||||||
// block and QLocalServer
|
|
||||||
d->genBlockServerName();
|
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes
|
|
||||||
// attempting to attach at the same time
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
// By explicitly attaching it and then deleting it we make sure that the
|
|
||||||
// memory is deleted even after the process has crashed on Unix.
|
|
||||||
d->memory = new QSharedMemory( d->blockServerName );
|
|
||||||
d->memory->attach();
|
|
||||||
delete d->memory;
|
|
||||||
#endif
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
d->memory = new QSharedMemory( d->blockServerName );
|
|
||||||
|
|
||||||
// Create a shared memory block
|
|
||||||
if( d->memory->create( sizeof( InstancesInfo ) )){
|
|
||||||
// Initialize the shared memory block
|
|
||||||
if( ! d->memory->lock() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
} else {
|
|
||||||
if( d->memory->error() == QSharedMemory::AlreadyExists ){
|
|
||||||
// Attempt to attach to the memory segment
|
|
||||||
if( ! d->memory->attach() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if( ! d->memory->lock() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCritical() << "SingleApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
|
||||||
while( true ){
|
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if( d->blockChecksum() == inst->checksum ) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and
|
|
||||||
// assume it's position
|
|
||||||
if( time.elapsed() > 5000 ){
|
|
||||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again. The random sleep here
|
|
||||||
// limits the probability of a collision between two racing apps and
|
|
||||||
// allows the app to initialise faster
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
if( ! d->memory->lock() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( inst->primary == false ){
|
|
||||||
d->startPrimary();
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if another instance can be started
|
|
||||||
if( allowSecondary ){
|
|
||||||
d->startSecondary();
|
|
||||||
if( d->options & Mode::SecondaryNotification ){
|
|
||||||
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
|
||||||
}
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit( EXIT_SUCCESS );
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleApplication::~SingleApplication()
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is primary.
|
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isPrimary() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->server != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is secondary.
|
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isSecondary() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->server == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to identify an instance by returning unique consecutive instance
|
|
||||||
* ids. It is reset when the first (primary) instance of your app starts and
|
|
||||||
* only incremented afterwards.
|
|
||||||
* @return Returns a unique instance id.
|
|
||||||
*/
|
|
||||||
quint32 SingleApplication::instanceId() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->instanceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the OS PID (Process Identifier) of the process running the primary
|
|
||||||
* instance. Especially useful when SingleApplication is coupled with OS.
|
|
||||||
* specific APIs.
|
|
||||||
* @return Returns the primary instance PID.
|
|
||||||
*/
|
|
||||||
qint64 SingleApplication::primaryPid() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->primaryPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the primary instance is running as.
|
|
||||||
* @return Returns the username the primary instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::primaryUser() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->primaryUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the current instance is running as.
|
|
||||||
* @return Returns the username the current instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::currentUser() const
|
|
||||||
{
|
|
||||||
return SingleApplicationPrivate::getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to the Primary Instance.
|
|
||||||
* @param message The message to send.
|
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
|
||||||
* @return true if the message was sent successfuly, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
|
|
||||||
// Nobody to connect to
|
|
||||||
if( isPrimary() ) return false;
|
|
||||||
|
|
||||||
// Make sure the socket is connected
|
|
||||||
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
d->socket->write( message );
|
|
||||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
|
||||||
d->socket->flush();
|
|
||||||
return dataWritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleApplication::abortSafely()
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
|
|
||||||
delete d;
|
|
||||||
::exit( EXIT_FAILURE );
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SingleApplication::userData() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->appData();
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef SINGLE_APPLICATION_H
|
|
||||||
#define SINGLE_APPLICATION_H
|
|
||||||
|
|
||||||
#include <QtCore/QtGlobal>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
|
|
||||||
#ifndef QAPPLICATION_CLASS
|
|
||||||
#define QAPPLICATION_CLASS QApplication
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
|
||||||
|
|
||||||
class SingleApplicationPrivate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The SingleApplication class handles multiple instances of the same
|
|
||||||
* Application
|
|
||||||
* @see QCoreApplication
|
|
||||||
*/
|
|
||||||
class SingleApplication : public QAPPLICATION_CLASS
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using app_t = QAPPLICATION_CLASS;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Mode of operation of SingleApplication.
|
|
||||||
* Whether the block should be user-wide or system-wide and whether the
|
|
||||||
* primary instance should be notified when a secondary instance had been
|
|
||||||
* started.
|
|
||||||
* @note Operating system can restrict the shared memory blocks to the same
|
|
||||||
* user, in which case the User/System modes will have no effect and the
|
|
||||||
* block will be user wide.
|
|
||||||
* @enum
|
|
||||||
*/
|
|
||||||
enum Mode {
|
|
||||||
User = 1 << 0,
|
|
||||||
System = 1 << 1,
|
|
||||||
SecondaryNotification = 1 << 2,
|
|
||||||
ExcludeAppVersion = 1 << 3,
|
|
||||||
ExcludeAppPath = 1 << 4
|
|
||||||
};
|
|
||||||
Q_DECLARE_FLAGS(Options, Mode)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Intitializes a SingleApplication instance with argc command line
|
|
||||||
* arguments in argv
|
|
||||||
* @arg {int &} argc - Number of arguments in argv
|
|
||||||
* @arg {const char *[]} argv - Supplied command line arguments
|
|
||||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
|
||||||
* if there is already a primary instance.
|
|
||||||
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
|
||||||
* User wide or System wide.
|
|
||||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
|
||||||
* @note argc and argv may be changed as Qt removes arguments that it
|
|
||||||
* recognizes
|
|
||||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
|
||||||
* instance and the secondary instance.
|
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
|
||||||
* operations. It does not guarantee that the SingleApplication
|
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
|
||||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
|
||||||
*/
|
|
||||||
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
|
|
||||||
~SingleApplication() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is the primary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isPrimary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is a secondary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isSecondary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a unique identifier for the current instance
|
|
||||||
* @returns {qint32}
|
|
||||||
*/
|
|
||||||
quint32 instanceId() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
|
||||||
* @returns {qint64}
|
|
||||||
*/
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the user running the primary instance
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString primaryUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the current user
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString currentUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
|
||||||
* @param {int} timeout - Timeout for connecting
|
|
||||||
* @returns {bool}
|
|
||||||
* @note sendMessage() will return false if invoked from the primary
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
bool sendMessage( const QByteArray &message, int timeout = 100 );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the set user data.
|
|
||||||
* @returns {QStringList}
|
|
||||||
*/
|
|
||||||
QStringList userData() const;
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void instanceStarted();
|
|
||||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
|
||||||
|
|
||||||
private:
|
|
||||||
SingleApplicationPrivate *d_ptr;
|
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
|
||||||
void abortSafely();
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
|
||||||
|
|
||||||
#endif // SINGLE_APPLICATION_H
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
QT += core network
|
|
||||||
CONFIG += c++11
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
$$PWD/singleapplication.h \
|
|
||||||
$$PWD/singleapplication_p.h
|
|
||||||
SOURCES += $$PWD/singleapplication.cpp \
|
|
||||||
$$PWD/singleapplication_p.cpp
|
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD
|
|
||||||
|
|
||||||
win32 {
|
|
||||||
msvc:LIBS += Advapi32.lib
|
|
||||||
gcc:LIBS += -ladvapi32
|
|
||||||
}
|
|
||||||
@@ -1,486 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This file is not part of the SingleApplication API. It is used purely as an
|
|
||||||
// implementation detail. This header file may change from version to
|
|
||||||
// version without notice, or may even be removed.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include <QtCore/QDir>
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QDataStream>
|
|
||||||
#include <QtCore/QElapsedTimer>
|
|
||||||
#include <QtCore/QCryptographicHash>
|
|
||||||
#include <QtNetwork/QLocalServer>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
#include <QtCore/QRandomGenerator>
|
|
||||||
#else
|
|
||||||
#include <QtCore/QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX 1
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
|
||||||
: q_ptr( q_ptr )
|
|
||||||
{
|
|
||||||
server = nullptr;
|
|
||||||
socket = nullptr;
|
|
||||||
memory = nullptr;
|
|
||||||
instanceNumber = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
|
||||||
{
|
|
||||||
if( socket != nullptr ){
|
|
||||||
socket->close();
|
|
||||||
delete socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( memory != nullptr ){
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>(memory->data());
|
|
||||||
if( server != nullptr ){
|
|
||||||
server->close();
|
|
||||||
delete server;
|
|
||||||
inst->primary = false;
|
|
||||||
inst->primaryPid = -1;
|
|
||||||
inst->primaryUser[0] = '\0';
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
}
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
delete memory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleApplicationPrivate::getUsername()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if( GetUserNameW( username, &usernameLength ) )
|
|
||||||
return QString::fromWCharArray( username );
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
|
|
||||||
#else
|
|
||||||
return qEnvironmentVariable( "USERNAME" );
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
QString username;
|
|
||||||
uid_t uid = geteuid();
|
|
||||||
struct passwd *pw = getpwuid( uid );
|
|
||||||
if( pw )
|
|
||||||
username = QString::fromLocal8Bit( pw->pw_name );
|
|
||||||
if ( username.isEmpty() ){
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
|
|
||||||
#else
|
|
||||||
username = qEnvironmentVariable( "USER" );
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::genBlockServerName()
|
|
||||||
{
|
|
||||||
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
|
||||||
appData.addData( "SingleApplication", 17 );
|
|
||||||
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
|
||||||
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
|
||||||
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
|
||||||
|
|
||||||
if ( ! appDataList.isEmpty() )
|
|
||||||
appData.addData( appDataList.join( "" ).toUtf8() );
|
|
||||||
|
|
||||||
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
|
|
||||||
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
|
||||||
#else
|
|
||||||
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
|
||||||
if( options & SingleApplication::Mode::User ){
|
|
||||||
appData.addData( getUsername().toUtf8() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
|
||||||
// server naming requirements.
|
|
||||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() const
|
|
||||||
{
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
inst->primary = false;
|
|
||||||
inst->secondary = 0;
|
|
||||||
inst->primaryPid = -1;
|
|
||||||
inst->primaryUser[0] = '\0';
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary()
|
|
||||||
{
|
|
||||||
// Reset the number of connections
|
|
||||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
|
||||||
|
|
||||||
inst->primary = true;
|
|
||||||
inst->primaryPid = QCoreApplication::applicationPid();
|
|
||||||
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
instanceNumber = 0;
|
|
||||||
// Successful creation means that no main process exists
|
|
||||||
// So we start a QLocalServer to listen for connections
|
|
||||||
QLocalServer::removeServer( blockServerName );
|
|
||||||
server = new QLocalServer();
|
|
||||||
|
|
||||||
// Restrict access to the socket according to the
|
|
||||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if( options & SingleApplication::Mode::User ){
|
|
||||||
server->setSocketOptions( QLocalServer::UserAccessOption );
|
|
||||||
} else {
|
|
||||||
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
|
||||||
}
|
|
||||||
|
|
||||||
server->listen( blockServerName );
|
|
||||||
QObject::connect(
|
|
||||||
server,
|
|
||||||
&QLocalServer::newConnection,
|
|
||||||
this,
|
|
||||||
&SingleApplicationPrivate::slotConnectionEstablished
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary()
|
|
||||||
{
|
|
||||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
|
||||||
|
|
||||||
inst->secondary += 1;
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
instanceNumber = inst->secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
|
||||||
{
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already
|
|
||||||
// connected.
|
|
||||||
if( socket == nullptr ){
|
|
||||||
socket = new QLocalSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( socket->state() == QLocalSocket::ConnectedState ) return true;
|
|
||||||
|
|
||||||
if( socket->state() != QLocalSocket::ConnectedState ){
|
|
||||||
|
|
||||||
while( true ){
|
|
||||||
randomSleep();
|
|
||||||
|
|
||||||
if( socket->state() != QLocalSocket::ConnectingState )
|
|
||||||
socket->connectToServer( blockServerName );
|
|
||||||
|
|
||||||
if( socket->state() == QLocalSocket::ConnectingState ){
|
|
||||||
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// If connected break out of the loop
|
|
||||||
if( socket->state() == QLocalSocket::ConnectedState ) break;
|
|
||||||
|
|
||||||
// If elapsed time since start is longer than the method timeout return
|
|
||||||
if( time.elapsed() >= msecs ) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisation message according to the SingleApplication protocol
|
|
||||||
QByteArray initMsg;
|
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
writeStream << blockServerName.toLatin1();
|
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber;
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
|
||||||
#endif
|
|
||||||
writeStream << checksum;
|
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
#endif
|
|
||||||
headerStream << static_cast <quint64>( initMsg.length() );
|
|
||||||
|
|
||||||
socket->write( header );
|
|
||||||
socket->write( initMsg );
|
|
||||||
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
|
|
||||||
socket->flush();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() const
|
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
|
|
||||||
#endif
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const
|
|
||||||
{
|
|
||||||
qint64 pid;
|
|
||||||
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleApplicationPrivate::primaryUser() const
|
|
||||||
{
|
|
||||||
QByteArray username;
|
|
||||||
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
username = inst->primaryUser;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return QString::fromUtf8( username );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Executed when a connection has been made to the LocalServer
|
|
||||||
*/
|
|
||||||
void SingleApplicationPrivate::slotConnectionEstablished()
|
|
||||||
{
|
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
|
||||||
[nextConnSocket, this](){
|
|
||||||
auto &info = connectionMap[nextConnSocket];
|
|
||||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
|
|
||||||
[nextConnSocket, this](){
|
|
||||||
connectionMap.remove(nextConnSocket);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
|
||||||
[nextConnSocket, this](){
|
|
||||||
auto &info = connectionMap[nextConnSocket];
|
|
||||||
switch(info.stage){
|
|
||||||
case StageHeader:
|
|
||||||
readInitMessageHeader(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageBody:
|
|
||||||
readInitMessageBody(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageConnected:
|
|
||||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
|
||||||
{
|
|
||||||
if (!connectionMap.contains( sock )){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream headerStream( sock );
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
headerStream.setVersion( QDataStream::Qt_5_6 );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
|
||||||
quint64 msgLen = 0;
|
|
||||||
headerStream >> msgLen;
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
|
||||||
info.stage = StageBody;
|
|
||||||
info.msgLen = msgLen;
|
|
||||||
|
|
||||||
if ( sock->bytesAvailable() >= (qint64) msgLen ){
|
|
||||||
readInitMessageBody( sock );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
|
||||||
{
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
|
|
||||||
if (!connectionMap.contains( sock )){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
|
||||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the message body
|
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
|
||||||
QDataStream readStream(msgBytes);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
readStream.setVersion( QDataStream::Qt_5_6 );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// server name
|
|
||||||
QByteArray latin1Name;
|
|
||||||
readStream >> latin1Name;
|
|
||||||
|
|
||||||
// connection type
|
|
||||||
ConnectionType connectionType = InvalidConnection;
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
|
||||||
readStream >> connTypeVal;
|
|
||||||
connectionType = static_cast <ConnectionType>( connTypeVal );
|
|
||||||
|
|
||||||
// instance id
|
|
||||||
quint32 instanceId = 0;
|
|
||||||
readStream >> instanceId;
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
quint16 msgChecksum = 0;
|
|
||||||
readStream >> msgChecksum;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
|
||||||
#else
|
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool isValid = readStream.status() == QDataStream::Ok &&
|
|
||||||
QLatin1String(latin1Name) == blockServerName &&
|
|
||||||
msgChecksum == actualChecksum;
|
|
||||||
|
|
||||||
if( !isValid ){
|
|
||||||
sock->close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.instanceId = instanceId;
|
|
||||||
info.stage = StageConnected;
|
|
||||||
|
|
||||||
if( connectionType == NewInstance ||
|
|
||||||
( connectionType == SecondaryInstance &&
|
|
||||||
options & SingleApplication::Mode::SecondaryNotification ) )
|
|
||||||
{
|
|
||||||
Q_EMIT q->instanceStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0){
|
|
||||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
|
||||||
{
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
|
||||||
{
|
|
||||||
if( closedSocket->bytesAvailable() > 0 )
|
|
||||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::randomSleep()
|
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
|
|
||||||
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
|
||||||
#else
|
|
||||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
|
||||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::addAppData(const QString &data)
|
|
||||||
{
|
|
||||||
appDataList.push_back(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SingleApplicationPrivate::appData() const
|
|
||||||
{
|
|
||||||
return appDataList;
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This file is not part of the SingleApplication API. It is used purely as an
|
|
||||||
// implementation detail. This header file may change from version to
|
|
||||||
// version without notice, or may even be removed.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SINGLEAPPLICATION_P_H
|
|
||||||
#define SINGLEAPPLICATION_P_H
|
|
||||||
|
|
||||||
#include <QtCore/QSharedMemory>
|
|
||||||
#include <QtNetwork/QLocalServer>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
#include "singleapplication.h"
|
|
||||||
|
|
||||||
struct InstancesInfo {
|
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum; // Must be the last field
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
qint64 msgLen = 0;
|
|
||||||
quint32 instanceId = 0;
|
|
||||||
quint8 stage = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum ConnectionType : quint8 {
|
|
||||||
InvalidConnection = 0,
|
|
||||||
NewInstance = 1,
|
|
||||||
SecondaryInstance = 2,
|
|
||||||
Reconnect = 3
|
|
||||||
};
|
|
||||||
enum ConnectionStage : quint8 {
|
|
||||||
StageHeader = 0,
|
|
||||||
StageBody = 1,
|
|
||||||
StageConnected = 2,
|
|
||||||
};
|
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
|
||||||
|
|
||||||
SingleApplicationPrivate( SingleApplication *q_ptr );
|
|
||||||
~SingleApplicationPrivate() override;
|
|
||||||
|
|
||||||
static QString getUsername();
|
|
||||||
void genBlockServerName();
|
|
||||||
void initializeMemoryBlock() const;
|
|
||||||
void startPrimary();
|
|
||||||
void startSecondary();
|
|
||||||
bool connectToPrimary( int msecs, ConnectionType connectionType );
|
|
||||||
quint16 blockChecksum() const;
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
QString primaryUser() const;
|
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
|
||||||
static void randomSleep();
|
|
||||||
void addAppData(const QString &data);
|
|
||||||
QStringList appData() const;
|
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
|
||||||
QSharedMemory *memory;
|
|
||||||
QLocalSocket *socket;
|
|
||||||
QLocalServer *server;
|
|
||||||
quint32 instanceNumber;
|
|
||||||
QString blockServerName;
|
|
||||||
SingleApplication::Options options;
|
|
||||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
|
||||||
QStringList appDataList;
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void slotConnectionEstablished();
|
|
||||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
|
||||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_P_H
|
|
||||||
Vendored
+1
-1
Submodule client/3rd/qtkeychain updated: 74776e2a3e...7460df6a97
+12
-181
@@ -24,9 +24,12 @@ execute_process(
|
|||||||
|
|
||||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||||
|
|
||||||
if(IOS)
|
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
|
||||||
endif()
|
|
||||||
|
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||||
|
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||||
|
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
set(PACKAGES ${PACKAGES} Widgets)
|
set(PACKAGES ${PACKAGES} Widgets)
|
||||||
@@ -34,17 +37,13 @@ endif()
|
|||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||||
|
|
||||||
set(LIBS ${LIBS}
|
set(LIBS ${LIBS}
|
||||||
Qt6::Core Qt6::Gui
|
Qt6::Core Qt6::Gui
|
||||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||||
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
||||||
Qt6::Core5Compat Qt6::Concurrent
|
Qt6::Core5Compat Qt6::Concurrent
|
||||||
)
|
)
|
||||||
|
|
||||||
if(IOS)
|
|
||||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
set(LIBS ${LIBS} Qt6::Widgets)
|
set(LIBS ${LIBS} Qt6::Widgets)
|
||||||
endif()
|
endif()
|
||||||
@@ -55,6 +54,7 @@ qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
|||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||||
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||||
@@ -88,11 +88,6 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
|
|||||||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||||
# -- i18n end
|
# -- i18n end
|
||||||
|
|
||||||
if(IOS)
|
|
||||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
|
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(IS_CI ${CI})
|
set(IS_CI ${CI})
|
||||||
if(IS_CI)
|
if(IS_CI)
|
||||||
message("Detected CI env")
|
message("Detected CI env")
|
||||||
@@ -102,169 +97,29 @@ if(IS_CI)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/migrations.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mozilla headres
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
|
|
||||||
)
|
|
||||||
|
|
||||||
include_directories(mozilla)
|
include_directories(mozilla)
|
||||||
include_directories(mozilla/shared)
|
include_directories(mozilla/shared)
|
||||||
include_directories(mozilla/models)
|
include_directories(mozilla/models)
|
||||||
|
|
||||||
if(NOT IOS)
|
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT ANDROID)
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mozilla sources
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT IOS)
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT ANDROID)
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
|
|
||||||
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
|
||||||
|
|
||||||
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
|
|
||||||
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
|
|
||||||
|
|
||||||
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
|
|
||||||
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
|
|
||||||
|
|
||||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
|
|
||||||
)
|
|
||||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
|
|
||||||
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${COMMON_FILES_H}
|
|
||||||
${PAGE_LOGIC_H}
|
|
||||||
${CONFIGURATORS_H}
|
|
||||||
${UI_MODELS_H}
|
|
||||||
${UI_CONTROLLERS_H}
|
|
||||||
)
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${COMMON_FILES_CPP}
|
|
||||||
${PAGE_LOGIC_CPP}
|
|
||||||
${CONFIGURATORS_CPP}
|
|
||||||
${UI_MODELS_CPP}
|
|
||||||
${UI_CONTROLLERS_CPP}
|
|
||||||
)
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
|
||||||
)
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set(RESOURCES ${RESOURCES}
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -311,30 +166,6 @@ endif()
|
|||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
message("Client desktop build")
|
message("Client desktop build")
|
||||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
|
|||||||
+35
-242
@@ -2,39 +2,27 @@
|
|||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QQuickItem>
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#include <QResource>
|
#include <QResource>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "ui/controllers/pageController.h"
|
||||||
#include "ui/models/installedAppsModel.h"
|
#include "ui/models/installedAppsModel.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include "platforms/ios/QRCodeReaderBase.h"
|
#include "platforms/ios/QRCodeReaderBase.h"
|
||||||
#if defined(Q_OS_ANDROID)
|
|
||||||
#include "core/installedAppsImageProvider.h"
|
|
||||||
#include "platforms/android/android_controller.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "protocols/qml_register_protocols.h"
|
#include "protocols/qml_register_protocols.h"
|
||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
|
||||||
#include "platforms/ios/ios_controller.h"
|
|
||||||
#include <AmneziaVPN-Swift.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||||
#else
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
|
|
||||||
const QString &userData)
|
|
||||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
setQuitOnLastWindowClosed(false);
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
@@ -88,106 +76,30 @@ void AmneziaApplication::init()
|
|||||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||||
m_vpnConnectionThread.start();
|
m_vpnConnectionThread.start();
|
||||||
|
|
||||||
initModels();
|
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||||
loadTranslator();
|
|
||||||
initControllers();
|
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
if (!AndroidController::initLogging()) {
|
|
||||||
qFatal("Android logging initialization failed");
|
|
||||||
}
|
|
||||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
|
||||||
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
|
||||||
|
|
||||||
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
|
||||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
|
||||||
|
|
||||||
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
|
||||||
|
|
||||||
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
|
||||||
m_connectionController->onConnectionStateChanged(state);
|
|
||||||
if (m_vpnConnection)
|
|
||||||
m_vpnConnection->restoreConnection();
|
|
||||||
});
|
|
||||||
if (!AndroidController::instance()->initialize()) {
|
|
||||||
qFatal("Android controller initialization failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
|
||||||
m_pageController->replaceStartPage();
|
|
||||||
m_importController->extractConfigFromData(data);
|
|
||||||
m_pageController->goToPageViewConfig();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
|
||||||
IosController::Instance()->initialize();
|
|
||||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
|
||||||
m_pageController->replaceStartPage();
|
|
||||||
m_importController->extractConfigFromData(data);
|
|
||||||
m_pageController->goToPageViewConfig();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
|
||||||
m_pageController->replaceStartPage();
|
|
||||||
m_pageController->goToPageSettingsBackup();
|
|
||||||
m_settingsController->importBackupFromOutside(filePath);
|
|
||||||
});
|
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
|
||||||
|
|
||||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
|
||||||
|
|
||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
|
||||||
&NotificationHandler::setConnectionState);
|
|
||||||
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
|
||||||
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
|
||||||
&ConnectionController::closeConnection);
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||||
m_engine->load(url);
|
m_engine->load(url);
|
||||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
|
||||||
|
m_coreController->setQmlRoot();
|
||||||
|
|
||||||
bool enabled = m_settings->isSaveLogs();
|
bool enabled = m_settings->isSaveLogs();
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!Logger::init()) {
|
if (!Logger::init(false)) {
|
||||||
qWarning() << "Initialization of debug subsystem failed";
|
qWarning() << "Initialization of debug subsystem failed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Logger::setServiceLogsEnabled(enabled);
|
Logger::setServiceLogsEnabled(enabled);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN //TODO
|
||||||
if (m_parser.isSet("a"))
|
if (m_parser.isSet("a"))
|
||||||
m_pageController->showOnStartup();
|
m_coreController->pageController()->showOnStartup();
|
||||||
else
|
else
|
||||||
emit m_pageController->raiseMainWindow();
|
emit m_coreController->pageController()->raiseMainWindow();
|
||||||
#else
|
#else
|
||||||
m_pageController->showOnStartup();
|
m_coreController->pageController()->showOnStartup();
|
||||||
#endif
|
|
||||||
|
|
||||||
// TODO - fix
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
if (isPrimary()) {
|
|
||||||
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
|
|
||||||
qDebug() << "Secondary instance started, showing this window instead";
|
|
||||||
emit m_pageController->raiseMainWindow();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Android TextArea clipboard workaround
|
// Android TextArea clipboard workaround
|
||||||
@@ -244,33 +156,6 @@ void AmneziaApplication::loadFonts()
|
|||||||
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::loadTranslator()
|
|
||||||
{
|
|
||||||
auto locale = m_settings->getAppLanguage();
|
|
||||||
m_translator.reset(new QTranslator());
|
|
||||||
updateTranslator(locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AmneziaApplication::updateTranslator(const QLocale &locale)
|
|
||||||
{
|
|
||||||
if (!m_translator->isEmpty()) {
|
|
||||||
QCoreApplication::removeTranslator(m_translator.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
|
||||||
if (m_translator->load(strFileName)) {
|
|
||||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
|
||||||
m_settings->setAppLanguage(locale);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_settings->setAppLanguage(QLocale::English);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_engine->retranslate();
|
|
||||||
|
|
||||||
emit translationsUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AmneziaApplication::parseCommands()
|
bool AmneziaApplication::parseCommands()
|
||||||
{
|
{
|
||||||
m_parser.setApplicationDescription(APPLICATION_NAME);
|
m_parser.setApplicationDescription(APPLICATION_NAME);
|
||||||
@@ -294,128 +179,36 @@ bool AmneziaApplication::parseCommands()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
void AmneziaApplication::startLocalServer()
|
||||||
|
{
|
||||||
|
const QString serverName("AmneziaVPNInstance");
|
||||||
|
QLocalServer::removeServer(serverName);
|
||||||
|
|
||||||
|
QLocalServer *server = new QLocalServer(this);
|
||||||
|
server->listen(serverName);
|
||||||
|
|
||||||
|
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||||
|
if (server) {
|
||||||
|
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||||
|
clientConnection->deleteLater();
|
||||||
|
}
|
||||||
|
emit m_coreController->pageController()->raiseMainWindow(); //TODO
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||||
{
|
{
|
||||||
return m_engine;
|
return m_engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::initModels()
|
QNetworkAccessManager *AmneziaApplication::networkManager()
|
||||||
{
|
{
|
||||||
m_containersModel.reset(new ContainersModel(this));
|
return m_nam;
|
||||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
|
||||||
|
|
||||||
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
|
||||||
|
|
||||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
|
||||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
|
||||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
|
||||||
&ContainersModel::updateModel);
|
|
||||||
m_serversModel->resetModel();
|
|
||||||
|
|
||||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
|
||||||
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
|
||||||
|
|
||||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
|
||||||
|
|
||||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
|
||||||
|
|
||||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
|
||||||
|
|
||||||
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
|
||||||
|
|
||||||
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
|
||||||
|
|
||||||
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
|
||||||
|
|
||||||
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
|
||||||
|
|
||||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
|
||||||
|
|
||||||
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
|
||||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
|
||||||
|
|
||||||
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
|
||||||
|
|
||||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
|
||||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
|
||||||
&ServersModel::clearCachedProfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::initControllers()
|
QClipboard *AmneziaApplication::getClipboard()
|
||||||
{
|
{
|
||||||
m_connectionController.reset(
|
return this->clipboard();
|
||||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
|
||||||
|
|
||||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this, [this](const QString &errorMessage) {
|
|
||||||
emit m_pageController->showErrorMessage(errorMessage);
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this, [this](ErrorCode errorCode) {
|
|
||||||
emit m_pageController->showErrorMessage(errorCode);
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
|
||||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
|
||||||
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
|
||||||
|
|
||||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
|
||||||
|
|
||||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
|
||||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
|
||||||
&PageController::showPassphraseRequestDrawer);
|
|
||||||
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
|
||||||
&InstallController::setEncryptedPassphrase);
|
|
||||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
|
||||||
&ConnectionController::onCurrentContainerUpdated);
|
|
||||||
|
|
||||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
|
||||||
|
|
||||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
|
||||||
|
|
||||||
m_settingsController.reset(
|
|
||||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
|
||||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
|
||||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
|
||||||
}
|
|
||||||
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
|
||||||
|
|
||||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
|
||||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
|
||||||
|
|
||||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
|
||||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
|
||||||
|
|
||||||
m_systemController.reset(new SystemController(m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,127 +11,53 @@
|
|||||||
#else
|
#else
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#endif
|
#endif
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
|
#include "core/controllers/coreController.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "vpnconnection.h"
|
#include "vpnconnection.h"
|
||||||
|
|
||||||
#include "ui/controllers/connectionController.h"
|
|
||||||
#include "ui/controllers/exportController.h"
|
|
||||||
#include "ui/controllers/importController.h"
|
|
||||||
#include "ui/controllers/installController.h"
|
|
||||||
#include "ui/controllers/pageController.h"
|
|
||||||
#include "ui/controllers/settingsController.h"
|
|
||||||
#include "ui/controllers/sitesController.h"
|
|
||||||
#include "ui/controllers/systemController.h"
|
|
||||||
#include "ui/controllers/appSplitTunnelingController.h"
|
|
||||||
#include "ui/models/containers_model.h"
|
|
||||||
#include "ui/models/languageModel.h"
|
|
||||||
#include "ui/models/protocols/cloakConfigModel.h"
|
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
#include "ui/notificationhandler.h"
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_WINDOWS
|
|
||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
|
||||||
#endif
|
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
|
||||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
|
||||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
|
||||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
|
||||||
#include "ui/models/protocols/xrayConfigModel.h"
|
|
||||||
#include "ui/models/protocols_model.h"
|
|
||||||
#include "ui/models/servers_model.h"
|
|
||||||
#include "ui/models/services/sftpConfigModel.h"
|
|
||||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
|
||||||
#include "ui/models/sites_model.h"
|
|
||||||
#include "ui/models/clientManagementModel.h"
|
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
|
||||||
|
|
||||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||||
#else
|
#else
|
||||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
#define AMNEZIA_BASE_CLASS QApplication
|
||||||
#define QAPPLICATION_CLASS QApplication
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication(int &argc, char *argv[]);
|
AmneziaApplication(int &argc, char *argv[]);
|
||||||
#else
|
|
||||||
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
|
|
||||||
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
|
|
||||||
const QString &userData = {});
|
|
||||||
#endif
|
|
||||||
virtual ~AmneziaApplication();
|
virtual ~AmneziaApplication();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void registerTypes();
|
void registerTypes();
|
||||||
void loadFonts();
|
void loadFonts();
|
||||||
void loadTranslator();
|
|
||||||
void updateTranslator(const QLocale &locale);
|
|
||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
QNetworkAccessManager *manager() { return m_nam; }
|
void startLocalServer();
|
||||||
|
#endif
|
||||||
|
|
||||||
signals:
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
void translationsUpdated();
|
QNetworkAccessManager *networkManager();
|
||||||
|
QClipboard *getClipboard();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initModels();
|
|
||||||
void initControllers();
|
|
||||||
|
|
||||||
QQmlApplicationEngine *m_engine {};
|
QQmlApplicationEngine *m_engine {};
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
|
QScopedPointer<CoreController> m_coreController;
|
||||||
|
|
||||||
QSharedPointer<ContainerProps> m_containerProps;
|
QSharedPointer<ContainerProps> m_containerProps;
|
||||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||||
|
|
||||||
QSharedPointer<QTranslator> m_translator;
|
|
||||||
QCommandLineParser m_parser;
|
QCommandLineParser m_parser;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
|
||||||
QSharedPointer<LanguageModel> m_languageModel;
|
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
|
||||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
|
||||||
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
|
||||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
|
||||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
|
||||||
#ifdef Q_OS_WINDOWS
|
|
||||||
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
|
||||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
|
||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QScopedPointer<ConnectionController> m_connectionController;
|
|
||||||
QScopedPointer<PageController> m_pageController;
|
|
||||||
QScopedPointer<InstallController> m_installController;
|
|
||||||
QScopedPointer<ImportController> m_importController;
|
|
||||||
QScopedPointer<ExportController> m_exportController;
|
|
||||||
QScopedPointer<SettingsController> m_settingsController;
|
|
||||||
QScopedPointer<SitesController> m_sitesController;
|
|
||||||
QScopedPointer<SystemController> m_systemController;
|
|
||||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
|
||||||
|
|
||||||
QNetworkAccessManager *m_nam;
|
QNetworkAccessManager *m_nam;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<manifest
|
<manifest
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.amnezia.vpn"
|
|
||||||
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
||||||
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- To request network state -->
|
<!-- To request network state -->
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" android:maxSdkVersion="30" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -68,9 +67,6 @@
|
|||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.extract_android_style"
|
|
||||||
android:value="minimal" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -88,6 +84,20 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Translucent" />
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
|
<activity android:name=".AuthActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
|
<activity android:name=".TvFilePicker"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ImportConfigActivity"
|
android:name=".ImportConfigActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
|||||||
@@ -1,81 +1,21 @@
|
|||||||
package org.amnezia.vpn.protocol.awg
|
package org.amnezia.vpn.protocol.awg
|
||||||
|
|
||||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||||
|
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config example:
|
|
||||||
* {
|
|
||||||
* "protocol": "awg",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "awg_config_data": {
|
|
||||||
* "H1": "969537490",
|
|
||||||
* "H2": "481688153",
|
|
||||||
* "H3": "2049399200",
|
|
||||||
* "H4": "52029755",
|
|
||||||
* "Jc": "3",
|
|
||||||
* "Jmax": "1000",
|
|
||||||
* "Jmin": "50",
|
|
||||||
* "S1": "49",
|
|
||||||
* "S2": "60",
|
|
||||||
* "client_ip": "10.8.1.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "port": 12345,
|
|
||||||
* "client_pub_key": "clientPublicKeyBase64",
|
|
||||||
* "client_priv_key": "privateKeyBase64",
|
|
||||||
* "psk_key": "presharedKeyBase64",
|
|
||||||
* "server_pub_key": "publicKeyBase64",
|
|
||||||
* "config": "[Interface]
|
|
||||||
* Address = 10.8.1.1/32
|
|
||||||
* DNS = 1.1.1.1, 1.0.0.1
|
|
||||||
* PrivateKey = privateKeyBase64
|
|
||||||
* Jc = 3
|
|
||||||
* Jmin = 50
|
|
||||||
* Jmax = 1000
|
|
||||||
* S1 = 49
|
|
||||||
* S2 = 60
|
|
||||||
* H1 = 969537490
|
|
||||||
* H2 = 481688153
|
|
||||||
* H3 = 2049399200
|
|
||||||
* H4 = 52029755
|
|
||||||
*
|
|
||||||
* [Peer]
|
|
||||||
* PublicKey = publicKeyBase64
|
|
||||||
* PresharedKey = presharedKeyBase64
|
|
||||||
* AllowedIPs = 0.0.0.0/0, ::/0
|
|
||||||
* Endpoint = 100.100.100.0:12345
|
|
||||||
* PersistentKeepalive = 25
|
|
||||||
* "
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Awg : Wireguard() {
|
class Awg : Wireguard() {
|
||||||
|
|
||||||
override val ifName: String = "awg0"
|
override val ifName: String = "awg0"
|
||||||
|
|
||||||
override fun parseConfig(config: JSONObject): AwgConfig {
|
override fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
val configDataJson = config.getJSONObject("awg_config_data")
|
val configData = config.getJSONObject("awg_config_data")
|
||||||
val configData = parseConfigData(configDataJson.getString("config"))
|
return WireguardConfig.build {
|
||||||
return AwgConfig.build {
|
setUseProtocolExtension(true)
|
||||||
configWireguard(configData, configDataJson)
|
configExtensionParameters(configData)
|
||||||
|
configWireguard(config, configData)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
configAppSplitTunneling(config)
|
configAppSplitTunneling(config)
|
||||||
configData["Jc"]?.let { setJc(it.toInt()) }
|
|
||||||
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
|
||||||
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
|
||||||
configData["S1"]?.let { setS1(it.toInt()) }
|
|
||||||
configData["S2"]?.let { setS2(it.toInt()) }
|
|
||||||
configData["H1"]?.let { setH1(it.toLong()) }
|
|
||||||
configData["H2"]?.let { setH2(it.toLong()) }
|
|
||||||
configData["H3"]?.let { setH3(it.toLong()) }
|
|
||||||
configData["H4"]?.let { setH4(it.toLong()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
package org.amnezia.vpn.protocol.awg
|
|
||||||
|
|
||||||
import org.amnezia.vpn.protocol.BadConfigException
|
|
||||||
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
|
||||||
|
|
||||||
class AwgConfig private constructor(
|
|
||||||
wireguardConfigBuilder: WireguardConfig.Builder,
|
|
||||||
val jc: Int,
|
|
||||||
val jmin: Int,
|
|
||||||
val jmax: Int,
|
|
||||||
val s1: Int,
|
|
||||||
val s2: Int,
|
|
||||||
val h1: Long,
|
|
||||||
val h2: Long,
|
|
||||||
val h3: Long,
|
|
||||||
val h4: Long
|
|
||||||
) : WireguardConfig(wireguardConfigBuilder) {
|
|
||||||
|
|
||||||
private constructor(builder: Builder) : this(
|
|
||||||
builder,
|
|
||||||
builder.jc,
|
|
||||||
builder.jmin,
|
|
||||||
builder.jmax,
|
|
||||||
builder.s1,
|
|
||||||
builder.s2,
|
|
||||||
builder.h1,
|
|
||||||
builder.h2,
|
|
||||||
builder.h3,
|
|
||||||
builder.h4
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
|
||||||
super.appendDeviceLine(this)
|
|
||||||
appendLine("jc=$jc")
|
|
||||||
appendLine("jmin=$jmin")
|
|
||||||
appendLine("jmax=$jmax")
|
|
||||||
appendLine("s1=$s1")
|
|
||||||
appendLine("s2=$s2")
|
|
||||||
appendLine("h1=$h1")
|
|
||||||
appendLine("h2=$h2")
|
|
||||||
appendLine("h3=$h3")
|
|
||||||
appendLine("h4=$h4")
|
|
||||||
}
|
|
||||||
|
|
||||||
class Builder : WireguardConfig.Builder() {
|
|
||||||
|
|
||||||
private var _jc: Int? = null
|
|
||||||
internal var jc: Int
|
|
||||||
get() = _jc ?: throw BadConfigException("AWG: parameter jc is undefined")
|
|
||||||
private set(value) { _jc = value }
|
|
||||||
|
|
||||||
private var _jmin: Int? = null
|
|
||||||
internal var jmin: Int
|
|
||||||
get() = _jmin ?: throw BadConfigException("AWG: parameter jmin is undefined")
|
|
||||||
private set(value) { _jmin = value }
|
|
||||||
|
|
||||||
private var _jmax: Int? = null
|
|
||||||
internal var jmax: Int
|
|
||||||
get() = _jmax ?: throw BadConfigException("AWG: parameter jmax is undefined")
|
|
||||||
private set(value) { _jmax = value }
|
|
||||||
|
|
||||||
private var _s1: Int? = null
|
|
||||||
internal var s1: Int
|
|
||||||
get() = _s1 ?: throw BadConfigException("AWG: parameter s1 is undefined")
|
|
||||||
private set(value) { _s1 = value }
|
|
||||||
|
|
||||||
private var _s2: Int? = null
|
|
||||||
internal var s2: Int
|
|
||||||
get() = _s2 ?: throw BadConfigException("AWG: parameter s2 is undefined")
|
|
||||||
private set(value) { _s2 = value }
|
|
||||||
|
|
||||||
private var _h1: Long? = null
|
|
||||||
internal var h1: Long
|
|
||||||
get() = _h1 ?: throw BadConfigException("AWG: parameter h1 is undefined")
|
|
||||||
private set(value) { _h1 = value }
|
|
||||||
|
|
||||||
private var _h2: Long? = null
|
|
||||||
internal var h2: Long
|
|
||||||
get() = _h2 ?: throw BadConfigException("AWG: parameter h2 is undefined")
|
|
||||||
private set(value) { _h2 = value }
|
|
||||||
|
|
||||||
private var _h3: Long? = null
|
|
||||||
internal var h3: Long
|
|
||||||
get() = _h3 ?: throw BadConfigException("AWG: parameter h3 is undefined")
|
|
||||||
private set(value) { _h3 = value }
|
|
||||||
|
|
||||||
private var _h4: Long? = null
|
|
||||||
internal var h4: Long
|
|
||||||
get() = _h4 ?: throw BadConfigException("AWG: parameter h4 is undefined")
|
|
||||||
private set(value) { _h4 = value }
|
|
||||||
|
|
||||||
fun setJc(jc: Int) = apply { this.jc = jc }
|
|
||||||
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
|
|
||||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
|
||||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
|
||||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
|
||||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
|
||||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
|
||||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
|
||||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
|
||||||
|
|
||||||
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) }
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
inline fun build(block: Builder.() -> Unit): AwgConfig = Builder().apply(block).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,3 +3,6 @@
|
|||||||
// android.bundle.enableUncompressedNativeLibs is deprecated
|
// android.bundle.enableUncompressedNativeLibs is deprecated
|
||||||
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
||||||
useLegacyPackaging
|
useLegacyPackaging
|
||||||
|
|
||||||
|
// package name for androiddeployqt
|
||||||
|
namespace = "org.amnezia.vpn"
|
||||||
|
|||||||
@@ -115,9 +115,11 @@ dependencies {
|
|||||||
implementation(project(":xray"))
|
implementation(project(":xray"))
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.fragment)
|
||||||
implementation(libs.kotlinx.coroutines)
|
implementation(libs.kotlinx.coroutines)
|
||||||
implementation(libs.kotlinx.serialization.protobuf)
|
implementation(libs.kotlinx.serialization.protobuf)
|
||||||
implementation(libs.bundles.androidx.camera)
|
implementation(libs.bundles.androidx.camera)
|
||||||
implementation(libs.google.mlkit)
|
implementation(libs.google.mlkit)
|
||||||
implementation(libs.androidx.datastore)
|
implementation(libs.androidx.datastore)
|
||||||
|
implementation(libs.androidx.biometric)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,40 +3,16 @@ package org.amnezia.vpn.protocol.cloak
|
|||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import net.openvpn.ovpn3.ClientAPI_Config
|
import net.openvpn.ovpn3.ClientAPI_Config
|
||||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config Example:
|
|
||||||
* {
|
|
||||||
* "protocol": "cloak",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "openvpn_config_data": {
|
|
||||||
* "config": "openVpnConfig"
|
|
||||||
* }
|
|
||||||
* "cloak_config_data": {
|
|
||||||
* "BrowserSig": "chrome",
|
|
||||||
* "EncryptionMethod": "aes-gcm",
|
|
||||||
* "NumConn": 1,
|
|
||||||
* "ProxyMethod": "openvpn",
|
|
||||||
* "PublicKey": "PublicKey=",
|
|
||||||
* "RemoteHost": "100.100.100.0",
|
|
||||||
* "RemotePort": "443",
|
|
||||||
* "ServerName": "servername",
|
|
||||||
* "StreamTimeout": 300,
|
|
||||||
* "Transport": "direct",
|
|
||||||
* "UID": "UID="
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Cloak : OpenVpn() {
|
class Cloak : OpenVpn() {
|
||||||
|
|
||||||
|
override fun internalInit() {
|
||||||
|
super.internalInit()
|
||||||
|
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
override fun parseConfig(config: JSONObject): ClientAPI_Config {
|
override fun parseConfig(config: JSONObject): ClientAPI_Config {
|
||||||
val openVpnConfig = ClientAPI_Config()
|
val openVpnConfig = ClientAPI_Config()
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
|
|||||||
# For development copy and set local values for these parameters in local.properties
|
# For development copy and set local values for these parameters in local.properties
|
||||||
#androidCompileSdkVersion=android-34
|
#androidCompileSdkVersion=android-34
|
||||||
#androidBuildToolsVersion=34.0.0
|
#androidBuildToolsVersion=34.0.0
|
||||||
#qtMinSdkVersion=24
|
#qtMinSdkVersion=26
|
||||||
#qtTargetSdkVersion=34
|
#qtTargetSdkVersion=34
|
||||||
#androidNdkVersion=26.1.10909125
|
#androidNdkVersion=26.1.10909125
|
||||||
#qtTargetAbiList=x86_64
|
#qtTargetAbiList=x86_64
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.2.0"
|
agp = "8.5.2"
|
||||||
kotlin = "1.9.20"
|
kotlin = "1.9.24"
|
||||||
androidx-core = "1.12.0"
|
androidx-core = "1.13.1"
|
||||||
androidx-activity = "1.8.1"
|
androidx-activity = "1.9.1"
|
||||||
androidx-annotation = "1.7.0"
|
androidx-annotation = "1.8.2"
|
||||||
androidx-camera = "1.3.0"
|
androidx-biometric = "1.2.0-alpha05"
|
||||||
|
androidx-camera = "1.3.4"
|
||||||
|
androidx-fragment = "1.8.2"
|
||||||
androidx-security-crypto = "1.1.0-alpha06"
|
androidx-security-crypto = "1.1.0-alpha06"
|
||||||
androidx-datastore = "1.1.0-beta01"
|
androidx-datastore = "1.1.1"
|
||||||
kotlinx-coroutines = "1.7.3"
|
kotlinx-coroutines = "1.8.1"
|
||||||
kotlinx-serialization = "1.6.3"
|
kotlinx-serialization = "1.6.3"
|
||||||
google-mlkit = "17.2.0"
|
google-mlkit = "17.3.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||||
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
||||||
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
||||||
|
androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" }
|
||||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
||||||
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
||||||
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
||||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
||||||
|
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||||
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
||||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||||
|
|||||||
Binary file not shown.
+1
-3
@@ -1,7 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Vendored
+5
-2
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -84,7 +86,8 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|||||||
Vendored
+12
-10
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
@@ -11,28 +11,12 @@ import org.amnezia.vpn.protocol.Protocol
|
|||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
import org.amnezia.vpn.util.net.getLocalNetworks
|
import org.amnezia.vpn.util.net.getLocalNetworks
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config Example:
|
|
||||||
* {
|
|
||||||
* "protocol": "openvpn",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "openvpn_config_data": {
|
|
||||||
* "config": "openVpnConfig"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
open class OpenVpn : Protocol() {
|
open class OpenVpn : Protocol() {
|
||||||
|
|
||||||
private var openVpnClient: OpenVpnClient? = null
|
private var openVpnClient: OpenVpnClient? = null
|
||||||
@@ -51,14 +35,17 @@ open class OpenVpn : Protocol() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun internalInit() {
|
override fun internalInit() {
|
||||||
if (!isInitialized) loadSharedLibrary(context, "ovpn3")
|
if (!isInitialized) {
|
||||||
|
loadSharedLibrary(context, "ovpn3")
|
||||||
|
loadSharedLibrary(context, "ovpnutil")
|
||||||
|
}
|
||||||
if (this::scope.isInitialized) {
|
if (this::scope.isInitialized) {
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
scope = CoroutineScope(Dispatchers.IO)
|
scope = CoroutineScope(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val configBuilder = OpenVpnConfig.Builder()
|
val configBuilder = OpenVpnConfig.Builder()
|
||||||
|
|
||||||
openVpnClient = OpenVpnClient(
|
openVpnClient = OpenVpnClient(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol
|
|||||||
|
|
||||||
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||||
|
|
||||||
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
|
||||||
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||||
|
|
||||||
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.amnezia.vpn.protocol
|
package org.amnezia.vpn.protocol
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.IpPrefix
|
import android.net.IpPrefix
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
@@ -8,9 +7,6 @@ import android.net.VpnService.Builder
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
@@ -42,7 +38,7 @@ abstract class Protocol {
|
|||||||
|
|
||||||
protected abstract fun internalInit()
|
protected abstract fun internalInit()
|
||||||
|
|
||||||
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
abstract suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||||
|
|
||||||
abstract fun stopVpn()
|
abstract fun stopVpn()
|
||||||
|
|
||||||
@@ -158,60 +154,6 @@ abstract class Protocol {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
vpnBuilder.setMetered(false)
|
vpnBuilder.setMetered(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
|
|
||||||
Log.d(TAG, "Extracting library: $libraryName")
|
|
||||||
val apks = hashSetOf<String>()
|
|
||||||
context.applicationInfo.run {
|
|
||||||
sourceDir?.let { apks += it }
|
|
||||||
splitSourceDirs?.let { apks += it }
|
|
||||||
}
|
|
||||||
for (abi in Build.SUPPORTED_ABIS) {
|
|
||||||
for (apk in apks) {
|
|
||||||
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
|
|
||||||
val mappedName = System.mapLibraryName(libraryName)
|
|
||||||
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
|
|
||||||
val zipEntry = zipFile.getEntry(libraryZipPath)
|
|
||||||
zipEntry?.let {
|
|
||||||
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
|
|
||||||
FileOutputStream(destination).use { outStream ->
|
|
||||||
zipFile.getInputStream(zipEntry).use { inStream ->
|
|
||||||
inStream.copyTo(outStream, 32 * 1024)
|
|
||||||
outStream.fd.sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
|
||||||
fun loadSharedLibrary(context: Context, libraryName: String) {
|
|
||||||
Log.d(TAG, "Loading library: $libraryName")
|
|
||||||
try {
|
|
||||||
System.loadLibrary(libraryName)
|
|
||||||
return
|
|
||||||
} catch (_: UnsatisfiedLinkError) {
|
|
||||||
Log.d(TAG, "Failed to load library, try to extract it from apk")
|
|
||||||
}
|
|
||||||
var tempFile: File? = null
|
|
||||||
try {
|
|
||||||
tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
|
|
||||||
if (extractLibrary(context, libraryName, tempFile)) {
|
|
||||||
System.load(tempFile.absolutePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw LoadLibraryException("Failed to load library apk: $libraryName", e)
|
|
||||||
} finally {
|
|
||||||
tempFile?.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)
|
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_banner_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -23,4 +23,6 @@
|
|||||||
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
|
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
|
||||||
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
||||||
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
||||||
|
|
||||||
|
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_banner_background">#1E1E1F</color>
|
|
||||||
</resources>
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
|
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
|
||||||
|
|
||||||
<array name="bundled_libs">
|
<array name="bundled_libs">
|
||||||
<!-- %%INSERT_EXTRA_LIBS%% -->
|
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="qt_libs">
|
<array name="qt_libs">
|
||||||
|
|||||||
@@ -23,4 +23,6 @@
|
|||||||
<string name="notificationSettingsDialogTitle">Notification settings</string>
|
<string name="notificationSettingsDialogTitle">Notification settings</string>
|
||||||
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
||||||
<string name="openNotificationSettings">Open notification settings</string>
|
<string name="openNotificationSettings">Open notification settings</string>
|
||||||
|
|
||||||
|
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<color name="black">#FF0E0E11</color>
|
||||||
<style name="NoActionBar">
|
<style name="NoActionBar">
|
||||||
|
<item name="android:windowBackground">@color/black</item>
|
||||||
|
<item name="android:colorBackground">@color/black</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ dependencyResolutionManagement {
|
|||||||
includeBuild("./gradle/plugins")
|
includeBuild("./gradle/plugins")
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.settings") version "8.2.0"
|
id("com.android.settings") version "8.5.2"
|
||||||
id("settings-property-delegate")
|
id("settings-property-delegate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.Manifest
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -12,6 +13,7 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
|||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -20,7 +22,13 @@ import android.os.IBinder
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
import android.os.Messenger
|
import android.os.Messenger
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.provider.OpenableColumns
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager.LayoutParams
|
import android.view.WindowManager.LayoutParams
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -29,6 +37,7 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.text.RegexOption.IGNORE_CASE
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
import AppListProvider
|
import AppListProvider
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
@@ -43,6 +52,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.amnezia.vpn.protocol.getStatistics
|
import org.amnezia.vpn.protocol.getStatistics
|
||||||
import org.amnezia.vpn.protocol.getStatus
|
import org.amnezia.vpn.protocol.getStatus
|
||||||
import org.amnezia.vpn.qt.QtAndroidController
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.Prefs
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
@@ -69,6 +79,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
private var isInBoundState = false
|
private var isInBoundState = false
|
||||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||||
private lateinit var vpnServiceMessenger: IpcMessenger
|
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||||
|
private var pfd: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||||
@@ -157,7 +168,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
*/
|
*/
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
Log.d(TAG, "Create Amnezia activity")
|
||||||
|
loadLibs()
|
||||||
|
window.apply {
|
||||||
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
statusBarColor = getColor(R.color.black)
|
||||||
|
}
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
val proto = mainScope.async(Dispatchers.IO) {
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
VpnStateStore.getVpnState().vpnProto
|
VpnStateStore.getVpnState().vpnProto
|
||||||
@@ -175,6 +191,17 @@ class AmneziaActivity : QtActivity() {
|
|||||||
runBlocking { vpnProto = proto.await() }
|
runBlocking { vpnProto = proto.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadLibs() {
|
||||||
|
listOf(
|
||||||
|
"rsapss",
|
||||||
|
"crypto_3",
|
||||||
|
"ssl_3",
|
||||||
|
"ssh"
|
||||||
|
).forEach {
|
||||||
|
loadSharedLibrary(this.applicationContext, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun registerBroadcastReceivers() {
|
private fun registerBroadcastReceivers() {
|
||||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
registerBroadcastReceiver(
|
registerBroadcastReceiver(
|
||||||
@@ -183,7 +210,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Log.d(
|
Log.v(
|
||||||
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
||||||
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
||||||
)
|
)
|
||||||
@@ -197,7 +224,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.v(TAG, "onNewIntent: $intent")
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +413,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@MainThread
|
@MainThread
|
||||||
private fun startVpn(vpnConfig: String) {
|
private fun startVpn(vpnConfig: String) {
|
||||||
getVpnProto(vpnConfig)?.let { proto ->
|
getVpnProto(vpnConfig)?.let { proto ->
|
||||||
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||||
if (isServiceConnected) {
|
if (isServiceConnected) {
|
||||||
if (proto.serviceClass == vpnProto?.serviceClass) {
|
if (proto.serviceClass == vpnProto?.serviceClass) {
|
||||||
vpnProto = proto
|
vpnProto = proto
|
||||||
@@ -496,21 +523,25 @@ class AmneziaActivity : QtActivity() {
|
|||||||
type = "text/*"
|
type = "text/*"
|
||||||
putExtra(Intent.EXTRA_TITLE, fileName)
|
putExtra(Intent.EXTRA_TITLE, fileName)
|
||||||
}.also {
|
}.also {
|
||||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
try {
|
||||||
onSuccess = {
|
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
it?.data?.let { uri ->
|
onSuccess = {
|
||||||
Log.d(TAG, "Save file to $uri")
|
it?.data?.let { uri ->
|
||||||
try {
|
Log.v(TAG, "Save file to $uri")
|
||||||
contentResolver.openOutputStream(uri)?.use { os ->
|
try {
|
||||||
os.bufferedWriter().use { it.write(data) }
|
contentResolver.openOutputStream(uri)?.use { os ->
|
||||||
|
os.bufferedWriter().use { it.write(data) }
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Failed to save file $uri: $e")
|
||||||
|
// todo: send error to Qt
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e(TAG, "Failed to save file $uri: $e")
|
|
||||||
// todo: send error to Qt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
))
|
||||||
))
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,46 +550,115 @@ class AmneziaActivity : QtActivity() {
|
|||||||
fun openFile(filter: String?) {
|
fun openFile(filter: String?) {
|
||||||
Log.v(TAG, "Open file with filter: $filter")
|
Log.v(TAG, "Open file with filter: $filter")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
val intent = if (!isOnTv()) {
|
||||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||||
val mime = MimeTypeMap.getSingleton()
|
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||||
extensionRegex.findAll(filter).map {
|
val mime = MimeTypeMap.getSingleton()
|
||||||
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
extensionRegex.findAll(filter).map {
|
||||||
}.toSet()
|
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
||||||
} else emptySet()
|
}.toSet()
|
||||||
|
} else emptySet()
|
||||||
|
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||||
if ("*/*" in mimeTypes) {
|
if ("*/*" in mimeTypes) {
|
||||||
type = "*/*"
|
type = "*/*"
|
||||||
} else {
|
} else {
|
||||||
when (mimeTypes.size) {
|
when (mimeTypes.size) {
|
||||||
1 -> type = mimeTypes.first()
|
1 -> type = mimeTypes.first()
|
||||||
|
|
||||||
in 2..Int.MAX_VALUE -> {
|
in 2..Int.MAX_VALUE -> {
|
||||||
type = "*/*"
|
type = "*/*"
|
||||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> type = "*/*"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> type = "*/*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
} else {
|
||||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
onAny = {
|
onAny = {
|
||||||
val uri = it?.data?.toString() ?: ""
|
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||||
Log.d(TAG, "Open file: $uri")
|
showNoFileBrowserAlertDialog()
|
||||||
|
}
|
||||||
|
val uri = it?.data?.apply {
|
||||||
|
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}?.toString() ?: ""
|
||||||
|
Log.v(TAG, "Open file: $uri")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
qtInitialized.await()
|
qtInitialized.await()
|
||||||
QtAndroidController.onFileOpened(uri)
|
QtAndroidController.onFileOpened(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
showNoFileBrowserAlertDialog()
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onFileOpened("")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showNoFileBrowserAlertDialog() {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setMessage(R.string.tvNoFileBrowser)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
try {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
|
||||||
|
} catch (_: Throwable) {}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getFd(fileName: String): Int {
|
||||||
|
Log.v(TAG, "Get fd for $fileName")
|
||||||
|
return blockingCall {
|
||||||
|
try {
|
||||||
|
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||||
|
pfd?.fd ?: -1
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to get fd: $e")
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun closeFd() {
|
||||||
|
Log.v(TAG, "Close fd")
|
||||||
|
mainScope.launch {
|
||||||
|
pfd?.close()
|
||||||
|
pfd = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getFileName(uri: String): String {
|
||||||
|
Log.v(TAG, "Get file name for uri: $uri")
|
||||||
|
return blockingCall {
|
||||||
|
try {
|
||||||
|
contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||||
|
return@blockingCall cursor.getString(0) ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to get file name: $e")
|
||||||
|
}
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||||
@@ -610,6 +710,14 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun setNavigationBarColor(color: Int) {
|
||||||
|
Log.v(TAG, "Change navigation bar color: ${"#%08X".format(color)}")
|
||||||
|
mainScope.launch {
|
||||||
|
window.navigationBarColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun minimizeApp() {
|
fun minimizeApp() {
|
||||||
Log.v(TAG, "Minimize application")
|
Log.v(TAG, "Minimize application")
|
||||||
@@ -684,9 +792,132 @@ class AmneziaActivity : QtActivity() {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun requestAuthentication() {
|
||||||
|
Log.v(TAG, "Request authentication")
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
Intent(this@AmneziaActivity, AuthActivity::class.java).also {
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// method to workaround Qt's problem with calling the keyboard on TVs
|
||||||
|
@Suppress("unused")
|
||||||
|
fun sendTouch(x: Float, y: Float) {
|
||||||
|
Log.v(TAG, "Send touch: $x, $y")
|
||||||
|
blockingCall {
|
||||||
|
findQtWindow(window.decorView)?.let {
|
||||||
|
Log.v(TAG, "Send touch to $it")
|
||||||
|
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||||
|
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findQtWindow(view: View): View? {
|
||||||
|
Log.v(TAG, "findQtWindow: process $view")
|
||||||
|
if (view::class.simpleName == "QtWindow") return view
|
||||||
|
else if (view is ViewGroup) {
|
||||||
|
for (i in 0 until view.childCount) {
|
||||||
|
val result = findQtWindow(view.getChildAt(i))
|
||||||
|
if (result != null) return result
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent =
|
||||||
|
MotionEvent.obtain(
|
||||||
|
eventTime,
|
||||||
|
eventTime,
|
||||||
|
action,
|
||||||
|
1,
|
||||||
|
arrayOf(MotionEvent.PointerProperties().apply {
|
||||||
|
id = 0
|
||||||
|
toolType = MotionEvent.TOOL_TYPE_FINGER
|
||||||
|
}),
|
||||||
|
arrayOf(MotionEvent.PointerCoords().apply {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
pressure = 1f
|
||||||
|
size = 1f
|
||||||
|
}),
|
||||||
|
0, 0, 1.0f, 1.0f, 0, 0, 0,0
|
||||||
|
)
|
||||||
|
|
||||||
|
// workaround for a bug in Qt that causes the mouse click event not to be handled
|
||||||
|
// also disable right-click, as it causes the application to crash
|
||||||
|
private var lastButtonState = 0
|
||||||
|
private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain(
|
||||||
|
downTime,
|
||||||
|
eventTime,
|
||||||
|
action,
|
||||||
|
pointerCount,
|
||||||
|
(0 until pointerCount).map { i ->
|
||||||
|
MotionEvent.PointerProperties().apply {
|
||||||
|
getPointerProperties(i, this)
|
||||||
|
}
|
||||||
|
}.toTypedArray(),
|
||||||
|
(0 until pointerCount).map { i ->
|
||||||
|
MotionEvent.PointerCoords().apply {
|
||||||
|
getPointerCoords(i, this)
|
||||||
|
}
|
||||||
|
}.toTypedArray(),
|
||||||
|
metaState,
|
||||||
|
MotionEvent.BUTTON_PRIMARY,
|
||||||
|
xPrecision,
|
||||||
|
yPrecision,
|
||||||
|
deviceId,
|
||||||
|
edgeFlags,
|
||||||
|
source,
|
||||||
|
flags
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean {
|
||||||
|
when (ev.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
lastButtonState = ev.buttonState
|
||||||
|
if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
when (lastButtonState) {
|
||||||
|
MotionEvent.BUTTON_SECONDARY -> return true
|
||||||
|
MotionEvent.BUTTON_PRIMARY -> {
|
||||||
|
val modEvent = ev.fixCopy()
|
||||||
|
return superDispatch(modEvent).apply { modEvent.recycle() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return superDispatch(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||||
|
Log.v(TAG, "dispatchTouch: $ev")
|
||||||
|
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
|
||||||
|
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
|
||||||
|
}
|
||||||
|
return super.dispatchTouchEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
|
||||||
|
ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }}
|
||||||
|
return super.dispatchTrackballEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils methods
|
* Utils methods
|
||||||
*/
|
*/
|
||||||
|
private fun <T> blockingCall(
|
||||||
|
context: CoroutineContext = Dispatchers.Main.immediate,
|
||||||
|
block: suspend () -> T
|
||||||
|
) = runBlocking {
|
||||||
|
mainScope.async(context) { block() }.await()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun actionCodeToString(actionCode: Int): String =
|
private fun actionCodeToString(actionCode: Int): String =
|
||||||
when (actionCode) {
|
when (actionCode) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.annotation.MainThread
|
|||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import java.net.UnknownHostException
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
@@ -31,6 +32,7 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
@@ -39,7 +41,6 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.amnezia.vpn.protocol.BadConfigException
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.LoadLibraryException
|
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
@@ -49,6 +50,7 @@ import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
|||||||
import org.amnezia.vpn.protocol.VpnException
|
import org.amnezia.vpn.protocol.VpnException
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
import org.amnezia.vpn.protocol.putStatus
|
import org.amnezia.vpn.protocol.putStatus
|
||||||
|
import org.amnezia.vpn.util.LoadLibraryException
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.Prefs
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.amnezia.vpn.util.net.NetworkState
|
import org.amnezia.vpn.util.net.NetworkState
|
||||||
@@ -111,6 +113,10 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
||||||
|
|
||||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||||
|
connectionJob?.cancel()
|
||||||
|
connectionJob = null
|
||||||
|
disconnectionJob?.cancel()
|
||||||
|
disconnectionJob = null
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
when (e) {
|
when (e) {
|
||||||
is IllegalArgumentException,
|
is IllegalArgumentException,
|
||||||
@@ -122,6 +128,8 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
||||||
|
|
||||||
|
is UnknownHostException -> onError("Unknown host")
|
||||||
|
|
||||||
else -> throw e
|
else -> throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +300,7 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
||||||
) {
|
) {
|
||||||
it?.action?.let { action ->
|
it?.action?.let { action ->
|
||||||
Log.d(TAG, "Broadcast request received: $action")
|
Log.v(TAG, "Broadcast request received: $action")
|
||||||
when (action) {
|
when (action) {
|
||||||
ACTION_CONNECT -> connect()
|
ACTION_CONNECT -> connect()
|
||||||
ACTION_DISCONNECT -> disconnect()
|
ACTION_DISCONNECT -> disconnect()
|
||||||
@@ -309,7 +317,7 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
|
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
|
||||||
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
||||||
if (state == false) {
|
if (state == false) {
|
||||||
enableNotification()
|
enableNotification()
|
||||||
} else {
|
} else {
|
||||||
@@ -442,7 +450,7 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
serviceNotification.isNotificationEnabled() &&
|
serviceNotification.isNotificationEnabled() &&
|
||||||
getSystemService<PowerManager>()?.isInteractive != false
|
getSystemService<PowerManager>()?.isInteractive != false
|
||||||
) {
|
) {
|
||||||
Log.d(TAG, "Launch traffic stats update")
|
Log.v(TAG, "Launch traffic stats update")
|
||||||
trafficStats.reset()
|
trafficStats.reset()
|
||||||
startTrafficStatsUpdateJob()
|
startTrafficStatsUpdateJob()
|
||||||
}
|
}
|
||||||
@@ -531,7 +539,7 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
protocolState.value = DISCONNECTING
|
protocolState.value = DISCONNECTING
|
||||||
|
|
||||||
disconnectionJob = connectionScope.launch {
|
disconnectionJob = connectionScope.launch {
|
||||||
connectionJob?.join()
|
connectionJob?.cancelAndJoin()
|
||||||
connectionJob = null
|
connectionJob = null
|
||||||
|
|
||||||
vpnProto?.protocol?.stopVpn()
|
vpnProto?.protocol?.stopVpn()
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.biometric.BiometricPrompt.AuthenticationResult
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "AuthActivity"
|
||||||
|
|
||||||
|
private const val AUTHENTICATORS = BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||||
|
|
||||||
|
class AuthActivity : FragmentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val biometricManager = BiometricManager.from(applicationContext)
|
||||||
|
when (biometricManager.canAuthenticate(AUTHENTICATORS)) {
|
||||||
|
BiometricManager.BIOMETRIC_SUCCESS -> {
|
||||||
|
showBiometricPrompt(biometricManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
|
||||||
|
Log.w(TAG, "Unknown biometric status")
|
||||||
|
showBiometricPrompt(biometricManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
|
||||||
|
Log.e(TAG, "The specified options are incompatible with the current Android " +
|
||||||
|
"version ${Build.VERSION.SDK_INT}")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
|
||||||
|
Log.w(TAG, "The hardware is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
|
||||||
|
Log.w(TAG, "No biometric or device credential is enrolled")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
|
||||||
|
Log.w(TAG, "There is no suitable hardware")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
|
||||||
|
Log.w(TAG, "A security vulnerability has been discovered with one or " +
|
||||||
|
"more hardware sensors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QtAndroidController.onAuthResult(true)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBiometricPrompt(biometricManager: BiometricManager) {
|
||||||
|
val executor = ContextCompat.getMainExecutor(applicationContext)
|
||||||
|
val biometricPrompt = BiometricPrompt(this, executor,
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
Log.v(TAG, "Authentication succeeded")
|
||||||
|
QtAndroidController.onAuthResult(true)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Log.w(TAG, "Authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
Log.e(TAG, "Authentication error $errorCode: $errString")
|
||||||
|
QtAndroidController.onAuthResult(false)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setAllowedAuthenticators(AUTHENTICATORS)
|
||||||
|
.setTitle("AmneziaVPN")
|
||||||
|
.setSubtitle(biometricManager.getStrings(AUTHENTICATORS)?.promptMessage)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
biometricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package org.amnezia.vpn;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.app.KeyguardManager;
|
|
||||||
import android.content.Intent;
|
|
||||||
import org.qtproject.qt.android.bindings.QtActivity;
|
|
||||||
|
|
||||||
|
|
||||||
import static android.content.Context.KEYGUARD_SERVICE;
|
|
||||||
|
|
||||||
public class AuthHelper extends QtActivity {
|
|
||||||
|
|
||||||
static final String TAG = "AuthHelper";
|
|
||||||
|
|
||||||
public static Intent getAuthIntent(Context context) {
|
|
||||||
KeyguardManager mKeyguardManager = (KeyguardManager)context.getSystemService(KEYGUARD_SERVICE);
|
|
||||||
if (mKeyguardManager.isDeviceSecure()) {
|
|
||||||
return mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Import Config Activity: $intent")
|
Log.v(TAG, "Create Import Config Activity: $intent")
|
||||||
intent?.let(::readConfig)
|
intent?.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.v(TAG, "onNewIntent: $intent")
|
||||||
intent?.let(::readConfig)
|
intent.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readConfig(intent: Intent) {
|
private fun readConfig(intent: Intent) {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_SEND -> {
|
ACTION_SEND -> {
|
||||||
Log.d(TAG, "Process SEND action, type: ${intent.type}")
|
Log.v(TAG, "Process SEND action, type: ${intent.type}")
|
||||||
when (intent.type) {
|
when (intent.type) {
|
||||||
"application/octet-stream" -> {
|
"application/octet-stream" -> {
|
||||||
intent.getUriCompat()?.let { uri ->
|
intent.getUriCompat()?.let { uri ->
|
||||||
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ACTION_VIEW -> {
|
ACTION_VIEW -> {
|
||||||
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
||||||
when (intent.scheme) {
|
when (intent.scheme) {
|
||||||
"file", "content" -> {
|
"file", "content" -> {
|
||||||
intent.data?.let { uri ->
|
intent.data?.let { uri ->
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
|
|||||||
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
||||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||||
|
|
||||||
Log.d(TAG, "Build notification: $serverName, $state")
|
Log.v(TAG, "Build notification: $serverName, $state")
|
||||||
|
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||||
@@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) {
|
|||||||
fun isNotificationEnabled(): Boolean {
|
fun isNotificationEnabled(): Boolean {
|
||||||
if (!context.isNotificationPermissionGranted()) return false
|
if (!context.isNotificationPermissionGranted()) return false
|
||||||
if (!notificationManager.areNotificationsEnabled()) return false
|
if (!notificationManager.areNotificationsEnabled()) return false
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
|
||||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
|
it.importance != NotificationManager.IMPORTANCE_NONE
|
||||||
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
|
} ?: true
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
||||||
if (context.isNotificationPermissionGranted()) {
|
if (context.isNotificationPermissionGranted()) {
|
||||||
Log.d(TAG, "Update notification: $serverName, $state")
|
Log.v(TAG, "Update notification: $serverName, $state")
|
||||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "TvFilePicker"
|
||||||
|
|
||||||
|
class TvFilePicker : ComponentActivity() {
|
||||||
|
|
||||||
|
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||||
|
setResult(RESULT_OK, Intent().apply { data = it })
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.v(TAG, "onCreate")
|
||||||
|
getFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
Log.v(TAG, "onNewIntent")
|
||||||
|
getFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFile() {
|
||||||
|
try {
|
||||||
|
Log.v(TAG, "getFile")
|
||||||
|
fileChooseResultLauncher.launch("*/*")
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
Log.w(TAG, "Activity not found")
|
||||||
|
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||||
|
finish()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to get file: $e")
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,5 +25,7 @@ object QtAndroidController {
|
|||||||
|
|
||||||
external fun onConfigImported(data: String)
|
external fun onConfigImported(data: String)
|
||||||
|
|
||||||
|
external fun onAuthResult(result: Boolean)
|
||||||
|
|
||||||
external fun decodeQrCode(data: String): Boolean
|
external fun decodeQrCode(data: String): Boolean
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.amnezia.vpn.util
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
inline fun <reified T> JSONArray.asSequence(): Sequence<T> =
|
||||||
|
(0..<length()).asSequence().map { get(it) as T }
|
||||||
|
|
||||||
|
fun JSONObject.optStringOrNull(name: String) = optString(name).ifEmpty { null }
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.amnezia.vpn.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
|
private const val TAG = "LibraryLoader"
|
||||||
|
|
||||||
|
object LibraryLoader {
|
||||||
|
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
|
||||||
|
Log.d(TAG, "Extracting library: $libraryName")
|
||||||
|
val apks = hashSetOf<String>()
|
||||||
|
context.applicationInfo.run {
|
||||||
|
sourceDir?.let { apks += it }
|
||||||
|
splitSourceDirs?.let { apks += it }
|
||||||
|
}
|
||||||
|
for (abi in Build.SUPPORTED_ABIS) {
|
||||||
|
for (apk in apks) {
|
||||||
|
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
|
||||||
|
val mappedName = System.mapLibraryName(libraryName)
|
||||||
|
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
|
||||||
|
val zipEntry = zipFile.getEntry(libraryZipPath)
|
||||||
|
zipEntry?.let {
|
||||||
|
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
|
||||||
|
FileOutputStream(destination).use { outStream ->
|
||||||
|
zipFile.getInputStream(zipEntry).use { inStream ->
|
||||||
|
inStream.copyTo(outStream, 32 * 1024)
|
||||||
|
outStream.fd.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
|
fun loadSharedLibrary(context: Context, libraryName: String) {
|
||||||
|
Log.d(TAG, "Loading library: $libraryName")
|
||||||
|
try {
|
||||||
|
System.loadLibrary(libraryName)
|
||||||
|
return
|
||||||
|
} catch (_: UnsatisfiedLinkError) {
|
||||||
|
Log.w(TAG, "Failed to load library, try to extract it from apk")
|
||||||
|
}
|
||||||
|
var tempFile: File? = null
|
||||||
|
try {
|
||||||
|
tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
|
||||||
|
if (extractLibrary(context, libraryName, tempFile)) {
|
||||||
|
System.load(tempFile.absolutePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw LoadLibraryException("Failed to load library apk: $libraryName", e)
|
||||||
|
} finally {
|
||||||
|
tempFile?.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.amnezia.vpn.util
|
package org.amnezia.vpn.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.icu.text.DateFormat
|
|
||||||
import android.icu.text.SimpleDateFormat
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
|
|||||||
import java.nio.channels.FileLock
|
import java.nio.channels.FileLock
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import org.amnezia.vpn.util.Log.Priority.D
|
import org.amnezia.vpn.util.Log.Priority.D
|
||||||
import org.amnezia.vpn.util.Log.Priority.E
|
import org.amnezia.vpn.util.Log.Priority.E
|
||||||
@@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024
|
|||||||
* | | | create a report and/or terminate the process |
|
* | | | create a report and/or terminate the process |
|
||||||
*/
|
*/
|
||||||
object Log {
|
object Log {
|
||||||
private val dateTimeFormat: Any =
|
private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
|
||||||
else object : ThreadLocal<DateFormat>() {
|
|
||||||
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var logDir: File
|
private lateinit var logDir: File
|
||||||
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
|
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
|
||||||
@@ -143,12 +135,7 @@ object Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||||
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||||
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
|
|
||||||
} else {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
|
|
||||||
}
|
|
||||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||||
"$tag: $msg\n"
|
"$tag: $msg\n"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,18 +42,12 @@ class NetworkState(
|
|||||||
private val networkCallback: NetworkCallback by lazy(NONE) {
|
private val networkCallback: NetworkCallback by lazy(NONE) {
|
||||||
object : NetworkCallback() {
|
object : NetworkCallback() {
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
Log.d(TAG, "onAvailable: $network")
|
Log.v(TAG, "onAvailable: $network")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
checkNetworkState(network, networkCapabilities)
|
||||||
checkNetworkState(network, networkCapabilities)
|
|
||||||
} else {
|
|
||||||
handler.post {
|
|
||||||
checkNetworkState(network, networkCapabilities)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
@@ -73,11 +67,11 @@ class NetworkState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
||||||
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
|
Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
Log.d(TAG, "onLost: $network")
|
Log.v(TAG, "onLost: $network")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,8 +81,8 @@ class NetworkState(
|
|||||||
Log.d(TAG, "Bind network listener")
|
Log.d(TAG, "Bind network listener")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
} else {
|
||||||
val numberAttempts = 3
|
val numberAttempts = 300
|
||||||
var attemptCount = 0
|
var attemptCount = 0
|
||||||
while(true) {
|
while(true) {
|
||||||
try {
|
try {
|
||||||
@@ -108,8 +102,6 @@ class NetworkState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
|
||||||
}
|
}
|
||||||
isListenerBound = true
|
isListenerBound = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseInetAddress(address: String): InetAddress = parseNumericAddressCompat(address)
|
fun parseInetAddress(address: String): InetAddress = InetAddress.getByName(address)
|
||||||
|
|
||||||
private val parseNumericAddressCompat: (String) -> InetAddress =
|
private val parseNumericAddressCompat: (String) -> InetAddress =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
@@ -60,7 +60,7 @@ private val parseNumericAddressCompat: (String) -> InetAddress =
|
|||||||
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
|
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
|
||||||
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
|
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
|
||||||
|
|
||||||
internal val InetAddress.ip: String
|
val InetAddress.ip: String
|
||||||
get() = if (this is Inet4Address) {
|
get() = if (this is Inet4Address) {
|
||||||
hostAddress!!
|
hostAddress!!
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+92
-69
@@ -1,60 +1,35 @@
|
|||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import java.util.TreeMap
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.amnezia.awg.GoBackend
|
import org.amnezia.awg.GoBackend
|
||||||
import org.amnezia.vpn.protocol.Protocol
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.amnezia.vpn.util.asSequence
|
||||||
import org.amnezia.vpn.util.net.InetEndpoint
|
import org.amnezia.vpn.util.net.InetEndpoint
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
|
import org.amnezia.vpn.util.optStringOrNull
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config example:
|
|
||||||
* {
|
|
||||||
* "protocol": "wireguard",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "wireguard_config_data": {
|
|
||||||
* "client_ip": "10.8.1.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "port": 12345,
|
|
||||||
* "client_pub_key": "clientPublicKeyBase64",
|
|
||||||
* "client_priv_key": "privateKeyBase64",
|
|
||||||
* "psk_key": "presharedKeyBase64",
|
|
||||||
* "server_pub_key": "publicKeyBase64",
|
|
||||||
* "config": "[Interface]
|
|
||||||
* Address = 10.8.1.1/32
|
|
||||||
* DNS = 1.1.1.1, 1.0.0.1
|
|
||||||
* PrivateKey = privateKeyBase64
|
|
||||||
*
|
|
||||||
* [Peer]
|
|
||||||
* PublicKey = publicKeyBase64
|
|
||||||
* PresharedKey = presharedKeyBase64
|
|
||||||
* AllowedIPs = 0.0.0.0/0, ::/0
|
|
||||||
* Endpoint = 100.100.100.0:12345
|
|
||||||
* PersistentKeepalive = 25
|
|
||||||
* "
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
private const val TAG = "Wireguard"
|
private const val TAG = "Wireguard"
|
||||||
|
|
||||||
open class Wireguard : Protocol() {
|
open class Wireguard : Protocol() {
|
||||||
|
|
||||||
private var tunnelHandle: Int = -1
|
private var tunnelHandle: Int = -1
|
||||||
protected open val ifName: String = "amn0"
|
protected open val ifName: String = "amn0"
|
||||||
|
private lateinit var scope: CoroutineScope
|
||||||
|
private var statusJob: Job? = null
|
||||||
|
|
||||||
override val statistics: Statistics
|
override val statistics: Statistics
|
||||||
get() {
|
get() {
|
||||||
@@ -77,69 +52,78 @@ open class Wireguard : Protocol() {
|
|||||||
|
|
||||||
override fun internalInit() {
|
override fun internalInit() {
|
||||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||||
|
if (this::scope.isInitialized) {
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
scope = CoroutineScope(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val wireguardConfig = parseConfig(config)
|
val wireguardConfig = parseConfig(config)
|
||||||
start(wireguardConfig, vpnBuilder, protect)
|
start(wireguardConfig, vpnBuilder, protect)
|
||||||
state.value = CONNECTED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
val configDataJson = config.getJSONObject("wireguard_config_data")
|
val configData = config.getJSONObject("wireguard_config_data")
|
||||||
val configData = parseConfigData(configDataJson.getString("config"))
|
|
||||||
return WireguardConfig.build {
|
return WireguardConfig.build {
|
||||||
configWireguard(configData, configDataJson)
|
configWireguard(config, configData)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
configAppSplitTunneling(config)
|
configAppSplitTunneling(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) {
|
protected fun WireguardConfig.Builder.configWireguard(config: JSONObject, configData: JSONObject) {
|
||||||
configData["Address"]?.split(",")?.map { address ->
|
configData.getString("client_ip").split(",").map { address ->
|
||||||
InetNetwork.parse(address.trim())
|
InetNetwork.parse(address.trim())
|
||||||
}?.forEach(::addAddress)
|
}.forEach(::addAddress)
|
||||||
|
|
||||||
configData["DNS"]?.split(",")?.map { dns ->
|
config.optStringOrNull("dns1")?.let { dns ->
|
||||||
parseInetAddress(dns.trim())
|
addDnsServer(parseInetAddress(dns.trim()))
|
||||||
}?.forEach(::addDnsServer)
|
}
|
||||||
|
|
||||||
|
config.optStringOrNull("dns2")?.let { dns ->
|
||||||
|
addDnsServer(parseInetAddress(dns.trim()))
|
||||||
|
}
|
||||||
|
|
||||||
val defRoutes = hashSetOf(
|
val defRoutes = hashSetOf(
|
||||||
InetNetwork("0.0.0.0", 0),
|
InetNetwork("0.0.0.0", 0),
|
||||||
InetNetwork("::", 0)
|
InetNetwork("::", 0)
|
||||||
)
|
)
|
||||||
val routes = hashSetOf<InetNetwork>()
|
val routes = hashSetOf<InetNetwork>()
|
||||||
configData["AllowedIPs"]?.split(",")?.map { route ->
|
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||||
InetNetwork.parse(route.trim())
|
InetNetwork.parse(route.trim())
|
||||||
}?.forEach(routes::add)
|
}.forEach(routes::add)
|
||||||
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
|
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
|
||||||
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
||||||
addRoutes(routes)
|
addRoutes(routes)
|
||||||
|
|
||||||
configDataJson.optString("mtu").let { mtu ->
|
configData.optStringOrNull("mtu")?.let { setMtu(it.toInt()) }
|
||||||
if (mtu.isNotEmpty()) {
|
|
||||||
setMtu(mtu.toInt())
|
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||||
} else {
|
val port = configData.getInt("port")
|
||||||
configData["MTU"]?.let { setMtu(it.toInt()) }
|
setEndpoint(InetEndpoint(host, port))
|
||||||
}
|
|
||||||
|
if (configData.optBoolean("isObfuscationEnabled")) {
|
||||||
|
setUseProtocolExtension(true)
|
||||||
|
configExtensionParameters(configData)
|
||||||
}
|
}
|
||||||
|
|
||||||
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
|
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||||
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
|
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||||
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
|
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||||
configData["PublicKey"]?.let { setPublicKeyHex(it.base64ToHex()) }
|
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||||
configData["PresharedKey"]?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun parseConfigData(data: String): Map<String, String> {
|
protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) {
|
||||||
val parsedData = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
|
configData.optStringOrNull("Jc")?.let { setJc(it.toInt()) }
|
||||||
data.lineSequence()
|
configData.optStringOrNull("Jmin")?.let { setJmin(it.toInt()) }
|
||||||
.filter { it.isNotEmpty() && !it.startsWith('[') }
|
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||||
.forEach { line ->
|
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||||
val attr = line.split("=", limit = 2)
|
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||||
parsedData[attr.first().trim()] = attr.last().trim()
|
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||||
}
|
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||||
return parsedData
|
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||||
|
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
@@ -168,6 +152,43 @@ open class Wireguard : Protocol() {
|
|||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
||||||
}
|
}
|
||||||
|
launchStatusJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchStatusJob() {
|
||||||
|
Log.d(TAG, "Launch status job")
|
||||||
|
statusJob = scope.launch {
|
||||||
|
while (true) {
|
||||||
|
val lastHandshake = getLastHandshake()
|
||||||
|
Log.v(TAG, "lastHandshake=$lastHandshake")
|
||||||
|
if (lastHandshake == 0L) {
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED
|
||||||
|
else if (lastHandshake == -1L) state.value = DISCONNECTED
|
||||||
|
statusJob = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLastHandshake(): Long {
|
||||||
|
if (tunnelHandle == -1) {
|
||||||
|
Log.e(TAG, "Trying to get config of a non-existent tunnel")
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
val config = GoBackend.awgGetConfig(tunnelHandle)
|
||||||
|
if (config == null) {
|
||||||
|
Log.e(TAG, "Failed to get tunnel config")
|
||||||
|
return -2
|
||||||
|
}
|
||||||
|
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
|
||||||
|
if (lastHandshake == null) {
|
||||||
|
Log.e(TAG, "Failed to get last_handshake_time_sec")
|
||||||
|
return -2
|
||||||
|
}
|
||||||
|
return lastHandshake
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopVpn() {
|
override fun stopVpn() {
|
||||||
@@ -175,6 +196,8 @@ open class Wireguard : Protocol() {
|
|||||||
Log.w(TAG, "Tunnel already down")
|
Log.w(TAG, "Tunnel already down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
statusJob?.cancel()
|
||||||
|
statusJob = null
|
||||||
val handleToClose = tunnelHandle
|
val handleToClose = tunnelHandle
|
||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
GoBackend.awgTurnOff(handleToClose)
|
GoBackend.awgTurnOff(handleToClose)
|
||||||
|
|||||||
+71
-2
@@ -1,6 +1,7 @@
|
|||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||||
import org.amnezia.vpn.util.net.InetEndpoint
|
import org.amnezia.vpn.util.net.InetEndpoint
|
||||||
|
|
||||||
@@ -12,7 +13,17 @@ open class WireguardConfig protected constructor(
|
|||||||
val persistentKeepalive: Int,
|
val persistentKeepalive: Int,
|
||||||
val publicKeyHex: String,
|
val publicKeyHex: String,
|
||||||
val preSharedKeyHex: String?,
|
val preSharedKeyHex: String?,
|
||||||
val privateKeyHex: String
|
val privateKeyHex: String,
|
||||||
|
val useProtocolExtension: Boolean,
|
||||||
|
val jc: Int?,
|
||||||
|
val jmin: Int?,
|
||||||
|
val jmax: Int?,
|
||||||
|
val s1: Int?,
|
||||||
|
val s2: Int?,
|
||||||
|
val h1: Long?,
|
||||||
|
val h2: Long?,
|
||||||
|
val h3: Long?,
|
||||||
|
val h4: Long?
|
||||||
) : ProtocolConfig(protocolConfigBuilder) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
@@ -21,7 +32,17 @@ open class WireguardConfig protected constructor(
|
|||||||
builder.persistentKeepalive,
|
builder.persistentKeepalive,
|
||||||
builder.publicKeyHex,
|
builder.publicKeyHex,
|
||||||
builder.preSharedKeyHex,
|
builder.preSharedKeyHex,
|
||||||
builder.privateKeyHex
|
builder.privateKeyHex,
|
||||||
|
builder.useProtocolExtension,
|
||||||
|
builder.jc,
|
||||||
|
builder.jmin,
|
||||||
|
builder.jmax,
|
||||||
|
builder.s1,
|
||||||
|
builder.s2,
|
||||||
|
builder.h1,
|
||||||
|
builder.h2,
|
||||||
|
builder.h3,
|
||||||
|
builder.h4
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||||
@@ -33,6 +54,30 @@ open class WireguardConfig protected constructor(
|
|||||||
|
|
||||||
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
||||||
appendLine("private_key=$privateKeyHex")
|
appendLine("private_key=$privateKeyHex")
|
||||||
|
if (useProtocolExtension) {
|
||||||
|
validateProtocolExtensionParameters()
|
||||||
|
appendLine("jc=$jc")
|
||||||
|
appendLine("jmin=$jmin")
|
||||||
|
appendLine("jmax=$jmax")
|
||||||
|
appendLine("s1=$s1")
|
||||||
|
appendLine("s2=$s2")
|
||||||
|
appendLine("h1=$h1")
|
||||||
|
appendLine("h2=$h2")
|
||||||
|
appendLine("h3=$h3")
|
||||||
|
appendLine("h4=$h4")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateProtocolExtensionParameters() {
|
||||||
|
if (jc == null) throw BadConfigException("Parameter jc is undefined")
|
||||||
|
if (jmin == null) throw BadConfigException("Parameter jmin is undefined")
|
||||||
|
if (jmax == null) throw BadConfigException("Parameter jmax is undefined")
|
||||||
|
if (s1 == null) throw BadConfigException("Parameter s1 is undefined")
|
||||||
|
if (s2 == null) throw BadConfigException("Parameter s2 is undefined")
|
||||||
|
if (h1 == null) throw BadConfigException("Parameter h1 is undefined")
|
||||||
|
if (h2 == null) throw BadConfigException("Parameter h2 is undefined")
|
||||||
|
if (h3 == null) throw BadConfigException("Parameter h3 is undefined")
|
||||||
|
if (h4 == null) throw BadConfigException("Parameter h4 is undefined")
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||||
@@ -65,6 +110,18 @@ open class WireguardConfig protected constructor(
|
|||||||
|
|
||||||
override var mtu: Int = WIREGUARD_DEFAULT_MTU
|
override var mtu: Int = WIREGUARD_DEFAULT_MTU
|
||||||
|
|
||||||
|
internal var useProtocolExtension: Boolean = false
|
||||||
|
|
||||||
|
internal var jc: Int? = null
|
||||||
|
internal var jmin: Int? = null
|
||||||
|
internal var jmax: Int? = null
|
||||||
|
internal var s1: Int? = null
|
||||||
|
internal var s2: Int? = null
|
||||||
|
internal var h1: Long? = null
|
||||||
|
internal var h2: Long? = null
|
||||||
|
internal var h3: Long? = null
|
||||||
|
internal var h4: Long? = null
|
||||||
|
|
||||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||||
|
|
||||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||||
@@ -75,6 +132,18 @@ open class WireguardConfig protected constructor(
|
|||||||
|
|
||||||
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
|
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
|
||||||
|
|
||||||
|
fun setUseProtocolExtension(useProtocolExtension: Boolean) = apply { this.useProtocolExtension = useProtocolExtension }
|
||||||
|
|
||||||
|
fun setJc(jc: Int) = apply { this.jc = jc }
|
||||||
|
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
|
||||||
|
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||||
|
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||||
|
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||||
|
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||||
|
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||||
|
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||||
|
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||||
|
|
||||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,72 +17,10 @@ import org.amnezia.vpn.protocol.xray.libXray.Logger
|
|||||||
import org.amnezia.vpn.protocol.xray.libXray.Tun2SocksConfig
|
import org.amnezia.vpn.protocol.xray.libXray.Tun2SocksConfig
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
|
import org.amnezia.vpn.util.net.ip
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config example:
|
|
||||||
* {
|
|
||||||
* "appSplitTunnelType": 0,
|
|
||||||
* "config_version": 0,
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "protocol": "xray",
|
|
||||||
* "splitTunnelApps": [],
|
|
||||||
* "splitTunnelSites": [],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "xray_config_data": {
|
|
||||||
* "inbounds": [
|
|
||||||
* {
|
|
||||||
* "listen": "127.0.0.1",
|
|
||||||
* "port": 8080,
|
|
||||||
* "protocol": "socks",
|
|
||||||
* "settings": {
|
|
||||||
* "udp": true
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ],
|
|
||||||
* "log": {
|
|
||||||
* "loglevel": "error"
|
|
||||||
* },
|
|
||||||
* "outbounds": [
|
|
||||||
* {
|
|
||||||
* "protocol": "vless",
|
|
||||||
* "settings": {
|
|
||||||
* "vnext": [
|
|
||||||
* {
|
|
||||||
* "address": "100.100.100.0",
|
|
||||||
* "port": 443,
|
|
||||||
* "users": [
|
|
||||||
* {
|
|
||||||
* "encryption": "none",
|
|
||||||
* "flow": "xtls-rprx-vision",
|
|
||||||
* "id": "id"
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* },
|
|
||||||
* "streamSettings": {
|
|
||||||
* "network": "tcp",
|
|
||||||
* "realitySettings": {
|
|
||||||
* "fingerprint": "chrome",
|
|
||||||
* "publicKey": "publicKey",
|
|
||||||
* "serverName": "google.com",
|
|
||||||
* "shortId": "id",
|
|
||||||
* "spiderX": ""
|
|
||||||
* },
|
|
||||||
* "security": "reality"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
private const val TAG = "Xray"
|
private const val TAG = "Xray"
|
||||||
private const val LIBXRAY_TAG = "libXray"
|
private const val LIBXRAY_TAG = "libXray"
|
||||||
|
|
||||||
@@ -109,7 +47,7 @@ class Xray : Protocol() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
Log.w(TAG, "XRay already running")
|
Log.w(TAG, "XRay already running")
|
||||||
return
|
return
|
||||||
@@ -124,7 +62,15 @@ class Xray : Protocol() {
|
|||||||
.put("loglevel", "warning")
|
.put("loglevel", "warning")
|
||||||
.put("access", "none") // disable access log
|
.put("access", "none") // disable access log
|
||||||
|
|
||||||
start(xrayConfig, xrayJsonConfig.toString(), vpnBuilder, protect)
|
var xrayJsonConfigString = xrayJsonConfig.toString()
|
||||||
|
config.getString("hostName").let { hostName ->
|
||||||
|
val ipAddress = parseInetAddress(hostName).ip
|
||||||
|
if (hostName != ipAddress) {
|
||||||
|
xrayJsonConfigString = xrayJsonConfigString.replace(hostName, ipAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(xrayConfig, xrayJsonConfigString, vpnBuilder, protect)
|
||||||
state.value = CONNECTED
|
state.value = CONNECTED
|
||||||
isRunning = true
|
isRunning = true
|
||||||
}
|
}
|
||||||
@@ -184,8 +130,8 @@ class Xray : Protocol() {
|
|||||||
LibXray.initXray(assetsPath)
|
LibXray.initXray(assetsPath)
|
||||||
val geoDir = File(assetsPath, "geo").absolutePath
|
val geoDir = File(assetsPath, "geo").absolutePath
|
||||||
val configPath = File(context.cacheDir, "config.json")
|
val configPath = File(context.cacheDir, "config.json")
|
||||||
Log.d(TAG, "xray.location.asset: $geoDir")
|
Log.v(TAG, "xray.location.asset: $geoDir")
|
||||||
Log.d(TAG, "config: $configPath")
|
Log.v(TAG, "config: $configPath")
|
||||||
try {
|
try {
|
||||||
configPath.writeText(configJson)
|
configPath.writeText(configJson)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
|||||||
@@ -2,15 +2,11 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
|||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
||||||
|
|
||||||
if(NOT IOS AND NOT ANDROID)
|
|
||||||
include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
||||||
set(LIBS ${LIBS} SortFilterProxyModel)
|
set(LIBS ${LIBS} SortFilterProxyModel)
|
||||||
|
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||||
|
|
||||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||||
include(${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/QSimpleCrypto.cmake)
|
|
||||||
|
|
||||||
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
|
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
|
||||||
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
|
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
|
||||||
@@ -83,13 +79,12 @@ set(BUILD_WITH_QT6 ON)
|
|||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||||
set(LIBS ${LIBS} qt6keychain)
|
set(LIBS ${LIBS} qt6keychain)
|
||||||
|
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${OPENSSL_INCLUDE_DIR}
|
${OPENSSL_INCLUDE_DIR}
|
||||||
${LIBSSH_INCLUDE_DIR}/include
|
${LIBSSH_INCLUDE_DIR}/include
|
||||||
${LIBSSH_ROOT_DIR}/include
|
${LIBSSH_ROOT_DIR}/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
|
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
|
||||||
|
|
||||||
|
include_directories(${QSIMPLECRYPTO_DIR})
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QAead.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QRsa.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QX509.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
|
||||||
|
)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||||
|
|
||||||
set(APP_ANDROID_MIN_SDK 24)
|
set(APP_ANDROID_MIN_SDK 26)
|
||||||
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||||
"The minimum API level supported by the application or library" FORCE)
|
"The minimum API level supported by the application or library" FORCE)
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
|||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
||||||
)
|
)
|
||||||
@@ -35,7 +34,6 @@ set(HEADERS ${HEADERS}
|
|||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -76,11 +76,7 @@ set_target_properties(${PROJECT} PROPERTIES
|
|||||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
|
||||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN"
|
|
||||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
|
|
||||||
)
|
)
|
||||||
set_target_properties(${PROJECT} PROPERTIES
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
@@ -126,9 +122,9 @@ add_subdirectory(ios/networkextension)
|
|||||||
add_dependencies(${PROJECT} networkextension)
|
add_dependencies(${PROJECT} networkextension)
|
||||||
|
|
||||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
|
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
|
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/migrations.h
|
||||||
|
${CLIENT_ROOT_DIR}/../ipc/ipc.h
|
||||||
|
${CLIENT_ROOT_DIR}/amnezia_application.h
|
||||||
|
${CLIENT_ROOT_DIR}/containers/containers_defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/errorstrings.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/scripts_registry.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/server_defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/api/apiDefs.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/qrCodeUtils.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/serverController.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/protocols_defs.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/pages.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/qautostart.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/sshclient.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/networkUtilities.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/serialization.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
|
||||||
|
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||||
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mozilla headres
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/models/server.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT IOS)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ANDROID)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/migrations.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/amnezia_application.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/containers/containers_defs.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/errorstrings.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/scripts_registry.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/server_defs.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/qautostart.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/sshclient.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/vless.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mozilla sources
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT IOS)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ANDROID)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h)
|
||||||
|
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
|
||||||
|
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
|
||||||
|
|
||||||
|
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h)
|
||||||
|
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp)
|
||||||
|
|
||||||
|
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/services/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/api/*.h
|
||||||
|
)
|
||||||
|
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/*.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
|
||||||
|
)
|
||||||
|
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${COMMON_FILES_H}
|
||||||
|
${PAGE_LOGIC_H}
|
||||||
|
${CONFIGURATORS_H}
|
||||||
|
${UI_MODELS_H}
|
||||||
|
${UI_CONTROLLERS_H}
|
||||||
|
)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${COMMON_FILES_CPP}
|
||||||
|
${PAGE_LOGIC_CPP}
|
||||||
|
${CONFIGURATORS_CPP}
|
||||||
|
${UI_MODELS_CPP}
|
||||||
|
${UI_CONTROLLERS_CPP}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(RESOURCES ${RESOURCES}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
|
message("Client desktop build")
|
||||||
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/privileged_process.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "core/networkUtilities.h"
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
|
|
||||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
QObject *parent)
|
QObject *parent)
|
||||||
: ConfiguratorBase(settings, serverController, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
@@ -119,20 +120,14 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
|||||||
|
|
||||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
// Prevent ipv6 leak
|
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
|
||||||
#endif
|
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
|
|
||||||
// no redirect-gateway
|
// no redirect-gateway
|
||||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
|
||||||
#endif
|
#endif
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
}
|
}
|
||||||
@@ -169,7 +164,6 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
|
|||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
|
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
|
|
||||||
// remove block-outside-dns for all exported configs
|
// remove block-outside-dns for all exported configs
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
@@ -19,13 +20,17 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
|
||||||
bool isAwg, QObject *parent)
|
const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||||
|
QObject *parent)
|
||||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||||
{
|
{
|
||||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
m_serverConfigPath =
|
||||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
m_serverPublicKeyPath =
|
||||||
|
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||||
|
m_serverPskKeyPath =
|
||||||
|
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||||
|
|
||||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||||
@@ -63,9 +68,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
|
||||||
|
{
|
||||||
|
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
|
||||||
|
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
|
||||||
|
|
||||||
|
QList<QHostAddress> ips;
|
||||||
|
|
||||||
|
while (matchIterator.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = matchIterator.next();
|
||||||
|
const QString address_string { match.captured(1) };
|
||||||
|
const QHostAddress address { address_string };
|
||||||
|
if (address.isNull()) {
|
||||||
|
qWarning() << "Couldn't recognize the ip address: " << address_string;
|
||||||
|
} else {
|
||||||
|
ips << address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig,
|
||||||
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
@@ -76,53 +103,45 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get list of already created clients (only IP addresses)
|
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||||
QString nextIpNumber;
|
QString stdOut;
|
||||||
{
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
stdOut += data + "\n";
|
||||||
QString stdOut;
|
return ErrorCode::NoError;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
};
|
||||||
stdOut += data + "\n";
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
};
|
|
||||||
|
|
||||||
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
auto ips = getIpsFromConf(stdOut);
|
||||||
|
|
||||||
stdOut.replace("AllowedIPs = ", "");
|
QHostAddress nextIp = [&] {
|
||||||
stdOut.replace("/32", "");
|
QHostAddress result;
|
||||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
QHostAddress lastIp;
|
||||||
|
if (ips.empty()) {
|
||||||
// Calc next IP address
|
lastIp.setAddress(containerConfig.value(m_protocolName)
|
||||||
if (ips.isEmpty()) {
|
.toObject()
|
||||||
nextIpNumber = "2";
|
.value(config_key::subnet_address)
|
||||||
|
.toString(protocols::wireguard::defaultSubnetAddress));
|
||||||
} else {
|
} else {
|
||||||
int next = ips.last().split(".").last().toInt() + 1;
|
lastIp = ips.last();
|
||||||
if (next > 254) {
|
|
||||||
errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
|
||||||
}
|
|
||||||
nextIpNumber = QString::number(next);
|
|
||||||
}
|
}
|
||||||
}
|
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
||||||
|
switch (lastOctet) {
|
||||||
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
||||||
{
|
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
||||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
||||||
if (l.isEmpty()) {
|
|
||||||
errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
|
||||||
}
|
}
|
||||||
l.removeLast();
|
|
||||||
l.append(nextIpNumber);
|
|
||||||
|
|
||||||
connData.clientIP = l.join(".");
|
return result;
|
||||||
}
|
}();
|
||||||
|
|
||||||
|
connData.clientIP = nextIp.toString();
|
||||||
|
|
||||||
// Get keys
|
// Get keys
|
||||||
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
connData.serverPubKey =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||||
connData.serverPubKey.replace("\n", "");
|
connData.serverPubKey.replace("\n", "");
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
@@ -149,10 +168,12 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
||||||
|
.arg(m_serverConfigPath);
|
||||||
|
|
||||||
errorCode = m_serverController->runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
credentials,
|
||||||
|
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
@@ -161,8 +182,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||||
QString config =
|
QString config = m_serverController->replaceVars(
|
||||||
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
@@ -187,21 +208,25 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
||||||
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
||||||
|
|
||||||
|
jConfig[config_key::persistent_keep_alive] = "25";
|
||||||
|
QJsonArray allowedIps { "0.0.0.0/0", "::/0" };
|
||||||
|
jConfig[config_key::allowed_ips] = allowedIps;
|
||||||
|
|
||||||
jConfig[config_key::clientId] = connData.clientPubKey;
|
jConfig[config_key::clientId] = connData.clientPubKey;
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
|
||||||
QString &protocolConfigString)
|
const bool isApiConfig, QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
return protocolConfigString;
|
return protocolConfigString;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
|
||||||
QString &protocolConfigString)
|
const bool isApiConfig, QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||||
#define WIREGUARD_CONFIGURATOR_H
|
#define WIREGUARD_CONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
QObject *parent = nullptr);
|
bool isAwg, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData
|
struct ConnectionData
|
||||||
{
|
{
|
||||||
@@ -26,15 +27,18 @@ public:
|
|||||||
QString port;
|
QString port;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
QString &protocolConfigString);
|
||||||
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
|
||||||
static ConnectionData genClientKeys();
|
static ConnectionData genClientKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QList<QHostAddress> getIpsFromConf(const QString &input);
|
||||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
|
|||||||
@@ -3,38 +3,169 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QUuid>
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Logger logger("XrayConfigurator");
|
||||||
|
}
|
||||||
|
|
||||||
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: ConfiguratorBase(settings, serverController, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
ErrorCode &errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
// Generate new UUID for client
|
||||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
|
|
||||||
QString xrayPublicKey =
|
// Get current server config
|
||||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
QString currentConfig = m_serverController->getTextFileFromContainer(
|
||||||
xrayPublicKey.replace("\n", "");
|
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||||
|
|
||||||
QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
|
|
||||||
xrayUuid.replace("\n", "");
|
|
||||||
|
|
||||||
QString xrayShortId =
|
|
||||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
|
||||||
xrayShortId.replace("\n", "");
|
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Failed to get server config file";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
config.replace("$XRAY_CLIENT_ID", xrayUuid);
|
// Parse current config as JSON
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||||
|
if (doc.isNull() || !doc.isObject()) {
|
||||||
|
logger.error() << "Failed to parse server config JSON";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject serverConfig = doc.object();
|
||||||
|
|
||||||
|
// Validate server config structure
|
||||||
|
if (!serverConfig.contains("inbounds")) {
|
||||||
|
logger.error() << "Server config missing 'inbounds' field";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray inbounds = serverConfig["inbounds"].toArray();
|
||||||
|
if (inbounds.isEmpty()) {
|
||||||
|
logger.error() << "Server config has empty 'inbounds' array";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject inbound = inbounds[0].toObject();
|
||||||
|
if (!inbound.contains("settings")) {
|
||||||
|
logger.error() << "Inbound missing 'settings' field";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject settings = inbound["settings"].toObject();
|
||||||
|
if (!settings.contains("clients")) {
|
||||||
|
logger.error() << "Settings missing 'clients' field";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray clients = settings["clients"].toArray();
|
||||||
|
|
||||||
|
// Create configuration for new client
|
||||||
|
QJsonObject clientConfig {
|
||||||
|
{"id", clientId},
|
||||||
|
{"flow", "xtls-rprx-vision"}
|
||||||
|
};
|
||||||
|
|
||||||
|
clients.append(clientConfig);
|
||||||
|
|
||||||
|
// Update config
|
||||||
|
settings["clients"] = clients;
|
||||||
|
inbound["settings"] = settings;
|
||||||
|
inbounds[0] = inbound;
|
||||||
|
serverConfig["inbounds"] = inbounds;
|
||||||
|
|
||||||
|
// Save updated config to server
|
||||||
|
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||||
|
errorCode = m_serverController->uploadTextFileToContainer(
|
||||||
|
container,
|
||||||
|
credentials,
|
||||||
|
updatedConfig,
|
||||||
|
amnezia::protocols::xray::serverConfigPath,
|
||||||
|
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||||
|
);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Failed to upload updated config";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart container
|
||||||
|
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||||
|
errorCode = m_serverController->runScript(
|
||||||
|
credentials,
|
||||||
|
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Failed to restart container";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
|
{
|
||||||
|
// Get client ID from prepareServerConfig
|
||||||
|
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||||
|
logger.error() << "Failed to prepare server config";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||||
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
|
if (config.isEmpty()) {
|
||||||
|
logger.error() << "Failed to get config template";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString xrayPublicKey =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
||||||
|
logger.error() << "Failed to get public key";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
xrayPublicKey.replace("\n", "");
|
||||||
|
|
||||||
|
QString xrayShortId =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
||||||
|
logger.error() << "Failed to get short ID";
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
xrayShortId.replace("\n", "");
|
||||||
|
|
||||||
|
// Validate all required variables are present
|
||||||
|
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
|
||||||
|
logger.error() << "Config template missing required variables:"
|
||||||
|
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
|
||||||
|
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
|
||||||
|
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
|
||||||
|
errorCode = ErrorCode::InternalError;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
config.replace("$XRAY_CLIENT_ID", xrayClientId);
|
||||||
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||||
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ public:
|
|||||||
|
|
||||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
ErrorCode &errorCode);
|
ErrorCode &errorCode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
|
ErrorCode &errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // XRAY_CONFIGURATOR_H
|
#endif // XRAY_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -110,22 +110,19 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||||
"own security protocol with SSL/TLS for key exchange.") },
|
"own security protocol with SSL/TLS for key exchange.") },
|
||||||
{ DockerContainer::ShadowSocks,
|
{ DockerContainer::ShadowSocks,
|
||||||
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
|
QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") },
|
||||||
"may be recognized by analysis systems in some highly censored regions.") },
|
|
||||||
{ DockerContainer::Cloak,
|
{ DockerContainer::Cloak,
|
||||||
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
||||||
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
|
"active-probing detection. It is very resistant to detection, but offers low speed.") },
|
||||||
"of censorship.") },
|
|
||||||
{ DockerContainer::WireGuard,
|
{ DockerContainer::WireGuard,
|
||||||
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
|
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||||
"consumption. Recommended for regions with low levels of censorship.") },
|
"consumption.") },
|
||||||
{ DockerContainer::Awg,
|
{ DockerContainer::Awg,
|
||||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||||
"but very resistant to blockages. "
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||||
"Recommended for regions with high levels of censorship.") },
|
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
|
||||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
"It is highly resistant to detection and offers high speed.") },
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
||||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||||
@@ -144,20 +141,20 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
return {
|
return {
|
||||||
{ DockerContainer::OpenVpn,
|
{ DockerContainer::OpenVpn,
|
||||||
QObject::tr(
|
QObject::tr(
|
||||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
||||||
"It employs its unique security protocol, "
|
"It employs its unique security protocol, "
|
||||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
||||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
||||||
"catering to a wide range of devices and operating systems. "
|
"catering to a wide range of devices and operating systems. "
|
||||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
||||||
"which continually reinforces its security. "
|
"which continually reinforces its security. "
|
||||||
"With a strong balance of performance, security, and compatibility, "
|
"With a strong balance of performance, security, and compatibility, "
|
||||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Available in the AmneziaVPN across all platforms\n"
|
||||||
"* Normal power consumption on mobile devices\n"
|
"* Normal power consumption on mobile devices\n"
|
||||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||||
"* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
|
"* Recognised by DPI systems and therefore susceptible to blocking\n"
|
||||||
"* Can operate over both TCP and UDP network protocols.") },
|
"* Can operate over both TCP and UDP network protocols.") },
|
||||||
{ DockerContainer::ShadowSocks,
|
{ DockerContainer::ShadowSocks,
|
||||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
||||||
@@ -169,28 +166,26 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
"* Works over TCP network protocol.") },
|
"* Works over TCP network protocol.") },
|
||||||
{ DockerContainer::Cloak,
|
{ DockerContainer::Cloak,
|
||||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||||
"protecting against blocking.\n\n"
|
"protecting against detection.\n\n"
|
||||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
||||||
"and the server.\n\n"
|
"and the server.\n\n"
|
||||||
"Cloak protects OpenVPN from detection and blocking. \n\n"
|
"Cloak protects OpenVPN from detection. \n\n"
|
||||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||||
"being detected\n\n"
|
"being detected\n\n"
|
||||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||||
"invisible to analysis systems.\n\n"
|
"invisible to analysis systems.\n\n"
|
||||||
"If there is a extreme level of Internet censorship in your region, we advise you to use only "
|
|
||||||
"OpenVPN over Cloak from the first connection\n\n"
|
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Available in the AmneziaVPN across all platforms\n"
|
||||||
"* High power consumption on mobile devices\n"
|
"* High power consumption on mobile devices\n"
|
||||||
"* Flexible settings\n"
|
"* Flexible settings\n"
|
||||||
"* Not recognised by DPI analysis systems\n"
|
"* Not recognised by detection systems\n"
|
||||||
"* Works over TCP network protocol, 443 port.\n") },
|
"* Works over TCP network protocol, 443 port.\n") },
|
||||||
{ DockerContainer::WireGuard,
|
{ DockerContainer::WireGuard,
|
||||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
||||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||||
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
|
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
|
||||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||||
@@ -213,18 +208,18 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Available in the AmneziaVPN across all platforms\n"
|
||||||
"* Low power consumption\n"
|
"* Low power consumption\n"
|
||||||
"* Minimum number of settings\n"
|
"* Minimum number of settings\n"
|
||||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
"* Not recognised by traffic analysis systems\n"
|
||||||
"* Works over UDP network protocol.") },
|
"* Works over UDP network protocol.") },
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||||
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
|
||||||
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
"It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, "
|
||||||
"thus presenting an authentic TLS certificate and data. \n"
|
"thus presenting an authentic TLS certificate and data. \n"
|
||||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||||
"legitimate sites without the need for specific configurations. \n"
|
"legitimate sites without the need for specific configurations. \n"
|
||||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
|
||||||
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
"This makes REALITY a robust solution for maintaining internet freedom.")
|
||||||
},
|
},
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||||
@@ -332,9 +327,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
|||||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return true;
|
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
// case DockerContainer::Cloak: return true;
|
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,9 +335,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
|||||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return tr("Low");
|
case DockerContainer::Awg: return tr("Automatic");
|
||||||
case DockerContainer::Awg: return tr("High");
|
|
||||||
// case DockerContainer::Cloak: return tr("Extreme");
|
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,10 +343,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
|||||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
|
||||||
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
||||||
// case DockerContainer::Cloak:
|
|
||||||
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,9 +352,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
|||||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return 3;
|
case DockerContainer::Awg: return 1;
|
||||||
case DockerContainer::Awg: return 2;
|
|
||||||
// case DockerContainer::Cloak: return 1;
|
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,9 +371,9 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||||||
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||||
{
|
{
|
||||||
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||||
.toObject()
|
.toObject()
|
||||||
.value(config_key::last_config)
|
.value(config_key::last_config)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef APIDEFS_H
|
||||||
|
#define APIDEFS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace apiDefs
|
||||||
|
{
|
||||||
|
enum ConfigType {
|
||||||
|
AmneziaFreeV2 = 0,
|
||||||
|
AmneziaFreeV3,
|
||||||
|
AmneziaPremiumV1,
|
||||||
|
AmneziaPremiumV2,
|
||||||
|
SelfHosted,
|
||||||
|
ExternalPremium
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ConfigSource {
|
||||||
|
Telegram = 1,
|
||||||
|
AmneziaGateway
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace key
|
||||||
|
{
|
||||||
|
constexpr QLatin1String configVersion("config_version");
|
||||||
|
|
||||||
|
constexpr QLatin1String apiConfig("api_config");
|
||||||
|
constexpr QLatin1String stackType("stack_type");
|
||||||
|
constexpr QLatin1String serviceType("service_type");
|
||||||
|
|
||||||
|
constexpr QLatin1String vpnKey("vpn_key");
|
||||||
|
|
||||||
|
constexpr QLatin1String installationUuid("installation_uuid");
|
||||||
|
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
||||||
|
constexpr QLatin1String lastDownloaded("last_downloaded");
|
||||||
|
constexpr QLatin1String sourceType("source_type");
|
||||||
|
|
||||||
|
constexpr QLatin1String serverCountryCode("server_country_code");
|
||||||
|
constexpr QLatin1String serverCountryName("server_country_name");
|
||||||
|
|
||||||
|
constexpr QLatin1String osVersion("os_version");
|
||||||
|
|
||||||
|
constexpr QLatin1String availableCountries("available_countries");
|
||||||
|
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||||
|
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||||
|
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||||
|
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||||
|
|
||||||
|
constexpr QLatin1String supportInfo("support_info");
|
||||||
|
constexpr QLatin1String email("email");
|
||||||
|
constexpr QLatin1String billingEmail("billing_email");
|
||||||
|
constexpr QLatin1String website("website");
|
||||||
|
constexpr QLatin1String websiteName("website_name");
|
||||||
|
constexpr QLatin1String telegram("telegram");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APIDEFS_H
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include "apiUtils.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||||
|
{
|
||||||
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
|
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||||
|
return endDate < now;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||||
|
switch (configVersion) {
|
||||||
|
case apiDefs::ConfigSource::Telegram: return true;
|
||||||
|
case apiDefs::ConfigSource::AmneziaGateway: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||||
|
switch (configVersion) {
|
||||||
|
case apiDefs::ConfigSource::Telegram: {
|
||||||
|
};
|
||||||
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
|
constexpr QLatin1String stackPremium("prem");
|
||||||
|
constexpr QLatin1String stackFree("free");
|
||||||
|
|
||||||
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
|
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||||
|
|
||||||
|
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
|
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||||
|
|
||||||
|
if (serviceType == servicePremium) {
|
||||||
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
|
} else if (serviceType == serviceFree) {
|
||||||
|
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||||
|
} else if (serviceType == serviceExternalPremium) {
|
||||||
|
return apiDefs::ConfigType::ExternalPremium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return apiDefs::ConfigType::SelfHosted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
const int httpStatusCodeConflict = 409;
|
||||||
|
const int httpStatusCodeNotFound = 404;
|
||||||
|
|
||||||
|
if (!sslErrors.empty()) {
|
||||||
|
qDebug().noquote() << sslErrors;
|
||||||
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
return amnezia::ErrorCode::NoError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
|
} else {
|
||||||
|
QString err = reply->errorString();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
|
if (httpStatusCode == httpStatusCodeConflict) {
|
||||||
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
|
} else if (httpStatusCode == httpStatusCodeNotFound) {
|
||||||
|
return amnezia::ErrorCode::ApiNotFoundError;
|
||||||
|
}
|
||||||
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "something went wrong";
|
||||||
|
return amnezia::ErrorCode::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||||
|
apiDefs::ConfigType::ExternalPremium };
|
||||||
|
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef APIUTILS_H
|
||||||
|
#define APIUTILS_H
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "apiDefs.h"
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
namespace apiUtils
|
||||||
|
{
|
||||||
|
bool isServerFromApi(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
|
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||||
|
|
||||||
|
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
|
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||||
|
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APIUTILS_H
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
#include "apiController.h"
|
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
|
||||||
#include "configurators/wireguard_configurator.h"
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
namespace configKey
|
|
||||||
{
|
|
||||||
constexpr char cloak[] = "cloak";
|
|
||||||
constexpr char awg[] = "awg";
|
|
||||||
|
|
||||||
constexpr char apiEdnpoint[] = "api_endpoint";
|
|
||||||
constexpr char accessToken[] = "api_key";
|
|
||||||
constexpr char certificate[] = "certificate";
|
|
||||||
constexpr char publicKey[] = "public_key";
|
|
||||||
constexpr char protocol[] = "protocol";
|
|
||||||
|
|
||||||
constexpr char uuid[] = "installation_uuid";
|
|
||||||
constexpr char osVersion[] = "os_version";
|
|
||||||
constexpr char appVersion[] = "app_version";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiController::ApiController(QObject *parent) : QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
|
||||||
{
|
|
||||||
if (protocol == configKey::cloak) {
|
|
||||||
config.replace("<key>", "<key>\n");
|
|
||||||
config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
|
||||||
return;
|
|
||||||
} else if (protocol == configKey::awg) {
|
|
||||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
|
||||||
auto serverConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
|
||||||
auto containers = serverConfig.value(config_key::containers).toArray();
|
|
||||||
if (containers.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto container = containers.at(0).toObject();
|
|
||||||
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
|
||||||
auto containerConfig = container.value(containerName).toObject();
|
|
||||||
auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
|
|
||||||
containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount);
|
|
||||||
containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize);
|
|
||||||
containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize);
|
|
||||||
containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize);
|
|
||||||
containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize);
|
|
||||||
containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader);
|
|
||||||
containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader);
|
|
||||||
containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader);
|
|
||||||
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
|
||||||
container[containerName] = containerConfig;
|
|
||||||
containers.replace(0, container);
|
|
||||||
serverConfig[config_key::containers] = containers;
|
|
||||||
config = QString(QJsonDocument(serverConfig).toJson());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
|
||||||
{
|
|
||||||
ApiController::ApiPayloadData apiPayload;
|
|
||||||
if (protocol == configKey::cloak) {
|
|
||||||
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
|
|
||||||
} else if (protocol == configKey::awg) {
|
|
||||||
auto connData = WireguardConfigurator::genClientKeys();
|
|
||||||
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
|
|
||||||
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
|
|
||||||
}
|
|
||||||
return apiPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
|
|
||||||
{
|
|
||||||
QJsonObject obj;
|
|
||||||
if (protocol == configKey::cloak) {
|
|
||||||
obj[configKey::certificate] = apiPayloadData.certRequest.request;
|
|
||||||
} else if (protocol == configKey::awg) {
|
|
||||||
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj[configKey::osVersion] = QSysInfo::productType();
|
|
||||||
obj[configKey::appVersion] = QString(APP_VERSION);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_IOS
|
|
||||||
IosController::Instance()->requestInetAccess();
|
|
||||||
QThread::msleep(10);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setTransferTimeout(7000);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
|
||||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
|
||||||
request.setUrl(endpoint);
|
|
||||||
|
|
||||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
|
||||||
|
|
||||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
|
||||||
|
|
||||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
|
||||||
apiPayload[configKey::uuid] = installationUuid;
|
|
||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QString contents = QString::fromUtf8(reply->readAll());
|
|
||||||
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
|
||||||
|
|
||||||
data.replace("vpn://", "");
|
|
||||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
||||||
|
|
||||||
if (ba.isEmpty()) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray ba_uncompressed = qUncompress(ba);
|
|
||||||
if (!ba_uncompressed.isEmpty()) {
|
|
||||||
ba = ba_uncompressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString configStr = ba;
|
|
||||||
processApiConfig(protocol, apiPayloadData, configStr);
|
|
||||||
|
|
||||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
|
||||||
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
|
||||||
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
|
||||||
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
|
||||||
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
|
||||||
|
|
||||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
|
||||||
serverConfig[config_key::defaultContainer] = defaultContainer;
|
|
||||||
|
|
||||||
emit configUpdated(true, serverConfig, serverIndex);
|
|
||||||
} else {
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
|
||||||
} else {
|
|
||||||
QString err = reply->errorString();
|
|
||||||
qDebug() << QString::fromUtf8(reply->readAll());
|
|
||||||
qDebug() << reply->error();
|
|
||||||
qDebug() << err;
|
|
||||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigDownloadError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reply->deleteLater();
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
|
||||||
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
|
||||||
qDebug().noquote() << errors;
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#ifndef APICONTROLLER_H
|
|
||||||
#define APICONTROLLER_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
|
||||||
#include "platforms/ios/ios_controller.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ApiController : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ApiController(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void errorOccurred(ErrorCode errorCode);
|
|
||||||
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct ApiPayloadData
|
|
||||||
{
|
|
||||||
OpenVpnConfigurator::ConnectionData certRequest;
|
|
||||||
|
|
||||||
QString wireGuardClientPrivKey;
|
|
||||||
QString wireGuardClientPubKey;
|
|
||||||
};
|
|
||||||
|
|
||||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
|
||||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
|
||||||
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // APICONTROLLER_H
|
|
||||||
@@ -0,0 +1,375 @@
|
|||||||
|
#include "coreController.h"
|
||||||
|
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID)
|
||||||
|
#include "core/installedAppsImageProvider.h"
|
||||||
|
#include "platforms/android/android_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_IOS)
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#include <AmneziaVPN-Swift.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||||
|
QQmlApplicationEngine *engine, QObject *parent)
|
||||||
|
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||||
|
{
|
||||||
|
initModels();
|
||||||
|
initControllers();
|
||||||
|
initSignalHandlers();
|
||||||
|
|
||||||
|
initAndroidController();
|
||||||
|
initAppleController();
|
||||||
|
|
||||||
|
initNotificationHandler();
|
||||||
|
|
||||||
|
auto locale = m_settings->getAppLanguage();
|
||||||
|
m_translator.reset(new QTranslator());
|
||||||
|
updateTranslator(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initModels()
|
||||||
|
{
|
||||||
|
m_containersModel.reset(new ContainersModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||||
|
|
||||||
|
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
||||||
|
|
||||||
|
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||||
|
|
||||||
|
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||||
|
|
||||||
|
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||||
|
|
||||||
|
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||||
|
|
||||||
|
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||||
|
|
||||||
|
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
||||||
|
|
||||||
|
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
||||||
|
|
||||||
|
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
||||||
|
|
||||||
|
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
||||||
|
|
||||||
|
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||||
|
|
||||||
|
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||||
|
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||||
|
|
||||||
|
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||||
|
|
||||||
|
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
|
|
||||||
|
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||||
|
|
||||||
|
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||||
|
|
||||||
|
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
||||||
|
|
||||||
|
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initControllers()
|
||||||
|
{
|
||||||
|
m_connectionController.reset(
|
||||||
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
|
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||||
|
|
||||||
|
m_focusController.reset(new FocusController(m_engine, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
|
||||||
|
|
||||||
|
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||||
|
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||||
|
|
||||||
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
|
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||||
|
|
||||||
|
m_settingsController.reset(
|
||||||
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
|
|
||||||
|
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
|
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||||
|
|
||||||
|
m_systemController.reset(new SystemController(m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||||
|
|
||||||
|
m_apiSettingsController.reset(
|
||||||
|
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||||
|
|
||||||
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAndroidController()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (!AndroidController::initLogging()) {
|
||||||
|
qFatal("Android logging initialization failed");
|
||||||
|
}
|
||||||
|
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||||
|
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||||
|
|
||||||
|
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||||
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||||
|
|
||||||
|
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||||
|
m_connectionController->onConnectionStateChanged(state);
|
||||||
|
if (m_vpnConnection)
|
||||||
|
m_vpnConnection->restoreConnection();
|
||||||
|
});
|
||||||
|
if (!AndroidController::instance()->initialize()) {
|
||||||
|
qFatal("Android controller initialization failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||||
|
emit m_pageController->goToPageHome();
|
||||||
|
m_importController->extractConfigFromData(data);
|
||||||
|
data.clear();
|
||||||
|
emit m_pageController->goToPageViewConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAppleController()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->initialize();
|
||||||
|
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||||
|
emit m_pageController->goToPageHome();
|
||||||
|
m_importController->extractConfigFromData(data);
|
||||||
|
emit m_pageController->goToPageViewConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||||
|
emit m_pageController->goToPageHome();
|
||||||
|
m_pageController->goToPageSettingsBackup();
|
||||||
|
emit m_settingsController->importBackupFromOutside(filePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initSignalHandlers()
|
||||||
|
{
|
||||||
|
initErrorMessagesHandler();
|
||||||
|
|
||||||
|
initApiCountryModelUpdateHandler();
|
||||||
|
initContainerModelUpdateHandler();
|
||||||
|
initAdminConfigRevokedHandler();
|
||||||
|
initPassphraseRequestHandler();
|
||||||
|
initTranslationsUpdatedHandler();
|
||||||
|
initAutoConnectHandler();
|
||||||
|
initAmneziaDnsToggledHandler();
|
||||||
|
initPrepareConfigHandler();
|
||||||
|
initStrictKillSwitchHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initNotificationHandler()
|
||||||
|
{
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
|
&NotificationHandler::setConnectionState);
|
||||||
|
|
||||||
|
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||||
|
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||||
|
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||||
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
|
&ConnectionController::closeConnection);
|
||||||
|
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::updateTranslator(const QLocale &locale)
|
||||||
|
{
|
||||||
|
if (!m_translator->isEmpty()) {
|
||||||
|
QCoreApplication::removeTranslator(m_translator.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList availableTranslations;
|
||||||
|
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
availableTranslations << it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code allow to load translation for the language only, without country code
|
||||||
|
const QString lang = locale.name().split("_").first();
|
||||||
|
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
|
||||||
|
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||||
|
for (const QString &translation : availableTranslations) {
|
||||||
|
if (translation.contains(translationFilePrefix)) {
|
||||||
|
strFileName = translation;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_translator->load(strFileName)) {
|
||||||
|
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||||
|
m_settings->setAppLanguage(locale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_settings->setAppLanguage(QLocale::English);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_engine->retranslate();
|
||||||
|
|
||||||
|
emit translationsUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initErrorMessagesHandler()
|
||||||
|
{
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||||
|
emit m_pageController->showErrorMessage(errorCode);
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(),
|
||||||
|
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::setQmlRoot()
|
||||||
|
{
|
||||||
|
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initApiCountryModelUpdateHandler()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
||||||
|
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||||
|
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||||
|
});
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||||
|
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initContainerModelUpdateHandler()
|
||||||
|
{
|
||||||
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
|
&ContainersModel::updateModel);
|
||||||
|
m_serversModel->resetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAdminConfigRevokedHandler()
|
||||||
|
{
|
||||||
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
|
&ServersModel::clearCachedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initPassphraseRequestHandler()
|
||||||
|
{
|
||||||
|
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||||
|
&PageController::showPassphraseRequestDrawer);
|
||||||
|
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
||||||
|
&InstallController::setEncryptedPassphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initTranslationsUpdatedHandler()
|
||||||
|
{
|
||||||
|
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator);
|
||||||
|
connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
||||||
|
connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAutoConnectHandler()
|
||||||
|
{
|
||||||
|
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||||
|
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initAmneziaDnsToggledHandler()
|
||||||
|
{
|
||||||
|
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initPrepareConfigHandler()
|
||||||
|
{
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() {
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
||||||
|
|
||||||
|
if (!m_apiConfigsController->isConfigValid()) {
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_installController->isConfigValid()) {
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connectionController->openConnection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initStrictKillSwitchHandler()
|
||||||
|
{
|
||||||
|
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged,
|
||||||
|
m_vpnConnection.get(), &VpnConnection::onKillSwitchModeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<PageController> CoreController::pageController() const
|
||||||
|
{
|
||||||
|
return m_pageController;
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#ifndef CORECONTROLLER_H
|
||||||
|
#define CORECONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
|
#include "ui/controllers/allowedDnsController.h"
|
||||||
|
#include "ui/controllers/connectionController.h"
|
||||||
|
#include "ui/controllers/exportController.h"
|
||||||
|
#include "ui/controllers/focusController.h"
|
||||||
|
#include "ui/controllers/importController.h"
|
||||||
|
#include "ui/controllers/installController.h"
|
||||||
|
#include "ui/controllers/pageController.h"
|
||||||
|
#include "ui/controllers/settingsController.h"
|
||||||
|
#include "ui/controllers/sitesController.h"
|
||||||
|
#include "ui/controllers/systemController.h"
|
||||||
|
|
||||||
|
#include "ui/models/allowed_dns_model.h"
|
||||||
|
#include "ui/models/containers_model.h"
|
||||||
|
#include "ui/models/languageModel.h"
|
||||||
|
#include "ui/models/protocols/cloakConfigModel.h"
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
|
#endif
|
||||||
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
|
#include "ui/models/api/apiDevicesModel.h"
|
||||||
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
|
#include "ui/models/clientManagementModel.h"
|
||||||
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
|
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||||
|
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||||
|
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||||
|
#include "ui/models/protocols/xrayConfigModel.h"
|
||||||
|
#include "ui/models/protocols_model.h"
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
|
#include "ui/models/sites_model.h"
|
||||||
|
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
#include "ui/notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class CoreController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||||
|
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QSharedPointer<PageController> pageController() const;
|
||||||
|
void setQmlRoot();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void translationsUpdated();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initModels();
|
||||||
|
void initControllers();
|
||||||
|
void initAndroidController();
|
||||||
|
void initAppleController();
|
||||||
|
void initSignalHandlers();
|
||||||
|
|
||||||
|
void initNotificationHandler();
|
||||||
|
|
||||||
|
void updateTranslator(const QLocale &locale);
|
||||||
|
|
||||||
|
void initErrorMessagesHandler();
|
||||||
|
|
||||||
|
void initApiCountryModelUpdateHandler();
|
||||||
|
void initContainerModelUpdateHandler();
|
||||||
|
void initAdminConfigRevokedHandler();
|
||||||
|
void initPassphraseRequestHandler();
|
||||||
|
void initTranslationsUpdatedHandler();
|
||||||
|
void initAutoConnectHandler();
|
||||||
|
void initAmneziaDnsToggledHandler();
|
||||||
|
void initPrepareConfigHandler();
|
||||||
|
void initStrictKillSwitchHandler();
|
||||||
|
|
||||||
|
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
|
QSharedPointer<QTranslator> m_translator;
|
||||||
|
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||||
|
|
||||||
|
QScopedPointer<ConnectionController> m_connectionController;
|
||||||
|
QScopedPointer<FocusController> m_focusController;
|
||||||
|
QSharedPointer<PageController> m_pageController; // TODO
|
||||||
|
QScopedPointer<InstallController> m_installController;
|
||||||
|
QScopedPointer<ImportController> m_importController;
|
||||||
|
QScopedPointer<ExportController> m_exportController;
|
||||||
|
QScopedPointer<SettingsController> m_settingsController;
|
||||||
|
QScopedPointer<SitesController> m_sitesController;
|
||||||
|
QScopedPointer<SystemController> m_systemController;
|
||||||
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
QScopedPointer<AllowedDnsController> m_allowedDnsController;
|
||||||
|
|
||||||
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
|
|
||||||
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
|
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||||
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|
||||||
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|
||||||
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
|
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||||
|
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||||
|
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||||
|
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
|
||||||
|
#endif
|
||||||
|
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||||
|
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CORECONTROLLER_H
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
#include "gatewayController.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "QBlockCipher.h"
|
||||||
|
#include "QRsa.h"
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "core/networkUtilities.h"
|
||||||
|
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
#include "core/ipcclient.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace configKey
|
||||||
|
{
|
||||||
|
constexpr char aesKey[] = "aes_key";
|
||||||
|
constexpr char aesIv[] = "aes_iv";
|
||||||
|
constexpr char aesSalt[] = "aes_salt";
|
||||||
|
|
||||||
|
constexpr char apiPayload[] = "api_payload";
|
||||||
|
constexpr char keyPayload[] = "key_payload";
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||||
|
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||||
|
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
||||||
|
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
// bypass killSwitch exceptions for API-gateway
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
{
|
||||||
|
QString host = QUrl(request.url()).host();
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
|
if (!ip.isEmpty()) {
|
||||||
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkReply *reply;
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
responseBody = reply->readAll();
|
||||||
|
|
||||||
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||||
|
auto requestFunction = [&request, &responseBody](const QString &url) {
|
||||||
|
request.setUrl(url);
|
||||||
|
return amnApp->networkManager()->get(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
||||||
|
const QList<QSslError> &nestedSslErrors) {
|
||||||
|
responseBody = nestedReply->readAll();
|
||||||
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
||||||
|
sslErrors = nestedSslErrors;
|
||||||
|
reply = nestedReply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
// bypass killSwitch exceptions for API-gateway
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
{
|
||||||
|
QString host = QUrl(request.url()).host();
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
|
if (!ip.isEmpty()) {
|
||||||
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
|
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||||
|
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
|
QJsonObject keyPayload;
|
||||||
|
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||||
|
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||||
|
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||||
|
|
||||||
|
QByteArray encryptedKeyPayload;
|
||||||
|
QByteArray encryptedApiPayload;
|
||||||
|
try {
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
|
||||||
|
EVP_PKEY *publicKey = nullptr;
|
||||||
|
try {
|
||||||
|
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error loading public key from environment variables";
|
||||||
|
return ErrorCode::ApiMissingAgwPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when encrypting the request body";
|
||||||
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject requestBody;
|
||||||
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
|
||||||
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
|
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||||
|
request.setUrl(url);
|
||||||
|
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
};
|
||||||
|
|
||||||
|
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||||
|
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||||
|
encryptedResponseBody = nestedReply->readAll();
|
||||||
|
reply = nestedReply;
|
||||||
|
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
|
sslErrors = nestedSslErrors;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
if (errorCode) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList GatewayController::getProxyUrls()
|
||||||
|
{
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QNetworkReply *reply;
|
||||||
|
|
||||||
|
QStringList proxyStorageUrls;
|
||||||
|
if (m_isDevEnvironment) {
|
||||||
|
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||||
|
} else {
|
||||||
|
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
|
request.setUrl(proxyStorageUrl);
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
EVP_PKEY *privateKey = nullptr;
|
||||||
|
QByteArray responseBody;
|
||||||
|
try {
|
||||||
|
if (!m_isDevEnvironment) {
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||||
|
hash.addData(key);
|
||||||
|
QByteArray hashResult = hash.result().toHex();
|
||||||
|
|
||||||
|
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||||
|
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||||
|
|
||||||
|
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||||
|
} else {
|
||||||
|
responseBody = encryptedResponseBody;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
|
} else {
|
||||||
|
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||||
|
const QByteArray &iv, const QByteArray &salt)
|
||||||
|
{
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
qDebug() << "timeout occurred";
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return true;
|
||||||
|
} else if (responseBody.contains("html")) {
|
||||||
|
qDebug() << "the response contains an html tag";
|
||||||
|
return true;
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||||
|
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||||
|
|| responseBody.contains(errorResponsePattern3)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return true;
|
||||||
|
} else if (checkEncryption) {
|
||||||
|
try {
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||||
|
} catch (...) {
|
||||||
|
qDebug() << "failed to decrypt the data";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
||||||
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||||
|
{
|
||||||
|
QStringList proxyUrls = getProxyUrls();
|
||||||
|
std::random_device randomDevice;
|
||||||
|
std::mt19937 generator(randomDevice());
|
||||||
|
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QByteArray responseBody;
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
qDebug() << "go to the next proxy endpoint";
|
||||||
|
reply->deleteLater(); // delete the previous reply
|
||||||
|
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (replyProcessingFunction(reply, sslErrors)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class GatewayController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||||
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QStringList getProxyUrls();
|
||||||
|
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||||
|
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
|
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
|
|
||||||
|
int m_requestTimeoutMsecs;
|
||||||
|
QString m_gatewayEndpoint;
|
||||||
|
bool m_isDevEnvironment = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GATEWAYCONTROLLER_H
|
||||||
@@ -83,7 +83,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
|||||||
}
|
}
|
||||||
|
|
||||||
qDebug().noquote() << lineToExec;
|
qDebug().noquote() << lineToExec;
|
||||||
Logger::appendSshLog("Run command:" + lineToExec);
|
|
||||||
|
|
||||||
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
|
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
@@ -100,13 +99,13 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
|||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||||
{
|
{
|
||||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||||
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
|
||||||
|
|
||||||
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
QString runner =
|
||||||
|
QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
||||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||||
@@ -347,7 +346,9 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::Awg) {
|
if (container == DockerContainer::Awg) {
|
||||||
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||||
|
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||||
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||||
@@ -371,8 +372,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::WireGuard) {
|
if (container == DockerContainer::WireGuard) {
|
||||||
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||||
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||||
|
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +429,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||||||
if (errorCode)
|
if (errorCode)
|
||||||
return errorCode;
|
return errorCode;
|
||||||
|
|
||||||
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),dockerFilePath);
|
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), dockerFilePath);
|
||||||
|
|
||||||
if (errorCode)
|
if (errorCode)
|
||||||
return errorCode;
|
return errorCode;
|
||||||
@@ -436,14 +439,22 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
||||||
|
stdOut += data + "\n";
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
};
|
||||||
|
|
||||||
errorCode = runScript(credentials,
|
ErrorCode error =
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
runScript(credentials,
|
||||||
cbReadStdOut);
|
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||||
if (errorCode)
|
cbReadStdOut, cbReadStdErr);
|
||||||
return errorCode;
|
|
||||||
|
if (stdOut.contains("doesn't work on cgroups v2"))
|
||||||
|
return ErrorCode::ServerDockerOnCgroupsV2;
|
||||||
|
if (stdOut.contains("cgroup mountpoint does not exist"))
|
||||||
|
return ErrorCode::ServerCgroupMountpoint;
|
||||||
|
|
||||||
return errorCode;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
@@ -569,6 +580,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
|
|
||||||
// Xray vars
|
// Xray vars
|
||||||
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
||||||
|
vars.append({ { "$XRAY_SERVER_PORT", xrayConfig.value(config_key::port).toString(protocols::xray::defaultPort) } });
|
||||||
|
|
||||||
// Wireguard vars
|
// Wireguard vars
|
||||||
vars.append({ { "$WIREGUARD_SUBNET_IP",
|
vars.append({ { "$WIREGUARD_SUBNET_IP",
|
||||||
@@ -606,6 +618,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
||||||
|
|
||||||
// Amnezia wireguard vars
|
// Amnezia wireguard vars
|
||||||
|
vars.append({ { "$AWG_SUBNET_IP",
|
||||||
|
amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
||||||
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
||||||
|
|
||||||
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
||||||
@@ -620,13 +634,15 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
|
|
||||||
// Socks5 proxy vars
|
// Socks5 proxy vars
|
||||||
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||||
auto username = socks5ProxyConfig.value(config_key:: userName).toString();
|
auto username = socks5ProxyConfig.value(config_key::userName).toString();
|
||||||
auto password = socks5ProxyConfig.value(config_key::password).toString();
|
auto password = socks5ProxyConfig.value(config_key::password).toString();
|
||||||
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
|
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
|
||||||
vars.append({ { "$SOCKS5_USER", socks5user } });
|
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||||
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||||
|
|
||||||
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
||||||
|
? NetworkUtilities::getIPAddress(credentials.hostName)
|
||||||
|
: credentials.hostName;
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||||
} else {
|
} else {
|
||||||
@@ -700,7 +716,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||||
|
|
||||||
// TODO reimplement with netstat
|
// TODO reimplement with netstat
|
||||||
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
@@ -712,7 +728,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
udpProtoScript.append("' | grep -i udp");
|
udpProtoScript.append("' | grep -i udp");
|
||||||
tcpProtoScript.append(" | grep LISTEN");
|
tcpProtoScript.append(" | grep LISTEN");
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
ErrorCode errorCode =
|
||||||
|
runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
@@ -747,10 +764,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
|
|
||||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
if (credentials.userName == "root") {
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
@@ -764,8 +777,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
|||||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||||
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
if (!stdOut.contains("sudo"))
|
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
|
||||||
|
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
|
||||||
|
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
|
||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
|
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||||
|
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||||
|
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||||
|
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||||
|
if (stdOut.contains("password is required"))
|
||||||
|
return ErrorCode::ServerUserPasswordRequired;
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user