diff --git a/.github/actions/install-softhsm/action.yml b/.github/actions/install-softhsm/action.yml new file mode 100644 index 0000000..f382403 --- /dev/null +++ b/.github/actions/install-softhsm/action.yml @@ -0,0 +1,54 @@ +name: install-softhsm +author: Matthias Valvekens +description: Install SoftHSM and configure an empty token +inputs: + os: + description: OS to target + required: true + token-label: + description: Label assigned to the token + required: false + default: TEST + token-user-pin: + description: User PIN to configure on the token + required: false + default: "1234" + token-so-pin: + description: Security officer PIN to configure on the token + required: false + default: "5678" +runs: + using: "composite" + steps: + - name: Install SoftHSM + shell: bash + run: | + if [[ "${OS_NAME:0:6}" == 'ubuntu' ]]; then + sudo apt-get install softhsm2 + mkdir softhsm_tokens + echo "directories.tokendir = $(pwd)/softhsm_tokens" > /tmp/softhsm2.conf + echo "SOFTHSM2_CONF=/tmp/softhsm2.conf" >> "$GITHUB_ENV" + echo "PKCS11_MODULE=/usr/lib/softhsm/libsofthsm2.so" >> "$GITHUB_ENV" + elif [[ "${OS_NAME:0:5}" == 'macos' ]]; then + brew install softhsm + echo "PKCS11_MODULE=/opt/homebrew/lib/softhsm/libsofthsm2.so" >> "$GITHUB_ENV" + elif [[ "${OS_NAME:0:7}" == 'windows' ]]; then + choco install softhsm.install + echo "SOFTHSM2_CONF=D:/SoftHSM2/etc/softhsm2.conf" >> "$GITHUB_ENV" + echo "PKCS11_MODULE=D:/SoftHSM2/lib/softhsm2-x64.dll" >> "$GITHUB_ENV" + echo "D:/SoftHSM2/bin" >> "$GITHUB_PATH" + echo "D:/SoftHSM2/lib" >> "$GITHUB_PATH" + else + echo "$OS_NAME is not a supported target system" + exit 1 + fi + env: + OS_NAME: ${{ inputs.os }} + - name: Initialize SoftHSM token + shell: bash + run: | + softhsm2-util --init-token --free --label "${{ inputs.token-label }}" \ + --pin "${{ inputs.token-user-pin }}" --so-pin "${{ inputs.token-so-pin }}" + echo "PKCS11_TOKEN_LABEL=${{ inputs.token-label }}" >> "$GITHUB_ENV" + echo "PKCS11_TOKEN_PIN=${{ inputs.token-user-pin }}" >> "$GITHUB_ENV" + echo "PKCS11_TOKEN_SO_PIN=${{ inputs.token-so-pin }}" >> "$GITHUB_ENV" diff --git a/.github/release-template.md b/.github/release-template.md new file mode 100644 index 0000000..41f35f9 --- /dev/null +++ b/.github/release-template.md @@ -0,0 +1 @@ +A source distribution and wheels for common platforms have been published to [PyPI](https://pypi.org/project/python-pkcs11/:VERSION). \ No newline at end of file diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 0033861..662d905 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,14 +1,16 @@ name: Code quality on: - push: + push: {} + pull_request: {} env: UV_PYTHON_PREFERENCE: only-system + UV_NO_SYNC: "1" jobs: run: runs-on: ubuntu-latest steps: - name: Acquire sources - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4 - name: Install uv uses: astral-sh/setup-uv@v4 @@ -16,13 +18,13 @@ jobs: enable-cache: true - name: Setup Python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5 with: python-version: "3.13" architecture: x64 - - name: Install dev dependencies - run: uv sync + - name: Install lint dependencies + run: uv sync --no-dev --exact --group lint - name: ruff format run: uv run ruff format --diff . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bdd1ec..38cb61b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,18 +4,141 @@ on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+' - + workflow_dispatch: + inputs: + environment: + type: environment + description: "Environment in which to execute the release process" +env: + UV_PYTHON_PREFERENCE: only-system + # we do all UV syncing explicitly + UV_NO_SYNC: "1" jobs: - pypi-publish: - name: Build and upload release to PyPI + extract-params: + name: Determine release parameters + runs-on: ubuntu-latest + permissions: {} + outputs: + publish-env: ${{ steps.setenv.outputs.envname }} + version: ${{ steps.getrelease.outputs.version }} + steps: + - id: setenv + run: | + if [[ $GITHUB_EVENT_NAME == 'workflow_dispatch' ]]; then + echo "envname=${{ inputs.environment }}" >> "$GITHUB_OUTPUT" + elif [[ $GITHUB_EVENT_NAME == 'push' ]]; then + echo "envname=pypi" >> "$GITHUB_OUTPUT" + else + echo "Cannot run release workflow for trigger event $GITHUB_EVENT_NAME" + exit 1 + fi + cat "$GITHUB_OUTPUT" + - name: Get version information + id: getrelease + run: | + set -eo pipefail + + VER_REGEX="v[0-9]\+\.[0-9]\+\..\+" + if [[ "${GITHUB_REF:0:11}" != 'refs/tags/v' ]]; then + echo "Cannot run release workflow for ref $GITHUB_REF, must be a tag starting with 'v'" + exit 1 + fi + VERSION=${GITHUB_REF:10} + + if echo $VERSION | grep -q "$VER_REGEX"; then + echo "version=${VERSION:1}" >> "$GITHUB_OUTPUT" + else + echo "Tag $VERSION does not follow v naming scheme" + exit 1 + fi + - uses: actions/checkout@v4 + - name: Generate release body + run: | + sed "s/:VERSION/$VERSION/g" < .github/release-template.md > release.md + cat release.md + env: + VERSION: ${{ steps.getrelease.outputs.version }} + - name: Upload release body + uses: actions/upload-artifact@v4 + with: + name: release-body + path: release.md + build-wheels: + runs-on: ${{ matrix.os }} + needs: [extract-params] + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - ubuntu-24.04-arm + - windows-latest + - macos-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + - name: Build wheels + uses: pypa/cibuildwheel@v3.0.0 + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + build-sdist: + runs-on: ubuntu-latest + needs: [extract-params] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + - name: Build source distribution + run: uv build --sdist + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: ./dist/*.tar.gz + publish: + name: Publish release artifacts + needs: [extract-params, build-sdist, build-wheels] runs-on: ubuntu-latest - environment: release + environment: ${{ needs.extract-params.outputs.publish-env }} permissions: + # we use PyPI's trusted publisher model -> expose identity token id-token: write + # Needed to create GitHub releases + contents: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - - run: pip install build - - run: python -m build - - name: Publish package distributions to PyPI + - name: Download wheels + uses: actions/download-artifact@v4 + with: + pattern: wheels-* + path: dist/ + merge-multiple: 'true' + - name: Download source distribution + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist/ + - name: Download release body + uses: actions/download-artifact@v4 + with: + name: release-body + path: release-body + - name: Upload to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: ${{ vars.REPOSITORY_URL }} + - name: Create GitHub release + if: needs.extract-params.outputs.publish-env == 'pypi' && startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: | + dist/*.whl + dist/*.tar.gz + body_path: release-body/release.md + fail_on_unmatched_files: true + name: python-pkcs11 ${{ needs.extract-params.outputs.version }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ed28186..d15be01 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,12 +1,12 @@ name: Tests on: - push: {} + push: + branches: ["*"] + pull_request: {} workflow_dispatch: {} env: UV_PYTHON_PREFERENCE: only-system - PKCS11_TOKEN_LABEL: TEST - PKCS11_TOKEN_PIN: 1234 - PKCS11_TOKEN_SO_PIN: 5678 + UV_NO_SYNC: "1" jobs: run: runs-on: ${{ matrix.os }} @@ -27,46 +27,21 @@ jobs: steps: - name: Acquire sources - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install Softhsm - shell: bash - run: | - if [[ $OS_NAME == 'ubuntu-latest' ]]; then - sudo apt-get install softhsm2 - mkdir softhsm_tokens - echo "directories.tokendir = $(pwd)/softhsm_tokens" > /tmp/softhsm2.conf - echo "SOFTHSM2_CONF=/tmp/softhsm2.conf" >> "$GITHUB_ENV" - echo "PKCS11_MODULE=/usr/lib/softhsm/libsofthsm2.so" >> "$GITHUB_ENV" - elif [[ $OS_NAME == 'macos-latest' ]]; then - brew install softhsm - echo "PKCS11_MODULE=/opt/homebrew/lib/softhsm/libsofthsm2.so" >> "$GITHUB_ENV" - elif [[ $OS_NAME == 'windows-latest' ]]; then - choco install softhsm.install - echo "SOFTHSM2_CONF=D:/SoftHSM2/etc/softhsm2.conf" >> "$GITHUB_ENV" - echo "PKCS11_MODULE=D:/SoftHSM2/lib/softhsm2-x64.dll" >> "$GITHUB_ENV" - echo "D:/SoftHSM2/bin" >> "$GITHUB_PATH" - echo "D:/SoftHSM2/lib" >> "$GITHUB_PATH" - else - echo "$OS_NAME is not a supported target system" - exit 1 - fi - env: - OS_NAME: ${{ matrix.os }} - - name: Initialize SoftHSM token - shell: bash - run: | - softhsm2-util --init-token --free --label $PKCS11_TOKEN_LABEL --pin $PKCS11_TOKEN_PIN --so-pin $PKCS11_TOKEN_SO_PIN + - uses: ./.github/actions/install-softhsm + with: + os: ${{ matrix.os }} - name: Install uv uses: astral-sh/setup-uv@v4 with: enable-cache: true python-version: ${{ matrix.python-version }} - - name: Install dev dependencies - run: uv sync --all-extras + - name: Install testing dependencies + run: uv sync --no-dev --exact --group testing - name: Run tests run: uv run pytest -v \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..89b64d9 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.12" + # readthedocs does not support [dependency-groups] directly + jobs: + create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + build: + html: + - make -C docs html BUILDDIR=$READTHEDOCS_OUTPUT +formats: all diff --git a/docs/Makefile b/docs/Makefile index e24c264..30ba8c9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,19 +2,19 @@ # # You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build SPHINXPROJ = PythonPKCS11 SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @uv run --no-dev --group docs-build $(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @uv run --no-dev --group docs-build $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/pyproject.toml b/pyproject.toml index f43e265..8c65775 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,9 @@ [build-system] -requires = ["setuptools>=80.8", "cython"] +requires = ["setuptools>=80.8", "cython", "setuptools-scm>=8.3.1"] build-backend = "setuptools.build_meta" [project] name = "python-pkcs11" -version = "0.7.0" description = "PKCS#11 support for Python" readme = "README.rst" authors = [ @@ -27,10 +26,11 @@ classifiers = [ dependencies = ["asn1crypto>=1.4.0"] license = "MIT" requires-python = ">=3.9" +dynamic = ["version"] [project.urls] -Homepage = "http://python-pkcs11.readthedocs.io/en/latest/" -Documentation = "http://python-pkcs11.readthedocs.io/en/latest/" +Homepage = "https://python-pkcs11.readthedocs.io/en/latest/" +Documentation = "https://python-pkcs11.readthedocs.io/en/latest/" Issues = "https://github.com/pyauth/python-pkcs11/issues" Repository = "https://github.com/pyauth/python-pkcs11" @@ -50,18 +50,50 @@ extend-select = [ [tool.ruff.lint.isort] combine-as-imports = true +[tool.setuptools] +ext-modules = [ + {name = "pkcs11._pkcs11", sources = ["pkcs11/_pkcs11.pyx"]} +] + +[tool.cibuildwheel.linux] +archs = ["auto64"] + +[tool.cibuildwheel.windows] +archs = ["AMD64"] + +[tool.cibuildwheel.macos] +archs = ["universal2"] + [tool.setuptools.packages.find] include = ["pkcs11*"] [dependency-groups] -dev = [ +testing = [ "cryptography>=44.0.0", "parameterized>=0.9.0", "pytest>=8.3.4", +] +docs = [ + "sphinx>=7.4.7", + "sphinx-rtd-theme>=3.0.2", +] +lint = [ "ruff>=0.8.3", +] +release = [ "setuptools>=80.8", "setuptools-scm>=8.3.1", - "sphinx>=7.4.7", - "sphinx-rtd-theme>=3.0.2", - "cython" + "cython", ] +docs-build = [ + { include-group = "docs" }, + "python-pkcs11", +] +dev = [ + { include-group = "docs" }, + { include-group = "testing" }, + { include-group = "lint" }, + { include-group = "release" }, +] + +[tool.setuptools_scm] \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100755 index 1ff33c0..0000000 --- a/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -# Add cython extension module to build configuration. -# -# See also: https://setuptools.pypa.io/en/latest/userguide/ext_modules.html - -import platform - -from setuptools import Extension, setup - -libraries = [] - -# if compiling using MSVC, we need to link against user32 library -if platform.system() == "Windows": - libraries.append("user32") - - -setup( - ext_modules=[ - Extension( - name="pkcs11._pkcs11", - sources=[ - "pkcs11/_pkcs11.pyx", - ], - libraries=libraries, - ), - ], -) diff --git a/tests/test_dsa.py b/tests/test_dsa.py index 4a927d5..1a92d4d 100644 --- a/tests/test_dsa.py +++ b/tests/test_dsa.py @@ -42,7 +42,11 @@ def test_generate_keypair_and_sign(self): public, private = dhparams.generate_keypair() self.assertIsInstance(public, pkcs11.PublicKey) self.assertIsInstance(private, pkcs11.PrivateKey) - self.assertEqual(len(public[Attribute.VALUE]), 1024 // 8) + # We expect a length of 128 (1024/8) in the vast majority of cases, + # but since the length of an integer value in DER is not fixed, there's + # a chance that we end up with a slightly shorter key length. + # The probability that the length falls short of 120 is vanishingly low, though. + self.assertGreater(len(public[Attribute.VALUE]), 120) data = "Message to sign" signature = private.sign(data, mechanism=Mechanism.DSA_SHA1) @@ -52,4 +56,5 @@ def test_generate_keypair_and_sign(self): @FIXME.nfast # returns Function Failed def test_generate_keypair_directly(self): public, private = self.session.generate_keypair(KeyType.DSA, 1024) - self.assertEqual(len(public[Attribute.VALUE]), 1024 // 8) + # See above. + self.assertGreater(len(public[Attribute.VALUE]), 120) diff --git a/tools/buildbot/build.sh b/tools/buildbot/build.sh deleted file mode 100755 index 32f0cd3..0000000 --- a/tools/buildbot/build.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# -# Build pkcs11 -# - -set -xe - -# Enable our virtualenv -. python_env/bin/activate - -python setup.py build_ext -i diff --git a/tools/buildbot/install.sh b/tools/buildbot/install.sh deleted file mode 100755 index ce2dd03..0000000 --- a/tools/buildbot/install.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -# -# Install dependencies -# - -set -xe - -# Create virtualenv if needed -[ -d python_env ] || python3 -m venv python_env - -# Enable our virtualenv -. python_env/bin/activate - -pip install -U pip six -pip install -U setuptools cython -pip install -r requirements.txt -r dev-requirements.txt diff --git a/tools/buildbot/test.sh b/tools/buildbot/test.sh deleted file mode 100755 index f56deec..0000000 --- a/tools/buildbot/test.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -# -# Test pkcs11 -# - -set -xe - -# Enable our virtualenv -. python_env/bin/activate - -# Test parameters come from ShellCommand -# export PKCS11_MODULE= -# export PKCS11_TOKEN_LABEL= -# export PKCS11_TOKEN_PIN= - -python -m unittest diff --git a/tools/nfast/build.sh b/tools/nfast/build.sh deleted file mode 100755 index 7ebf469..0000000 --- a/tools/nfast/build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# Build pkcs11 -# - -set -xe - -# Enable Python 3.5 from SCL -. /opt/rh/rh-python35/enable - -# Enable our virtualenv -. python_env/bin/activate - -python setup.py build_ext -i diff --git a/tools/nfast/install.sh b/tools/nfast/install.sh deleted file mode 100755 index 607671e..0000000 --- a/tools/nfast/install.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -# -# Install dependencies for nFast tests on RHEL7 -# - -set -xe - -# Enable Python 3.5 from SCL -. /opt/rh/rh-python35/enable - -# Create virtualenv if needed -[ -d python_env ] || virtualenv -p python3 python_env - -# Enable our virtualenv -. python_env/bin/activate - -pip install -U pip six -pip install -U setuptools cython -pip install -r requirements.txt -r dev-requirements.txt diff --git a/tools/nfast/test.sh b/tools/nfast/test.sh deleted file mode 100755 index 6182bf1..0000000 --- a/tools/nfast/test.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# -# Test pkcs11 -# - -set -xe - -# Enable Python 3.5 from SCL -. /opt/rh/rh-python35/enable - -# Enable our virtualenv -. python_env/bin/activate - -# Test parameters -export CKNFAST_FAKE_ACCELERATOR_LOGIN=true -export CKNFAST_LOADSHARING=1 -export CKNFAST_DEBUG=6 -export PKCS11_MODULE=/opt/nfast/toolkits/pkcs11/libcknfast.so -export PKCS11_TOKEN_LABEL='loadshared accelerator' -export PKCS11_TOKEN_PIN='0000' - -python -m unittest diff --git a/uv.lock b/uv.lock index b42accb..c8199f6 100644 --- a/uv.lock +++ b/uv.lock @@ -522,7 +522,6 @@ wheels = [ [[package]] name = "python-pkcs11" -version = "0.7.0" source = { editable = "." } dependencies = [ { name = "asn1crypto" }, @@ -542,6 +541,32 @@ dev = [ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-rtd-theme" }, ] +docs = [ + { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-rtd-theme" }, +] +docs-build = [ + { name = "python-pkcs11" }, + { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-rtd-theme" }, +] +lint = [ + { name = "ruff" }, +] +release = [ + { name = "cython" }, + { name = "setuptools" }, + { name = "setuptools-scm" }, +] +testing = [ + { name = "cryptography" }, + { name = "parameterized" }, + { name = "pytest" }, +] [package.metadata] requires-dist = [{ name = "asn1crypto", specifier = ">=1.4.0" }] @@ -558,6 +583,26 @@ dev = [ { name = "sphinx", specifier = ">=7.4.7" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, ] +docs = [ + { name = "sphinx", specifier = ">=7.4.7" }, + { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, +] +docs-build = [ + { name = "python-pkcs11" }, + { name = "sphinx", specifier = ">=7.4.7" }, + { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, +] +lint = [{ name = "ruff", specifier = ">=0.8.3" }] +release = [ + { name = "cython" }, + { name = "setuptools", specifier = ">=80.8" }, + { name = "setuptools-scm", specifier = ">=8.3.1" }, +] +testing = [ + { name = "cryptography", specifier = ">=44.0.0" }, + { name = "parameterized", specifier = ">=0.9.0" }, + { name = "pytest", specifier = ">=8.3.4" }, +] [[package]] name = "requests"