From 352237dd46418291d27072aab88de31a1b446ea1 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 3 Aug 2023 17:07:21 +0100 Subject: [PATCH 1/7] automations --- .github/FUNDING.yml | 1 + .github/workflows/dev2master.yml | 18 +++++++ .github/workflows/publish_alpha.yml | 72 +++++++++++++++++++++++++++ .github/workflows/publish_build.yml | 76 +++++++++++++++++++++++++++++ .github/workflows/publish_major.yml | 76 +++++++++++++++++++++++++++++ .github/workflows/publish_minor.yml | 76 +++++++++++++++++++++++++++++ poorman_handshake/version.py | 7 +++ scripts/bump_alpha.py | 18 +++++++ scripts/bump_build.py | 21 ++++++++ scripts/bump_major.py | 27 ++++++++++ scripts/bump_minor.py | 24 +++++++++ scripts/remove_alpha.py | 13 +++++ setup.py | 45 ++++++++++++++++- 13 files changed, 472 insertions(+), 2 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/dev2master.yml create mode 100644 .github/workflows/publish_alpha.yml create mode 100644 .github/workflows/publish_build.yml create mode 100644 .github/workflows/publish_major.yml create mode 100644 .github/workflows/publish_minor.yml create mode 100644 poorman_handshake/version.py create mode 100644 scripts/bump_alpha.py create mode 100644 scripts/bump_build.py create mode 100644 scripts/bump_major.py create mode 100644 scripts/bump_minor.py create mode 100644 scripts/remove_alpha.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..01ecd10 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +liberapay: jarbasAI diff --git a/.github/workflows/dev2master.yml b/.github/workflows/dev2master.yml new file mode 100644 index 0000000..5363b5e --- /dev/null +++ b/.github/workflows/dev2master.yml @@ -0,0 +1,18 @@ +name: Push dev -> master +on: + workflow_dispatch: + +jobs: + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + ref: dev + - name: Push dev -> master + uses: ad-m/github-push-action@master + + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: master \ No newline at end of file diff --git a/.github/workflows/publish_alpha.yml b/.github/workflows/publish_alpha.yml new file mode 100644 index 0000000..92b48ed --- /dev/null +++ b/.github/workflows/publish_alpha.yml @@ -0,0 +1,72 @@ +# This workflow will generate a distribution and upload it to PyPI + +name: Publish Alpha Build ...aX +on: + push: + branches: + - dev + paths-ignore: + - 'poorman_handshake/version.py' + - 'test/**' + - 'examples/**' + - '.github/**' + - '.gitignore' + - 'LICENSE' + - 'CHANGELOG.md' + - 'MANIFEST.in' + - 'readme.md' + - 'scripts/**' + workflow_dispatch: + +jobs: + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: Increment Version + run: | + VER=$(python setup.py --version) + python scripts/bump_alpha.py + - name: "Generate release changelog" + uses: heinrichreimer/github-changelog-generator-action@v2.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: changelog + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Increment Version + branch: dev + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: V${{ steps.version.outputs.version }} + release_name: Release ${{ steps.version.outputs.version }} + body: | + Changes in this Release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: true + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_build.yml b/.github/workflows/publish_build.yml new file mode 100644 index 0000000..9fdcd30 --- /dev/null +++ b/.github/workflows/publish_build.yml @@ -0,0 +1,76 @@ +# This workflow will generate a distribution and upload it to PyPI + +name: Publish Build Release ..X +on: + workflow_dispatch: + +jobs: + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: Remove alpha (declare stable) + run: | + VER=$(python setup.py --version) + python scripts/remove_alpha.py + - name: "Generate release changelog" + uses: heinrichreimer/github-changelog-generator-action@v2.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: changelog + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Declare alpha stable + branch: dev + - name: Push dev -> master + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: master + force: true + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: V${{ steps.version.outputs.version }} + release_name: Release ${{ steps.version.outputs.version }} + body: | + Changes in this Release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + - name: Prepare next Build version + run: echo "::set-output name=version::$(python setup.py --version)" + id: alpha + - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 + run: | + VER=$(python setup.py --version) + python scripts/bump_build.py + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Prepare Next Version + branch: dev + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_major.yml b/.github/workflows/publish_major.yml new file mode 100644 index 0000000..87cee86 --- /dev/null +++ b/.github/workflows/publish_major.yml @@ -0,0 +1,76 @@ +# This workflow will generate a distribution and upload it to PyPI + +name: Publish Major Release X.0.0 +on: + workflow_dispatch: + +jobs: + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: Remove alpha (declare stable) + run: | + VER=$(python setup.py --version) + python scripts/remove_alpha.py + - name: "Generate release changelog" + uses: heinrichreimer/github-changelog-generator-action@v2.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: changelog + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Declare alpha stable + branch: dev + - name: Push dev -> master + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: master + force: true + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: V${{ steps.version.outputs.version }} + release_name: Release ${{ steps.version.outputs.version }} + body: | + Changes in this Release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + - name: Prepare next Major version + run: echo "::set-output name=version::$(python setup.py --version)" + id: alpha + - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 + run: | + VER=$(python setup.py --version) + python scripts/bump_major.py + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Prepare Next Version + branch: dev + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_minor.yml b/.github/workflows/publish_minor.yml new file mode 100644 index 0000000..4e8b231 --- /dev/null +++ b/.github/workflows/publish_minor.yml @@ -0,0 +1,76 @@ +# This workflow will generate a distribution and upload it to PyPI + +name: Publish Minor Release .X.0 +on: + workflow_dispatch: + +jobs: + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: Remove alpha (declare stable) + run: | + VER=$(python setup.py --version) + python scripts/remove_alpha.py + - name: "Generate release changelog" + uses: heinrichreimer/github-changelog-generator-action@v2.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: changelog + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Declare alpha stable + branch: dev + - name: Push dev -> master + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: master + force: true + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: V${{ steps.version.outputs.version }} + release_name: Release ${{ steps.version.outputs.version }} + body: | + Changes in this Release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + - name: Prepare next Minor version + run: echo "::set-output name=version::$(python setup.py --version)" + id: alpha + - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 + run: | + VER=$(python setup.py --version) + python scripts/bump_minor.py + - name: Commit to dev + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Prepare Next Version + branch: dev + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/poorman_handshake/version.py b/poorman_handshake/version.py new file mode 100644 index 0000000..72eafb2 --- /dev/null +++ b/poorman_handshake/version.py @@ -0,0 +1,7 @@ +# The following lines are replaced during the release process. +# START_VERSION_BLOCK +VERSION_MAJOR = 0 +VERSION_MINOR = 2 +VERSION_BUILD = 2 +VERSION_ALPHA = 1 +# END_VERSION_BLOCK diff --git a/scripts/bump_alpha.py b/scripts/bump_alpha.py new file mode 100644 index 0000000..3d24e5a --- /dev/null +++ b/scripts/bump_alpha.py @@ -0,0 +1,18 @@ +import fileinput +from os.path import join, dirname + + +version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") +version_var_name = "VERSION_ALPHA" + +with open(version_file, "r", encoding="utf-8") as v: + for line in v.readlines(): + if line.startswith(version_var_name): + version = int(line.split("=")[-1]) + new_version = int(version) + 1 + +for line in fileinput.input(version_file, inplace=True): + if line.startswith(version_var_name): + print(f"{version_var_name} = {new_version}") + else: + print(line.rstrip('\n')) diff --git a/scripts/bump_build.py b/scripts/bump_build.py new file mode 100644 index 0000000..8b125a3 --- /dev/null +++ b/scripts/bump_build.py @@ -0,0 +1,21 @@ +import fileinput +from os.path import join, dirname + + +version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") +version_var_name = "VERSION_BUILD" +alpha_var_name = "VERSION_ALPHA" + +with open(version_file, "r", encoding="utf-8") as v: + for line in v.readlines(): + if line.startswith(version_var_name): + version = int(line.split("=")[-1]) + new_version = int(version) + 1 + +for line in fileinput.input(version_file, inplace=True): + if line.startswith(version_var_name): + print(f"{version_var_name} = {new_version}") + elif line.startswith(alpha_var_name): + print(f"{alpha_var_name} = 0") + else: + print(line.rstrip('\n')) diff --git a/scripts/bump_major.py b/scripts/bump_major.py new file mode 100644 index 0000000..1138da7 --- /dev/null +++ b/scripts/bump_major.py @@ -0,0 +1,27 @@ +import fileinput +from os.path import join, dirname + + +version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") +version_var_name = "VERSION_MAJOR" +minor_var_name = "VERSION_MINOR" +build_var_name = "VERSION_BUILD" +alpha_var_name = "VERSION_ALPHA" + +with open(version_file, "r", encoding="utf-8") as v: + for line in v.readlines(): + if line.startswith(version_var_name): + version = int(line.split("=")[-1]) + new_version = int(version) + 1 + +for line in fileinput.input(version_file, inplace=True): + if line.startswith(version_var_name): + print(f"{version_var_name} = {new_version}") + elif line.startswith(minor_var_name): + print(f"{minor_var_name} = 0") + elif line.startswith(build_var_name): + print(f"{build_var_name} = 0") + elif line.startswith(alpha_var_name): + print(f"{alpha_var_name} = 0") + else: + print(line.rstrip('\n')) diff --git a/scripts/bump_minor.py b/scripts/bump_minor.py new file mode 100644 index 0000000..526ac93 --- /dev/null +++ b/scripts/bump_minor.py @@ -0,0 +1,24 @@ +import fileinput +from os.path import join, dirname + + +version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") +version_var_name = "VERSION_MINOR" +build_var_name = "VERSION_BUILD" +alpha_var_name = "VERSION_ALPHA" + +with open(version_file, "r", encoding="utf-8") as v: + for line in v.readlines(): + if line.startswith(version_var_name): + version = int(line.split("=")[-1]) + new_version = int(version) + 1 + +for line in fileinput.input(version_file, inplace=True): + if line.startswith(version_var_name): + print(f"{version_var_name} = {new_version}") + elif line.startswith(build_var_name): + print(f"{build_var_name} = 0") + elif line.startswith(alpha_var_name): + print(f"{alpha_var_name} = 0") + else: + print(line.rstrip('\n')) diff --git a/scripts/remove_alpha.py b/scripts/remove_alpha.py new file mode 100644 index 0000000..0771912 --- /dev/null +++ b/scripts/remove_alpha.py @@ -0,0 +1,13 @@ +import fileinput +from os.path import join, dirname + + +version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") + +alpha_var_name = "VERSION_ALPHA" + +for line in fileinput.input(version_file, inplace=True): + if line.startswith(alpha_var_name): + print(f"{alpha_var_name} = 0") + else: + print(line.rstrip('\n')) diff --git a/setup.py b/setup.py index fd792cb..97aa64c 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,55 @@ +import os from setuptools import setup +BASEDIR = os.path.abspath(os.path.dirname(__file__)) + + +def get_version(): + """ Find the version of the package""" + version = None + version_file = os.path.join(BASEDIR, 'poorman_handshake', 'version.py') + major, minor, build, alpha = (None, None, None, None) + with open(version_file) as f: + for line in f: + if 'VERSION_MAJOR' in line: + major = line.split('=')[1].strip() + elif 'VERSION_MINOR' in line: + minor = line.split('=')[1].strip() + elif 'VERSION_BUILD' in line: + build = line.split('=')[1].strip() + elif 'VERSION_ALPHA' in line: + alpha = line.split('=')[1].strip() + + if ((major and minor and build and alpha) or + '# END_VERSION_BLOCK' in line): + break + version = f"{major}.{minor}.{build}" + if alpha: + version += f"a{alpha}" + return version + + +def required(requirements_file): + """ Read requirements file and remove comments and empty lines. """ + with open(os.path.join(BASEDIR, requirements_file), 'r') as f: + requirements = f.read().splitlines() + if 'MYCROFT_LOOSE_REQUIREMENTS' in os.environ: + print('USING LOOSE REQUIREMENTS!') + requirements = [r.replace('==', '>=').replace('~=', '>=') for r in requirements] + return [pkg for pkg in requirements + if pkg.strip() and not pkg.startswith("#")] + + + setup( name='poorman_handshake', - version='0.2.1', + version=get_version(), packages=['poorman_handshake', 'poorman_handshake.asymmetric', 'poorman_handshake.symmetric'], url='https://github.com/JarbasHiveMind/poorman_handshake', license='Apache-2.0', author='jarbasAi', - install_requires=["PGPy"], + install_requires=required("requirements.txt"), author_email='jarbasai@mailfence.com', description='poor man\'s key exchange, powered by RSA' ) From 7cfa30dca21a5a9bc7d0814aee066b8ef542c350 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 3 Aug 2023 16:08:09 +0000 Subject: [PATCH 2/7] Increment Version --- CHANGELOG.md | 9 +++++++++ poorman_handshake/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dd5cee5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## [0.2.0](https://github.com/JarbasHiveMind/poorman_handshake/tree/0.2.0) (2021-02-14) + +[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/5314fd298768286eda26e13382281a62926579be...0.2.0) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/poorman_handshake/version.py b/poorman_handshake/version.py index 72eafb2..40c7479 100644 --- a/poorman_handshake/version.py +++ b/poorman_handshake/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 2 VERSION_BUILD = 2 -VERSION_ALPHA = 1 +VERSION_ALPHA = 2 # END_VERSION_BLOCK From 9853a586d4531ae384abfab63b27209eb0b46411 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Thu, 3 Aug 2023 16:12:06 +0000 Subject: [PATCH 3/7] Increment Version --- CHANGELOG.md | 4 ++++ poorman_handshake/version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd5cee5..60a1447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [V0.2.2a2](https://github.com/JarbasHiveMind/poorman_handshake/tree/V0.2.2a2) (2023-08-03) + +[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/0.2.0...V0.2.2a2) + ## [0.2.0](https://github.com/JarbasHiveMind/poorman_handshake/tree/0.2.0) (2021-02-14) [Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/5314fd298768286eda26e13382281a62926579be...0.2.0) diff --git a/poorman_handshake/version.py b/poorman_handshake/version.py index 40c7479..def76d9 100644 --- a/poorman_handshake/version.py +++ b/poorman_handshake/version.py @@ -3,5 +3,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 2 VERSION_BUILD = 2 -VERSION_ALPHA = 2 +VERSION_ALPHA = 3 # END_VERSION_BLOCK From 03550949bf0aa4e8075b4ee160c15a2128f80fad Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Mon, 30 Dec 2024 05:11:09 +0000 Subject: [PATCH 4/7] refactor!: use RSA directly, drop PGPy (#1) * refactor!: use RSA directly, drop PGPy * semver * error log to help migration * regen key * @coderabbitai review * typing * backwards compat * backwards compat --- .github/workflows/conventional-label.yaml | 10 + .github/workflows/dev2master.yml | 18 -- .github/workflows/publish_alpha.yml | 72 ------- .github/workflows/publish_build.yml | 76 ------- .github/workflows/publish_major.yml | 76 ------- .github/workflows/publish_minor.yml | 76 ------- .github/workflows/publish_stable.yml | 58 ++++++ .github/workflows/release_workflow.yml | 108 ++++++++++ readme.md => README.md | 0 examples/half_handshake.py | 2 +- examples/key_handling.py | 5 - examples/simple_handshake.py | 2 +- examples/simple_mitm.py | 2 +- examples/static_handshake.py | 2 +- examples/static_mitm.py | 2 +- examples/tofu_handshake.py | 2 +- examples/tofu_mitm.py | 2 +- poorman_handshake/asymmetric/__init__.py | 239 +++++++++++++++------- poorman_handshake/asymmetric/utils.py | 236 ++++++++++++++++++--- poorman_handshake/symmetric/utils.py | 13 +- requirements.txt | 2 +- scripts/bump_alpha.py | 18 -- scripts/bump_build.py | 21 -- scripts/bump_major.py | 27 --- scripts/bump_minor.py | 24 --- scripts/remove_alpha.py | 13 -- 26 files changed, 561 insertions(+), 545 deletions(-) create mode 100644 .github/workflows/conventional-label.yaml delete mode 100644 .github/workflows/dev2master.yml delete mode 100644 .github/workflows/publish_alpha.yml delete mode 100644 .github/workflows/publish_build.yml delete mode 100644 .github/workflows/publish_major.yml delete mode 100644 .github/workflows/publish_minor.yml create mode 100644 .github/workflows/publish_stable.yml create mode 100644 .github/workflows/release_workflow.yml rename readme.md => README.md (100%) delete mode 100644 examples/key_handling.py delete mode 100644 scripts/bump_alpha.py delete mode 100644 scripts/bump_build.py delete mode 100644 scripts/bump_major.py delete mode 100644 scripts/bump_minor.py delete mode 100644 scripts/remove_alpha.py diff --git a/.github/workflows/conventional-label.yaml b/.github/workflows/conventional-label.yaml new file mode 100644 index 0000000..0a449cb --- /dev/null +++ b/.github/workflows/conventional-label.yaml @@ -0,0 +1,10 @@ +# auto add labels to PRs +on: + pull_request_target: + types: [ opened, edited ] +name: conventional-release-labels +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: bcoe/conventional-release-labels@v1 \ No newline at end of file diff --git a/.github/workflows/dev2master.yml b/.github/workflows/dev2master.yml deleted file mode 100644 index 5363b5e..0000000 --- a/.github/workflows/dev2master.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Push dev -> master -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - ref: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master \ No newline at end of file diff --git a/.github/workflows/publish_alpha.yml b/.github/workflows/publish_alpha.yml deleted file mode 100644 index 92b48ed..0000000 --- a/.github/workflows/publish_alpha.yml +++ /dev/null @@ -1,72 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Alpha Build ...aX -on: - push: - branches: - - dev - paths-ignore: - - 'poorman_handshake/version.py' - - 'test/**' - - 'examples/**' - - '.github/**' - - '.gitignore' - - 'LICENSE' - - 'CHANGELOG.md' - - 'MANIFEST.in' - - 'readme.md' - - 'scripts/**' - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Increment Version - run: | - VER=$(python setup.py --version) - python scripts/bump_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Increment Version - branch: dev - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: true - - name: Build Distribution Packages - run: | - python setup.py bdist_wheel - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_build.yml b/.github/workflows/publish_build.yml deleted file mode 100644 index 9fdcd30..0000000 --- a/.github/workflows/publish_build.yml +++ /dev/null @@ -1,76 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Build Release ..X -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Remove alpha (declare stable) - run: | - VER=$(python setup.py --version) - python scripts/remove_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Declare alpha stable - branch: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - - name: Build Distribution Packages - run: | - python setup.py bdist_wheel - - name: Prepare next Build version - run: echo "::set-output name=version::$(python setup.py --version)" - id: alpha - - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 - run: | - VER=$(python setup.py --version) - python scripts/bump_build.py - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Prepare Next Version - branch: dev - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_major.yml b/.github/workflows/publish_major.yml deleted file mode 100644 index 87cee86..0000000 --- a/.github/workflows/publish_major.yml +++ /dev/null @@ -1,76 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Major Release X.0.0 -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Remove alpha (declare stable) - run: | - VER=$(python setup.py --version) - python scripts/remove_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Declare alpha stable - branch: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - - name: Build Distribution Packages - run: | - python setup.py bdist_wheel - - name: Prepare next Major version - run: echo "::set-output name=version::$(python setup.py --version)" - id: alpha - - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 - run: | - VER=$(python setup.py --version) - python scripts/bump_major.py - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Prepare Next Version - branch: dev - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_minor.yml b/.github/workflows/publish_minor.yml deleted file mode 100644 index 4e8b231..0000000 --- a/.github/workflows/publish_minor.yml +++ /dev/null @@ -1,76 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Minor Release .X.0 -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Remove alpha (declare stable) - run: | - VER=$(python setup.py --version) - python scripts/remove_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Declare alpha stable - branch: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - - name: Build Distribution Packages - run: | - python setup.py bdist_wheel - - name: Prepare next Minor version - run: echo "::set-output name=version::$(python setup.py --version)" - id: alpha - - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 - run: | - VER=$(python setup.py --version) - python scripts/bump_minor.py - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Prepare Next Version - branch: dev - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_stable.yml b/.github/workflows/publish_stable.yml new file mode 100644 index 0000000..2e67dc2 --- /dev/null +++ b/.github/workflows/publish_stable.yml @@ -0,0 +1,58 @@ +name: Stable Release +on: + push: + branches: [master] + workflow_dispatch: + +jobs: + publish_stable: + uses: TigreGotico/gh-automations/.github/workflows/publish-stable.yml@master + secrets: inherit + with: + branch: 'master' + version_file: 'poorman_handshake/version.py' + setup_py: 'setup.py' + publish_release: true + + publish_pypi: + needs: publish_stable + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Build Distribution Packages + run: | + python setup.py sdist bdist_wheel + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} + + + sync_dev: + needs: publish_stable + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + ref: master + - name: Push master -> dev + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: dev \ No newline at end of file diff --git a/.github/workflows/release_workflow.yml b/.github/workflows/release_workflow.yml new file mode 100644 index 0000000..4655830 --- /dev/null +++ b/.github/workflows/release_workflow.yml @@ -0,0 +1,108 @@ +name: Release Alpha and Propose Stable + +on: + pull_request: + types: [closed] + branches: [dev] + +jobs: + publish_alpha: + if: github.event.pull_request.merged == true + uses: TigreGotico/gh-automations/.github/workflows/publish-alpha.yml@master + secrets: inherit + with: + branch: 'dev' + version_file: 'poorman_handshake/version.py' + setup_py: 'setup.py' + update_changelog: true + publish_prerelease: true + changelog_max_issues: 100 + + notify: + if: github.event.pull_request.merged == true + needs: publish_alpha + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Send message to Matrix bots channel + id: matrix-chat-message + uses: fadenb/matrix-chat-message@v0.0.6 + with: + homeserver: 'matrix.org' + token: ${{ secrets.MATRIX_TOKEN }} + channel: '!WjxEKjjINpyBRPFgxl:krbel.duckdns.org' + message: | + new ${{ github.event.repository.name }} PR merged! https://github.com/${{ github.repository }}/pull/${{ github.event.number }} + + publish_pypi: + needs: publish_alpha + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Build Distribution Packages + run: | + python setup.py sdist bdist_wheel + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} + + + propose_release: + needs: publish_alpha + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - name: Checkout dev branch + uses: actions/checkout@v3 + with: + ref: dev + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + - name: Get version from setup.py + id: get_version + run: | + VERSION=$(python setup.py --version) + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Create and push new branch + run: | + git checkout -b release-${{ env.VERSION }} + git push origin release-${{ env.VERSION }} + + - name: Open Pull Request from dev to master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Variables + BRANCH_NAME="release-${{ env.VERSION }}" + BASE_BRANCH="master" + HEAD_BRANCH="release-${{ env.VERSION }}" + PR_TITLE="Release ${{ env.VERSION }}" + PR_BODY="Human review requested!" + + # Create a PR using GitHub API + curl -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -d "{\"title\":\"$PR_TITLE\",\"body\":\"$PR_BODY\",\"head\":\"$HEAD_BRANCH\",\"base\":\"$BASE_BRANCH\"}" \ + https://api.github.com/repos/${{ github.repository }}/pulls + diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md diff --git a/examples/half_handshake.py b/examples/half_handshake.py index b85bb62..97b37f5 100644 --- a/examples/half_handshake.py +++ b/examples/half_handshake.py @@ -23,7 +23,7 @@ def do_the_shake(alice, bob): # read and verify handshakes alice.receive_and_verify(bob_shake) - print("Success", alice.secret) + print("Success", alice.secret.hex()) assert alice.secret == bob.secret diff --git a/examples/key_handling.py b/examples/key_handling.py deleted file mode 100644 index 99d77a4..0000000 --- a/examples/key_handling.py +++ /dev/null @@ -1,5 +0,0 @@ -from poorman_handshake import create_private_key, export_private_key - -export_private_key("key.asc") -export_private_key("key.bin", binary=True) - diff --git a/examples/simple_handshake.py b/examples/simple_handshake.py index a58f931..8987549 100644 --- a/examples/simple_handshake.py +++ b/examples/simple_handshake.py @@ -25,7 +25,7 @@ def do_the_shake(alice, bob): bob.receive_and_verify(alice_shake) alice.receive_and_verify(bob_shake) - print("Success", bob.secret) + print("Success", bob.secret.hex()) do_the_shake(alice, bob) \ No newline at end of file diff --git a/examples/simple_mitm.py b/examples/simple_mitm.py index e06e077..11328e7 100644 --- a/examples/simple_mitm.py +++ b/examples/simple_mitm.py @@ -25,7 +25,7 @@ def do_the_shake(alice, bob): bob.receive_and_verify(alice_shake) alice.receive_and_verify(bob_shake) - print("Success", bob.secret) + print("Success", bob.secret.hex()) do_the_shake(alice, bob) diff --git a/examples/static_handshake.py b/examples/static_handshake.py index 9802ada..fdcccc1 100644 --- a/examples/static_handshake.py +++ b/examples/static_handshake.py @@ -26,7 +26,7 @@ def do_the_shake(alice, bob): bob.receive_and_verify(alice_shake) alice.receive_and_verify(bob_shake) - print("Success", bob.secret) + print("Success", bob.secret.hex()) do_the_shake(alice, bob) diff --git a/examples/static_mitm.py b/examples/static_mitm.py index 1c8ec5b..7e41392 100644 --- a/examples/static_mitm.py +++ b/examples/static_mitm.py @@ -26,7 +26,7 @@ def do_the_shake(alice, bob): bob.receive_and_verify(alice_shake) alice.receive_and_verify(bob_shake) - print("Success", bob.secret) + print("Success", bob.secret.hex()) # eve pretends to be bob diff --git a/examples/tofu_handshake.py b/examples/tofu_handshake.py index 5052001..0dde92d 100644 --- a/examples/tofu_handshake.py +++ b/examples/tofu_handshake.py @@ -38,7 +38,7 @@ def do_the_shake(alice, bob): bob.receive_and_verify(alice_shake) alice.receive_and_verify(bob_shake) - print("Success", bob.secret) + print("Success", bob.secret.hex()) do_the_shake(alice, bob) # trust established diff --git a/examples/tofu_mitm.py b/examples/tofu_mitm.py index c6a56b4..7518044 100644 --- a/examples/tofu_mitm.py +++ b/examples/tofu_mitm.py @@ -39,7 +39,7 @@ def do_the_shake(alice, bob): bob.receive_and_verify(alice_shake) alice.receive_and_verify(bob_shake) - print("Success", bob.secret) + print("Success", bob.secret.hex()) do_the_shake(alice, bob) # trust established diff --git a/poorman_handshake/asymmetric/__init__.py b/poorman_handshake/asymmetric/__init__.py index 8754c3c..ec8c2af 100644 --- a/poorman_handshake/asymmetric/__init__.py +++ b/poorman_handshake/asymmetric/__init__.py @@ -1,94 +1,183 @@ +import os +from binascii import hexlify, unhexlify from os.path import isfile -from poorman_handshake.asymmetric.utils import * +from typing import Union, Optional + +import logging +import shutil +from Cryptodome.PublicKey import RSA +from Cryptodome.Random import get_random_bytes + +from poorman_handshake.asymmetric.utils import ( + load_RSA_key, + export_RSA_key, + decrypt_RSA, + encrypt_RSA, + sign_RSA, + verify_RSA, +) class HandShake: - def __init__(self, path=None, expires=None): - if path: - if path.endswith(".asc") or path.endswith(".txt"): - binary = False - else: - binary = True + """ + Class for performing a secure handshake using RSA encryption and signatures. + + Attributes: + private_key (RSA.RsaKey): The private RSA key for this handshake instance. + target_key (RSA.RsaKey): The public key of the target for the handshake. + secret (bytes): The shared secret generated during the handshake. + """ + + def __init__(self, path: str = None, key_size: int = 2048): + """ + Initializes the HandShake instance. + + Args: + path (str, optional): Path to load or save the private key. + key_size (int, optional): Size of the RSA key in bits (default is 2048). + """ + self.private_key = None if path and isfile(path): - self.load_private(path, binary=binary) - else: - self.private_key = create_private_key(expires=expires) + + try: + self.load_private(path) + except ValueError: + try: + backup_path = path + ".bak" + shutil.copy2(path, backup_path) + os.remove(path) + logging.warning( + f"Invalid RSA key format in '{path}'. " + f"Created backup at '{backup_path}' and will generate new key." + ) + except Exception as e: + raise ValueError(f"Failed to handle invalid key file: {e}") + + if not self.private_key: + self.private_key = RSA.generate(key_size) if path: - self.export_private_key(path, binary) + self.export_private_key(path) self.target_key = None self.secret = None - def load_private(self, path, binary=False): - if binary: - with open(path, "rb") as f: - key_blob = f.read() - else: - with open(path, "r") as f: - key_blob = f.read() - self.private_key = self.import_key(key_blob) + def load_private(self, path: str): + """ + Loads the private RSA key from a file. + + Args: + path (str): Path to the private key file. + """ + self.private_key = load_RSA_key(path) - def export_private_key(self, path, binary=False): - return export_private_key(path, self.private_key, binary) + def export_private_key(self, path: str): + """ + Exports the private RSA key to a file. + + Args: + path (str): Path to save the private key. + """ + secret = self.private_key.export_key(format="PEM") + export_RSA_key(secret, path) @property - def pubkey(self): + def pubkey(self) -> str: + """ + Returns the public key as a PEM-encoded string. + + Returns: + str: PEM-encoded public key. + """ if not self.private_key: return None - return str(self.private_key.pubkey) - - @staticmethod - def import_key(key_blob): - pubkey, _ = pgpy.PGPKey.from_blob(key_blob) - return pubkey - - def generate_handshake(self, pub=None): - pub = pub or self.target_key - # read pubkey from client - pubkey = self.import_key(pub) - # generate new key - self.secret = SymmetricKeyAlgorithm.AES256.gen_key() - text_message = pgpy.PGPMessage.new(self.secret) - # encrypt generated key - encrypted_message = pubkey.encrypt(text_message) - # sign message - # the bitwise OR operator '|' is used to add a signature to a PGPMessage. - encrypted_message |= self.private_key.sign(encrypted_message, - intended_recipients=[pubkey]) - return str(encrypted_message) - - def load_public(self, pub): - self.target_key = pub - - def receive_handshake(self, shake): - message_from_blob = pgpy.PGPMessage.from_blob(shake) - decrypted = self.private_key.decrypt(message_from_blob) - # XOR - self.secret = bytes(a ^ b for (a, b) in - zip(self.secret, decrypted.message)) - - def verify(self, shake, pub): - message = pgpy.PGPMessage.from_blob(shake) - pubkey = self.import_key(pub) - return pubkey.verify(message) - - def receive_and_verify(self, shake, pub=None): + return self.private_key.public_key().export_key(format="PEM").decode("utf-8") + + def generate_handshake(self, pub: Optional[Union[str, bytes, RSA.RsaKey]] = None) -> str: + """ + Generates a handshake message encrypted with the target's public key. + + Args: + pub (str, optional): Public key of the recipient in PEM format. + + Returns: + bytes: Hex-encoded handshake message (signature + ciphertext). + """ + pubkey = pub or self.target_key + self.secret = get_random_bytes(32) # Generate a new shared secret + ciphertext = encrypt_RSA(pubkey, self.secret) # Encrypt the secret + signature = sign_RSA(self.private_key, ciphertext) # Sign the ciphertext + return hexlify(signature + ciphertext).decode("utf-8") + + def load_public(self, pub: Union[str, bytes, RSA.RsaKey]): + """ + Loads the target's public RSA key. + + Args: + pub (str): Public key in PEM format. + """ + if isinstance(pub, RSA.RsaKey): + self.target_key = pub + else: + self.target_key = RSA.import_key(pub) + + def receive_handshake(self, shake: Union[str, bytes]): + """ + Processes a received handshake message to decrypt the shared secret. + + Args: + shake (bytes): Hex-encoded handshake message (signature + ciphertext). + """ + signature_size = self.private_key.size_in_bytes() + ciphertext = unhexlify(shake)[signature_size:] # Drop the signature + decrypted = decrypt_RSA(self.private_key, ciphertext) + # XOR the received secret with the existing one + self.secret = bytes(a ^ b for a, b in zip(self.secret, decrypted)) + + def verify(self, shake: Union[str, bytes], pub: Union[str, bytes, RSA.RsaKey]) -> bool: + """ + Verifies the signature in a handshake message. + + Args: + shake (bytes): Hex-encoded handshake message (signature + ciphertext). + pub (str): Public key in PEM format of the sender. + + Returns: + bool: True if the signature is valid, False otherwise. + """ + if isinstance(pub, RSA.RsaKey): + signature_size = pub.size_in_bytes() + else: + signature_size = RSA.import_key(pub).size_in_bytes() + ciphertext = unhexlify(shake) + signature = ciphertext[:signature_size] + ciphertext = ciphertext[signature_size:] + return verify_RSA(pub, ciphertext, signature) + + def receive_and_verify(self, shake: Union[str, bytes], + pub: Optional[Union[str, bytes, RSA.RsaKey]] = None): + """ + Verifies and processes a handshake message. + + Args: + shake (bytes): Hex-encoded handshake message (signature + ciphertext). + pub (str, optional): Public key in PEM format of the sender. + """ pub = pub or self.target_key - verified = self.verify(shake, pub) - if verified: + if self.verify(shake, pub): self.receive_handshake(shake) class HalfHandShake(HandShake): - - def generate_handshake(self, pub=None): - enc = super().generate_handshake(pub) - self.secret = bytes(self.secret) - return enc - - def receive_handshake(self, shake): - message_from_blob = pgpy.PGPMessage.from_blob(shake) - decrypted = self.private_key.decrypt(message_from_blob) - # XOR - self.secret = bytes(decrypted.message) - - + """ + A simpler handshake implementation where the shared secret is directly decrypted. + """ + + def receive_handshake(self, shake: Union[str, bytes]): + """ + Processes a received handshake message to decrypt the shared secret. + + Args: + shake (bytes): Hex-encoded handshake message (signature + ciphertext). + """ + signature_size = self.private_key.size_in_bytes() + ciphertext = unhexlify(shake)[signature_size:] # Drop the signature + self.secret = decrypt_RSA(self.private_key, ciphertext) diff --git a/poorman_handshake/asymmetric/utils.py b/poorman_handshake/asymmetric/utils.py index a85dc7d..f3e60bc 100644 --- a/poorman_handshake/asymmetric/utils.py +++ b/poorman_handshake/asymmetric/utils.py @@ -1,35 +1,215 @@ +import logging import os -import pgpy -from pgpy.constants import * -from datetime import datetime, timedelta +import warnings +from typing import Tuple, Union +from Cryptodome.Cipher import PKCS1_OAEP +from Cryptodome.Hash import SHA256 +from Cryptodome.PublicKey import RSA +from Cryptodome.Signature import pss -def export_private_key(path, key=None, binary=False, *args, **kwargs): - os.makedirs(os.path.dirname(path), exist_ok=True) - key = key or create_private_key(*args, **kwargs) - if binary: - with open(path, "wb") as f: - f.write(bytes(key)) - else: - with open(path, "w") as f: - f.write(str(key)) + +def export_private_key(path, key=None): + """ + Deprecated function for exporting an RSA private key. + Logs a deprecation warning and redirects to export_RSA_key. + + Args: + path (str): File path to save the key. + key: The RSA private key. + + Returns: + None + """ + warnings.warn( + "export_private_key is deprecated and will be removed in a future version. " + "Use export_RSA_key instead.", + DeprecationWarning, + stacklevel=2, + ) + logging.warning( + "export_private_key is deprecated and will be removed in a future version. Use export_RSA_key instead." + ) + key = key or RSA.generate(2048) + export_RSA_key(key, path) def create_private_key(name="PoorManHandshake", expires=None): - key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096) - uid = pgpy.PGPUID.new(name) - if isinstance(expires, timedelta): - expires = datetime.now() + expires - key.add_uid(uid, - usage={KeyFlags.Sign, - KeyFlags.EncryptCommunications}, - hashes=[HashAlgorithm.SHA512, - HashAlgorithm.SHA256], - ciphers=[SymmetricKeyAlgorithm.AES256, - SymmetricKeyAlgorithm.Camellia256], - compression=[CompressionAlgorithm.BZ2, - CompressionAlgorithm.ZIP, - CompressionAlgorithm.Uncompressed], - expiry_date=expires) - return key + """ + Deprecated function for creating an RSA private key. + Logs a deprecation warning and creates a new RSA key. + + Args: + name (str): Unused parameter for naming the key. + expires: Unused parameter for key expiration. + + Returns: + RSA.RsaKey: The generated RSA private key. + """ + warnings.warn( + "create_private_key is deprecated and will be removed in a future version. " + "Use create_RSA_key instead.", + DeprecationWarning, + stacklevel=2, + ) + logging.warning( + "create_private_key is deprecated and will be removed in a future version. Use create_RSA_key instead." + ) + k = RSA.generate(2048) + # add property that NodeIdentity expects for compat + k.pubkey = k.public_key().export_key(format="PEM").decode("utf-8") + return k + + +def export_RSA_key(key: Union[str, bytes, RSA.RsaKey], path: str): + """ + Exports an RSA key (public or private) to a file in PEM format. + + Args: + key (Union[str, bytes, RSA.RsaKey]): The RSA key to export. Can be a string, bytes, or an RSA.RsaKey object. + path (str): The file path where the key will be saved. + + Returns: + None + """ + base = os.path.dirname(path) + if base: + os.makedirs(base, exist_ok=True) + if isinstance(key, RSA.RsaKey): + key = key.export_key(format="PEM") + if isinstance(key, str): + key = key.encode("utf-8") + with open(path, "wb") as f: + f.write(key) + + +def load_RSA_key(path: str) -> RSA.RsaKey: + """ + Loads an RSA key (public or private) from a file. + + Args: + path (str): The file path to the PEM-formatted key. + + Returns: + RSA.RsaKey: The loaded RSA key. + """ + with open(path, "rb") as f: + return RSA.import_key(f.read()) + + +def create_RSA_key(key_size=2048) -> Tuple[str, str]: + """ + Generates a new RSA key pair. + + Args: + key_size (int, optional): The size of the RSA key in bits. Default is 2048. + + Returns: + Tuple[str, str]: A tuple containing the public key and private key as PEM-encoded strings. + """ + mykey = RSA.generate(key_size) + secret = mykey.export_key(format="PEM").decode("utf-8") + pub = mykey.public_key().export_key(format="PEM").decode("utf-8") + return pub, secret + + +def encrypt_RSA(public_key: Union[str, bytes, RSA.RsaKey], plaintext: Union[str, bytes]) -> bytes: + """ + Encrypts plaintext using an RSA public key. + + Args: + public_key (Union[str, bytes, RSA.RsaKey]): The RSA public key to use for encryption. + plaintext (Union[str, bytes]): The plaintext to encrypt. Can be a string or bytes. + + Returns: + bytes: The encrypted ciphertext. + """ + if isinstance(public_key, RSA.RsaKey): + key = public_key + else: + key = RSA.import_key(public_key) + cipher = PKCS1_OAEP.new(key) + if isinstance(plaintext, str): + plaintext = plaintext.encode("utf-8") + return cipher.encrypt(plaintext) + + +def decrypt_RSA(secret_key: Union[str, bytes, RSA.RsaKey], ciphertext: Union[str, bytes]) -> bytes: + """ + Decrypts ciphertext using an RSA private key. + + Args: + secret_key (Union[str, bytes, RSA.RsaKey]): The RSA private key to use for decryption. + ciphertext (Union[str, bytes]): The ciphertext to decrypt. Can be bytes or a string (if encoded). + + Returns: + bytes: The decrypted plaintext. + """ + if isinstance(secret_key, RSA.RsaKey): + key = secret_key + else: + key = RSA.import_key(secret_key) + cipher = PKCS1_OAEP.new(key) + if isinstance(ciphertext, str): + ciphertext = ciphertext.encode("utf-8") + return cipher.decrypt(ciphertext) + + +def sign_RSA(secret_key: Union[str, bytes, RSA.RsaKey], message: Union[str, bytes]) -> bytes: + """ + Signs a message using an RSA private key. + + Args: + secret_key (Union[str, bytes, RSA.RsaKey]): The RSA private key to use for signing. + message (Union[str, bytes]): The message to sign. Can be a string or bytes. + + Returns: + bytes: The signature of the message. + """ + if isinstance(secret_key, RSA.RsaKey): + key = secret_key + else: + key = RSA.import_key(secret_key) + if isinstance(message, str): + message = message.encode("utf-8") + h = SHA256.new(message) + return pss.new(key).sign(h) + + +def verify_RSA(public_key: Union[str, bytes, RSA.RsaKey], message: Union[str, bytes], + signature: bytes) -> bool: + """ + Verifies a signature using an RSA public key. + + Args: + public_key (Union[str, bytes, RSA.RsaKey]): The RSA public key to use for verification. + message (Union[str, bytes]): The message whose signature needs to be verified. Can be a string or bytes. + signature (bytes): The signature to verify. + + Returns: + bool: True if the signature is valid, False otherwise. + """ + if isinstance(public_key, RSA.RsaKey): + key = public_key + else: + key = RSA.import_key(public_key) + if isinstance(message, str): + message = message.encode("utf-8") + h = SHA256.new(message) + verifier = pss.new(key) + try: + verifier.verify(h, signature) + return True + except (ValueError, TypeError): + return False + + +if __name__ == "__main__": + pub, sec = create_RSA_key() + m = "attack at dawn" + encrypted = encrypt_RSA(pub, m) + assert decrypt_RSA(sec, encrypted).decode("utf-8") == m + signature = sign_RSA(sec, encrypted) + print(verify_RSA(pub, encrypted, signature)) + print(len(signature)) diff --git a/poorman_handshake/symmetric/utils.py b/poorman_handshake/symmetric/utils.py index 3525be7..9ec4b26 100644 --- a/poorman_handshake/symmetric/utils.py +++ b/poorman_handshake/symmetric/utils.py @@ -1,13 +1,10 @@ -from hashlib import sha256 -import string -import random +from Cryptodome.Hash import SHA256 +from Cryptodome.Random import get_random_bytes -# TODO os.urandom -def generate_iv(key_lenght=8): +def generate_iv(key_length=8): """Generate a random string of letters and digits """ - valid_chars = string.ascii_letters + string.digits - return ''.join(random.choice(valid_chars) for i in range(key_lenght)).encode("utf-8") + return get_random_bytes(key_length) def create_hsub(text, iv=None, hsublen=48): @@ -18,7 +15,7 @@ def create_hsub(text, iv=None, hsublen=48): # Generate a 64bit random IV if none is provided. if iv is None: iv = generate_iv() - hashed = sha256(iv + text.encode("utf-8")).digest() + hashed = SHA256.new(iv + text.encode("utf-8")).digest() # Concatenate our IV with a SHA256 hash of text + IV. hsub = iv + hashed return hsub.hex()[:hsublen] diff --git a/requirements.txt b/requirements.txt index 21ccd6e..519006b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -PGPy \ No newline at end of file +pycryptodomex>=3.19.1 \ No newline at end of file diff --git a/scripts/bump_alpha.py b/scripts/bump_alpha.py deleted file mode 100644 index 3d24e5a..0000000 --- a/scripts/bump_alpha.py +++ /dev/null @@ -1,18 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") -version_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - else: - print(line.rstrip('\n')) diff --git a/scripts/bump_build.py b/scripts/bump_build.py deleted file mode 100644 index 8b125a3..0000000 --- a/scripts/bump_build.py +++ /dev/null @@ -1,21 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") -version_var_name = "VERSION_BUILD" -alpha_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - elif line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/scripts/bump_major.py b/scripts/bump_major.py deleted file mode 100644 index 1138da7..0000000 --- a/scripts/bump_major.py +++ /dev/null @@ -1,27 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") -version_var_name = "VERSION_MAJOR" -minor_var_name = "VERSION_MINOR" -build_var_name = "VERSION_BUILD" -alpha_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - elif line.startswith(minor_var_name): - print(f"{minor_var_name} = 0") - elif line.startswith(build_var_name): - print(f"{build_var_name} = 0") - elif line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/scripts/bump_minor.py b/scripts/bump_minor.py deleted file mode 100644 index 526ac93..0000000 --- a/scripts/bump_minor.py +++ /dev/null @@ -1,24 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") -version_var_name = "VERSION_MINOR" -build_var_name = "VERSION_BUILD" -alpha_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - elif line.startswith(build_var_name): - print(f"{build_var_name} = 0") - elif line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/scripts/remove_alpha.py b/scripts/remove_alpha.py deleted file mode 100644 index 0771912..0000000 --- a/scripts/remove_alpha.py +++ /dev/null @@ -1,13 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "poorman_handshake", "version.py") - -alpha_var_name = "VERSION_ALPHA" - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) From e4644d50a404ddd9a7f088a00cf2e40fbdf2ed8d Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 30 Dec 2024 05:11:27 +0000 Subject: [PATCH 5/7] Increment Version to 0.2.2a4 --- poorman_handshake/version.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/poorman_handshake/version.py b/poorman_handshake/version.py index def76d9..3b02d52 100644 --- a/poorman_handshake/version.py +++ b/poorman_handshake/version.py @@ -1,7 +1,6 @@ -# The following lines are replaced during the release process. # START_VERSION_BLOCK VERSION_MAJOR = 0 VERSION_MINOR = 2 VERSION_BUILD = 2 -VERSION_ALPHA = 3 +VERSION_ALPHA = 4 # END_VERSION_BLOCK From 12c746c102895df469994cd349acaa5064137c91 Mon Sep 17 00:00:00 2001 From: JarbasAl Date: Mon, 30 Dec 2024 05:11:46 +0000 Subject: [PATCH 6/7] Update Changelog --- CHANGELOG.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a1447..404a439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,20 @@ # Changelog -## [V0.2.2a2](https://github.com/JarbasHiveMind/poorman_handshake/tree/V0.2.2a2) (2023-08-03) +## [0.2.2a4](https://github.com/JarbasHiveMind/poorman_handshake/tree/0.2.2a4) (2024-12-30) -[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/0.2.0...V0.2.2a2) +[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/V0.2.2a3...0.2.2a4) + +**Merged pull requests:** + +- refactor!: use RSA directly, drop PGPy [\#1](https://github.com/JarbasHiveMind/poorman_handshake/pull/1) ([JarbasAl](https://github.com/JarbasAl)) -## [0.2.0](https://github.com/JarbasHiveMind/poorman_handshake/tree/0.2.0) (2021-02-14) +## [V0.2.2a3](https://github.com/JarbasHiveMind/poorman_handshake/tree/V0.2.2a3) (2023-08-03) -[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/5314fd298768286eda26e13382281a62926579be...0.2.0) +[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/V0.2.2a2...V0.2.2a3) + +## [V0.2.2a2](https://github.com/JarbasHiveMind/poorman_handshake/tree/V0.2.2a2) (2023-08-03) + +[Full Changelog](https://github.com/JarbasHiveMind/poorman_handshake/compare/0.2.0...V0.2.2a2) From 1a2d314347cfaf43ebd0c5db5560dd487fcdf2fd Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:54:23 +0000 Subject: [PATCH 7/7] Update version.py --- poorman_handshake/version.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poorman_handshake/version.py b/poorman_handshake/version.py index 3b02d52..cd3bcb6 100644 --- a/poorman_handshake/version.py +++ b/poorman_handshake/version.py @@ -1,6 +1,6 @@ # START_VERSION_BLOCK -VERSION_MAJOR = 0 -VERSION_MINOR = 2 -VERSION_BUILD = 2 -VERSION_ALPHA = 4 +VERSION_MAJOR = 1 +VERSION_MINOR = 0 +VERSION_BUILD = 0 +VERSION_ALPHA = 1 # END_VERSION_BLOCK