Compare commits

..

1 Commits

Author SHA1 Message Date
Nethius
c0b46467e5 reduce chunk size for qr code 2024-05-02 12:09:41 +03:00
1258 changed files with 34684 additions and 117290 deletions

View File

@@ -1,39 +0,0 @@
BasedOnStyle: WebKit
AccessModifierOffset: '-4'
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: 'true'
AlignTrailingComments: 'true'
AllowAllArgumentsOnNextLine: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'true'
AllowShortEnumsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: None
AlwaysBreakTemplateDeclarations: 'No'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakConstructorInitializers: BeforeColon
ColumnLimit: '120'
CommentPragmas: '"^!|^:"'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
ConstructorInitializerIndentWidth: '4'
ContinuationIndentWidth: '8'
IndentPPDirectives: BeforeHash
NamespaceIndentation: All
PenaltyExcessCharacter: '10'
PointerAlignment: Right
SortIncludes: 'true'
SpaceAfterTemplateKeyword: 'false'
Standard: Auto

View File

@@ -1,20 +0,0 @@
/client/3rd
/client/3rd-prebuild
/client/android
/client/cmake
/client/core/utils/serialization
/client/daemon
/client/fonts
/client/images
/client/ios
/client/mozilla
/client/platforms/dummy
/client/platforms/linux
/client/platforms/macos
/client/platforms/windows
/client/server_scripts
/client/translations
/deploy
/docs
/metadata
/service/src

View File

@@ -1,38 +0,0 @@
# .github/actions/apple-install-cert/action.yml
name: Setup apple keychain
description: Creates and configures a temporary build keychain
inputs:
keychain-path:
description: Path to the keychain
required: true
keychain-password:
description: Password to the keychain
required: true
cert-base64:
description: Base64-encoded certificate
required: true
cert-password:
description: Certificate password
required: true
runs:
using: composite
steps:
- name: Create keychain
shell: bash
env:
KEYCHAIN_PATH: ${{ inputs.keychain-path }}
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
CERT_BASE64: ${{ inputs.cert-base64 }}
CERT_PASSWORD: ${{ inputs.cert-password }}
run: |
CERT_PATH=$(mktemp /tmp/cert_XXXXXX.p12)
trap "rm -f '$CERT_PATH'" EXIT
echo -n "$CERT_BASE64" | base64 --decode -o "$CERT_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$CERT_PASSWORD" -A -t cert -f pkcs12
security set-key-partition-list -S apple-tool:,apple:,codesign: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

View File

@@ -1,57 +0,0 @@
# .github/actions/apple-setup-keychain/action.yml
name: Setup apple keychain
description: Creates and configures a temporary build keychain
inputs:
keychain-name:
description: Name of the keychain
required: false
default: "ci-amnezia"
keychain-password:
description: The keychain password
required: true
lock-timeout:
description: A timeout after exceeding which the keychain would be locked
required: false
default: "0"
outputs:
keychain-path:
description: "Full path to the keychain created"
value: ${{ steps.setup.outputs.keychain-path }}
keychain-name:
description: "Actual name of the keychain created"
value: ${{ steps.setup.outputs.keychain-name }}
runs:
using: composite
steps:
- name: Setup keychain
id: setup
shell: bash
env:
KEYCHAIN_NAME: ${{ inputs.keychain-name }}
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
LOCK_TIMEOUT: ${{ inputs.lock-timeout }}
run: |
KEYCHAIN_PATH="$HOME/Library/Keychains/$KEYCHAIN_NAME.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
if [[ "$LOCK_TIMEOUT" == "0" ]]; then
security set-keychain-settings "$KEYCHAIN_PATH"
else
security set-keychain-settings -u -t "$LOCK_TIMEOUT" "$KEYCHAIN_PATH"
fi
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "${{ github.action_path }}/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -A
security import "${{ github.action_path }}/AppleWWDRCAG3.cer" -k "$KEYCHAIN_PATH" -A
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
echo "keychain-name=$KEYCHAIN_NAME" >> $GITHUB_OUTPUT
echo "keychain-path=$KEYCHAIN_PATH" >> $GITHUB_OUTPUT

View File

@@ -1,31 +0,0 @@
# .github/actions/apple-setup-provisioning-profile/action.yml
name: Setup provisioning profiles
description: Decodes and installs provisioning profiles
inputs:
provisioning_profile_base64:
description: Base64-encoded provisioning profile
required: true
runs:
using: composite
steps:
- name: Setup provisioning profile
shell: bash
run: |
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
TEMP_FILE=$(mktemp)
echo "${{ inputs.provisioning_profile_base64 }}" | base64 --decode > "$TEMP_FILE"
PROFILE_UUID=$(grep UUID -A1 -a "$TEMP_FILE" | grep -io "[-A-F0-9]\{36\}")
if [[ -z "$PROFILE_UUID" ]]; then
echo "Failed to extract UUID from provisioning profile"
rm -f "$TEMP_FILE"
exit 1
fi
mkdir -p "$PROFILES_DIR"
mv "$TEMP_FILE" "$PROFILES_DIR/$PROFILE_UUID.mobileprovision"
echo "Installed profile: $PROFILE_UUID"

File diff suppressed because it is too large Load Diff

View File

@@ -15,14 +15,6 @@ jobs:
env:
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'

View File

@@ -1,41 +1,64 @@
name: 'Upload a new version'
on:
workflow_dispatch:
inputs:
RELEASE_VERSION:
description: 'Release version (e.g. 1.2.3.4)'
required: true
type: string
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
jobs:
Upload-S3:
upload:
runs-on: ubuntu-latest
name: upload
steps:
- name: Checkout
- name: Checkout CMakeLists.txt
uses: actions/checkout@v4
with:
ref: ${{ inputs.RELEASE_VERSION }}
ref: ${{ github.ref_name }}
sparse-checkout: |
CMakeLists.txt
deploy/deploy_s3.sh
sparse-checkout-cone-mode: false
- name: Verify git tag
run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
GIT_TAG=${{ github.ref_name }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
else
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
exit 1
fi
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
- name: Download artifacts from the "${{ github.ref_name }}" tag
uses: robinraju/release-downloader@v1.8
with:
rclone_config: ${{ secrets.RCLONE_CONFIG }}
tag: ${{ github.ref_name }}
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
out-file-path: ${{ github.ref_name }}
- name: Send dist to S3
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
- name: Upload beta version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'dev')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: beta/${{ github.ref_name }}
- name: Upload stable version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'master')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: stable/${{ github.ref_name }}

12
.gitignore vendored
View File

@@ -9,7 +9,6 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
@@ -81,7 +80,6 @@ client/.DS_Store
._.DS_Store
._*
*.dmg
deploy/data/macos/pf/amn.400.allowPIA.conf
# tmp files
*.*~
@@ -135,12 +133,4 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
out/
# CMake files
CMakeFiles/
ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12
CMakeFiles/

13
.gitmodules vendored
View File

@@ -1,16 +1,15 @@
[submodule "client/3rd/OpenVPNAdapter"]
path = client/3rd/OpenVPNAdapter
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
[submodule "client/3rd/qtkeychain"]
path = client/3rd/qtkeychain
url = https://github.com/frankosterfeld/qtkeychain.git
[submodule "client/3rd/SortFilterProxyModel"]
path = client/3rd/SortFilterProxyModel
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
[submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/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
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,34 +1,17 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.15.4)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
${CMAKE_SOURCE_DIR}/cmake/platform_settings.cmake
${CMAKE_SOURCE_DIR}/cmake/recipes_bootstrap.cmake
${CMAKE_SOURCE_DIR}/cmake/conan_provider.cmake
CACHE STRING "" FORCE)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
project(${PROJECT} VERSION 4.5.2.0
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
if (PREBUILTS_ONLY)
# trigger conan to kick off `conan install`
find_package(OpenSSL REQUIRED)
return()
endif()
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2120)
set(APP_ANDROID_VERSION_CODE 51)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -45,34 +28,17 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
endif()
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(AMN_PF_RULE_IDENTITY "user { root }")
else()
set(AMN_PF_RULE_IDENTITY "group { amnvpn }")
endif()
configure_file(
"${CMAKE_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
@ONLY
)
file(COPY_FILE
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
"${CMAKE_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
ONLY_IF_DIFFERENT
)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_subdirectory(service)
endif()
if ((LINUX AND NOT ANDROID) OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (WIN32))
include(${CMAKE_SOURCE_DIR}/cmake/CPack.cmake)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()

218
README.md
View File

@@ -1,51 +1,25 @@
# Amnezia VPN
### _The best client for self-hosted VPN_
## _The best client for self-hosted VPN_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
<br/>
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
## Features
- 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.
- Classic VPN-protocols: OpenVPN, WireGuard and IKEv2 protocols.
- 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 or add Apps (only for Android and Desktop).
- 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.
- OpenVPN, ShadowSocks, WireGuard, and IKEv2 protocols support.
- Masking VPN with OpenVPN over Cloak plugin
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
- 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
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
- [https://t.me/amnezia_vpn_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
[https://amnezia.org](https://amnezia.org) - project website
[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](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
## Tech
@@ -53,26 +27,11 @@ AmneziaVPN uses several open-source projects to work:
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [ShadowSocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org)
- [WireGuard](https://www.wireguard.com/)
- [Xray-core](https://xtls.github.io/en/)
- [Conan](https://conan.io/)
- [LibSsh](https://libssh.org) - forked from Qt Creator
- and more...
## Help us 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.
## Checking out the source code
Make sure to pull all submodules after checking out the repo.
@@ -81,104 +40,113 @@ Make sure to pull all submodules after checking out the repo.
git submodule update --init --recursive
```
## Hacking guide
## Development
Want to contribute? Welcome!
### Build requirements
### Building sources and deployment
* [`CMake`](https://cmake.org/download/)
* Compiler and underlying build system, depending on the target:
- [Linux] Any of `make` and `gcc`
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) or [`Xcode command line tools`](https://developer.apple.com/xcode/)
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) or [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
- [Android] [`Android SDK`](#installing-android-sdk) and [`Ninja`](https://ninja-build.org/)
* [`Qt 6.10+`](https://www.qt.io/download-open-source) with the following modules:
- Core module for targeting platform (Desktop/Android/iOS)
- Qt 5 Compatibility module
- Qt Remote Objects
* [`Conan`](https://conan.io/downloads) package manager
- On MacOS is enough just to use `homebrew` or install it in `.venv` in project root
- Other systems must have it in `PATH`
* (Optional) Installer dependencies:
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
Check deploy folder for build scripts.
### Building the project using scripts
### How to build an iOS app from source code on MacOS
* Run scripts located in `deploy` directory
* Basically, if dependencies are located in default installation paths, the scripts will find them automatically.
* If they differ, specify them using the following variables:
- `QT_INSTALL_DIR` - Qt root installation folder
- `QT_ROOT_PATH` - Qt framework root directory
- `QIF_ROOT_PATH` - Qt Installer Framework root path
- `ANDROID_HOME` - Path to Android SDK root folder
- and others. Check scripts for more
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
Unix-like:
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:
- MacOS
- iOS
- Qt 5 Compatibility Module
- Qt Shader Tools
- Additional Libraries:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/)
4. You also need to install go >= v1.16. If you don't have it installed already,
download go from the [official website](https://golang.org/dl/) or use Homebrew.
The latest version is recommended. Install gomobile
```bash
# Build executables for the host platform
deploy/build.sh
# Or just
deploy/build.sh
# Build executables and installers for the host platform
deploy/build.sh --installer all
# Build Android APK and AAB
deploy/build.sh -t android --aab
# Call for help
deploy/build.sh -h
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
```
Windows:
```batch
:: Build executables for Windows
deploy/build.bat
5. Build the project
```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
```
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment
:: Build executables with IFW installer for Windows
deploy/build.bat --installer ifw
:: Build executables with IFW and WIX installer for Windows
deploy/build.bat --installer ifw --installer wix
:: Or just
deploy/build.bat --installer all
If you get `gomobile: command not found` make sure to set PATH to the location
of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
```bash
export PATH=$(PATH):/path/to/GOPATH/bin
```
### Developing the project in IDEs
6. Open the XCode project. You can then run /test/archive/ship the app.
* Basically, you can use any IDE that handles CMake and Qt kits properly to run configure and build steps, and to navigate through the code nicely. For example:
- `Qt Creator`
- `Visual Studio Code` with `Qt Extension Pack`
- and so on
If the build fails with the following error
```
make: ***
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
Error 1
```
Add a user-defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
* To use `Xcode`, you have to configure project first by using `cmake`. The easiest way to do it is to use `Qt Creator` for configuration. Then open `AmneziaVPN.xcodeproj` file from the build folder by using `Xcode`. Note that none of the files changed are saved - the files actually getting changed in build directory. Copy them manually if necessary
if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
```
arch -arm64 brew install cmake
```
* `Android studio` could be used in the same way - just configure the project by using `cmake` manually or by using `Qt Creator`. Open `<build-dir>/client/android-build` in `Android studio` then. Do not forget to copy the changes - everything you do is saved under the build directory actually.
Build might fail with the "source files not found" error the first time you try it, because the modern XCode build system compiles dependencies in parallel, and some dependencies end up being built after the ones that
require them. In this case, simply restart the build.
### Installing Android SDK
## How to build the Android app
* Android SDK could be installed using the following methods:
- Using `Qt Creator`. Use `Preferences`->`SDKs`
- Using `Android studio`. By default it installs necessary `SDKs` automatically during the installation
- Manually by using `sdk-manager`. Check [this](https://developer.android.com/tools) page for details
_Tested on Mac OS_
The Android app has the following requirements:
* JDK 11
* Android platform SDK 33
* 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`.
* 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! 
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. 
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on `Projects`, and on the left, you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that all Android targets will be built. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (the `Details` button might be hidden in case the QT Creator Window is not running in full screen!). Here we are: Choose `android-33` as `Android Build Platform SDK`.
That's it! You should be ready to compile the project from QT Creator!
### Development flow
After you've hit the build button, QT-Creator copies the whole project to a folder in the repository parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repository's Android project so that you can add and commit your changes!
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to go.
## License
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
GPL v3.0
## Donate
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
payeer.com: P2561305
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
## Acknowledgments
This project is tested with BrowserStack.

View File

@@ -1,180 +0,0 @@
# Amnezia VPN
### _Лучший клиент для создания VPN на собственном сервере_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
<br/>
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
## Особенности
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
## Ссылки
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
## Технологии
AmneziaVPN использует несколько проектов с открытым исходным кодом:
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org)
- [WireGuard](https://www.wireguard.com/)
- [Xray-core](https://xtls.github.io/en/)
- [Conan](https://conan.io/)
- и другие...
## Помощь с переводами
Загрузите самые актуальные файлы перевода.
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
## Проверка исходного кода
После клонирования репозитория обязательно загрузите все подмодули.
```bash
git submodule update --init --recursive
```
## Руководство по разработке
Хотите внести свой вклад? Добро пожаловать!
### Требования для сборки
* [`CMake`](https://cmake.org/download/)
* Компилятор и система сборки, в зависимости от таргета:
- [Linux] Любые `make` и `gcc`
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) или [`Xcode command line tools`](https://developer.apple.com/xcode/)
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) или [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
- [Android] [`Android SDK`](#установка-android-sdk) и [`Ninja`](https://ninja-build.org/)
* [`Qt 6.10+`](https://www.qt.io/download-open-source) со следующими модулями:
- Основные модули для таргета (Desktop/Android/iOS)
- Qt 5 Compatibility module
- Qt Remote Objects
* Пакетный менеджер [`Conan`](https://conan.io/downloads)
- На MacOS достаточно использовать `homebrew` или установить в `.venv` в корень проекта
- Для остальных систем необходимо прописать пути в `PATH`
* (Необязательно) Заивисимости для установщиков:
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
### Сборка проекта через скрипты
* Запустите скрипты, находящиеся в папке `deploy`
* Если все зависимости установлены в стандартных локациях, скрипт найдёт их самостоятельно
* Если пути отличаются, их нужно явно указать используя:
- `QT_INSTALL_DIR` - корневая папка установки Qt
- `QT_ROOT_PATH` - корневая папка Qt Framework
- `QIF_ROOT_PATH` - корневая папка Qt Installer Framework
- `ANDROID_HOME` - путь к Android SDK
- и другие. Их можно получить из вышеуказанных скриптов
Unix-like:
```bash
# Build executables for the host platform
deploy/build.sh
# Or just
deploy/build.sh
# Build executables and installers for the host platform
deploy/build.sh --installer all
# Build Android APK and AAB
deploy/build.sh -t android --aab
# Call for help
deploy/build.sh -h
```
Windows:
```batch
:: Build executables for Windows
deploy/build.bat
:: Build executables with IFW installer for Windows
deploy/build.bat --installer ifw
:: Build executables with IFW and WIX installer for Windows
deploy/build.bat --installer ifw --installer wix
:: Or just
deploy/build.bat --installer all
```
### Разработка в IDE
* Можно использовать любые IDE которые умеют работать с CMake и находить Qt Kits. Например:
- `Qt Creator`
- `Visual Studio Code` with `Qt Extension Pack`
- и так далее
* Для использования `Xcode` нужно сконфигурировать проект с помощью `cmake`. Самый простой способ это сделать - использовать `Qt Creator` для конфигурации. Затем, нужно открыть файл `AmneziaVPN.xcodeproj` из папки сборки с помощью `Xcode`. Учтите, что никакие файлы фактически не сохраняются - они сохраняются в директории сборки. Если требуется, скопируйте файлы вручную
* `Android studio` может быть использована подобным вышеуказанному способу - нужно использовать `cmake` вручную или через `Qt Creator` для конфигурации. Далее, откройте `<build-dir>/client/android-build` в `Android studio`. Не забудьте скопировать изменённые файлы в папку с исходным кодом - все файлы, изменённые в IDE, сохраняются фактически в папке сборки.
### Установка Android SDK
* Android SDK может быть установлен следующими способами:
- Используя `Qt Creator`, через настройки в пунктах `Preferences`->`SDKs`
- Используя `Android studio`. По умолчанию необходимые `SDK` устанавливаются автоматически.
- Вручную, используя `sdk-manager`. Подробности можно найти [здесь](https://developer.android.com/tools)
## Лицензия
GPL v3.0
## Донаты
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
## Благодарности
Этот проект тестируется с помощью BrowserStack.
Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.

View File

@@ -1,149 +0,0 @@
# Third-Party Licenses
This project is licensed under the GNU General Public License v3.0.
This file lists third-party software components used by this repository.
Each component is distributed under its own license as linked below.
---
## QtKeychain
- Source: https://github.com/frankosterfeld/qtkeychain
- License: BSD License
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
---
## QSimpleCrypto
- Source: https://github.com/n1flh31mur/QSimpleCrypto
- License: Apache License 2.0
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
---
## SortFilterProxyModel
- Source: https://github.com/oKcerG/SortFilterProxyModel
- License: MIT License
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
---
## QJsonStruct
- Source: https://github.com/Qv2ray/QJsonStruct
- License: MIT License
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
---
## QR Code Generator (qrcodegen)
- Source: https://github.com/nayuki/QR-Code-generator
- License: MIT License
- License Text: https://www.nayuki.io/page/qr-code-generator-library
---
## Qt Gamepad
- Source: https://github.com/qt/qtgamepad
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
---
## AmneziaWG Apple (WireGuard)
- Source: https://github.com/amnezia-vpn/amneziawg-apple
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
---
## AmneziaWG Android
- Source: https://github.com/amnezia-vpn/amneziawg-go
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
---
## Xray Core
- Source: https://github.com/XTLS/Xray-core
- License: Mozilla Public License 2.0 (MPL-2.0)
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
---
## Cloak
- Source: https://github.com/cbeuw/Cloak
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
---
## Shadowsocks
- Source: https://github.com/shadowsocks/shadowsocks-libev
- License: GPL-3.0-or-later
- License Text: http://www.gnu.org/licenses/
---
## OpenSSL
- Source: https://github.com/openssl/openssl
- License: Apache License 2.0
- License Text: https://www.openssl.org/source/license.html
---
## libssh
- Source: https://www.libssh.org/
- License: GNU Lesser General Public License (LGPL)
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
---
## OpenVPNAdapter
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
---
## Wintun
- Source: https://www.wintun.net/
- License: Prebuilt Binaries License
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
---
## Mullvad Split Tunnel Driver
- Source: https://github.com/mullvad/win-split-tunnel
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
---
## tun2socks
- Source: https://github.com/eycorsican/go-tun2socks
- License: MIT License
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
---
## TAP-Windows Driver
- Source: https://github.com/OpenVPN/tap-windows6
- License: tap-windows6 license
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING

1
client/3rd-prebuilt Submodule

Submodule client/3rd-prebuilt added at c969f28b84

1
client/3rd/OpenVPNAdapter vendored Submodule

View File

@@ -1,2 +0,0 @@
*.user
build/

View File

@@ -1,19 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(QJsonStruct LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(BUILD_TESTING ON)
include(QJsonStruct.cmake)
find_package(Qt5 COMPONENTS Core REQUIRED)
if(BUILD_TESTING)
include(CTest)
add_subdirectory(test)
endif()

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Qv2ray Workgroup
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,190 +0,0 @@
#pragma once
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QList>
#include <tuple>
enum class QJsonIOPathType
{
JSONIO_MODE_ARRAY,
JSONIO_MODE_OBJECT
};
typedef QPair<QString, QJsonIOPathType> QJsonIONodeType;
struct QJsonIOPath : QList<QJsonIONodeType>
{
template<typename type1, typename type2, typename... types>
QJsonIOPath(const type1 t1, const type2 t2, const types... ts)
{
AppendPath(t1);
AppendPath(t2);
(AppendPath(ts), ...);
}
void AppendPath(size_t index)
{
append({ QString::number(index), QJsonIOPathType::JSONIO_MODE_ARRAY });
}
void AppendPath(const QString &key)
{
append({ key, QJsonIOPathType::JSONIO_MODE_OBJECT });
}
template<typename t>
QJsonIOPath &operator<<(const t &str)
{
AppendPath(str);
return *this;
}
template<typename t>
QJsonIOPath &operator+=(const t &val)
{
AppendPath(val);
return *this;
}
QJsonIOPath &operator<<(const QJsonIOPath &other)
{
for (const auto &x : other)
this->append(x);
return *this;
}
template<typename t>
QJsonIOPath &operator<<(const t &val) const
{
auto _new = *this;
return _new << val;
}
template<typename t>
QJsonIOPath operator+(const t &val) const
{
auto _new = *this;
return _new << val;
}
QJsonIOPath operator+(const QJsonIOPath &other) const
{
auto _new = *this;
for (const auto &x : other)
_new.append(x);
return _new;
}
};
class QJsonIO
{
public:
const static inline QJsonValue Null = QJsonValue::Null;
const static inline QJsonValue Undefined = QJsonValue::Undefined;
template<typename current_key_type, typename... t_other_types>
static QJsonValue GetValue(const QJsonValue &parent, const current_key_type &current, const t_other_types &...other)
{
if constexpr (sizeof...(t_other_types) == 0)
if constexpr (std::is_integral_v<current_key_type>)
return parent.toArray()[current];
else
return parent.toObject()[current];
else if constexpr (std::is_integral_v<current_key_type>)
return GetValue(parent.toArray()[current], other...);
else
return GetValue(parent.toObject()[current], other...);
}
template<typename... key_types_t>
static QJsonValue GetValue(QJsonValue value, const std::tuple<key_types_t...> &keys, const QJsonValue &defaultValue = Undefined)
{
std::apply([&](auto &&...args) { ((value = value[args]), ...); }, keys);
return value.isUndefined() ? defaultValue : value;
}
template<typename parent_type, typename t_value_type, typename current_key_type, typename... t_other_key_types>
static void SetValue(parent_type &parent, const t_value_type &val, const current_key_type &current, const t_other_key_types &...other)
{
// If current parent is an array, increase its size to fit the "key"
if constexpr (std::is_integral_v<current_key_type>)
for (auto i = parent.size(); i <= current; i++)
parent.insert(i, {});
// If the t_other_key_types has nothing....
// Means we have reached the end of recursion.
if constexpr (sizeof...(t_other_key_types) == 0)
parent[current] = val;
else if constexpr (std::is_integral_v<typename std::tuple_element_t<0, std::tuple<t_other_key_types...>>>)
{
// Means we still have many keys
// So this element is an array.
auto _array = parent[current].toArray();
SetValue(_array, val, other...);
parent[current] = _array;
}
else
{
auto _object = parent[current].toObject();
SetValue(_object, val, other...);
parent[current] = _object;
}
}
static QJsonValue GetValue(const QJsonValue &parent, const QJsonIOPath &path, const QJsonValue &defaultValue = QJsonIO::Undefined)
{
QJsonValue val = parent;
for (const auto &[k, t] : path)
{
if (t == QJsonIOPathType::JSONIO_MODE_ARRAY)
val = val.toArray()[k.toInt()];
else
val = val.toObject()[k];
}
return val.isUndefined() ? defaultValue : val;
}
template<typename parent_type, typename value_type>
static void SetValue(parent_type &parent, const value_type &t, const QJsonIOPath &path)
{
QList<std::tuple<QString, QJsonIOPathType, QJsonValue>> _stack;
QJsonValue lastNode = parent;
for (const auto &[key, type] : path)
{
_stack.prepend({ key, type, lastNode });
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
lastNode = lastNode.toArray().at(key.toInt());
else
lastNode = lastNode.toObject()[key];
}
lastNode = t;
for (const auto &[key, type, node] : _stack)
{
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
{
const auto index = key.toInt();
auto nodeArray = node.toArray();
for (auto i = nodeArray.size(); i <= index; i++)
nodeArray.insert(i, {});
nodeArray[index] = lastNode;
lastNode = nodeArray;
}
else
{
auto nodeObject = node.toObject();
nodeObject[key] = lastNode;
lastNode = nodeObject;
}
}
if constexpr (std::is_same_v<parent_type, QJsonObject>)
parent = lastNode.toObject();
else if constexpr (std::is_same_v<parent_type, QJsonArray>)
parent = lastNode.toArray();
else
Q_UNREACHABLE();
}
};

View File

@@ -1,5 +0,0 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
set(QJSONSTRUCT_SOURCES
${CMAKE_CURRENT_LIST_DIR}/QJsonStruct.hpp
${CMAKE_CURRENT_LIST_DIR}/QJsonIO.hpp)

View File

@@ -1,215 +0,0 @@
#pragma once
#include "macroexpansion.hpp"
#ifndef _X
#include <QJsonArray>
#include <QJsonObject>
#include <QList>
#include <QVariant>
#endif
/// macro to define an operator==
#define ___JSONSTRUCT_DEFAULT_COMPARE_IMPL(x) (this->x == ___another___instance__.x) &&
#define JSONSTRUCT_COMPARE(CLASS, ...) \
bool operator==(const CLASS &___another___instance__) const \
{ \
return FOR_EACH(___JSONSTRUCT_DEFAULT_COMPARE_IMPL, __VA_ARGS__) true; \
}
// ============================================================================================
// Load JSON IMPL
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL(name) name::loadJson(___json_object_);
#define ___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC(name) ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name)
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
#define ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name) \
if (___json_object_.toObject().contains(#name)) \
{ \
JsonStructHelper::Deserialize(this->name, ___json_object_.toObject()[#name]); \
} \
else \
{ \
this->name = ___qjsonstruct_default_check.name; \
}
// ============================================================================================
// To JSON IMPL
#define ___SERIALIZE_TO_JSON_CONVERT_F_FUNC(name) \
if (!(___qjsonstruct_default_check.name == this->name)) \
{ \
___json_object_.insert(#name, JsonStructHelper::Serialize(name)); \
}
#define ___SERIALIZE_TO_JSON_CONVERT_A_FUNC(name) ___json_object_.insert(#name, JsonStructHelper::Serialize(name));
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL(name) JsonStructHelper::MergeJson(___json_object_, name::toJson());
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
// ============================================================================================
// Load JSON Wrapper
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC, __VA_ARGS__)
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC, __VA_ARGS__)
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC, __VA_ARGS__)
#define ___DESERIALIZE_FROM_JSON_EXTRACT_B_F(name_option) ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_##name_option
// ============================================================================================
// To JSON Wrapper
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_A_FUNC, __VA_ARGS__)
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_F_FUNC, __VA_ARGS__)
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_B_FUNC, __VA_ARGS__)
#define ___SERIALIZE_TO_JSON_EXTRACT_B_F(name_option) ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_##name_option
// ============================================================================================
#define JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, ...) \
void loadJson(const QJsonValue &___json_object_) \
{ \
___class_type_ ___qjsonstruct_default_check; \
FOREACH_CALL_FUNC(___DESERIALIZE_FROM_JSON_EXTRACT_B_F, __VA_ARGS__); \
} \
[[nodiscard]] const QJsonObject toJson() const \
{ \
___class_type_ ___qjsonstruct_default_check; \
QJsonObject ___json_object_; \
FOREACH_CALL_FUNC(___SERIALIZE_TO_JSON_EXTRACT_B_F, __VA_ARGS__); \
return ___json_object_; \
}
#define JSONSTRUCT_REGISTER(___class_type_, ...) \
JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, __VA_ARGS__); \
[[nodiscard]] static auto fromJson(const QJsonValue &___json_object_) \
{ \
___class_type_ _t; \
_t.loadJson(___json_object_); \
return _t; \
}
#define ___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(type, convert_func) \
static void Deserialize(type &t, const QJsonValue &d) \
{ \
t = d.convert_func; \
}
class JsonStructHelper
{
public:
static void MergeJson(QJsonObject &mergeTo, const QJsonObject &mergeIn)
{
for (const auto &key : mergeIn.keys())
mergeTo[key] = mergeIn.value(key);
}
//
template<typename T>
static void Deserialize(T &t, const QJsonValue &d)
{
if constexpr (std::is_enum_v<T>)
t = (T) d.toInt();
else if constexpr (std::is_same_v<T, QJsonObject>)
t = d.toObject();
else if constexpr (std::is_same_v<T, QJsonArray>)
t = d.toArray();
else
t.loadJson(d);
}
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QString, toString());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QChar, toVariant().toChar());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::string, toString().toStdString());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::wstring, toString().toStdWString());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(bool, toBool());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(double, toDouble());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(float, toVariant().toFloat());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(int, toInt());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long, toVariant().toLongLong());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long long, toVariant().toLongLong());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned int, toVariant().toUInt());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long, toVariant().toULongLong());
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long long, toVariant().toULongLong());
template<typename T>
static void Deserialize(QList<T> &t, const QJsonValue &d)
{
t.clear();
for (const auto &val : d.toArray())
{
T data;
Deserialize(data, val);
t.push_back(data);
}
}
template<typename TKey, typename TValue>
static void Deserialize(QMap<TKey, TValue> &t, const QJsonValue &d)
{
t.clear();
const auto &jsonObject = d.toObject();
TKey keyVal;
TValue valueVal;
for (const auto &key : jsonObject.keys())
{
Deserialize(keyVal, key);
Deserialize(valueVal, jsonObject.value(key));
t.insert(keyVal, valueVal);
}
}
// =========================== Store Json Data ===========================
template<typename T>
static QJsonValue Serialize(const T &t)
{
if constexpr (std::is_enum_v<T>)
return (int) t;
else if constexpr (std::is_same_v<T, QJsonObject> || std::is_same_v<T, QJsonArray>)
return t;
else
return t.toJson();
}
#define pure_func(x) (x)
#define ___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(type) \
static QJsonValue Serialize(const type &t) \
{ \
return QJsonValue(t); \
}
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(int);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(bool);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonArray);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonObject);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QString);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(long long);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(float);
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(double);
#define ___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(type, func) \
static QJsonValue Serialize(const type &t) \
{ \
return QJsonValue::fromVariant(func); \
}
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::string, QString::fromStdString(t))
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::wstring, QString::fromStdWString(t))
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(long, QVariant::fromValue<long>(t))
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned int, QVariant::fromValue<unsigned int>(t))
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long, QVariant::fromValue<unsigned long>(t))
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long long, QVariant::fromValue<unsigned long long>(t))
template<typename TValue>
static QJsonValue Serialize(const QMap<QString, TValue> &t)
{
QJsonObject mapObject;
for (const auto &key : t.keys())
{
auto valueVal = Serialize(t.value(key));
mapObject.insert(key, valueVal);
}
return mapObject;
}
template<typename T>
static QJsonValue Serialize(const QList<T> &t)
{
QJsonArray listObject;
for (const auto &item : t)
{
listObject.push_back(Serialize(item));
}
return listObject;
}
};

View File

@@ -1,74 +0,0 @@
#pragma once
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2) arg1##arg2
#define CONCATENATE(x, y) x##y
#define EXPAND(...) __VA_ARGS__
#define FOR_EACH_1(what, x, ...) what(x)
#define FOR_EACH_2(what, x, ...) what(x) EXPAND(FOR_EACH_1(what, __VA_ARGS__))
#define FOR_EACH_3(what, x, ...) what(x) EXPAND(FOR_EACH_2(what, __VA_ARGS__))
#define FOR_EACH_4(what, x, ...) what(x) EXPAND(FOR_EACH_3(what, __VA_ARGS__))
#define FOR_EACH_5(what, x, ...) what(x) EXPAND(FOR_EACH_4(what, __VA_ARGS__))
#define FOR_EACH_6(what, x, ...) what(x) EXPAND(FOR_EACH_5(what, __VA_ARGS__))
#define FOR_EACH_7(what, x, ...) what(x) EXPAND(FOR_EACH_6(what, __VA_ARGS__))
#define FOR_EACH_8(what, x, ...) what(x) EXPAND(FOR_EACH_7(what, __VA_ARGS__))
#define FOR_EACH_9(what, x, ...) what(x) EXPAND(FOR_EACH_8(what, __VA_ARGS__))
#define FOR_EACH_10(what, x, ...) what(x) EXPAND(FOR_EACH_9(what, __VA_ARGS__))
#define FOR_EACH_11(what, x, ...) what(x) EXPAND(FOR_EACH_10(what, __VA_ARGS__))
#define FOR_EACH_12(what, x, ...) what(x) EXPAND(FOR_EACH_11(what, __VA_ARGS__))
#define FOR_EACH_13(what, x, ...) what(x) EXPAND(FOR_EACH_12(what, __VA_ARGS__))
#define FOR_EACH_14(what, x, ...) what(x) EXPAND(FOR_EACH_13(what, __VA_ARGS__))
#define FOR_EACH_15(what, x, ...) what(x) EXPAND(FOR_EACH_14(what, __VA_ARGS__))
#define FOR_EACH_16(what, x, ...) what(x) EXPAND(FOR_EACH_15(what, __VA_ARGS__))
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) EXPAND(FOR_EACH_ARG_N(__VA_ARGS__))
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
#define FOR_EACH_RSEQ_N() 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#define FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__))
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
#define FOREACH_CALL_FUNC(func, ...) FOR_EACH(func, __VA_ARGS__)
// Bad hack ==========================================================================================================================
#define _2X_FOR_EACH_1(what, x, ...) what(x)
#define _2X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_1(what, __VA_ARGS__))
#define _2X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_2(what, __VA_ARGS__))
#define _2X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_3(what, __VA_ARGS__))
#define _2X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_4(what, __VA_ARGS__))
#define _2X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_5(what, __VA_ARGS__))
#define _2X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_6(what, __VA_ARGS__))
#define _2X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_7(what, __VA_ARGS__))
#define _2X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_8(what, __VA_ARGS__))
#define _2X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_9(what, __VA_ARGS__))
#define _2X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_10(what, __VA_ARGS__))
#define _2X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_11(what, __VA_ARGS__))
#define _2X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_12(what, __VA_ARGS__))
#define _2X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_13(what, __VA_ARGS__))
#define _2X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_14(what, __VA_ARGS__))
#define _2X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_15(what, __VA_ARGS__))
#define _2X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_2X_FOR_EACH_, N)(what, __VA_ARGS__))
#define _2X_FOR_EACH(what, ...) _2X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
#define FOREACH_CALL_FUNC_2(func, ...) _2X_FOR_EACH(func, __VA_ARGS__)
// Bad hack ==========================================================================================================================
#define _3X_FOR_EACH_1(what, x, ...) what(x)
#define _3X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_1(what, __VA_ARGS__))
#define _3X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_2(what, __VA_ARGS__))
#define _3X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_3(what, __VA_ARGS__))
#define _3X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_4(what, __VA_ARGS__))
#define _3X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_5(what, __VA_ARGS__))
#define _3X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_6(what, __VA_ARGS__))
#define _3X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_7(what, __VA_ARGS__))
#define _3X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_8(what, __VA_ARGS__))
#define _3X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_9(what, __VA_ARGS__))
#define _3X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_10(what, __VA_ARGS__))
#define _3X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_11(what, __VA_ARGS__))
#define _3X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_12(what, __VA_ARGS__))
#define _3X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_13(what, __VA_ARGS__))
#define _3X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_14(what, __VA_ARGS__))
#define _3X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_15(what, __VA_ARGS__))
#define _3X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_3X_FOR_EACH_, N)(what, __VA_ARGS__))
#define _3X_FOR_EACH(what, ...) _3X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
#define FOREACH_CALL_FUNC_3(func, ...) _3X_FOR_EACH(func, __VA_ARGS__)

View File

@@ -1,16 +0,0 @@
function(QJSONSTRUCT_ADD_TEST TEST_NAME TEST_SOURCE)
add_executable(${TEST_NAME} ${TEST_SOURCE} catch.hpp ${QJSONSTRUCT_SOURCES})
target_include_directories(${TEST_NAME}
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
target_link_libraries(
${TEST_NAME}
PRIVATE
Qt::Core
)
add_test(NAME QJSONSTRUCT_TEST_${TEST_NAME} COMMAND $<TARGET_FILE:${TEST_NAME}> -s)
endfunction()
QJSONSTRUCT_ADD_TEST(serialization serialize/main.cpp)
#QJSONSTRUCT_ADD_TEST(serialize_strings serialize/strings.cpp)

View File

@@ -1,45 +0,0 @@
#pragma once
#include "QJsonStruct.hpp"
#ifndef _X
#include <QList>
#include <QString>
#include <QStringList>
#endif
struct BaseStruct
{
QString baseStr;
int o;
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr, o))
};
struct BaseStruct2
{
QString baseStr2;
int o2;
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr2, o2))
};
struct TestInnerStruct
: BaseStruct
, BaseStruct2
{
QJsonObject jobj;
QJsonArray jarray;
QString str;
JSONSTRUCT_REGISTER(TestInnerStruct, B(BaseStruct, BaseStruct2), F(str, jobj, jarray))
};
struct JsonIOTest
{
QString str;
QList<int> listOfNumber;
QList<bool> listOfBool;
QList<QString> listOfString;
QList<QList<QString>> listOfListOfString;
QMap<QString, QString> map;
TestInnerStruct inner;
JSONSTRUCT_REGISTER(JsonIOTest, F(str, listOfNumber, listOfBool, listOfString, listOfListOfString, map, inner));
JsonIOTest(){};
};

View File

@@ -1,19 +0,0 @@
#pragma once
#include "QJsonStruct.hpp"
struct SubData
{
QString subString;
JSONSTRUCT_REGISTER_TOJSON(subString)
};
struct ToJsonOnlyData
{
QString x;
int y;
int z;
QList<int> ints;
SubData sub;
QMap<QString, SubData> subs;
JSONSTRUCT_REGISTER_TOJSON(x, y, z, sub, ints, subs)
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +0,0 @@
#include "QJsonIO.hpp"
#include "QJsonStruct.hpp"
#include "TestIO.hpp"
#include "TestOut.hpp"
#include <QCoreApplication>
#include <QJsonDocument>
#include <iostream>
int main(int argc, char *argv[])
{
Q_UNUSED(argc)
Q_UNUSED(argv)
{
ToJsonOnlyData data;
data.x = "1string";
data.y = 2;
data.ints << 0;
data.ints << 100;
data.ints << 900;
data.sub.subString = "subs";
data.subs["subs-1"] = { "subs1-data" };
data.subs["subs-2"] = { "subs2-data" };
data.subs["subs-3"] = { "subs3-data" };
data.z = 3;
auto x = data.toJson();
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
}
//
{
auto f = JsonIOTest::fromJson( //
QJsonObject{
{ "inner", QJsonObject{ { "str", "innerString" }, //
{ "jobj", QJsonObject{ { "key", "value" } } }, //
{ "jarray", QJsonArray{ "array0", "array1", "array2" } }, //
{ "baseStr", "baseInnerString" } } }, //
{ "str", "data1" }, //
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
QJsonArray{ "1", "2" }, //
QJsonArray{ "1", "2", "3" }, //
QJsonArray{ "1", "2", "3", "4" }, //
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
});
auto x = f.toJson();
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
}
{
QJsonObject obj{
{ "inner", QJsonObject{ { "str", "innerString" }, { "baseStr", "baseInnerString" } } }, //
{ "str", "data1" }, //
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
QJsonArray{ "1", "2" }, //
QJsonArray{ "1", "2", "3" }, //
QJsonArray{ "1", "2", "3", "4" }, //
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
};
auto y = QJsonIO::GetValue(obj, std::tuple{ "listOfListOfString", 2 });
y.toObject();
}
return 0;
}

View File

@@ -1,181 +0,0 @@
#include "QJsonStruct.hpp"
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
const static inline auto INT_TEST_MAX = std::numeric_limits<int>::max() - 1;
const static inline auto INT_TEST_MIN = -(std::numeric_limits<int>::min() + 1);
#define SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance) \
class CLASS \
{ \
public: \
TYPE field = defaultvalue; \
JSONSTRUCT_REGISTER(CLASS, existance(field)); \
};
// SINGLE_ELEMENT_REQUIRE( CLASS_NAME , TYPE , FIELD , DEFAULT_VALUE , SET VALUE , CHECK VALUE )
#define SINGLE_ELEMENT_REQUIRE(CLASS, TYPE, field, defaultvalue, value, checkvalue, existance) \
SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance); \
CLASS CLASS##_class; \
CLASS##_class.field = value; \
REQUIRE(CLASS##_class.toJson()[#field] == checkvalue);
using namespace std;
SCENARIO("Test Serialization", "[Serialize]")
{
GIVEN("Single Element")
{
const static QList<QString> defaultList{ "entry 1", "entry 2" };
const static QMap<QString, QString> defaultMap{ { "key1", "value1" }, { "key2", "value2" } };
typedef QMap<QString, QString> QStringQStringMap;
WHEN("Serialize a single element")
{
const static QStringQStringMap setValueMap{ { "newkey1", "newvalue1" } };
const static QJsonObject setValueJson{ { "newkey1", QJsonValue{ "newvalue1" } } };
SINGLE_ELEMENT_REQUIRE(QStringTest_Empty, QString, a, "empty", "", "", F);
SINGLE_ELEMENT_REQUIRE(QStringTest, QString, a, "empty", "Some QString", "Some QString", F);
SINGLE_ELEMENT_REQUIRE(QStringTest_WithQoutes, QString, a, "empty", "\"", "\"", F);
SINGLE_ELEMENT_REQUIRE(QStringTest_zint, int, a, -10, 0, 0, F);
SINGLE_ELEMENT_REQUIRE(QStringTest_nint, int, a, -10, 1, 1, F);
SINGLE_ELEMENT_REQUIRE(QStringTest_pint, int, a, -10, -1, -1, F);
SINGLE_ELEMENT_REQUIRE(QStringTest_pmint, int, a, -1, INT_TEST_MAX, INT_TEST_MAX, F);
SINGLE_ELEMENT_REQUIRE(QStringTest_zmint, int, a, -1, INT_TEST_MIN, INT_TEST_MIN, F);
SINGLE_ELEMENT_REQUIRE(QStringTest_zuint, uint, a, -10, 0, 0, F);
SINGLE_ELEMENT_REQUIRE(QStringTest_puint, uint, a, -10, 1, 1, F);
SINGLE_ELEMENT_REQUIRE(BoolTest_True, bool, a, false, true, true, F);
SINGLE_ELEMENT_REQUIRE(BoolTest_False, bool, a, true, false, false, F);
SINGLE_ELEMENT_REQUIRE(StdStringTest, string, a, "def", "std::string _test", "std::string _test", F);
SINGLE_ELEMENT_REQUIRE(QListTest, QList<QString>, a, defaultList, { "newEntry" }, QJsonArray{ "newEntry" }, F);
SINGLE_ELEMENT_REQUIRE(QMapTest, QStringQStringMap, a, defaultMap, {}, QJsonObject{}, F);
SINGLE_ELEMENT_REQUIRE(QMapValueTest, QStringQStringMap, a, defaultMap, setValueMap, setValueJson, F);
}
WHEN("Serialize a default value")
{
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", QJsonValue::Undefined, F);
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, QJsonValue::Undefined, F);
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, QJsonValue::Undefined, F);
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, QJsonValue::Undefined, F);
}
WHEN("Serialize a force existance default value")
{
const static QJsonArray defaultListJson{ "entry 1", "entry 2" };
const static QJsonObject defaultMapJson{ { "key1", "value1" }, { "key2", "value2" } };
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", "defaultvalue", A);
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, 12345, A);
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, defaultListJson, A);
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, defaultMapJson, A);
}
}
GIVEN("Multiple Simple Elements")
{
WHEN("Can Omit Default Value")
{
class MultipleNonDefaultElementTestClass
{
public:
QString astring;
int integer = 0;
double adouble = 0.0;
QList<QString> myList;
JSONSTRUCT_REGISTER(MultipleNonDefaultElementTestClass, F(astring, integer, adouble, myList))
};
MultipleNonDefaultElementTestClass instance;
const auto json = instance.toJson();
REQUIRE(json["astring"] == QJsonValue::Undefined);
REQUIRE(json["integer"] == QJsonValue::Undefined);
REQUIRE(json["adouble"] == QJsonValue::Undefined);
REQUIRE(json["myList"] == QJsonValue::Undefined);
}
WHEN("Forcing Existance")
{
class MultipleNonDefaultExistanceElementTestClass
{
public:
QString astring;
int integer = 0;
double adouble = 0.0;
QList<QString> myList;
JSONSTRUCT_REGISTER(MultipleNonDefaultExistanceElementTestClass, A(astring, integer, adouble, myList))
};
MultipleNonDefaultExistanceElementTestClass instance;
const auto json = instance.toJson();
REQUIRE(json["astring"] == "");
REQUIRE(json["integer"] == 0);
REQUIRE(json["adouble"] == 0.0);
REQUIRE(json["myList"] == QJsonArray{});
}
}
GIVEN("Nested Elements")
{
WHEN("Can Omit Default Value")
{
class Parent
{
class NestedChild
{
class NestedChild2
{
public:
int childChildInt = 13579;
JSONSTRUCT_COMPARE(NestedChild2, childChildInt)
JSONSTRUCT_REGISTER(NestedChild2, F(childChildInt))
};
public:
int childInt = 54321;
QString childQString = "A QString";
NestedChild2 anotherChild;
JSONSTRUCT_COMPARE(NestedChild, childInt, childQString, anotherChild)
JSONSTRUCT_REGISTER(NestedChild, F(childInt, childQString, anotherChild))
};
public:
int parentInt = 12345;
NestedChild child;
JSONSTRUCT_REGISTER(Parent, F(parentInt, child))
};
WHEN("Omitted whole child element")
{
Parent parent;
const auto json = parent.toJson();
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
REQUIRE(json["child"] == QJsonValue::Undefined);
}
WHEN("Omitted one element in the child")
{
const auto childJson = QJsonObject{ { "childInt", 1314 } };
Parent parent;
parent.child.childInt = 1314;
const auto json = parent.toJson();
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
REQUIRE(json["child"] == childJson);
REQUIRE(json["child"]["childInt"] == 1314);
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
REQUIRE(json["child"]["child"]["anotherChild"] == QJsonValue::Undefined);
}
WHEN("Omitted one element in the child child")
{
Parent parent;
parent.child.childInt = 1314;
parent.child.anotherChild.childChildInt = 97531;
const auto json = parent.toJson();
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
REQUIRE(json["child"]["childInt"] == 1314);
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
const QJsonObject childChild{ { "childChildInt", 97531 } };
REQUIRE(json["child"]["anotherChild"] == childChild);
REQUIRE(json["child"]["anotherChild"]["childChildInt"] == 97531);
}
}
}
}

View File

@@ -0,0 +1,20 @@
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
)

View File

@@ -0,0 +1,18 @@
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

View File

@@ -0,0 +1,87 @@
/**
* 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

View File

@@ -0,0 +1,84 @@
/**
* 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

View File

@@ -0,0 +1,45 @@
#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

View File

@@ -0,0 +1,104 @@
/**
* 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

View File

@@ -0,0 +1,9 @@
#ifndef QSIMPLECRYPTO_GLOBAL_H
#define QSIMPLECRYPTO_GLOBAL_H
#include <QtCore/qglobal.h>
#include <stdexcept>
#define QSIMPLECRYPTO_EXPORT
#endif // QSIMPLECRYPTO_GLOBAL_H

View File

@@ -0,0 +1,87 @@
/**
* 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

View File

@@ -0,0 +1,120 @@
/**
* 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

View File

@@ -0,0 +1,364 @@
/**
* 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();
}

View File

@@ -0,0 +1,193 @@
/**
* 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();
}

View File

@@ -0,0 +1,6 @@
#include "include/QCryptoError.h"
QSimpleCrypto::QCryptoError::QCryptoError(QObject* parent)
: QObject(parent)
{
}

View File

@@ -0,0 +1,274 @@
/**
* 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 "";
}
}

View File

@@ -0,0 +1,234 @@
/**
* 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;
}
}

View File

@@ -0,0 +1,176 @@
/**
* 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;
}

View File

@@ -0,0 +1,25 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
find_package(Qt6 REQUIRED COMPONENTS
Core Network
)
set(LIBS ${LIBS} Qt6::Core Qt6::Network)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
)
if(WIN32)
if(MSVC)
set(LIBS ${LIBS} Advapi32.lib)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(LIBS ${LIBS} advapi32)
endif()
endif()

View File

@@ -0,0 +1,274 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <QtCore/QElapsedTimer>
#include <QtCore/QByteArray>
#include <QtCore/QSharedMemory>
#include "singleapplication.h"
#include "singleapplication_p.h"
/**
* @brief Constructor. Checks and fires up LocalServer or closes the program
* if another instance already exists
* @param argc
* @param argv
* @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
{
Q_D( SingleApplication );
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
// On Android and iOS since the library is not supported fallback to
// standard QApplication behaviour by simply returning at this point.
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
return;
#endif
// Store the current mode of the program
d->options = options;
// Add any unique user data
if ( ! userData.isEmpty() )
d->addAppData( userData );
// Generating an application ID used for identifying the shared memory
// block and QLocalServer
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes
// attempting to attach at the same time
SingleApplicationPrivate::randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix.
d->memory = new QSharedMemory( d->blockServerName );
d->memory->attach();
delete d->memory;
#endif
// Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory( d->blockServerName );
// Create a shared memory block
if( d->memory->create( sizeof( InstancesInfo ) )){
// Initialize the shared memory block
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock();
} else {
if( d->memory->error() == QSharedMemory::AlreadyExists ){
// Attempt to attach to the memory segment
if( ! d->memory->attach() ){
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
abortSafely();
}
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
} else {
qCritical() << "SingleApplication: Unable to create block.";
abortSafely();
}
}
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
QElapsedTimer time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
while( true ){
// If the shared memory block's checksum is valid continue
if( d->blockChecksum() == inst->checksum ) break;
// If more than 5s have elapsed, assume the primary instance crashed and
// assume it's position
if( time.elapsed() > 5000 ){
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock();
}
// Otherwise wait for a random period and try again. The random sleep here
// limits the probability of a collision between two racing apps and
// allows the app to initialise faster
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory->errorString();
}
SingleApplicationPrivate::randomSleep();
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
if( inst->primary == false ){
d->startPrimary();
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory->errorString();
}
return;
}
// Check if another instance can be started
if( allowSecondary ){
d->startSecondary();
if( d->options & Mode::SecondaryNotification ){
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
}
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory->errorString();
}
return;
}
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory->errorString();
}
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
delete d;
::exit( EXIT_SUCCESS );
}
SingleApplication::~SingleApplication()
{
Q_D( SingleApplication );
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleApplication::isPrimary() const
{
Q_D( const SingleApplication );
return d->server != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() const
{
Q_D( const SingleApplication );
return d->server == nullptr;
}
/**
* Allows you to identify an instance by returning unique consecutive instance
* ids. It is reset when the first (primary) instance of your app starts and
* only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() const
{
Q_D( const SingleApplication );
return d->instanceNumber;
}
/**
* Returns the OS PID (Process Identifier) of the process running the primary
* instance. Especially useful when SingleApplication is coupled with OS.
* specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() const
{
Q_D( const SingleApplication );
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() const
{
Q_D( const SingleApplication );
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() const
{
return SingleApplicationPrivate::getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
{
Q_D( SingleApplication );
// Nobody to connect to
if( isPrimary() ) return false;
// Make sure the socket is connected
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
return false;
d->socket->write( message );
bool dataWritten = d->socket->waitForBytesWritten( timeout );
d->socket->flush();
return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely()
{
Q_D( SingleApplication );
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
delete d;
::exit( EXIT_FAILURE );
}
QStringList SingleApplication::userData() const
{
Q_D( const SingleApplication );
return d->appData();
}

View File

@@ -0,0 +1,154 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H
#include <QtCore/QtGlobal>
#include <QtNetwork/QLocalSocket>
#ifndef QAPPLICATION_CLASS
#define QAPPLICATION_CLASS QApplication
#endif
#include QT_STRINGIFY(QAPPLICATION_CLASS)
class SingleApplicationPrivate;
/**
* @brief The SingleApplication class handles multiple instances of the same
* Application
* @see QCoreApplication
*/
class SingleApplication : public QAPPLICATION_CLASS
{
Q_OBJECT
using app_t = QAPPLICATION_CLASS;
public:
/**
* @brief Mode of operation of SingleApplication.
* Whether the block should be user-wide or system-wide and whether the
* primary instance should be notified when a secondary instance had been
* started.
* @note Operating system can restrict the shared memory blocks to the same
* user, in which case the User/System modes will have no effect and the
* block will be user wide.
* @enum
*/
enum Mode {
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
/**
* @brief Intitializes a SingleApplication instance with argc command line
* arguments in argv
* @arg {int &} argc - Number of arguments in argv
* @arg {const char *[]} argv - Supplied command line arguments
* @arg {bool} allowSecondary - Whether to start the instance as secondary
* if there is already a primary instance.
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
* User wide or System wide.
* @arg {int} timeout - Timeout to wait in milliseconds.
* @note argc and argv may be changed as Qt removes arguments that it
* recognizes
* @note Mode::SecondaryNotification only works if set on both the primary
* instance and the secondary instance.
* @note The timeout is just a hint for the maximum time of blocking
* operations. It does not guarantee that the SingleApplication
* initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
~SingleApplication() override;
/**
* @brief Returns if the instance is the primary instance
* @returns {bool}
*/
bool isPrimary() const;
/**
* @brief Returns if the instance is a secondary instance
* @returns {bool}
*/
bool isSecondary() const;
/**
* @brief Returns a unique identifier for the current instance
* @returns {qint32}
*/
quint32 instanceId() const;
/**
* @brief Returns the process ID (PID) of the primary instance
* @returns {qint64}
*/
qint64 primaryPid() const;
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser() const;
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser() const;
/**
* @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting
* @returns {bool}
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( const QByteArray &message, int timeout = 100 );
/**
* @brief Get the set user data.
* @returns {QStringList}
*/
QStringList userData() const;
Q_SIGNALS:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
private:
SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
#endif // SINGLE_APPLICATION_H

View File

@@ -0,0 +1,15 @@
QT += core network
CONFIG += c++11
HEADERS += \
$$PWD/singleapplication.h \
$$PWD/singleapplication_p.h
SOURCES += $$PWD/singleapplication.cpp \
$$PWD/singleapplication_p.cpp
INCLUDEPATH += $$PWD
win32 {
msvc:LIBS += Advapi32.lib
gcc:LIBS += -ladvapi32
}

View File

@@ -0,0 +1,486 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <cstdlib>
#include <cstddef>
#include <QtCore/QDir>
#include <QtCore/QThread>
#include <QtCore/QByteArray>
#include <QtCore/QDataStream>
#include <QtCore/QElapsedTimer>
#include <QtCore/QCryptographicHash>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#include <QtCore/QRandomGenerator>
#else
#include <QtCore/QDateTime>
#endif
#include "singleapplication.h"
#include "singleapplication_p.h"
#ifdef Q_OS_UNIX
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#endif
#ifdef Q_OS_WIN
#ifndef NOMINMAX
#define NOMINMAX 1
#endif
#include <windows.h>
#include <lmcons.h>
#endif
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
: q_ptr( q_ptr )
{
server = nullptr;
socket = nullptr;
memory = nullptr;
instanceNumber = 0;
}
SingleApplicationPrivate::~SingleApplicationPrivate()
{
if( socket != nullptr ){
socket->close();
delete socket;
}
if( memory != nullptr ){
memory->lock();
auto *inst = static_cast<InstancesInfo*>(memory->data());
if( server != nullptr ){
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
memory->unlock();
delete memory;
}
}
QString SingleApplicationPrivate::getUsername()
{
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) )
return QString::fromWCharArray( username );
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
#else
return qEnvironmentVariable( "USERNAME" );
#endif
#endif
#ifdef Q_OS_UNIX
QString username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid( uid );
if( pw )
username = QString::fromLocal8Bit( pw->pw_name );
if ( username.isEmpty() ){
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
#else
username = qEnvironmentVariable( "USER" );
#endif
}
return username;
#endif
}
void SingleApplicationPrivate::genBlockServerName()
{
QCryptographicHash appData( QCryptographicHash::Sha256 );
appData.addData( "SingleApplication", 17 );
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
if ( ! appDataList.isEmpty() )
appData.addData( appDataList.join( "" ).toUtf8() );
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
}
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
#ifdef Q_OS_WIN
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
#else
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
#endif
}
// User level block requires a user specific data in the hash
if( options & SingleApplication::Mode::User ){
appData.addData( getUsername().toUtf8() );
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
// server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_");
}
void SingleApplicationPrivate::initializeMemoryBlock() const
{
auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
void SingleApplicationPrivate::startPrimary()
{
// Reset the number of connections
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true;
inst->primaryPid = QCoreApplication::applicationPid();
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
inst->checksum = blockChecksum();
instanceNumber = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer( blockServerName );
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions
if( options & SingleApplication::Mode::User ){
server->setSocketOptions( QLocalServer::UserAccessOption );
} else {
server->setSocketOptions( QLocalServer::WorldAccessOption );
}
server->listen( blockServerName );
QObject::connect(
server,
&QLocalServer::newConnection,
this,
&SingleApplicationPrivate::slotConnectionEstablished
);
}
void SingleApplicationPrivate::startSecondary()
{
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber = inst->secondary;
}
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
{
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already
// connected.
if( socket == nullptr ){
socket = new QLocalSocket();
}
if( socket->state() == QLocalSocket::ConnectedState ) return true;
if( socket->state() != QLocalSocket::ConnectedState ){
while( true ){
randomSleep();
if( socket->state() != QLocalSocket::ConnectingState )
socket->connectToServer( blockServerName );
if( socket->state() == QLocalSocket::ConnectingState ){
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
}
// If connected break out of the loop
if( socket->state() == QLocalSocket::ConnectedState ) break;
// If elapsed time since start is longer than the method timeout return
if( time.elapsed() >= msecs ) return false;
}
}
// Initialisation message according to the SingleApplication protocol
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
writeStream.setVersion(QDataStream::Qt_5_6);
#endif
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif
writeStream << checksum;
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion(QDataStream::Qt_5_6);
#endif
headerStream << static_cast <quint64>( initMsg.length() );
socket->write( header );
socket->write( initMsg );
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
socket->flush();
return result;
}
quint16 SingleApplicationPrivate::blockChecksum() const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
#else
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
#endif
return checksum;
}
qint64 SingleApplicationPrivate::primaryPid() const
{
qint64 pid;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid;
memory->unlock();
return pid;
}
QString SingleApplicationPrivate::primaryUser() const
{
QByteArray username;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
username = inst->primaryUser;
memory->unlock();
return QString::fromUtf8( username );
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleApplicationPrivate::slotConnectionEstablished()
{
QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
[nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
}
);
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
[nextConnSocket, this](){
connectionMap.remove(nextConnSocket);
}
);
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
[nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket];
switch(info.stage){
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
case StageBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnected:
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
break;
default:
break;
};
}
);
}
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
{
if (!connectionMap.contains( sock )){
return;
}
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
return;
}
QDataStream headerStream( sock );
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion( QDataStream::Qt_5_6 );
#endif
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock];
info.stage = StageBody;
info.msgLen = msgLen;
if ( sock->bytesAvailable() >= (qint64) msgLen ){
readInitMessageBody( sock );
}
}
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
{
Q_Q(SingleApplication);
if (!connectionMap.contains( sock )){
return;
}
ConnectionInfo &info = connectionMap[sock];
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
return;
}
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
readStream.setVersion( QDataStream::Qt_5_6 );
#endif
// server name
QByteArray latin1Name;
readStream >> latin1Name;
// connection type
ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
connectionType = static_cast <ConnectionType>( connTypeVal );
// instance id
quint32 instanceId = 0;
readStream >> instanceId;
// checksum
quint16 msgChecksum = 0;
readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif
bool isValid = readStream.status() == QDataStream::Ok &&
QLatin1String(latin1Name) == blockServerName &&
msgChecksum == actualChecksum;
if( !isValid ){
sock->close();
return;
}
info.instanceId = instanceId;
info.stage = StageConnected;
if( connectionType == NewInstance ||
( connectionType == SecondaryInstance &&
options & SingleApplication::Mode::SecondaryNotification ) )
{
Q_EMIT q->instanceStarted();
}
if (sock->bytesAvailable() > 0){
Q_EMIT this->slotDataAvailable( sock, instanceId );
}
}
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
{
Q_Q(SingleApplication);
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
}
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
{
if( closedSocket->bytesAvailable() > 0 )
Q_EMIT slotDataAvailable( closedSocket, instanceId );
}
void SingleApplicationPrivate::randomSleep()
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
#else
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
#endif
}
void SingleApplicationPrivate::addAppData(const QString &data)
{
appDataList.push_back(data);
}
QStringList SingleApplicationPrivate::appData() const
{
return appDataList;
}

View File

@@ -0,0 +1,104 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#ifndef SINGLEAPPLICATION_P_H
#define SINGLEAPPLICATION_P_H
#include <QtCore/QSharedMemory>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#include "singleapplication.h"
struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
char primaryUser[128];
quint16 checksum; // Must be the last field
};
struct ConnectionInfo {
qint64 msgLen = 0;
quint32 instanceId = 0;
quint8 stage = 0;
};
class SingleApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 {
InvalidConnection = 0,
NewInstance = 1,
SecondaryInstance = 2,
Reconnect = 3
};
enum ConnectionStage : quint8 {
StageHeader = 0,
StageBody = 1,
StageConnected = 2,
};
Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate() override;
static QString getUsername();
void genBlockServerName();
void initializeMemoryBlock() const;
void startPrimary();
void startSecondary();
bool connectToPrimary( int msecs, ConnectionType connectionType );
quint16 blockChecksum() const;
qint64 primaryPid() const;
QString primaryUser() const;
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);
static void randomSleep();
void addAppData(const QString &data);
QStringList appData() const;
SingleApplication *q_ptr;
QSharedMemory *memory;
QLocalSocket *socket;
QLocalServer *server;
quint32 instanceNumber;
QString blockServerName;
SingleApplication::Options options;
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
QStringList appDataList;
public Q_SLOTS:
void slotConnectionEstablished();
void slotDataAvailable( QLocalSocket*, quint32 );
void slotClientConnectionClosed( QLocalSocket*, quint32 );
};
#endif // SINGLEAPPLICATION_P_H

Submodule client/3rd/qtgamepad deleted from f72b3e0c62

View File

@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -23,16 +24,9 @@ execute_process(
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
add_definitions(-DFALLBACK_S3_ENDPOINT="$ENV{FALLBACK_S3_ENDPOINT}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
@@ -40,37 +34,34 @@ endif()
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
set(LIBS ${LIBS}
set(LIBS ${LIBS}
Qt6::Core Qt6::Gui
Qt6::Network Qt6::Xml Qt6::RemoteObjects
Qt6::Quick Qt6::Svg Qt6::QuickControls2
Qt6::Core5Compat Qt6::Concurrent
)
if(IOS)
set(LIBS ${LIBS} Qt6::Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets)
endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
endif()
qt6_add_resources(QRC ${QRC}
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
${CMAKE_CURRENT_LIST_DIR}/client_scripts/clientScripts.qrc
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
)
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
# -- i18n begin
set(CMAKE_AUTORCC ON)
set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
@@ -78,16 +69,28 @@ set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
)
qt6_add_translations(${PROJECT}
TS_FILES ${AMNEZIAVPN_TS_FILES}
RESOURCE_PREFIX "/translations"
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
set(QM_FILE_LIST "")
foreach(FILE ${AMNEZIAVPN_QM_FILES})
get_filename_component(QM_FILE_NAME ${FILE} NAME)
list(APPEND QM_FILE_LIST "<file>${QM_FILE_NAME}</file>")
endforeach()
string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST})
configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
# -- i18n end
if(IOS)
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
endif()
set(IS_CI ${CI})
if(IS_CI)
message("Detected CI env")
@@ -97,38 +100,151 @@ if(IS_CI)
endif()
endif()
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
${CMAKE_CURRENT_LIST_DIR}/../common/logger
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/migrations.h
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
)
# Mozilla headres
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
)
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
if(NOT IOS)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
)
endif()
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
)
# Mozilla sources
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
endif()
if(NOT IOS)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
)
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
set(HEADERS ${HEADERS}
${COMMON_FILES_H}
${PAGE_LOGIC_H}
${CONFIGURATORS_H}
${UI_MODELS_H}
${UI_CONTROLLERS_H}
)
set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
${PAGE_LOGIC_CPP}
${CONFIGURATORS_CPP}
${UI_MODELS_CPP}
${UI_CONTROLLERS_CPP}
)
if(WIN32)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
)
set(RESOURCES ${RESOURCES}
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
@@ -145,7 +261,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -164,10 +280,7 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
if (BUILD_VPN_KEYCHAIN)
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--keychain ${BUILD_VPN_KEYCHAIN}")
endif()
endif()
if(LINUX AND NOT ANDROID)
@@ -175,8 +288,33 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
)
endif()
if(ANDROID)
@@ -186,78 +324,44 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
elseif(APPLE AND NOT IOS)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(tests)
endif()
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()
install(TARGETS ${PROJECT}
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
)
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
)
set(deploy_tool_options "")
# deploy artifacts required to run the application to the debug build folder
if(WIN32)
set(deploy_tool_options "--force-openssl --force")
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
set(DEPLOY_PLATFORM_PATH "windows/x64")
else()
set(DEPLOY_PLATFORM_PATH "windows/x32")
endif()
elseif(LINUX)
set(DEPLOY_PLATFORM_PATH "linux/client")
elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
qt_generate_deploy_qml_app_script(
TARGET ${PROJECT}
OUTPUT_SCRIPT QT_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_TOOL_OPTIONS ${deploy_tool_options}
)
install(SCRIPT ${QT_DEPLOY_SCRIPT}
COMPONENT AmneziaVPN
)
if (APPLE AND NOT IOS AND NOT MACOS_NE)
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
endif()
if (LINUX AND NOT ANDROID)
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
endif()
if(OVPN_SCRIPTS)
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${OVPN_SCRIPTS}
"$<TARGET_FILE_DIR:${PROJECT}>"
if(NOT IOS AND NOT ANDROID)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
install(FILES ${OVPN_SCRIPTS}
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
PERMISSIONS
OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})

View File

@@ -1,309 +0,0 @@
#include "amneziaApplication.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include <QtQuick/QQuickWindow>
#include <QWindow>
#include "core/protocols/qmlRegisterProtocols.h"
#include "logger.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
bool AmneziaApplication::m_forceQuit = false;
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, this);
m_nam = new QNetworkAccessManager(this);
}
AmneziaApplication::~AmneziaApplication()
{
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
if (m_engine) {
delete m_engine;
}
}
#ifdef Q_OS_ANDROID
namespace {
static void clearQtCaches()
{
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
if (!cacheRoot.isEmpty()) {
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
QDir(cacheRoot + "/qmlcache").removeRecursively();
}
}
}
#endif
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
#ifdef Q_OS_ANDROID
QObject::connect(win, &QQuickWindow::sceneGraphError,
[](QQuickWindow::SceneGraphError, const QString &msg) {
qWarning() << "Scene graph error (suppressed):" << msg;
});
// Keep graphics context alive across hide/show cycles to avoid
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
if (!data.isEmpty()) {
if (m_coreController) {
m_coreController->importConfigFromData(data);
}
}
}
m_engine->load(url);
m_coreController->setQmlRoot();
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
#else
m_coreController->pageController()->showOnStartup();
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#endif
if (m_parser.isSet(m_optConnect)) {
bool ok = false;
int idx = m_parser.value(m_optConnect).toInt(&ok);
if (ok) {
QTimer::singleShot(0, this, [this, idx]() {
if (m_coreController) {
m_coreController->openConnectionByIndex(idx);
}
});
}
}
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
using namespace amnezia::ProtocolEnumNS;
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
amnezia::declareQmlProtocolEnum();
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
m_parser.process(*this);
if (m_parser.isSet(m_optCleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
QLocalServer *server = new QLocalServer(this);
server->listen(serverName);
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) {
QLocalSocket *clientConnection = server->nextPendingConnection();
clientConnection->deleteLater();
}
emit m_coreController->pageController()->raiseMainWindow(); //TODO
});
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
QNetworkAccessManager *AmneziaApplication::networkManager()
{
return m_nam;
}
QClipboard *AmneziaApplication::getClipboard()
{
return this->clipboard();
}

View File

@@ -1,78 +0,0 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include <QClipboard>
#include "core/controllers/coreController.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
#include "ui/models/containerProps.h"
#include "ui/models/protocolProps.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
AmneziaApplication(int &argc, char *argv[]);
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
SecureQSettings* m_settings;
QScopedPointer<CoreController> m_coreController;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -0,0 +1,406 @@
#include "amnezia_application.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QMimeData>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QQuickItem>
#include "logger.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID)
#include "core/installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
#endif
#include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
#else
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif
{
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = std::shared_ptr<Settings>(new Settings);
}
AmneziaApplication::~AmneziaApplication()
{
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
initModels();
loadTranslator();
initControllers();
#ifdef Q_OS_ANDROID
if (!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection)
m_vpnConnection->restoreConnection();
});
if (!AndroidController::instance()->initialize()) {
qFatal("Android controller initialization failed");
}
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
#endif
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
m_pageController->replaceStartPage();
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
#endif
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
&ConnectionController::openConnection);
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
#ifndef Q_OS_ANDROID
if (m_settings->isSaveLogs()) {
if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#endif
#ifdef Q_OS_WIN
if (m_parser.isSet("a"))
m_pageController->showOnStartup();
else
emit m_pageController->raiseMainWindow();
#else
m_pageController->showOnStartup();
#endif
// TODO - fix
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (isPrimary()) {
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
qDebug() << "Secondary instance started, showing this window instead";
emit m_pageController->raiseMainWindow();
});
}
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#endif
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
declareQmlProtocolEnum();
declareQmlContainerEnum();
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
void AmneziaApplication::loadTranslator()
{
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
}
void AmneziaApplication::updateTranslator(const QLocale &locale)
{
if (!m_translator->isEmpty()) {
QCoreApplication::removeTranslator(m_translator.get());
}
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
}
} else {
m_settings->setAppLanguage(QLocale::English);
}
m_engine->retranslate();
emit translationsUpdated();
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
void AmneziaApplication::initModels()
{
m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_defaultServerContainersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
m_serversModel->resetModel();
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
&ServersModel::clearCachedProfile);
}
void AmneziaApplication::initControllers()
{
m_connectionController.reset(
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) {
emit m_pageController->showErrorMessage(errorMessage);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
&ConnectionController::toggleConnection, Qt::QueuedConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
}

View File

@@ -0,0 +1,129 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "settings.h"
#include "vpnconnection.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
#include "ui/notificationhandler.h"
#ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h"
#endif
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS SingleApplication
#define QAPPLICATION_CLASS QApplication
#include "singleapplication.h"
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication(int &argc, char *argv[]);
#else
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
const QString &userData = {});
#endif
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
void loadTranslator();
void updateTranslator(const QLocale &locale);
bool parseCommands();
QQmlApplicationEngine *qmlEngine() const;
signals:
void translationsUpdated();
private:
void initModels();
void initControllers();
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QScopedPointer<NotificationHandler> m_notificationHandler;
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -3,6 +3,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:installLocation="auto">
@@ -10,9 +11,6 @@
<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.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
of the application. Remove the comment if you do not require these default features. -->
@@ -20,7 +18,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<!-- To request network state -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -33,11 +31,9 @@
android:label="-- %%INSERT_APP_NAME%% --"
android:icon="@mipmap/icon"
android:roundIcon="@mipmap/icon_round"
android:banner="@mipmap/ic_banner"
android:theme="@style/NoActionBar"
android:fullBackupContent="@xml/backup_content"
android:dataExtractionRules="@xml/data_extraction_rules"
android:hasFragileUserData="false"
tools:targetApi="s">
<activity
@@ -45,14 +41,12 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
@@ -68,6 +62,9 @@
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
<meta-data
android:name="android.app.extract_android_style"
android:value="minimal" />
</activity>
<activity
@@ -85,20 +82,6 @@
android:exported="false"
android:theme="@style/Translucent" />
<activity android:name=".AuthActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity android:name=".TvFilePicker"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity
android:name=".ImportConfigActivity"
android:excludeFromRecents="true"
@@ -153,34 +136,8 @@
</activity>
<service
android:name=".AwgService"
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:name=".AmneziaVpnService"
android:process=":amneziaVpnService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="systemExempted"
android:exported="false"
@@ -215,4 +172,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -1,21 +1,81 @@
package org.amnezia.vpn.protocol.awg
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
import org.json.JSONObject
/**
* Config example:
* {
* "protocol": "awg",
* "description": "Server 1",
* "dns1": "1.1.1.1",
* "dns2": "1.0.0.1",
* "hostName": "100.100.100.0",
* "splitTunnelSites": [
* ],
* "splitTunnelType": 0,
* "awg_config_data": {
* "H1": "969537490",
* "H2": "481688153",
* "H3": "2049399200",
* "H4": "52029755",
* "Jc": "3",
* "Jmax": "1000",
* "Jmin": "50",
* "S1": "49",
* "S2": "60",
* "client_ip": "10.8.1.1",
* "hostName": "100.100.100.0",
* "port": 12345,
* "client_pub_key": "clientPublicKeyBase64",
* "client_priv_key": "privateKeyBase64",
* "psk_key": "presharedKeyBase64",
* "server_pub_key": "publicKeyBase64",
* "config": "[Interface]
* Address = 10.8.1.1/32
* DNS = 1.1.1.1, 1.0.0.1
* PrivateKey = privateKeyBase64
* Jc = 3
* Jmin = 50
* Jmax = 1000
* S1 = 49
* S2 = 60
* H1 = 969537490
* H2 = 481688153
* H3 = 2049399200
* H4 = 52029755
*
* [Peer]
* PublicKey = publicKeyBase64
* PresharedKey = presharedKeyBase64
* AllowedIPs = 0.0.0.0/0, ::/0
* Endpoint = 100.100.100.0:12345
* PersistentKeepalive = 25
* "
* }
* }
*/
class Awg : Wireguard() {
override val ifName: String = "awg0"
override fun parseConfig(config: JSONObject): WireguardConfig {
val configData = config.getJSONObject("awg_config_data")
return WireguardConfig.build {
setUseProtocolExtension(true)
configExtensionParameters(configData)
configWireguard(config, configData)
override fun parseConfig(config: JSONObject): AwgConfig {
val configDataJson = config.getJSONObject("awg_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
return AwgConfig.build {
configWireguard(configData, configDataJson)
configSplitTunneling(config)
configAppSplitTunneling(config)
configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) }
configData["Jmax"]?.let { setJmax(it.toInt()) }
configData["S1"]?.let { setS1(it.toInt()) }
configData["S2"]?.let { setS2(it.toInt()) }
configData["H1"]?.let { setH1(it.toLong()) }
configData["H2"]?.let { setH2(it.toLong()) }
configData["H3"]?.let { setH3(it.toLong()) }
configData["H4"]?.let { setH4(it.toLong()) }
}
}
}

View File

@@ -0,0 +1,108 @@
package org.amnezia.vpn.protocol.awg
import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
class AwgConfig private constructor(
wireguardConfigBuilder: WireguardConfig.Builder,
val jc: Int,
val jmin: Int,
val jmax: Int,
val s1: Int,
val s2: Int,
val h1: Long,
val h2: Long,
val h3: Long,
val h4: Long
) : WireguardConfig(wireguardConfigBuilder) {
private constructor(builder: Builder) : this(
builder,
builder.jc,
builder.jmin,
builder.jmax,
builder.s1,
builder.s2,
builder.h1,
builder.h2,
builder.h3,
builder.h4
)
override fun appendDeviceLine(sb: StringBuilder) = with(sb) {
super.appendDeviceLine(this)
appendLine("jc=$jc")
appendLine("jmin=$jmin")
appendLine("jmax=$jmax")
appendLine("s1=$s1")
appendLine("s2=$s2")
appendLine("h1=$h1")
appendLine("h2=$h2")
appendLine("h3=$h3")
appendLine("h4=$h4")
}
class Builder : WireguardConfig.Builder() {
private var _jc: Int? = null
internal var jc: Int
get() = _jc ?: throw BadConfigException("AWG: parameter jc is undefined")
private set(value) { _jc = value }
private var _jmin: Int? = null
internal var jmin: Int
get() = _jmin ?: throw BadConfigException("AWG: parameter jmin is undefined")
private set(value) { _jmin = value }
private var _jmax: Int? = null
internal var jmax: Int
get() = _jmax ?: throw BadConfigException("AWG: parameter jmax is undefined")
private set(value) { _jmax = value }
private var _s1: Int? = null
internal var s1: Int
get() = _s1 ?: throw BadConfigException("AWG: parameter s1 is undefined")
private set(value) { _s1 = value }
private var _s2: Int? = null
internal var s2: Int
get() = _s2 ?: throw BadConfigException("AWG: parameter s2 is undefined")
private set(value) { _s2 = value }
private var _h1: Long? = null
internal var h1: Long
get() = _h1 ?: throw BadConfigException("AWG: parameter h1 is undefined")
private set(value) { _h1 = value }
private var _h2: Long? = null
internal var h2: Long
get() = _h2 ?: throw BadConfigException("AWG: parameter h2 is undefined")
private set(value) { _h2 = value }
private var _h3: Long? = null
internal var h3: Long
get() = _h3 ?: throw BadConfigException("AWG: parameter h3 is undefined")
private set(value) { _h3 = value }
private var _h4: Long? = null
internal var h4: Long
get() = _h4 ?: throw BadConfigException("AWG: parameter h4 is undefined")
private set(value) { _h4 = value }
fun setJc(jc: Int) = apply { this.jc = jc }
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
fun setS1(s1: Int) = apply { this.s1 = s1 }
fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setH1(h1: Long) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 }
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) }
}
companion object {
inline fun build(block: Builder.() -> Unit): AwgConfig = Builder().apply(block).build()
}
}

View File

@@ -3,6 +3,3 @@
// android.bundle.enableUncompressedNativeLibs is deprecated
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
useLegacyPackaging
// package name for androiddeployqt
namespace = "org.amnezia.vpn"

View File

@@ -3,7 +3,6 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
id("property-delegate")
}
@@ -39,7 +38,6 @@ android {
// keeps language resources for only the locales specified below
resourceConfigurations += listOf("en", "ru", "b+zh+Hans")
ndk.abiFilters += qtTargetAbiList.split(",")
}
sourceSets {
@@ -53,12 +51,44 @@ android {
}
}
signingConfigs {
register("release") {
storeFile = providers.environmentVariable("ANDROID_KEYSTORE_PATH").orNull?.let { file(it) }
storePassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
keyAlias = providers.environmentVariable("ANDROID_KEYSTORE_KEY_ALIAS").orNull
keyPassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
}
}
buildTypes {
release {
// exclude coroutine debug resource from release build
packaging {
resources.excludes += "DebugProbesKt.bin"
}
signingConfig = signingConfigs["release"]
}
}
splits {
abi {
isEnable = true
reset()
include(*qtTargetAbiList.split(',').toTypedArray())
isUniversalApk = false
}
}
// fix for Qt Creator to allow deploying the application to a device
// to enable this fix, add the line outputBaseName=android-build to local.properties
if (outputBaseName.isNotEmpty()) {
applicationVariants.all {
outputs.map { it as BaseVariantOutputImpl }
.forEach { output ->
if (output.outputFileName.endsWith(".apk")) {
output.outputFileName = "$outputBaseName-${buildType.name}.apk"
}
}
}
}
@@ -68,20 +98,18 @@ android {
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
implementation(project(":qt"))
implementation(project(":utils"))
implementation(project(":protocolApi"))
implementation(project(":wireguard"))
implementation(project(":awg"))
implementation(project(":openvpn"))
implementation(project(":xray"))
implementation(project(":cloak"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)
implementation(libs.androidx.fragment)
implementation(libs.kotlinx.coroutines)
implementation(libs.kotlinx.serialization.protobuf)
implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit)
implementation(libs.androidx.datastore)
implementation(libs.androidx.biometric)
}

View File

@@ -8,12 +8,11 @@ kotlin {
}
android {
namespace = "org.amnezia.vpn.protocol.xray"
namespace = "org.amnezia.vpn.protocol.cloak"
}
dependencies {
compileOnly(project(":utils"))
compileOnly(project(":protocolApi"))
implementation(project(":xray:libXray"))
implementation(libs.kotlinx.coroutines)
implementation(project(":openvpn"))
}

View File

@@ -0,0 +1,79 @@
package org.amnezia.vpn.protocol.cloak
import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject
/**
* Config Example:
* {
* "protocol": "cloak",
* "description": "Server 1",
* "dns1": "1.1.1.1",
* "dns2": "1.0.0.1",
* "hostName": "100.100.100.0",
* "splitTunnelSites": [
* ],
* "splitTunnelType": 0,
* "openvpn_config_data": {
* "config": "openVpnConfig"
* }
* "cloak_config_data": {
* "BrowserSig": "chrome",
* "EncryptionMethod": "aes-gcm",
* "NumConn": 1,
* "ProxyMethod": "openvpn",
* "PublicKey": "PublicKey=",
* "RemoteHost": "100.100.100.0",
* "RemotePort": "443",
* "ServerName": "servername",
* "StreamTimeout": 300,
* "Transport": "direct",
* "UID": "UID="
* }
* }
*/
class Cloak : OpenVpn() {
override fun parseConfig(config: JSONObject): ClientAPI_Config {
val openVpnConfig = ClientAPI_Config()
val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config")
val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data"))
val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT)
val configStr = "$openVpnConfigStr\n<cloak>\n$cloakConfigStr\n</cloak>\n"
openVpnConfig.usePluggableTransports = true
openVpnConfig.content = configStr
return openVpnConfig
}
override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn")
if (cloakConfigJson.has("port")) {
val port = cloakConfigJson["port"]
cloakConfigJson.remove("port")
cloakConfigJson.put("RemotePort", port)
}
if (cloakConfigJson.has("remote")) {
val remote = cloakConfigJson["remote"]
cloakConfigJson.remove("remote")
cloakConfigJson.put("RemoteHost", remote)
}
return cloakConfigJson
}
}

View File

@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
# For development copy and set local values for these parameters in local.properties
#androidCompileSdkVersion=android-34
#androidBuildToolsVersion=34.0.0
#qtMinSdkVersion=26
#qtMinSdkVersion=24
#qtTargetSdkVersion=34
#androidNdkVersion=26.1.10909125
#qtTargetAbiList=x86_64

View File

@@ -1,32 +1,26 @@
[versions]
agp = "8.6.1"
kotlin = "1.9.24"
androidx-core = "1.13.1"
androidx-activity = "1.9.1"
androidx-annotation = "1.8.2"
androidx-biometric = "1.2.0-alpha05"
androidx-camera = "1.5.3"
androidx-fragment = "1.8.2"
agp = "8.2.0"
kotlin = "1.9.20"
androidx-core = "1.12.0"
androidx-activity = "1.8.1"
androidx-annotation = "1.7.0"
androidx-camera = "1.3.0"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.1"
kotlinx-coroutines = "1.8.1"
kotlinx-serialization = "1.6.3"
google-mlkit = "17.3.0"
androidx-datastore = "1.1.0-beta01"
kotlinx-coroutines = "1.7.3"
google-mlkit = "17.2.0"
[libraries]
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
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" }
[bundles]
@@ -41,4 +35,3 @@ androidx-camera = [
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -86,8 +84,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

View File

@@ -13,8 +13,6 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -45,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -59,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail

View File

@@ -1,24 +1,41 @@
package org.amnezia.vpn.protocol.openvpn
import android.content.Context
import android.net.VpnService.Builder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.getLocalNetworks
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject
/**
* Config Example:
* {
* "protocol": "openvpn",
* "description": "Server 1",
* "dns1": "1.1.1.1",
* "dns2": "1.0.0.1",
* "hostName": "100.100.100.0",
* "splitTunnelSites": [
* ],
* "splitTunnelType": 0,
* "openvpn_config_data": {
* "config": "openVpnConfig"
* }
* }
*/
open class OpenVpn : Protocol() {
private lateinit var context: Context
private var openVpnClient: OpenVpnClient? = null
private lateinit var scope: CoroutineScope
@@ -34,18 +51,14 @@ open class OpenVpn : Protocol() {
return Statistics.EMPTY_STATISTICS
}
override fun internalInit() {
if (!isInitialized) {
loadSharedLibrary(context, "ovpn3")
loadSharedLibrary(context, "ovpnutil")
}
if (this::scope.isInitialized) {
scope.cancel()
}
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
super.initialize(context, state, onError)
loadSharedLibrary(context, "ovpn3")
this.context = context
scope = CoroutineScope(Dispatchers.IO)
}
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val configBuilder = OpenVpnConfig.Builder()
openVpnClient = OpenVpnClient(
@@ -64,12 +77,6 @@ open class OpenVpn : Protocol() {
if (evalConfig.error) {
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
}
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
configPluggableTransport(configBuilder, config)
configBuilder.configSplitTunneling(config)
configBuilder.configAppSplitTunneling(config)
@@ -93,7 +100,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder)
it.reconnect(0)

View File

@@ -2,6 +2,7 @@ package org.amnezia.vpn.protocol
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)

View File

@@ -1,5 +1,6 @@
package org.amnezia.vpn.protocol
import android.annotation.SuppressLint
import android.content.Context
import android.net.IpPrefix
import android.net.VpnService
@@ -7,6 +8,9 @@ import android.net.VpnService.Builder
import android.os.Build
import android.system.OsConstants
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork
@@ -23,26 +27,19 @@ private const val SPLIT_TUNNEL_EXCLUDE = 2
abstract class Protocol {
abstract val statistics: Statistics
protected lateinit var context: Context
protected lateinit var state: MutableStateFlow<ProtocolState>
protected lateinit var onError: (String) -> Unit
protected var isInitialized: Boolean = false
fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
this.context = context
open fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
this.state = state
this.onError = onError
internalInit()
isInitialized = true
}
protected abstract fun internalInit()
abstract suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun reconnectVpn(vpnBuilder: Builder)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) {
@@ -108,27 +105,25 @@ abstract class Protocol {
vpnBuilder.addSearchDomain(it)
}
for ((inetNetwork, include) in config.routes) {
if (include) {
Log.d(TAG, "addRoute: $inetNetwork")
vpnBuilder.addRoute(inetNetwork)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Log.d(TAG, "excludeRoute: $inetNetwork")
vpnBuilder.excludeRoute(inetNetwork)
} else {
Log.e(TAG, "Trying to exclude route $inetNetwork on old Android")
}
for (addr in config.routes) {
Log.d(TAG, "addRoute: $addr")
vpnBuilder.addRoute(addr)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
for (addr in config.excludedRoutes) {
Log.d(TAG, "excludeRoute: $addr")
vpnBuilder.excludeRoute(addr)
}
}
for (app in config.includedApplications) {
Log.d(TAG, "addAllowedApplication")
Log.d(TAG, "addAllowedApplication: $app")
vpnBuilder.addAllowedApplication(app)
}
for (app in config.excludedApplications) {
Log.d(TAG, "addDisallowedApplication")
Log.d(TAG, "addDisallowedApplication: $app")
vpnBuilder.addDisallowedApplication(app)
}
@@ -154,6 +149,60 @@ abstract class Protocol {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
vpnBuilder.setMetered(false)
}
companion object {
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
Log.d(TAG, "Extracting library: $libraryName")
val apks = hashSetOf<String>()
context.applicationInfo.run {
sourceDir?.let { apks += it }
splitSourceDirs?.let { apks += it }
}
for (abi in Build.SUPPORTED_ABIS) {
for (apk in apks) {
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
val mappedName = System.mapLibraryName(libraryName)
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
val zipEntry = zipFile.getEntry(libraryZipPath)
zipEntry?.let {
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
FileOutputStream(destination).use { outStream ->
zipFile.getInputStream(zipEntry).use { inStream ->
inStream.copyTo(outStream, 32 * 1024)
outStream.fd.sync()
}
}
}
return true
}
}
}
return false
}
@SuppressLint("UnsafeDynamicallyLoadedCode")
fun loadSharedLibrary(context: Context, libraryName: String) {
Log.d(TAG, "Loading library: $libraryName")
try {
System.loadLibrary(libraryName)
return
} catch (_: UnsatisfiedLinkError) {
Log.d(TAG, "Failed to load library, try to extract it from apk")
}
var tempFile: File? = null
try {
tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
if (extractLibrary(context, libraryName, tempFile)) {
System.load(tempFile.absolutePath)
return
}
} catch (e: Exception) {
throw LoadLibraryException("Failed to load library apk: $libraryName", e)
} finally {
tempFile?.delete()
}
}
}
}
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)

View File

@@ -12,7 +12,8 @@ open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>,
val dnsServers: Set<InetAddress>,
val searchDomain: String?,
val routes: Set<Route>,
val routes: Set<InetNetwork>,
val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>,
val includedApplications: Set<String>,
@@ -28,6 +29,7 @@ open class ProtocolConfig protected constructor(
builder.dnsServers,
builder.searchDomain,
builder.routes,
builder.excludedRoutes,
builder.includedAddresses,
builder.excludedAddresses,
builder.includedApplications,
@@ -41,7 +43,8 @@ open class ProtocolConfig protected constructor(
open class Builder(blockingMode: Boolean) {
internal val addresses: MutableSet<InetNetwork> = hashSetOf()
internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
internal val routes: MutableSet<Route> = mutableSetOf()
internal val routes: MutableSet<InetNetwork> = hashSetOf()
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val includedApplications: MutableSet<String> = hashSetOf()
@@ -74,21 +77,13 @@ open class ProtocolConfig protected constructor(
fun setSearchDomain(domain: String) = apply { this.searchDomain = domain }
fun addRoute(route: InetNetwork) = apply { this.routes += Route(route, true) }
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, true) } }
fun excludeRoute(route: InetNetwork) = apply { this.routes += Route(route, false) }
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, false) } }
fun removeRoute(route: InetNetwork) = apply { this.routes.removeIf { it.inetNetwork == route } }
fun addRoute(route: InetNetwork) = apply { this.routes += route }
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes }
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
fun clearRoutes() = apply { this.routes.clear() }
fun prependRoutes(block: Builder.() -> Unit) = apply {
val savedRoutes = mutableListOf<Route>().apply { addAll(routes) }
routes.clear()
block()
routes.addAll(savedRoutes)
}
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
@@ -122,46 +117,37 @@ open class ProtocolConfig protected constructor(
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
removeRoute(InetNetwork("2000::", 3))
prependRoutes {
addRoutes(includedAddresses)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, add the default route to the excluded routes
// to correctly build the excluded subnets list later
excludeRoute(InetNetwork("0.0.0.0", 0))
}
addRoutes(includedAddresses)
} else if (excludedAddresses.isNotEmpty()) {
prependRoutes {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// default routes are required for split tunneling in newer versions of Android
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("2000::", 3))
excludeRoutes(excludedAddresses)
addRoute(InetNetwork("::", 0))
}
excludeRoutes(excludedAddresses)
}
}
private fun processRoutes() {
// replace ::/0 as it may cause LAN connection issues
val ipv6DefaultRoute = InetNetwork("::", 0)
if (routes.removeIf { it.include && it.inetNetwork == ipv6DefaultRoute }) {
prependRoutes {
addRoute(InetNetwork("2000::", 3))
}
}
// for older versions of Android, build a list of subnets without excluded routes
// and add them to routes
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && routes.any { !it.include }) {
private fun processExcludedRoutes() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && excludedRoutes.isNotEmpty()) {
// todo: rewrite, taking into account the current routes
// for older versions of Android, build a list of subnets without excluded routes
// and add them to routes
val ipRangeSet = IpRangeSet()
routes.forEach {
if (it.include) ipRangeSet.add(IpRange(it.inetNetwork))
else ipRangeSet.remove(IpRange(it.inetNetwork))
}
ipRangeSet.remove(IpRange("127.0.0.0", 8))
ipRangeSet.remove(IpRange("::1", 128))
routes.clear()
excludedRoutes.forEach {
ipRangeSet.remove(IpRange(it))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
}
// filter ipv4 and ipv6 loopback addresses
val ipv6Loopback = InetNetwork("::1", 128)
routes.removeIf {
it.include &&
if (it.inetNetwork.isIpv4) it.inetNetwork.address.address[0] == 127.toByte()
else it.inetNetwork == ipv6Loopback
addRoute(InetNetwork("2000::", 3))
}
}
@@ -179,7 +165,7 @@ open class ProtocolConfig protected constructor(
protected fun configBuild() {
processSplitTunneling()
processRoutes()
processExcludedRoutes()
validate()
}
@@ -191,5 +177,3 @@ open class ProtocolConfig protected constructor(
Builder(blockingMode).apply(block).build()
}
}
data class Route(val inetNetwork: InetNetwork, val include: Boolean)

View File

@@ -21,5 +21,5 @@ android {
}
dependencies {
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar", "*.aar"))))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,28 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="disconnected">Не подключено</string>
<string name="connected">Подключено</string>
<string name="connecting">Подключение…</string>
<string name="disconnecting">Отключение…</string>
<string name="reconnecting">Переподключение…</string>
<string name="connect">Подключиться</string>
<string name="disconnect">Отключиться</string>
<string name="ok">ОК</string>
<string name="connecting">Подключение</string>
<string name="disconnecting">Отключение</string>
<string name="cancel">Отмена</string>
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="ok">ОК</string>
<string name="vpnGranted">VPN-подключение разрешено</string>
<string name="vpnDenied">VPN-подключение запрещено</string>
<string name="vpnSetupFailed">Ошибка настройки VPN</string>
<string name="vpnSetupFailedMessage">Чтобы подключиться к AmneziaVPN необходимо:\n\n- Разрешить приложению подключаться к сети VPN\n- Отключить функцию \"Постоянная VPN\" для всех остальных VPN-приложений в системных настройках VPN</string>
<string name="openVpnSettings">Открыть настройки VPN</string>
<string name="notificationChannelDescription">Уведомления сервиса AmneziaVPN</string>
<string name="notificationDialogTitle">Сервис AmneziaVPN</string>
<string name="notificationDialogMessage">Показывать статус VPN в строке состояния?</string>
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
</resources>

View File

@@ -3,6 +3,7 @@
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
</array>
<array name="qt_libs">

View File

@@ -1,28 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="disconnected">Not connected</string>
<string name="connected">Connected</string>
<string name="connecting">Connecting…</string>
<string name="disconnecting">Disconnecting…</string>
<string name="reconnecting">Reconnecting…</string>
<string name="connect">Connect</string>
<string name="disconnect">Disconnect</string>
<string name="ok">OK</string>
<string name="connecting">Connecting</string>
<string name="disconnecting">Disconnecting</string>
<string name="cancel">Cancel</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="ok">OK</string>
<string name="vpnGranted">VPN permission granted</string>
<string name="vpnDenied">VPN permission denied</string>
<string name="vpnSetupFailed">VPN setup error</string>
<string name="vpnSetupFailedMessage">To connect to AmneziaVPN, please do the following:\n\n- Allow the app to set up a VPN connection\n- Disable Always-on VPN for any other VPN app in the VPN system settings</string>
<string name="openVpnSettings">Open VPN settings</string>
<string name="notificationChannelDescription">AmneziaVPN service notification</string>
<string name="notificationDialogTitle">AmneziaVPN service</string>
<string name="notificationDialogMessage">Show the VPN state in the status bar?</string>
<string name="notificationSettingsDialogTitle">Notification settings</string>
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
<string name="openNotificationSettings">Open notification settings</string>
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
</resources>

View File

@@ -1,14 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF0E0E11</color>
<style name="NoActionBar">
<item name="android:windowBackground">@color/black</item>
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
</style>
<style name="Translucent" parent="NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>

View File

@@ -22,10 +22,11 @@ dependencyResolutionManagement {
includeBuild("./gradle/plugins")
plugins {
id("com.android.settings") version "8.5.2"
id("com.android.settings") version "8.2.0"
id("settings-property-delegate")
}
rootProject.name = "AmneziaVPN"
rootProject.buildFileName = "build.gradle.kts"
include(":qt")
@@ -34,8 +35,7 @@ include(":protocolApi")
include(":wireguard")
include(":awg")
include(":openvpn")
include(":xray")
include(":xray:libXray")
include(":cloak")
// get values from gradle or local properties
val androidBuildToolsVersion: String by gradleProperties
@@ -46,7 +46,15 @@ val qtMinSdkVersion: String by gradleProperties
// set default values for all modules
configure<SettingsExtension> {
buildToolsVersion = androidBuildToolsVersion
compileSdk = androidCompileSdkVersion.split('-')[1].toInt()
compileSdk = androidCompileSdkVersion.substringAfter('-').toInt()
minSdk = qtMinSdkVersion.toInt()
ndkVersion = androidNdkVersion
}
// stop Gradle running by androiddeployqt
gradle.taskGraph.whenReady {
if (providers.environmentVariable("ANDROIDDEPLOYQT_RUN").isPresent
&& !providers.systemProperty("explicitRun").isPresent) {
allTasks.forEach { it.enabled = false }
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More