Compare commits
359 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1909d3c94e | ||
|
|
10a107716c | ||
|
|
5445e6637b | ||
|
|
2380cd5cfb | ||
|
|
42661618dc | ||
|
|
8a7e901d7a | ||
|
|
f8bea71716 | ||
|
|
efcc0b7efc | ||
|
|
4d17e913b5 | ||
|
|
b341934863 | ||
|
|
127f8ed3bb | ||
|
|
9dca80de18 | ||
|
|
b0a6bcc055 | ||
|
|
f0626e2eca | ||
|
|
979ab42c5a | ||
|
|
e152e84ddc | ||
|
|
2605978889 | ||
|
|
a2d30efaab | ||
|
|
d3715d00ae | ||
|
|
c37662dbe2 | ||
|
|
768ca1e73d | ||
|
|
a20516850c | ||
|
|
7a203868ec | ||
|
|
43c3ce9a6e | ||
|
|
369e08844f | ||
|
|
48a5452a65 | ||
|
|
c2f9340db6 | ||
|
|
a6508e642a | ||
|
|
a3e73797c2 | ||
|
|
df7bf204ea | ||
|
|
e16243ff55 | ||
|
|
e23cbe67ad | ||
|
|
7702f2f74c | ||
|
|
b457ef9a3f | ||
|
|
a28ed6a977 | ||
|
|
0c73682cfc | ||
|
|
7e380b6cfb | ||
|
|
63b5257986 | ||
|
|
acc4485e81 | ||
|
|
2c44999a31 | ||
|
|
e59a48f9f4 | ||
|
|
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 | ||
|
|
3fc9edd346 | ||
|
|
1a1f75d873 | ||
|
|
df02e0bf78 | ||
|
|
264d77463d | ||
|
|
0a37ffd5e3 | ||
|
|
1343d10aa7 | ||
|
|
6f96ebd8bf | ||
|
|
cb531dacb3 | ||
|
|
dfd0b4d0e5 | ||
|
|
f978f55e7f | ||
|
|
73516c28af | ||
|
|
dc85a99e08 | ||
|
|
ef06fcb4f4 | ||
|
|
ffe2314d47 | ||
|
|
4e970322d0 | ||
|
|
8dee0d27cf | ||
|
|
c520f9a2a4 | ||
|
|
003c3a23c4 | ||
|
|
1754a82f67 | ||
|
|
3384008277 | ||
|
|
af22115706 | ||
|
|
4b114fd3b6 | ||
|
|
9d531f5d74 | ||
|
|
a5564148f5 | ||
|
|
196f7778fc | ||
|
|
de20add857 | ||
|
|
c59216b58a | ||
|
|
acf7fa261a | ||
|
|
c3eddc92bd | ||
|
|
18c74f4b02 | ||
|
|
5de4b8eeb8 | ||
|
|
aae420e469 |
738 changed files with 46979 additions and 18185 deletions
39
.clang-format
Normal file
39
.clang-format
Normal file
|
|
@ -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
|
||||||
20
.clang-format-ignore
Normal file
20
.clang-format-ignore
Normal file
|
|
@ -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
|
||||||
180
.github/workflows/deploy.yml
vendored
180
.github/workflows/deploy.yml
vendored
|
|
@ -10,12 +10,18 @@ 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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
|
|
@ -65,16 +71,29 @@ jobs:
|
||||||
path: deploy/AppDir
|
path: deploy/AppDir
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: 'Upload translations artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_translations
|
||||||
|
path: client/translations
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
|
|
@ -130,13 +149,19 @@ 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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
|
@ -171,7 +196,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'
|
||||||
|
|
@ -198,7 +223,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 }}
|
||||||
|
|
@ -220,20 +249,88 @@ jobs:
|
||||||
|
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-MacOS:
|
Build-MacOS-old:
|
||||||
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
|
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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_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'
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.QT_VERSION }}
|
||||||
|
host: 'mac'
|
||||||
|
target: 'desktop'
|
||||||
|
arch: 'clang_64'
|
||||||
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
|
dir: ${{ runner.temp }}
|
||||||
|
setup-python: 'true'
|
||||||
|
set-env: 'true'
|
||||||
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
|
|
||||||
|
- name: 'Get sources'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
fetch-depth: 10
|
||||||
|
|
||||||
|
- name: 'Setup ccache'
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
|
- name: 'Build project'
|
||||||
|
run: |
|
||||||
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
|
bash deploy/build_macos.sh
|
||||||
|
|
||||||
|
- name: 'Upload installer artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_MacOS_old_installer
|
||||||
|
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: 'Upload unpacked artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_MacOS_old_unpacked
|
||||||
|
path: deploy/build/client/AmneziaVPN.app
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
Build-MacOS:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
QT_VERSION: 6.8.0
|
||||||
|
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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Setup xcode'
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '15.4.0'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
|
|
@ -248,11 +345,6 @@ jobs:
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
|
||||||
run: |
|
|
||||||
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
|
||||||
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
|
||||||
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
@ -266,14 +358,13 @@ jobs:
|
||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
run: |
|
run: |
|
||||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
|
|
||||||
bash deploy/build_macos.sh
|
bash deploy/build_macos.sh
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_MacOS_installer
|
name: AmneziaVPN_MacOS_installer
|
||||||
path: AmneziaVPN.dmg
|
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
|
|
@ -286,28 +377,35 @@ 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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_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'
|
||||||
|
|
@ -315,10 +413,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'
|
||||||
|
|
@ -326,10 +425,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'
|
||||||
|
|
@ -337,10 +437,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'
|
||||||
|
|
@ -348,7 +449,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
|
||||||
|
|
@ -432,3 +534,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
|
||||||
|
|
|
||||||
7
.github/workflows/tag-deploy.yml
vendored
7
.github/workflows/tag-deploy.yml
vendored
|
|
@ -15,6 +15,13 @@ 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 }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|
|
||||||
61
.github/workflows/tag-upload.yml
vendored
61
.github/workflows/tag-upload.yml
vendored
|
|
@ -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 }}
|
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -133,4 +133,8 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
|
||||||
out/
|
out/
|
||||||
|
|
||||||
# CMake files
|
# CMake files
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
|
|
||||||
|
ios-ne-build.sh
|
||||||
|
macos-ne-build.sh
|
||||||
|
macos-signed-build.sh
|
||||||
|
|
|
||||||
7
.gitmodules
vendored
7
.gitmodules
vendored
|
|
@ -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
|
||||||
|
|
@ -10,6 +7,10 @@
|
||||||
[submodule "client/3rd-prebuilt"]
|
[submodule "client/3rd-prebuilt"]
|
||||||
path = client/3rd-prebuilt
|
path = client/3rd-prebuilt
|
||||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||||
|
branch = feature/special-handshake
|
||||||
[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,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.6.0.3
|
project(${PROJECT} VERSION 4.8.8.1
|
||||||
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 55)
|
set(APP_ANDROID_VERSION_CODE 2087)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
||||||
77
README.md
77
README.md
|
|
@ -1,27 +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)
|
||||||
|
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
||||||
|
[](https://amnezia.org)
|
||||||
|
|
||||||
|
### [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)
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> 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)
|
||||||
|
|
||||||
|
<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://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||||
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
[https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||||
[https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||||
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
- [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://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
|
|
||||||
|
|
@ -46,6 +70,19 @@ git submodule update --init --recursive
|
||||||
|
|
||||||
Want to contribute? Welcome!
|
Want to contribute? Welcome!
|
||||||
|
|
||||||
|
### Help with translations
|
||||||
|
|
||||||
|
Download the most actual translation files.
|
||||||
|
|
||||||
|
Go to ["Actions" tab](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), click on the first line.
|
||||||
|
Then scroll down to the "Artifacts" section and download "AmneziaVPN_translations".
|
||||||
|
|
||||||
|
Unzip this file.
|
||||||
|
Each *.ts file contains strings for one corresponding language.
|
||||||
|
|
||||||
|
Translate or correct some strings in one or multiple *.ts files and commit them back to this repository into the ``client/translations`` folder.
|
||||||
|
You can do it via a web-interface or any other method you're familiar with.
|
||||||
|
|
||||||
### Building sources and deployment
|
### Building sources and deployment
|
||||||
|
|
||||||
Check deploy folder for build scripts.
|
Check deploy folder for build scripts.
|
||||||
|
|
@ -121,9 +158,11 @@ The Android app has the following requirements:
|
||||||
* Android platform SDK 33
|
* Android platform SDK 33
|
||||||
* CMake 3.25.0
|
* CMake 3.25.0
|
||||||
|
|
||||||
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly.
|
||||||
* set path to JDK 11
|
|
||||||
* set path to Android SDK ($ANDROID_HOME)
|
- Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||||
|
- Set path to JDK 11
|
||||||
|
- Set path to Android SDK (`$ANDROID_HOME`)
|
||||||
|
|
||||||
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
||||||
Double-check that the right CMake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake.
|
Double-check that the right CMake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake.
|
||||||
|
|
@ -145,10 +184,12 @@ GPL v3.0
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4
|
|
||||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d
|
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
|
||||||
|
|
||||||
|
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||||
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||||
|
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
This project is tested with BrowserStack.
|
This project is tested with BrowserStack.
|
||||||
|
|
|
||||||
181
README_RU.md
Normal file
181
README_RU.md
Normal file
|
|
@ -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 @@
|
||||||
Subproject commit ff8445c8aa1cda38497bb6f6cb0e520f5a3c8de0
|
Subproject commit 840b7b070e6ac8b90dda2fac6e98859b23727c0c
|
||||||
1
client/3rd/OpenVPNAdapter
vendored
1
client/3rd/OpenVPNAdapter
vendored
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b
|
|
||||||
1
client/3rd/QSimpleCrypto
vendored
Submodule
1
client/3rd/QSimpleCrypto
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c99b33f0e08b7206116ddff85c22d3b97ce1e79d
|
||||||
|
|
@ -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
|
|
||||||
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 76e7db556a6d7e2582f9481df91db188a46c009c
|
Subproject commit 811af0a83b3faeade89a9093a588595666d32066
|
||||||
2
client/3rd/qtkeychain
vendored
2
client/3rd/qtkeychain
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd
|
Subproject commit 7460df6a978669290de5b56c2d98b199b61c3f88
|
||||||
|
|
@ -24,9 +24,15 @@ 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}")
|
||||||
|
|
||||||
|
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||||
|
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_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 +40,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 +57,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 +91,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 +100,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 +169,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)
|
||||||
|
|
|
||||||
|
|
@ -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,104 +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();
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
if (m_settings->isSaveLogs()) {
|
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);
|
||||||
|
|
||||||
#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
|
||||||
|
|
@ -242,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);
|
||||||
|
|
@ -292,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">
|
||||||
|
|
@ -11,6 +10,9 @@
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
<!-- for TV -->
|
||||||
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
|
|
||||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
||||||
of the application. Remove the comment if you do not require these default features. -->
|
of the application. Remove the comment if you do not require these default features. -->
|
||||||
|
|
@ -18,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" />
|
||||||
|
|
@ -31,9 +33,11 @@
|
||||||
android:label="-- %%INSERT_APP_NAME%% --"
|
android:label="-- %%INSERT_APP_NAME%% --"
|
||||||
android:icon="@mipmap/icon"
|
android:icon="@mipmap/icon"
|
||||||
android:roundIcon="@mipmap/icon_round"
|
android:roundIcon="@mipmap/icon_round"
|
||||||
|
android:banner="@mipmap/ic_banner"
|
||||||
android:theme="@style/NoActionBar"
|
android:theme="@style/NoActionBar"
|
||||||
android:fullBackupContent="@xml/backup_content"
|
android:fullBackupContent="@xml/backup_content"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:hasFragileUserData="false"
|
||||||
tools:targetApi="s">
|
tools:targetApi="s">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
@ -41,12 +45,13 @@
|
||||||
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>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -62,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
|
||||||
|
|
@ -82,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" }
|
||||||
|
|
|
||||||
BIN
client/android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
client/android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
|
|
@ -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
|
||||||
|
|
|
||||||
7
client/android/gradlew
vendored
7
client/android/gradlew
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
22
client/android/gradlew.bat
vendored
22
client/android/gradlew.bat
vendored
|
|
@ -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"))))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
client/android/res/mipmap-hdpi/ic_banner.png
Normal file
BIN
client/android/res/mipmap-hdpi/ic_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
client/android/res/mipmap-mdpi/ic_banner.png
Normal file
BIN
client/android/res/mipmap-mdpi/ic_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
client/android/res/mipmap-xhdpi/ic_banner.png
Normal file
BIN
client/android/res/mipmap-xhdpi/ic_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 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>
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
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
|
||||||
|
|
@ -11,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
|
||||||
|
|
@ -19,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
|
||||||
|
|
@ -28,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
|
||||||
|
|
@ -42,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
|
||||||
|
|
@ -68,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>()
|
||||||
|
|
@ -156,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
|
||||||
|
|
@ -174,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(
|
||||||
|
|
@ -182,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)}"
|
||||||
)
|
)
|
||||||
|
|
@ -196,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,7 +258,10 @@ class AmneziaActivity : QtActivity() {
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
Log.d(TAG, "Stop Amnezia activity")
|
Log.d(TAG, "Stop Amnezia activity")
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
QtAndroidController.onServiceDisconnected()
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onServiceDisconnected()
|
||||||
|
}
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,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
|
||||||
|
|
@ -492,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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -515,49 +550,122 @@ 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)
|
||||||
onSuccess = {
|
}
|
||||||
val uri = it?.data?.toString() ?: ""
|
|
||||||
Log.d(TAG, "Open file: $uri")
|
try {
|
||||||
|
startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
|
onAny = {
|
||||||
|
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||||
|
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")
|
@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")
|
||||||
|
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun startQrCodeReader() {
|
fun startQrCodeReader() {
|
||||||
Log.v(TAG, "Start camera")
|
Log.v(TAG, "Start camera")
|
||||||
|
|
@ -602,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")
|
||||||
|
|
@ -676,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()
|
||||||
|
|
|
||||||
97
client/android/src/org/amnezia/vpn/AuthActivity.kt
Normal file
97
client/android/src/org/amnezia/vpn/AuthActivity.kt
Normal file
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
client/android/src/org/amnezia/vpn/TvFilePicker.kt
Normal file
45
client/android/src/org/amnezia/vpn/TvFilePicker.kt
Normal file
|
|
@ -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
|
||||||
}
|
}
|
||||||
9
client/android/utils/src/main/kotlin/JsonExt.kt
Normal file
9
client/android/utils/src/main/kotlin/JsonExt.kt
Normal file
|
|
@ -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 }
|
||||||
66
client/android/utils/src/main/kotlin/LibraryLoader.kt
Normal file
66
client/android/utils/src/main/kotlin/LibraryLoader.kt
Normal file
|
|
@ -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,21 +81,27 @@ 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 {
|
||||||
try {
|
val numberAttempts = 300
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
var attemptCount = 0
|
||||||
} catch (e: SecurityException) {
|
while(true) {
|
||||||
Log.e(TAG, "Failed to bind network listener: $e")
|
try {
|
||||||
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
|
||||||
if (e.message?.startsWith("Package android does not belong to") == true) {
|
|
||||||
delay(1000)
|
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||||
} else {
|
break
|
||||||
throw e
|
} catch (e: SecurityException) {
|
||||||
|
Log.e(TAG, "Failed to bind network listener: $e")
|
||||||
|
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
||||||
|
if (e.message?.startsWith("Package android does not belong to") == true) {
|
||||||
|
if (++attemptCount > numberAttempts) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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 {
|
||||||
|
|
|
||||||
|
|
@ -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,89 @@ 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("S3")?.let { setS3(it.toInt()) }
|
||||||
}
|
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
||||||
return parsedData
|
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||||
|
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||||
|
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||||
|
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||||
|
configData.optStringOrNull("I1")?.let { setI1(it) }
|
||||||
|
configData.optStringOrNull("I2")?.let { setI2(it) }
|
||||||
|
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||||
|
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||||
|
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||||
|
configData.optStringOrNull("J1")?.let { setJ1(it) }
|
||||||
|
configData.optStringOrNull("J2")?.let { setJ2(it) }
|
||||||
|
configData.optStringOrNull("J3")?.let { setJ3(it) }
|
||||||
|
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
|
|
@ -168,6 +163,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 +207,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)
|
||||||
|
|
|
||||||
|
|
@ -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,28 @@ 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 s3: Int?,
|
||||||
|
val s4: Int?,
|
||||||
|
val h1: Long?,
|
||||||
|
val h2: Long?,
|
||||||
|
val h3: Long?,
|
||||||
|
val h4: Long?,
|
||||||
|
var i1: String?,
|
||||||
|
var i2: String?,
|
||||||
|
var i3: String?,
|
||||||
|
var i4: String?,
|
||||||
|
var i5: String?,
|
||||||
|
var j1: String?,
|
||||||
|
var j2: String?,
|
||||||
|
var j3: String?,
|
||||||
|
var itime: Int?
|
||||||
) : ProtocolConfig(protocolConfigBuilder) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
|
|
@ -21,7 +43,28 @@ 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.s3,
|
||||||
|
builder.s4,
|
||||||
|
builder.h1,
|
||||||
|
builder.h2,
|
||||||
|
builder.h3,
|
||||||
|
builder.h4,
|
||||||
|
builder.i1,
|
||||||
|
builder.i2,
|
||||||
|
builder.i3,
|
||||||
|
builder.i4,
|
||||||
|
builder.i5,
|
||||||
|
builder.j1,
|
||||||
|
builder.j2,
|
||||||
|
builder.j3,
|
||||||
|
builder.itime
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||||
|
|
@ -33,6 +76,41 @@ 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")
|
||||||
|
s3?.let { appendLine("s3=$it") }
|
||||||
|
s4?.let { appendLine("s4=$it") }
|
||||||
|
appendLine("h1=$h1")
|
||||||
|
appendLine("h2=$h2")
|
||||||
|
appendLine("h3=$h3")
|
||||||
|
appendLine("h4=$h4")
|
||||||
|
i1?.let { appendLine("i1=$it") }
|
||||||
|
i2?.let { appendLine("i2=$it") }
|
||||||
|
i3?.let { appendLine("i3=$it") }
|
||||||
|
i4?.let { appendLine("i4=$it") }
|
||||||
|
i5?.let { appendLine("i5=$it") }
|
||||||
|
j1?.let { appendLine("j1=$it") }
|
||||||
|
j2?.let { appendLine("j2=$it") }
|
||||||
|
j3?.let { appendLine("j3=$it") }
|
||||||
|
itime?.let { appendLine("itime=$it") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +143,29 @@ 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 s3: Int? = null
|
||||||
|
internal var s4: Int? = null
|
||||||
|
internal var h1: Long? = null
|
||||||
|
internal var h2: Long? = null
|
||||||
|
internal var h3: Long? = null
|
||||||
|
internal var h4: Long? = null
|
||||||
|
internal var i1: String? = null
|
||||||
|
internal var i2: String? = null
|
||||||
|
internal var i3: String? = null
|
||||||
|
internal var i4: String? = null
|
||||||
|
internal var i5: String? = null
|
||||||
|
internal var j1: String? = null
|
||||||
|
internal var j2: String? = null
|
||||||
|
internal var j3: String? = null
|
||||||
|
internal var itime: Int? = 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 +176,29 @@ 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 setS3(s3: Int) = apply { this.s3 = s3 }
|
||||||
|
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||||
|
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 }
|
||||||
|
fun setI1(i1: String) = apply { this.i1 = i1 }
|
||||||
|
fun setI2(i2: String) = apply { this.i2 = i2 }
|
||||||
|
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||||
|
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||||
|
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||||
|
fun setJ1(j1: String) = apply { this.j1 = j1 }
|
||||||
|
fun setJ2(j2: String) = apply { this.j2 = j2 }
|
||||||
|
fun setJ3(j3: String) = apply { this.j3 = j3 }
|
||||||
|
fun setItime(itime: Int) = apply { this.itime = itime }
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
21
client/cmake/QSimpleCrypto.cmake
Normal file
21
client/cmake/QSimpleCrypto.cmake
Normal file
|
|
@ -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,12 +76,22 @@ 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_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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(DEFINED DEPLOY)
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set_target_properties(${PROJECT} PROPERTIES
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||||
|
|
@ -126,9 +136,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")
|
||||||
|
|
||||||
|
|
|
||||||
191
client/cmake/sources.cmake
Normal file
191
client/cmake/sources.cmake
Normal file
|
|
@ -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()
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "awg_configurator.h"
|
#include "awg_configurator.h"
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
@ -39,6 +40,20 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
|
||||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||||
|
|
||||||
|
// jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
|
||||||
|
// jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
|
||||||
|
|
||||||
|
// jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
|
||||||
|
// jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
|
||||||
|
// jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
|
||||||
|
// jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
|
||||||
|
// jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
|
||||||
|
// jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
|
||||||
|
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
|
||||||
|
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
|
||||||
|
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
|
||||||
|
|
||||||
jsonConfig[config_key::mtu] =
|
jsonConfig[config_key::mtu] =
|
||||||
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -117,20 +118,23 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
|
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||||
|
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||||
|
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||||
|
config.replace(dnsRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
// 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");
|
||||||
} 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) {
|
||||||
#ifndef Q_OS_ANDROID
|
#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");
|
||||||
#endif
|
|
||||||
// Prevent ipv6 leak
|
// 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,10 +167,15 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
|
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||||
|
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||||
|
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||||
|
config.replace(dnsRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||||
{ DockerContainer::Awg, "AmneziaWG" },
|
{ DockerContainer::Awg, "AmneziaWG" },
|
||||||
{ DockerContainer::Xray, "XRay" },
|
{ DockerContainer::Xray, "XRay" },
|
||||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||||
{ DockerContainer::SSXray, "ShadowSocks"},
|
{ DockerContainer::SSXray, "Shadowsocks"},
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
||||||
|
|
@ -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.") },
|
||||||
|
|
@ -143,100 +140,83 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{ DockerContainer::OpenVpn,
|
{ DockerContainer::OpenVpn,
|
||||||
QObject::tr(
|
QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
|
||||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
"It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
|
||||||
"It employs its unique security protocol, "
|
"and is continuously improved by the community due to its open-source nature. "
|
||||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
"It provides a good balance between speed and security but is easily recognized by DPI systems, "
|
||||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
"making it susceptible to blocking.\n"
|
||||||
"catering to a wide range of devices and operating systems. "
|
"\nFeatures:\n"
|
||||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"which continually reinforces its security. "
|
"* Normal battery consumption on mobile devices\n"
|
||||||
"With a strong balance of performance, security, and compatibility, "
|
"* Flexible customization for various devices and OS\n"
|
||||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
"* Operates over both TCP and UDP protocols") },
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
|
||||||
"* Normal power consumption on mobile 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"
|
|
||||||
"* 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 is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
|
||||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
"Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
|
||||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
"Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
|
||||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
"\nFeatures:\n"
|
||||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
"* Available in AmneziaVPN only on desktop platforms\n"
|
||||||
"* Configurable encryption protocol\n"
|
"* Customizable encryption protocol\n"
|
||||||
"* Detectable by some DPI systems\n"
|
"* Detectable by some DPI systems\n"
|
||||||
"* Works over TCP network protocol.") },
|
"* Operates over TCP protocol\n") },
|
||||||
{ DockerContainer::Cloak,
|
{ DockerContainer::Cloak,
|
||||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
|
||||||
"protecting against blocking.\n\n"
|
"\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
|
||||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
"\nThe Cloak plugin further protects the connection from DPI detection. "
|
||||||
"and the server.\n\n"
|
"It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
|
||||||
"Cloak protects OpenVPN from detection and blocking. \n\n"
|
"If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
|
||||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
"\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
|
||||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
"\nFeatures:\n"
|
||||||
"being detected\n\n"
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"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 "
|
|
||||||
"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"
|
|
||||||
"* High power consumption on mobile devices\n"
|
"* High power consumption on mobile devices\n"
|
||||||
"* Flexible settings\n"
|
"* Flexible configuration options\n"
|
||||||
"* Not recognised by DPI analysis systems\n"
|
"* Undetectable by DPI systems\n"
|
||||||
"* Works over TCP network protocol, 443 port.\n") },
|
"* Operates over TCP protocol on port 443") },
|
||||||
{ DockerContainer::WireGuard,
|
{ DockerContainer::WireGuard,
|
||||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
|
||||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
"It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
|
||||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
"However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
|
||||||
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
|
"\nFeatures:\n"
|
||||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
"* Low power consumption on mobile devices\n"
|
||||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
"* Minimal configuration required\n"
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||||
"* Low power consumption\n"
|
"* Operates over UDP protocol") },
|
||||||
"* Minimum number of settings\n"
|
|
||||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
|
||||||
"* Works over UDP network protocol.") },
|
|
||||||
{ DockerContainer::Awg,
|
{ DockerContainer::Awg,
|
||||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
||||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
"combining simplified architecture with high performance across all devices. "
|
||||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
||||||
"While WireGuard is known for its efficiency, "
|
"making VPN traffic indistinguishable from regular internet traffic.\n"
|
||||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
"\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
|
||||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
"\nFeatures:\n"
|
||||||
"making its traffic blend in with regular internet traffic.\n"
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"This means that AmneziaWG keeps the fast performance of the original "
|
"* Low battery consumption on mobile devices\n"
|
||||||
"while adding an extra layer of stealth, "
|
"* Minimal settings required\n"
|
||||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
"* Undetectable by traffic analysis systems (DPI)\n"
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Operates over UDP protocol") },
|
||||||
"* Low power consumption\n"
|
|
||||||
"* Minimum number of settings\n"
|
|
||||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
|
||||||
"* Works over UDP network protocol.") },
|
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
|
||||||
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
"REALITY identifies censorship systems during the TLS handshake, "
|
||||||
"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, "
|
"redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
|
||||||
"thus presenting an authentic TLS certificate and data. \n"
|
"This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
|
||||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
"\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
|
||||||
"legitimate sites without the need for specific configurations. \n"
|
"effectively protecting against DPI and other traffic analysis methods.\n"
|
||||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
"\nFeatures:\n"
|
||||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
"* Resistant to active probing and DPI detection\n"
|
||||||
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
"* No special configuration required to disguise traffic\n"
|
||||||
},
|
"* Highly effective in heavily censored regions\n"
|
||||||
|
"* Minimal battery consumption on devices\n"
|
||||||
|
"* Operates over TCP protocol") },
|
||||||
{ 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, combined with IPSec encryption, is a modern and reliable VPN protocol. "
|
||||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
"It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
|
||||||
"making it particularly adaptive in dynamic network environments. \n"
|
"While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
|
||||||
"While it offers a blend of security, stability, and speed, "
|
"\nFeatures:\n"
|
||||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
"* Available in AmneziaVPN only on Windows\n"
|
||||||
"* Available in the AmneziaVPN only on Windows\n"
|
"* Low battery consumption on mobile devices\n"
|
||||||
"* Low power consumption, on mobile devices\n"
|
"* Minimal configuration required\n"
|
||||||
"* Minimal configuration\n"
|
"* Detectable by DPI analysis systems(easily blocked)\n"
|
||||||
"* Recognised by DPI analysis systems\n"
|
"* Operates over UDP protocol(ports 500 and 4500)") },
|
||||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||||
|
|
@ -286,8 +266,8 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||||
case DockerContainer::OpenVpn: return true;
|
case DockerContainer::OpenVpn: return true;
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Xray: return true;
|
case DockerContainer::Xray: return true;
|
||||||
case DockerContainer::Cloak:
|
case DockerContainer::Cloak: return true;
|
||||||
return true;
|
case DockerContainer::SSXray: return true;
|
||||||
// case DockerContainer::ShadowSocks: return true;
|
// case DockerContainer::ShadowSocks: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
|
|
@ -332,9 +312,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 +320,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 +328,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 +337,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 +356,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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
client/core/api/apiDefs.h
Normal file
72
client/core/api/apiDefs.h
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#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 apiEndpoint("api_endpoint");
|
||||||
|
constexpr QLatin1String apiKey("api_key");
|
||||||
|
constexpr QLatin1String description("description");
|
||||||
|
constexpr QLatin1String name("name");
|
||||||
|
constexpr QLatin1String protocol("protocol");
|
||||||
|
|
||||||
|
constexpr QLatin1String apiConfig("api_config");
|
||||||
|
constexpr QLatin1String stackType("stack_type");
|
||||||
|
constexpr QLatin1String serviceType("service_type");
|
||||||
|
constexpr QLatin1String cliVersion("cli_version");
|
||||||
|
constexpr QLatin1String supportedProtocols("supported_protocols");
|
||||||
|
|
||||||
|
constexpr QLatin1String vpnKey("vpn_key");
|
||||||
|
constexpr QLatin1String config("config");
|
||||||
|
constexpr QLatin1String configs("configs");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
constexpr QLatin1String id("id");
|
||||||
|
constexpr QLatin1String orderId("order_id");
|
||||||
|
constexpr QLatin1String migrationCode("migration_code");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APIDEFS_H
|
||||||
164
client/core/api/apiUtils.cpp
Normal file
164
client/core/api/apiUtils.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
#include "apiUtils.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
||||||
|
|
||||||
|
QString escapeUnicode(const QString &input)
|
||||||
|
{
|
||||||
|
QString output;
|
||||||
|
for (QChar c : input) {
|
||||||
|
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
|
||||||
|
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
|
||||||
|
} else {
|
||||||
|
output += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {
|
||||||
|
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||||
|
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||||
|
|
||||||
|
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||||
|
|
||||||
|
if (apiEndpoint.contains(premiumV1Endpoint)) {
|
||||||
|
return apiDefs::ConfigType::AmneziaPremiumV1;
|
||||||
|
} else if (apiEndpoint.contains(freeV2Endpoint)) {
|
||||||
|
return apiDefs::ConfigType::AmneziaFreeV2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
|
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 if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QPair<QString, QVariant>> orderedFields;
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
|
||||||
|
|
||||||
|
QString vpnKeyStr = "{";
|
||||||
|
for (int i = 0; i < orderedFields.size(); ++i) {
|
||||||
|
const auto &pair = orderedFields[i];
|
||||||
|
if (pair.second.typeId() == QMetaType::Type::QString) {
|
||||||
|
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
|
||||||
|
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
|
||||||
|
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < orderedFields.size() - 1) {
|
||||||
|
vpnKeyStr += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vpnKeyStr += "}";
|
||||||
|
|
||||||
|
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||||
|
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||||
|
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||||
|
|
||||||
|
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||||
|
|
||||||
|
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||||
|
}
|
||||||
26
client/core/api/apiUtils.h
Normal file
26
client/core/api/apiUtils.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#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);
|
||||||
|
|
||||||
|
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
|
||||||
399
client/core/controllers/coreController.cpp
Normal file
399
client/core/controllers/coreController.cpp
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
#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());
|
||||||
|
|
||||||
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.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();
|
||||||
|
initImportPremiumV2VpnKeyHandler();
|
||||||
|
initShowMigrationDrawerHandler();
|
||||||
|
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::initImportPremiumV2VpnKeyHandler()
|
||||||
|
{
|
||||||
|
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
|
||||||
|
m_importController->extractConfigFromData(vpnKey);
|
||||||
|
m_importController->importConfig();
|
||||||
|
|
||||||
|
emit m_apiPremV1MigrationController->migrationFinished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initShowMigrationDrawerHandler()
|
||||||
|
{
|
||||||
|
QTimer::singleShot(1000, this, [this]() {
|
||||||
|
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
|
||||||
|
m_apiPremV1MigrationController->showMigrationDrawer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initStrictKillSwitchHandler()
|
||||||
|
{
|
||||||
|
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
||||||
|
&VpnConnection::onKillSwitchModeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<PageController> CoreController::pageController() const
|
||||||
|
{
|
||||||
|
return m_pageController;
|
||||||
|
}
|
||||||
145
client/core/controllers/coreController.h
Normal file
145
client/core/controllers/coreController.h
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#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/api/apiPremV1MigrationController.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 initImportPremiumV2VpnKeyHandler();
|
||||||
|
void initShowMigrationDrawerHandler();
|
||||||
|
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;
|
||||||
|
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||||
|
|
||||||
|
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
|
||||||
364
client/core/controllers/gatewayController.cpp
Normal file
364
client/core/controllers/gatewayController.cpp
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
#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 "core/networkUtilities.h"
|
||||||
|
#include "utilities.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.");
|
||||||
|
|
||||||
|
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
|
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_gatewayEndpoint(gatewayEndpoint),
|
||||||
|
m_isDevEnvironment(isDevEnvironment),
|
||||||
|
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||||
|
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
|
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
|
||||||
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
|
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::OperationNotImplementedError) {
|
||||||
|
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
client/core/controllers/gatewayController.h
Normal file
37
client/core/controllers/gatewayController.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#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, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
|
const bool isStrictKillSwitchEnabled, 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;
|
||||||
|
bool m_isStrictKillSwitchEnabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GATEWAYCONTROLLER_H
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue