Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bdb7795334 | |||
| 758b25947c | |||
| b036c38981 | |||
| eab2b8e45a | |||
| dfdec2bf4b | |||
| 843156cf1b | |||
| 01413e5a4c | |||
| 743304c359 | |||
| 6c5d590169 | |||
| a1e68f5506 | |||
| 424315b17f | |||
| 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 | |||
| 3f90ee915d | |||
| 401ad0db0e | |||
| 5945133d30 | |||
| ff4fbde0b0 | |||
| 74ae4f3e67 | |||
| ae4b33d042 | |||
| 53fa280037 | |||
| 8ecde90bc7 | |||
| 34a583f272 | |||
| 5de4b8eeb8 | |||
| aae420e469 | |||
| 0612f70c06 | |||
| d0c82efa1c | |||
| cf8492240e | |||
| 2bceb9f7ba | |||
| 760f935965 | |||
| eeeb2805c5 | |||
| 9a592d67ad | |||
| ea6618b2f6 | |||
| 7b092e73ad | |||
| b2e25c42c7 | |||
| c8dd38ac31 | |||
| 563ee4703f | |||
| beceed81de | |||
| 3bf96253db | |||
| da2d0ec203 | |||
| 008b858203 | |||
| 130fc8277d | |||
| 468d3357b8 | |||
| f1271da527 | |||
| 249a7c7ca3 | |||
| 0094d0ebc4 | |||
| 834b504dff | |||
| a516d0e757 | |||
| afdfbdbc59 |
@@ -10,12 +10,12 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-Linux-Ubuntu:
|
Build-Linux-Ubuntu:
|
||||||
name: 'Build-Linux-Ubuntu'
|
|
||||||
runs-on: ubuntu-20.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 }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
@@ -65,16 +65,23 @@ 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 }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
@@ -130,13 +137,13 @@ 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 }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
@@ -221,13 +228,13 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-MacOS:
|
Build-MacOS:
|
||||||
name: 'Build-MacOS'
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||||
QT_VERSION: 6.4.3
|
QT_VERSION: 6.4.3
|
||||||
QIF_VERSION: 4.6
|
QIF_VERSION: 4.6
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
@@ -286,13 +293,13 @@ 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.6.2
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
@@ -432,3 +439,21 @@ jobs:
|
|||||||
path: deploy/build/AmneziaVPN-release.aab
|
path: deploy/build/AmneziaVPN-release.aab
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
Extra:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Search a corresponding PR
|
||||||
|
uses: octokit/request-action@v2.x
|
||||||
|
id: pull_request
|
||||||
|
with:
|
||||||
|
route: GET /repos/${{ github.repository }}/pulls
|
||||||
|
head: ${{ github.repository_owner }}:${{ github.ref_name }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Add PR link to build summary
|
||||||
|
if: ${{ fromJSON(steps.pull_request.outputs.data)[0].number != '' }}
|
||||||
|
run: |
|
||||||
|
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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 }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|||||||
@@ -13,3 +13,6 @@
|
|||||||
[submodule "client/3rd/amneziawg-apple"]
|
[submodule "client/3rd/amneziawg-apple"]
|
||||||
path = client/3rd/amneziawg-apple
|
path = client/3rd/amneziawg-apple
|
||||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||||
|
[submodule "client/3rd/QSimpleCrypto"]
|
||||||
|
path = client/3rd/QSimpleCrypto
|
||||||
|
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.5.3.0
|
project(${PROJECT} VERSION 4.7.0.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 52)
|
set(APP_ANDROID_VERSION_CODE 57)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -6,20 +6,44 @@
|
|||||||
|
|
||||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_Linux_4.6.0.3.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.6.0.3"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
|
||||||
|
|
||||||
|
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
## 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
|
||||||
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||||
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||||
|
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||||
|
- [https://t.me/amnezia_vpn](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
|
||||||
|
|
||||||
@@ -27,7 +51,7 @@ AmneziaVPN uses several open-source projects to work:
|
|||||||
|
|
||||||
- [OpenSSL](https://www.openssl.org/)
|
- [OpenSSL](https://www.openssl.org/)
|
||||||
- [OpenVPN](https://openvpn.net/)
|
- [OpenVPN](https://openvpn.net/)
|
||||||
- [ShadowSocks](https://shadowsocks.org/)
|
- [Shadowsocks](https://shadowsocks.org/)
|
||||||
- [Qt](https://www.qt.io/)
|
- [Qt](https://www.qt.io/)
|
||||||
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
||||||
- and more...
|
- and more...
|
||||||
@@ -44,6 +68,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.
|
||||||
@@ -52,7 +89,7 @@ Check deploy folder for build scripts.
|
|||||||
|
|
||||||
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
|
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
|
||||||
|
|
||||||
2. We use QT to generate the XCode project. We need QT version 6.6.1. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
2. We use QT to generate the XCode project. We need QT version 6.6.2. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
||||||
- MacOS
|
- MacOS
|
||||||
- iOS
|
- iOS
|
||||||
- Qt 5 Compatibility Module
|
- Qt 5 Compatibility Module
|
||||||
@@ -119,9 +156,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.
|
||||||
@@ -142,10 +181,12 @@ GPL v3.0
|
|||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
|
|
||||||
|
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||||
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||||
payeer.com: P2561305
|
|
||||||
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,9 @@ execute_process(
|
|||||||
|
|
||||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||||
|
|
||||||
|
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||||
|
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
|
||||||
|
|
||||||
if(IOS)
|
if(IOS)
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
set(PACKAGES ${PACKAGES} Multimedia)
|
||||||
endif()
|
endif()
|
||||||
@@ -34,7 +37,7 @@ 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
|
||||||
@@ -136,6 +139,7 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -252,7 +256,7 @@ set(SOURCES ${SOURCES}
|
|||||||
|
|
||||||
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
|
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#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/models/installedAppsModel.h"
|
#include "ui/models/installedAppsModel.h"
|
||||||
@@ -116,7 +116,7 @@ void AmneziaApplication::init()
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
||||||
m_pageController->replaceStartPage();
|
m_pageController->goToPageHome();
|
||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
@@ -127,13 +127,13 @@ void AmneziaApplication::init()
|
|||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->initialize();
|
IosController::Instance()->initialize();
|
||||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
||||||
m_pageController->replaceStartPage();
|
m_pageController->goToPageHome();
|
||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
||||||
m_pageController->replaceStartPage();
|
m_pageController->goToPageHome();
|
||||||
m_pageController->goToPageSettingsBackup();
|
m_pageController->goToPageSettingsBackup();
|
||||||
m_settingsController->importBackupFromOutside(filePath);
|
m_settingsController->importBackupFromOutside(filePath);
|
||||||
});
|
});
|
||||||
@@ -157,16 +157,19 @@ void AmneziaApplication::init()
|
|||||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||||
m_engine->load(url);
|
m_engine->load(url);
|
||||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||||
|
|
||||||
|
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()) {
|
||||||
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
|
||||||
if (m_parser.isSet("a"))
|
if (m_parser.isSet("a"))
|
||||||
@@ -358,6 +361,18 @@ void AmneziaApplication::initModels()
|
|||||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
&ServersModel::clearCachedProfile);
|
&ServersModel::clearCachedProfile);
|
||||||
|
|
||||||
|
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());
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, 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 AmneziaApplication::initControllers()
|
void AmneziaApplication::initControllers()
|
||||||
@@ -366,25 +381,26 @@ void AmneziaApplication::initControllers()
|
|||||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this, [this](const QString &errorMessage) {
|
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
|
||||||
emit m_pageController->showErrorMessage(errorMessage);
|
[this](const QString &errorMessage) {
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_pageController->showErrorMessage(errorMessage);
|
||||||
});
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this, [this](ErrorCode errorCode) {
|
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
|
||||||
emit m_pageController->showErrorMessage(errorCode);
|
[this](ErrorCode errorCode) {
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_pageController->showErrorMessage(errorCode);
|
||||||
});
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
|
||||||
|
|
||||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||||
|
|
||||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
|
||||||
|
m_apiServicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||||
&PageController::showPassphraseRequestDrawer);
|
&PageController::showPassphraseRequestDrawer);
|
||||||
@@ -393,6 +409,30 @@ void AmneziaApplication::initControllers()
|
|||||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||||
&ConnectionController::onCurrentContainerUpdated);
|
&ConnectionController::onCurrentContainerUpdated);
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
||||||
|
disconnect(m_reloadConfigErrorOccurredConnection);
|
||||||
|
emit m_connectionController->configFromApiUpdated();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
|
||||||
|
m_reloadConfigErrorOccurredConnection = connect(
|
||||||
|
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||||
|
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||||
|
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
|
||||||
|
m_reloadConfigErrorOccurredConnection = connect(
|
||||||
|
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||||
|
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||||
|
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
||||||
|
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||||
|
|
||||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@
|
|||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
|
#include "ui/models/apiServicesModel.h"
|
||||||
|
#include "ui/models/apiCountryModel.h"
|
||||||
|
|
||||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||||
|
|
||||||
@@ -103,6 +105,8 @@ private:
|
|||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
@@ -134,6 +138,8 @@ private:
|
|||||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
|
||||||
QNetworkAccessManager *m_nam;
|
QNetworkAccessManager *m_nam;
|
||||||
|
|
||||||
|
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|||||||
@@ -11,6 +11,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. -->
|
||||||
@@ -31,9 +34,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
|
||||||
@@ -47,6 +52,7 @@
|
|||||||
<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>
|
||||||
@@ -136,8 +142,34 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".AmneziaVpnService"
|
android:name=".AwgService"
|
||||||
android:process=":amneziaVpnService"
|
android:process=":amneziaAwgService"
|
||||||
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
|
android:foregroundServiceType="systemExempted"
|
||||||
|
android:exported="false"
|
||||||
|
tools:ignore="ForegroundServicePermission">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.VpnService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".OpenVpnService"
|
||||||
|
android:process=":amneziaOpenVpnService"
|
||||||
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
|
android:foregroundServiceType="systemExempted"
|
||||||
|
android:exported="false"
|
||||||
|
tools:ignore="ForegroundServicePermission">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.VpnService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".XrayService"
|
||||||
|
android:process=":amneziaXrayService"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:foregroundServiceType="systemExempted"
|
android:foregroundServiceType="systemExempted"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
id("property-delegate")
|
id("property-delegate")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +69,12 @@ android {
|
|||||||
}
|
}
|
||||||
signingConfig = signingConfigs["release"]
|
signingConfig = signingConfigs["release"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create("fdroid") {
|
||||||
|
initWith(getByName("release"))
|
||||||
|
signingConfig = null
|
||||||
|
matchingFallbacks += "release"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
@@ -98,7 +105,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
|
|
||||||
implementation(project(":qt"))
|
implementation(project(":qt"))
|
||||||
implementation(project(":utils"))
|
implementation(project(":utils"))
|
||||||
implementation(project(":protocolApi"))
|
implementation(project(":protocolApi"))
|
||||||
@@ -106,9 +112,11 @@ dependencies {
|
|||||||
implementation(project(":awg"))
|
implementation(project(":awg"))
|
||||||
implementation(project(":openvpn"))
|
implementation(project(":openvpn"))
|
||||||
implementation(project(":cloak"))
|
implementation(project(":cloak"))
|
||||||
|
implementation(project(":xray"))
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
implementation(libs.kotlinx.coroutines)
|
implementation(libs.kotlinx.coroutines)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ androidx-camera = "1.3.0"
|
|||||||
androidx-security-crypto = "1.1.0-alpha06"
|
androidx-security-crypto = "1.1.0-alpha06"
|
||||||
androidx-datastore = "1.1.0-beta01"
|
androidx-datastore = "1.1.0-beta01"
|
||||||
kotlinx-coroutines = "1.7.3"
|
kotlinx-coroutines = "1.7.3"
|
||||||
|
kotlinx-serialization = "1.6.3"
|
||||||
google-mlkit = "17.2.0"
|
google-mlkit = "17.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -21,6 +22,7 @@ androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "
|
|||||||
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" }
|
||||||
|
kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
|
||||||
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
@@ -35,3 +37,4 @@ androidx-camera = [
|
|||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
package org.amnezia.vpn.protocol.openvpn
|
package org.amnezia.vpn.protocol.openvpn
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.openvpn.ovpn3.ClientAPI_Config
|
import net.openvpn.ovpn3.ClientAPI_Config
|
||||||
import org.amnezia.vpn.protocol.BadConfigException
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.Protocol
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
import org.amnezia.vpn.protocol.ProtocolState
|
|
||||||
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
|
||||||
@@ -37,7 +35,6 @@ import org.json.JSONObject
|
|||||||
|
|
||||||
open class OpenVpn : Protocol() {
|
open class OpenVpn : Protocol() {
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private var openVpnClient: OpenVpnClient? = null
|
private var openVpnClient: OpenVpnClient? = null
|
||||||
private lateinit var scope: CoroutineScope
|
private lateinit var scope: CoroutineScope
|
||||||
|
|
||||||
@@ -53,10 +50,11 @@ open class OpenVpn : Protocol() {
|
|||||||
return Statistics.EMPTY_STATISTICS
|
return Statistics.EMPTY_STATISTICS
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
override fun internalInit() {
|
||||||
super.initialize(context, state, onError)
|
if (!isInitialized) loadSharedLibrary(context, "ovpn3")
|
||||||
loadSharedLibrary(context, "ovpn3")
|
if (this::scope.isInitialized) {
|
||||||
this.context = context
|
scope.cancel()
|
||||||
|
}
|
||||||
scope = CoroutineScope(Dispatchers.IO)
|
scope = CoroutineScope(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,21 @@ private const val SPLIT_TUNNEL_EXCLUDE = 2
|
|||||||
abstract class Protocol {
|
abstract class Protocol {
|
||||||
|
|
||||||
abstract val statistics: Statistics
|
abstract val statistics: Statistics
|
||||||
|
protected lateinit var context: Context
|
||||||
protected lateinit var state: MutableStateFlow<ProtocolState>
|
protected lateinit var state: MutableStateFlow<ProtocolState>
|
||||||
protected lateinit var onError: (String) -> Unit
|
protected lateinit var onError: (String) -> Unit
|
||||||
|
protected var isInitialized: Boolean = false
|
||||||
|
|
||||||
open fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||||
|
this.context = context
|
||||||
this.state = state
|
this.state = state
|
||||||
this.onError = onError
|
this.onError = onError
|
||||||
|
internalInit()
|
||||||
|
isInitialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract fun internalInit()
|
||||||
|
|
||||||
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||||
|
|
||||||
abstract fun stopVpn()
|
abstract fun stopVpn()
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar", "*.aar"))))
|
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_banner_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_banner_background">#1E1E1F</color>
|
||||||
|
</resources>
|
||||||
@@ -36,6 +36,8 @@ include(":wireguard")
|
|||||||
include(":awg")
|
include(":awg")
|
||||||
include(":openvpn")
|
include(":openvpn")
|
||||||
include(":cloak")
|
include(":cloak")
|
||||||
|
include(":xray")
|
||||||
|
include(":xray:libXray")
|
||||||
|
|
||||||
// get values from gradle or local properties
|
// get values from gradle or local properties
|
||||||
val androidBuildToolsVersion: String by gradleProperties
|
val androidBuildToolsVersion: String by gradleProperties
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
@@ -34,6 +35,7 @@ import kotlinx.coroutines.CompletableDeferred
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -43,6 +45,8 @@ import org.amnezia.vpn.protocol.getStatus
|
|||||||
import org.amnezia.vpn.qt.QtAndroidController
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
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.JSONObject
|
||||||
import org.qtproject.qt.android.bindings.QtActivity
|
import org.qtproject.qt.android.bindings.QtActivity
|
||||||
|
|
||||||
private const val TAG = "AmneziaActivity"
|
private const val TAG = "AmneziaActivity"
|
||||||
@@ -59,6 +63,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
private lateinit var mainScope: CoroutineScope
|
private lateinit var mainScope: CoroutineScope
|
||||||
private val qtInitialized = CompletableDeferred<Unit>()
|
private val qtInitialized = CompletableDeferred<Unit>()
|
||||||
|
private var vpnProto: VpnProto? = null
|
||||||
private var isWaitingStatus = true
|
private var isWaitingStatus = true
|
||||||
private var isServiceConnected = false
|
private var isServiceConnected = false
|
||||||
private var isInBoundState = false
|
private var isInBoundState = false
|
||||||
@@ -141,6 +146,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
override fun onBindingDied(name: ComponentName?) {
|
override fun onBindingDied(name: ComponentName?) {
|
||||||
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
|
QtAndroidController.onServiceDisconnected()
|
||||||
doBindService()
|
doBindService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,15 +159,20 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
|
VpnStateStore.getVpnState().vpnProto
|
||||||
|
}
|
||||||
vpnServiceMessenger = IpcMessenger(
|
vpnServiceMessenger = IpcMessenger(
|
||||||
"VpnService",
|
"VpnService",
|
||||||
onDeadObjectException = {
|
onDeadObjectException = {
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
|
QtAndroidController.onServiceDisconnected()
|
||||||
doBindService()
|
doBindService()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
registerBroadcastReceivers()
|
registerBroadcastReceivers()
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
|
runBlocking { vpnProto = proto.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerBroadcastReceivers() {
|
private fun registerBroadcastReceivers() {
|
||||||
@@ -209,13 +220,21 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Log.d(TAG, "Start Amnezia activity")
|
Log.d(TAG, "Start Amnezia activity")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
qtInitialized.await()
|
qtInitialized.await()
|
||||||
doBindService()
|
vpnProto?.let { proto ->
|
||||||
|
if (AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||||
|
doBindService()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
Log.d(TAG, "Stop Amnezia activity")
|
Log.d(TAG, "Stop Amnezia activity")
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onServiceDisconnected()
|
||||||
|
}
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,10 +288,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@MainThread
|
@MainThread
|
||||||
private fun doBindService() {
|
private fun doBindService() {
|
||||||
Log.d(TAG, "Bind service")
|
Log.d(TAG, "Bind service")
|
||||||
Intent(this, AmneziaVpnService::class.java).also {
|
vpnProto?.let { proto ->
|
||||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
Intent(this, proto.serviceClass).also {
|
||||||
|
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
isInBoundState = true
|
||||||
}
|
}
|
||||||
isInBoundState = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
@@ -280,7 +301,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
if (isInBoundState) {
|
if (isInBoundState) {
|
||||||
Log.d(TAG, "Unbind service")
|
Log.d(TAG, "Unbind service")
|
||||||
isWaitingStatus = true
|
isWaitingStatus = true
|
||||||
QtAndroidController.onServiceDisconnected()
|
|
||||||
isServiceConnected = false
|
isServiceConnected = false
|
||||||
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
||||||
vpnServiceMessenger.reset()
|
vpnServiceMessenger.reset()
|
||||||
@@ -365,13 +385,32 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun startVpn(vpnConfig: String) {
|
private fun startVpn(vpnConfig: String) {
|
||||||
if (isServiceConnected) {
|
getVpnProto(vpnConfig)?.let { proto ->
|
||||||
connectToVpn(vpnConfig)
|
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||||
} else {
|
if (isServiceConnected) {
|
||||||
|
if (proto.serviceClass == vpnProto?.serviceClass) {
|
||||||
|
vpnProto = proto
|
||||||
|
connectToVpn(vpnConfig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doUnbindService()
|
||||||
|
}
|
||||||
|
vpnProto = proto
|
||||||
isWaitingStatus = false
|
isWaitingStatus = false
|
||||||
startVpnService(vpnConfig)
|
startVpnService(vpnConfig, proto)
|
||||||
doBindService()
|
doBindService()
|
||||||
}
|
} ?: QtAndroidController.onServiceError()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVpnProto(vpnConfig: String): VpnProto? = try {
|
||||||
|
require(vpnConfig.isNotBlank()) { "Blank VPN config" }
|
||||||
|
VpnProto.get(JSONObject(vpnConfig).getString("protocol"))
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
Log.e(TAG, "Invalid VPN config json format: ${e.message}")
|
||||||
|
null
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Log.e(TAG, "Protocol not found: ${e.message}")
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connectToVpn(vpnConfig: String) {
|
private fun connectToVpn(vpnConfig: String) {
|
||||||
@@ -383,15 +422,15 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startVpnService(vpnConfig: String) {
|
private fun startVpnService(vpnConfig: String, proto: VpnProto) {
|
||||||
Log.d(TAG, "Start VPN service")
|
Log.d(TAG, "Start VPN service: $proto")
|
||||||
Intent(this, AmneziaVpnService::class.java).apply {
|
Intent(this, proto.serviceClass).apply {
|
||||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||||
}.also {
|
}.also {
|
||||||
try {
|
try {
|
||||||
ContextCompat.startForegroundService(this, it)
|
ContextCompat.startForegroundService(this, it)
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||||
QtAndroidController.onServiceError()
|
QtAndroidController.onServiceError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,7 +546,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
onSuccess = {
|
onAny = {
|
||||||
val uri = it?.data?.toString() ?: ""
|
val uri = it?.data?.toString() ?: ""
|
||||||
Log.d(TAG, "Open file: $uri")
|
Log.d(TAG, "Open file: $uri")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
@@ -521,8 +560,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@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")
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ class AmneziaTileService : TileService() {
|
|||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var isServiceConnected = false
|
private var isServiceConnected = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var vpnProto: VpnProto? = null
|
||||||
private var isInBoundState = false
|
private var isInBoundState = false
|
||||||
@Volatile
|
@Volatile
|
||||||
private var isVpnConfigExists = false
|
private var isVpnConfigExists = false
|
||||||
@@ -94,16 +97,21 @@ class AmneziaTileService : TileService() {
|
|||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
Log.d(TAG, "Start listening")
|
scope.launch {
|
||||||
if (AmneziaVpnService.isRunning(applicationContext)) {
|
Log.d(TAG, "Start listening")
|
||||||
Log.d(TAG, "Vpn service is running")
|
vpnProto = VpnStateStore.getVpnState().vpnProto
|
||||||
doBindService()
|
vpnProto.also { proto ->
|
||||||
} else {
|
if (proto != null && AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||||
Log.d(TAG, "Vpn service is not running")
|
Log.d(TAG, "Vpn service is running")
|
||||||
isServiceConnected = false
|
doBindService()
|
||||||
updateVpnState(DISCONNECTED)
|
} else {
|
||||||
|
Log.d(TAG, "Vpn service is not running")
|
||||||
|
isServiceConnected = false
|
||||||
|
updateVpnState(DISCONNECTED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vpnStateListeningJob = launchVpnStateListening()
|
||||||
}
|
}
|
||||||
vpnStateListeningJob = launchVpnStateListening()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
@@ -124,7 +132,7 @@ class AmneziaTileService : TileService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onClickInternal() {
|
private fun onClickInternal() {
|
||||||
if (isVpnConfigExists) {
|
if (isVpnConfigExists && vpnProto != null) {
|
||||||
Log.d(TAG, "Change VPN state")
|
Log.d(TAG, "Change VPN state")
|
||||||
if (qsTile.state == Tile.STATE_INACTIVE) {
|
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||||
Log.d(TAG, "Start VPN")
|
Log.d(TAG, "Start VPN")
|
||||||
@@ -147,10 +155,12 @@ class AmneziaTileService : TileService() {
|
|||||||
|
|
||||||
private fun doBindService() {
|
private fun doBindService() {
|
||||||
Log.d(TAG, "Bind service")
|
Log.d(TAG, "Bind service")
|
||||||
Intent(this, AmneziaVpnService::class.java).also {
|
vpnProto?.let { proto ->
|
||||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
Intent(this, proto.serviceClass).also {
|
||||||
|
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||||
|
}
|
||||||
|
isInBoundState = true
|
||||||
}
|
}
|
||||||
isInBoundState = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doUnbindService() {
|
private fun doUnbindService() {
|
||||||
@@ -180,6 +190,7 @@ class AmneziaTileService : TileService() {
|
|||||||
if (VpnService.prepare(applicationContext) != null) {
|
if (VpnService.prepare(applicationContext) != null) {
|
||||||
Intent(this, VpnRequestActivity::class.java).apply {
|
Intent(this, VpnRequestActivity::class.java).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
putExtra(EXTRA_PROTOCOL, vpnProto)
|
||||||
}.also {
|
}.also {
|
||||||
startActivityAndCollapseCompat(it)
|
startActivityAndCollapseCompat(it)
|
||||||
}
|
}
|
||||||
@@ -189,14 +200,16 @@ class AmneziaTileService : TileService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startVpnService() {
|
private fun startVpnService() {
|
||||||
try {
|
vpnProto?.let { proto ->
|
||||||
ContextCompat.startForegroundService(
|
try {
|
||||||
applicationContext,
|
ContextCompat.startForegroundService(
|
||||||
Intent(this, AmneziaVpnService::class.java)
|
applicationContext,
|
||||||
)
|
Intent(this, proto.serviceClass)
|
||||||
} catch (e: SecurityException) {
|
)
|
||||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
} catch (e: SecurityException) {
|
||||||
}
|
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||||
|
}
|
||||||
|
} ?: Log.e(TAG, "Failed to start vpn service: vpnProto is null")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||||
@@ -220,11 +233,8 @@ class AmneziaTileService : TileService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateVpnState(state: ProtocolState) {
|
private fun updateVpnState(state: ProtocolState) =
|
||||||
scope.launch {
|
scope.launch { VpnStateStore.store { it.copy(protocolState = state) } }
|
||||||
VpnStateStore.store { it.copy(protocolState = state) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchVpnStateListening() =
|
private fun launchVpnStateListening() =
|
||||||
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
|
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
|
||||||
@@ -232,9 +242,10 @@ class AmneziaTileService : TileService() {
|
|||||||
private fun updateTile(vpnState: VpnState) {
|
private fun updateTile(vpnState: VpnState) {
|
||||||
Log.d(TAG, "Update tile: $vpnState")
|
Log.d(TAG, "Update tile: $vpnState")
|
||||||
isVpnConfigExists = vpnState.serverName != null
|
isVpnConfigExists = vpnState.serverName != null
|
||||||
|
vpnProto = vpnState.vpnProto
|
||||||
val tile = qsTile ?: return
|
val tile = qsTile ?: return
|
||||||
tile.apply {
|
tile.apply {
|
||||||
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
label = (vpnState.serverName ?: DEFAULT_TILE_LABEL) + (vpnProto?.let { " ${it.label}" } ?: "")
|
||||||
when (val protocolState = vpnState.protocolState) {
|
when (val protocolState = vpnState.protocolState) {
|
||||||
CONNECTED -> {
|
CONNECTED -> {
|
||||||
state = Tile.STATE_ACTIVE
|
state = Tile.STATE_ACTIVE
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
@@ -39,7 +40,6 @@ 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.LoadLibraryException
|
||||||
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.CONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
@@ -48,11 +48,7 @@ import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
|||||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
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.awg.Awg
|
|
||||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
|
||||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
|
||||||
import org.amnezia.vpn.protocol.putStatus
|
import org.amnezia.vpn.protocol.putStatus
|
||||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
|
||||||
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
|
||||||
@@ -63,6 +59,7 @@ import org.json.JSONObject
|
|||||||
private const val TAG = "AmneziaVpnService"
|
private const val TAG = "AmneziaVpnService"
|
||||||
|
|
||||||
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
|
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
|
||||||
|
const val ACTION_CONNECT = "org.amnezia.vpn.action.connect"
|
||||||
|
|
||||||
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||||
const val MSG_ERROR = "ERROR"
|
const val MSG_ERROR = "ERROR"
|
||||||
@@ -73,19 +70,18 @@ const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
|
|||||||
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
||||||
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||||
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||||
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
|
||||||
// private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
// private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||||
private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
|
private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
|
||||||
private const val DISCONNECT_TIMEOUT = 5000L
|
private const val DISCONNECT_TIMEOUT = 5000L
|
||||||
private const val STOP_SERVICE_TIMEOUT = 5000L
|
private const val STOP_SERVICE_TIMEOUT = 5000L
|
||||||
|
|
||||||
class AmneziaVpnService : VpnService() {
|
@SuppressLint("Registered")
|
||||||
|
open class AmneziaVpnService : VpnService() {
|
||||||
|
|
||||||
private lateinit var mainScope: CoroutineScope
|
private lateinit var mainScope: CoroutineScope
|
||||||
private lateinit var connectionScope: CoroutineScope
|
private lateinit var connectionScope: CoroutineScope
|
||||||
private var isServiceBound = false
|
private var isServiceBound = false
|
||||||
private var protocol: Protocol? = null
|
private var vpnProto: VpnProto? = null
|
||||||
private val protocolCache = mutableMapOf<String, Protocol>()
|
|
||||||
private var protocolState = MutableStateFlow(UNKNOWN)
|
private var protocolState = MutableStateFlow(UNKNOWN)
|
||||||
private var serverName: String? = null
|
private var serverName: String? = null
|
||||||
private var serverIndex: Int = -1
|
private var serverIndex: Int = -1
|
||||||
@@ -105,7 +101,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
// private var statisticsSendingJob: Job? = null
|
// private var statisticsSendingJob: Job? = null
|
||||||
private lateinit var networkState: NetworkState
|
private lateinit var networkState: NetworkState
|
||||||
private lateinit var trafficStats: TrafficStats
|
private lateinit var trafficStats: TrafficStats
|
||||||
private var disconnectReceiver: BroadcastReceiver? = null
|
private var controlReceiver: BroadcastReceiver? = null
|
||||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||||
private var screenOnReceiver: BroadcastReceiver? = null
|
private var screenOnReceiver: BroadcastReceiver? = null
|
||||||
private var screenOffReceiver: BroadcastReceiver? = null
|
private var screenOffReceiver: BroadcastReceiver? = null
|
||||||
@@ -116,7 +112,6 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
protocol = null
|
|
||||||
when (e) {
|
when (e) {
|
||||||
is IllegalArgumentException,
|
is IllegalArgumentException,
|
||||||
is VpnStartException,
|
is VpnStartException,
|
||||||
@@ -227,7 +222,8 @@ class AmneziaVpnService : VpnService() {
|
|||||||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||||
}
|
}
|
||||||
ServiceCompat.startForeground(
|
ServiceCompat.startForeground(
|
||||||
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
|
this, NOTIFICATION_ID,
|
||||||
|
serviceNotification.buildNotification(serverName, vpnProto?.label, protocolState.value),
|
||||||
foregroundServiceTypeCompat
|
foregroundServiceTypeCompat
|
||||||
)
|
)
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
@@ -292,9 +288,17 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
private fun registerBroadcastReceivers() {
|
private fun registerBroadcastReceivers() {
|
||||||
Log.d(TAG, "Register broadcast receivers")
|
Log.d(TAG, "Register broadcast receivers")
|
||||||
disconnectReceiver = registerBroadcastReceiver(ACTION_DISCONNECT, ContextCompat.RECEIVER_NOT_EXPORTED) {
|
controlReceiver = registerBroadcastReceiver(
|
||||||
Log.d(TAG, "Broadcast request received: $ACTION_DISCONNECT")
|
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
||||||
disconnect()
|
) {
|
||||||
|
it?.action?.let { action ->
|
||||||
|
Log.d(TAG, "Broadcast request received: $action")
|
||||||
|
when (action) {
|
||||||
|
ACTION_CONNECT -> connect()
|
||||||
|
ACTION_DISCONNECT -> disconnect()
|
||||||
|
else -> Log.w(TAG, "Unknown action received: $action")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
@@ -340,10 +344,10 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
private fun unregisterBroadcastReceivers() {
|
private fun unregisterBroadcastReceivers() {
|
||||||
Log.d(TAG, "Unregister broadcast receivers")
|
Log.d(TAG, "Unregister broadcast receivers")
|
||||||
unregisterBroadcastReceiver(disconnectReceiver)
|
unregisterBroadcastReceiver(controlReceiver)
|
||||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
unregisterScreenStateBroadcastReceivers()
|
unregisterScreenStateBroadcastReceivers()
|
||||||
disconnectReceiver = null
|
controlReceiver = null
|
||||||
notificationStateReceiver = null
|
notificationStateReceiver = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +360,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
protocolState.drop(1).collect { protocolState ->
|
protocolState.drop(1).collect { protocolState ->
|
||||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||||
|
|
||||||
serviceNotification.updateNotification(serverName, protocolState)
|
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState)
|
||||||
|
|
||||||
clientMessengers.send {
|
clientMessengers.send {
|
||||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||||
@@ -364,7 +368,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex, vpnProto) }
|
||||||
|
|
||||||
when (protocolState) {
|
when (protocolState) {
|
||||||
CONNECTED -> {
|
CONNECTED -> {
|
||||||
@@ -421,7 +425,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
@MainThread
|
@MainThread
|
||||||
private fun enableNotification() {
|
private fun enableNotification() {
|
||||||
registerScreenStateBroadcastReceivers()
|
registerScreenStateBroadcastReceivers()
|
||||||
serviceNotification.updateNotification(serverName, protocolState.value)
|
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState.value)
|
||||||
launchTrafficStatsUpdate()
|
launchTrafficStatsUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,8 +488,6 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
Log.d(TAG, "Start VPN connection")
|
Log.d(TAG, "Start VPN connection")
|
||||||
|
|
||||||
protocolState.value = CONNECTING
|
|
||||||
|
|
||||||
val config = parseConfigToJson(vpnConfig)
|
val config = parseConfigToJson(vpnConfig)
|
||||||
saveServerData(config)
|
saveServerData(config)
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
@@ -494,6 +496,16 @@ class AmneziaVpnService : VpnService() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
vpnProto = VpnProto.get(config.getString("protocol"))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onError("Invalid VPN config: ${e.message}")
|
||||||
|
protocolState.value = DISCONNECTED
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolState.value = CONNECTING
|
||||||
|
|
||||||
if (!checkPermission()) {
|
if (!checkPermission()) {
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
return
|
return
|
||||||
@@ -503,8 +515,10 @@ class AmneziaVpnService : VpnService() {
|
|||||||
disconnectionJob?.join()
|
disconnectionJob?.join()
|
||||||
disconnectionJob = null
|
disconnectionJob = null
|
||||||
|
|
||||||
protocol = getProtocol(config.getString("protocol"))
|
vpnProto?.protocol?.let { protocol ->
|
||||||
protocol?.startVpn(config, Builder(), ::protect)
|
protocol.initialize(applicationContext, protocolState, ::onError)
|
||||||
|
protocol.startVpn(config, Builder(), ::protect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,8 +534,8 @@ class AmneziaVpnService : VpnService() {
|
|||||||
connectionJob?.join()
|
connectionJob?.join()
|
||||||
connectionJob = null
|
connectionJob = null
|
||||||
|
|
||||||
protocol?.stopVpn()
|
vpnProto?.protocol?.stopVpn()
|
||||||
protocol = null
|
|
||||||
try {
|
try {
|
||||||
withTimeout(DISCONNECT_TIMEOUT) {
|
withTimeout(DISCONNECT_TIMEOUT) {
|
||||||
// waiting for disconnect state
|
// waiting for disconnect state
|
||||||
@@ -543,22 +557,10 @@ class AmneziaVpnService : VpnService() {
|
|||||||
protocolState.value = RECONNECTING
|
protocolState.value = RECONNECTING
|
||||||
|
|
||||||
connectionJob = connectionScope.launch {
|
connectionJob = connectionScope.launch {
|
||||||
protocol?.reconnectVpn(Builder())
|
vpnProto?.protocol?.reconnectVpn(Builder())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
|
||||||
private fun getProtocol(protocolName: String): Protocol =
|
|
||||||
protocolCache[protocolName]
|
|
||||||
?: when (protocolName) {
|
|
||||||
"wireguard" -> Wireguard()
|
|
||||||
"awg" -> Awg()
|
|
||||||
"openvpn" -> OpenVpn()
|
|
||||||
"cloak" -> Cloak()
|
|
||||||
else -> throw IllegalArgumentException("Protocol '$protocolName' not found")
|
|
||||||
}.apply { initialize(applicationContext, protocolState, ::onError) }
|
|
||||||
.also { protocolCache[protocolName] = it }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils methods
|
* Utils methods
|
||||||
*/
|
*/
|
||||||
@@ -603,6 +605,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
if (prepare(applicationContext) != null) {
|
if (prepare(applicationContext) != null) {
|
||||||
Intent(this, VpnRequestActivity::class.java).apply {
|
Intent(this, VpnRequestActivity::class.java).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
putExtra(EXTRA_PROTOCOL, vpnProto)
|
||||||
}.also {
|
}.also {
|
||||||
startActivity(it)
|
startActivity(it)
|
||||||
}
|
}
|
||||||
@@ -612,9 +615,9 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun isRunning(context: Context): Boolean =
|
fun isRunning(context: Context, processName: String): Boolean =
|
||||||
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
||||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
it.processName == processName && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
class AwgService : AmneziaVpnService()
|
||||||
@@ -140,7 +140,7 @@ class CameraActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.addOnFailureListener {
|
}.addOnFailureListener {
|
||||||
Log.e(TAG, "Processing QR-code image failed: ${it.message}")
|
Log.e(TAG, "Processing QR code image failed: ${it.message}")
|
||||||
}.addOnCompleteListener {
|
}.addOnCompleteListener {
|
||||||
imageProxy.close()
|
imageProxy.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
class OpenVpnService : AmneziaVpnService()
|
||||||
@@ -59,14 +59,14 @@ class ServiceNotification(private val context: Context) {
|
|||||||
formatSpeedString(rxString, txString)
|
formatSpeedString(rxString, txString)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildNotification(serverName: 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.d(TAG, "Build notification: $serverName, $state")
|
||||||
|
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||||
.setContentTitle(serverName ?: "AmneziaVPN")
|
.setContentTitle((serverName ?: "AmneziaVPN") + (protocol?.let { " $it" } ?: ""))
|
||||||
.setContentText(context.getString(state))
|
.setContentText(context.getString(state))
|
||||||
.setSubText(speedString)
|
.setSubText(speedString)
|
||||||
.setWhen(System.currentTimeMillis())
|
.setWhen(System.currentTimeMillis())
|
||||||
@@ -96,10 +96,10 @@ class ServiceNotification(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun updateNotification(serverName: 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.d(TAG, "Update notification: $serverName, $state")
|
||||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
|
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ class ServiceNotification(private val context: Context) {
|
|||||||
context,
|
context,
|
||||||
DISCONNECT_REQUEST_CODE,
|
DISCONNECT_REQUEST_CODE,
|
||||||
Intent(ACTION_DISCONNECT).apply {
|
Intent(ACTION_DISCONNECT).apply {
|
||||||
setPackage("org.amnezia.vpn")
|
setPackage(context.packageName)
|
||||||
},
|
},
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
@@ -135,10 +135,12 @@ class ServiceNotification(private val context: Context) {
|
|||||||
DISCONNECTED -> {
|
DISCONNECTED -> {
|
||||||
Action(
|
Action(
|
||||||
0, context.getString(R.string.connect),
|
0, context.getString(R.string.connect),
|
||||||
createServicePendingIntent(
|
PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
CONNECT_REQUEST_CODE,
|
CONNECT_REQUEST_CODE,
|
||||||
Intent(context, AmneziaVpnService::class.java),
|
Intent(ACTION_CONNECT).apply {
|
||||||
|
setPackage(context.packageName)
|
||||||
|
},
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -148,13 +150,6 @@ class ServiceNotification(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val createServicePendingIntent: (Context, Int, Intent, Int) -> PendingIntent =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
PendingIntent::getForegroundService
|
|
||||||
} else {
|
|
||||||
PendingIntent::getService
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun createNotificationChannel(context: Context) {
|
fun createNotificationChannel(context: Context) {
|
||||||
with(NotificationManagerCompat.from(context)) {
|
with(NotificationManagerCompat.from(context)) {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
|
import org.amnezia.vpn.protocol.awg.Awg
|
||||||
|
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||||
|
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||||
|
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||||
|
import org.amnezia.vpn.protocol.xray.Xray
|
||||||
|
|
||||||
|
enum class VpnProto(
|
||||||
|
val label: String,
|
||||||
|
val processName: String,
|
||||||
|
val serviceClass: Class<out AmneziaVpnService>
|
||||||
|
) {
|
||||||
|
WIREGUARD(
|
||||||
|
"WireGuard",
|
||||||
|
"org.amnezia.vpn:amneziaAwgService",
|
||||||
|
AwgService::class.java
|
||||||
|
) {
|
||||||
|
override fun createProtocol(): Protocol = Wireguard()
|
||||||
|
},
|
||||||
|
|
||||||
|
AWG(
|
||||||
|
"AmneziaWG",
|
||||||
|
"org.amnezia.vpn:amneziaAwgService",
|
||||||
|
AwgService::class.java
|
||||||
|
) {
|
||||||
|
override fun createProtocol(): Protocol = Awg()
|
||||||
|
},
|
||||||
|
|
||||||
|
OPENVPN(
|
||||||
|
"OpenVPN",
|
||||||
|
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||||
|
OpenVpnService::class.java
|
||||||
|
) {
|
||||||
|
override fun createProtocol(): Protocol = OpenVpn()
|
||||||
|
},
|
||||||
|
|
||||||
|
CLOAK(
|
||||||
|
"Cloak",
|
||||||
|
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||||
|
OpenVpnService::class.java
|
||||||
|
) {
|
||||||
|
override fun createProtocol(): Protocol = Cloak()
|
||||||
|
},
|
||||||
|
|
||||||
|
XRAY(
|
||||||
|
"XRay",
|
||||||
|
"org.amnezia.vpn:amneziaXrayService",
|
||||||
|
XrayService::class.java
|
||||||
|
) {
|
||||||
|
override fun createProtocol(): Protocol = Xray.instance
|
||||||
|
},
|
||||||
|
|
||||||
|
SSXRAY(
|
||||||
|
"SSXRay",
|
||||||
|
"org.amnezia.vpn:amneziaXrayService",
|
||||||
|
XrayService::class.java
|
||||||
|
) {
|
||||||
|
override fun createProtocol(): Protocol = Xray.instance
|
||||||
|
};
|
||||||
|
|
||||||
|
private var _protocol: Protocol? = null
|
||||||
|
val protocol: Protocol
|
||||||
|
get() {
|
||||||
|
if (_protocol == null) _protocol = createProtocol()
|
||||||
|
return _protocol ?: throw AssertionError("Set to null by another thread")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun createProtocol(): Protocol
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import android.content.Intent
|
|||||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -18,9 +19,11 @@ import androidx.core.content.getSystemService
|
|||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
private const val TAG = "VpnRequestActivity"
|
private const val TAG = "VpnRequestActivity"
|
||||||
|
const val EXTRA_PROTOCOL = "PROTOCOL"
|
||||||
|
|
||||||
class VpnRequestActivity : ComponentActivity() {
|
class VpnRequestActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private var vpnProto: VpnProto? = null
|
||||||
private var userPresentReceiver: BroadcastReceiver? = null
|
private var userPresentReceiver: BroadcastReceiver? = null
|
||||||
private val requestLauncher =
|
private val requestLauncher =
|
||||||
registerForActivityResult(StartActivityForResult(), ::checkRequestResult)
|
registerForActivityResult(StartActivityForResult(), ::checkRequestResult)
|
||||||
@@ -28,6 +31,12 @@ class VpnRequestActivity : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Start request activity")
|
Log.d(TAG, "Start request activity")
|
||||||
|
vpnProto = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
intent.extras?.getSerializable(EXTRA_PROTOCOL, VpnProto::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
intent.extras?.getSerializable(EXTRA_PROTOCOL) as VpnProto
|
||||||
|
}
|
||||||
val requestIntent = VpnService.prepare(applicationContext)
|
val requestIntent = VpnService.prepare(applicationContext)
|
||||||
if (requestIntent != null) {
|
if (requestIntent != null) {
|
||||||
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
||||||
@@ -66,10 +75,18 @@ class VpnRequestActivity : ComponentActivity() {
|
|||||||
|
|
||||||
private fun onPermissionGranted() {
|
private fun onPermissionGranted() {
|
||||||
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
vpnProto?.let { proto ->
|
||||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
Intent(applicationContext, proto.serviceClass).apply {
|
||||||
}.also {
|
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||||
ContextCompat.startForegroundService(this, it)
|
}.also {
|
||||||
|
ContextCompat.startForegroundService(this, it)
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
Intent(this, AmneziaActivity::class.java).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}.also {
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.datastore.core.CorruptionException
|
||||||
import androidx.datastore.core.MultiProcessDataStoreFactory
|
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||||
import androidx.datastore.core.Serializer
|
import androidx.datastore.core.Serializer
|
||||||
|
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||||
import androidx.datastore.dataStoreFile
|
import androidx.datastore.dataStoreFile
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.ObjectInputStream
|
|
||||||
import java.io.ObjectOutputStream
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.Serializable
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.decodeFromByteArray
|
||||||
|
import kotlinx.serialization.encodeToByteArray
|
||||||
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
import org.amnezia.vpn.protocol.ProtocolState
|
import org.amnezia.vpn.protocol.ProtocolState
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
@@ -21,13 +24,14 @@ import org.amnezia.vpn.util.Log
|
|||||||
private const val TAG = "VpnState"
|
private const val TAG = "VpnState"
|
||||||
private const val STORE_FILE_NAME = "vpnState"
|
private const val STORE_FILE_NAME = "vpnState"
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class VpnState(
|
data class VpnState(
|
||||||
val protocolState: ProtocolState,
|
val protocolState: ProtocolState,
|
||||||
val serverName: String? = null,
|
val serverName: String? = null,
|
||||||
val serverIndex: Int = -1
|
val serverIndex: Int = -1,
|
||||||
) : Serializable {
|
val vpnProto: VpnProto? = null
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val serialVersionUID: Long = -1760654961004181606
|
|
||||||
val defaultState: VpnState = VpnState(DISCONNECTED)
|
val defaultState: VpnState = VpnState(DISCONNECTED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +41,11 @@ object VpnStateStore {
|
|||||||
|
|
||||||
private val dataStore = MultiProcessDataStoreFactory.create(
|
private val dataStore = MultiProcessDataStoreFactory.create(
|
||||||
serializer = VpnStateSerializer(),
|
serializer = VpnStateSerializer(),
|
||||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
|
produceFile = { app.dataStoreFile(STORE_FILE_NAME) },
|
||||||
|
corruptionHandler = ReplaceFileCorruptionHandler { e ->
|
||||||
|
Log.e(TAG, "VpnState DataStore corrupted: $e")
|
||||||
|
VpnState.defaultState
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun init(app: Application) {
|
fun init(app: Application) {
|
||||||
@@ -45,36 +53,36 @@ object VpnStateStore {
|
|||||||
this.app = app
|
this.app = app
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dataFlow(): Flow<VpnState> = dataStore.data
|
fun dataFlow(): Flow<VpnState> = dataStore.data.catch { e ->
|
||||||
|
Log.e(TAG, "Failed to read VpnState from store: ${e.message}")
|
||||||
|
emit(VpnState.defaultState)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getVpnState(): VpnState = dataFlow().firstOrNull() ?: VpnState.defaultState
|
||||||
|
|
||||||
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
|
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
|
||||||
try {
|
try {
|
||||||
dataStore.updateData(f)
|
dataStore.updateData(f)
|
||||||
} catch (e : Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to store VpnState: $e")
|
Log.e(TAG, "Failed to store VpnState: $e")
|
||||||
|
Log.w(TAG, "Remove DataStore file")
|
||||||
|
app.dataStoreFile(STORE_FILE_NAME).delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
private class VpnStateSerializer : Serializer<VpnState> {
|
private class VpnStateSerializer : Serializer<VpnState> {
|
||||||
override val defaultValue: VpnState = VpnState.defaultState
|
override val defaultValue: VpnState = VpnState.defaultState
|
||||||
|
|
||||||
override suspend fun readFrom(input: InputStream): VpnState {
|
override suspend fun readFrom(input: InputStream): VpnState = try {
|
||||||
return withContext(Dispatchers.IO) {
|
ProtoBuf.decodeFromByteArray<VpnState>(input.readBytes())
|
||||||
val bios = ByteArrayInputStream(input.readBytes())
|
} catch (e: SerializationException) {
|
||||||
ObjectInputStream(bios).use {
|
Log.e(TAG, "Failed to deserialize data: $e")
|
||||||
it.readObject() as VpnState
|
throw CorruptionException("Failed to deserialize data", e)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
withContext(Dispatchers.IO) {
|
override suspend fun writeTo(t: VpnState, output: OutputStream) =
|
||||||
val baos = ByteArrayOutputStream()
|
output.write(ProtoBuf.encodeToByteArray(t))
|
||||||
ObjectOutputStream(baos).use {
|
|
||||||
it.writeObject(t)
|
|
||||||
}
|
|
||||||
output.write(baos.toByteArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
class XrayService : AmneziaVpnService()
|
||||||
@@ -88,16 +88,24 @@ class NetworkState(
|
|||||||
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 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
try {
|
val numberAttempts = 3
|
||||||
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 {
|
} else {
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
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
|
|
||||||
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
|
||||||
@@ -78,9 +75,8 @@ open class Wireguard : Protocol() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
override fun internalInit() {
|
||||||
super.initialize(context, state, onError)
|
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||||
loadSharedLibrary(context, "wg-go")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
plugins {
|
||||||
|
id(libs.plugins.android.library.get().pluginId)
|
||||||
|
id(libs.plugins.kotlin.android.get().pluginId)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "org.amnezia.vpn.protocol.xray"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(project(":utils"))
|
||||||
|
compileOnly(project(":protocolApi"))
|
||||||
|
implementation(project(":xray:libXray"))
|
||||||
|
implementation(libs.kotlinx.coroutines)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@file:Suppress("UnstableApiUsage")
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
maybeCreate("default")
|
||||||
|
}
|
||||||
|
artifacts.add("default", file("libxray.aar"))
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package org.amnezia.vpn.protocol.xray
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.VpnService.Builder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import go.Seq
|
||||||
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
|
import org.amnezia.vpn.protocol.xray.libXray.DialerController
|
||||||
|
import org.amnezia.vpn.protocol.xray.libXray.LibXray
|
||||||
|
import org.amnezia.vpn.protocol.xray.libXray.Logger
|
||||||
|
import org.amnezia.vpn.protocol.xray.libXray.Tun2SocksConfig
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
|
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 LIBXRAY_TAG = "libXray"
|
||||||
|
|
||||||
|
class Xray : Protocol() {
|
||||||
|
|
||||||
|
private var isRunning: Boolean = false
|
||||||
|
override val statistics: Statistics = Statistics.EMPTY_STATISTICS
|
||||||
|
|
||||||
|
override fun internalInit() {
|
||||||
|
Seq.setContext(context)
|
||||||
|
if (!isInitialized) {
|
||||||
|
LibXray.initLogger(object : Logger {
|
||||||
|
override fun warning(s: String) = Log.w(LIBXRAY_TAG, s)
|
||||||
|
|
||||||
|
override fun error(s: String) = Log.e(LIBXRAY_TAG, s)
|
||||||
|
|
||||||
|
override fun write(msg: ByteArray): Long {
|
||||||
|
Log.w(LIBXRAY_TAG, String(msg))
|
||||||
|
return msg.size.toLong()
|
||||||
|
}
|
||||||
|
}).isNotNullOrBlank { err ->
|
||||||
|
Log.w(TAG, "Failed to initialize logger: $err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
|
if (isRunning) {
|
||||||
|
Log.w(TAG, "XRay already running")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val xrayJsonConfig = config.optJSONObject("xray_config_data")
|
||||||
|
?: config.optJSONObject("ssxray_config_data")
|
||||||
|
?: throw BadConfigException("config_data not found")
|
||||||
|
val xrayConfig = parseConfig(config, xrayJsonConfig)
|
||||||
|
|
||||||
|
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
|
||||||
|
.put("loglevel", "warning")
|
||||||
|
.put("access", "none") // disable access log
|
||||||
|
|
||||||
|
start(xrayConfig, xrayJsonConfig.toString(), vpnBuilder, protect)
|
||||||
|
state.value = CONNECTED
|
||||||
|
isRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseConfig(config: JSONObject, xrayJsonConfig: JSONObject): XrayConfig {
|
||||||
|
return XrayConfig.build {
|
||||||
|
addAddress(XrayConfig.DEFAULT_IPV4_ADDRESS)
|
||||||
|
|
||||||
|
config.optString("dns1").let {
|
||||||
|
if (it.isNotBlank()) addDnsServer(parseInetAddress(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
config.optString("dns2").let {
|
||||||
|
if (it.isNotBlank()) addDnsServer(parseInetAddress(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
addRoute(InetNetwork("0.0.0.0", 0))
|
||||||
|
addRoute(InetNetwork("2000::0", 3))
|
||||||
|
config.getString("hostName").let {
|
||||||
|
excludeRoute(InetNetwork(it, 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
config.optString("mtu").let {
|
||||||
|
if (it.isNotBlank()) setMtu(it.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
val socksConfig = xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject
|
||||||
|
socksConfig.getInt("port").let { setSocksPort(it) }
|
||||||
|
|
||||||
|
configSplitTunneling(config)
|
||||||
|
configAppSplitTunneling(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun start(config: XrayConfig, configJson: String, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
|
buildVpnInterface(config, vpnBuilder)
|
||||||
|
|
||||||
|
DialerController { protect(it.toInt()) }.also {
|
||||||
|
LibXray.registerDialerController(it).isNotNullOrBlank { err ->
|
||||||
|
throw VpnStartException("Failed to register dialer controller: $err")
|
||||||
|
}
|
||||||
|
LibXray.registerListenerController(it).isNotNullOrBlank { err ->
|
||||||
|
throw VpnStartException("Failed to register listener controller: $err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vpnBuilder.establish().use { tunFd ->
|
||||||
|
if (tunFd == null) {
|
||||||
|
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Run tun2Socks")
|
||||||
|
runTun2Socks(config, tunFd.detachFd())
|
||||||
|
|
||||||
|
Log.d(TAG, "Run XRay")
|
||||||
|
Log.i(TAG, "xray ${LibXray.xrayVersion()}")
|
||||||
|
val assetsPath = context.getDir("assets", Context.MODE_PRIVATE).absolutePath
|
||||||
|
LibXray.initXray(assetsPath)
|
||||||
|
val geoDir = File(assetsPath, "geo").absolutePath
|
||||||
|
val configPath = File(context.cacheDir, "config.json")
|
||||||
|
Log.d(TAG, "xray.location.asset: $geoDir")
|
||||||
|
Log.d(TAG, "config: $configPath")
|
||||||
|
try {
|
||||||
|
configPath.writeText(configJson)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
LibXray.stopTun2Socks()
|
||||||
|
throw VpnStartException("Failed to write xray config: ${e.message}")
|
||||||
|
}
|
||||||
|
LibXray.runXray(geoDir, configPath.absolutePath, config.maxMemory).isNotNullOrBlank { err ->
|
||||||
|
LibXray.stopTun2Socks()
|
||||||
|
throw VpnStartException("Failed to start xray: $err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopVpn() {
|
||||||
|
LibXray.stopXray().isNotNullOrBlank { err ->
|
||||||
|
Log.e(TAG, "Failed to stop XRay: $err")
|
||||||
|
}
|
||||||
|
LibXray.stopTun2Socks().isNotNullOrBlank { err ->
|
||||||
|
Log.e(TAG, "Failed to stop tun2Socks: $err")
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning = false
|
||||||
|
state.value = DISCONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||||
|
state.value = CONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runTun2Socks(config: XrayConfig, fd: Int) {
|
||||||
|
val tun2SocksConfig = Tun2SocksConfig().apply {
|
||||||
|
mtu = config.mtu.toLong()
|
||||||
|
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
||||||
|
device = "fd://$fd"
|
||||||
|
logLevel = "warning"
|
||||||
|
}
|
||||||
|
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
||||||
|
throw VpnStartException("Failed to start tun2socks: $err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val instance: Xray by lazy { Xray() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String?.isNotNullOrBlank(block: (String) -> Unit) {
|
||||||
|
if (!this.isNullOrBlank()) {
|
||||||
|
block(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.amnezia.vpn.protocol.xray
|
||||||
|
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||||
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
|
|
||||||
|
private const val XRAY_DEFAULT_MTU = 1500
|
||||||
|
private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
|
||||||
|
|
||||||
|
class XrayConfig protected constructor(
|
||||||
|
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||||
|
val socksPort: Int,
|
||||||
|
val maxMemory: Long,
|
||||||
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
|
protected constructor(builder: Builder) : this(
|
||||||
|
builder,
|
||||||
|
builder.socksPort,
|
||||||
|
builder.maxMemory
|
||||||
|
)
|
||||||
|
|
||||||
|
class Builder : ProtocolConfig.Builder(false) {
|
||||||
|
internal var socksPort: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
|
||||||
|
private set
|
||||||
|
|
||||||
|
override var mtu: Int = XRAY_DEFAULT_MTU
|
||||||
|
|
||||||
|
fun setSocksPort(port: Int) = apply { socksPort = port }
|
||||||
|
|
||||||
|
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
|
||||||
|
|
||||||
|
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal val DEFAULT_IPV4_ADDRESS: InetNetwork = InetNetwork("10.0.42.2", 30)
|
||||||
|
|
||||||
|
inline fun build(block: Builder.() -> Unit): XrayConfig = Builder().apply(block).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,9 @@ 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 +83,12 @@ set(BUILD_WITH_QT6 ON)
|
|||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||||
set(LIBS ${LIBS} qt6keychain)
|
set(LIBS ${LIBS} qt6keychain)
|
||||||
|
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${OPENSSL_INCLUDE_DIR}
|
${OPENSSL_INCLUDE_DIR}
|
||||||
${LIBSSH_INCLUDE_DIR}/include
|
${LIBSSH_INCLUDE_DIR}/include
|
||||||
${LIBSSH_ROOT_DIR}/include
|
${LIBSSH_ROOT_DIR}/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
|
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
|
||||||
|
|
||||||
|
include_directories(${QSIMPLECRYPTO_DIR})
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QAead.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QRsa.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QX509.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
|
||||||
|
)
|
||||||
@@ -52,3 +52,6 @@ foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/xray/android/libxray.aar
|
||||||
|
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||||
|
|||||||
@@ -119,18 +119,21 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
|||||||
|
|
||||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||||
|
#endif
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
|
|
||||||
// no redirect-gateway
|
// no redirect-gateway
|
||||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
#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");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,17 +90,17 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||||||
{
|
{
|
||||||
return { { DockerContainer::None, "Not installed" },
|
return { { DockerContainer::None, "Not installed" },
|
||||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||||
{ DockerContainer::ShadowSocks, "ShadowSocks" },
|
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||||
{ DockerContainer::WireGuard, "WireGuard" },
|
{ DockerContainer::WireGuard, "WireGuard" },
|
||||||
{ 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("Amnezia DNS") },
|
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
||||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") },
|
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
|
||||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ 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 similar to normal web traffic, but it "
|
||||||
"may be recognized by analysis systems in some highly censored regions.") },
|
"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 "
|
||||||
@@ -127,7 +127,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2 - 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.") },
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") },
|
{ DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") },
|
||||||
@@ -164,7 +164,6 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||||
"* Normal power consumption on mobile devices\n\n"
|
|
||||||
"* Configurable encryption protocol\n"
|
"* Configurable encryption protocol\n"
|
||||||
"* Detectable by some DPI systems\n"
|
"* Detectable by some DPI systems\n"
|
||||||
"* Works over TCP network protocol.") },
|
"* Works over TCP network protocol.") },
|
||||||
@@ -286,8 +285,9 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
case DockerContainer::WireGuard: return true;
|
case DockerContainer::WireGuard: return true;
|
||||||
case DockerContainer::OpenVpn: return true;
|
case DockerContainer::OpenVpn: return true;
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Cloak:
|
case DockerContainer::Xray: return true;
|
||||||
return true;
|
case DockerContainer::Cloak: return true;
|
||||||
|
case DockerContainer::SSXray: return true;
|
||||||
// case DockerContainer::ShadowSocks: return true;
|
// case DockerContainer::ShadowSocks: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
@@ -305,6 +305,8 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
case DockerContainer::ShadowSocks: return false;
|
case DockerContainer::ShadowSocks: return false;
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Cloak: return true;
|
case DockerContainer::Cloak: return true;
|
||||||
|
case DockerContainer::Xray: return true;
|
||||||
|
case DockerContainer::SSXray: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,3 +390,18 @@ QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol,
|
|||||||
|
|
||||||
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ContainerProps::installPageOrder(DockerContainer container)
|
||||||
|
{
|
||||||
|
switch (container) {
|
||||||
|
case DockerContainer::OpenVpn: return 4;
|
||||||
|
case DockerContainer::Cloak: return 5;
|
||||||
|
case DockerContainer::ShadowSocks: return 6;
|
||||||
|
case DockerContainer::WireGuard: return 2;
|
||||||
|
case DockerContainer::Awg: return 1;
|
||||||
|
case DockerContainer::Xray: return 3;
|
||||||
|
case DockerContainer::Ipsec: return 7;
|
||||||
|
case DockerContainer::SSXray: return 8;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ namespace amnezia
|
|||||||
static bool isShareable(amnezia::DockerContainer container);
|
static bool isShareable(amnezia::DockerContainer container);
|
||||||
|
|
||||||
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||||
|
|
||||||
|
static int installPageOrder(amnezia::DockerContainer container);
|
||||||
};
|
};
|
||||||
|
|
||||||
static void declareQmlContainerEnum()
|
static void declareQmlContainerEnum()
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include "QBlockCipher.h"
|
||||||
|
#include "QRsa.h"
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
|
#include "core/enums/apiEnums.h"
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
@@ -25,25 +29,74 @@ namespace
|
|||||||
constexpr char uuid[] = "installation_uuid";
|
constexpr char uuid[] = "installation_uuid";
|
||||||
constexpr char osVersion[] = "os_version";
|
constexpr char osVersion[] = "os_version";
|
||||||
constexpr char appVersion[] = "app_version";
|
constexpr char appVersion[] = "app_version";
|
||||||
|
|
||||||
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
|
constexpr char serverCountryCode[] = "server_country_code";
|
||||||
|
constexpr char serviceType[] = "service_type";
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList proxyStorageUrl = {""};
|
||||||
|
|
||||||
|
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
if (!sslErrors.empty()) {
|
||||||
|
qDebug().noquote() << sslErrors;
|
||||||
|
return ErrorCode::ApiConfigSslError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
return ErrorCode::ApiConfigTimeoutError;
|
||||||
|
} else {
|
||||||
|
QString err = reply->errorString();
|
||||||
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
|
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
|
return ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiController::ApiController(QObject *parent) : QObject(parent)
|
ApiController::ApiController(const QString &gatewayEndpoint, QObject *parent) : QObject(parent), m_gatewayEndpoint(gatewayEndpoint)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
||||||
|
const QByteArray &apiResponseBody, QJsonObject &serverConfig)
|
||||||
{
|
{
|
||||||
if (protocol == configKey::cloak) {
|
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString();
|
||||||
config.replace("<key>", "<key>\n");
|
|
||||||
config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
data.replace("vpn://", "");
|
||||||
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
|
||||||
|
if (ba.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
|
if (!ba_uncompressed.isEmpty()) {
|
||||||
|
ba = ba_uncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString configStr = ba;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
configStr.replace("<key>", "<key>\n");
|
||||||
|
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||||
} else if (protocol == configKey::awg) {
|
} else if (protocol == configKey::awg) {
|
||||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||||
auto serverConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
auto containers = serverConfig.value(config_key::containers).toArray();
|
auto containers = serverConfig.value(config_key::containers).toArray();
|
||||||
if (containers.isEmpty()) {
|
if (containers.isEmpty()) {
|
||||||
return;
|
return; // todo process error
|
||||||
}
|
}
|
||||||
auto container = containers.at(0).toObject();
|
auto container = containers.at(0).toObject();
|
||||||
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
||||||
@@ -61,11 +114,75 @@ void ApiController::processApiConfig(const QString &protocol, const ApiControlle
|
|||||||
container[containerName] = containerConfig;
|
container[containerName] = containerConfig;
|
||||||
containers.replace(0, container);
|
containers.replace(0, container);
|
||||||
serverConfig[config_key::containers] = containers;
|
serverConfig[config_key::containers] = containers;
|
||||||
config = QString(QJsonDocument(serverConfig).toJson());
|
configStr = QString(QJsonDocument(serverConfig).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||||
|
serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion);
|
||||||
|
serverConfig[config_key::description] = apiConfig.value(config_key::description);
|
||||||
|
serverConfig[config_key::name] = apiConfig.value(config_key::name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||||
|
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList ApiController::getProxyUrls()
|
||||||
|
{
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(7000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QNetworkReply* reply;
|
||||||
|
|
||||||
|
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
||||||
|
request.setUrl(proxyStorageUrl);
|
||||||
|
reply = amnApp->manager()->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) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
EVP_PKEY *privateKey = nullptr;
|
||||||
|
QByteArray responseBody;
|
||||||
|
try {
|
||||||
|
QByteArray key = PROD_PROXY_STORAGE_KEY;
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
privateKey = rsa.getPrivateKeyFromByteArray(key, "");
|
||||||
|
responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING);
|
||||||
|
} catch (...) {
|
||||||
|
qCritical() << "error loading private key from environment variables or decrypting payload";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
|
}
|
||||||
|
|
||||||
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
||||||
{
|
{
|
||||||
ApiController::ApiPayloadData apiPayload;
|
ApiController::ApiPayloadData apiPayload;
|
||||||
@@ -101,8 +218,6 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
|||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(7000);
|
request.setTransferTimeout(7000);
|
||||||
@@ -120,39 +235,13 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
|||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
QNetworkReply *reply = amnApp->manager()->post(request, requestBody);
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
QString contents = QString::fromUtf8(reply->readAll());
|
auto apiResponseBody = reply->readAll();
|
||||||
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
|
||||||
|
emit finished(serverConfig, serverIndex);
|
||||||
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 {
|
} else {
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
@@ -178,3 +267,154 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(7000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
QNetworkReply* reply;
|
||||||
|
reply = amnApp->manager()->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();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
m_proxyUrls = getProxyUrls();
|
||||||
|
for (const QString &proxyUrl : m_proxyUrls) {
|
||||||
|
request.setUrl(QString("%1v1/services").arg(proxyUrl));
|
||||||
|
reply = amnApp->manager()->get(request);
|
||||||
|
|
||||||
|
QObject::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::TimeoutError && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody = reply->readAll();
|
||||||
|
auto errorCode = checkErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||||
|
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(7000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = userCountryCode;
|
||||||
|
if (!serverCountryCode.isEmpty()) {
|
||||||
|
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||||
|
}
|
||||||
|
apiPayload[configKey::serviceType] = serviceType;
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
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 key = PROD_AGW_PUBLIC_KEY;
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
publicKey = rsa.getPublicKeyFromByteArray(key);
|
||||||
|
} catch (...) {
|
||||||
|
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?
|
||||||
|
qCritical() << "error when encrypting the request body";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject requestBody;
|
||||||
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
|
QNetworkReply* reply = manager.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();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
if (m_proxyUrls.isEmpty()) {
|
||||||
|
m_proxyUrls = getProxyUrls();
|
||||||
|
}
|
||||||
|
for (const QString &proxyUrl : m_proxyUrls) {
|
||||||
|
request.setUrl(QString("%1v1/config").arg(proxyUrl));
|
||||||
|
reply = manager.post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
|
||||||
|
QObject::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::TimeoutError && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = checkErrors(sslErrors, reply);
|
||||||
|
if (errorCode) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
try {
|
||||||
|
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
|
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,14 +14,18 @@ class ApiController : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ApiController(QObject *parent = nullptr);
|
explicit ApiController(const QString &gatewayEndpoint, QObject *parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||||
|
|
||||||
|
ErrorCode getServicesList(QByteArray &responseBody);
|
||||||
|
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||||
|
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
void finished(const QJsonObject &config, const int serverIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ApiPayloadData
|
struct ApiPayloadData
|
||||||
@@ -34,7 +38,12 @@ private:
|
|||||||
|
|
||||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
||||||
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody,
|
||||||
|
QJsonObject &serverConfig);
|
||||||
|
QStringList getProxyUrls();
|
||||||
|
|
||||||
|
QString m_gatewayEndpoint;
|
||||||
|
QStringList m_proxyUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APICONTROLLER_H
|
#endif // APICONTROLLER_H
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
|||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME sh %1 ").arg(fileName);
|
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
||||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||||
@@ -569,6 +569,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
|
|
||||||
// Xray vars
|
// Xray vars
|
||||||
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
||||||
|
vars.append({ { "$XRAY_SERVER_PORT", xrayConfig.value(config_key::port).toString(protocols::xray::defaultPort) } });
|
||||||
|
|
||||||
// Wireguard vars
|
// Wireguard vars
|
||||||
vars.append({ { "$WIREGUARD_SUBNET_IP",
|
vars.append({ { "$WIREGUARD_SUBNET_IP",
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ namespace amnezia
|
|||||||
ApiConfigEmptyError = 1102,
|
ApiConfigEmptyError = 1102,
|
||||||
ApiConfigTimeoutError = 1103,
|
ApiConfigTimeoutError = 1103,
|
||||||
ApiConfigSslError = 1104,
|
ApiConfigSslError = 1104,
|
||||||
|
ApiMissingAgwPublicKey = 1105,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef APIENUMS_H
|
||||||
|
#define APIENUMS_H
|
||||||
|
|
||||||
|
enum ApiConfigSources {
|
||||||
|
Telegram = 1,
|
||||||
|
AmneziaGateway
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APIENUMS_H
|
||||||
@@ -9,7 +9,7 @@ QString errorString(ErrorCode code) {
|
|||||||
|
|
||||||
// General error codes
|
// General error codes
|
||||||
case(ErrorCode::NoError): errorMessage = QObject::tr("No error"); break;
|
case(ErrorCode::NoError): errorMessage = QObject::tr("No error"); break;
|
||||||
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown error"); break;
|
||||||
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||||
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
||||||
|
|
||||||
@@ -23,15 +23,15 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||||
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("SSH request was interrupted"); break;
|
||||||
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("SSH internal error"); break;
|
||||||
case(ErrorCode::SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
case(ErrorCode::SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
||||||
case(ErrorCode::SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
case(ErrorCode::SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
||||||
case(ErrorCode::SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
case(ErrorCode::SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||||
|
|
||||||
// Ssh scp errors
|
// Ssh scp errors
|
||||||
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("SCP error: Generic failure"); break;
|
||||||
|
|
||||||
// Local errors
|
// Local errors
|
||||||
case (ErrorCode::OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
case (ErrorCode::OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||||
@@ -39,7 +39,7 @@ QString errorString(ErrorCode code) {
|
|||||||
|
|
||||||
// Distro errors
|
// Distro errors
|
||||||
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||||
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("Shadowsocks (ss-local) executable missing"); break;
|
||||||
case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
||||||
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||||
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||||
@@ -60,6 +60,7 @@ QString errorString(ErrorCode code) {
|
|||||||
case (ErrorCode::ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
|
case (ErrorCode::ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
|
||||||
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
||||||
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
||||||
|
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 282 B |