Merge branch 'dev' into feature/xray-user-management
49
.github/workflows/deploy.yml
vendored
|
|
@ -10,12 +10,13 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-Linux-Ubuntu:
|
Build-Linux-Ubuntu:
|
||||||
name: 'Build-Linux-Ubuntu'
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
|
|
@ -75,13 +76,14 @@ jobs:
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-Windows:
|
Build-Windows:
|
||||||
name: Build-Windows
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
BUILD_ARCH: 64
|
BUILD_ARCH: 64
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
|
|
@ -137,13 +139,14 @@ jobs:
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-iOS:
|
Build-iOS:
|
||||||
name: 'Build-iOS'
|
|
||||||
runs-on: macos-13
|
runs-on: macos-13
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
CC: cc
|
CC: cc
|
||||||
CXX: c++
|
CXX: c++
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
|
@ -178,7 +181,7 @@ jobs:
|
||||||
- name: 'Install go'
|
- name: 'Install go'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.22.1'
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: 'Setup gomobile'
|
- name: 'Setup gomobile'
|
||||||
|
|
@ -228,13 +231,14 @@ jobs:
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-MacOS:
|
Build-MacOS:
|
||||||
name: 'Build-MacOS'
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||||
QT_VERSION: 6.4.3
|
QT_VERSION: 6.4.3
|
||||||
QIF_VERSION: 4.6
|
QIF_VERSION: 4.6
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
|
@ -293,28 +297,29 @@ jobs:
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-Android:
|
Build-Android:
|
||||||
name: 'Build-Android'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.7.2
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'gcc_64'
|
arch: 'linux_gcc_64'
|
||||||
modules: ${{ env.QT_MODULES }}
|
modules: ${{ env.QT_MODULES }}
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_x86_64 Qt'
|
- name: 'Install android_x86_64 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -325,7 +330,7 @@ jobs:
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_x86 Qt'
|
- name: 'Install android_x86 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -336,7 +341,7 @@ jobs:
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_armv7 Qt'
|
- name: 'Install android_armv7 Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -347,7 +352,7 @@ jobs:
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install android_arm64_v8a Qt'
|
- name: 'Install android_arm64_v8a Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
|
|
@ -439,3 +444,21 @@ jobs:
|
||||||
path: deploy/build/AmneziaVPN-release.aab
|
path: deploy/build/AmneziaVPN-release.aab
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
Extra:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Search a corresponding PR
|
||||||
|
uses: octokit/request-action@v2.x
|
||||||
|
id: pull_request
|
||||||
|
with:
|
||||||
|
route: GET /repos/${{ github.repository }}/pulls
|
||||||
|
head: ${{ github.repository_owner }}:${{ github.ref_name }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Add PR link to build summary
|
||||||
|
if: ${{ fromJSON(steps.pull_request.outputs.data)[0].number != '' }}
|
||||||
|
run: |
|
||||||
|
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
|
||||||
2
.github/workflows/tag-deploy.yml
vendored
|
|
@ -15,6 +15,8 @@ jobs:
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.4.1
|
QT_VERSION: 6.4.1
|
||||||
QIF_VERSION: 4.5
|
QIF_VERSION: 4.5
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
|
|
@ -13,3 +13,6 @@
|
||||||
[submodule "client/3rd/amneziawg-apple"]
|
[submodule "client/3rd/amneziawg-apple"]
|
||||||
path = client/3rd/amneziawg-apple
|
path = client/3rd/amneziawg-apple
|
||||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||||
|
[submodule "client/3rd/QSimpleCrypto"]
|
||||||
|
path = client/3rd/QSimpleCrypto
|
||||||
|
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.6.1.0
|
project(${PROJECT} VERSION 4.8.1.9
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
|
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 56)
|
set(APP_ANDROID_VERSION_CODE 65)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
||||||
21
README.md
|
|
@ -10,10 +10,10 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_Linux_4.6.0.3.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_Linux_4.7.0.0.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
|
||||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.6.0.3"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
|
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.7.0.0"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|
@ -28,11 +28,12 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
- Very easy to use - enter your IP address, SSH login, password and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||||
- OpenVPN, Shadowsocks, WireGuard, and IKEv2 protocols support.
|
- Classic VPN-protocols: OpenVPN, WireGuard and IKEv2 protocols.
|
||||||
- Masking VPN with OpenVPN over Cloak plugin
|
- Protocols with traffic Masking (Obfuscation): OpenVPN over [Cloak](https://github.com/cbeuw/Cloak) plugin, Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||||
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
|
- Split tunneling support - add any sites to the client to enable VPN only for them or add Apps (only for Android and Desktop).
|
||||||
- Windows, MacOS, Linux, Android, iOS releases.
|
- Windows, MacOS, Linux, Android, iOS releases.
|
||||||
|
- Support for AmneziaWG protocol configuration on [Keenetic beta firmware](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
|
@ -41,7 +42,8 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||||
|
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
|
|
||||||
|
|
@ -181,6 +183,7 @@ GPL v3.0
|
||||||
|
|
||||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
|
|
||||||
|
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit c38a587fcda89bab4009560d36239fa8de74705e
|
Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61
|
||||||
2
client/3rd/OpenVPNAdapter
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit dea6040996298e947d63fb172709e6abfec2ba93
|
Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b
|
||||||
1
client/3rd/QSimpleCrypto
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c99b33f0e08b7206116ddff85c22d3b97ce1e79d
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QAead.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QBlockCipher.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QCryptoError.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QRsa.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QSimpleCrypto_global.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509Store.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QAead.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QBlockCipher.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QCryptoError.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QRsa.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509Store.cpp
|
|
||||||
)
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
INCLUDEPATH += $$PWD
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
$$PWD/include/QAead.h \
|
|
||||||
$$PWD/include/QBlockCipher.h \
|
|
||||||
$$PWD/include/QCryptoError.h \
|
|
||||||
$$PWD/include/QRsa.h \
|
|
||||||
$$PWD/include/QSimpleCrypto_global.h \
|
|
||||||
$$PWD/include/QX509.h \
|
|
||||||
$$PWD/include/QX509Store.h
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
$$PWD/sources/QAead.cpp \
|
|
||||||
$$PWD/sources/QBlockCipher.cpp \
|
|
||||||
$$PWD/sources/QCryptoError.cpp \
|
|
||||||
$$PWD/sources/QRsa.cpp \
|
|
||||||
$$PWD/sources/QX509.cpp \
|
|
||||||
$$PWD/sources/QX509Store.cpp
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QAEAD_H
|
|
||||||
#define QAEAD_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/aes.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QAead {
|
|
||||||
public:
|
|
||||||
QAead();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encryptAesGcm - Function encrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decryptAesGcm - Function decrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted
|
|
||||||
/// \param key - AES key
|
|
||||||
/// \param iv - Initialization vector
|
|
||||||
/// \param tag - Authorization tag
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encryptAesCcm - Function encrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decryptAesCcm - Function decrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QAEAD_H
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QBLOCKCIPHER_H
|
|
||||||
#define QBLOCKCIPHER_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/aes.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QBlockCipher {
|
|
||||||
|
|
||||||
#define Aes128Rounds 10
|
|
||||||
#define Aes192Rounds 12
|
|
||||||
#define Aes256Rounds 14
|
|
||||||
|
|
||||||
public:
|
|
||||||
QBlockCipher();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief generateRandomBytes - Function generates random bytes by size.
|
|
||||||
/// \param size - Size of generated bytes.
|
|
||||||
/// \return Returns random bytes.
|
|
||||||
///
|
|
||||||
QByteArray generateRandomBytes(const int& size);
|
|
||||||
QByteArray generateSecureRandomBytes(const int& size);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Encryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
|
||||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Decryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
|
||||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QBLOCKCIPHER_H
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
#ifndef QCRYPTOERROR_H
|
|
||||||
#define QCRYPTOERROR_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
/// TODO: Add Special error code for each error.
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QCryptoError : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QCryptoError(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setError - Sets error information
|
|
||||||
/// \param errorCode - Error code.
|
|
||||||
/// \param errorSummary - Error summary.
|
|
||||||
///
|
|
||||||
inline void setError(const quint8 errorCode, const QString& errorSummary)
|
|
||||||
{
|
|
||||||
m_currentErrorCode = errorCode;
|
|
||||||
m_errorSummary = errorSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief lastError - Returns last error.
|
|
||||||
/// \return Returns eror ID and error Text.
|
|
||||||
///
|
|
||||||
inline QPair<quint8, QString> lastError() const
|
|
||||||
{
|
|
||||||
return QPair<quint8, QString>(m_currentErrorCode, m_errorSummary);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
quint8 m_currentErrorCode;
|
|
||||||
QString m_errorSummary;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // QCRYPTOERROR_H
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QRSA_H
|
|
||||||
#define QRSA_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QRsa {
|
|
||||||
|
|
||||||
#define PublicEncrypt 0
|
|
||||||
#define PrivateEncrypt 1
|
|
||||||
#define PublicDecrypt 2
|
|
||||||
#define PrivateDecrypt 3
|
|
||||||
|
|
||||||
public:
|
|
||||||
QRsa();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
|
||||||
/// \param bits - RSA key size.
|
|
||||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
|
||||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
RSA* generateRsaKeys(const int& bits, const int& rsaBigNumber);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief savePublicKey - Saves to file RSA public key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param publicKeyFileName - Public key file name.
|
|
||||||
///
|
|
||||||
void savePublicKey(RSA *rsa, const QByteArray& publicKeyFileName);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief savePrivateKey - Saves to file RSA private key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param privateKeyFileName - Private key file name.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
///
|
|
||||||
void savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password = "", const EVP_CIPHER* cipher = nullptr);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief getPublicKeyFromFile - Gets RSA public key from a file.
|
|
||||||
/// \param filePath - File path to public key file.
|
|
||||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* getPublicKeyFromFile(const QByteArray& filePath);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief getPrivateKeyFromFile - Gets RSA private key from a file.
|
|
||||||
/// \param filePath - File path to private key file.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password = "");
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief encrypt - Encrypt data with RSA algorithm.
|
|
||||||
/// \param plaintext - Text that must be encrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
|
||||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray encrypt(QByteArray plainText, RSA* rsa, const int& encryptType = PublicEncrypt, const int& padding = RSA_PKCS1_PADDING);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief decrypt - Decrypt data with RSA algorithm.
|
|
||||||
/// \param cipherText - Text that must be decrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
|
||||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return - Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType = PrivateDecrypt, const int& padding = RSA_PKCS1_PADDING);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QRSA_H
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#ifndef QSIMPLECRYPTO_GLOBAL_H
|
|
||||||
#define QSIMPLECRYPTO_GLOBAL_H
|
|
||||||
|
|
||||||
#include <QtCore/qglobal.h>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#define QSIMPLECRYPTO_EXPORT
|
|
||||||
|
|
||||||
#endif // QSIMPLECRYPTO_GLOBAL_H
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QX509_H
|
|
||||||
#define QX509_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QMap>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/x509.h>
|
|
||||||
#include <openssl/x509_vfy.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QX509 {
|
|
||||||
|
|
||||||
#define oneYear 31536000L
|
|
||||||
#define x509LastVersion 2
|
|
||||||
|
|
||||||
public:
|
|
||||||
QX509();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
|
||||||
/// \param fileName - File path to certificate.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* loadCertificateFromFile(const QByteArray& fileName);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
|
||||||
/// \param endCertificate - Certificate that will be signed
|
|
||||||
/// \param caCertificate - CA certificate that will sign end certificate
|
|
||||||
/// \param caPrivateKey - CA certificate private key
|
|
||||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
|
||||||
///
|
|
||||||
X509* signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName = "");
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
|
||||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
|
||||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
|
||||||
///
|
|
||||||
X509* verifyCertificate(X509* x509, X509_STORE* store);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
|
||||||
/// \param rsa - OpenSSL RSA.
|
|
||||||
/// \param additionalData - Certificate information.
|
|
||||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
|
||||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
|
||||||
/// \param serialNumber - X509 certificate serial number.
|
|
||||||
/// \param version - X509 certificate version.
|
|
||||||
/// \param notBefore - X509 start date.
|
|
||||||
/// \param notAfter - X509 end date.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
|
||||||
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
|
||||||
const long& serialNumber = 1, const long& version = x509LastVersion,
|
|
||||||
const long& notBefore = 0, const long& notAfter = oneYear);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
} // namespace QSimpleCrypto
|
|
||||||
|
|
||||||
#endif // QX509_H
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifndef QX509STORE_H
|
|
||||||
#define QX509STORE_H
|
|
||||||
|
|
||||||
#include "QSimpleCrypto_global.h"
|
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/x509_vfy.h>
|
|
||||||
#include <openssl/x509v3.h>
|
|
||||||
|
|
||||||
#include "QCryptoError.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
namespace QSimpleCrypto
|
|
||||||
{
|
|
||||||
class QSIMPLECRYPTO_EXPORT QX509Store {
|
|
||||||
public:
|
|
||||||
QX509Store();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief addCertificateToStore
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param x509 - OpenSSL X509.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool addCertificateToStore(X509_STORE* store, X509* x509);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief addLookup
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setCertificateDepth
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setDepth(X509_STORE* store, const int& depth);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setFlag(X509_STORE* store, const unsigned long& flag);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setPurpose(X509_STORE* store, const int& purpose);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setTrust
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setTrust(X509_STORE* store, const int& trust);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief setDefaultPaths
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool setDefaultPaths(X509_STORE* store);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
|
||||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param file - Qt QFile that will be loaded.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool loadLocations(X509_STORE* store, const QFile& file);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileInfo - Qt QFileInfo.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool loadLocations(X509_STORE* store, const QFileInfo& fileInfo);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief error - Error handler class.
|
|
||||||
///
|
|
||||||
QCryptoError error;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // QX509STORE_H
|
|
||||||
|
|
@ -1,364 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QAead.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QAead::QAead()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::encryptAesGcm - Function encrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (encryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int plainTextLength = data.size();
|
|
||||||
int cipherTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize cipherText. Here encrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
|
||||||
if (cipherText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize encryption operation. */
|
|
||||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Check if aad need to be used */
|
|
||||||
// if (aad.length() > 0) {
|
|
||||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
// if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
// throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
|
||||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the encryption. Normally cipher text bytes may be written at
|
|
||||||
* this stage, but this does not occur in GCM mode
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Get tag */
|
|
||||||
// if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
// throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::decryptAesGcm - Function decrypts data with Gcm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted
|
|
||||||
/// \param key - AES key
|
|
||||||
/// \param iv - Initialization vector
|
|
||||||
/// \param tag - Authorization tag
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (decryptionCipher.get() == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int cipherTextLength = data.size();
|
|
||||||
int plainTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize plainText. Here decrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize decryption operation. */
|
|
||||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Check if aad need to be used */
|
|
||||||
// if (aad.length() > 0) {
|
|
||||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
// if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
// throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be decrypted, and obtain the plain text output.
|
|
||||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
|
||||||
// if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
// throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the decryption. A positive return value indicates success,
|
|
||||||
* anything else is a failure - the plain text is not trustworthy.
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::encryptAesCcm - Function encrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (encryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int plainTextLength = data.size();
|
|
||||||
int cipherTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize cipherText. Here encrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
|
||||||
if (cipherText.get() == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize encryption operation. */
|
|
||||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set tag length */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if aad need to be used */
|
|
||||||
if (aad.length() > 0) {
|
|
||||||
/* Provide the total plain text length */
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, nullptr, plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
|
||||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the encryption. Normally ciphertext bytes may be written at
|
|
||||||
* this stage, but this does not occur in GCM mode
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get tag */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QAEAD::decryptAesCcm - Function decrypts data with Ccm algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param tag - Authorization tag.
|
|
||||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
|
||||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QAead::decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (decryptionCipher.get() == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int cipherTextLength = data.size();
|
|
||||||
int plainTextLength = 0;
|
|
||||||
|
|
||||||
/* Initialize plainText. Here decrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize decryption operation. */
|
|
||||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
|
||||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
|
||||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
|
||||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if aad need to be used */
|
|
||||||
if (aad.length() > 0) {
|
|
||||||
/* Provide the total ciphertext length */
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, nullptr, cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Provide any AAD data. This can be called zero or more times as required */
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
|
||||||
throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
|
||||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the decryption. A positive return value indicates success,
|
|
||||||
* anything else is a failure - the plaintext is not trustworthy.
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QBlockCipher.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher::QBlockCipher()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QBlockCipher::generateRandomBytes - Function generates random bytes by size.
|
|
||||||
/// \param size - Size of generated bytes.
|
|
||||||
/// \return Returns random bytes.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::generateRandomBytes(const int& size)
|
|
||||||
{
|
|
||||||
unsigned char arr[sizeof(size)];
|
|
||||||
RAND_bytes(arr, sizeof(size));
|
|
||||||
|
|
||||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::generateSecureRandomBytes(const int &size)
|
|
||||||
{
|
|
||||||
unsigned char arr[sizeof(size)];
|
|
||||||
RAND_priv_bytes(arr, sizeof(size));
|
|
||||||
|
|
||||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be encrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Encryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::encryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv,
|
|
||||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (encryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reinterpret values for multi use */
|
|
||||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
|
||||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int cipherTextLength(data.size() + AES_BLOCK_SIZE);
|
|
||||||
int finalLength = 0;
|
|
||||||
|
|
||||||
/* Initialize cipcherText. Here encrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[cipherTextLength]() };
|
|
||||||
if (cipherText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug here
|
|
||||||
// /* Start encryption with password based encryption routine */
|
|
||||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<unsigned char*>(salt.data()), reinterpret_cast<unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
|
||||||
// throw std::runtime_error("Couldn't start encryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Initialize encryption operation. */
|
|
||||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
|
||||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
|
||||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finalize the encryption. Normally ciphertext bytes may be written at this stage */
|
|
||||||
if (!EVP_EncryptFinal(encryptionCipher.get(), cipherText.get() + cipherTextLength, &finalLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength + finalLength);
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
|
||||||
return QByteArray();
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
|
||||||
/// \param data - Data that will be decrypted.
|
|
||||||
/// \param key - AES key.
|
|
||||||
/// \param iv - Initialization vector.
|
|
||||||
/// \param password - Decryption password.
|
|
||||||
/// \param salt - Random delta.
|
|
||||||
/// \param rounds - Transformation rounds.
|
|
||||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
|
||||||
/// \return Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QBlockCipher::decryptAesBlockCipher(QByteArray data, QByteArray key,
|
|
||||||
QByteArray iv,
|
|
||||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize EVP_CIPHER_CTX */
|
|
||||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
|
||||||
if (decryptionCipher == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reinterpret values for multi use */
|
|
||||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
|
||||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
|
||||||
|
|
||||||
/* Set data length */
|
|
||||||
int plainTextLength(data.size());
|
|
||||||
int finalLength = 0;
|
|
||||||
|
|
||||||
/* Initialize plainText. Here decrypted data will be stored */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[plainTextLength + AES_BLOCK_SIZE]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for \'plainText\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug here
|
|
||||||
// /* Start encryption with password based encryption routine */
|
|
||||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<const unsigned char*>(salt.data()), reinterpret_cast<const unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
|
||||||
// throw std::runtime_error("Couldn't start decryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Initialize decryption operation. */
|
|
||||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
|
||||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
|
||||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
|
||||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finalize the decryption. A positive return value indicates success,
|
|
||||||
* anything else is a failure - the plaintext is not trustworthy.
|
|
||||||
*/
|
|
||||||
if (!EVP_DecryptFinal(decryptionCipher.get(), plainText.get() + plainTextLength, &finalLength)) {
|
|
||||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finilize data to be readable with qt */
|
|
||||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength + finalLength);
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
|
||||||
return QByteArray(exception.what());
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#include "include/QCryptoError.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QCryptoError::QCryptoError(QObject* parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,274 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QRsa.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QRsa::QRsa()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
|
||||||
/// \param bits - RSA key size.
|
|
||||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
|
||||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
RSA* QSimpleCrypto::QRsa::generateRsaKeys(const int& bits, const int& rsaBigNumber)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize big number */
|
|
||||||
std::unique_ptr<BIGNUM, void (*)(BIGNUM*)> bigNumber { BN_new(), BN_free };
|
|
||||||
if (bigNumber == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'bigNumber\'. BN_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set big number */
|
|
||||||
if (!BN_set_word(bigNumber.get(), rsaBigNumber)) {
|
|
||||||
throw std::runtime_error("Couldn't set bigNumber. BN_set_word(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize RSA */
|
|
||||||
RSA* rsa = nullptr;
|
|
||||||
if (!(rsa = RSA_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize x509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generate key pair and store it in RSA */
|
|
||||||
if (!RSA_generate_key_ex(rsa, bits, bigNumber.get(), nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't generate RSA. RSA_generate_key_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return rsa;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::savePublicKey - Saves to file RSA public key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param publicKeyFileName - Public key file name.
|
|
||||||
///
|
|
||||||
void QSimpleCrypto::QRsa::savePublicKey(RSA* rsa, const QByteArray& publicKeyFileName)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(publicKeyFileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (bioPublicKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize \'bioPublicKey\'. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write public key on file */
|
|
||||||
if (!PEM_write_bio_RSA_PUBKEY(bioPublicKey.get(), rsa)) {
|
|
||||||
throw std::runtime_error("Couldn't save public key. PEM_write_bio_RSAPublicKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::savePrivateKey - Saves to file RSA private key.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param privateKeyFileName - Private key file name.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
|
||||||
///
|
|
||||||
void QSimpleCrypto::QRsa::savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password, const EVP_CIPHER* cipher)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(privateKeyFileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (bioPrivateKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write private key to file */
|
|
||||||
if (!PEM_write_bio_RSAPrivateKey(bioPrivateKey.get(), rsa, cipher, reinterpret_cast<unsigned char*>(password.data()), password.size(), nullptr, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't save private key. PEM_write_bio_RSAPrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::getPublicKeyFromFile - Gets RSA public key from a file.
|
|
||||||
/// \param filePath - File path to public key file.
|
|
||||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* QSimpleCrypto::QRsa::getPublicKeyFromFile(const QByteArray& filePath)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
|
||||||
if (bioPublicKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize bioPublicKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize EVP_PKEY */
|
|
||||||
EVP_PKEY* keyStore = nullptr;
|
|
||||||
if (!(keyStore = EVP_PKEY_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write private key to file */
|
|
||||||
if (!PEM_read_bio_PUBKEY(bioPublicKey.get(), &keyStore, nullptr, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyStore;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::getPrivateKeyFromFile - Gets RSA private key from a file.
|
|
||||||
/// \param filePath - File path to private key file.
|
|
||||||
/// \param password - Private key password.
|
|
||||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
|
||||||
///
|
|
||||||
EVP_PKEY* QSimpleCrypto::QRsa::getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
|
||||||
if (bioPrivateKey == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize EVP_PKEY */
|
|
||||||
EVP_PKEY* keyStore = nullptr;
|
|
||||||
if (!(keyStore = EVP_PKEY_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write private key to file */
|
|
||||||
if (!PEM_read_bio_PrivateKey(bioPrivateKey.get(), &keyStore, nullptr, (void*)password.data())) {
|
|
||||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyStore;
|
|
||||||
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::encrypt - Encrypt data with RSA algorithm.
|
|
||||||
/// \param plaintext - Text that must be encrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
|
||||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return Returns encrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QRsa::encrypt(QByteArray plainText, RSA* rsa, const int& encryptType, const int& padding)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize array. Here encrypted data will be saved */
|
|
||||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[RSA_size(rsa)]() };
|
|
||||||
if (cipherText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Result of encryption operation */
|
|
||||||
short int result = 0;
|
|
||||||
|
|
||||||
/* Execute encryption operation */
|
|
||||||
if (encryptType == PublicDecrypt) {
|
|
||||||
result = RSA_public_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
|
||||||
} else if (encryptType == PrivateDecrypt) {
|
|
||||||
result = RSA_private_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for result */
|
|
||||||
if (result <= -1) {
|
|
||||||
throw std::runtime_error("Couldn't encrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get encrypted data */
|
|
||||||
const QByteArray& encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), RSA_size(rsa));
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return "";
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QRSA::decrypt - Decrypt data with RSA algorithm.
|
|
||||||
/// \param cipherText - Text that must be decrypted.
|
|
||||||
/// \param rsa - OpenSSL RSA structure.
|
|
||||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
|
||||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
|
||||||
/// \return - Returns decrypted data or "", if error happened.
|
|
||||||
///
|
|
||||||
QByteArray QSimpleCrypto::QRsa::decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType, const int& padding)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize array. Here decrypted data will be saved */
|
|
||||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherText.size()]() };
|
|
||||||
if (plainText == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't allocate memory for 'plainText'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Result of decryption operation */
|
|
||||||
short int result = 0;
|
|
||||||
|
|
||||||
/* Execute decryption operation */
|
|
||||||
if (decryptType == PublicDecrypt) {
|
|
||||||
result = RSA_public_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
|
||||||
} else if (decryptType == PrivateDecrypt) {
|
|
||||||
result = RSA_private_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for result */
|
|
||||||
if (result <= -1) {
|
|
||||||
throw std::runtime_error("Couldn't decrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get decrypted data */
|
|
||||||
const QByteArray& decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()));
|
|
||||||
|
|
||||||
return decryptedData;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
|
||||||
return "";
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QX509.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QX509::QX509()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
|
||||||
/// \param fileName - File path to certificate.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::loadCertificateFromFile(const QByteArray& fileName)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize X509 */
|
|
||||||
X509* x509 = nullptr;
|
|
||||||
if (!(x509 = X509_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "r+"), BIO_free_all };
|
|
||||||
if (certFile == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read file */
|
|
||||||
if (!PEM_read_bio_X509(certFile.get(), &x509, nullptr, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't read certificate file from disk. PEM_read_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
|
||||||
/// \param endCertificate - Certificate that will be signed
|
|
||||||
/// \param caCertificate - CA certificate that will sign end certificate
|
|
||||||
/// \param caPrivateKey - CA certificate private key
|
|
||||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Set issuer to CA's subject. */
|
|
||||||
if (!X509_set_issuer_name(endCertificate, X509_get_subject_name(caCertificate))) {
|
|
||||||
throw std::runtime_error("Couldn't set issuer name for X509. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sign the certificate with key. */
|
|
||||||
if (!X509_sign(endCertificate, caPrivateKey, EVP_sha256())) {
|
|
||||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write certificate file on disk. If needed */
|
|
||||||
if (!fileName.isEmpty()) {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (certFile == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write file on disk */
|
|
||||||
if (!PEM_write_bio_X509(certFile.get(), endCertificate)) {
|
|
||||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return endCertificate;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
|
||||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
|
||||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize X509_STORE_CTX */
|
|
||||||
std::unique_ptr<X509_STORE_CTX, void (*)(X509_STORE_CTX*)> ctx { X509_STORE_CTX_new(), X509_STORE_CTX_free };
|
|
||||||
if (ctx == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set up CTX for a subsequent verification operation */
|
|
||||||
if (!X509_STORE_CTX_init(ctx.get(), store, x509, nullptr)) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509_STORE_CTX. X509_STORE_CTX_init(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify X509 */
|
|
||||||
if (!X509_verify_cert(ctx.get())) {
|
|
||||||
throw std::runtime_error("Couldn't verify cert. X509_verify_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
|
||||||
/// \param rsa - OpenSSL RSA.
|
|
||||||
/// \param additionalData - Certificate information.
|
|
||||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
|
||||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
|
||||||
/// \param serialNumber - X509 certificate serial number.
|
|
||||||
/// \param version - X509 certificate version.
|
|
||||||
/// \param notBefore - X509 start date.
|
|
||||||
/// \param notAfter - X509 end date.
|
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
|
||||||
///
|
|
||||||
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
|
||||||
const QByteArray& certificateFileName, const EVP_MD* md,
|
|
||||||
const long& serialNumber, const long& version,
|
|
||||||
const long& notBefore, const long& notAfter)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* Initialize X509 */
|
|
||||||
X509* x509 = nullptr;
|
|
||||||
if (!(x509 = X509_new())) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize EVP_PKEY */
|
|
||||||
std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> keyStore { EVP_PKEY_new(), EVP_PKEY_free };
|
|
||||||
if (keyStore == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sign rsa key */
|
|
||||||
if (!EVP_PKEY_assign_RSA(keyStore.get(), rsa)) {
|
|
||||||
throw std::runtime_error("Couldn't assign rsa. EVP_PKEY_assign_RSA(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate serial number. */
|
|
||||||
if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)) {
|
|
||||||
throw std::runtime_error("Couldn't set serial number. ASN1_INTEGER_set(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate version */
|
|
||||||
if (!X509_set_version(x509, version)) {
|
|
||||||
throw std::runtime_error("Couldn't set version. X509_set_version(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate creation and expiration date */
|
|
||||||
X509_gmtime_adj(X509_get_notBefore(x509), notBefore);
|
|
||||||
X509_gmtime_adj(X509_get_notAfter(x509), notAfter);
|
|
||||||
|
|
||||||
/* Set certificate public key */
|
|
||||||
if (!X509_set_pubkey(x509, keyStore.get())) {
|
|
||||||
throw std::runtime_error("Couldn't set public key. X509_set_pubkey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize X509_NAME */
|
|
||||||
X509_NAME* x509Name = X509_get_subject_name(x509);
|
|
||||||
if (x509Name == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize X509_NAME. X509_NAME(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add additional data to certificate */
|
|
||||||
QMapIterator<QByteArray, QByteArray> certificateInformationList(additionalData);
|
|
||||||
while (certificateInformationList.hasNext()) {
|
|
||||||
/* Read next item in list */
|
|
||||||
certificateInformationList.next();
|
|
||||||
|
|
||||||
/* Set additional data */
|
|
||||||
if (!X509_NAME_add_entry_by_txt(x509Name, certificateInformationList.key().data(), MBSTRING_UTF8, reinterpret_cast<const unsigned char*>(certificateInformationList.value().data()), -1, -1, 0)) {
|
|
||||||
throw std::runtime_error("Couldn't set additional information. X509_NAME_add_entry_by_txt(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate info */
|
|
||||||
if (!X509_set_issuer_name(x509, x509Name)) {
|
|
||||||
throw std::runtime_error("Couldn't set issuer name. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sign certificate */
|
|
||||||
if (!X509_sign(x509, keyStore.get(), md)) {
|
|
||||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write certificate file on disk. If needed */
|
|
||||||
if (!certificateFileName.isEmpty()) {
|
|
||||||
/* Initialize BIO */
|
|
||||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(certificateFileName.data(), "w+"), BIO_free_all };
|
|
||||||
if (certFile == nullptr) {
|
|
||||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write file on disk */
|
|
||||||
if (!PEM_write_bio_X509(certFile.get(), x509)) {
|
|
||||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509;
|
|
||||||
} catch (std::exception& exception) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
|
||||||
return nullptr;
|
|
||||||
} catch (...) {
|
|
||||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
||||||
* this file except in compliance with the License. You can obtain a copy
|
|
||||||
* in the file LICENSE in the source distribution
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include "include/QX509Store.h"
|
|
||||||
|
|
||||||
QSimpleCrypto::QX509Store::QX509Store()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509::addCertificateToStore
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param x509 - OpenSSL X509.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::addCertificateToStore(X509_STORE* store, X509* x509)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_add_cert(store, x509)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add certificate to X509_STORE. X509_STORE_add_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::addLookup
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_add_lookup(store, method)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add lookup to X509_STORE. X509_STORE_add_lookup(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setCertificateDepth
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setDepth(X509_STORE* store, const int& depth)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_depth(store, depth)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set depth for X509_STORE. X509_STORE_set_depth(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setFlag(X509_STORE* store, const unsigned long& flag)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_flags(store, flag)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set flag for X509_STORE. X509_STORE_set_flags(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setPurpose(X509_STORE* store, const int& purpose)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_purpose(store, purpose)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set purpose for X509_STORE. X509_STORE_set_purpose(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setTrust
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setTrust(X509_STORE* store, const int& trust)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_trust(store, trust)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set trust for X509_STORE. X509_STORE_set_trust(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::setDefaultPaths
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::setDefaultPaths(X509_STORE* store)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_set_default_paths(store)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set default paths for X509_STORE. X509_STORE_set_default_paths(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
|
||||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_load_locations(store, fileName, dirPath)) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param file - Qt QFile that will be loaded.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFile& file)
|
|
||||||
{
|
|
||||||
/* Initialize QFileInfo to read information about file */
|
|
||||||
QFileInfo info(file);
|
|
||||||
|
|
||||||
if (!X509_STORE_load_locations(store, info.fileName().toLocal8Bit(), info.absoluteDir().path().toLocal8Bit())) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
|
||||||
/// \param store - OpenSSL X509_STORE.
|
|
||||||
/// \param fileInfo - Qt QFileInfo.
|
|
||||||
/// \return Returns 'true' on success and 'false', if error happened.
|
|
||||||
///
|
|
||||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFileInfo& fileInfo)
|
|
||||||
{
|
|
||||||
if (!X509_STORE_load_locations(store, fileInfo.fileName().toLocal8Bit(), fileInfo.absoluteDir().path().toLocal8Bit())) {
|
|
||||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS
|
|
||||||
Core Network
|
|
||||||
)
|
|
||||||
set(LIBS ${LIBS} Qt6::Core Qt6::Network)
|
|
||||||
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
if(MSVC)
|
|
||||||
set(LIBS ${LIBS} Advapi32.lib)
|
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
|
||||||
set(LIBS ${LIBS} advapi32)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
@ -1,274 +0,0 @@
|
||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
#include <QtCore/QElapsedTimer>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QSharedMemory>
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
|
||||||
* if another instance already exists
|
|
||||||
* @param argc
|
|
||||||
* @param argv
|
|
||||||
* @param allowSecondary Whether to enable secondary instance support
|
|
||||||
* @param options Optional flags to toggle specific behaviour
|
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
|
||||||
*/
|
|
||||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
|
|
||||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
// On Android and iOS since the library is not supported fallback to
|
|
||||||
// standard QApplication behaviour by simply returning at this point.
|
|
||||||
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Store the current mode of the program
|
|
||||||
d->options = options;
|
|
||||||
|
|
||||||
// Add any unique user data
|
|
||||||
if ( ! userData.isEmpty() )
|
|
||||||
d->addAppData( userData );
|
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory
|
|
||||||
// block and QLocalServer
|
|
||||||
d->genBlockServerName();
|
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes
|
|
||||||
// attempting to attach at the same time
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
// By explicitly attaching it and then deleting it we make sure that the
|
|
||||||
// memory is deleted even after the process has crashed on Unix.
|
|
||||||
d->memory = new QSharedMemory( d->blockServerName );
|
|
||||||
d->memory->attach();
|
|
||||||
delete d->memory;
|
|
||||||
#endif
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
d->memory = new QSharedMemory( d->blockServerName );
|
|
||||||
|
|
||||||
// Create a shared memory block
|
|
||||||
if( d->memory->create( sizeof( InstancesInfo ) )){
|
|
||||||
// Initialize the shared memory block
|
|
||||||
if( ! d->memory->lock() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
} else {
|
|
||||||
if( d->memory->error() == QSharedMemory::AlreadyExists ){
|
|
||||||
// Attempt to attach to the memory segment
|
|
||||||
if( ! d->memory->attach() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if( ! d->memory->lock() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCritical() << "SingleApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
|
||||||
while( true ){
|
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if( d->blockChecksum() == inst->checksum ) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and
|
|
||||||
// assume it's position
|
|
||||||
if( time.elapsed() > 5000 ){
|
|
||||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again. The random sleep here
|
|
||||||
// limits the probability of a collision between two racing apps and
|
|
||||||
// allows the app to initialise faster
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
if( ! d->memory->lock() ){
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( inst->primary == false ){
|
|
||||||
d->startPrimary();
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if another instance can be started
|
|
||||||
if( allowSecondary ){
|
|
||||||
d->startSecondary();
|
|
||||||
if( d->options & Mode::SecondaryNotification ){
|
|
||||||
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
|
||||||
}
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ! d->memory->unlock() ){
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
|
||||||
qDebug() << d->memory->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit( EXIT_SUCCESS );
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleApplication::~SingleApplication()
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is primary.
|
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isPrimary() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->server != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is secondary.
|
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isSecondary() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->server == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to identify an instance by returning unique consecutive instance
|
|
||||||
* ids. It is reset when the first (primary) instance of your app starts and
|
|
||||||
* only incremented afterwards.
|
|
||||||
* @return Returns a unique instance id.
|
|
||||||
*/
|
|
||||||
quint32 SingleApplication::instanceId() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->instanceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the OS PID (Process Identifier) of the process running the primary
|
|
||||||
* instance. Especially useful when SingleApplication is coupled with OS.
|
|
||||||
* specific APIs.
|
|
||||||
* @return Returns the primary instance PID.
|
|
||||||
*/
|
|
||||||
qint64 SingleApplication::primaryPid() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->primaryPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the primary instance is running as.
|
|
||||||
* @return Returns the username the primary instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::primaryUser() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->primaryUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the current instance is running as.
|
|
||||||
* @return Returns the username the current instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::currentUser() const
|
|
||||||
{
|
|
||||||
return SingleApplicationPrivate::getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to the Primary Instance.
|
|
||||||
* @param message The message to send.
|
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
|
||||||
* @return true if the message was sent successfuly, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
|
|
||||||
// Nobody to connect to
|
|
||||||
if( isPrimary() ) return false;
|
|
||||||
|
|
||||||
// Make sure the socket is connected
|
|
||||||
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
d->socket->write( message );
|
|
||||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
|
||||||
d->socket->flush();
|
|
||||||
return dataWritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleApplication::abortSafely()
|
|
||||||
{
|
|
||||||
Q_D( SingleApplication );
|
|
||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
|
|
||||||
delete d;
|
|
||||||
::exit( EXIT_FAILURE );
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SingleApplication::userData() const
|
|
||||||
{
|
|
||||||
Q_D( const SingleApplication );
|
|
||||||
return d->appData();
|
|
||||||
}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef SINGLE_APPLICATION_H
|
|
||||||
#define SINGLE_APPLICATION_H
|
|
||||||
|
|
||||||
#include <QtCore/QtGlobal>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
|
|
||||||
#ifndef QAPPLICATION_CLASS
|
|
||||||
#define QAPPLICATION_CLASS QApplication
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
|
||||||
|
|
||||||
class SingleApplicationPrivate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The SingleApplication class handles multiple instances of the same
|
|
||||||
* Application
|
|
||||||
* @see QCoreApplication
|
|
||||||
*/
|
|
||||||
class SingleApplication : public QAPPLICATION_CLASS
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using app_t = QAPPLICATION_CLASS;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Mode of operation of SingleApplication.
|
|
||||||
* Whether the block should be user-wide or system-wide and whether the
|
|
||||||
* primary instance should be notified when a secondary instance had been
|
|
||||||
* started.
|
|
||||||
* @note Operating system can restrict the shared memory blocks to the same
|
|
||||||
* user, in which case the User/System modes will have no effect and the
|
|
||||||
* block will be user wide.
|
|
||||||
* @enum
|
|
||||||
*/
|
|
||||||
enum Mode {
|
|
||||||
User = 1 << 0,
|
|
||||||
System = 1 << 1,
|
|
||||||
SecondaryNotification = 1 << 2,
|
|
||||||
ExcludeAppVersion = 1 << 3,
|
|
||||||
ExcludeAppPath = 1 << 4
|
|
||||||
};
|
|
||||||
Q_DECLARE_FLAGS(Options, Mode)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Intitializes a SingleApplication instance with argc command line
|
|
||||||
* arguments in argv
|
|
||||||
* @arg {int &} argc - Number of arguments in argv
|
|
||||||
* @arg {const char *[]} argv - Supplied command line arguments
|
|
||||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
|
||||||
* if there is already a primary instance.
|
|
||||||
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
|
||||||
* User wide or System wide.
|
|
||||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
|
||||||
* @note argc and argv may be changed as Qt removes arguments that it
|
|
||||||
* recognizes
|
|
||||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
|
||||||
* instance and the secondary instance.
|
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
|
||||||
* operations. It does not guarantee that the SingleApplication
|
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
|
||||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
|
||||||
*/
|
|
||||||
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
|
|
||||||
~SingleApplication() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is the primary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isPrimary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is a secondary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isSecondary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a unique identifier for the current instance
|
|
||||||
* @returns {qint32}
|
|
||||||
*/
|
|
||||||
quint32 instanceId() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
|
||||||
* @returns {qint64}
|
|
||||||
*/
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the user running the primary instance
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString primaryUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the current user
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString currentUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
|
||||||
* @param {int} timeout - Timeout for connecting
|
|
||||||
* @returns {bool}
|
|
||||||
* @note sendMessage() will return false if invoked from the primary
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
bool sendMessage( const QByteArray &message, int timeout = 100 );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the set user data.
|
|
||||||
* @returns {QStringList}
|
|
||||||
*/
|
|
||||||
QStringList userData() const;
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void instanceStarted();
|
|
||||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
|
||||||
|
|
||||||
private:
|
|
||||||
SingleApplicationPrivate *d_ptr;
|
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
|
||||||
void abortSafely();
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
|
||||||
|
|
||||||
#endif // SINGLE_APPLICATION_H
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
QT += core network
|
|
||||||
CONFIG += c++11
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
$$PWD/singleapplication.h \
|
|
||||||
$$PWD/singleapplication_p.h
|
|
||||||
SOURCES += $$PWD/singleapplication.cpp \
|
|
||||||
$$PWD/singleapplication_p.cpp
|
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD
|
|
||||||
|
|
||||||
win32 {
|
|
||||||
msvc:LIBS += Advapi32.lib
|
|
||||||
gcc:LIBS += -ladvapi32
|
|
||||||
}
|
|
||||||
|
|
@ -1,486 +0,0 @@
|
||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This file is not part of the SingleApplication API. It is used purely as an
|
|
||||||
// implementation detail. This header file may change from version to
|
|
||||||
// version without notice, or may even be removed.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include <QtCore/QDir>
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QDataStream>
|
|
||||||
#include <QtCore/QElapsedTimer>
|
|
||||||
#include <QtCore/QCryptographicHash>
|
|
||||||
#include <QtNetwork/QLocalServer>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
#include <QtCore/QRandomGenerator>
|
|
||||||
#else
|
|
||||||
#include <QtCore/QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX 1
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
|
||||||
: q_ptr( q_ptr )
|
|
||||||
{
|
|
||||||
server = nullptr;
|
|
||||||
socket = nullptr;
|
|
||||||
memory = nullptr;
|
|
||||||
instanceNumber = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
|
||||||
{
|
|
||||||
if( socket != nullptr ){
|
|
||||||
socket->close();
|
|
||||||
delete socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( memory != nullptr ){
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>(memory->data());
|
|
||||||
if( server != nullptr ){
|
|
||||||
server->close();
|
|
||||||
delete server;
|
|
||||||
inst->primary = false;
|
|
||||||
inst->primaryPid = -1;
|
|
||||||
inst->primaryUser[0] = '\0';
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
}
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
delete memory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleApplicationPrivate::getUsername()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if( GetUserNameW( username, &usernameLength ) )
|
|
||||||
return QString::fromWCharArray( username );
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
|
|
||||||
#else
|
|
||||||
return qEnvironmentVariable( "USERNAME" );
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
QString username;
|
|
||||||
uid_t uid = geteuid();
|
|
||||||
struct passwd *pw = getpwuid( uid );
|
|
||||||
if( pw )
|
|
||||||
username = QString::fromLocal8Bit( pw->pw_name );
|
|
||||||
if ( username.isEmpty() ){
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
|
|
||||||
#else
|
|
||||||
username = qEnvironmentVariable( "USER" );
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::genBlockServerName()
|
|
||||||
{
|
|
||||||
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
|
||||||
appData.addData( "SingleApplication", 17 );
|
|
||||||
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
|
||||||
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
|
||||||
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
|
||||||
|
|
||||||
if ( ! appDataList.isEmpty() )
|
|
||||||
appData.addData( appDataList.join( "" ).toUtf8() );
|
|
||||||
|
|
||||||
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
|
|
||||||
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
|
||||||
#else
|
|
||||||
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
|
||||||
if( options & SingleApplication::Mode::User ){
|
|
||||||
appData.addData( getUsername().toUtf8() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
|
||||||
// server naming requirements.
|
|
||||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() const
|
|
||||||
{
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
inst->primary = false;
|
|
||||||
inst->secondary = 0;
|
|
||||||
inst->primaryPid = -1;
|
|
||||||
inst->primaryUser[0] = '\0';
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary()
|
|
||||||
{
|
|
||||||
// Reset the number of connections
|
|
||||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
|
||||||
|
|
||||||
inst->primary = true;
|
|
||||||
inst->primaryPid = QCoreApplication::applicationPid();
|
|
||||||
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
instanceNumber = 0;
|
|
||||||
// Successful creation means that no main process exists
|
|
||||||
// So we start a QLocalServer to listen for connections
|
|
||||||
QLocalServer::removeServer( blockServerName );
|
|
||||||
server = new QLocalServer();
|
|
||||||
|
|
||||||
// Restrict access to the socket according to the
|
|
||||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if( options & SingleApplication::Mode::User ){
|
|
||||||
server->setSocketOptions( QLocalServer::UserAccessOption );
|
|
||||||
} else {
|
|
||||||
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
|
||||||
}
|
|
||||||
|
|
||||||
server->listen( blockServerName );
|
|
||||||
QObject::connect(
|
|
||||||
server,
|
|
||||||
&QLocalServer::newConnection,
|
|
||||||
this,
|
|
||||||
&SingleApplicationPrivate::slotConnectionEstablished
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary()
|
|
||||||
{
|
|
||||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
|
||||||
|
|
||||||
inst->secondary += 1;
|
|
||||||
inst->checksum = blockChecksum();
|
|
||||||
instanceNumber = inst->secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
|
||||||
{
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already
|
|
||||||
// connected.
|
|
||||||
if( socket == nullptr ){
|
|
||||||
socket = new QLocalSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( socket->state() == QLocalSocket::ConnectedState ) return true;
|
|
||||||
|
|
||||||
if( socket->state() != QLocalSocket::ConnectedState ){
|
|
||||||
|
|
||||||
while( true ){
|
|
||||||
randomSleep();
|
|
||||||
|
|
||||||
if( socket->state() != QLocalSocket::ConnectingState )
|
|
||||||
socket->connectToServer( blockServerName );
|
|
||||||
|
|
||||||
if( socket->state() == QLocalSocket::ConnectingState ){
|
|
||||||
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// If connected break out of the loop
|
|
||||||
if( socket->state() == QLocalSocket::ConnectedState ) break;
|
|
||||||
|
|
||||||
// If elapsed time since start is longer than the method timeout return
|
|
||||||
if( time.elapsed() >= msecs ) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisation message according to the SingleApplication protocol
|
|
||||||
QByteArray initMsg;
|
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
writeStream << blockServerName.toLatin1();
|
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber;
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
|
||||||
#endif
|
|
||||||
writeStream << checksum;
|
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
#endif
|
|
||||||
headerStream << static_cast <quint64>( initMsg.length() );
|
|
||||||
|
|
||||||
socket->write( header );
|
|
||||||
socket->write( initMsg );
|
|
||||||
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
|
|
||||||
socket->flush();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() const
|
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
|
|
||||||
#endif
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const
|
|
||||||
{
|
|
||||||
qint64 pid;
|
|
||||||
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleApplicationPrivate::primaryUser() const
|
|
||||||
{
|
|
||||||
QByteArray username;
|
|
||||||
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
username = inst->primaryUser;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return QString::fromUtf8( username );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Executed when a connection has been made to the LocalServer
|
|
||||||
*/
|
|
||||||
void SingleApplicationPrivate::slotConnectionEstablished()
|
|
||||||
{
|
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
|
||||||
[nextConnSocket, this](){
|
|
||||||
auto &info = connectionMap[nextConnSocket];
|
|
||||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
|
|
||||||
[nextConnSocket, this](){
|
|
||||||
connectionMap.remove(nextConnSocket);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
|
||||||
[nextConnSocket, this](){
|
|
||||||
auto &info = connectionMap[nextConnSocket];
|
|
||||||
switch(info.stage){
|
|
||||||
case StageHeader:
|
|
||||||
readInitMessageHeader(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageBody:
|
|
||||||
readInitMessageBody(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageConnected:
|
|
||||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
|
||||||
{
|
|
||||||
if (!connectionMap.contains( sock )){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream headerStream( sock );
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
headerStream.setVersion( QDataStream::Qt_5_6 );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
|
||||||
quint64 msgLen = 0;
|
|
||||||
headerStream >> msgLen;
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
|
||||||
info.stage = StageBody;
|
|
||||||
info.msgLen = msgLen;
|
|
||||||
|
|
||||||
if ( sock->bytesAvailable() >= (qint64) msgLen ){
|
|
||||||
readInitMessageBody( sock );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
|
||||||
{
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
|
|
||||||
if (!connectionMap.contains( sock )){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
|
||||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the message body
|
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
|
||||||
QDataStream readStream(msgBytes);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
||||||
readStream.setVersion( QDataStream::Qt_5_6 );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// server name
|
|
||||||
QByteArray latin1Name;
|
|
||||||
readStream >> latin1Name;
|
|
||||||
|
|
||||||
// connection type
|
|
||||||
ConnectionType connectionType = InvalidConnection;
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
|
||||||
readStream >> connTypeVal;
|
|
||||||
connectionType = static_cast <ConnectionType>( connTypeVal );
|
|
||||||
|
|
||||||
// instance id
|
|
||||||
quint32 instanceId = 0;
|
|
||||||
readStream >> instanceId;
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
quint16 msgChecksum = 0;
|
|
||||||
readStream >> msgChecksum;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
|
||||||
#else
|
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool isValid = readStream.status() == QDataStream::Ok &&
|
|
||||||
QLatin1String(latin1Name) == blockServerName &&
|
|
||||||
msgChecksum == actualChecksum;
|
|
||||||
|
|
||||||
if( !isValid ){
|
|
||||||
sock->close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.instanceId = instanceId;
|
|
||||||
info.stage = StageConnected;
|
|
||||||
|
|
||||||
if( connectionType == NewInstance ||
|
|
||||||
( connectionType == SecondaryInstance &&
|
|
||||||
options & SingleApplication::Mode::SecondaryNotification ) )
|
|
||||||
{
|
|
||||||
Q_EMIT q->instanceStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0){
|
|
||||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
|
||||||
{
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
|
||||||
{
|
|
||||||
if( closedSocket->bytesAvailable() > 0 )
|
|
||||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::randomSleep()
|
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
|
|
||||||
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
|
||||||
#else
|
|
||||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
|
||||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::addAppData(const QString &data)
|
|
||||||
{
|
|
||||||
appDataList.push_back(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SingleApplicationPrivate::appData() const
|
|
||||||
{
|
|
||||||
return appDataList;
|
|
||||||
}
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This file is not part of the SingleApplication API. It is used purely as an
|
|
||||||
// implementation detail. This header file may change from version to
|
|
||||||
// version without notice, or may even be removed.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SINGLEAPPLICATION_P_H
|
|
||||||
#define SINGLEAPPLICATION_P_H
|
|
||||||
|
|
||||||
#include <QtCore/QSharedMemory>
|
|
||||||
#include <QtNetwork/QLocalServer>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
#include "singleapplication.h"
|
|
||||||
|
|
||||||
struct InstancesInfo {
|
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum; // Must be the last field
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
qint64 msgLen = 0;
|
|
||||||
quint32 instanceId = 0;
|
|
||||||
quint8 stage = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum ConnectionType : quint8 {
|
|
||||||
InvalidConnection = 0,
|
|
||||||
NewInstance = 1,
|
|
||||||
SecondaryInstance = 2,
|
|
||||||
Reconnect = 3
|
|
||||||
};
|
|
||||||
enum ConnectionStage : quint8 {
|
|
||||||
StageHeader = 0,
|
|
||||||
StageBody = 1,
|
|
||||||
StageConnected = 2,
|
|
||||||
};
|
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
|
||||||
|
|
||||||
SingleApplicationPrivate( SingleApplication *q_ptr );
|
|
||||||
~SingleApplicationPrivate() override;
|
|
||||||
|
|
||||||
static QString getUsername();
|
|
||||||
void genBlockServerName();
|
|
||||||
void initializeMemoryBlock() const;
|
|
||||||
void startPrimary();
|
|
||||||
void startSecondary();
|
|
||||||
bool connectToPrimary( int msecs, ConnectionType connectionType );
|
|
||||||
quint16 blockChecksum() const;
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
QString primaryUser() const;
|
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
|
||||||
static void randomSleep();
|
|
||||||
void addAppData(const QString &data);
|
|
||||||
QStringList appData() const;
|
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
|
||||||
QSharedMemory *memory;
|
|
||||||
QLocalSocket *socket;
|
|
||||||
QLocalServer *server;
|
|
||||||
quint32 instanceNumber;
|
|
||||||
QString blockServerName;
|
|
||||||
SingleApplication::Options options;
|
|
||||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
|
||||||
QStringList appDataList;
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void slotConnectionEstablished();
|
|
||||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
|
||||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_P_H
|
|
||||||
2
client/3rd/qtkeychain
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd
|
Subproject commit 7460df6a978669290de5b56c2d98b199b61c3f88
|
||||||
|
|
@ -24,6 +24,12 @@ execute_process(
|
||||||
|
|
||||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||||
|
|
||||||
|
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||||
|
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
|
||||||
|
|
||||||
|
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||||
|
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||||
|
|
||||||
if(IOS)
|
if(IOS)
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
set(PACKAGES ${PACKAGES} Multimedia)
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -34,7 +40,7 @@ endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||||
|
|
||||||
set(LIBS ${LIBS}
|
set(LIBS ${LIBS}
|
||||||
Qt6::Core Qt6::Gui
|
Qt6::Core Qt6::Gui
|
||||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||||
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
||||||
|
|
@ -55,6 +61,7 @@ qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||||
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||||
|
|
@ -107,6 +114,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
|
@ -128,7 +136,6 @@ set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.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}/ui/qautostart.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
|
|
@ -136,6 +143,8 @@ set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
|
|
@ -186,6 +195,7 @@ set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
|
|
@ -252,7 +262,7 @@ set(SOURCES ${SOURCES}
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QQuickItem>
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#include <QResource>
|
#include <QResource>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QQuickItem>
|
#include <QLocalSocket>
|
||||||
|
#include <QLocalServer>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/models/installedAppsModel.h"
|
#include "ui/models/installedAppsModel.h"
|
||||||
|
|
@ -28,13 +30,7 @@
|
||||||
#include <AmneziaVPN-Swift.h>
|
#include <AmneziaVPN-Swift.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||||
#else
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
|
|
||||||
const QString &userData)
|
|
||||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
setQuitOnLastWindowClosed(false);
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
|
@ -115,10 +111,11 @@ void AmneziaApplication::init()
|
||||||
qFatal("Android controller initialization failed");
|
qFatal("Android controller initialization failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||||
m_pageController->replaceStartPage();
|
emit m_pageController->goToPageHome();
|
||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
data.clear();
|
||||||
|
emit m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||||
|
|
@ -126,16 +123,16 @@ void AmneziaApplication::init()
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->initialize();
|
IosController::Instance()->initialize();
|
||||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||||
m_pageController->replaceStartPage();
|
emit m_pageController->goToPageHome();
|
||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
emit m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||||
m_pageController->replaceStartPage();
|
emit m_pageController->goToPageHome();
|
||||||
m_pageController->goToPageSettingsBackup();
|
m_pageController->goToPageSettingsBackup();
|
||||||
m_settingsController->importBackupFromOutside(filePath);
|
emit m_settingsController->importBackupFromOutside(filePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||||
|
|
@ -164,7 +161,7 @@ void AmneziaApplication::init()
|
||||||
bool enabled = m_settings->isSaveLogs();
|
bool enabled = m_settings->isSaveLogs();
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!Logger::init()) {
|
if (!Logger::init(false)) {
|
||||||
qWarning() << "Initialization of debug subsystem failed";
|
qWarning() << "Initialization of debug subsystem failed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,16 +177,6 @@ void AmneziaApplication::init()
|
||||||
m_pageController->showOnStartup();
|
m_pageController->showOnStartup();
|
||||||
#endif
|
#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
|
// Android TextArea clipboard workaround
|
||||||
// Text from TextArea always has "text/html" mime-type:
|
// Text from TextArea always has "text/html" mime-type:
|
||||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
||||||
|
|
@ -294,6 +281,24 @@ bool AmneziaApplication::parseCommands()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
void AmneziaApplication::startLocalServer() {
|
||||||
|
const QString serverName("AmneziaVPNInstance");
|
||||||
|
QLocalServer::removeServer(serverName);
|
||||||
|
|
||||||
|
QLocalServer* server = new QLocalServer(this);
|
||||||
|
server->listen(serverName);
|
||||||
|
|
||||||
|
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||||
|
if (server) {
|
||||||
|
QLocalSocket* clientConnection = server->nextPendingConnection();
|
||||||
|
clientConnection->deleteLater();
|
||||||
|
}
|
||||||
|
emit m_pageController->raiseMainWindow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||||
{
|
{
|
||||||
return m_engine;
|
return m_engine;
|
||||||
|
|
@ -361,6 +366,18 @@ void AmneziaApplication::initModels()
|
||||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
&ServersModel::clearCachedProfile);
|
&ServersModel::clearCachedProfile);
|
||||||
|
|
||||||
|
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||||
|
|
||||||
|
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
|
||||||
|
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||||
|
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||||
|
});
|
||||||
|
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||||
|
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::initControllers()
|
void AmneziaApplication::initControllers()
|
||||||
|
|
@ -369,25 +386,26 @@ void AmneziaApplication::initControllers()
|
||||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this, [this](const QString &errorMessage) {
|
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
|
||||||
emit m_pageController->showErrorMessage(errorMessage);
|
[this](const QString &errorMessage) {
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_pageController->showErrorMessage(errorMessage);
|
||||||
});
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this, [this](ErrorCode errorCode) {
|
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
|
||||||
emit m_pageController->showErrorMessage(errorCode);
|
[this](ErrorCode errorCode) {
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_pageController->showErrorMessage(errorCode);
|
||||||
});
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
|
||||||
|
|
||||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||||
|
|
||||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
|
||||||
|
m_apiServicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||||
&PageController::showPassphraseRequestDrawer);
|
&PageController::showPassphraseRequestDrawer);
|
||||||
|
|
@ -396,6 +414,30 @@ void AmneziaApplication::initControllers()
|
||||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||||
&ConnectionController::onCurrentContainerUpdated);
|
&ConnectionController::onCurrentContainerUpdated);
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
||||||
|
disconnect(m_reloadConfigErrorOccurredConnection);
|
||||||
|
emit m_connectionController->configFromApiUpdated();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
|
||||||
|
m_reloadConfigErrorOccurredConnection = connect(
|
||||||
|
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||||
|
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||||
|
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
|
||||||
|
m_reloadConfigErrorOccurredConnection = connect(
|
||||||
|
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||||
|
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||||
|
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
||||||
|
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||||
|
|
||||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,28 +45,22 @@
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
|
#include "ui/models/apiServicesModel.h"
|
||||||
|
#include "ui/models/apiCountryModel.h"
|
||||||
|
|
||||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||||
#else
|
#else
|
||||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
#define AMNEZIA_BASE_CLASS QApplication
|
||||||
#define QAPPLICATION_CLASS QApplication
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication(int &argc, char *argv[]);
|
AmneziaApplication(int &argc, char *argv[]);
|
||||||
#else
|
|
||||||
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
|
|
||||||
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
|
|
||||||
const QString &userData = {});
|
|
||||||
#endif
|
|
||||||
virtual ~AmneziaApplication();
|
virtual ~AmneziaApplication();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
@ -76,6 +70,10 @@ public:
|
||||||
void updateTranslator(const QLocale &locale);
|
void updateTranslator(const QLocale &locale);
|
||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
void startLocalServer();
|
||||||
|
#endif
|
||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
QNetworkAccessManager *manager() { return m_nam; }
|
QNetworkAccessManager *manager() { return m_nam; }
|
||||||
|
|
||||||
|
|
@ -103,6 +101,8 @@ private:
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
|
|
@ -134,6 +134,8 @@ private:
|
||||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
|
||||||
QNetworkAccessManager *m_nam;
|
QNetworkAccessManager *m_nam;
|
||||||
|
|
||||||
|
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<manifest
|
<manifest
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.amnezia.vpn"
|
|
||||||
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
||||||
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
@ -46,7 +45,7 @@
|
||||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -68,9 +67,6 @@
|
||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.extract_android_style"
|
|
||||||
android:value="minimal" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
@ -88,6 +84,13 @@
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Translucent" />
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
|
<activity android:name=".AuthActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Translucent" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ImportConfigActivity"
|
android:name=".ImportConfigActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,21 @@
|
||||||
package org.amnezia.vpn.protocol.awg
|
package org.amnezia.vpn.protocol.awg
|
||||||
|
|
||||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||||
|
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config example:
|
|
||||||
* {
|
|
||||||
* "protocol": "awg",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "awg_config_data": {
|
|
||||||
* "H1": "969537490",
|
|
||||||
* "H2": "481688153",
|
|
||||||
* "H3": "2049399200",
|
|
||||||
* "H4": "52029755",
|
|
||||||
* "Jc": "3",
|
|
||||||
* "Jmax": "1000",
|
|
||||||
* "Jmin": "50",
|
|
||||||
* "S1": "49",
|
|
||||||
* "S2": "60",
|
|
||||||
* "client_ip": "10.8.1.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "port": 12345,
|
|
||||||
* "client_pub_key": "clientPublicKeyBase64",
|
|
||||||
* "client_priv_key": "privateKeyBase64",
|
|
||||||
* "psk_key": "presharedKeyBase64",
|
|
||||||
* "server_pub_key": "publicKeyBase64",
|
|
||||||
* "config": "[Interface]
|
|
||||||
* Address = 10.8.1.1/32
|
|
||||||
* DNS = 1.1.1.1, 1.0.0.1
|
|
||||||
* PrivateKey = privateKeyBase64
|
|
||||||
* Jc = 3
|
|
||||||
* Jmin = 50
|
|
||||||
* Jmax = 1000
|
|
||||||
* S1 = 49
|
|
||||||
* S2 = 60
|
|
||||||
* H1 = 969537490
|
|
||||||
* H2 = 481688153
|
|
||||||
* H3 = 2049399200
|
|
||||||
* H4 = 52029755
|
|
||||||
*
|
|
||||||
* [Peer]
|
|
||||||
* PublicKey = publicKeyBase64
|
|
||||||
* PresharedKey = presharedKeyBase64
|
|
||||||
* AllowedIPs = 0.0.0.0/0, ::/0
|
|
||||||
* Endpoint = 100.100.100.0:12345
|
|
||||||
* PersistentKeepalive = 25
|
|
||||||
* "
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Awg : Wireguard() {
|
class Awg : Wireguard() {
|
||||||
|
|
||||||
override val ifName: String = "awg0"
|
override val ifName: String = "awg0"
|
||||||
|
|
||||||
override fun parseConfig(config: JSONObject): AwgConfig {
|
override fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
val configDataJson = config.getJSONObject("awg_config_data")
|
val configData = config.getJSONObject("awg_config_data")
|
||||||
val configData = parseConfigData(configDataJson.getString("config"))
|
return WireguardConfig.build {
|
||||||
return AwgConfig.build {
|
setUseProtocolExtension(true)
|
||||||
configWireguard(configData, configDataJson)
|
configExtensionParameters(configData)
|
||||||
|
configWireguard(config, configData)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
configAppSplitTunneling(config)
|
configAppSplitTunneling(config)
|
||||||
configData["Jc"]?.let { setJc(it.toInt()) }
|
|
||||||
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
|
||||||
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
|
||||||
configData["S1"]?.let { setS1(it.toInt()) }
|
|
||||||
configData["S2"]?.let { setS2(it.toInt()) }
|
|
||||||
configData["H1"]?.let { setH1(it.toLong()) }
|
|
||||||
configData["H2"]?.let { setH2(it.toLong()) }
|
|
||||||
configData["H3"]?.let { setH3(it.toLong()) }
|
|
||||||
configData["H4"]?.let { setH4(it.toLong()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
package org.amnezia.vpn.protocol.awg
|
|
||||||
|
|
||||||
import org.amnezia.vpn.protocol.BadConfigException
|
|
||||||
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
|
||||||
|
|
||||||
class AwgConfig private constructor(
|
|
||||||
wireguardConfigBuilder: WireguardConfig.Builder,
|
|
||||||
val jc: Int,
|
|
||||||
val jmin: Int,
|
|
||||||
val jmax: Int,
|
|
||||||
val s1: Int,
|
|
||||||
val s2: Int,
|
|
||||||
val h1: Long,
|
|
||||||
val h2: Long,
|
|
||||||
val h3: Long,
|
|
||||||
val h4: Long
|
|
||||||
) : WireguardConfig(wireguardConfigBuilder) {
|
|
||||||
|
|
||||||
private constructor(builder: Builder) : this(
|
|
||||||
builder,
|
|
||||||
builder.jc,
|
|
||||||
builder.jmin,
|
|
||||||
builder.jmax,
|
|
||||||
builder.s1,
|
|
||||||
builder.s2,
|
|
||||||
builder.h1,
|
|
||||||
builder.h2,
|
|
||||||
builder.h3,
|
|
||||||
builder.h4
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
|
||||||
super.appendDeviceLine(this)
|
|
||||||
appendLine("jc=$jc")
|
|
||||||
appendLine("jmin=$jmin")
|
|
||||||
appendLine("jmax=$jmax")
|
|
||||||
appendLine("s1=$s1")
|
|
||||||
appendLine("s2=$s2")
|
|
||||||
appendLine("h1=$h1")
|
|
||||||
appendLine("h2=$h2")
|
|
||||||
appendLine("h3=$h3")
|
|
||||||
appendLine("h4=$h4")
|
|
||||||
}
|
|
||||||
|
|
||||||
class Builder : WireguardConfig.Builder() {
|
|
||||||
|
|
||||||
private var _jc: Int? = null
|
|
||||||
internal var jc: Int
|
|
||||||
get() = _jc ?: throw BadConfigException("AWG: parameter jc is undefined")
|
|
||||||
private set(value) { _jc = value }
|
|
||||||
|
|
||||||
private var _jmin: Int? = null
|
|
||||||
internal var jmin: Int
|
|
||||||
get() = _jmin ?: throw BadConfigException("AWG: parameter jmin is undefined")
|
|
||||||
private set(value) { _jmin = value }
|
|
||||||
|
|
||||||
private var _jmax: Int? = null
|
|
||||||
internal var jmax: Int
|
|
||||||
get() = _jmax ?: throw BadConfigException("AWG: parameter jmax is undefined")
|
|
||||||
private set(value) { _jmax = value }
|
|
||||||
|
|
||||||
private var _s1: Int? = null
|
|
||||||
internal var s1: Int
|
|
||||||
get() = _s1 ?: throw BadConfigException("AWG: parameter s1 is undefined")
|
|
||||||
private set(value) { _s1 = value }
|
|
||||||
|
|
||||||
private var _s2: Int? = null
|
|
||||||
internal var s2: Int
|
|
||||||
get() = _s2 ?: throw BadConfigException("AWG: parameter s2 is undefined")
|
|
||||||
private set(value) { _s2 = value }
|
|
||||||
|
|
||||||
private var _h1: Long? = null
|
|
||||||
internal var h1: Long
|
|
||||||
get() = _h1 ?: throw BadConfigException("AWG: parameter h1 is undefined")
|
|
||||||
private set(value) { _h1 = value }
|
|
||||||
|
|
||||||
private var _h2: Long? = null
|
|
||||||
internal var h2: Long
|
|
||||||
get() = _h2 ?: throw BadConfigException("AWG: parameter h2 is undefined")
|
|
||||||
private set(value) { _h2 = value }
|
|
||||||
|
|
||||||
private var _h3: Long? = null
|
|
||||||
internal var h3: Long
|
|
||||||
get() = _h3 ?: throw BadConfigException("AWG: parameter h3 is undefined")
|
|
||||||
private set(value) { _h3 = value }
|
|
||||||
|
|
||||||
private var _h4: Long? = null
|
|
||||||
internal var h4: Long
|
|
||||||
get() = _h4 ?: throw BadConfigException("AWG: parameter h4 is undefined")
|
|
||||||
private set(value) { _h4 = value }
|
|
||||||
|
|
||||||
fun setJc(jc: Int) = apply { this.jc = jc }
|
|
||||||
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
|
|
||||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
|
||||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
|
||||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
|
||||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
|
||||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
|
||||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
|
||||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
|
||||||
|
|
||||||
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) }
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
inline fun build(block: Builder.() -> Unit): AwgConfig = Builder().apply(block).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,3 +3,6 @@
|
||||||
// android.bundle.enableUncompressedNativeLibs is deprecated
|
// android.bundle.enableUncompressedNativeLibs is deprecated
|
||||||
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
||||||
useLegacyPackaging
|
useLegacyPackaging
|
||||||
|
|
||||||
|
// package name for androiddeployqt
|
||||||
|
namespace = "org.amnezia.vpn"
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,11 @@ dependencies {
|
||||||
implementation(project(":xray"))
|
implementation(project(":xray"))
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.fragment)
|
||||||
implementation(libs.kotlinx.coroutines)
|
implementation(libs.kotlinx.coroutines)
|
||||||
implementation(libs.kotlinx.serialization.protobuf)
|
implementation(libs.kotlinx.serialization.protobuf)
|
||||||
implementation(libs.bundles.androidx.camera)
|
implementation(libs.bundles.androidx.camera)
|
||||||
implementation(libs.google.mlkit)
|
implementation(libs.google.mlkit)
|
||||||
implementation(libs.androidx.datastore)
|
implementation(libs.androidx.datastore)
|
||||||
|
implementation(libs.androidx.biometric)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,40 +3,16 @@ package org.amnezia.vpn.protocol.cloak
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import net.openvpn.ovpn3.ClientAPI_Config
|
import net.openvpn.ovpn3.ClientAPI_Config
|
||||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config Example:
|
|
||||||
* {
|
|
||||||
* "protocol": "cloak",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "openvpn_config_data": {
|
|
||||||
* "config": "openVpnConfig"
|
|
||||||
* }
|
|
||||||
* "cloak_config_data": {
|
|
||||||
* "BrowserSig": "chrome",
|
|
||||||
* "EncryptionMethod": "aes-gcm",
|
|
||||||
* "NumConn": 1,
|
|
||||||
* "ProxyMethod": "openvpn",
|
|
||||||
* "PublicKey": "PublicKey=",
|
|
||||||
* "RemoteHost": "100.100.100.0",
|
|
||||||
* "RemotePort": "443",
|
|
||||||
* "ServerName": "servername",
|
|
||||||
* "StreamTimeout": 300,
|
|
||||||
* "Transport": "direct",
|
|
||||||
* "UID": "UID="
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Cloak : OpenVpn() {
|
class Cloak : OpenVpn() {
|
||||||
|
|
||||||
|
override fun internalInit() {
|
||||||
|
super.internalInit()
|
||||||
|
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
override fun parseConfig(config: JSONObject): ClientAPI_Config {
|
override fun parseConfig(config: JSONObject): ClientAPI_Config {
|
||||||
val openVpnConfig = ClientAPI_Config()
|
val openVpnConfig = ClientAPI_Config()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,28 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.2.0"
|
agp = "8.5.2"
|
||||||
kotlin = "1.9.20"
|
kotlin = "1.9.24"
|
||||||
androidx-core = "1.12.0"
|
androidx-core = "1.13.1"
|
||||||
androidx-activity = "1.8.1"
|
androidx-activity = "1.9.1"
|
||||||
androidx-annotation = "1.7.0"
|
androidx-annotation = "1.8.2"
|
||||||
androidx-camera = "1.3.0"
|
androidx-biometric = "1.2.0-alpha05"
|
||||||
|
androidx-camera = "1.3.4"
|
||||||
|
androidx-fragment = "1.8.2"
|
||||||
androidx-security-crypto = "1.1.0-alpha06"
|
androidx-security-crypto = "1.1.0-alpha06"
|
||||||
androidx-datastore = "1.1.0-beta01"
|
androidx-datastore = "1.1.1"
|
||||||
kotlinx-coroutines = "1.7.3"
|
kotlinx-coroutines = "1.8.1"
|
||||||
kotlinx-serialization = "1.6.3"
|
kotlinx-serialization = "1.6.3"
|
||||||
google-mlkit = "17.2.0"
|
google-mlkit = "17.3.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||||
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
||||||
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
||||||
|
androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" }
|
||||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
||||||
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
||||||
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
||||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
||||||
|
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||||
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
||||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||||
|
|
|
||||||
BIN
client/android/gradle/wrapper/gradle-wrapper.jar
vendored
|
|
@ -1,7 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
7
client/android/gradlew
vendored
|
|
@ -15,6 +15,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
|
|
@ -55,7 +57,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|
@ -84,7 +86,8 @@ done
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
|
||||||
22
client/android/gradlew.bat
vendored
|
|
@ -13,6 +13,8 @@
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,28 +11,12 @@ import org.amnezia.vpn.protocol.Protocol
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
import org.amnezia.vpn.util.net.getLocalNetworks
|
import org.amnezia.vpn.util.net.getLocalNetworks
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config Example:
|
|
||||||
* {
|
|
||||||
* "protocol": "openvpn",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "openvpn_config_data": {
|
|
||||||
* "config": "openVpnConfig"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
open class OpenVpn : Protocol() {
|
open class OpenVpn : Protocol() {
|
||||||
|
|
||||||
private var openVpnClient: OpenVpnClient? = null
|
private var openVpnClient: OpenVpnClient? = null
|
||||||
|
|
@ -51,14 +35,17 @@ open class OpenVpn : Protocol() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun internalInit() {
|
override fun internalInit() {
|
||||||
if (!isInitialized) loadSharedLibrary(context, "ovpn3")
|
if (!isInitialized) {
|
||||||
|
loadSharedLibrary(context, "ovpn3")
|
||||||
|
loadSharedLibrary(context, "ovpnutil")
|
||||||
|
}
|
||||||
if (this::scope.isInitialized) {
|
if (this::scope.isInitialized) {
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
scope = CoroutineScope(Dispatchers.IO)
|
scope = CoroutineScope(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val configBuilder = OpenVpnConfig.Builder()
|
val configBuilder = OpenVpnConfig.Builder()
|
||||||
|
|
||||||
openVpnClient = OpenVpnClient(
|
openVpnClient = OpenVpnClient(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol
|
||||||
|
|
||||||
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||||
|
|
||||||
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
|
||||||
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||||
|
|
||||||
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ abstract class Protocol {
|
||||||
|
|
||||||
protected abstract fun internalInit()
|
protected abstract fun internalInit()
|
||||||
|
|
||||||
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
abstract suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||||
|
|
||||||
abstract fun stopVpn()
|
abstract fun stopVpn()
|
||||||
|
|
||||||
|
|
@ -158,60 +158,6 @@ abstract class Protocol {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
vpnBuilder.setMetered(false)
|
vpnBuilder.setMetered(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
|
|
||||||
Log.d(TAG, "Extracting library: $libraryName")
|
|
||||||
val apks = hashSetOf<String>()
|
|
||||||
context.applicationInfo.run {
|
|
||||||
sourceDir?.let { apks += it }
|
|
||||||
splitSourceDirs?.let { apks += it }
|
|
||||||
}
|
|
||||||
for (abi in Build.SUPPORTED_ABIS) {
|
|
||||||
for (apk in apks) {
|
|
||||||
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
|
|
||||||
val mappedName = System.mapLibraryName(libraryName)
|
|
||||||
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
|
|
||||||
val zipEntry = zipFile.getEntry(libraryZipPath)
|
|
||||||
zipEntry?.let {
|
|
||||||
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
|
|
||||||
FileOutputStream(destination).use { outStream ->
|
|
||||||
zipFile.getInputStream(zipEntry).use { inStream ->
|
|
||||||
inStream.copyTo(outStream, 32 * 1024)
|
|
||||||
outStream.fd.sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
|
||||||
fun loadSharedLibrary(context: Context, libraryName: String) {
|
|
||||||
Log.d(TAG, "Loading library: $libraryName")
|
|
||||||
try {
|
|
||||||
System.loadLibrary(libraryName)
|
|
||||||
return
|
|
||||||
} catch (_: UnsatisfiedLinkError) {
|
|
||||||
Log.d(TAG, "Failed to load library, try to extract it from apk")
|
|
||||||
}
|
|
||||||
var tempFile: File? = null
|
|
||||||
try {
|
|
||||||
tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
|
|
||||||
if (extractLibrary(context, libraryName, tempFile)) {
|
|
||||||
System.load(tempFile.absolutePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw LoadLibraryException("Failed to load library apk: $libraryName", e)
|
|
||||||
} finally {
|
|
||||||
tempFile?.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)
|
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,5 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
|
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
|
||||||
|
|
||||||
<array name="bundled_libs">
|
<array name="bundled_libs">
|
||||||
<!-- %%INSERT_EXTRA_LIBS%% -->
|
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="qt_libs">
|
<array name="qt_libs">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<color name="black">#FF0E0E11</color>
|
||||||
<style name="NoActionBar">
|
<style name="NoActionBar">
|
||||||
|
<item name="android:windowBackground">@color/black</item>
|
||||||
|
<item name="android:colorBackground">@color/black</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ dependencyResolutionManagement {
|
||||||
includeBuild("./gradle/plugins")
|
includeBuild("./gradle/plugins")
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.settings") version "8.2.0"
|
id("com.android.settings") version "8.5.2"
|
||||||
id("settings-property-delegate")
|
id("settings-property-delegate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.vpn.protocol.getStatistics
|
import org.amnezia.vpn.protocol.getStatistics
|
||||||
import org.amnezia.vpn.protocol.getStatus
|
import org.amnezia.vpn.protocol.getStatus
|
||||||
import org.amnezia.vpn.qt.QtAndroidController
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.Prefs
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
|
|
@ -158,6 +159,11 @@ class AmneziaActivity : QtActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||||
|
loadLibs()
|
||||||
|
window.apply {
|
||||||
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
statusBarColor = getColor(R.color.black)
|
||||||
|
}
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
val proto = mainScope.async(Dispatchers.IO) {
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
VpnStateStore.getVpnState().vpnProto
|
VpnStateStore.getVpnState().vpnProto
|
||||||
|
|
@ -175,6 +181,17 @@ class AmneziaActivity : QtActivity() {
|
||||||
runBlocking { vpnProto = proto.await() }
|
runBlocking { vpnProto = proto.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadLibs() {
|
||||||
|
listOf(
|
||||||
|
"rsapss",
|
||||||
|
"crypto_3",
|
||||||
|
"ssl_3",
|
||||||
|
"ssh"
|
||||||
|
).forEach {
|
||||||
|
loadSharedLibrary(this.applicationContext, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun registerBroadcastReceivers() {
|
private fun registerBroadcastReceivers() {
|
||||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
registerBroadcastReceiver(
|
registerBroadcastReceiver(
|
||||||
|
|
@ -610,6 +627,14 @@ class AmneziaActivity : QtActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun setNavigationBarColor(color: Int) {
|
||||||
|
Log.v(TAG, "Change navigation bar color: ${"#%08X".format(color)}")
|
||||||
|
mainScope.launch {
|
||||||
|
window.navigationBarColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun minimizeApp() {
|
fun minimizeApp() {
|
||||||
Log.v(TAG, "Minimize application")
|
Log.v(TAG, "Minimize application")
|
||||||
|
|
@ -684,6 +709,17 @@ class AmneziaActivity : QtActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun requestAuthentication() {
|
||||||
|
Log.v(TAG, "Request authentication")
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
Intent(this@AmneziaActivity, AuthActivity::class.java).also {
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils methods
|
* Utils methods
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import androidx.annotation.MainThread
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import java.net.UnknownHostException
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
|
@ -31,6 +32,7 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
|
|
@ -39,7 +41,6 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.amnezia.vpn.protocol.BadConfigException
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.LoadLibraryException
|
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
|
|
@ -49,6 +50,7 @@ import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||||
import org.amnezia.vpn.protocol.VpnException
|
import org.amnezia.vpn.protocol.VpnException
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
import org.amnezia.vpn.protocol.putStatus
|
import org.amnezia.vpn.protocol.putStatus
|
||||||
|
import org.amnezia.vpn.util.LoadLibraryException
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.Prefs
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.amnezia.vpn.util.net.NetworkState
|
import org.amnezia.vpn.util.net.NetworkState
|
||||||
|
|
@ -111,6 +113,10 @@ open class AmneziaVpnService : VpnService() {
|
||||||
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
||||||
|
|
||||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||||
|
connectionJob?.cancel()
|
||||||
|
connectionJob = null
|
||||||
|
disconnectionJob?.cancel()
|
||||||
|
disconnectionJob = null
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
when (e) {
|
when (e) {
|
||||||
is IllegalArgumentException,
|
is IllegalArgumentException,
|
||||||
|
|
@ -122,6 +128,8 @@ open class AmneziaVpnService : VpnService() {
|
||||||
|
|
||||||
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
||||||
|
|
||||||
|
is UnknownHostException -> onError("Unknown host")
|
||||||
|
|
||||||
else -> throw e
|
else -> throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -531,7 +539,7 @@ open class AmneziaVpnService : VpnService() {
|
||||||
protocolState.value = DISCONNECTING
|
protocolState.value = DISCONNECTING
|
||||||
|
|
||||||
disconnectionJob = connectionScope.launch {
|
disconnectionJob = connectionScope.launch {
|
||||||
connectionJob?.join()
|
connectionJob?.cancelAndJoin()
|
||||||
connectionJob = null
|
connectionJob = null
|
||||||
|
|
||||||
vpnProto?.protocol?.stopVpn()
|
vpnProto?.protocol?.stopVpn()
|
||||||
|
|
|
||||||
97
client/android/src/org/amnezia/vpn/AuthActivity.kt
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.biometric.BiometricPrompt.AuthenticationResult
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "AuthActivity"
|
||||||
|
|
||||||
|
private const val AUTHENTICATORS = BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||||
|
|
||||||
|
class AuthActivity : FragmentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val biometricManager = BiometricManager.from(applicationContext)
|
||||||
|
when (biometricManager.canAuthenticate(AUTHENTICATORS)) {
|
||||||
|
BiometricManager.BIOMETRIC_SUCCESS -> {
|
||||||
|
showBiometricPrompt(biometricManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
|
||||||
|
Log.w(TAG, "Unknown biometric status")
|
||||||
|
showBiometricPrompt(biometricManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
|
||||||
|
Log.e(TAG, "The specified options are incompatible with the current Android " +
|
||||||
|
"version ${Build.VERSION.SDK_INT}")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
|
||||||
|
Log.w(TAG, "The hardware is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
|
||||||
|
Log.w(TAG, "No biometric or device credential is enrolled")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
|
||||||
|
Log.w(TAG, "There is no suitable hardware")
|
||||||
|
}
|
||||||
|
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
|
||||||
|
Log.w(TAG, "A security vulnerability has been discovered with one or " +
|
||||||
|
"more hardware sensors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QtAndroidController.onAuthResult(true)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBiometricPrompt(biometricManager: BiometricManager) {
|
||||||
|
val executor = ContextCompat.getMainExecutor(applicationContext)
|
||||||
|
val biometricPrompt = BiometricPrompt(this, executor,
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
Log.d(TAG, "Authentication succeeded")
|
||||||
|
QtAndroidController.onAuthResult(true)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Log.w(TAG, "Authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
Log.e(TAG, "Authentication error $errorCode: $errString")
|
||||||
|
QtAndroidController.onAuthResult(false)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setAllowedAuthenticators(AUTHENTICATORS)
|
||||||
|
.setTitle("AmneziaVPN")
|
||||||
|
.setSubtitle(biometricManager.getStrings(AUTHENTICATORS)?.promptMessage)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
biometricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package org.amnezia.vpn;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.app.KeyguardManager;
|
|
||||||
import android.content.Intent;
|
|
||||||
import org.qtproject.qt.android.bindings.QtActivity;
|
|
||||||
|
|
||||||
|
|
||||||
import static android.content.Context.KEYGUARD_SERVICE;
|
|
||||||
|
|
||||||
public class AuthHelper extends QtActivity {
|
|
||||||
|
|
||||||
static final String TAG = "AuthHelper";
|
|
||||||
|
|
||||||
public static Intent getAuthIntent(Context context) {
|
|
||||||
KeyguardManager mKeyguardManager = (KeyguardManager)context.getSystemService(KEYGUARD_SERVICE);
|
|
||||||
if (mKeyguardManager.isDeviceSecure()) {
|
|
||||||
return mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -33,10 +33,10 @@ class ImportConfigActivity : ComponentActivity() {
|
||||||
intent?.let(::readConfig)
|
intent?.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.d(TAG, "onNewIntent: $intent")
|
||||||
intent?.let(::readConfig)
|
intent.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readConfig(intent: Intent) {
|
private fun readConfig(intent: Intent) {
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,7 @@ object QtAndroidController {
|
||||||
|
|
||||||
external fun onConfigImported(data: String)
|
external fun onConfigImported(data: String)
|
||||||
|
|
||||||
|
external fun onAuthResult(result: Boolean)
|
||||||
|
|
||||||
external fun decodeQrCode(data: String): Boolean
|
external fun decodeQrCode(data: String): Boolean
|
||||||
}
|
}
|
||||||
9
client/android/utils/src/main/kotlin/JsonExt.kt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.amnezia.vpn.util
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
inline fun <reified T> JSONArray.asSequence(): Sequence<T> =
|
||||||
|
(0..<length()).asSequence().map { get(it) as T }
|
||||||
|
|
||||||
|
fun JSONObject.optStringOrNull(name: String) = optString(name).ifEmpty { null }
|
||||||
66
client/android/utils/src/main/kotlin/LibraryLoader.kt
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.amnezia.vpn.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
|
private const val TAG = "LibraryLoader"
|
||||||
|
|
||||||
|
object LibraryLoader {
|
||||||
|
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
|
||||||
|
Log.d(TAG, "Extracting library: $libraryName")
|
||||||
|
val apks = hashSetOf<String>()
|
||||||
|
context.applicationInfo.run {
|
||||||
|
sourceDir?.let { apks += it }
|
||||||
|
splitSourceDirs?.let { apks += it }
|
||||||
|
}
|
||||||
|
for (abi in Build.SUPPORTED_ABIS) {
|
||||||
|
for (apk in apks) {
|
||||||
|
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
|
||||||
|
val mappedName = System.mapLibraryName(libraryName)
|
||||||
|
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
|
||||||
|
val zipEntry = zipFile.getEntry(libraryZipPath)
|
||||||
|
zipEntry?.let {
|
||||||
|
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
|
||||||
|
FileOutputStream(destination).use { outStream ->
|
||||||
|
zipFile.getInputStream(zipEntry).use { inStream ->
|
||||||
|
inStream.copyTo(outStream, 32 * 1024)
|
||||||
|
outStream.fd.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
|
fun loadSharedLibrary(context: Context, libraryName: String) {
|
||||||
|
Log.d(TAG, "Loading library: $libraryName")
|
||||||
|
try {
|
||||||
|
System.loadLibrary(libraryName)
|
||||||
|
return
|
||||||
|
} catch (_: UnsatisfiedLinkError) {
|
||||||
|
Log.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||||
|
|
@ -88,7 +88,7 @@ class NetworkState(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val numberAttempts = 3
|
val numberAttempts = 300
|
||||||
var attemptCount = 0
|
var attemptCount = 0
|
||||||
while(true) {
|
while(true) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseInetAddress(address: String): InetAddress = parseNumericAddressCompat(address)
|
fun parseInetAddress(address: String): InetAddress = InetAddress.getByName(address)
|
||||||
|
|
||||||
private val parseNumericAddressCompat: (String) -> InetAddress =
|
private val parseNumericAddressCompat: (String) -> InetAddress =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
|
@ -60,7 +60,7 @@ private val parseNumericAddressCompat: (String) -> InetAddress =
|
||||||
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
|
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
|
||||||
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
|
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
|
||||||
|
|
||||||
internal val InetAddress.ip: String
|
val InetAddress.ip: String
|
||||||
get() = if (this is Inet4Address) {
|
get() = if (this is Inet4Address) {
|
||||||
hostAddress!!
|
hostAddress!!
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,26 @@
|
||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import java.util.TreeMap
|
import java.io.IOException
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.GoBackend
|
import org.amnezia.awg.GoBackend
|
||||||
import org.amnezia.vpn.protocol.Protocol
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.amnezia.vpn.util.asSequence
|
||||||
import org.amnezia.vpn.util.net.InetEndpoint
|
import org.amnezia.vpn.util.net.InetEndpoint
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
|
import org.amnezia.vpn.util.optStringOrNull
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config example:
|
|
||||||
* {
|
|
||||||
* "protocol": "wireguard",
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "splitTunnelSites": [
|
|
||||||
* ],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "wireguard_config_data": {
|
|
||||||
* "client_ip": "10.8.1.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "port": 12345,
|
|
||||||
* "client_pub_key": "clientPublicKeyBase64",
|
|
||||||
* "client_priv_key": "privateKeyBase64",
|
|
||||||
* "psk_key": "presharedKeyBase64",
|
|
||||||
* "server_pub_key": "publicKeyBase64",
|
|
||||||
* "config": "[Interface]
|
|
||||||
* Address = 10.8.1.1/32
|
|
||||||
* DNS = 1.1.1.1, 1.0.0.1
|
|
||||||
* PrivateKey = privateKeyBase64
|
|
||||||
*
|
|
||||||
* [Peer]
|
|
||||||
* PublicKey = publicKeyBase64
|
|
||||||
* PresharedKey = presharedKeyBase64
|
|
||||||
* AllowedIPs = 0.0.0.0/0, ::/0
|
|
||||||
* Endpoint = 100.100.100.0:12345
|
|
||||||
* PersistentKeepalive = 25
|
|
||||||
* "
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
private const val TAG = "Wireguard"
|
private const val TAG = "Wireguard"
|
||||||
|
|
||||||
open class Wireguard : Protocol() {
|
open class Wireguard : Protocol() {
|
||||||
|
|
@ -79,67 +51,105 @@ open class Wireguard : Protocol() {
|
||||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val wireguardConfig = parseConfig(config)
|
val wireguardConfig = parseConfig(config)
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
start(wireguardConfig, vpnBuilder, protect)
|
start(wireguardConfig, vpnBuilder, protect)
|
||||||
|
waitForConnection(startTime)
|
||||||
state.value = CONNECTED
|
state.value = CONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun waitForConnection(startTime: Long) {
|
||||||
|
Log.d(TAG, "Waiting for connection")
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
|
||||||
|
try {
|
||||||
|
delay(1000)
|
||||||
|
var log = getLogcat(time)
|
||||||
|
Log.d(TAG, "First waiting log: $log")
|
||||||
|
// check that there is a connection log,
|
||||||
|
// to avoid infinite connection
|
||||||
|
if (!log.contains("Attaching to interface")) {
|
||||||
|
Log.w(TAG, "Logs do not contain a connection log")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
while (!log.contains("Received handshake response")) {
|
||||||
|
delay(1000)
|
||||||
|
log = getLogcat(time)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Failed to get logcat: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLogcat(time: String): String =
|
||||||
|
ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
.start()
|
||||||
|
.inputStream.reader().readText()
|
||||||
|
|
||||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
val configDataJson = config.getJSONObject("wireguard_config_data")
|
val configData = config.getJSONObject("wireguard_config_data")
|
||||||
val configData = parseConfigData(configDataJson.getString("config"))
|
|
||||||
return WireguardConfig.build {
|
return WireguardConfig.build {
|
||||||
configWireguard(configData, configDataJson)
|
configWireguard(config, configData)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
configAppSplitTunneling(config)
|
configAppSplitTunneling(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) {
|
protected fun WireguardConfig.Builder.configWireguard(config: JSONObject, configData: JSONObject) {
|
||||||
configData["Address"]?.split(",")?.map { address ->
|
configData.getString("client_ip").split(",").map { address ->
|
||||||
InetNetwork.parse(address.trim())
|
InetNetwork.parse(address.trim())
|
||||||
}?.forEach(::addAddress)
|
}.forEach(::addAddress)
|
||||||
|
|
||||||
configData["DNS"]?.split(",")?.map { dns ->
|
config.optStringOrNull("dns1")?.let { dns ->
|
||||||
parseInetAddress(dns.trim())
|
addDnsServer(parseInetAddress(dns.trim()))
|
||||||
}?.forEach(::addDnsServer)
|
}
|
||||||
|
|
||||||
|
config.optStringOrNull("dns2")?.let { dns ->
|
||||||
|
addDnsServer(parseInetAddress(dns.trim()))
|
||||||
|
}
|
||||||
|
|
||||||
val defRoutes = hashSetOf(
|
val defRoutes = hashSetOf(
|
||||||
InetNetwork("0.0.0.0", 0),
|
InetNetwork("0.0.0.0", 0),
|
||||||
InetNetwork("::", 0)
|
InetNetwork("::", 0)
|
||||||
)
|
)
|
||||||
val routes = hashSetOf<InetNetwork>()
|
val routes = hashSetOf<InetNetwork>()
|
||||||
configData["AllowedIPs"]?.split(",")?.map { route ->
|
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||||
InetNetwork.parse(route.trim())
|
InetNetwork.parse(route.trim())
|
||||||
}?.forEach(routes::add)
|
}.forEach(routes::add)
|
||||||
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
|
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
|
||||||
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
||||||
addRoutes(routes)
|
addRoutes(routes)
|
||||||
|
|
||||||
configDataJson.optString("mtu").let { mtu ->
|
configData.optStringOrNull("mtu")?.let { setMtu(it.toInt()) }
|
||||||
if (mtu.isNotEmpty()) {
|
|
||||||
setMtu(mtu.toInt())
|
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||||
} else {
|
val port = configData.getInt("port")
|
||||||
configData["MTU"]?.let { setMtu(it.toInt()) }
|
setEndpoint(InetEndpoint(host, port))
|
||||||
}
|
|
||||||
|
if (configData.optBoolean("isObfuscationEnabled")) {
|
||||||
|
setUseProtocolExtension(true)
|
||||||
|
configExtensionParameters(configData)
|
||||||
}
|
}
|
||||||
|
|
||||||
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
|
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||||
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
|
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||||
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
|
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||||
configData["PublicKey"]?.let { setPublicKeyHex(it.base64ToHex()) }
|
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||||
configData["PresharedKey"]?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun parseConfigData(data: String): Map<String, String> {
|
protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) {
|
||||||
val parsedData = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
|
configData.optStringOrNull("Jc")?.let { setJc(it.toInt()) }
|
||||||
data.lineSequence()
|
configData.optStringOrNull("Jmin")?.let { setJmin(it.toInt()) }
|
||||||
.filter { it.isNotEmpty() && !it.startsWith('[') }
|
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||||
.forEach { line ->
|
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||||
val attr = line.split("=", limit = 2)
|
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||||
parsedData[attr.first().trim()] = attr.last().trim()
|
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||||
}
|
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||||
return parsedData
|
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||||
|
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||||
import org.amnezia.vpn.util.net.InetEndpoint
|
import org.amnezia.vpn.util.net.InetEndpoint
|
||||||
|
|
||||||
|
|
@ -12,7 +13,17 @@ open class WireguardConfig protected constructor(
|
||||||
val persistentKeepalive: Int,
|
val persistentKeepalive: Int,
|
||||||
val publicKeyHex: String,
|
val publicKeyHex: String,
|
||||||
val preSharedKeyHex: String?,
|
val preSharedKeyHex: String?,
|
||||||
val privateKeyHex: String
|
val privateKeyHex: String,
|
||||||
|
val useProtocolExtension: Boolean,
|
||||||
|
val jc: Int?,
|
||||||
|
val jmin: Int?,
|
||||||
|
val jmax: Int?,
|
||||||
|
val s1: Int?,
|
||||||
|
val s2: Int?,
|
||||||
|
val h1: Long?,
|
||||||
|
val h2: Long?,
|
||||||
|
val h3: Long?,
|
||||||
|
val h4: Long?
|
||||||
) : ProtocolConfig(protocolConfigBuilder) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
|
|
@ -21,7 +32,17 @@ open class WireguardConfig protected constructor(
|
||||||
builder.persistentKeepalive,
|
builder.persistentKeepalive,
|
||||||
builder.publicKeyHex,
|
builder.publicKeyHex,
|
||||||
builder.preSharedKeyHex,
|
builder.preSharedKeyHex,
|
||||||
builder.privateKeyHex
|
builder.privateKeyHex,
|
||||||
|
builder.useProtocolExtension,
|
||||||
|
builder.jc,
|
||||||
|
builder.jmin,
|
||||||
|
builder.jmax,
|
||||||
|
builder.s1,
|
||||||
|
builder.s2,
|
||||||
|
builder.h1,
|
||||||
|
builder.h2,
|
||||||
|
builder.h3,
|
||||||
|
builder.h4
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||||
|
|
@ -33,6 +54,30 @@ open class WireguardConfig protected constructor(
|
||||||
|
|
||||||
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
||||||
appendLine("private_key=$privateKeyHex")
|
appendLine("private_key=$privateKeyHex")
|
||||||
|
if (useProtocolExtension) {
|
||||||
|
validateProtocolExtensionParameters()
|
||||||
|
appendLine("jc=$jc")
|
||||||
|
appendLine("jmin=$jmin")
|
||||||
|
appendLine("jmax=$jmax")
|
||||||
|
appendLine("s1=$s1")
|
||||||
|
appendLine("s2=$s2")
|
||||||
|
appendLine("h1=$h1")
|
||||||
|
appendLine("h2=$h2")
|
||||||
|
appendLine("h3=$h3")
|
||||||
|
appendLine("h4=$h4")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateProtocolExtensionParameters() {
|
||||||
|
if (jc == null) throw BadConfigException("Parameter jc is undefined")
|
||||||
|
if (jmin == null) throw BadConfigException("Parameter jmin is undefined")
|
||||||
|
if (jmax == null) throw BadConfigException("Parameter jmax is undefined")
|
||||||
|
if (s1 == null) throw BadConfigException("Parameter s1 is undefined")
|
||||||
|
if (s2 == null) throw BadConfigException("Parameter s2 is undefined")
|
||||||
|
if (h1 == null) throw BadConfigException("Parameter h1 is undefined")
|
||||||
|
if (h2 == null) throw BadConfigException("Parameter h2 is undefined")
|
||||||
|
if (h3 == null) throw BadConfigException("Parameter h3 is undefined")
|
||||||
|
if (h4 == null) throw BadConfigException("Parameter h4 is undefined")
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||||
|
|
@ -65,6 +110,18 @@ open class WireguardConfig protected constructor(
|
||||||
|
|
||||||
override var mtu: Int = WIREGUARD_DEFAULT_MTU
|
override var mtu: Int = WIREGUARD_DEFAULT_MTU
|
||||||
|
|
||||||
|
internal var useProtocolExtension: Boolean = false
|
||||||
|
|
||||||
|
internal var jc: Int? = null
|
||||||
|
internal var jmin: Int? = null
|
||||||
|
internal var jmax: Int? = null
|
||||||
|
internal var s1: Int? = null
|
||||||
|
internal var s2: Int? = null
|
||||||
|
internal var h1: Long? = null
|
||||||
|
internal var h2: Long? = null
|
||||||
|
internal var h3: Long? = null
|
||||||
|
internal var h4: Long? = null
|
||||||
|
|
||||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||||
|
|
||||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||||
|
|
@ -75,6 +132,18 @@ open class WireguardConfig protected constructor(
|
||||||
|
|
||||||
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
|
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
|
||||||
|
|
||||||
|
fun setUseProtocolExtension(useProtocolExtension: Boolean) = apply { this.useProtocolExtension = useProtocolExtension }
|
||||||
|
|
||||||
|
fun setJc(jc: Int) = apply { this.jc = jc }
|
||||||
|
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
|
||||||
|
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||||
|
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||||
|
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||||
|
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||||
|
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||||
|
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||||
|
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||||
|
|
||||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,72 +17,10 @@ import org.amnezia.vpn.protocol.xray.libXray.Logger
|
||||||
import org.amnezia.vpn.protocol.xray.libXray.Tun2SocksConfig
|
import org.amnezia.vpn.protocol.xray.libXray.Tun2SocksConfig
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
|
import org.amnezia.vpn.util.net.ip
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
|
||||||
* Config example:
|
|
||||||
* {
|
|
||||||
* "appSplitTunnelType": 0,
|
|
||||||
* "config_version": 0,
|
|
||||||
* "description": "Server 1",
|
|
||||||
* "dns1": "1.1.1.1",
|
|
||||||
* "dns2": "1.0.0.1",
|
|
||||||
* "hostName": "100.100.100.0",
|
|
||||||
* "protocol": "xray",
|
|
||||||
* "splitTunnelApps": [],
|
|
||||||
* "splitTunnelSites": [],
|
|
||||||
* "splitTunnelType": 0,
|
|
||||||
* "xray_config_data": {
|
|
||||||
* "inbounds": [
|
|
||||||
* {
|
|
||||||
* "listen": "127.0.0.1",
|
|
||||||
* "port": 8080,
|
|
||||||
* "protocol": "socks",
|
|
||||||
* "settings": {
|
|
||||||
* "udp": true
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ],
|
|
||||||
* "log": {
|
|
||||||
* "loglevel": "error"
|
|
||||||
* },
|
|
||||||
* "outbounds": [
|
|
||||||
* {
|
|
||||||
* "protocol": "vless",
|
|
||||||
* "settings": {
|
|
||||||
* "vnext": [
|
|
||||||
* {
|
|
||||||
* "address": "100.100.100.0",
|
|
||||||
* "port": 443,
|
|
||||||
* "users": [
|
|
||||||
* {
|
|
||||||
* "encryption": "none",
|
|
||||||
* "flow": "xtls-rprx-vision",
|
|
||||||
* "id": "id"
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* },
|
|
||||||
* "streamSettings": {
|
|
||||||
* "network": "tcp",
|
|
||||||
* "realitySettings": {
|
|
||||||
* "fingerprint": "chrome",
|
|
||||||
* "publicKey": "publicKey",
|
|
||||||
* "serverName": "google.com",
|
|
||||||
* "shortId": "id",
|
|
||||||
* "spiderX": ""
|
|
||||||
* },
|
|
||||||
* "security": "reality"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
private const val TAG = "Xray"
|
private const val TAG = "Xray"
|
||||||
private const val LIBXRAY_TAG = "libXray"
|
private const val LIBXRAY_TAG = "libXray"
|
||||||
|
|
||||||
|
|
@ -109,7 +47,7 @@ class Xray : Protocol() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
Log.w(TAG, "XRay already running")
|
Log.w(TAG, "XRay already running")
|
||||||
return
|
return
|
||||||
|
|
@ -124,7 +62,15 @@ class Xray : Protocol() {
|
||||||
.put("loglevel", "warning")
|
.put("loglevel", "warning")
|
||||||
.put("access", "none") // disable access log
|
.put("access", "none") // disable access log
|
||||||
|
|
||||||
start(xrayConfig, xrayJsonConfig.toString(), vpnBuilder, protect)
|
var xrayJsonConfigString = xrayJsonConfig.toString()
|
||||||
|
config.getString("hostName").let { hostName ->
|
||||||
|
val ipAddress = parseInetAddress(hostName).ip
|
||||||
|
if (hostName != ipAddress) {
|
||||||
|
xrayJsonConfigString = xrayJsonConfigString.replace(hostName, ipAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(xrayConfig, xrayJsonConfigString, vpnBuilder, protect)
|
||||||
state.value = CONNECTED
|
state.value = CONNECTED
|
||||||
isRunning = true
|
isRunning = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,11 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
||||||
|
|
||||||
if(NOT IOS AND NOT ANDROID)
|
|
||||||
include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
||||||
set(LIBS ${LIBS} SortFilterProxyModel)
|
set(LIBS ${LIBS} SortFilterProxyModel)
|
||||||
|
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||||
|
|
||||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||||
include(${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/QSimpleCrypto.cmake)
|
|
||||||
|
|
||||||
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
|
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
|
||||||
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
|
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
|
||||||
|
|
@ -83,13 +79,12 @@ set(BUILD_WITH_QT6 ON)
|
||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||||
set(LIBS ${LIBS} qt6keychain)
|
set(LIBS ${LIBS} qt6keychain)
|
||||||
|
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${OPENSSL_INCLUDE_DIR}
|
${OPENSSL_INCLUDE_DIR}
|
||||||
${LIBSSH_INCLUDE_DIR}/include
|
${LIBSSH_INCLUDE_DIR}/include
|
||||||
${LIBSSH_ROOT_DIR}/include
|
${LIBSSH_ROOT_DIR}/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
|
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
||||||
|
|
|
||||||
21
client/cmake/QSimpleCrypto.cmake
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
|
||||||
|
|
||||||
|
include_directories(${QSIMPLECRYPTO_DIR})
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QAead.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QRsa.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QX509.h
|
||||||
|
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
|
||||||
|
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
|
||||||
|
)
|
||||||
|
|
@ -27,7 +27,6 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
||||||
)
|
)
|
||||||
|
|
@ -35,7 +34,6 @@ set(HEADERS ${HEADERS}
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||||
stdOut.replace("/32", "");
|
stdOut.replace("/32", "");
|
||||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||||
|
|
||||||
|
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
||||||
|
// and added there more IPs for route his itnernal networks, like:
|
||||||
|
// ...
|
||||||
|
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
||||||
|
// ...
|
||||||
|
// without this code - next IP would be 1 if last item in 'ips' has format above
|
||||||
|
QStringList vpnIps;
|
||||||
|
for (const auto &ip : ips) {
|
||||||
|
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
|
||||||
|
}
|
||||||
|
ips = vpnIps;
|
||||||
|
|
||||||
// Calc next IP address
|
// Calc next IP address
|
||||||
if (ips.isEmpty()) {
|
if (ips.isEmpty()) {
|
||||||
nextIpNumber = "2";
|
nextIpNumber = "2";
|
||||||
|
|
@ -187,6 +199,10 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
||||||
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
||||||
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
||||||
|
|
||||||
|
jConfig[config_key::persistent_keep_alive] = "25";
|
||||||
|
QJsonArray allowedIps { "0.0.0.0/0", "::/0" };
|
||||||
|
jConfig[config_key::allowed_ips] = allowedIps;
|
||||||
|
|
||||||
jConfig[config_key::clientId] = connData.clientPubKey;
|
jConfig[config_key::clientId] = connData.clientPubKey;
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include "QBlockCipher.h"
|
||||||
|
#include "QRsa.h"
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "core/enums/apiEnums.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
|
@ -25,25 +29,75 @@ namespace
|
||||||
constexpr char uuid[] = "installation_uuid";
|
constexpr char uuid[] = "installation_uuid";
|
||||||
constexpr char osVersion[] = "os_version";
|
constexpr char osVersion[] = "os_version";
|
||||||
constexpr char appVersion[] = "app_version";
|
constexpr char appVersion[] = "app_version";
|
||||||
|
|
||||||
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
|
constexpr char serverCountryCode[] = "server_country_code";
|
||||||
|
constexpr char serviceType[] = "service_type";
|
||||||
|
|
||||||
|
constexpr char aesKey[] = "aes_key";
|
||||||
|
constexpr char aesIv[] = "aes_iv";
|
||||||
|
constexpr char aesSalt[] = "aes_salt";
|
||||||
|
|
||||||
|
constexpr char apiPayload[] = "api_payload";
|
||||||
|
constexpr char keyPayload[] = "key_payload";
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList proxyStorageUrl = { "" };
|
||||||
|
|
||||||
|
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
if (!sslErrors.empty()) {
|
||||||
|
qDebug().noquote() << sslErrors;
|
||||||
|
return ErrorCode::ApiConfigSslError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
return ErrorCode::ApiConfigTimeoutError;
|
||||||
|
} else {
|
||||||
|
QString err = reply->errorString();
|
||||||
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
|
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
|
return ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiController::ApiController(QObject *parent) : QObject(parent)
|
ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
|
||||||
|
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
||||||
|
const QByteArray &apiResponseBody, QJsonObject &serverConfig)
|
||||||
{
|
{
|
||||||
if (protocol == configKey::cloak) {
|
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString();
|
||||||
config.replace("<key>", "<key>\n");
|
|
||||||
config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
data.replace("vpn://", "");
|
||||||
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
|
||||||
|
if (ba.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
|
if (!ba_uncompressed.isEmpty()) {
|
||||||
|
ba = ba_uncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString configStr = ba;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
configStr.replace("<key>", "<key>\n");
|
||||||
|
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||||
} else if (protocol == configKey::awg) {
|
} else if (protocol == configKey::awg) {
|
||||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||||
auto serverConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
auto containers = serverConfig.value(config_key::containers).toArray();
|
auto containers = serverConfig.value(config_key::containers).toArray();
|
||||||
if (containers.isEmpty()) {
|
if (containers.isEmpty()) {
|
||||||
return;
|
return; // todo process error
|
||||||
}
|
}
|
||||||
auto container = containers.at(0).toObject();
|
auto container = containers.at(0).toObject();
|
||||||
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
||||||
|
|
@ -61,11 +115,75 @@ void ApiController::processApiConfig(const QString &protocol, const ApiControlle
|
||||||
container[containerName] = containerConfig;
|
container[containerName] = containerConfig;
|
||||||
containers.replace(0, container);
|
containers.replace(0, container);
|
||||||
serverConfig[config_key::containers] = containers;
|
serverConfig[config_key::containers] = containers;
|
||||||
config = QString(QJsonDocument(serverConfig).toJson());
|
configStr = QString(QJsonDocument(serverConfig).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
|
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
||||||
|
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
||||||
|
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
||||||
|
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
||||||
|
|
||||||
|
if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||||
|
serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion);
|
||||||
|
serverConfig[config_key::description] = apiConfig.value(config_key::description);
|
||||||
|
serverConfig[config_key::name] = apiConfig.value(config_key::name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||||
|
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList ApiController::getProxyUrls()
|
||||||
|
{
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(7000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QNetworkReply *reply;
|
||||||
|
|
||||||
|
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
||||||
|
request.setUrl(proxyStorageUrl);
|
||||||
|
reply = amnApp->manager()->get(request);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
EVP_PKEY *privateKey = nullptr;
|
||||||
|
QByteArray responseBody;
|
||||||
|
try {
|
||||||
|
QByteArray key = PROD_PROXY_STORAGE_KEY;
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
privateKey = rsa.getPrivateKeyFromByteArray(key, "");
|
||||||
|
responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING);
|
||||||
|
} catch (...) {
|
||||||
|
qCritical() << "error loading private key from environment variables or decrypting payload";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
|
}
|
||||||
|
|
||||||
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
||||||
{
|
{
|
||||||
ApiController::ApiPayloadData apiPayload;
|
ApiController::ApiPayloadData apiPayload;
|
||||||
|
|
@ -101,8 +219,6 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(7000);
|
request.setTransferTimeout(7000);
|
||||||
|
|
@ -120,39 +236,13 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
QNetworkReply *reply = amnApp->manager()->post(request, requestBody);
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
QString contents = QString::fromUtf8(reply->readAll());
|
auto apiResponseBody = reply->readAll();
|
||||||
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
|
||||||
|
emit finished(serverConfig, serverIndex);
|
||||||
data.replace("vpn://", "");
|
|
||||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
||||||
|
|
||||||
if (ba.isEmpty()) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray ba_uncompressed = qUncompress(ba);
|
|
||||||
if (!ba_uncompressed.isEmpty()) {
|
|
||||||
ba = ba_uncompressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString configStr = ba;
|
|
||||||
processApiConfig(protocol, apiPayloadData, configStr);
|
|
||||||
|
|
||||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
|
||||||
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
|
||||||
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
|
||||||
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
|
||||||
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
|
||||||
|
|
||||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
|
||||||
serverConfig[config_key::defaultContainer] = defaultContainer;
|
|
||||||
|
|
||||||
emit configUpdated(true, serverConfig, serverIndex);
|
|
||||||
} else {
|
} else {
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
|
@ -178,3 +268,156 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(7000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
QNetworkReply *reply;
|
||||||
|
reply = amnApp->manager()->get(request);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
m_proxyUrls = getProxyUrls();
|
||||||
|
for (const QString &proxyUrl : m_proxyUrls) {
|
||||||
|
request.setUrl(QString("%1v1/services").arg(proxyUrl));
|
||||||
|
reply = amnApp->manager()->get(request);
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
|
||||||
|
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody = reply->readAll();
|
||||||
|
auto errorCode = checkErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||||
|
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(7000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
|
||||||
|
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = userCountryCode;
|
||||||
|
if (!serverCountryCode.isEmpty()) {
|
||||||
|
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||||
|
}
|
||||||
|
apiPayload[configKey::serviceType] = serviceType;
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
|
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||||
|
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
|
QJsonObject keyPayload;
|
||||||
|
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||||
|
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||||
|
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||||
|
|
||||||
|
QByteArray encryptedKeyPayload;
|
||||||
|
QByteArray encryptedApiPayload;
|
||||||
|
try {
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
|
||||||
|
EVP_PKEY *publicKey = nullptr;
|
||||||
|
try {
|
||||||
|
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||||
|
} catch (...) {
|
||||||
|
qCritical() << "error loading public key from environment variables";
|
||||||
|
return ErrorCode::ApiMissingAgwPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
qCritical() << "error when encrypting the request body";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject requestBody;
|
||||||
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
|
QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
if (m_proxyUrls.isEmpty()) {
|
||||||
|
m_proxyUrls = getProxyUrls();
|
||||||
|
}
|
||||||
|
for (const QString &proxyUrl : m_proxyUrls) {
|
||||||
|
request.setUrl(QString("%1v1/config").arg(proxyUrl));
|
||||||
|
reply = manager.post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
|
||||||
|
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = checkErrors(sslErrors, reply);
|
||||||
|
if (errorCode) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
try {
|
||||||
|
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
|
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
||||||
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,18 @@ class ApiController : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ApiController(QObject *parent = nullptr);
|
explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||||
|
|
||||||
|
ErrorCode getServicesList(QByteArray &responseBody);
|
||||||
|
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||||
|
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
void finished(const QJsonObject &config, const int serverIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ApiPayloadData
|
struct ApiPayloadData
|
||||||
|
|
@ -34,7 +38,13 @@ private:
|
||||||
|
|
||||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
||||||
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody,
|
||||||
|
QJsonObject &serverConfig);
|
||||||
|
QStringList getProxyUrls();
|
||||||
|
|
||||||
|
QString m_gatewayEndpoint;
|
||||||
|
QStringList m_proxyUrls;
|
||||||
|
bool m_isDevEnvironment = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APICONTROLLER_H
|
#endif // APICONTROLLER_H
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug().noquote() << lineToExec;
|
qDebug().noquote() << lineToExec;
|
||||||
Logger::appendSshLog("Run command:" + lineToExec);
|
|
||||||
|
|
||||||
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
|
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
|
|
@ -100,13 +99,13 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||||
{
|
{
|
||||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||||
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
|
||||||
|
|
||||||
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
QString runner =
|
||||||
|
QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
||||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||||
|
|
@ -426,7 +425,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
||||||
if (errorCode)
|
if (errorCode)
|
||||||
return errorCode;
|
return errorCode;
|
||||||
|
|
||||||
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),dockerFilePath);
|
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), dockerFilePath);
|
||||||
|
|
||||||
if (errorCode)
|
if (errorCode)
|
||||||
return errorCode;
|
return errorCode;
|
||||||
|
|
@ -437,9 +436,10 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
errorCode = runScript(credentials,
|
errorCode =
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
runScript(credentials,
|
||||||
cbReadStdOut);
|
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||||
|
cbReadStdOut);
|
||||||
if (errorCode)
|
if (errorCode)
|
||||||
return errorCode;
|
return errorCode;
|
||||||
|
|
||||||
|
|
@ -621,13 +621,15 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||||
|
|
||||||
// Socks5 proxy vars
|
// Socks5 proxy vars
|
||||||
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||||
auto username = socks5ProxyConfig.value(config_key:: userName).toString();
|
auto username = socks5ProxyConfig.value(config_key::userName).toString();
|
||||||
auto password = socks5ProxyConfig.value(config_key::password).toString();
|
auto password = socks5ProxyConfig.value(config_key::password).toString();
|
||||||
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
|
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
|
||||||
vars.append({ { "$SOCKS5_USER", socks5user } });
|
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||||
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||||
|
|
||||||
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
||||||
|
? NetworkUtilities::getIPAddress(credentials.hostName)
|
||||||
|
: credentials.hostName;
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -713,7 +715,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||||
udpProtoScript.append("' | grep -i udp");
|
udpProtoScript.append("' | grep -i udp");
|
||||||
tcpProtoScript.append(" | grep LISTEN");
|
tcpProtoScript.append(" | grep LISTEN");
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
ErrorCode errorCode =
|
||||||
|
runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,13 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
|
||||||
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
|
||||||
|
// add mtu for old configs
|
||||||
|
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
||||||
|
vpnConfigData[config_key::mtu] = container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData);
|
vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ namespace amnezia
|
||||||
|
|
||||||
// import and install errors
|
// import and install errors
|
||||||
ImportInvalidConfigError = 900,
|
ImportInvalidConfigError = 900,
|
||||||
|
ImportOpenConfigError = 901,
|
||||||
|
|
||||||
// Android errors
|
// Android errors
|
||||||
AndroidError = 1000,
|
AndroidError = 1000,
|
||||||
|
|
@ -106,6 +107,7 @@ namespace amnezia
|
||||||
ApiConfigEmptyError = 1102,
|
ApiConfigEmptyError = 1102,
|
||||||
ApiConfigTimeoutError = 1103,
|
ApiConfigTimeoutError = 1103,
|
||||||
ApiConfigSslError = 1104,
|
ApiConfigSslError = 1104,
|
||||||
|
ApiMissingAgwPublicKey = 1105,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|
|
||||||
9
client/core/enums/apiEnums.h
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#ifndef APIENUMS_H
|
||||||
|
#define APIENUMS_H
|
||||||
|
|
||||||
|
enum ApiConfigSources {
|
||||||
|
Telegram = 1,
|
||||||
|
AmneziaGateway
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APIENUMS_H
|
||||||
|
|
@ -50,6 +50,7 @@ QString errorString(ErrorCode code) {
|
||||||
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
||||||
|
|
||||||
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||||
|
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr(""); break;
|
||||||
|
|
||||||
// Android errors
|
// Android errors
|
||||||
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||||
|
|
@ -60,6 +61,7 @@ QString errorString(ErrorCode code) {
|
||||||
case (ErrorCode::ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
|
case (ErrorCode::ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
|
||||||
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
||||||
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
||||||
|
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@ QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
||||||
return Instance()->m_ipcClient;
|
return Instance()->m_ipcClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
||||||
|
{
|
||||||
|
if (!Instance()) return nullptr;
|
||||||
|
return Instance()->m_Tun2SocksClient;
|
||||||
|
}
|
||||||
|
|
||||||
bool IpcClient::init(IpcClient *instance)
|
bool IpcClient::init(IpcClient *instance)
|
||||||
{
|
{
|
||||||
m_instance = instance;
|
m_instance = instance;
|
||||||
|
|
@ -44,6 +50,12 @@ bool IpcClient::init(IpcClient *instance)
|
||||||
qWarning() << "IpcClient replica is not connected!";
|
qWarning() << "IpcClient replica is not connected!";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Instance()->m_Tun2SocksClient.reset(Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>());
|
||||||
|
Instance()->m_Tun2SocksClient->waitForSource(1000);
|
||||||
|
|
||||||
|
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
|
||||||
|
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(Instance()->m_localSocket, &QLocalSocket::disconnected, [instance](){
|
connect(Instance()->m_localSocket, &QLocalSocket::disconnected, [instance](){
|
||||||
|
|
@ -51,16 +63,16 @@ bool IpcClient::init(IpcClient *instance)
|
||||||
});
|
});
|
||||||
|
|
||||||
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
|
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
|
||||||
|
|
||||||
Instance()->m_localSocket->waitForConnected();
|
Instance()->m_localSocket->waitForConnected();
|
||||||
|
|
||||||
if (!Instance()->m_ipcClient) {
|
if (!Instance()->m_ipcClient) {
|
||||||
qDebug() << "IpcClient::init failed";
|
qDebug() << "IpcClient::init failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "IpcClient::init succeed";
|
qDebug() << "IpcClient::init succeed";
|
||||||
|
|
||||||
return Instance()->m_ipcClient->isReplicaValid();
|
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
#include "rep_ipc_interface_replica.h"
|
#include "rep_ipc_interface_replica.h"
|
||||||
|
#include "rep_ipc_process_tun2socks_replica.h"
|
||||||
|
|
||||||
#include "privileged_process.h"
|
#include "privileged_process.h"
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ public:
|
||||||
static IpcClient *Instance();
|
static IpcClient *Instance();
|
||||||
static bool init(IpcClient *instance);
|
static bool init(IpcClient *instance);
|
||||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||||
|
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
||||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||||
|
|
||||||
bool isSocketConnected() const;
|
bool isSocketConnected() const;
|
||||||
|
|
@ -28,8 +30,11 @@ private:
|
||||||
~IpcClient() override;
|
~IpcClient() override;
|
||||||
|
|
||||||
QRemoteObjectNode m_ClientNode;
|
QRemoteObjectNode m_ClientNode;
|
||||||
|
QRemoteObjectNode m_Tun2SocksNode;
|
||||||
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
|
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
|
||||||
QPointer<QLocalSocket> m_localSocket;
|
QPointer<QLocalSocket> m_localSocket;
|
||||||
|
QPointer<QLocalSocket> m_tun2socksSocket;
|
||||||
|
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
|
||||||
|
|
||||||
struct ProcessDescriptor {
|
struct ProcessDescriptor {
|
||||||
ProcessDescriptor () {
|
ProcessDescriptor () {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,10 @@ QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QStr
|
||||||
|
|
||||||
QString NetworkUtilities::getIPAddress(const QString &host)
|
QString NetworkUtilities::getIPAddress(const QString &host)
|
||||||
{
|
{
|
||||||
if (ipAddressRegExp().match(host).hasMatch()) {
|
QHostAddress address(host);
|
||||||
|
if (QAbstractSocket::IPv4Protocol == address.protocol()) {
|
||||||
|
return host;
|
||||||
|
} else if (QAbstractSocket::IPv6Protocol == address.protocol()) {
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 14 KiB |
|
|
@ -1,18 +0,0 @@
|
||||||
<svg width="280" height="280" viewBox="0 0 280 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g filter="url(#filter0_f_1379_19114)">
|
|
||||||
<circle cx="140" cy="140" r="107.5" stroke="#FBB36A"/>
|
|
||||||
</g>
|
|
||||||
<circle cx="140" cy="140" r="107" stroke="#FBB36A" stroke-width="2"/>
|
|
||||||
<circle cx="140" cy="140" r="107" stroke="url(#paint0_linear_1379_19114)" stroke-width="2"/>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_f_1379_19114" x="2" y="2" width="276" height="276" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="15" result="effect1_foregroundBlur_1379_19114"/>
|
|
||||||
</filter>
|
|
||||||
<linearGradient id="paint0_linear_1379_19114" x1="-2.43527" y1="89.3291" x2="192.652" y2="11.9798" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#E0AA84"/>
|
|
||||||
<stop offset="1" stop-color="#DF7D37"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 969 B |
|
|
@ -1,17 +0,0 @@
|
||||||
<svg width="280" height="280" viewBox="0 0 280 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g filter="url(#filter0_d_1379_19118)">
|
|
||||||
<path d="M140 235C127.524 235 115.171 232.543 103.645 227.769C92.1191 222.994 81.6464 215.997 72.8248 207.175C64.0033 198.354 57.0056 187.881 52.2314 176.355C47.4572 164.829 45 152.476 45 140C45 127.524 47.4572 115.171 52.2314 103.645C57.0056 92.1191 64.0033 81.6464 72.8249 72.8248C81.6464 64.0033 92.1191 57.0056 103.645 52.2314C115.171 47.4572 127.524 45 140 45C152.476 45 164.829 47.4572 176.355 52.2314C187.881 57.0056 198.354 64.0033 207.175 72.8249C215.997 81.6464 222.994 92.1192 227.769 103.645C232.543 115.171 235 127.524 235 140C235 152.476 232.543 164.829 227.769 176.355C222.994 187.881 215.997 198.354 207.175 207.175C198.354 215.997 187.881 222.994 176.355 227.769C164.829 232.543 152.476 235 140 235L140 235Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_d_1379_19118" x="38" y="38" width="204" height="204" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset/>
|
|
||||||
<feGaussianBlur stdDeviation="3"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="out"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.2 0"/>
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1379_19118"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1379_19118" result="shape"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,30 +0,0 @@
|
||||||
<svg width="280" height="280" viewBox="0 0 280 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g opacity="0.1" filter="url(#filter0_d_1379_19115)">
|
|
||||||
<path d="M235 140C235 152.476 232.543 164.829 227.769 176.355C222.994 187.881 215.997 198.354 207.175 207.175C198.354 215.997 187.881 222.994 176.355 227.769C164.829 232.543 152.476 235 140 235C127.524 235 115.171 232.543 103.645 227.769C92.1191 222.994 81.6464 215.997 72.8249 207.175C64.0033 198.354 57.0056 187.881 52.2314 176.355C47.4572 164.829 45 152.476 45 140C45 127.524 47.4572 115.171 52.2314 103.645C57.0056 92.1191 64.0033 81.6464 72.8249 72.8248C81.6464 64.0033 92.1192 57.0056 103.645 52.2314C115.171 47.4572 127.524 45 140 45C152.476 45 164.829 47.4573 176.355 52.2314C187.881 57.0056 198.354 64.0033 207.175 72.8249C215.997 81.6464 222.994 92.1192 227.769 103.645C232.543 115.171 235 127.524 235 140L235 140Z" stroke="#FBB36A"/>
|
|
||||||
</g>
|
|
||||||
<g filter="url(#filter1_d_1379_19115)">
|
|
||||||
<path d="M140 235C126.016 235 112.204 231.913 99.551 225.959C86.8977 220.004 75.7151 211.33 66.8012 200.555C57.8874 189.78 51.4623 177.17 47.9846 163.626C44.5069 150.081 44.0623 135.935 46.6827 122.199C49.3031 108.462 54.9237 95.4738 63.1434 84.1604C71.363 72.847 81.979 63.4878 94.2334 56.7509C106.488 50.014 120.078 46.0655 134.035 45.1875C147.991 44.3094 161.97 46.5233 174.972 51.6712" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_d_1379_19115" x="38.5" y="38.5" width="203" height="203" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset/>
|
|
||||||
<feGaussianBlur stdDeviation="3"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="out"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.984314 0 0 0 0 0.717647 0 0 0 0 0.317647 0 0 0 1 0"/>
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1379_19115"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1379_19115" result="shape"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter1_d_1379_19115" x="38" y="38" width="143.973" height="204" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset/>
|
|
||||||
<feGaussianBlur stdDeviation="3"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="out"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.988235 0 0 0 0 0.301961 0 0 0 0 0.0705883 0 0 0 0.49 0"/>
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1379_19115"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1379_19115" result="shape"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB |
7
client/images/controls/archive-restore.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 4H4C2.89543 4 2 4.89543 2 6V7C2 8.10457 2.89543 9 4 9H20C21.1046 9 22 8.10457 22 7V6C22 4.89543 21.1046 4 20 4Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 13V20" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 16L12 13L15 16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 9V18C4 18.5304 4.21071 19.0391 4.58579 19.4142C4.96086 19.7893 5.46957 20 6 20H8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20 9V18C20 18.5304 19.7893 19.0391 19.4142 19.4142C19.0391 19.7893 18.5304 20 18 20H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 884 B |
11
client/images/controls/bug.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16 10C16 7.79086 14.2091 6 12 6C9.79086 6 8 7.79086 8 10V16C8 18.2091 9.79086 20 12 20C14.2091 20 16 18.2091 16 16V10Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19 7L16 9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5 7L8 9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19 19L16 17" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5 19L8 17" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20 13H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 13H8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 4L11 6" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 4L13 6" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
5
client/images/controls/folder-search-2.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 20H20C20.5304 20 21.0391 19.7893 21.4142 19.4142C21.7893 19.0391 22 18.5304 22 18V8C22 7.46957 21.7893 6.96086 21.4142 6.58579C21.0391 6.21071 20.5304 6 20 6H12.07C11.7406 5.9983 11.4167 5.91525 11.1271 5.75824C10.8375 5.60123 10.5912 5.37512 10.41 5.1L9.59 3.9C9.40882 3.62488 9.1625 3.39877 8.8729 3.24176C8.58331 3.08475 8.25941 3.0017 7.93 3H4C3.46957 3 2.96086 3.21071 2.58579 3.58579C2.21071 3.96086 2 4.46957 2 5V18C2 19.1 2.9 20 4 20Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.5 15C12.8807 15 14 13.8807 14 12.5C14 11.1193 12.8807 10 11.5 10C10.1193 10 9 11.1193 9 12.5C9 13.8807 10.1193 15 11.5 15Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.27 14.27L15 16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 974 B |
4
client/images/controls/gauge.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 15L15.5 11.5" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20.3 18C20.7 17 21 15.8 21 14.6C21 9.8 17 6 12 6C7 6 3 9.8 3 14.6C3 15.8 3.3 17 3.7 18" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 394 B |
5
client/images/controls/help-circle.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9.09009 9.00008C9.32519 8.33175 9.78924 7.76819 10.4 7.40921C11.0108 7.05024 11.729 6.91902 12.4273 7.03879C13.1255 7.15857 13.7589 7.52161 14.2152 8.06361C14.6714 8.60561 14.9211 9.2916 14.9201 10.0001C14.9201 12.0001 11.9201 13.0001 11.9201 13.0001" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 17H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 761 B |
5
client/images/controls/history.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 3V8H8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.05 13.0001C3.27151 15.0059 4.1607 16.8791 5.57478 18.3188C6.98886 19.7585 8.84577 20.6812 10.8473 20.9387C12.8488 21.1961 14.8788 20.7735 16.6112 19.7386C18.3436 18.7036 19.678 17.1165 20.3999 15.2321C21.1219 13.3476 21.1896 11.2752 20.5921 9.34766C19.9947 7.42012 18.7668 5.7493 17.1056 4.60353C15.4444 3.45776 13.4463 2.90354 11.4322 3.02987C9.41817 3.15619 7.50501 3.95574 6 5.30009L3 8.00009" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 7V12L16 14" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 805 B |
5
client/images/controls/info.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 16V12" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 8H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 518 B |
4
client/images/controls/map-pin.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 10C20 16 12 22 12 22C12 22 4 16 4 10C4 7.87827 4.84285 5.84344 6.34315 4.34315C7.84344 2.84285 9.87827 2 12 2C14.1217 2 16.1566 2.84285 17.6569 4.34315C19.1571 5.84344 20 7.87827 20 10V10Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 13C13.6569 13 15 11.6569 15 10C15 8.34315 13.6569 7 12 7C10.3431 7 9 8.34315 9 10C9 11.6569 10.3431 13 12 13Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 596 B |
6
client/images/controls/refresh-cw.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21 2V8H15" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 12.0001C3.00158 10.2634 3.5056 8.56429 4.45125 7.10764C5.39691 5.651 6.74382 4.49907 8.32951 3.79079C9.9152 3.08252 11.6719 2.84815 13.3879 3.11596C15.1038 3.38377 16.7056 4.14232 18 5.30011L21 8.00011" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 22V16H9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21 12C20.9984 13.7367 20.4944 15.4358 19.5487 16.8925C18.6031 18.3491 17.2562 19.501 15.6705 20.2093C14.0848 20.9176 12.3281 21.152 10.6121 20.8841C8.89623 20.6163 7.29445 19.8578 6 18.7L3 16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 895 B |
7
client/images/controls/scan-line.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 7V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H7" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17 3H19C19.5304 3 20.0391 3.21071 20.4142 3.58579C20.7893 3.96086 21 4.46957 21 5V7" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21 17V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H17" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V17" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 12H17" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 917 B |
4
client/images/controls/tag.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2H2V12L11.29 21.29C12.23 22.23 13.77 22.23 14.71 21.29L21.29 14.71C22.23 13.77 22.23 12.23 21.29 11.29L12 2Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 7H7.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 412 B |