diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index e3aee77a..00000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-environment:
- matrix:
- - python: 27
- - python: 27-x64
- - python: 35
- - python: 35-x64
- - python: 36
- - python: 36-x64
- - python: 37
- - python: 37-x64
- - python: 38
- - python: 38-x64
-
-install:
- - SET PATH=C:\\Python%PYTHON%;c:\\Python%PYTHON%\\scripts;%PATH%
- - python -m pip install -U pip wheel setuptools
-
-build: off
-build_script:
- - python setup.py bdist_wheel
-
-test: off
-test_script:
- - pip install -r requirements-test.txt
- - pip install xmlsec --only-binary=xmlsec --no-index --find-links=dist
- - pytest -v --color=yes
- - ps: Get-ChildItem dist\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
diff --git a/.github/workflows/linuxbrew.yml b/.github/workflows/linuxbrew.yml
new file mode 100644
index 00000000..1c6d9543
--- /dev/null
+++ b/.github/workflows/linuxbrew.yml
@@ -0,0 +1,42 @@
+name: linuxbrew
+on: [push, pull_request]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref_name != 'master' }}
+jobs:
+ linuxbrew:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
+ env:
+ # For some unknown reason, linuxbrew tries to use "gcc-11" by default, which doesn't exist.
+ CC: gcc
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install brew
+ run: |
+ sudo apt install -y build-essential procps curl file git
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+ echo "/home/linuxbrew/.linuxbrew/bin" >> $GITHUB_PATH
+ - name: Install build dependencies
+ run: |
+ brew update
+ brew install python@${{ matrix.python }} gcc libxml2 libxmlsec1 pkg-config
+ echo "/home/linuxbrew/.linuxbrew/opt/python@${{ matrix.python }}/libexec/bin" >> $GITHUB_PATH
+ - name: Build wheel
+ run: |
+ python3 -m venv build_venv
+ source build_venv/bin/activate
+ pip3 install --upgrade setuptools wheel build
+ export CFLAGS="-I$(brew --prefix)/include"
+ export LDFLAGS="-L$(brew --prefix)/lib"
+ python3 -m build
+ rm -rf build/
+ - name: Run tests
+ run: |
+ python3 -m venv test_venv
+ source test_venv/bin/activate
+ pip3 install --upgrade --no-binary=lxml -r requirements-test.txt
+ pip3 install xmlsec --only-binary=xmlsec --no-index --find-links=dist/
+ pytest -v --color=yes
diff --git a/.github/workflows/macosx.yml b/.github/workflows/macosx.yml
index daf2c3dd..e2e2a0df 100644
--- a/.github/workflows/macosx.yml
+++ b/.github/workflows/macosx.yml
@@ -1,28 +1,54 @@
-name: MacOS
+name: macOS
on: [push, pull_request]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref_name != 'master' }}
jobs:
macosx:
runs-on: macos-latest
strategy:
matrix:
- python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
+ static_deps: ["static", ""]
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v3
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python }}
- name: Install build dependencies
run: |
- pip install --upgrade pip setuptools wheel
+ pip install --upgrade pip setuptools wheel build
brew install libxml2 libxmlsec1 pkg-config
+ - name: Build macosx_x86_64 wheel
+ env:
+ CC: clang
+ CFLAGS: "-fprofile-instr-generate -fcoverage-mapping"
+ LDFLAGS: "-fprofile-instr-generate -fcoverage-mapping"
+ PYXMLSEC_STATIC_DEPS: ${{ matrix.static_deps }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ export PKG_CONFIG_PATH="$(brew --prefix)/opt/libxml2/lib/pkgconfig"
+ export PYXMLSEC_LIBXML2_VERSION="$(pkg-config --modversion libxml-2.0)"
+ python -m build
+ rm -rf build/
- name: Set environment variables
shell: bash
run: |
- echo ::set-env name=PKGVER::$(python setup.py --version)
- - name: Build macosx_x86_64 wheel
- run: |
- python setup.py bdist_wheel
+ echo "PKGVER=$(python setup.py --version)" >> $GITHUB_ENV
+ echo "LLVM_PROFILE_FILE=pyxmlsec.profraw" >> $GITHUB_ENV
- name: Install test dependencies
run: |
- pip install --upgrade -r requirements-test.txt
+ export PKG_CONFIG_PATH="$(brew --prefix)/opt/libxml2/lib/pkgconfig"
+ pip install coverage --upgrade --no-binary=lxml -r requirements-test.txt
pip install xmlsec --only-binary=xmlsec --no-index --find-links=dist/
+ echo "PYXMLSEC_LIBFILE=$(python -c 'import xmlsec; print(xmlsec.__file__)')" >> $GITHUB_ENV
- name: Run tests
run: |
- pytest -v --color=yes
+ coverage run -m pytest -v --color=yes
+ - name: Report coverage to codecov
+ run: |
+ /Library/Developer/CommandLineTools/usr/bin/llvm-profdata merge -sparse ${{ env.LLVM_PROFILE_FILE }} -output pyxmlsec.profdata
+ /Library/Developer/CommandLineTools/usr/bin/llvm-cov show ${{ env.PYXMLSEC_LIBFILE }} --arch=$(uname -m) --instr-profile=pyxmlsec.profdata src > coverage.txt
+ bash <(curl -s https://codecov.io/bash) -f coverage.txt
+ if: matrix.static_deps != 'static'
diff --git a/.github/workflows/manylinux.yml b/.github/workflows/manylinux.yml
new file mode 100644
index 00000000..d1c205d7
--- /dev/null
+++ b/.github/workflows/manylinux.yml
@@ -0,0 +1,52 @@
+name: manylinux
+on: [push, pull_request]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref_name != 'master' }}
+jobs:
+ manylinux:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-abi: [cp39-cp39, cp310-cp310, cp311-cp311, cp312-cp312, cp313-cp313, cp314-cp314]
+ image:
+ - manylinux2014_x86_64
+ - manylinux_2_28_x86_64
+ - musllinux_1_2_x86_64
+ container: quay.io/pypa/${{ matrix.image }}
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install python build dependencies
+ run: |
+ # https://github.com/actions/runner/issues/2033
+ chown -R $(id -u):$(id -g) $PWD
+ /opt/python/${{ matrix.python-abi }}/bin/pip install --upgrade pip setuptools wheel build
+ - name: Install system build dependencies (manylinux)
+ run: |
+ yum install -y perl-core
+ if: contains(matrix.image, 'manylinux')
+ - name: Set environment variables
+ shell: bash
+ run: |
+ echo "PKGVER=$(/opt/python/${{ matrix.python-abi }}/bin/python setup.py --version)" >> $GITHUB_ENV
+ - name: Build linux_x86_64 wheel
+ env:
+ PYXMLSEC_STATIC_DEPS: true
+ PYXMLSEC_LIBXML2_VERSION: 2.14.6 # Lock it to libxml2 2.14.6 to match it with lxml
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ /opt/python/${{ matrix.python-abi }}/bin/python -m build
+ - name: Label manylinux wheel
+ run: |
+ ls -la dist/
+ auditwheel show dist/xmlsec-${{ env.PKGVER }}-${{ matrix.python-abi }}-linux_x86_64.whl
+ auditwheel repair dist/xmlsec-${{ env.PKGVER }}-${{ matrix.python-abi }}-linux_x86_64.whl
+ ls -la wheelhouse/
+ auditwheel show wheelhouse/xmlsec-${{ env.PKGVER }}-${{ matrix.python-abi }}-*${{ matrix.image }}*.whl
+ - name: Install test dependencies
+ run: |
+ /opt/python/${{ matrix.python-abi }}/bin/pip install --upgrade -r requirements-test.txt
+ /opt/python/${{ matrix.python-abi }}/bin/pip install xmlsec --only-binary=xmlsec --no-index --find-links=wheelhouse/
+ - name: Run tests
+ run: |
+ /opt/python/${{ matrix.python-abi }}/bin/pytest -v --color=yes
diff --git a/.github/workflows/manylinux2010.yml b/.github/workflows/manylinux2010.yml
deleted file mode 100644
index 7d58279a..00000000
--- a/.github/workflows/manylinux2010.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: manylinux2010
-on: [push, pull_request]
-jobs:
- manylinux2010_x86_64:
- runs-on: ubuntu-latest
- container: quay.io/pypa/manylinux2010_x86_64
- strategy:
- matrix:
- python-abi: [cp27-cp27m, cp27-cp27mu, cp35-cp35m, cp36-cp36m, cp37-cp37m, cp38-cp38]
- steps:
- - uses: actions/checkout@v1
- - name: Install build dependencies
- run: |
- /opt/python/${{ matrix.python-abi }}/bin/pip install --upgrade pip setuptools wheel
- - name: Set environment variables
- shell: bash
- run: |
- echo ::set-env name=PKGVER::$(/opt/python/${{ matrix.python-abi }}/bin/python setup.py --version)
- - name: Build linux_x86_64 wheel
- env:
- STATIC_DEPS: true
- run: |
- /opt/python/${{ matrix.python-abi }}/bin/python setup.py bdist_wheel
- - name: Label manylinux2010_x86_64 wheel
- run: |
- ls -la dist/
- auditwheel show dist/xmlsec-${PKGVER}-${{ matrix.python-abi }}-linux_x86_64.whl
- auditwheel repair dist/xmlsec-${PKGVER}-${{ matrix.python-abi }}-linux_x86_64.whl
- ls -l wheelhouse/
- auditwheel show wheelhouse/xmlsec-${PKGVER}-${{ matrix.python-abi }}-manylinux2010_x86_64.whl
- - name: Install test dependencies
- run: |
- /opt/python/${{ matrix.python-abi }}/bin/pip install --upgrade -r requirements-test.txt
- /opt/python/${{ matrix.python-abi }}/bin/pip install xmlsec --only-binary=xmlsec --no-index --find-links=wheelhouse/
- - name: Run tests
- run: |
- /opt/python/${{ matrix.python-abi }}/bin/pytest -v --color=yes
diff --git a/.github/workflows/sdist.yml b/.github/workflows/sdist.yml
index 9c6e1282..ecc53c31 100644
--- a/.github/workflows/sdist.yml
+++ b/.github/workflows/sdist.yml
@@ -1,14 +1,24 @@
name: sdist
on: [push, pull_request]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref_name != 'master' }}
jobs:
sdist:
- runs-on: ubuntu-latest
+ # Avoid Ubuntu 24.04 in sdist workflows, because it contains libxmlsec1-dev
+ # v1.2.39, which has a bug that causes tests/test_pkcs11.py to fail.
+ # (It thinks the softhsm engine has a public key instead of a private key.)
+ # libxmlsec1 <=1.2.33 or >=1.2.42 works. TODO: Try 26.04 when available.
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- - uses: actions/checkout@v1
- - name: Set up Python 3.8
- uses: actions/setup-python@v1
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
with:
- python-version: 3.8
+ python-version: ${{ matrix.python }}
- name: Install build dependencies
run: |
pip install --upgrade pip setuptools wheel
@@ -16,10 +26,10 @@ jobs:
run: |
python setup.py sdist
- name: Install test dependencies
- env:
- STATIC_DEPS: true
run: |
- pip install --upgrade -r requirements-test.txt
+ sudo apt-get update
+ sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl opensc softhsm2 libengine-pkcs11-openssl
+ pip install --upgrade -r requirements-test.txt --no-binary lxml
pip install dist/xmlsec-$(python setup.py --version).tar.gz
- name: Run tests
run: |
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
new file mode 100644
index 00000000..1d4564a6
--- /dev/null
+++ b/.github/workflows/wheels.yml
@@ -0,0 +1,138 @@
+name: Wheel build
+
+on:
+ release:
+ types: [created]
+ schedule:
+ # ┌───────────── minute (0 - 59)
+ # │ ┌───────────── hour (0 - 23)
+ # │ │ ┌───────────── day of the month (1 - 31)
+ # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
+ # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
+ # │ │ │ │ │
+ - cron: "42 3 * * 4"
+ push:
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref_name != 'master' }}
+
+permissions: {}
+
+jobs:
+ sdist:
+ # Avoid Ubuntu 24.04 in sdist workflows, because it contains libxmlsec1-dev
+ # v1.2.39, which has a bug that causes tests/test_pkcs11.py to fail.
+ # (It thinks the softhsm engine has a public key instead of a private key.)
+ # libxmlsec1 <=1.2.33 or >=1.2.42 works. TODO: Try 26.04 when available.
+ runs-on: ubuntu-22.04
+
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4.1.1
+ with:
+ fetch-depth: 0
+
+ - name: Set up Python
+ uses: actions/setup-python@v5.0.0
+ with:
+ python-version: "3.x"
+
+ - name: Install build dependencies
+ run: |
+ pip install --upgrade pip setuptools wheel
+
+ - name: Package source dist
+ run: python setup.py sdist
+
+ - name: Install test dependencies
+ run: |
+ sudo apt-get update -y -q
+ sudo apt-get install -y -q libxml2-dev libxslt1-dev libxmlsec1-dev libxmlsec1-openssl opensc softhsm2 libengine-pkcs11-openssl
+ pip install --upgrade -r requirements-test.txt --no-binary lxml
+ pip install dist/xmlsec-$(python setup.py --version).tar.gz
+
+ - name: Run tests
+ run: pytest -v --color=yes
+
+ - name: Upload sdist
+ uses: actions/upload-artifact@v4.3.1
+ with:
+ name: sdist
+ path: dist/*.tar.gz
+
+ generate-wheels-matrix:
+ # Create a matrix of all architectures & versions to build.
+ # This enables the next step to run cibuildwheel in parallel.
+ # From https://iscinumpy.dev/post/cibuildwheel-2-10-0/#only-210
+ name: Generate wheels matrix
+ runs-on: ubuntu-latest
+ outputs:
+ include: ${{ steps.set-matrix.outputs.include }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install cibuildwheel
+ # Nb. keep cibuildwheel version pin consistent with job below
+ run: pipx install cibuildwheel==3.1.4
+ - id: set-matrix
+ # Once we have the windows build figured out, it can be added here
+ # by updating the matrix to include windows builds as well.
+ # See example here:
+ # https://github.com/lxml/lxml/blob/3ccc7d583e325ceb0ebdf8fc295bbb7fc8cd404d/.github/workflows/wheels.yml#L95C1-L106C51
+ run: |
+ MATRIX=$(
+ {
+ cibuildwheel --print-build-identifiers --platform linux \
+ | jq -nRc '{"only": inputs, "os": "ubuntu-latest"}' \
+ && cibuildwheel --print-build-identifiers --platform macos \
+ | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
+ && cibuildwheel --print-build-identifiers --platform windows \
+ | jq -nRc '{"only": inputs, "os": "windows-2022"}' \
+ && cibuildwheel --print-build-identifiers --platform windows --archs ARM64 \
+ | jq -nRc '{"only": inputs, "os": "windows-11-arm"}'
+ } | jq -sc
+ )
+ echo "include=$MATRIX"
+ echo "include=$MATRIX" >> $GITHUB_OUTPUT
+
+ build_wheels:
+ name: Build for ${{ matrix.only }}
+ needs: generate-wheels-matrix
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include: ${{ fromJson(needs.generate-wheels-matrix.outputs.include) }}
+
+ env:
+ PYXMLSEC_LIBXML2_VERSION: 2.14.6
+ PYXMLSEC_LIBXSLT_VERSION: 1.1.43
+
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Set up QEMU
+ if: runner.os == 'Linux'
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: all
+
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v3.1.4
+ with:
+ only: ${{ matrix.only }}
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: actions/upload-artifact@v4.3.1
+ with:
+ path: ./wheelhouse/*.whl
+ name: xmlsec-wheel-${{ matrix.only }}
diff --git a/.gitignore b/.gitignore
index 1ef359b3..15f47985 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,8 @@
!.travis*
!.appveyor*
!.git*
-!.readthedocs.yml
+!.readthedocs.yaml
+!.pre-commit-config.yaml
# Python
/dist
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..aca65390
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,42 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.14.4
+ hooks:
+ - id: ruff
+ args: ["--fix"]
+ types: [python]
+ - id: ruff-format
+ types: [python]
+
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v6.0.0
+ hooks:
+ - id: no-commit-to-branch
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-added-large-files
+ - id: check-ast
+ - id: check-merge-conflict
+ - id: check-json
+ - id: detect-private-key
+ exclude: ^.*/rsakey.pem$
+ - id: mixed-line-ending
+ - id: pretty-format-json
+ args: [--autofix]
+
+- repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.18.2
+ hooks:
+ - id: mypy
+ exclude: (setup.py|tests|build_support/.*.py|doc/.*)
+ types: []
+ files: ^.*.pyi?$
+ additional_dependencies: [lxml-stubs, types-docutils]
+
+- repo: https://github.com/pre-commit/pygrep-hooks
+ rev: v1.10.0
+ hooks:
+ - id: rst-backticks
diff --git a/.readthedocs.yml b/.readthedocs.yaml
similarity index 74%
rename from .readthedocs.yml
rename to .readthedocs.yaml
index 4d8647be..93665c84 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yaml
@@ -1,10 +1,14 @@
version: 2
+build:
+ os: ubuntu-20.04
+ tools:
+ python: '3.9'
+
sphinx:
configuration: doc/source/conf.py
python:
- version: 3.7
install:
- method: pip
path: .
diff --git a/.travis.yml b/.travis.yml
index b5738884..8d3ca07e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,49 +1,53 @@
-dist: trusty
-sudo: false
+dist: focal
language: python
+travis:
+ auto_cancel:
+ push: true
+ pull_request: true
+
notifications:
email: false
-matrix:
- include:
- - python: 2.7
- - python: 3.5
- - python: 3.6
- - python: 3.7
- dist: xenial
- sudo: required
- - python: 3.8
- dist: xenial
- sudo: required
- - python: 3.9-dev
- dist: xenial
- sudo: required
+
+python:
+ - "3.9"
+ - "3.10"
+ - "3.11"
+ - "3.12"
+ - "3.13"
+ - "3.14"
+
env:
global:
- - CFLAGS=-coverage
- - LDFLAGS=-coverage -lgcov
- - PYXMLSEC_TEST_ITERATIONS=50
+ - CFLAGS=-coverage
+ - LDFLAGS=-coverage -lgcov
+ - PYXMLSEC_TEST_ITERATIONS=50
addons:
apt:
packages:
- - libssl-dev
- - libxmlsec1
- - libxmlsec1-dev
- - libxmlsec1-openssl
- - libxslt1-dev
- - pkg-config
- - lcov
+ - libssl-dev
+ - libxmlsec1
+ - libxmlsec1-dev
+ - libxmlsec1-openssl
+ - libxslt1-dev
+ - pkg-config
+ - lcov
+
install:
-- travis_retry pip install --upgrade pip setuptools wheel
-- travis_retry pip install coverage codecov -r requirements-test.txt --upgrade --force-reinstall
-- travis_retry pip install -e "."
-- pip list
-script: coverage run -m pytest -v tests --color=yes
+ - travis_retry pip install --upgrade pip setuptools wheel
+ - travis_retry pip install coverage -r requirements-test.txt --upgrade --force-reinstall
+ - python setup.py bdist_wheel
+ - pip install xmlsec --only-binary=xmlsec --no-index --find-links=dist/
+
+script:
+ - coverage run -m pytest -v tests --color=yes
+
after_success:
-- lcov --capture --directory . --output-file coverage.info
-- lcov --list coverage.info
-- codecov --file coverage.info
+ - lcov --capture --no-external --directory . --output-file coverage.info
+ - lcov --list coverage.info
+ - bash <(curl -s https://codecov.io/bash) -f coverage.info
+
before_deploy:
-- travis_retry pip install Sphinx -r doc/source/requirements.txt
-- git apply --verbose --no-index --unsafe-paths --directory=$(python -c "import site; print(site.getsitepackages()[0])") doc/source/sphinx-pr-6916.diff
-- sphinx-build -EWanb html doc/source build/sphinx
+ - travis_retry pip install Sphinx -r doc/source/requirements.txt
+ - git apply --verbose --no-index --unsafe-paths --directory=$(python -c "import site; print(site.getsitepackages()[0])") doc/source/sphinx-pr-6916.diff
+ - sphinx-build -EWanb html doc/source build/sphinx
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..60bde880
--- /dev/null
+++ b/README.md
@@ -0,0 +1,198 @@
+# python-xmlsec
+
+[](https://pypi.python.org/pypi/xmlsec)
+[](https://results.pre-commit.ci/latest/github/xmlsec/python-xmlsec/master)
+[](https://github.com/xmlsec/python-xmlsec/actions/workflows/manylinux.yml)
+[](https://github.com/xmlsec/python-xmlsec/actions/workflows/macosx.yml)
+[](https://github.com/xmlsec/python-xmlsec/actions/workflows/linuxbrew.yml)
+[](https://codecov.io/gh/xmlsec/python-xmlsec)
+[](https://xmlsec.readthedocs.io/en/latest/?badge=latest)
+
+Python bindings for the [XML Security
+Library](https://www.aleksey.com/xmlsec/).
+
+## Documentation
+
+Documentation for `xmlsec` can be found at
+[xmlsec.readthedocs.io](https://xmlsec.readthedocs.io/).
+
+## Usage
+
+Check the
+[examples](https://xmlsec.readthedocs.io/en/latest/examples.html)
+section in the documentation to see various examples of signing and
+verifying using the library.
+
+## Requirements
+
+- `libxml2 >= 2.9.1`
+- `libxmlsec1 >= 1.2.33`
+
+## Install
+
+`xmlsec` is available on PyPI:
+
+``` bash
+pip install xmlsec
+```
+
+Depending on your OS, you may need to install the required native
+libraries first:
+
+### Linux (Debian)
+
+``` bash
+apt-get install pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl
+```
+
+Note: There is no required version of LibXML2 for Ubuntu Precise, so you
+need to download and install it manually.
+
+``` bash
+wget http://xmlsoft.org/sources/libxml2-2.9.1.tar.gz
+tar -xvf libxml2-2.9.1.tar.gz
+cd libxml2-2.9.1
+./configure && make && make install
+```
+
+### Linux (CentOS)
+
+``` bash
+yum install libxml2-devel xmlsec1-devel xmlsec1-openssl-devel libtool-ltdl-devel
+```
+
+### Linux (Fedora)
+
+``` bash
+dnf install libxml2-devel xmlsec1-devel xmlsec1-openssl-devel libtool-ltdl-devel
+```
+
+### Mac
+
+``` bash
+brew install libxml2 libxmlsec1 pkg-config
+```
+
+or
+
+``` bash
+port install libxml2 xmlsec pkgconfig
+```
+
+### Alpine
+
+``` bash
+apk add build-base openssl libffi-dev openssl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec
+```
+
+## Troubleshooting
+
+### Mac
+
+If you get any fatal errors about missing `.h` files, update your
+`C_INCLUDE_PATH` environment variable to include the appropriate files
+from the `libxml2` and `libxmlsec1` libraries.
+
+### Windows
+
+Starting with 1.3.7, prebuilt wheels are available for Windows, so
+running `pip install xmlsec` should suffice. If you want to build from
+source:
+
+1. Configure build environment, see
+ [wiki.python.org](https://wiki.python.org/moin/WindowsCompilers) for
+ more details.
+
+2. Install from source dist:
+
+ ``` bash
+ pip install xmlsec --no-binary=xmlsec
+ ```
+
+## Building from source
+
+1. Clone the `xmlsec` source code repository to your local computer.
+
+ ``` bash
+ git clone https://github.com/xmlsec/python-xmlsec.git
+ ```
+
+2. Change into the `python-xmlsec` root directory.
+
+ ``` bash
+ cd /path/to/xmlsec
+ ```
+
+3. Install the project and all its dependencies using `pip`.
+
+ ``` bash
+ pip install .
+ ```
+
+## Contributing
+
+### Setting up your environment
+
+1. Follow steps 1 and 2 of the [manual installation
+ instructions](#building-from-source).
+
+2. Initialize a virtual environment to develop in. This is done so as
+ to ensure every contributor is working with close-to-identical
+ versions of packages.
+
+ ``` bash
+ mkvirtualenv xmlsec
+ ```
+
+ The `mkvirtualenv` command is available from `virtualenvwrapper`
+ package which can be installed by following
+ [link](http://virtualenvwrapper.readthedocs.org/en/latest/install.html#basic-installation).
+
+3. Activate the created virtual environment:
+
+ ``` bash
+ workon xmlsec
+ ```
+
+4. Install `xmlsec` in development mode with testing enabled. This will
+ download all dependencies required for running the unit tests.
+
+ ``` bash
+ pip install -r requirements-test.txt
+ pip install -e "."
+ ```
+
+### Running the test suite
+
+1. [Set up your environment](#setting-up-your-environment).
+
+2. Run the unit tests.
+
+ ``` bash
+ pytest tests
+ ```
+
+3. Tests configuration
+
+ Env variable `PYXMLSEC_TEST_ITERATIONS` specifies number of test
+ iterations to detect memory leaks.
+
+### Reporting an issue
+
+Please attach the output of following information:
+
+- version of `xmlsec`
+- version of `libxmlsec1`
+- version of `libxml2`
+- output from the command
+
+ ``` bash
+ pkg-config --cflags xmlsec1
+ ```
+
+## License
+
+Unless otherwise noted, all files contained within this project are
+licensed under the MIT open source license. See the included `LICENSE`
+file or visit [opensource.org](http://opensource.org/licenses/MIT) for
+more information.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index b58c518b..00000000
--- a/README.rst
+++ /dev/null
@@ -1,218 +0,0 @@
-python-xmlsec
-=============
-
-.. image:: https://travis-ci.org/mehcode/python-xmlsec.png?branch=master
- :target: https://travis-ci.org/mehcode/python-xmlsec
-.. image:: https://ci.appveyor.com/api/projects/status/ij87xk5wo8a39jua?svg=true
- :target: https://ci.appveyor.com/project/hoefling/xmlsec
-.. image:: https://github.com/mehcode/python-xmlsec/workflows/manylinux2010/badge.svg
- :target: https://github.com/mehcode/python-xmlsec/actions?query=workflow%3A%22manylinux2010%22
-.. image:: https://github.com/mehcode/python-xmlsec/workflows/MacOS/badge.svg
- :target: https://github.com/mehcode/python-xmlsec/actions?query=workflow%3A%22MacOS%22
-.. image:: https://codecov.io/gh/mehcode/python-xmlsec/branch/master/graph/badge.svg
- :target: https://codecov.io/gh/mehcode/python-xmlsec
-.. image:: https://img.shields.io/pypi/v/xmlsec.svg
- :target: https://pypi.python.org/pypi/xmlsec
-.. image:: https://readthedocs.org/projects/xmlsec/badge/?version=latest
- :target: https://xmlsec.readthedocs.io/en/latest/?badge=latest
- :alt: Documentation Status
-
-Python bindings for the `XML Security Library `_.
-
-Documentation
-*************
-
-A documentation for ``xmlsec`` can be found at `xmlsec.readthedocs.io `_.
-
-Usage
-*****
-
-Check the `examples `_ section in the documentation to see various examples of signing and verifying using the library.
-
-Requirements
-************
-- ``libxml2 >= 2.9.1``
-- ``libxmlsec1 >= 1.2.18``
-
-Install
-*******
-
-``xmlsec`` is available on PyPI:
-
-.. code-block:: bash
-
- pip install xmlsec
-
-Depending on your OS, you may need to install the required native
-libraries first:
-
-Linux (Debian)
-^^^^^^^^^^^^^^
-
-.. code-block:: bash
-
- apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
-
-
-Note: There is no required version of LibXML2 for Ubuntu Precise,
-so you need to download and install it manually.
-
-.. code-block:: bash
-
- wget http://xmlsoft.org/sources/libxml2-2.9.1.tar.gz
- tar -xvf libxml2-2.9.1.tar.gz
- cd libxml2-2.9.1
- ./configure && make && make install
-
-
-Linux (CentOS)
-^^^^^^^^^^^^^^
-
-.. code-block:: bash
-
- yum install libxml2-devel xmlsec1-devel xmlsec1-openssl-devel libtool-ltdl-devel
-
-
-Linux (Fedora)
-^^^^^^^^^^^^^^
-
-.. code-block:: bash
-
- dnf install libxml2-devel xmlsec1-devel xmlsec1-openssl-devel libtool-ltdl-devel
-
-
-Mac
-^^^
-
-.. code-block:: bash
-
- brew install libxml2 libxmlsec1 pkg-config
-
-
-Alpine
-^^^^^^
-
-.. code-block:: bash
-
- apk add build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec
-
-
-Troubleshooting
-***************
-
-Mac
-^^^
-
-If you get any fatal errors about missing ``.h`` files, update your
-``C_INCLUDE_PATH`` environment variable to include the appropriate
-files from the ``libxml2`` and ``libxmlsec1`` libraries.
-
-
-Windows
-^^^^^^^
-
-Starting with 1.3.7, prebuilt wheels are available for Windows,
-so running ``pip install xmlsec`` should suffice. If you want
-to build from source:
-
-#. Configure build environment, see `wiki.python.org `_ for more details.
-
-#. Install from source dist:
-
- .. code-block:: bash
-
- pip install xmlsec --no-binary=xmlsec
-
-
-Building from source
-********************
-
-#. Clone the ``xmlsec`` source code repository to your local computer.
-
- .. code-block:: bash
-
- git clone https://github.com/mehcode/python-xmlsec.git
-
-#. Change into the ``python-xmlsec`` root directory.
-
- .. code-block:: bash
-
- cd /path/to/xmlsec
-
-
-#. Install the project and all its dependencies using ``pip``.
-
- .. code-block:: bash
-
- pip install .
-
-
-Contributing
-************
-
-Setting up your environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-#. Follow steps 1 and 2 of the `manual installation instructions <#building-from-source>`_.
-
-
-#. Initialize a virtual environment to develop in.
- This is done so as to ensure every contributor is working with
- close-to-identicial versions of packages.
-
- .. code-block:: bash
-
- mkvirtualenv xmlsec
-
- The ``mkvirtualenv`` command is available from ``virtualenvwrapper`` package which can be installed by following `link `_.
-
-#. Activate the created virtual environment:
-
- .. code-block:: bash
-
- workon xmlsec
-
-#. Install ``xmlsec`` in development mode with testing enabled.
- This will download all dependencies required for running the unit tests.
-
- .. code-block:: bash
-
- pip install -r requirements-test.txt
- pip install -e "."
-
-
-Running the test suite
-^^^^^^^^^^^^^^^^^^^^^^
-
-#. `Set up your environment <#setting-up-your-environment>`_.
-
-#. Run the unit tests.
-
- .. code-block:: bash
-
- pytest tests
-
-#. Tests configuration
-
- Env variable ``PYXMLSEC_TEST_ITERATIONS`` specifies number of
- test iterations to detect memory leaks.
-
-Reporting an issue
-^^^^^^^^^^^^^^^^^^
-
-Please attach the output of following information:
-
-* version of ``xmlsec``
-* version of ``libxmlsec1``
-* version of ``libxml2``
-* output from the command
-
- .. code-block:: bash
-
- pkg-config --cflags xmlsec1
-
-License
-*******
-
-Unless otherwise noted, all files contained within this project are licensed under the MIT opensource license.
-See the included ``LICENSE`` file or visit `opensource.org `_ for more information.
diff --git a/typeshed/lxml/__init__.pyi b/build_support/__init__.py
similarity index 100%
rename from typeshed/lxml/__init__.pyi
rename to build_support/__init__.py
diff --git a/build_support/build_ext.py b/build_support/build_ext.py
new file mode 100644
index 00000000..c4fb5bc9
--- /dev/null
+++ b/build_support/build_ext.py
@@ -0,0 +1,86 @@
+import os
+import sys
+from distutils import log
+from distutils.errors import DistutilsError
+
+from setuptools.command.build_ext import build_ext as build_ext_orig
+
+from .static_build import CrossCompileInfo, StaticBuildHelper
+
+
+class build_ext(build_ext_orig):
+ def info(self, message):
+ self.announce(message, level=log.INFO)
+
+ def run(self):
+ ext = self.ext_map['xmlsec']
+ self.debug = os.environ.get('PYXMLSEC_ENABLE_DEBUG', False)
+ self.static = os.environ.get('PYXMLSEC_STATIC_DEPS', False)
+ self.size_opt = os.environ.get('PYXMLSEC_OPTIMIZE_SIZE', True)
+
+ if self.static or sys.platform == 'win32':
+ helper = StaticBuildHelper(self)
+ helper.prepare(sys.platform)
+ else:
+ import pkgconfig
+
+ try:
+ config = pkgconfig.parse('xmlsec1')
+ except OSError as error:
+ raise DistutilsError('Unable to invoke pkg-config.') from error
+ except pkgconfig.PackageNotFoundError as error:
+ raise DistutilsError('xmlsec1 is not installed or not in path.') from error
+
+ if config is None or not config.get('libraries'):
+ raise DistutilsError('Bad or incomplete result returned from pkg-config.')
+
+ ext.define_macros.extend(config['define_macros'])
+ ext.include_dirs.extend(config['include_dirs'])
+ ext.library_dirs.extend(config['library_dirs'])
+ ext.libraries.extend(config['libraries'])
+
+ import lxml
+
+ ext.include_dirs.extend(lxml.get_include())
+
+ ext.define_macros.extend(
+ [('MODULE_NAME', self.distribution.metadata.name), ('MODULE_VERSION', self.distribution.metadata.version)]
+ )
+ for key, value in ext.define_macros:
+ if key == 'XMLSEC_CRYPTO' and not (value.startswith('"') and value.endswith('"')):
+ ext.define_macros.remove((key, value))
+ ext.define_macros.append((key, f'"{value}"'))
+ break
+
+ if sys.platform == 'win32':
+ ext.extra_compile_args.append('/Zi')
+ else:
+ ext.extra_compile_args.extend(
+ [
+ '-g',
+ '-std=c99',
+ '-fPIC',
+ '-fno-strict-aliasing',
+ '-Wno-error=declaration-after-statement',
+ '-Werror=implicit-function-declaration',
+ ]
+ )
+
+ if self.debug:
+ ext.define_macros.append(('PYXMLSEC_ENABLE_DEBUG', '1'))
+ if sys.platform == 'win32':
+ ext.extra_compile_args.append('/Od')
+ else:
+ ext.extra_compile_args.append('-Wall')
+ ext.extra_compile_args.append('-O0')
+ else:
+ if self.size_opt:
+ if sys.platform == 'win32':
+ ext.extra_compile_args.append('/Os')
+ else:
+ ext.extra_compile_args.append('-Os')
+
+ super().run()
+
+
+__all__ = ('CrossCompileInfo', 'build_ext')
diff --git a/build_support/network.py b/build_support/network.py
new file mode 100644
index 00000000..7ac0bb5e
--- /dev/null
+++ b/build_support/network.py
@@ -0,0 +1,29 @@
+import contextlib
+import json
+from urllib.request import Request, urlopen
+
+DEFAULT_USER_AGENT = 'https://github.com/xmlsec/python-xmlsec'
+DOWNLOAD_USER_AGENT = 'python-xmlsec build'
+
+
+def make_request(url, github_token=None, json_response=False):
+ headers = {'User-Agent': DEFAULT_USER_AGENT}
+ if github_token:
+ headers['authorization'] = 'Bearer ' + github_token
+ request = Request(url, headers=headers)
+ with contextlib.closing(urlopen(request)) as response:
+ charset = response.headers.get_content_charset() or 'utf-8'
+ content = response.read().decode(charset)
+ if json_response:
+ return json.loads(content)
+ return content
+
+
+def download_lib(url, filename):
+ request = Request(url, headers={'User-Agent': DOWNLOAD_USER_AGENT})
+ with urlopen(request) as response, open(filename, 'wb') as target:
+ while True:
+ chunk = response.read(8192)
+ if not chunk:
+ break
+ target.write(chunk)
diff --git a/build_support/releases.py b/build_support/releases.py
new file mode 100644
index 00000000..089162e2
--- /dev/null
+++ b/build_support/releases.py
@@ -0,0 +1,77 @@
+import html.parser
+import os
+import re
+from distutils import log
+from distutils.version import StrictVersion as Version
+
+from .network import make_request
+
+
+class HrefCollector(html.parser.HTMLParser):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.hrefs = []
+
+ def handle_starttag(self, tag, attrs):
+ if tag == 'a':
+ for name, value in attrs:
+ if name == 'href':
+ self.hrefs.append(value)
+
+
+def latest_release_from_html(url, matcher):
+ content = make_request(url)
+ collector = HrefCollector()
+ collector.feed(content)
+ hrefs = collector.hrefs
+
+ def comp(text):
+ try:
+ return Version(matcher.match(text).groupdict()['version'])
+ except (AttributeError, ValueError):
+ return Version('0.0')
+
+ latest = max(hrefs, key=comp)
+ return f'{url}/{latest}'
+
+
+def latest_release_from_gnome_org_cache(url, lib_name):
+ cache_url = f'{url}/cache.json'
+ cache = make_request(cache_url, json_response=True)
+ latest_version = cache[2][lib_name][-1]
+ latest_source = cache[1][lib_name][latest_version]['tar.xz']
+ return f'{url}/{latest_source}'
+
+
+def latest_release_json_from_github_api(repo):
+ api_url = f'https://api.github.com/repos/{repo}/releases/latest'
+ token = os.environ.get('GH_TOKEN')
+ if token:
+ log.info('Using GitHub token to avoid rate limiting')
+ return make_request(api_url, token, json_response=True)
+
+
+def latest_openssl_release():
+ return latest_release_json_from_github_api('openssl/openssl')['tarball_url']
+
+
+def latest_zlib_release():
+ return latest_release_from_html('https://zlib.net/fossils', re.compile('zlib-(?P.*).tar.gz'))
+
+
+def latest_libiconv_release():
+ return latest_release_from_html('https://ftpmirror.gnu.org/libiconv', re.compile('libiconv-(?P.*).tar.gz'))
+
+
+def latest_libxml2_release():
+ return latest_release_from_gnome_org_cache('https://download.gnome.org/sources/libxml2', 'libxml2')
+
+
+def latest_libxslt_release():
+ return latest_release_from_gnome_org_cache('https://download.gnome.org/sources/libxslt', 'libxslt')
+
+
+def latest_xmlsec_release():
+ assets = latest_release_json_from_github_api('lsh123/xmlsec')['assets']
+ (tar_gz,) = [asset for asset in assets if asset['name'].endswith('.tar.gz')]
+ return tar_gz['browser_download_url']
diff --git a/build_support/static_build.py b/build_support/static_build.py
new file mode 100644
index 00000000..09e2039a
--- /dev/null
+++ b/build_support/static_build.py
@@ -0,0 +1,453 @@
+import multiprocessing
+import os
+import platform
+import subprocess
+import sys
+import tarfile
+import zipfile
+from distutils.errors import DistutilsError
+from pathlib import Path
+from urllib.parse import urljoin
+from urllib.request import urlcleanup
+
+from .network import download_lib
+from .releases import (
+ latest_libiconv_release,
+ latest_libxml2_release,
+ latest_libxslt_release,
+ latest_openssl_release,
+ latest_xmlsec_release,
+ latest_zlib_release,
+)
+
+
+class CrossCompileInfo:
+ def __init__(self, host, arch, compiler):
+ self.host = host
+ self.arch = arch
+ self.compiler = compiler
+
+ @property
+ def triplet(self):
+ return f'{self.host}-{self.arch}-{self.compiler}'
+
+
+class StaticBuildHelper:
+ def __init__(self, builder):
+ self.builder = builder
+ self.ext = builder.ext_map['xmlsec']
+ self.info = builder.info
+ self._prepare_directories()
+
+ def prepare(self, platform_name):
+ self.info(f'starting static build on {sys.platform}')
+ if platform_name == 'win32':
+ self._prepare_windows_build()
+ elif 'linux' in platform_name or 'darwin' in platform_name:
+ self._prepare_unix_build(platform_name)
+ else:
+ raise DistutilsError(f'Unsupported static build platform: {platform_name}')
+
+ def _prepare_directories(self):
+ buildroot = Path('build', 'tmp')
+
+ prefix_dir = buildroot / 'prefix'
+ prefix_dir.mkdir(parents=True, exist_ok=True)
+ self.prefix_dir = prefix_dir.absolute()
+
+ build_libs_dir = buildroot / 'libs'
+ build_libs_dir.mkdir(exist_ok=True)
+ self.build_libs_dir = build_libs_dir
+
+ libs_dir = Path(os.environ.get('PYXMLSEC_LIBS_DIR', 'libs'))
+ libs_dir.mkdir(exist_ok=True)
+ self.libs_dir = libs_dir
+ self.info('{:20} {}'.format('Lib sources in:', self.libs_dir.absolute()))
+
+ self.builder.prefix_dir = self.prefix_dir
+ self.builder.build_libs_dir = self.build_libs_dir
+ self.builder.libs_dir = self.libs_dir
+
+ def _prepare_windows_build(self):
+ release_url = 'https://github.com/mxamin/python-xmlsec-win-binaries/releases/download/2025.07.10/'
+ if platform.machine() == 'ARM64':
+ suffix = 'win-arm64'
+ elif sys.maxsize > 2**32:
+ suffix = 'win64'
+ else:
+ suffix = 'win32'
+
+ libs = [
+ f'libxml2-2.11.9-3.{suffix}.zip',
+ f'libxslt-1.1.39.{suffix}.zip',
+ f'zlib-1.3.1.{suffix}.zip',
+ f'iconv-1.18-1.{suffix}.zip',
+ f'openssl-3.0.16.pl1.{suffix}.zip',
+ f'xmlsec-1.3.7.{suffix}.zip',
+ ]
+
+ for libfile in libs:
+ url = urljoin(release_url, libfile)
+ destfile = self.libs_dir / libfile
+ if destfile.is_file():
+ self.info(f'Using local copy of "{url}"')
+ else:
+ self.info(f'Retrieving "{url}" to "{destfile}"')
+ urlcleanup()
+ download_lib(url, str(destfile))
+
+ for package in self.libs_dir.glob('*.zip'):
+ with zipfile.ZipFile(str(package)) as archive:
+ destdir = self.build_libs_dir
+ archive.extractall(path=str(destdir))
+
+ self.ext.define_macros = [
+ ('XMLSEC_CRYPTO', '\\"openssl\\"'),
+ ('__XMLSEC_FUNCTION__', '__FUNCTION__'),
+ ('XMLSEC_NO_GOST', '1'),
+ ('XMLSEC_NO_XKMS', '1'),
+ ('XMLSEC_NO_CRYPTO_DYNAMIC_LOADING', '1'),
+ ('XMLSEC_CRYPTO_OPENSSL', '1'),
+ ('UNICODE', '1'),
+ ('_UNICODE', '1'),
+ ('LIBXML_ICONV_ENABLED', 1),
+ ('LIBXML_STATIC', '1'),
+ ('LIBXSLT_STATIC', 1),
+ ('XMLSEC_STATIC', 1),
+ ('inline', '__inline'),
+ ]
+ self.ext.libraries = [
+ 'libxmlsec_a',
+ 'libxmlsec-openssl_a',
+ 'libcrypto',
+ 'iconv_a',
+ 'libxslt_a',
+ 'libexslt_a',
+ 'libxml2_a',
+ 'zlib',
+ 'WS2_32',
+ 'Advapi32',
+ 'User32',
+ 'Gdi32',
+ 'Crypt32',
+ ]
+ self.ext.library_dirs = [str(path.absolute()) for path in self.build_libs_dir.rglob('lib')]
+
+ includes = [path for path in self.build_libs_dir.rglob('include') if path.is_dir()]
+ includes.append(next(path / 'xmlsec' for path in includes if (path / 'xmlsec').is_dir()))
+ self.ext.include_dirs = [str(path.absolute()) for path in includes]
+
+ def _prepare_unix_build(self, build_platform):
+ self._capture_version_overrides()
+ archives = self._ensure_source_archives()
+ self._extract_archives(archives)
+
+ env, prefix_arg, ldflags, cross_compile = self._prepare_build_environment(build_platform)
+ self._build_dependencies(env, prefix_arg, ldflags, cross_compile)
+ self._configure_extension_for_static(build_platform)
+
+ def _capture_version_overrides(self):
+ builder = self.builder
+ builder.openssl_version = os.environ.get('PYXMLSEC_OPENSSL_VERSION', '3.6.0')
+ builder.libiconv_version = os.environ.get('PYXMLSEC_LIBICONV_VERSION', '1.18')
+ builder.libxml2_version = os.environ.get('PYXMLSEC_LIBXML2_VERSION', '2.14.6')
+ builder.libxslt_version = os.environ.get('PYXMLSEC_LIBXSLT_VERSION', '1.1.43')
+ builder.zlib_version = os.environ.get('PYXMLSEC_ZLIB_VERSION', '1.3.1')
+ builder.xmlsec1_version = os.environ.get('PYXMLSEC_XMLSEC1_VERSION', '1.3.9')
+
+ def _ensure_source_archives(self):
+ return [
+ self._ensure_source(
+ name='OpenSSL',
+ glob='openssl*.tar.gz',
+ filename='openssl.tar.gz',
+ version=self.builder.openssl_version,
+ env_label='PYXMLSEC_OPENSSL_VERSION',
+ default_url=latest_openssl_release,
+ version_url=lambda v: f'https://api.github.com/repos/openssl/openssl/tarball/openssl-{v}',
+ ),
+ self._ensure_source(
+ name='zlib',
+ glob='zlib*.tar.gz',
+ filename='zlib.tar.gz',
+ version=self.builder.zlib_version,
+ env_label='PYXMLSEC_ZLIB_VERSION',
+ default_url=latest_zlib_release,
+ version_url=lambda v: f'https://zlib.net/fossils/zlib-{v}.tar.gz',
+ ),
+ self._ensure_source(
+ name='libiconv',
+ glob='libiconv*.tar.gz',
+ filename='libiconv.tar.gz',
+ version=self.builder.libiconv_version,
+ env_label='PYXMLSEC_LIBICONV_VERSION',
+ default_url=latest_libiconv_release,
+ version_url=lambda v: f'https://ftpmirror.gnu.org/libiconv/libiconv-{v}.tar.gz',
+ ),
+ self._ensure_source(
+ name='libxml2',
+ glob='libxml2*.tar.xz',
+ filename='libxml2.tar.xz',
+ version=self.builder.libxml2_version,
+ env_label='PYXMLSEC_LIBXML2_VERSION',
+ default_url=latest_libxml2_release,
+ version_url=lambda v: self._libxml_related_url('libxml2', v),
+ ),
+ self._ensure_source(
+ name='libxslt',
+ glob='libxslt*.tar.xz',
+ filename='libxslt.tar.xz',
+ version=self.builder.libxslt_version,
+ env_label='PYXMLSEC_LIBXSLT_VERSION',
+ default_url=latest_libxslt_release,
+ version_url=lambda v: self._libxml_related_url('libxslt', v),
+ ),
+ self._ensure_source(
+ name='xmlsec1',
+ glob='xmlsec1*.tar.gz',
+ filename='xmlsec1.tar.gz',
+ version=self.builder.xmlsec1_version,
+ env_label='PYXMLSEC_XMLSEC1_VERSION',
+ default_url=latest_xmlsec_release,
+ version_url=lambda v: f'https://github.com/lsh123/xmlsec/releases/download/{v}/xmlsec1-{v}.tar.gz',
+ ),
+ ]
+
+ def _ensure_source(self, name, glob, filename, version, env_label, default_url, version_url):
+ archive = next(self.libs_dir.glob(glob), None)
+ if archive is not None:
+ return archive
+
+ self.info('{:10}: {}'.format(name, 'source tar not found, downloading ...'))
+ archive = self.libs_dir / filename
+ if version is None:
+ url = default_url()
+ self.info('{:10}: {}'.format(name, f'{env_label} unset, downloading latest from {url}'))
+ else:
+ url = version_url(version)
+ self.info('{:10}: {}'.format(name, f'{env_label}={version}, downloading from {url}'))
+ download_lib(url, str(archive))
+ return archive
+
+ def _libxml_related_url(self, lib_name, version):
+ version_prefix, _ = version.rsplit('.', 1)
+ return f'https://download.gnome.org/sources/{lib_name}/{version_prefix}/{lib_name}-{version}.tar.xz'
+
+ def _extract_archives(self, archives):
+ for archive in archives:
+ self.info(f'Unpacking {archive.name}')
+ try:
+ with tarfile.open(str(archive)) as tar:
+ tar.extractall(path=str(self.build_libs_dir))
+ except EOFError as error:
+ raise DistutilsError(f'Bad {archive.name} downloaded; remove it and try again.') from error
+
+ def _prepare_build_environment(self, build_platform):
+ prefix_arg = f'--prefix={self.prefix_dir}'
+ env = os.environ.copy()
+
+ cflags = []
+ if env.get('CFLAGS'):
+ cflags.append(env['CFLAGS'])
+ cflags.append('-fPIC')
+
+ ldflags = []
+ if env.get('LDFLAGS'):
+ ldflags.append(env['LDFLAGS'])
+
+ cross_compile = None
+ if build_platform == 'darwin':
+ arch = self.builder.plat_name.rsplit('-', 1)[1]
+ if arch != platform.machine() and arch in ('x86_64', 'arm64'):
+ self.info(f'Cross-compiling for {arch}')
+ cflags.append(f'-arch {arch}')
+ ldflags.append(f'-arch {arch}')
+ cross_compile = CrossCompileInfo('darwin64', arch, 'cc')
+ major_version, _ = tuple(map(int, platform.mac_ver()[0].split('.')[:2]))
+ if major_version >= 11 and 'MACOSX_DEPLOYMENT_TARGET' not in env:
+ env['MACOSX_DEPLOYMENT_TARGET'] = '11.0'
+
+ env['CFLAGS'] = ' '.join(cflags)
+ env['LDFLAGS'] = ' '.join(ldflags)
+ return env, prefix_arg, ldflags, cross_compile
+
+ def _build_dependencies(self, env, prefix_arg, ldflags, cross_compile):
+ self._build_openssl(env, prefix_arg, cross_compile)
+ self._build_zlib(env, prefix_arg)
+
+ host_arg = [f'--host={cross_compile.arch}'] if cross_compile else []
+ self._build_libiconv(env, prefix_arg, host_arg)
+ self._build_libxml2(env, prefix_arg, host_arg)
+ self._build_libxslt(env, prefix_arg, host_arg)
+
+ ldflags.append('-lpthread')
+ env['LDFLAGS'] = ' '.join(ldflags)
+ self._build_xmlsec1(env, prefix_arg, host_arg)
+
+ def _build_openssl(self, env, prefix_arg, cross_compile):
+ self.info('Building OpenSSL')
+ openssl_dir = next(self.build_libs_dir.glob('openssl-*'))
+ openssl_config_cmd = [prefix_arg, 'no-shared', '-fPIC', '--libdir=lib']
+ if platform.machine() == 'riscv64':
+ # openssl(riscv64): disable ASM to avoid R_RISCV_JAL relocation failure on 3.5.2
+ # OpenSSL 3.5.2 enables RISC-V64 AES assembly by default. When we statically
+ # link libcrypto alongside xmlsec, the AES asm path triggers a link-time error:
+ # relocation truncated to fit: R_RISCV_JAL against symbol `AES_set_encrypt_key'
+ # in .../libcrypto.a(libcrypto-lib-aes-riscv64.o)
+ # This appears to stem from a long-range jump emitted by the AES asm generator
+ # (see aes-riscv64.pl around L1069), which can exceed the JAL reach when objects
+ # end up far apart in the final static link.
+ # As a pragmatic workaround, disable ASM on riscv64 (pass `no-asm`) so the
+ # portable C implementation is used. This unblocks the build at the cost of
+ # some crypto performance on riscv64 only.
+ # Refs:
+ # - https://github.com/openssl/openssl/blob/0893a62/crypto/aes/asm/aes-riscv64.pl#L1069
+ openssl_config_cmd.append('no-asm')
+ if cross_compile:
+ openssl_config_cmd.insert(0, './Configure')
+ openssl_config_cmd.append(cross_compile.triplet)
+ else:
+ openssl_config_cmd.insert(0, './config')
+ subprocess.check_call(openssl_config_cmd, cwd=str(openssl_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}'], cwd=str(openssl_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}', 'install_sw'], cwd=str(openssl_dir), env=env)
+
+ def _build_zlib(self, env, prefix_arg):
+ self.info('Building zlib')
+ zlib_dir = next(self.build_libs_dir.glob('zlib-*'))
+ subprocess.check_call(['./configure', prefix_arg], cwd=str(zlib_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}'], cwd=str(zlib_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}', 'install'], cwd=str(zlib_dir), env=env)
+
+ def _build_libiconv(self, env, prefix_arg, host_arg):
+ self.info('Building libiconv')
+ libiconv_dir = next(self.build_libs_dir.glob('libiconv-*'))
+ subprocess.check_call(
+ [
+ './configure',
+ prefix_arg,
+ '--disable-dependency-tracking',
+ '--disable-shared',
+ *host_arg,
+ ],
+ cwd=str(libiconv_dir),
+ env=env,
+ )
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}'], cwd=str(libiconv_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}', 'install'], cwd=str(libiconv_dir), env=env)
+
+ def _build_libxml2(self, env, prefix_arg, host_arg):
+ self.info('Building LibXML2')
+ libxml2_dir = next(self.build_libs_dir.glob('libxml2-*'))
+ subprocess.check_call(
+ [
+ './configure',
+ prefix_arg,
+ '--disable-dependency-tracking',
+ '--disable-shared',
+ '--without-lzma',
+ '--without-python',
+ f'--with-iconv={self.prefix_dir}',
+ f'--with-zlib={self.prefix_dir}',
+ *host_arg,
+ ],
+ cwd=str(libxml2_dir),
+ env=env,
+ )
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}'], cwd=str(libxml2_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}', 'install'], cwd=str(libxml2_dir), env=env)
+
+ def _build_libxslt(self, env, prefix_arg, host_arg):
+ self.info('Building libxslt')
+ libxslt_dir = next(self.build_libs_dir.glob('libxslt-*'))
+ subprocess.check_call(
+ [
+ './configure',
+ prefix_arg,
+ '--disable-dependency-tracking',
+ '--disable-shared',
+ '--without-python',
+ '--without-crypto',
+ f'--with-libxml-prefix={self.prefix_dir}',
+ *host_arg,
+ ],
+ cwd=str(libxslt_dir),
+ env=env,
+ )
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}'], cwd=str(libxslt_dir), env=env)
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}', 'install'], cwd=str(libxslt_dir), env=env)
+
+ def _build_xmlsec1(self, env, prefix_arg, host_arg):
+ self.info('Building xmlsec1')
+ xmlsec1_dir = next(self.build_libs_dir.glob('xmlsec1-*'))
+ subprocess.check_call(
+ [
+ './configure',
+ prefix_arg,
+ '--disable-shared',
+ '--disable-gost',
+ '--enable-md5',
+ '--enable-ripemd160',
+ '--disable-crypto-dl',
+ '--enable-static=yes',
+ '--enable-shared=no',
+ '--enable-static-linking=yes',
+ '--with-default-crypto=openssl',
+ f'--with-openssl={self.prefix_dir}',
+ f'--with-libxml={self.prefix_dir}',
+ f'--with-libxslt={self.prefix_dir}',
+ *host_arg,
+ ],
+ cwd=str(xmlsec1_dir),
+ env=env,
+ )
+ include_flags = [
+ f'-I{self.prefix_dir / "include"}',
+ f'-I{self.prefix_dir / "include" / "libxml"}',
+ ]
+ subprocess.check_call(
+ ['make', f'-j{multiprocessing.cpu_count() + 1}', *include_flags],
+ cwd=str(xmlsec1_dir),
+ env=env,
+ )
+ subprocess.check_call(['make', f'-j{multiprocessing.cpu_count() + 1}', 'install'], cwd=str(xmlsec1_dir), env=env)
+
+ def _configure_extension_for_static(self, build_platform):
+ self.ext.define_macros = [
+ ('__XMLSEC_FUNCTION__', '__func__'),
+ ('XMLSEC_NO_SIZE_T', None),
+ ('XMLSEC_NO_GOST', '1'),
+ ('XMLSEC_NO_GOST2012', '1'),
+ ('XMLSEC_NO_XKMS', '1'),
+ ('XMLSEC_CRYPTO', '\\"openssl\\"'),
+ ('XMLSEC_NO_CRYPTO_DYNAMIC_LOADING', '1'),
+ ('XMLSEC_CRYPTO_OPENSSL', '1'),
+ ('LIBXML_ICONV_ENABLED', 1),
+ ('LIBXML_STATIC', 1),
+ ('LIBXSLT_STATIC', 1),
+ ('XMLSEC_STATIC', 1),
+ ('inline', '__inline'),
+ ('UNICODE', '1'),
+ ('_UNICODE', '1'),
+ ]
+
+ self.ext.include_dirs.append(str(self.prefix_dir / 'include'))
+ self.ext.include_dirs.extend([str(path.absolute()) for path in (self.prefix_dir / 'include').iterdir() if path.is_dir()])
+
+ self.ext.library_dirs = []
+ if build_platform == 'linux':
+ self.ext.libraries = ['m', 'rt']
+ extra_objects = [
+ 'libxmlsec1.a',
+ 'libxslt.a',
+ 'libxml2.a',
+ 'libz.a',
+ 'libxmlsec1-openssl.a',
+ 'libcrypto.a',
+ 'libiconv.a',
+ 'libxmlsec1.a',
+ ]
+ self.ext.extra_objects = [str(self.prefix_dir / 'lib' / obj) for obj in extra_objects]
+
+
+__all__ = ('CrossCompileInfo', 'StaticBuildHelper')
diff --git a/doc/Makefile b/doc/Makefile
index 119469fb..9b6324b7 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -17,4 +17,4 @@ help:
# 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
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/doc/source/conf.py b/doc/source/conf.py
index bb75403b..900b79da 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,20 +1,16 @@
-# -*- coding: utf-8 -*-
+from __future__ import annotations
-import sys
+import importlib.metadata
import urllib.request
import lxml
-
-from docutils.nodes import reference
-from packaging.version import parse
+from docutils.nodes import Text, reference
+from packaging.version import Version, parse
+from sphinx.addnodes import pending_xref
+from sphinx.application import Sphinx
+from sphinx.environment import BuildEnvironment
from sphinx.errors import ExtensionError
-if sys.version_info >= (3, 8):
- from importlib import metadata as importlib_metadata
-else:
- import importlib_metadata
-
-
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']
intersphinx_mapping = {'python': ('https://docs.python.org/3/', None)}
@@ -23,40 +19,39 @@
source_suffix = '.rst'
master_doc = 'index'
-project = u'python-xmlsec'
-copyright = u'2020, Oleg Hoefling '
-author = u'Bulat Gaifullin '
-release = importlib_metadata.version('xmlsec')
-parsed = parse(release)
-version = '{}.{}'.format(parsed.major, parsed.minor)
+project = 'python-xmlsec'
+copyright = '2020, Oleg Hoefling '
+author = 'Bulat Gaifullin '
+release = importlib.metadata.version('xmlsec')
+parsed: Version = parse(release)
+version = f'{parsed.major}.{parsed.minor}'
-language = None
-exclude_patterns = []
+exclude_patterns: list[str] = []
pygments_style = 'sphinx'
todo_include_todos = False
-html_theme = 'nature'
-html_static_path = []
+html_theme = 'furo'
+html_static_path: list[str] = []
htmlhelp_basename = 'python-xmlsecdoc'
-latex_elements = {}
+latex_elements: dict[str, str] = {}
latex_documents = [
(
master_doc,
'python-xmlsec.tex',
- u'python-xmlsec Documentation',
- u'Bulat Gaifullin \\textless{}gaifullinbf@gmail.com\\textgreater{}',
+ 'python-xmlsec Documentation',
+ 'Bulat Gaifullin \\textless{}gaifullinbf@gmail.com\\textgreater{}',
'manual',
)
]
-man_pages = [(master_doc, 'python-xmlsec', u'python-xmlsec Documentation', [author], 1)]
+man_pages = [(master_doc, 'python-xmlsec', 'python-xmlsec Documentation', [author], 1)]
texinfo_documents = [
(
master_doc,
'python-xmlsec',
- u'python-xmlsec Documentation',
+ 'python-xmlsec Documentation',
author,
'python-xmlsec',
'One line description of project.',
@@ -67,15 +62,20 @@
autodoc_member_order = 'groupwise'
autodoc_docstring_signature = True
+
+rst_prolog = """
+.. role:: xml(code)
+ :language: xml
+"""
+
# LXML crossref'ing stuff:
# LXML doesn't have an intersphinx docs,
# so we link to lxml.etree._Element explicitly
lxml_element_cls_doc_uri = 'https://lxml.de/api/lxml.etree._Element-class.html'
-def lxml_element_doc_reference(app, env, node, contnode):
- """
- Handle a missing reference only if it is a ``lxml.etree._Element`` ref.
+def lxml_element_doc_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: Text) -> reference:
+ """Handle a missing reference only if it is a ``lxml.etree._Element`` ref.
We handle only :class:`lxml.etree._Element` and :class:`~lxml.etree._Element` nodes.
"""
@@ -84,13 +84,13 @@ def lxml_element_doc_reference(app, env, node, contnode):
and node.get('reftarget', None) == 'lxml.etree._Element'
and contnode.astext() in ('lxml.etree._Element', '_Element')
):
- reftitle = '(in lxml v{})'.format(lxml.__version__)
+ reftitle = f'(in lxml v{lxml.__version__})' # type: ignore[attr-defined]
newnode = reference('', '', internal=False, refuri=lxml_element_cls_doc_uri, reftitle=reftitle)
newnode.append(contnode)
return newnode
-def setup(app):
+def setup(app: Sphinx) -> None:
# first, check whether the doc URL is still valid
if urllib.request.urlopen(lxml_element_cls_doc_uri).getcode() != 200:
raise ExtensionError('URL to `lxml.etree._Element` docs is not accesible.')
diff --git a/doc/source/examples/decrypt.py b/doc/source/examples/decrypt.py
index e107756f..cb474d22 100644
--- a/doc/source/examples/decrypt.py
+++ b/doc/source/examples/decrypt.py
@@ -6,7 +6,7 @@
key = xmlsec.Key.from_file('rsakey.pem', xmlsec.constants.KeyDataFormatPem)
manager.add_key(key)
enc_ctx = xmlsec.EncryptionContext(manager)
-root = etree.parse("enc1-res.xml").getroot()
-enc_data = xmlsec.tree.find_child(root, "EncryptedData", xmlsec.constants.EncNs)
+root = etree.parse('enc1-res.xml').getroot()
+enc_data = xmlsec.tree.find_child(root, 'EncryptedData', xmlsec.constants.EncNs)
decrypted = enc_ctx.decrypt(enc_data)
print(etree.tostring(decrypted))
diff --git a/doc/source/examples/encrypt.py b/doc/source/examples/encrypt.py
index f69d4613..2a92264e 100644
--- a/doc/source/examples/encrypt.py
+++ b/doc/source/examples/encrypt.py
@@ -2,39 +2,32 @@
import xmlsec
-manager = xmlsec.KeysManager()
-key = xmlsec.Key.from_file('rsacert.pem', xmlsec.constants.KeyDataFormatCertPem, None)
-manager.add_key(key)
-template = etree.parse('enc1-doc.xml').getroot()
+with open('enc1-doc.xml') as fp:
+ template = etree.parse(fp).getroot()
+
enc_data = xmlsec.template.encrypted_data_create(
template,
xmlsec.constants.TransformAes128Cbc,
type=xmlsec.constants.TypeEncElement,
- ns="xenc",
+ ns='xenc',
)
xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
-key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="dsig")
+key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns='dsig')
enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.constants.TransformRsaOaep)
xmlsec.template.encrypted_data_ensure_cipher_value(enc_key)
data = template.find('./Data')
# Encryption
+manager = xmlsec.KeysManager()
+key = xmlsec.Key.from_file('rsacert.pem', xmlsec.constants.KeyDataFormatCertPem, None)
+manager.add_key(key)
+
enc_ctx = xmlsec.EncryptionContext(manager)
-enc_ctx.key = xmlsec.Key.generate(
- xmlsec.constants.KeyDataAes, 128, xmlsec.constants.KeyDataTypeSession
-)
+enc_ctx.key = xmlsec.Key.generate(xmlsec.constants.KeyDataAes, 128, xmlsec.constants.KeyDataTypeSession)
enc_data = enc_ctx.encrypt_xml(enc_data, data)
-enc_method = xmlsec.tree.find_child(
- enc_data, xmlsec.constants.NodeEncryptionMethod, xmlsec.constants.EncNs
-)
-key_info = xmlsec.tree.find_child(
- enc_data, xmlsec.constants.NodeKeyInfo, xmlsec.constants.DSigNs
-)
-enc_method = xmlsec.tree.find_node(
- key_info, xmlsec.constants.NodeEncryptionMethod, xmlsec.constants.EncNs
-)
-cipher_value = xmlsec.tree.find_node(
- key_info, xmlsec.constants.NodeCipherValue, xmlsec.constants.EncNs
-)
+enc_method = xmlsec.tree.find_child(enc_data, xmlsec.constants.NodeEncryptionMethod, xmlsec.constants.EncNs)
+key_info = xmlsec.tree.find_child(enc_data, xmlsec.constants.NodeKeyInfo, xmlsec.constants.DSigNs)
+enc_method = xmlsec.tree.find_node(key_info, xmlsec.constants.NodeEncryptionMethod, xmlsec.constants.EncNs)
+cipher_value = xmlsec.tree.find_node(key_info, xmlsec.constants.NodeCipherValue, xmlsec.constants.EncNs)
print(etree.tostring(cipher_value))
diff --git a/doc/source/examples/sign.py b/doc/source/examples/sign.py
index 4529bc8a..519c13a0 100644
--- a/doc/source/examples/sign.py
+++ b/doc/source/examples/sign.py
@@ -2,7 +2,8 @@
import xmlsec
-template = etree.parse('sign1-tmpl.xml').getroot()
+with open('sign1-tmpl.xml') as fp:
+ template = etree.parse(fp).getroot()
signature_node = xmlsec.tree.find_node(template, xmlsec.constants.NodeSignature)
ctx = xmlsec.SignatureContext()
diff --git a/doc/source/examples/sign_binary.py b/doc/source/examples/sign_binary.py
index 4e6c0e00..275c6e40 100644
--- a/doc/source/examples/sign_binary.py
+++ b/doc/source/examples/sign_binary.py
@@ -1,5 +1,3 @@
-from lxml import etree
-
import xmlsec
ctx = xmlsec.SignatureContext()
diff --git a/doc/source/examples/verify.py b/doc/source/examples/verify.py
index 8629c550..c3240c99 100644
--- a/doc/source/examples/verify.py
+++ b/doc/source/examples/verify.py
@@ -2,8 +2,10 @@
import xmlsec
-template = etree.parse('sign1-res.xml').getroot()
-xmlsec.tree.add_ids(template, ["ID"])
+with open('sign1-res.xml') as fp:
+ template = etree.parse(fp).getroot()
+
+xmlsec.tree.add_ids(template, ['ID'])
signature_node = xmlsec.tree.find_node(template, xmlsec.constants.NodeSignature)
# Create a digital signature context (no key manager is needed).
ctx = xmlsec.SignatureContext()
diff --git a/doc/source/examples/verify_binary.py b/doc/source/examples/verify_binary.py
index 06c2b727..1f888b99 100644
--- a/doc/source/examples/verify_binary.py
+++ b/doc/source/examples/verify_binary.py
@@ -1,5 +1,3 @@
-from lxml import etree
-
import xmlsec
ctx = xmlsec.SignatureContext()
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 5cc758b9..e08f47d9 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,9 +3,6 @@
You can adapt this file completely to your liking, but it should at least
contain the root ``toctree`` directive.
-.. role:: xml(code)
- :language: xml
-
Welcome to python-xmlsec's documentation!
=========================================
diff --git a/doc/source/install.rst b/doc/source/install.rst
index 834b9acb..c892a3ea 100644
--- a/doc/source/install.rst
+++ b/doc/source/install.rst
@@ -59,7 +59,7 @@ Alpine
.. code-block:: bash
- apk add build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec
+ apk add build-base openssl libffi-dev openssl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec
Troubleshooting
diff --git a/doc/source/modules/constants.rst b/doc/source/modules/constants.rst
index 4a63fcd7..3df6b50f 100644
--- a/doc/source/modules/constants.rst
+++ b/doc/source/modules/constants.rst
@@ -49,7 +49,11 @@ KeyData
.. data:: xmlsec.constants.KeyDataEcdsa
- The ECDSA key klass.
+ (Deprecated. The EC key klass) The ECDSA key klass.
+
+.. data:: xmlsec.constants.KeyDataEc
+
+ The EC key klass.
.. data:: xmlsec.constants.KeyDataHmac
@@ -166,12 +170,6 @@ Namespaces
.. data:: xmlsec.constants.XPointerNs
:annotation: = 'http://www.w3.org/2001/04/xmldsig-more/xptr'
-.. data:: xmlsec.constants.Soap11Ns
- :annotation: = 'http://schemas.xmlsoap.org/soap/envelope/'
-
-.. data:: xmlsec.constants.Soap12Ns
- :annotation: = 'http://www.w3.org/2002/06/soap-envelope'
-
.. data:: xmlsec.constants.NsExcC14N
:annotation: = 'http://www.w3.org/2001/10/xml-exc-c14n#'
diff --git a/doc/source/requirements.txt b/doc/source/requirements.txt
index 09ff0002..ffb2b6d3 100644
--- a/doc/source/requirements.txt
+++ b/doc/source/requirements.txt
@@ -1,4 +1,5 @@
-lxml>=3.8
+lxml==6.0.2
importlib_metadata;python_version < '3.8'
packaging
Sphinx>=3
+furo>=2021.4.11b34
diff --git a/doc/source/sphinx-pr-6916.diff b/doc/source/sphinx-pr-6916.diff
deleted file mode 100644
index e7040a0f..00000000
--- a/doc/source/sphinx-pr-6916.diff
+++ /dev/null
@@ -1,46 +0,0 @@
-diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
-index bc9bf49a74..4804c89c52 100644
---- a/sphinx/environment/__init__.py
-+++ b/sphinx/environment/__init__.py
-@@ -46,6 +46,7 @@
- default_settings = {
- 'embed_stylesheet': False,
- 'cloak_email_addresses': True,
-+ 'syntax_highlight': 'short',
- 'pep_base_url': 'https://www.python.org/dev/peps/',
- 'pep_references': None,
- 'rfc_base_url': 'https://tools.ietf.org/html/',
-diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
-index 85eeb43..80f1eea 100644
---- a/sphinx/writers/html.py
-+++ b/sphinx/writers/html.py
-@@ -494,8 +494,11 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
- self.body.append(self.starttag(node, 'kbd', '',
- CLASS='docutils literal notranslate'))
- else:
-+ classes = 'docutils literal notranslate'
-+ if 'code' in node['classes']:
-+ classes += ' highlight'
- self.body.append(self.starttag(node, 'code', '',
-- CLASS='docutils literal notranslate'))
-+ CLASS=classes))
- self.protect_literal_text += 1
-
- def depart_literal(self, node: Element) -> None:
-diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
-index 80cedd3..470f559 100644
---- a/sphinx/writers/html5.py
-+++ b/sphinx/writers/html5.py
-@@ -446,8 +446,11 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
- self.body.append(self.starttag(node, 'kbd', '',
- CLASS='docutils literal notranslate'))
- else:
-+ classes = 'docutils literal notranslate'
-+ if 'code' in node['classes']:
-+ classes += ' highlight'
- self.body.append(self.starttag(node, 'code', '',
-- CLASS='docutils literal notranslate'))
-+ CLASS=classes))
- self.protect_literal_text += 1
-
- def depart_literal(self, node: Element) -> None:
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index ce190e35..00000000
--- a/mypy.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[mypy]
-files = src
-mypy_path = typeshed/
-ignore_missing_imports = False
-warn_unused_configs = True
-disallow_subclassing_any = True
-disallow_any_generics = True
-disallow_untyped_calls = True
-disallow_untyped_defs = True
-disallow_incomplete_defs = True
-check_untyped_defs = True
-disallow_untyped_decorators = True
-no_implicit_optional = True
-warn_redundant_casts = True
-warn_unused_ignores = True
-warn_return_any = True
-no_implicit_reexport = True
diff --git a/pyproject.toml b/pyproject.toml
index 636c52c9..7c7b4bf3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,33 +1,131 @@
-[tool.black]
-line_length = 130
-skip-string-normalization = true
-target_version = ['py38']
-include = '\.pyi?$'
-exclude = '''
-
-(
- /(
- \.eggs # exclude a few common directories in the
- | \.git # root of the project
- | \.mypy_cache
- | \.tox
- | build
- | dist
- )/
-)
-'''
-
-[tool.isort]
-force_alphabetical_sort_within_sections = true
-recursive = true
-line_length = 130
-multi_line_output = 3
-include_trailing_comma = true
-force_grid_wrap = 0
-use_parentheses = true
-combine_as_imports = true
-known_first_party = ['xmlsec']
-known_third_party = ['lxml', 'pytest', '_pytest', 'hypothesis']
-
[build-system]
-requires = ['setuptools>=42', 'wheel', 'setuptools_scm[toml]>=3.4']
+requires = ["setuptools==80.9.0", "wheel", "setuptools_scm[toml]>=3.4", "pkgconfig>=1.5.1", "lxml==6.0.2"]
+
+[tool.mypy]
+files = ['src']
+ignore_missing_imports = false
+warn_unused_configs = true
+disallow_subclassing_any = true
+disallow_any_generics = true
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+disallow_any_unimported = true
+strict_optional = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_return_any = true
+warn_no_return = true
+no_implicit_reexport = true
+show_error_codes = true
+
+[tool.ruff]
+# Maximum line length, same as your original Black + Flake8 config
+line-length = 130
+
+# Target Python version (used for autofixes and style rules)
+target-version = "py39"
+
+# Directories and files to exclude from linting and formatting
+exclude = [
+ ".venv*", # virtual environments
+ ".git", # git directory
+ "build", # build output
+ "dist", # distribution packages
+ "libs", # vendor libraries
+ ".eggs", # setuptools egg folders
+ ".direnv*", # direnv environments
+ "*_pb2.pyi" # protobuf-generated type stubs
+]
+
+[tool.ruff.lint]
+# Enable rule categories:
+# E = pycodestyle (style issues, like indentation, whitespace, etc.)
+# F = pyflakes (unused imports, undefined names)
+# I = isort (import sorting)
+# B = flake8-bugbear (common bugs & anti-patterns)
+# UP = pyupgrade (auto-upgrade syntax for newer Python)
+# SIM = flake8-simplify (simplifiable code patterns)
+# RUF = Ruff-native rules (extra, performance-optimized checks)
+select = ["E", "F", "I", "B", "UP", "SIM", "RUF"]
+# TODO: Add more rule categories as needed, e.g.:
+# D = pydocstyle (docstring format/style issues)
+
+[tool.ruff.lint.per-file-ignores]
+"*.pyi" = [
+ # Ignore formatting and import errors in stub files
+ "E301", # expected 1 blank line, found 0
+ "E302", # expected 2 blank lines, found 1
+ "E305", # expected 2 blank lines after class or function
+ "E501", # line too long
+ "E701", # multiple statements on one line
+ "F401", # unused import
+ "F811", # redefinition of unused name
+ "F822" # undefined name in `__all__`
+]
+"doc/source/conf.py" = [
+ "D1" # missing docstring in public module/class/function
+]
+"doc/source/examples/*.py" = [
+ "D1", # allow missing docstrings in examples
+ "E501" # allow long lines in code examples
+]
+"tests/*.py" = [
+ "D1" # allow missing docstrings in test files
+]
+
+[tool.ruff.format]
+# Always use single quotes (e.g., 'text' instead of "text")
+quote-style = "single"
+
+# Format code with or without trailing commas
+# true = prefer trailing commas where valid
+skip-magic-trailing-comma = false
+
+# Enforce Unix-style line endings (LF)
+line-ending = "lf"
+
+[tool.cibuildwheel]
+build = [
+ "cp39-*",
+ "cp310-*",
+ "cp311-*",
+ "cp312-*",
+ "cp313-*",
+ "cp314-*"
+]
+build-verbosity = 1
+build-frontend = "build"
+skip = [
+ "pp*", # Skips PyPy builds (pp38-*, pp39-*, etc.)
+ "*musllinux_riscv64" # maturin and ruff currently don’t support the musl + riscv64 target
+]
+test-command = "pytest -v --color=yes {package}/tests"
+before-test = "pip install -r requirements-test.txt"
+test-skip = "*-macosx_arm64"
+
+[tool.cibuildwheel.environment]
+PYXMLSEC_STATIC_DEPS = "true"
+
+[tool.cibuildwheel.linux]
+archs = ["x86_64", "aarch64", "riscv64"]
+environment-pass = [
+ "PYXMLSEC_LIBXML2_VERSION",
+ "PYXMLSEC_LIBXSLT_VERSION",
+ "PYXMLSEC_STATIC_DEPS",
+ "GH_TOKEN"
+]
+
+[tool.cibuildwheel.macos]
+archs = ["x86_64", "arm64"]
+before-all = "brew install perl"
+
+[tool.cibuildwheel.windows]
+archs = ["AMD64"]
+
+[[tool.cibuildwheel.overrides]]
+select = "*-manylinux*"
+before-all = "yum install -y perl-core"
diff --git a/requirements-test.txt b/requirements-test.txt
index dde7de3d..ad135d97 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,3 +1,5 @@
-r requirements.txt
-pytest>=4.6.9
-hypothesis
+
+pytest==8.4.1
+lxml-stubs==0.5.1
+ruff[format]==0.13.0
diff --git a/requirements.txt b/requirements.txt
index 827d75e0..8221c374 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1 @@
-pkgconfig
-lxml >= 3.8.0
+lxml==6.0.2
diff --git a/setup.cfg b/setup.cfg
index ada30e9b..8762c654 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,9 @@
[metadata]
-description-file = README.rst
+description_file = README.md
[bdist_rpm]
release = 1
-build-requires = pkg-config xmlsec1-devel libxml2-devel xmlsec1-openssl-devel
+build_requires = pkg-config xmlsec1-devel libxml2-devel xmlsec1-openssl-devel
group = Development/Libraries
requires = xmlsec1 xmlsec1-openssl
@@ -12,8 +12,11 @@ source-dir = doc/source
build-dir = doc/build
all_files = 1
-[upload_docs]
-upload-dir = doc/build/html
-
-[flake8]
-max-line-length = 130
+# [flake8]
+# per-file-ignores =
+# *.pyi: E301, E302, E305, E501, E701, F401, F822
+# doc/source/conf.py: D1
+# doc/source/examples/*.py: D1, E501
+# tests/*.py: D1
+# exclude = .venv*,.git,*_pb2.pyi,build,dist,libs,.eggs,.direnv*
+# max-line-length = 130
diff --git a/setup.py b/setup.py
index 6906174d..4100a52b 100644
--- a/setup.py
+++ b/setup.py
@@ -1,430 +1,17 @@
-import io
-import multiprocessing
-import os
-import subprocess
-import sys
-import tarfile
-import zipfile
-from distutils import log
-from distutils.errors import DistutilsError
+from pathlib import Path
from setuptools import Extension, setup
-from setuptools.command.build_ext import build_ext as build_ext_orig
-if sys.version_info >= (3, 4):
- from urllib.request import urlcleanup, urljoin, urlretrieve
-else:
- from urllib import urlcleanup, urlretrieve
- from urlparse import urljoin
-
-
-class build_ext(build_ext_orig, object):
- def info(self, message):
- self.announce(message, level=log.INFO)
-
- def run(self):
- if sys.version_info >= (3, 4):
- from pathlib import Path
- else:
- from pathlib2 import Path
-
- ext = self.ext_map['xmlsec']
- self.debug = os.environ.get('DEBUG', False)
- self.static = os.environ.get('STATIC_DEPS', False)
-
- if self.static or sys.platform == 'win32':
- self.info('starting static build on {}'.format(sys.platform))
- buildroot = Path('build', 'tmp')
-
- self.prefix_dir = buildroot / 'prefix'
- self.prefix_dir.mkdir(parents=True, exist_ok=True)
- self.prefix_dir = self.prefix_dir.absolute()
-
- self.build_libs_dir = buildroot / 'libs'
- self.build_libs_dir.mkdir(exist_ok=True)
-
- self.libs_dir = Path(os.environ.get('LIBS_DIR', 'libs'))
- self.libs_dir.mkdir(exist_ok=True)
-
- if sys.platform == 'win32':
- self.prepare_static_build_win()
- elif 'linux' in sys.platform:
- self.prepare_static_build_linux()
- else:
- import pkgconfig
-
- try:
- config = pkgconfig.parse('xmlsec1')
- except EnvironmentError:
- raise DistutilsError('Unable to invoke pkg-config.')
- except pkgconfig.PackageNotFoundError:
- raise DistutilsError('xmlsec1 is not installed or not in path.')
-
- if config is None or not config.get('libraries'):
- raise DistutilsError('Bad or incomplete result returned from pkg-config.')
-
- ext.define_macros.extend(config['define_macros'])
- ext.include_dirs.extend(config['include_dirs'])
- ext.library_dirs.extend(config['library_dirs'])
- ext.libraries.extend(config['libraries'])
-
- import lxml
-
- ext.include_dirs.extend(lxml.get_include())
-
- ext.define_macros.extend(
- [('MODULE_NAME', self.distribution.metadata.name), ('MODULE_VERSION', self.distribution.metadata.version)]
- )
- # escape the XMLSEC_CRYPTO macro value, see mehcode/python-xmlsec#141
- for (key, value) in ext.define_macros:
- if key == 'XMLSEC_CRYPTO' and not (value.startswith('"') and value.endswith('"')):
- ext.define_macros.remove((key, value))
- ext.define_macros.append((key, '"{0}"'.format(value)))
- break
-
- if sys.platform == 'win32':
- ext.extra_compile_args.append('/Zi')
- else:
- ext.extra_compile_args.extend(
- [
- '-g',
- '-std=c99',
- '-fPIC',
- '-fno-strict-aliasing',
- '-Wno-error=declaration-after-statement',
- '-Werror=implicit-function-declaration',
- ]
- )
-
- if self.debug:
- ext.extra_compile_args.append('-Wall')
- ext.extra_compile_args.append('-O0')
- ext.define_macros.append(('PYXMLSEC_ENABLE_DEBUG', '1'))
- else:
- ext.extra_compile_args.append('-Os')
-
- super(build_ext, self).run()
-
- def prepare_static_build_win(self):
- release_url = 'https://github.com/bgaifullin/libxml2-win-binaries/releases/download/v2018.08/'
- if sys.version_info < (3, 5):
- if sys.maxsize > 2147483647:
- suffix = 'vs2008.win64'
- else:
- suffix = "vs2008.win32"
- else:
- if sys.maxsize > 2147483647:
- suffix = "win64"
- else:
- suffix = "win32"
-
- libs = [
- 'libxml2-2.9.4.{}.zip'.format(suffix),
- 'libxslt-1.1.29.{}.zip'.format(suffix),
- 'zlib-1.2.8.{}.zip'.format(suffix),
- 'iconv-1.14.{}.zip'.format(suffix),
- 'openssl-1.0.1.{}.zip'.format(suffix),
- 'xmlsec-1.2.24.{}.zip'.format(suffix),
- ]
-
- for libfile in libs:
- url = urljoin(release_url, libfile)
- destfile = self.libs_dir / libfile
- if destfile.is_file():
- self.info('Using local copy of "{}"'.format(url))
- else:
- self.info('Retrieving "{}" to "{}"'.format(url, destfile))
- urlcleanup() # work around FTP bug 27973 in Py2.7.12+
- urlretrieve(url, str(destfile))
-
- for p in self.libs_dir.glob('*.zip'):
- with zipfile.ZipFile(str(p)) as f:
- destdir = self.build_libs_dir
- f.extractall(path=str(destdir))
-
- ext = self.ext_map['xmlsec']
- ext.define_macros = [
- ('XMLSEC_CRYPTO', '\\"openssl\\"'),
- ('__XMLSEC_FUNCTION__', '__FUNCTION__'),
- ('XMLSEC_NO_GOST', '1'),
- ('XMLSEC_NO_XKMS', '1'),
- ('XMLSEC_NO_CRYPTO_DYNAMIC_LOADING', '1'),
- ('XMLSEC_CRYPTO_OPENSSL', '1'),
- ('UNICODE', '1'),
- ('_UNICODE', '1'),
- ('LIBXML_ICONV_ENABLED', 1),
- ('LIBXML_STATIC', '1'),
- ('LIBXSLT_STATIC', '1'),
- ('XMLSEC_STATIC', '1'),
- ('inline', '__inline'),
- ]
- ext.libraries = [
- 'libxmlsec_a',
- 'libxmlsec-openssl_a',
- 'libeay32',
- 'iconv_a',
- 'libxslt_a',
- 'libexslt_a',
- 'libxml2_a',
- 'zlib',
- 'WS2_32',
- 'Advapi32',
- 'User32',
- 'Gdi32',
- 'Crypt32',
- ]
- ext.library_dirs = [str(p.absolute()) for p in self.build_libs_dir.rglob('lib')]
-
- includes = [p for p in self.build_libs_dir.rglob('include') if p.is_dir()]
- includes.append(next(p / 'xmlsec' for p in includes if (p / 'xmlsec').is_dir()))
- ext.include_dirs = [str(p.absolute()) for p in includes]
-
- def prepare_static_build_linux(self):
- self.openssl_version = os.environ.get('OPENSSL_VERSION', '1.1.1g')
- self.libiconv_version = os.environ.get('LIBICONV_VERSION', '1.16')
- self.libxml2_version = os.environ.get('LIBXML2_VERSION', None)
- self.libxslt_version = os.environ.get('LIBXLST_VERSION', None)
- self.zlib_version = os.environ.get('ZLIB_VERSION', '1.2.11')
- self.xmlsec1_version = os.environ.get('XMLSEC1_VERSION', '1.2.30')
-
- self.info('Settings:')
- self.info('{:20} {}'.format('Lib sources in:', self.libs_dir.absolute()))
- self.info('{:20} {}'.format('zlib version:', self.zlib_version))
- self.info('{:20} {}'.format('libiconv version:', self.libiconv_version))
- self.info('{:20} {}'.format('libxml2 version:', self.libxml2_version or 'unset, using latest'))
- self.info('{:20} {}'.format('libxslt version:', self.libxslt_version or 'unset, using latest'))
- self.info('{:20} {}'.format('xmlsec1 version:', self.xmlsec1_version))
-
- # fetch openssl
- openssl_tar = next(self.libs_dir.glob('openssl*.tar.gz'), None)
- if openssl_tar is None:
- self.info('OpenSSL source tar not found, downloading ...')
- openssl_tar = self.libs_dir / 'openssl.tar.gz'
- urlretrieve('https://www.openssl.org/source/openssl-{}.tar.gz'.format(self.openssl_version), str(openssl_tar))
-
- # fetch zlib
- zlib_tar = next(self.libs_dir.glob('zlib*.tar.gz'), None)
- if zlib_tar is None:
- self.info('zlib source tar not found, downloading ...')
- zlib_tar = self.libs_dir / 'zlib.tar.gz'
- urlretrieve('https://zlib.net/zlib-{}.tar.gz'.format(self.zlib_version), str(zlib_tar))
-
- # fetch libiconv
- libiconv_tar = next(self.libs_dir.glob('libiconv*.tar.gz'), None)
- if libiconv_tar is None:
- self.info('libiconv source tar not found, downloading ...')
- libiconv_tar = self.libs_dir / 'libiconv.tar.gz'
- urlretrieve(
- 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{}.tar.gz'.format(self.libiconv_version), str(libiconv_tar)
- )
-
- # fetch libxml2
- libxml2_tar = next(self.libs_dir.glob('libxml2*.tar.gz'), None)
- if libxml2_tar is None:
- self.info('Libxml2 source tar not found, downloading ...')
- if self.libxml2_version is None:
- url = 'http://xmlsoft.org/sources/LATEST_LIBXML2'
- else:
- url = 'http://xmlsoft.org/sources/libxml2-{}.tar.gz'.format(self.libxml2_version)
- libxml2_tar = self.libs_dir / 'libxml2.tar.gz'
- urlretrieve(url, str(libxml2_tar))
-
- # fetch libxslt
- libxslt_tar = next(self.libs_dir.glob('libxslt*.tar.gz'), None)
- if libxslt_tar is None:
- self.info('libxslt source tar not found, downloading ...')
- if self.libxslt_version is None:
- url = 'http://xmlsoft.org/sources/LATEST_LIBXSLT'
- else:
- url = 'http://xmlsoft.org/sources/libxslt-{}.tar.gz'.format(self.libxslt_version)
- libxslt_tar = self.libs_dir / 'libxslt.tar.gz'
- urlretrieve(url, str(libxslt_tar))
-
- # fetch xmlsec1
- xmlsec1_tar = next(self.libs_dir.glob('xmlsec1*.tar.gz'), None)
- if xmlsec1_tar is None:
- self.info('xmlsec1 source tar not found, downloading ...')
- url = 'http://www.aleksey.com/xmlsec/download/xmlsec1-{}.tar.gz'.format(self.xmlsec1_version)
- xmlsec1_tar = self.libs_dir / 'xmlsec1.tar.gz'
- urlretrieve(url, str(xmlsec1_tar))
-
- for file in (openssl_tar, zlib_tar, libiconv_tar, libxml2_tar, libxslt_tar, xmlsec1_tar):
- self.info('Unpacking {}'.format(file.name))
- try:
- with tarfile.open(str(file)) as tar:
- tar.extractall(path=str(self.build_libs_dir))
- except EOFError:
- raise DistutilsError('Bad {} downloaded; remove it and try again.'.format(file.name))
-
- prefix_arg = '--prefix={}'.format(self.prefix_dir)
-
- cflags = ['-fPIC']
- env = os.environ.copy()
- if 'CFLAGS' in env:
- env['CFLAGS'].append(' '.join(cflags))
- else:
- env['CFLAGS'] = ' '.join(cflags)
-
- self.info('Building OpenSSL')
- openssl_dir = next(self.build_libs_dir.glob('openssl-*'))
- subprocess.check_output(['./config', prefix_arg, 'no-shared', '-fPIC'], cwd=str(openssl_dir), env=env)
- subprocess.check_output(['make', '-j{}'.format(multiprocessing.cpu_count() + 1)], cwd=str(openssl_dir), env=env)
- subprocess.check_output(
- ['make', '-j{}'.format(multiprocessing.cpu_count() + 1), 'install_sw'], cwd=str(openssl_dir), env=env
- )
-
- self.info('Building zlib')
- zlib_dir = next(self.build_libs_dir.glob('zlib-*'))
- subprocess.check_output(['./configure', prefix_arg], cwd=str(zlib_dir), env=env)
- subprocess.check_output(['make', '-j{}'.format(multiprocessing.cpu_count() + 1)], cwd=str(zlib_dir), env=env)
- subprocess.check_output(['make', '-j{}'.format(multiprocessing.cpu_count() + 1), 'install'], cwd=str(zlib_dir), env=env)
-
- self.info('Building libiconv')
- libiconv_dir = next(self.build_libs_dir.glob('libiconv-*'))
- subprocess.check_output(
- ['./configure', prefix_arg, '--disable-dependency-tracking', '--disable-shared'], cwd=str(libiconv_dir), env=env
- )
- subprocess.check_output(['make', '-j{}'.format(multiprocessing.cpu_count() + 1)], cwd=str(libiconv_dir), env=env)
- subprocess.check_output(
- ['make', '-j{}'.format(multiprocessing.cpu_count() + 1), 'install'], cwd=str(libiconv_dir), env=env
- )
-
- self.info('Building LibXML2')
- libxml2_dir = next(self.build_libs_dir.glob('libxml2-*'))
- subprocess.check_output(
- [
- './configure',
- prefix_arg,
- '--disable-dependency-tracking',
- '--disable-shared',
- '--enable-rebuild-docs=no',
- '--without-lzma',
- '--without-python',
- '--with-iconv={}'.format(self.prefix_dir),
- '--with-zlib={}'.format(self.prefix_dir),
- ],
- cwd=str(libxml2_dir),
- env=env,
- )
- subprocess.check_output(['make', '-j{}'.format(multiprocessing.cpu_count() + 1)], cwd=str(libxml2_dir), env=env)
- subprocess.check_output(
- ['make', '-j{}'.format(multiprocessing.cpu_count() + 1), 'install'], cwd=str(libxml2_dir), env=env
- )
-
- self.info('Building libxslt')
- libxslt_dir = next(self.build_libs_dir.glob('libxslt-*'))
- subprocess.check_output(
- [
- './configure',
- prefix_arg,
- '--disable-dependency-tracking',
- '--disable-shared',
- '--without-python',
- '--without-crypto',
- '--with-libxml-prefix={}'.format(self.prefix_dir),
- ],
- cwd=str(libxslt_dir),
- env=env,
- )
- subprocess.check_output(['make', '-j{}'.format(multiprocessing.cpu_count() + 1)], cwd=str(libxslt_dir), env=env)
- subprocess.check_output(
- ['make', '-j{}'.format(multiprocessing.cpu_count() + 1), 'install'], cwd=str(libxslt_dir), env=env
- )
-
- self.info('Building xmlsec1')
- if 'LDFLAGS' in env:
- env['LDFLAGS'].append(' -lpthread')
- else:
- env['LDFLAGS'] = '-lpthread'
- xmlsec1_dir = next(self.build_libs_dir.glob('xmlsec1-*'))
- subprocess.check_output(
- [
- './configure',
- prefix_arg,
- '--disable-shared',
- '--disable-gost',
- '--disable-crypto-dl',
- '--enable-static=yes',
- '--enable-shared=no',
- '--enable-static-linking=yes',
- '--with-default-crypto=openssl',
- '--with-openssl={}'.format(self.prefix_dir),
- '--with-libxml={}'.format(self.prefix_dir),
- '--with-libxslt={}'.format(self.prefix_dir),
- ],
- cwd=str(xmlsec1_dir),
- env=env,
- )
- subprocess.check_output(
- ['make', '-j{}'.format(multiprocessing.cpu_count() + 1)]
- + ['-I{}'.format(str(self.prefix_dir / 'include')), '-I{}'.format(str(self.prefix_dir / 'include' / 'libxml'))],
- cwd=str(xmlsec1_dir),
- env=env,
- )
- subprocess.check_output(
- ['make', '-j{}'.format(multiprocessing.cpu_count() + 1), 'install'], cwd=str(xmlsec1_dir), env=env
- )
-
- ext = self.ext_map['xmlsec']
- ext.define_macros = [
- ('__XMLSEC_FUNCTION__', '__func__'),
- ('XMLSEC_NO_SIZE_T', None),
- ('XMLSEC_NO_GOST', '1'),
- ('XMLSEC_NO_GOST2012', '1'),
- ('XMLSEC_NO_XKMS', '1'),
- ('XMLSEC_CRYPTO', '\\"openssl\\"'),
- ('XMLSEC_NO_CRYPTO_DYNAMIC_LOADING', '1'),
- ('XMLSEC_CRYPTO_OPENSSL', '1'),
- ('LIBXML_ICONV_ENABLED', 1),
- ('LIBXML_STATIC', '1'),
- ('LIBXSLT_STATIC', '1'),
- ('XMLSEC_STATIC', '1'),
- ('inline', '__inline'),
- ('UNICODE', '1'),
- ('_UNICODE', '1'),
- ]
-
- ext.include_dirs.append(str(self.prefix_dir / 'include'))
- ext.include_dirs.extend([str(p.absolute()) for p in (self.prefix_dir / 'include').iterdir() if p.is_dir()])
-
- ext.library_dirs = []
- ext.libraries = ['m', 'rt']
- extra_objects = [
- 'libxmlsec1.a',
- 'libxslt.a',
- 'libxml2.a',
- 'libz.a',
- 'libxmlsec1-openssl.a',
- 'libcrypto.a',
- 'libiconv.a',
- 'libxmlsec1.a',
- ]
- ext.extra_objects = [str(self.prefix_dir / 'lib' / o) for o in extra_objects]
-
-
-if sys.version_info >= (3, 4):
- from pathlib import Path
-
- src_root = Path(__file__).parent / 'src'
- sources = [str(p.absolute()) for p in src_root.rglob('*.c')]
-else:
- import fnmatch
-
- src_root = os.path.join(os.path.dirname(__file__), 'src')
- sources = []
- for root, _, files in os.walk(src_root):
- for file in fnmatch.filter(files, '*.c'):
- sources.append(os.path.join(root, file))
+from build_support.build_ext import build_ext
+src_root = Path(__file__).parent / 'src'
+sources = [str(path.absolute()) for path in src_root.rglob('*.c')]
pyxmlsec = Extension('xmlsec', sources=sources)
-setup_reqs = ['setuptools_scm[toml]>=3.4', 'pkgconfig', 'lxml>=3.8']
-
-if sys.version_info < (3, 4):
- setup_reqs.append('pathlib2')
+setup_reqs = ['setuptools_scm[toml]>=3.4', 'pkgconfig>=1.5.1', 'lxml>=3.8']
-with io.open('README.rst', encoding='utf-8') as f:
- long_desc = f.read()
+with open('README.md', encoding='utf-8') as readme:
+ long_desc = readme.read()
setup(
@@ -432,16 +19,22 @@ def prepare_static_build_linux(self):
use_scm_version=True,
description='Python bindings for the XML Security Library',
long_description=long_desc,
+ long_description_content_type='text/markdown',
ext_modules=[pyxmlsec],
cmdclass={'build_ext': build_ext},
- python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
+ python_requires='>=3.9',
setup_requires=setup_reqs,
install_requires=['lxml>=3.8'],
- author="Bulat Gaifullin",
+ author='Bulat Gaifullin',
author_email='support@mehcode.com',
maintainer='Oleg Hoefling',
maintainer_email='oleg.hoefling@gmail.com',
url='https://github.com/mehcode/python-xmlsec',
+ project_urls={
+ 'Documentation': 'https://xmlsec.readthedocs.io',
+ 'Source': 'https://github.com/mehcode/python-xmlsec',
+ 'Changelog': 'https://github.com/mehcode/python-xmlsec/releases',
+ },
license='MIT',
keywords=['xmlsec'],
classifiers=[
@@ -451,12 +44,13 @@ def prepare_static_build_linux(self):
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: C',
- 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ 'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
'Topic :: Text Processing :: Markup :: XML',
'Typing :: Typed',
],
diff --git a/src/common.h b/src/common.h
index 243ed651..a6176551 100644
--- a/src/common.h
+++ b/src/common.h
@@ -13,7 +13,7 @@
#include "debug.h"
#ifndef MODULE_NAME
-#define MODULE_NAME "xmlsec"
+#define MODULE_NAME xmlsec
#endif
#define JOIN(X,Y) DO_JOIN1(X,Y)
diff --git a/src/constants.c b/src/constants.c
index 02f5250c..bd1fa5e0 100644
--- a/src/constants.c
+++ b/src/constants.c
@@ -27,7 +27,7 @@ static PyObject* PyXmlSec_Transform__str__(PyObject* self) {
else
snprintf(buf, sizeof(buf), "%s, None", transform->id->name);
- return PyString_FromString(buf);
+ return PyUnicode_FromString(buf);
}
// __repr__ method
@@ -38,18 +38,18 @@ static PyObject* PyXmlSec_Transform__repr__(PyObject* self) {
snprintf(buf, sizeof(buf), "__Transform('%s', '%s', %d)", transform->id->name, transform->id->href, transform->id->usage);
else
snprintf(buf, sizeof(buf), "__Transform('%s', None, %d)", transform->id->name, transform->id->usage);
- return PyString_FromString(buf);
+ return PyUnicode_FromString(buf);
}
static const char PyXmlSec_TransformNameGet__doc__[] = "The transform's name.";
static PyObject* PyXmlSec_TransformNameGet(PyXmlSec_Transform* self, void* closure) {
- return PyString_FromString((const char*)self->id->name);
+ return PyUnicode_FromString((const char*)self->id->name);
}
static const char PyXmlSec_TransformHrefGet__doc__[] = "The transform's identification string (href).";
static PyObject* PyXmlSec_TransformHrefGet(PyXmlSec_Transform* self, void* closure) {
if (self->id->href != NULL)
- return PyString_FromString((const char*)self->id->href);
+ return PyUnicode_FromString((const char*)self->id->href);
Py_RETURN_NONE;
}
@@ -149,7 +149,7 @@ static PyObject* PyXmlSec_KeyData__str__(PyObject* self) {
snprintf(buf, sizeof(buf), "%s, %s", keydata->id->name, keydata->id->href);
else
snprintf(buf, sizeof(buf), "%s, None", keydata->id->name);
- return PyString_FromString(buf);
+ return PyUnicode_FromString(buf);
}
// __repr__ method
@@ -160,18 +160,18 @@ static PyObject* PyXmlSec_KeyData__repr__(PyObject* self) {
snprintf(buf, sizeof(buf), "__KeyData('%s', '%s')", keydata->id->name, keydata->id->href);
else
snprintf(buf, sizeof(buf), "__KeyData('%s', None)", keydata->id->name);
- return PyString_FromString(buf);
+ return PyUnicode_FromString(buf);
}
static const char PyXmlSec_KeyDataNameGet__doc__[] = "The key data's name.";
static PyObject* PyXmlSec_KeyDataNameGet(PyXmlSec_KeyData* self, void* closure) {
- return PyString_FromString((const char*)self->id->name);
+ return PyUnicode_FromString((const char*)self->id->name);
}
static const char PyXmlSec_KeyDataHrefGet__doc__[] = "The key data's identification string (href).";
static PyObject* PyXmlSec_KeyDataHrefGet(PyXmlSec_KeyData* self, void* closure) {
if (self->id->href != NULL)
- return PyString_FromString((const char*)self->id->href);
+ return PyUnicode_FromString((const char*)self->id->href);
Py_RETURN_NONE;
}
@@ -245,7 +245,6 @@ static PyObject* PyXmlSec_KeyDataNew(xmlSecKeyDataId id) {
return (PyObject*)keydata;
}
-#ifdef PY3K
static PyModuleDef PyXmlSec_ConstantsModule =
{
PyModuleDef_HEAD_INIT,
@@ -253,7 +252,6 @@ static PyModuleDef PyXmlSec_ConstantsModule =
PYXMLSEC_CONSTANTS_DOC,
-1, NULL, NULL, NULL, NULL, NULL
};
-#endif // PY3K
// initialize constants module and registers it base package
int PyXmlSec_ConstantsModule_Init(PyObject* package) {
@@ -267,12 +265,7 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PyObject* keyDataTypeCls = NULL;
PyObject* tmp = NULL;
-#ifdef PY3K
constants = PyModule_Create(&PyXmlSec_ConstantsModule);
-#else
- constants = Py_InitModule3(STRINGIFY(MODULE_NAME) ".constants", NULL, PYXMLSEC_CONSTANTS_DOC);
- Py_XINCREF(constants);
-#endif
if (!constants) return -1;
@@ -292,7 +285,7 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
#undef PYXMLSEC_ADD_INT_CONSTANT
#define PYXMLSEC_DECLARE_NAMESPACE(var, name) \
- if (!(var = PyCreateDummyObject(name))) goto ON_FAIL; \
+ if (!(var = PyModule_New(name))) goto ON_FAIL; \
if (PyModule_AddObject(package, name, var) < 0) goto ON_FAIL; \
Py_INCREF(var); // add object steels reference
@@ -308,7 +301,7 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
#define PYXMLSEC_ADD_NS_CONSTANT(name, lname) \
- tmp = PyString_FromString((const char*)(JOIN(xmlSec, name))); \
+ tmp = PyUnicode_FromString((const char*)(JOIN(xmlSec, name))); \
PYXMLSEC_ADD_CONSTANT(nsCls, name, lname);
// namespaces
@@ -323,8 +316,6 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_NS_CONSTANT(XPathNs, "XPATH");
PYXMLSEC_ADD_NS_CONSTANT(XPath2Ns, "XPATH2");
PYXMLSEC_ADD_NS_CONSTANT(XPointerNs, "XPOINTER");
- PYXMLSEC_ADD_NS_CONSTANT(Soap11Ns, "SOAP11");
- PYXMLSEC_ADD_NS_CONSTANT(Soap12Ns, "SOAP12");
PYXMLSEC_ADD_NS_CONSTANT(NsExcC14N, "EXC_C14N");
PYXMLSEC_ADD_NS_CONSTANT(NsExcC14NWithComments, "EXC_C14N_WITH_COMMENT");
@@ -334,7 +325,7 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
#define PYXMLSEC_ADD_ENC_CONSTANT(name, lname) \
- tmp = PyString_FromString((const char*)(JOIN(xmlSec, name))); \
+ tmp = PyUnicode_FromString((const char*)(JOIN(xmlSec, name))); \
PYXMLSEC_ADD_CONSTANT(encryptionTypeCls, name, lname);
// encryption type
@@ -349,7 +340,7 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
#define PYXMLSEC_ADD_NODE_CONSTANT(name, lname) \
- tmp = PyString_FromString((const char*)(JOIN(xmlSec, name))); \
+ tmp = PyUnicode_FromString((const char*)(JOIN(xmlSec, name))); \
PYXMLSEC_ADD_CONSTANT(nodeCls, name, lname);
// node
@@ -448,12 +439,18 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataRetrievalMethod, "RETRIEVALMETHOD")
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataEncryptedKey, "ENCRYPTEDKEY")
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataAes, "AES")
+#ifndef XMLSEC_NO_DES
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataDes, "DES")
+#endif
#ifndef XMLSEC_NO_DSA
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataDsa, "DSA")
#endif
-#if XMLSEC_VERSION_HEX > 306
+#if XMLSEC_VERSION_HEX > 0x10212 && XMLSEC_VERSION_HEX < 0x10303
+ // from version 1.2.19 to version 1.3.2 (inclusive)
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataEcdsa, "ECDSA")
+#elif XMLSEC_VERSION_HEX >= 0x10303
+ // from version 1.3.3 (inclusive)
+ PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataEc, "ECDSA")
#endif
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataHmac, "HMAC")
PYXMLSEC_ADD_KEYDATA_CONSTANT(KeyDataRsa, "RSA")
@@ -484,7 +481,6 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformXPath, "XPATH");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformXPath2, "XPATH2");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformXPointer, "XPOINTER");
- PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformXslt, "XSLT");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformRemoveXmlTagsC14N, "REMOVE_XML_TAGS_C14N");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformVisa3DHack, "VISA3D_HACK");
@@ -496,13 +492,19 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformKWAes192, "KW_AES192");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformKWAes256, "KW_AES256");
+#ifndef XMLSEC_NO_DES
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformDes3Cbc, "DES3");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformKWDes3, "KW_DES3");
+#endif
#ifndef XMLSEC_NO_DSA
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformDsaSha1, "DSA_SHA1");
#endif
+#ifndef XMLSEC_NO_XSLT
+ PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformXslt, "XSLT");
+#endif
-#if XMLSEC_VERSION_HEX > 306
+#if XMLSEC_VERSION_HEX > 0x10212
+ // from version 1.2.19
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformEcdsaSha1, "ECDSA_SHA1");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformEcdsaSha224, "ECDSA_SHA224");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformEcdsaSha256, "ECDSA_SHA256");
@@ -510,7 +512,10 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformEcdsaSha512, "ECDSA_SHA512");
#endif
+#ifndef XMLSEC_NO_MD5
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformHmacMd5, "HMAC_MD5");
+#endif
+
#ifndef XMLSEC_NO_RIPEMD160
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformHmacRipemd160, "HMAC_RIPEMD160");
#endif
@@ -520,7 +525,10 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformHmacSha384, "HMAC_SHA384");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformHmacSha512, "HMAC_SHA512");
+#ifndef XMLSEC_NO_MD5
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformRsaMd5, "RSA_MD5");
+#endif
+
#ifndef XMLSEC_NO_RIPEMD160
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformRsaRipemd160, "RSA_RIPEMD160");
#endif
@@ -532,7 +540,10 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformRsaPkcs1, "RSA_PKCS1");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformRsaOaep, "RSA_OAEP");
+#ifndef XMLSEC_NO_MD5
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformMd5, "MD5");
+#endif
+
#ifndef XMLSEC_NO_RIPEMD160
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformRipemd160, "RIPEMD160");
#endif
@@ -543,6 +554,13 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) {
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformSha384, "SHA384");
PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformSha512, "SHA512");
+#if XMLSEC_VERSION_HEX > 0x1021B
+ // from version 1.2.28
+ PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformAes128Gcm, "AES128_GCM");
+ PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformAes192Gcm, "AES192_GCM");
+ PYXMLSEC_ADD_TRANSFORM_CONSTANT(TransformAes256Gcm, "AES256_GCM");
+#endif
+
PYXMLSEC_CLOSE_NAMESPACE(transformCls);
#undef PYXMLSEC_ADD_TRANSFORM_CONSTANT
diff --git a/src/ds.c b/src/ds.c
index 43d3d118..d0b4bdf9 100644
--- a/src/ds.c
+++ b/src/ds.c
@@ -87,11 +87,21 @@ static int PyXmlSec_SignatureContextKeySet(PyObject* self, PyObject* value, void
PyXmlSec_Key* key;
PYXMLSEC_DEBUGF("%p, %p", self, value);
+
+ if (value == NULL) { // key deletion
+ if (ctx->handle->signKey != NULL) {
+ xmlSecKeyDestroy(ctx->handle->signKey);
+ ctx->handle->signKey = NULL;
+ }
+ return 0;
+ }
+
if (!PyObject_IsInstance(value, (PyObject*)PyXmlSec_KeyType)) {
PyErr_SetString(PyExc_TypeError, "instance of *xmlsec.Key* expected.");
return -1;
}
key = (PyXmlSec_Key*)value;
+
if (key->handle == NULL) {
PyErr_SetString(PyExc_TypeError, "empty key.");
return -1;
@@ -252,6 +262,7 @@ static int PyXmlSec_ProcessSignBinary(PyXmlSec_SignatureContext* ctx, const xmlS
if (ctx->handle->signKey == NULL) {
PyErr_SetString(PyXmlSec_Error, "Sign key is not specified.");
+ return -1;
}
if (ctx->handle->signMethod != NULL) {
diff --git a/src/enc.c b/src/enc.c
index 02c01d8a..42195dd3 100644
--- a/src/enc.c
+++ b/src/enc.c
@@ -17,6 +17,11 @@
#include
#include
+// Backwards compatibility with xmlsec 1.2
+#ifndef XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH
+#define XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH 0x00008000
+#endif
+
typedef struct {
PyObject_HEAD
xmlSecEncCtxPtr handle;
@@ -50,6 +55,13 @@ static int PyXmlSec_EncryptionContext__init__(PyObject* self, PyObject* args, Py
}
ctx->manager = manager;
PYXMLSEC_DEBUGF("%p: init enc context - ok, manager - %p", self, manager);
+
+ // xmlsec 1.3 changed the key search to strict mode, causing various examples
+ // in the docs to fail. For backwards compatibility, this changes it back to
+ // lax mode for now.
+ ctx->handle->keyInfoReadCtx.flags = XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH;
+ ctx->handle->keyInfoWriteCtx.flags = XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH;
+
return 0;
ON_FAIL:
PYXMLSEC_DEBUGF("%p: init enc context - failed", self);
@@ -90,6 +102,15 @@ static int PyXmlSec_EncryptionContextKeySet(PyObject* self, PyObject* value, voi
PyXmlSec_Key* key;
PYXMLSEC_DEBUGF("%p, %p", self, value);
+
+ if (value == NULL) { // key deletion
+ if (ctx->handle->encKey != NULL) {
+ xmlSecKeyDestroy(ctx->handle->encKey);
+ ctx->handle->encKey = NULL;
+ }
+ return 0;
+ }
+
if (!PyObject_IsInstance(value, (PyObject*)PyXmlSec_KeyType)) {
PyErr_SetString(PyExc_TypeError, "instance of *xmlsec.Key* expected.");
return -1;
@@ -174,7 +195,7 @@ static PyObject* PyXmlSec_EncryptionContextEncryptBinary(PyObject* self, PyObjec
// release the replaced nodes in a way safe for `lxml`
static void PyXmlSec_ClearReplacedNodes(xmlSecEncCtxPtr ctx, PyXmlSec_LxmlDocumentPtr doc) {
- PyXmlSec_LxmlElementPtr* elem;
+ PyXmlSec_LxmlElementPtr elem;
// release the replaced nodes in a way safe for `lxml`
xmlNodePtr n = ctx->replacedNodeList;
xmlNodePtr nn;
@@ -183,7 +204,7 @@ static void PyXmlSec_ClearReplacedNodes(xmlSecEncCtxPtr ctx, PyXmlSec_LxmlDocume
PYXMLSEC_DEBUGF("clear replaced node %p", n);
nn = n->next;
// if n has references, it will not be deleted
- elem = PyXmlSec_elementFactory(doc, n);
+ elem = (PyXmlSec_LxmlElementPtr)PyXmlSec_elementFactory(doc, n);
if (NULL == elem)
xmlFreeNode(n);
else
@@ -224,7 +245,7 @@ static PyObject* PyXmlSec_EncryptionContextEncryptXml(PyObject* self, PyObject*
}
tmpType = xmlGetProp(template->_c_node, XSTR("Type"));
if (tmpType == NULL || !(xmlStrEqual(tmpType, xmlSecTypeEncElement) || xmlStrEqual(tmpType, xmlSecTypeEncContent))) {
- PyErr_SetString(PyXmlSec_Error, "unsupported `Type`, it should be `element` or `content`)");
+ PyErr_SetString(PyXmlSec_Error, "unsupported `Type`, it should be `element` or `content`");
goto ON_FAIL;
}
diff --git a/src/exception.c b/src/exception.c
index 2ca5ab57..ac0e44ee 100644
--- a/src/exception.c
+++ b/src/exception.c
@@ -23,7 +23,11 @@ PyObject* PyXmlSec_Error;
PyObject* PyXmlSec_InternalError;
PyObject* PyXmlSec_VerificationError;
+#if PY_MINOR_VERSION >= 7
+static Py_tss_t PyXmlSec_LastErrorKey;
+#else
static int PyXmlSec_LastErrorKey = 0;
+#endif
static int PyXmlSec_PrintErrorMessage = 0;
@@ -71,16 +75,26 @@ static PyXmlSec_ErrorHolder* PyXmlSec_ExchangeLastError(PyXmlSec_ErrorHolder* e)
PyXmlSec_ErrorHolder* v;
int r;
+ #if PY_MINOR_VERSION >= 7
+ if (PyThread_tss_is_created(&PyXmlSec_LastErrorKey) == 0) {
+ #else
if (PyXmlSec_LastErrorKey == 0) {
+ #endif
PYXMLSEC_DEBUG("WARNING: There is no error key.");
PyXmlSec_ErrorHolderFree(e);
return NULL;
}
// get_key_value and set_key_value are gil free
+ #if PY_MINOR_VERSION >= 7
+ v = (PyXmlSec_ErrorHolder*)PyThread_tss_get(&PyXmlSec_LastErrorKey);
+ //PyThread_tss_delete(&PyXmlSec_LastErrorKey);
+ r = PyThread_tss_set(&PyXmlSec_LastErrorKey, (void*)e);
+ #else
v = (PyXmlSec_ErrorHolder*)PyThread_get_key_value(PyXmlSec_LastErrorKey);
PyThread_delete_key_value(PyXmlSec_LastErrorKey);
r = PyThread_set_key_value(PyXmlSec_LastErrorKey, (void*)e);
+ #endif
PYXMLSEC_DEBUGF("set_key_value returns %d", r);
return v;
}
@@ -165,6 +179,16 @@ void PyXmlSecEnableDebugTrace(int v) {
PyXmlSec_PrintErrorMessage = v;
}
+void PyXmlSec_InstallErrorCallback() {
+ #if PY_MINOR_VERSION >= 7
+ if (PyThread_tss_is_created(&PyXmlSec_LastErrorKey) != 0) {
+ #else
+ if (PyXmlSec_LastErrorKey != 0) {
+ #endif
+ xmlSecErrorsSetCallback(PyXmlSec_ErrorCallback);
+ }
+}
+
// initializes errors module
int PyXmlSec_ExceptionsModule_Init(PyObject* package) {
PyXmlSec_Error = NULL;
@@ -184,10 +208,14 @@ int PyXmlSec_ExceptionsModule_Init(PyObject* package) {
if (PyModule_AddObject(package, "InternalError", PyXmlSec_InternalError) < 0) goto ON_FAIL;
if (PyModule_AddObject(package, "VerificationError", PyXmlSec_VerificationError) < 0) goto ON_FAIL;
- PyXmlSec_LastErrorKey = PyThread_create_key();
- if (PyXmlSec_LastErrorKey != 0) {
- xmlSecErrorsSetCallback(&PyXmlSec_ErrorCallback);
+ #if PY_MINOR_VERSION >= 7
+ if (PyThread_tss_create(&PyXmlSec_LastErrorKey) == 0) {
+ PyXmlSec_InstallErrorCallback();
}
+ #else
+ PyXmlSec_LastErrorKey = PyThread_create_key();
+ PyXmlSec_InstallErrorCallback();
+ #endif
return 0;
diff --git a/src/exception.h b/src/exception.h
index 9dea5ecb..687cd778 100644
--- a/src/exception.h
+++ b/src/exception.h
@@ -24,4 +24,6 @@ void PyXmlSec_ClearError(void);
void PyXmlSecEnableDebugTrace(int);
+void PyXmlSec_InstallErrorCallback();
+
#endif //__PYXMLSEC_EXCEPTIONS_H__
diff --git a/src/keys.c b/src/keys.c
index 7fd080a1..5ff04aae 100644
--- a/src/keys.c
+++ b/src/keys.c
@@ -142,7 +142,7 @@ static PyObject* PyXmlSec_KeyFromFile(PyObject* self, PyObject* args, PyObject*
Py_ssize_t data_size = 0;
PYXMLSEC_DEBUG("load key from file - start");
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OH|z:from_file", kwlist, &file, &format, &password)) {
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OI|z:from_file", kwlist, &file, &format, &password)) {
goto ON_FAIL;
}
@@ -163,7 +163,12 @@ static PyObject* PyXmlSec_KeyFromFile(PyObject* self, PyObject* args, PyObject*
if (is_content) {
key->handle = xmlSecCryptoAppKeyLoadMemory((const xmlSecByte*)data, (xmlSecSize)data_size, format, password, NULL, NULL);
} else {
- key->handle = xmlSecCryptoAppKeyLoad(data, format, password, NULL, NULL);
+ #if XMLSEC_VERSION_HEX >= 0x10303
+ // from version 1.3.3 (inclusive)
+ key->handle = xmlSecCryptoAppKeyLoadEx(data, xmlSecKeyDataTypePrivate, format, password, NULL, NULL);
+ #else
+ key->handle = xmlSecCryptoAppKeyLoad(data, format, password, NULL, NULL);
+ #endif
}
Py_END_ALLOW_THREADS;
@@ -185,6 +190,51 @@ static PyObject* PyXmlSec_KeyFromFile(PyObject* self, PyObject* args, PyObject*
return NULL;
}
+static const char PyXmlSec_KeyFromEngine__doc__[] = \
+ "from_engine(engine_and_key_id) -> xmlsec.Key\n"
+ "Loads PKI key from an engine.\n\n"
+ ":param engine_and_key_id: engine and key id, i.e. 'pkcs11;pkcs11:token=XmlsecToken;object=XmlsecKey;pin-value=password'\n"
+ ":type engine_and_key_id: :class:`str`, "
+ ":return: pointer to newly created key\n"
+ ":rtype: :class:`~xmlsec.Key`";
+static PyObject* PyXmlSec_KeyFromEngine(PyObject* self, PyObject* args, PyObject* kwargs) {
+ static char *kwlist[] = {"engine_and_key_id", NULL};
+
+ const char* engine_and_key_id = NULL;
+ PyXmlSec_Key* key = NULL;
+
+ PYXMLSEC_DEBUG("load key from engine - start");
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s:from_engine", kwlist, &engine_and_key_id)) {
+ goto ON_FAIL;
+ }
+
+ if ((key = PyXmlSec_NewKey1((PyTypeObject*)self)) == NULL) goto ON_FAIL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ #if XMLSEC_VERSION_HEX >= 0x10303
+ // from version 1.3.3 (inclusive)
+ key->handle = xmlSecCryptoAppKeyLoadEx(engine_and_key_id, xmlSecKeyDataTypePrivate, xmlSecKeyDataFormatEngine, NULL, xmlSecCryptoAppGetDefaultPwdCallback(), (void*)engine_and_key_id);
+ #else
+ key->handle = xmlSecCryptoAppKeyLoad(engine_and_key_id, xmlSecKeyDataFormatEngine, NULL, xmlSecCryptoAppGetDefaultPwdCallback(), (void*)engine_and_key_id);
+ #endif
+ Py_END_ALLOW_THREADS;
+
+ if (key->handle == NULL) {
+ PyXmlSec_SetLastError("cannot read key");
+ goto ON_FAIL;
+ }
+
+ key->is_own = 1;
+
+ PYXMLSEC_DEBUG("load key from engine - ok");
+ return (PyObject*)key;
+
+ON_FAIL:
+ PYXMLSEC_DEBUG("load key from engine - fail");
+ Py_XDECREF(key);
+ return NULL;
+}
+
static const char PyXmlSec_KeyGenerate__doc__[] = \
"generate(klass, size, type) -> xmlsec.Key\n"
"Generates key of kind ``klass`` with ``size`` and ``type``.\n\n"
@@ -249,7 +299,7 @@ static PyObject* PyXmlSec_KeyFromBinaryFile(PyObject* self, PyObject* args, PyOb
PYXMLSEC_DEBUG("load symmetric key - start");
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O&:from_binary_file", kwlist,
- PyXmlSec_KeyDataType, &keydata, PyString_FSConverter, &filepath))
+ PyXmlSec_KeyDataType, &keydata, PyUnicode_FSConverter, &filepath))
{
goto ON_FAIL;
}
@@ -436,7 +486,7 @@ static PyObject* PyXmlSec_KeyNameGet(PyObject* self, void* closure) {
}
cname = (const char*)xmlSecKeyGetName(key->handle);
if (cname != NULL) {
- return PyString_FromString(cname);
+ return PyUnicode_FromString(cname);
}
Py_RETURN_NONE;
}
@@ -452,10 +502,21 @@ static int PyXmlSec_KeyNameSet(PyObject* self, PyObject* value, void* closure) {
return -1;
}
- name = PyString_AsString(value);
+ if (value == NULL) {
+ if (xmlSecKeySetName(key->handle, NULL) < 0) {
+ PyXmlSec_SetLastError("cannot delete name");
+ return -1;
+ }
+ return 0;
+ }
+
+ name = PyUnicode_AsUTF8(value);
if (name == NULL) return -1;
- xmlSecKeySetName(key->handle, XSTR(name));
+ if (xmlSecKeySetName(key->handle, XSTR(name)) < 0) {
+ PyXmlSec_SetLastError("cannot set name");
+ return -1;
+ }
return 0;
}
@@ -483,6 +544,12 @@ static PyMethodDef PyXmlSec_KeyMethods[] = {
METH_CLASS|METH_VARARGS|METH_KEYWORDS,
PyXmlSec_KeyFromFile__doc__
},
+ {
+ "from_engine",
+ (PyCFunction)PyXmlSec_KeyFromEngine,
+ METH_CLASS|METH_VARARGS|METH_KEYWORDS,
+ PyXmlSec_KeyFromEngine__doc__
+ },
{
"generate",
(PyCFunction)PyXmlSec_KeyGenerate,
@@ -687,7 +754,7 @@ static PyObject* PyXmlSec_KeysManagerLoadCert(PyObject* self, PyObject* args, Py
PYXMLSEC_DEBUGF("%p: load cert - start", self);
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&II:load_cert", kwlist,
- PyString_FSConverter, &filepath, &format, &type)) {
+ PyUnicode_FSConverter, &filepath, &format, &type)) {
goto ON_FAIL;
}
diff --git a/src/lxml.c b/src/lxml.c
index 862ee682..c98e933b 100644
--- a/src/lxml.c
+++ b/src/lxml.c
@@ -9,16 +9,104 @@
#include "common.h"
#include "lxml.h"
+#include "exception.h"
#include
-#include
+#include
#include
#include
#include
+#define XMLSEC_EXTRACT_VERSION(x, y) ((x / (y)) % 100)
+
+#define XMLSEC_EXTRACT_MAJOR(x) XMLSEC_EXTRACT_VERSION(x, 100 * 100)
+#define XMLSEC_EXTRACT_MINOR(x) XMLSEC_EXTRACT_VERSION(x, 100)
+#define XMLSEC_EXTRACT_PATCH(x) XMLSEC_EXTRACT_VERSION(x, 1)
+
+static long PyXmlSec_GetLibXmlVersionLong() {
+ return PyOS_strtol(xmlParserVersion, NULL, 10);
+}
+long PyXmlSec_GetLibXmlVersionMajor() {
+ return XMLSEC_EXTRACT_MAJOR(PyXmlSec_GetLibXmlVersionLong());
+}
+long PyXmlSec_GetLibXmlVersionMinor() {
+ return XMLSEC_EXTRACT_MINOR(PyXmlSec_GetLibXmlVersionLong());
+}
+long PyXmlSec_GetLibXmlVersionPatch() {
+ return XMLSEC_EXTRACT_PATCH(PyXmlSec_GetLibXmlVersionLong());
+}
+
+long PyXmlSec_GetLibXmlCompiledVersionMajor() {
+ return XMLSEC_EXTRACT_MAJOR(LIBXML_VERSION);
+}
+long PyXmlSec_GetLibXmlCompiledVersionMinor() {
+ return XMLSEC_EXTRACT_MINOR(LIBXML_VERSION);
+}
+long PyXmlSec_GetLibXmlCompiledVersionPatch() {
+ return XMLSEC_EXTRACT_PATCH(LIBXML_VERSION);
+}
+
+static int PyXmlSec_CheckLxmlLibraryVersion(void) {
+ // Make sure that the version of libxml2 lxml is using is the same as the one we are using. Because
+ // we pass trees between the two libraries, we need to make sure that they are using the same version
+ // of libxml2, or we could run into difficult to debug segfaults.
+ // See: https://github.com/xmlsec/python-xmlsec/issues/283
+
+ PyObject* lxml = NULL;
+ PyObject* version = NULL;
+
+ // Default to failure
+ int result = -1;
+
+ lxml = PyImport_ImportModule("lxml.etree");
+ if (lxml == NULL) {
+ goto FINALIZE;
+ }
+ version = PyObject_GetAttrString(lxml, "LIBXML_VERSION");
+ if (version == NULL) {
+ goto FINALIZE;
+ }
+ if (!PyTuple_Check(version) || PyTuple_Size(version) < 2) {
+ goto FINALIZE;
+ }
+
+ PyObject* major = PyTuple_GetItem(version, 0);
+ if (major == NULL) {
+ goto FINALIZE;
+ }
+ PyObject* minor = PyTuple_GetItem(version, 1);
+ if (minor == NULL) {
+ goto FINALIZE;
+ }
+
+ if (!PyLong_Check(major) || !PyLong_Check(minor)) {
+ goto FINALIZE;
+ }
+
+ if (PyLong_AsLong(major) != PyXmlSec_GetLibXmlVersionMajor() || PyLong_AsLong(minor) != PyXmlSec_GetLibXmlVersionMinor()) {
+ goto FINALIZE;
+ }
+
+ result = 0;
+
+FINALIZE:
+ // Clear any errors that may have occurred
+ PyErr_Clear();
+
+ // Cleanup our references, and return the result
+ Py_XDECREF(lxml);
+ Py_XDECREF(version);
+
+ return result;
+}
int PyXmlSec_InitLxmlModule(void) {
+ if (PyXmlSec_CheckLxmlLibraryVersion() < 0) {
+ PyXmlSec_SetLastError("lxml & xmlsec libxml2 library version mismatch");
+ return -1;
+ }
+
return import_lxml__etree();
}
diff --git a/src/lxml.h b/src/lxml.h
index 435ccdff..72050efe 100644
--- a/src/lxml.h
+++ b/src/lxml.h
@@ -16,7 +16,7 @@
#include
#include
-#include
+#include
typedef struct LxmlElement* PyXmlSec_LxmlElementPtr;
typedef struct LxmlDocument* PyXmlSec_LxmlDocumentPtr;
@@ -29,4 +29,13 @@ PyXmlSec_LxmlElementPtr PyXmlSec_elementFactory(PyXmlSec_LxmlDocumentPtr doc, xm
// converts o to PyObject, None object is not allowed, does not increment ref_counts
int PyXmlSec_LxmlElementConverter(PyObject* o, PyXmlSec_LxmlElementPtr* p);
+// get version numbers for libxml2 both compiled and loaded
+long PyXmlSec_GetLibXmlVersionMajor();
+long PyXmlSec_GetLibXmlVersionMinor();
+long PyXmlSec_GetLibXmlVersionPatch();
+
+long PyXmlSec_GetLibXmlCompiledVersionMajor();
+long PyXmlSec_GetLibXmlCompiledVersionMinor();
+long PyXmlSec_GetLibXmlCompiledVersionPatch();
+
#endif // __PYXMLSEC_LXML_H__
diff --git a/src/main.c b/src/main.c
index 85f457f2..61eac139 100644
--- a/src/main.c
+++ b/src/main.c
@@ -10,10 +10,13 @@
#include "common.h"
#include "platform.h"
#include "exception.h"
+#include "lxml.h"
#include
#include
#include
+#include
+#include
#define _PYXMLSEC_FREE_NONE 0
#define _PYXMLSEC_FREE_XMLSEC 1
@@ -26,7 +29,7 @@ static int free_mode = _PYXMLSEC_FREE_NONE;
#ifndef XMLSEC_NO_CRYPTO_DYNAMIC_LOADING
static const xmlChar* PyXmlSec_GetCryptoLibName() {
-#if XMLSEC_VERSION_HEX > 308
+#if XMLSEC_VERSION_HEX > 0x10214
// xmlSecGetDefaultCrypto was introduced in version 1.2.21
const xmlChar* cryptoLib = xmlSecGetDefaultCrypto();
#else
@@ -86,6 +89,11 @@ static int PyXmlSec_Init(void) {
PyXmlSec_Free(_PYXMLSEC_FREE_ALL);
return -1;
}
+ // xmlsec will install default callback in xmlSecCryptoInit,
+ // overwriting any custom callbacks.
+ // We thus reinstall our callback now.
+ PyXmlSec_InstallErrorCallback();
+
free_mode = _PYXMLSEC_FREE_ALL;
return 0;
}
@@ -112,6 +120,37 @@ static PyObject* PyXmlSec_PyShutdown(PyObject* self) {
Py_RETURN_NONE;
}
+static char PyXmlSec_GetLibXmlSecVersion__doc__[] = \
+ "get_libxmlsec_version() -> tuple\n"
+ "Returns Version tuple of wrapped libxmlsec library.";
+static PyObject* PyXmlSec_GetLibXmlSecVersion() {
+ return Py_BuildValue("(iii)", XMLSEC_VERSION_MAJOR, XMLSEC_VERSION_MINOR, XMLSEC_VERSION_SUBMINOR);
+}
+
+static char PyXmlSec_GetLibXmlVersion__doc__[] = \
+ "get_libxml_version() -> tuple[int, int, int]\n"
+ "Returns version tuple of libxml2 library xmlsec is using.";
+static PyObject* PyXmlSec_GetLibXmlVersion() {
+ return Py_BuildValue(
+ "(iii)",
+ PyXmlSec_GetLibXmlVersionMajor(),
+ PyXmlSec_GetLibXmlVersionMinor(),
+ PyXmlSec_GetLibXmlVersionPatch()
+ );
+}
+
+static char PyXmlSec_GetLibXmlCompiledVersion__doc__[] = \
+ "get_libxml_compiled_version() -> tuple[int, int, int]\n"
+ "Returns version tuple of libxml2 library xmlsec was compiled with.";
+static PyObject* PyXmlSec_GetLibXmlCompiledVersion() {
+ return Py_BuildValue(
+ "(iii)",
+ PyXmlSec_GetLibXmlCompiledVersionMajor(),
+ PyXmlSec_GetLibXmlCompiledVersionMinor(),
+ PyXmlSec_GetLibXmlCompiledVersionPatch()
+ );
+}
+
static char PyXmlSec_PyEnableDebugOutput__doc__[] = \
"enable_debug_trace(enabled) -> None\n"
"Enables or disables calling LibXML2 callback from the default errors callback.\n\n"
@@ -127,6 +166,245 @@ static PyObject* PyXmlSec_PyEnableDebugOutput(PyObject *self, PyObject* args, Py
Py_RETURN_NONE;
}
+// NB: This whole thing assumes that the `xmlsec` callbacks are not re-entrant
+// (i.e. that xmlsec won't come across a link in the reference it's processing
+// and try to open that with these callbacks too).
+typedef struct CbList {
+ PyObject* match_cb;
+ PyObject* open_cb;
+ PyObject* read_cb;
+ PyObject* close_cb;
+ struct CbList* next;
+} CbList;
+
+static CbList* registered_callbacks = NULL;
+
+static void RCBListCons(CbList* cb_list_item) {
+ cb_list_item->next = registered_callbacks;
+ registered_callbacks = cb_list_item;
+}
+
+static void RCBListClear() {
+ CbList* cb_list_item = registered_callbacks;
+ while (cb_list_item) {
+ Py_DECREF(cb_list_item->match_cb);
+ Py_DECREF(cb_list_item->open_cb);
+ Py_DECREF(cb_list_item->read_cb);
+ Py_DECREF(cb_list_item->close_cb);
+ CbList* next = cb_list_item->next;
+ free(cb_list_item);
+ cb_list_item = next;
+ }
+ registered_callbacks = NULL;
+}
+
+// The currently executing set of Python callbacks:
+static CbList* cur_cb_list_item;
+
+static int PyXmlSec_MatchCB(const char* filename) {
+ cur_cb_list_item = registered_callbacks;
+ PyGILState_STATE state = PyGILState_Ensure();
+ PyObject* args = Py_BuildValue("(y)", filename);
+ while (cur_cb_list_item) {
+ PyObject* result = PyObject_CallObject(cur_cb_list_item->match_cb, args);
+ if (result && PyObject_IsTrue(result)) {
+ Py_DECREF(result);
+ Py_DECREF(args);
+ PyGILState_Release(state);
+ return 1;
+ }
+ Py_XDECREF(result);
+ cur_cb_list_item = cur_cb_list_item->next;
+ }
+ Py_DECREF(args);
+ PyGILState_Release(state);
+ return 0;
+}
+
+static void* PyXmlSec_OpenCB(const char* filename) {
+ PyGILState_STATE state = PyGILState_Ensure();
+
+ // NB: Assumes the match callback left the current callback list item in the
+ // right place:
+ PyObject* args = Py_BuildValue("(y)", filename);
+ PyObject* result = PyObject_CallObject(cur_cb_list_item->open_cb, args);
+ Py_DECREF(args);
+
+ PyGILState_Release(state);
+ return result;
+}
+
+static int PyXmlSec_ReadCB(void* context, char* buffer, int len) {
+ PyGILState_STATE state = PyGILState_Ensure();
+
+ // NB: Assumes the match callback left the current callback list item in the
+ // right place:
+ PyObject* py_buffer = PyMemoryView_FromMemory(buffer, (Py_ssize_t) len, PyBUF_WRITE);
+ PyObject* args = Py_BuildValue("(OO)", context, py_buffer);
+ PyObject* py_bytes_read = PyObject_CallObject(cur_cb_list_item->read_cb, args);
+ Py_DECREF(args);
+ Py_DECREF(py_buffer);
+ int result;
+ if (py_bytes_read && PyLong_Check(py_bytes_read)) {
+ result = (int)PyLong_AsLong(py_bytes_read);
+ } else {
+ result = EOF;
+ }
+ Py_XDECREF(py_bytes_read);
+
+ PyGILState_Release(state);
+ return result;
+}
+
+static int PyXmlSec_CloseCB(void* context) {
+ PyGILState_STATE state = PyGILState_Ensure();
+
+ PyObject* args = Py_BuildValue("(O)", context);
+ PyObject* result = PyObject_CallObject(cur_cb_list_item->close_cb, args);
+ Py_DECREF(args);
+ Py_DECREF(context);
+ Py_DECREF(result);
+
+ PyGILState_Release(state);
+ return 0;
+}
+
+static char PyXmlSec_PyIOCleanupCallbacks__doc__[] = \
+ "Unregister globally all sets of IO callbacks from xmlsec.";
+static PyObject* PyXmlSec_PyIOCleanupCallbacks(PyObject *self) {
+ xmlSecIOCleanupCallbacks();
+ // We always have callbacks registered to delegate to any Python callbacks
+ // we have registered within these bindings:
+ if (xmlSecIORegisterCallbacks(
+ PyXmlSec_MatchCB, PyXmlSec_OpenCB, PyXmlSec_ReadCB,
+ PyXmlSec_CloseCB) < 0) {
+ return NULL;
+ }
+ RCBListClear();
+ Py_RETURN_NONE;
+}
+
+static char PyXmlSec_PyIORegisterDefaultCallbacks__doc__[] = \
+ "Register globally xmlsec's own default set of IO callbacks.";
+static PyObject* PyXmlSec_PyIORegisterDefaultCallbacks(PyObject *self) {
+ // NB: The default callbacks (specifically libxml2's `xmlFileMatch`) always
+ // match, and callbacks are called in the reverse order to that which they
+ // were added. So, there's no point in holding onto any previously registered
+ // callbacks, because they will never be run:
+ xmlSecIOCleanupCallbacks();
+ RCBListClear();
+ if (xmlSecIORegisterDefaultCallbacks() < 0) {
+ return NULL;
+ }
+ // We need to make sure we can continue trying to match any newly added
+ // Python callbacks:
+ if (xmlSecIORegisterCallbacks(
+ PyXmlSec_MatchCB, PyXmlSec_OpenCB, PyXmlSec_ReadCB,
+ PyXmlSec_CloseCB) < 0) {
+ return NULL;
+ };
+ Py_RETURN_NONE;
+}
+
+static char PyXmlSec_PyIORegisterCallbacks__doc__[] = \
+ "register_callbacks(input_match_callback, input_open_callback, input_read_callback, input_close_callback) -> None\n"
+ "Register globally a custom set of IO callbacks with xmlsec.\n\n"
+ ":param input_match_callback: A callable that takes a filename `bytestring` and "
+ "returns a boolean as to whether the other callbacks in this set can handle that name.\n"
+ ":type input_match_callback: ~collections.abc.Callable[[bytes], bool]\n"
+ ":param input_open_callback: A callable that takes a filename and returns some "
+ "context object (e.g. a file object) that the remaining callables in this set will be passed "
+ "during handling.\n"
+ ":type input_open_callback: ~collections.abc.Callable[[bytes], Any]\n"
+ // FIXME: How do we handle failures in ^^ (e.g. can't find the file)?
+ ":param input_read_callback: A callable that that takes the context object from the "
+ "open callback and a buffer, and should fill the buffer with data (e.g. BytesIO.readinto()). "
+ "xmlsec will call this function several times until there is no more data returned.\n"
+ ":type input_read_callback: ~collections.abc.Callable[[Any, memoryview], int]\n"
+ ":param input_close_callback: A callable that takes the context object from the "
+ "open callback and can do any resource cleanup necessary.\n"
+ ":type input_close_callback: ~collections.abc.Callable[[Any], None]\n"
+ ;
+static PyObject* PyXmlSec_PyIORegisterCallbacks(PyObject *self, PyObject *args, PyObject *kwargs) {
+ static char *kwlist[] = {
+ "input_match_callback",
+ "input_open_callback",
+ "input_read_callback",
+ "input_close_callback",
+ NULL
+ };
+ CbList* cb_list_item = malloc(sizeof(CbList));
+ if (cb_list_item == NULL) {
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "OOOO:register_callbacks", kwlist,
+ &cb_list_item->match_cb, &cb_list_item->open_cb, &cb_list_item->read_cb,
+ &cb_list_item->close_cb)) {
+ free(cb_list_item);
+ return NULL;
+ }
+ if (!PyCallable_Check(cb_list_item->match_cb)) {
+ PyErr_SetString(PyExc_TypeError, "input_match_callback must be a callable");
+ free(cb_list_item);
+ return NULL;
+ }
+ if (!PyCallable_Check(cb_list_item->open_cb)) {
+ PyErr_SetString(PyExc_TypeError, "input_open_callback must be a callable");
+ free(cb_list_item);
+ return NULL;
+ }
+ if (!PyCallable_Check(cb_list_item->read_cb)) {
+ PyErr_SetString(PyExc_TypeError, "input_read_callback must be a callable");
+ free(cb_list_item);
+ return NULL;
+ }
+ if (!PyCallable_Check(cb_list_item->close_cb)) {
+ PyErr_SetString(PyExc_TypeError, "input_close_callback must be a callable");
+ free(cb_list_item);
+ return NULL;
+ }
+ Py_INCREF(cb_list_item->match_cb);
+ Py_INCREF(cb_list_item->open_cb);
+ Py_INCREF(cb_list_item->read_cb);
+ Py_INCREF(cb_list_item->close_cb);
+ cb_list_item->next = NULL;
+ RCBListCons(cb_list_item);
+ // NB: We don't need to register the callbacks with `xmlsec` here, because
+ // we've already registered our helper functions that will trawl through our
+ // list of callbacks.
+ Py_RETURN_NONE;
+}
+
+static char PyXmlSec_PyBase64DefaultLineSize__doc__[] = \
+ "base64_default_line_size(size = None)\n"
+ "Configures the default maximum columns size for base64 encoding.\n\n"
+ "If ``size`` is not given, this function returns the current default size, acting as a getter. "
+ "If ``size`` is given, a new value is applied and this function returns nothing, acting as a setter.\n"
+ ":param size: new default size value (optional)\n"
+ ":type size: :class:`int` or :data:`None`";
+static PyObject* PyXmlSec_PyBase64DefaultLineSize(PyObject *self, PyObject *args, PyObject *kwargs) {
+ static char *kwlist[] = { "size", NULL };
+ PyObject *pySize = NULL;
+ int size;
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:base64_default_line_size", kwlist, &pySize)) {
+ return NULL;
+ }
+ if (pySize == NULL) {
+ return PyLong_FromLong(xmlSecBase64GetDefaultLineSize());
+ }
+ size = (int)PyLong_AsLong(pySize);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ if (size < 0) {
+ PyErr_SetString(PyExc_ValueError, "size must be positive");
+ return NULL;
+ }
+ xmlSecBase64SetDefaultLineSize(size);
+ Py_RETURN_NONE;
+}
+
static PyMethodDef PyXmlSec_MainMethods[] = {
{
"init",
@@ -140,12 +418,54 @@ static PyMethodDef PyXmlSec_MainMethods[] = {
METH_NOARGS,
PyXmlSec_PyShutdown__doc__
},
+ {
+ "get_libxmlsec_version",
+ (PyCFunction)PyXmlSec_GetLibXmlSecVersion,
+ METH_NOARGS,
+ PyXmlSec_GetLibXmlSecVersion__doc__
+ },
+ {
+ "get_libxml_version",
+ (PyCFunction)PyXmlSec_GetLibXmlVersion,
+ METH_NOARGS,
+ PyXmlSec_GetLibXmlVersion__doc__
+ },
+ {
+ "get_libxml_compiled_version",
+ (PyCFunction)PyXmlSec_GetLibXmlCompiledVersion,
+ METH_NOARGS,
+ PyXmlSec_GetLibXmlCompiledVersion__doc__
+ },
{
"enable_debug_trace",
(PyCFunction)PyXmlSec_PyEnableDebugOutput,
METH_VARARGS|METH_KEYWORDS,
PyXmlSec_PyEnableDebugOutput__doc__
},
+ {
+ "cleanup_callbacks",
+ (PyCFunction)PyXmlSec_PyIOCleanupCallbacks,
+ METH_NOARGS,
+ PyXmlSec_PyIOCleanupCallbacks__doc__
+ },
+ {
+ "register_default_callbacks",
+ (PyCFunction)PyXmlSec_PyIORegisterDefaultCallbacks,
+ METH_NOARGS,
+ PyXmlSec_PyIORegisterDefaultCallbacks__doc__
+ },
+ {
+ "register_callbacks",
+ (PyCFunction)PyXmlSec_PyIORegisterCallbacks,
+ METH_VARARGS|METH_KEYWORDS,
+ PyXmlSec_PyIORegisterCallbacks__doc__
+ },
+ {
+ "base64_default_line_size",
+ (PyCFunction)PyXmlSec_PyBase64DefaultLineSize,
+ METH_VARARGS|METH_KEYWORDS,
+ PyXmlSec_PyBase64DefaultLineSize__doc__
+ },
{NULL, NULL} /* sentinel */
};
@@ -167,8 +487,6 @@ int PyXmlSec_EncModule_Init(PyObject* package);
// templates management
int PyXmlSec_TemplateModule_Init(PyObject* package);
-#ifdef PY3K
-
static int PyXmlSec_PyClear(PyObject *self) {
PyXmlSec_Free(free_mode);
return 0;
@@ -189,54 +507,12 @@ static PyModuleDef PyXmlSecModule = {
#define PYENTRY_FUNC_NAME JOIN(PyInit_, MODULE_NAME)
#define PY_MOD_RETURN(m) return m
-#else // PY3K
-#define PYENTRY_FUNC_NAME JOIN(init, MODULE_NAME)
-#define PY_MOD_RETURN(m) return
-
-static void PyXmlSec_PyModuleGuard__del__(PyObject* self)
-{
- PyXmlSec_Free(free_mode);
- Py_TYPE(self)->tp_free(self);
-}
-
-// we need guard to free resources on module unload
-typedef struct {
- PyObject_HEAD
-} PyXmlSec_PyModuleGuard;
-
-static PyTypeObject PyXmlSec_PyModuleGuardType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- STRINGIFY(MODULE_NAME) "__Guard", /* tp_name */
- sizeof(PyXmlSec_PyModuleGuard), /* tp_basicsize */
- 0, /* tp_itemsize */
- PyXmlSec_PyModuleGuard__del__, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_reserved */
- 0, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- 0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT, /* tp_flags */
-};
-#endif // PY3K
PyMODINIT_FUNC
PYENTRY_FUNC_NAME(void)
{
PyObject *module = NULL;
-#ifdef PY3K
module = PyModule_Create(&PyXmlSecModule);
-#else
- module = Py_InitModule3(STRINGIFY(MODULE_NAME), PyXmlSec_MainMethods, MODULE_DOC);
-#endif
if (!module) {
PY_MOD_RETURN(NULL); /* this really should never happen */
}
@@ -258,13 +534,6 @@ PYENTRY_FUNC_NAME(void)
if (PyXmlSec_EncModule_Init(module) < 0) goto ON_FAIL;
if (PyXmlSec_TemplateModule_Init(module) < 0) goto ON_FAIL;
-#ifndef PY3K
- if (PyType_Ready(&PyXmlSec_PyModuleGuardType) < 0) goto ON_FAIL;
- PYXMLSEC_DEBUGF("%p", &PyXmlSec_PyModuleGuardType);
- // added guard to free resources on module unload, this should be called after last
- if (PyModule_AddObject(module, "__guard", _PyObject_New(&PyXmlSec_PyModuleGuardType)) < 0) goto ON_FAIL;
-#endif
-
PY_MOD_RETURN(module);
ON_FAIL:
PY_MOD_RETURN(NULL);
diff --git a/src/platform.h b/src/platform.h
index 795062f2..35163e88 100644
--- a/src/platform.h
+++ b/src/platform.h
@@ -19,11 +19,11 @@
#include
#endif /* MS_WIN32 */
-#define XMLSEC_VERSION_HEX ((XMLSEC_VERSION_MAJOR << 8) | (XMLSEC_VERSION_MINOR << 4) | (XMLSEC_VERSION_SUBMINOR))
+#define XMLSEC_VERSION_HEX ((XMLSEC_VERSION_MAJOR << 16) | (XMLSEC_VERSION_MINOR << 8) | (XMLSEC_VERSION_SUBMINOR))
// XKMS support was removed in version 1.2.21
// https://mail.gnome.org/archives/commits-list/2015-February/msg10555.html
-#if XMLSEC_VERSION_HEX > 0x134
+#if XMLSEC_VERSION_HEX > 0x10214
#define XMLSEC_NO_XKMS 1
#endif
@@ -35,50 +35,6 @@ typedef int Py_ssize_t;
#define PY_SSIZE_T_MIN INT_MIN
#endif
-#if PY_MAJOR_VERSION >= 3
-#define PY3K 1
-#define PyString_Check PyUnicode_Check
-#define PyString_FromStringAndSize PyUnicode_FromStringAndSize
-
-#define PyString_FromString PyUnicode_FromString
-
-#define PyString_AsString PyUnicode_AsUTF8
-#define PyString_AsUtf8AndSize PyUnicode_AsUTF8AndSize
-
-#define PyCreateDummyObject PyModule_New
-
-#define PyString_FSConverter PyUnicode_FSConverter
-#else // PY3K
-
-#define PyBytes_Check PyString_Check
-#define PyBytes_FromStringAndSize PyString_FromStringAndSize
-
-#define PyBytes_AsString PyString_AsString
-#define PyBytes_AsStringAndSize PyString_AsStringAndSize
-
-static inline char* PyString_AsUtf8AndSize(PyObject *obj, Py_ssize_t* length) {
- char* buffer = NULL;
- return (PyString_AsStringAndSize(obj, &buffer, length) < 0) ? (char*)(0) : buffer;
-}
-
-static inline PyObject* PyCreateDummyObject(const char* name) {
- PyObject* tmp = Py_InitModule(name, NULL);
- Py_INCREF(tmp);
- return tmp;
-}
-
-static inline int PyString_FSConverter(PyObject* o, PyObject** p) {
- if (o == NULL) {
- return 0;
- }
-
- Py_INCREF(o);
- *p = o;
- return 1;
-}
-
-#endif // PYTHON3
-
static inline char* PyBytes_AsStringAndSize2(PyObject *obj, Py_ssize_t* length) {
char* buffer = NULL;
return ((PyBytes_AsStringAndSize(obj, &buffer, length) < 0) ? (char*)(0) : buffer);
diff --git a/src/template.c b/src/template.c
index 7d043606..ae0eca34 100644
--- a/src/template.c
+++ b/src/template.c
@@ -766,10 +766,10 @@ static PyObject* PyXmlSec_TemplateTransformAddC14NInclNamespaces(PyObject* self,
goto ON_FAIL;
}
if (PyList_Check(prefixes) || PyTuple_Check(prefixes)) {
- sep = PyString_FromString(" ");
+ sep = PyUnicode_FromString(" ");
prefixes = PyObject_CallMethod(sep, "join", "O", prefixes);
Py_DECREF(sep);
- } else if (PyString_Check(prefixes)) {
+ } else if (PyUnicode_Check(prefixes)) {
Py_INCREF(prefixes);
} else {
PyErr_SetString(PyExc_TypeError, "expected instance of str or list of str");
@@ -781,7 +781,7 @@ static PyObject* PyXmlSec_TemplateTransformAddC14NInclNamespaces(PyObject* self,
}
- c_prefixes = PyString_AsString(prefixes);
+ c_prefixes = PyUnicode_AsUTF8(prefixes);
Py_BEGIN_ALLOW_THREADS;
res = xmlSecTmplTransformAddC14NInclNamespaces(node->_c_node, XSTR(c_prefixes));
Py_END_ALLOW_THREADS;
@@ -918,7 +918,6 @@ static PyMethodDef PyXmlSec_TemplateMethods[] = {
{NULL, NULL} /* sentinel */
};
-#ifdef PY3K
static PyModuleDef PyXmlSec_TemplateModule =
{
PyModuleDef_HEAD_INIT,
@@ -931,15 +930,9 @@ static PyModuleDef PyXmlSec_TemplateModule =
NULL, /* m_clear */
NULL, /* m_free */
};
-#endif // PY3K
int PyXmlSec_TemplateModule_Init(PyObject* package) {
-#ifdef PY3K
PyObject* template = PyModule_Create(&PyXmlSec_TemplateModule);
-#else
- PyObject* template = Py_InitModule3(STRINGIFY(MODULE_NAME) ".template", PyXmlSec_TemplateMethods, PYXMLSEC_TEMPLATES_DOC);
- Py_XINCREF(template);
-#endif
if (!template) goto ON_FAIL;
PYXMLSEC_DEBUGF("%p", template);
diff --git a/src/tree.c b/src/tree.c
index 76037d3b..37cae785 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -182,7 +182,7 @@ static PyObject* PyXmlSec_TreeAddIds(PyObject* self, PyObject *args, PyObject *k
tmp = PyObject_GetItem(ids, key);
Py_DECREF(key);
if (tmp == NULL) goto ON_FAIL;
- list[i] = XSTR(PyString_AsString(tmp));
+ list[i] = XSTR(PyUnicode_AsUTF8(tmp));
Py_DECREF(tmp);
if (list[i] == NULL) goto ON_FAIL;
}
@@ -230,7 +230,6 @@ static PyMethodDef PyXmlSec_TreeMethods[] = {
{NULL, NULL} /* sentinel */
};
-#ifdef PY3K
static PyModuleDef PyXmlSec_TreeModule =
{
PyModuleDef_HEAD_INIT,
@@ -243,16 +242,10 @@ static PyModuleDef PyXmlSec_TreeModule =
NULL, /* m_clear */
NULL, /* m_free */
};
-#endif // PY3K
int PyXmlSec_TreeModule_Init(PyObject* package) {
-#ifdef PY3K
PyObject* tree = PyModule_Create(&PyXmlSec_TreeModule);
-#else
- PyObject* tree = Py_InitModule3(STRINGIFY(MODULE_NAME) ".tree", PyXmlSec_TreeMethods, PYXMLSEC_TREE_DOC);
- Py_XINCREF(tree);
-#endif
if (!tree) goto ON_FAIL;
diff --git a/src/utils.c b/src/utils.c
index ea6867f3..cdcb182b 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -25,14 +25,14 @@ PyObject* PyXmlSec_GetFilePathOrContent(PyObject* file, int* is_content) {
return data;
}
*is_content = 0;
- if (!PyString_FSConverter(file, &tmp)) {
+ if (!PyUnicode_FSConverter(file, &tmp)) {
return NULL;
}
return tmp;
}
int PyXmlSec_SetStringAttr(PyObject* obj, const char* name, const char* value) {
- PyObject* tmp = PyString_FromString(value);
+ PyObject* tmp = PyUnicode_FromString(value);
int r;
if (tmp == NULL) {
diff --git a/src/xmlsec/__init__.pyi b/src/xmlsec/__init__.pyi
index 540553c4..9cfc8cc6 100644
--- a/src/xmlsec/__init__.pyi
+++ b/src/xmlsec/__init__.pyi
@@ -1,33 +1,38 @@
-import sys
-from typing import AnyStr, IO, Iterable, Optional, Type, TypeVar, Union
+from collections.abc import Callable, Iterable
+from typing import IO, Any, AnyStr, TypeVar, overload
+from _typeshed import GenericPath, Self, StrOrBytesPath
from lxml.etree import _Element
-from xmlsec import constants, template, tree
-from xmlsec.constants import __KeyData as KeyData, __Transform as Transform
-
-if sys.version_info >= (3, 6):
- from os import PathLike
- from pathlib import PurePath
-
- _Path = Union[str, bytes, PurePath, PathLike[str], PathLike[bytes]]
-elif sys.version_info >= (3, 4):
- from pathlib import PurePath
-
- _Path = Union[str, bytes, PurePath]
-else:
- _Path = Union[str, bytes]
+from xmlsec import constants as constants
+from xmlsec import template as template
+from xmlsec import tree as tree
+from xmlsec.constants import __KeyData as KeyData
+from xmlsec.constants import __Transform as Transform
_E = TypeVar('_E', bound=_Element)
-_K = TypeVar('_K', bound=Key)
def enable_debug_trace(enabled: bool = ...) -> None: ...
+def get_libxml_version() -> tuple[int, int, int]: ...
+def get_libxml_compiled_version() -> tuple[int, int, int]: ...
def init() -> None: ...
def shutdown() -> None: ...
+def cleanup_callbacks() -> None: ...
+def register_default_callbacks() -> None: ...
+def register_callbacks(
+ input_match_callback: Callable[[bytes], bool],
+ input_open_callback: Callable[[bytes], Any],
+ input_read_callback: Callable[[Any, memoryview], int],
+ input_close_callback: Callable[[Any], None],
+) -> None: ...
+@overload
+def base64_default_line_size() -> int: ...
+@overload
+def base64_default_line_size(size: int) -> None: ...
class EncryptionContext:
- key: Optional[Key]
- def __init__(self, manager: Optional[KeysManager] = None) -> None: ...
+ key: Key | None
+ def __init__(self, manager: KeysManager | None = ...) -> None: ...
def decrypt(self, node: _Element) -> _Element: ...
def encrypt_binary(self, template: _E, data: bytes) -> _E: ...
def encrypt_uri(self, template: _E, uri: str) -> _E: ...
@@ -40,30 +45,32 @@ class InternalError(Error): ...
class Key:
name: str
@classmethod
- def from_binary_data(cls: Type[_K], klass: KeyData, data: AnyStr) -> _K: ...
+ def from_binary_data(cls: type[Self], klass: KeyData, data: AnyStr) -> Self: ...
+ @classmethod
+ def from_binary_file(cls: type[Self], klass: KeyData, filename: StrOrBytesPath) -> Self: ...
@classmethod
- def from_binary_file(cls: Type[_K], klass: KeyData, filename: _Path) -> _K: ...
+ def from_file(cls: type[Self], file: GenericPath[AnyStr] | IO[AnyStr], format: int, password: str | None = ...) -> Self: ...
@classmethod
- def from_file(cls: Type[_K], file: Union[_Path, IO[AnyStr]], format: int, password: Optional[str] = ...) -> _K: ...
+ def from_engine(cls: type[Self], engine_and_key_id: AnyStr) -> Self: ...
@classmethod
- def from_memory(cls: Type[_K], data: AnyStr, format: int, password: Optional[str] = ...) -> _K: ...
+ def from_memory(cls: type[Self], data: AnyStr, format: int, password: str | None = ...) -> Self: ...
@classmethod
- def generate(cls: Type[_K], klass: KeyData, size: int, type: int) -> _K: ...
- def load_cert_from_file(self, file: Union[_Path, IO[AnyStr]], format: int) -> None: ...
+ def generate(cls: type[Self], klass: KeyData, size: int, type: int) -> Self: ...
+ def load_cert_from_file(self, file: GenericPath[AnyStr] | IO[AnyStr], format: int) -> None: ...
def load_cert_from_memory(self, data: AnyStr, format: int) -> None: ...
- def __copy__(self: _K) -> _K: ...
- def __deepcopy__(self: _K) -> _K: ...
+ def __copy__(self: Self) -> Self: ...
+ def __deepcopy__(self: Self) -> Self: ...
class KeysManager:
def add_key(self, key: Key) -> None: ...
- def load_cert(self, filename: _Path, format: int, type: int) -> None: ...
+ def load_cert(self, filename: StrOrBytesPath, format: int, type: int) -> None: ...
def load_cert_from_memory(self, data: AnyStr, format: int, type: int) -> None: ...
class SignatureContext:
- key: Optional[Key]
+ key: Key | None
def enable_reference_transform(self, transform: Transform) -> None: ...
def enable_signature_transform(self, transform: Transform) -> None: ...
- def register_id(self, node: _Element, id_attr: str = "ID", id_ns: Optional[str] = None) -> None: ...
+ def register_id(self, node: _Element, id_attr: str = ..., id_ns: str | None = ...) -> None: ...
def set_enabled_key_data(self, keydata_list: Iterable[KeyData]) -> None: ...
def sign(self, node: _Element) -> None: ...
def sign_binary(self, bytes: bytes, transform: Transform) -> bytes: ...
diff --git a/src/xmlsec/constants.pyi b/src/xmlsec/constants.pyi
index 71b717a7..a9254ddd 100644
--- a/src/xmlsec/constants.pyi
+++ b/src/xmlsec/constants.pyi
@@ -1,148 +1,148 @@
import sys
-from typing import NamedTuple
-
-if sys.version_info >= (3, 8):
- from typing import Final
-else:
- from typing_extensions import Final
+from typing import Final, NamedTuple
class __KeyData(NamedTuple): # __KeyData type
href: str
name: str
+class __KeyDataNoHref(NamedTuple): # __KeyData type
+ href: None
+ name: str
+
class __Transform(NamedTuple): # __Transform type
href: str
name: str
usage: int
-DSigNs: Final = 'http://www.w3.org/2000/09/xmldsig#'
-EncNs: Final = 'http://www.w3.org/2001/04/xmlenc#'
-KeyDataAes: Final = __KeyData('aes', 'http://www.aleksey.com/xmlsec/2002#AESKeyValue')
-KeyDataDes: Final = __KeyData('des', 'http://www.aleksey.com/xmlsec/2002#DESKeyValue')
-KeyDataDsa: Final = __KeyData('dsa', 'http://www.w3.org/2000/09/xmldsig#DSAKeyValue')
-KeyDataEcdsa: Final = __KeyData('ecdsa', 'http://scap.nist.gov/specifications/tmsad/#resource-1.0')
-KeyDataEncryptedKey: Final = __KeyData('enc-key', 'http://www.w3.org/2001/04/xmlenc#EncryptedKey')
-KeyDataFormatBinary: Final = 1
-KeyDataFormatCertDer: Final = 8
-KeyDataFormatCertPem: Final = 7
-KeyDataFormatDer: Final = 3
-KeyDataFormatPem: Final = 2
-KeyDataFormatPkcs12: Final = 6
-KeyDataFormatPkcs8Der: Final = 5
-KeyDataFormatPkcs8Pem: Final = 4
-KeyDataFormatUnknown: Final = 0
-KeyDataHmac: Final = __KeyData('hmac', 'http://www.aleksey.com/xmlsec/2002#HMACKeyValue')
-KeyDataName: Final = __KeyData('key-name', None)
-KeyDataRawX509Cert: Final = __KeyData('raw-x509-cert', 'http://www.w3.org/2000/09/xmldsig#rawX509Certificate')
-KeyDataRetrievalMethod: Final = __KeyData('retrieval-method', None)
-KeyDataRsa: Final = __KeyData('rsa', 'http://www.w3.org/2000/09/xmldsig#RSAKeyValue')
-KeyDataTypeAny: Final = 65535
-KeyDataTypeNone: Final = 0
-KeyDataTypePermanent: Final = 16
-KeyDataTypePrivate: Final = 2
-KeyDataTypePublic: Final = 1
-KeyDataTypeSession: Final = 8
-KeyDataTypeSymmetric: Final = 4
-KeyDataTypeTrusted: Final = 256
-KeyDataTypeUnknown: Final = 0
-KeyDataValue: Final = __KeyData('key-value', None)
-KeyDataX509: Final = __KeyData('x509', 'http://www.w3.org/2000/09/xmldsig#X509Data')
-NodeCanonicalizationMethod: Final = 'CanonicalizationMethod'
-NodeCipherData: Final = 'CipherData'
-NodeCipherReference: Final = 'CipherReference'
-NodeCipherValue: Final = 'CipherValue'
-NodeDataReference: Final = 'DataReference'
-NodeDigestMethod: Final = 'DigestMethod'
-NodeDigestValue: Final = 'DigestValue'
-NodeEncryptedData: Final = 'EncryptedData'
-NodeEncryptedKey: Final = 'EncryptedKey'
-NodeEncryptionMethod: Final = 'EncryptionMethod'
-NodeEncryptionProperties: Final = 'EncryptionProperties'
-NodeEncryptionProperty: Final = 'EncryptionProperty'
-NodeKeyInfo: Final = 'KeyInfo'
-NodeKeyName: Final = 'KeyName'
-NodeKeyReference: Final = 'KeyReference'
-NodeKeyValue: Final = 'KeyValue'
-NodeManifest: Final = 'Manifest'
-NodeObject: Final = 'Object'
-NodeReference: Final = 'Reference'
-NodeReferenceList: Final = 'ReferenceList'
-NodeSignature: Final = 'Signature'
-NodeSignatureMethod: Final = 'SignatureMethod'
-NodeSignatureProperties: Final = 'SignatureProperties'
-NodeSignatureValue: Final = 'SignatureValue'
-NodeSignedInfo: Final = 'SignedInfo'
-NodeX509Data: Final = 'X509Data'
-Ns: Final = 'http://www.aleksey.com/xmlsec/2002'
-NsExcC14N: Final = 'http://www.w3.org/2001/10/xml-exc-c14n#'
-NsExcC14NWithComments: Final = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'
-Soap11Ns: Final = 'http://schemas.xmlsoap.org/soap/envelope/'
-Soap12Ns: Final = 'http://www.w3.org/2002/06/soap-envelope'
-TransformAes128Cbc: Final = __Transform('aes128-cbc', 'http://www.w3.org/2001/04/xmlenc#aes128-cbc', 16)
-TransformAes192Cbc: Final = __Transform('aes192-cbc', 'http://www.w3.org/2001/04/xmlenc#aes192-cbc', 16)
-TransformAes256Cbc: Final = __Transform('aes256-cbc', 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', 16)
-TransformDes3Cbc: Final = __Transform('tripledes-cbc', 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc', 16)
-TransformDsaSha1: Final = __Transform('dsa-sha1', 'http://www.w3.org/2000/09/xmldsig#dsa-sha1', 8)
-TransformEcdsaSha1: Final = __Transform('ecdsa-sha1', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1', 8)
-TransformEcdsaSha224: Final = __Transform('ecdsa-sha224', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224', 8)
-TransformEcdsaSha256: Final = __Transform('ecdsa-sha256', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256', 8)
-TransformEcdsaSha384: Final = __Transform('ecdsa-sha384', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384', 8)
-TransformEcdsaSha512: Final = __Transform('ecdsa-sha512', 'http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512', 8)
-TransformEnveloped: Final = __Transform('enveloped-signature', 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 1)
-TransformExclC14N: Final = __Transform('exc-c14n', 'http://www.w3.org/2001/10/xml-exc-c14n#', 3)
-TransformExclC14NWithComments: Final = __Transform(
- 'exc-c14n-with-comments', 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments', 3
-)
-TransformHmacMd5: Final = __Transform('hmac-md5', 'http://www.w3.org/2001/04/xmldsig-more#hmac-md5', 8)
-TransformHmacRipemd160: Final = __Transform('hmac-ripemd160', 'http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160', 8)
-TransformHmacSha1: Final = __Transform('hmac-sha1', 'http://www.w3.org/2000/09/xmldsig#hmac-sha1', 8)
-TransformHmacSha224: Final = __Transform('hmac-sha224', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224', 8)
-TransformHmacSha256: Final = __Transform('hmac-sha256', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256', 8)
-TransformHmacSha384: Final = __Transform('hmac-sha384', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha384', 8)
-TransformHmacSha512: Final = __Transform('hmac-sha512', 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha512', 8)
-TransformInclC14N: Final = __Transform('c14n', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315', 3)
-TransformInclC14N11: Final = __Transform('c14n11', 'http://www.w3.org/2006/12/xml-c14n11', 3)
-TransformInclC14N11WithComments: Final = __Transform(
- 'c14n11-with-comments', 'http://www.w3.org/2006/12/xml-c14n11#WithComments', 3
-)
-TransformInclC14NWithComments: Final = __Transform(
- 'c14n-with-comments', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments', 3
-)
-TransformKWAes128: Final = __Transform('kw-aes128', 'http://www.w3.org/2001/04/xmlenc#kw-aes128', 16)
-TransformKWAes192: Final = __Transform('kw-aes192', 'http://www.w3.org/2001/04/xmlenc#kw-aes192', 16)
-TransformKWAes256: Final = __Transform('kw-aes256', 'http://www.w3.org/2001/04/xmlenc#kw-aes256', 16)
-TransformKWDes3: Final = __Transform('kw-tripledes', 'http://www.w3.org/2001/04/xmlenc#kw-tripledes', 16)
-TransformMd5: Final = __Transform('md5', 'http://www.w3.org/2001/04/xmldsig-more#md5', 4)
-TransformRemoveXmlTagsC14N: Final = __Transform('remove-xml-tags-transform', None, 3)
-TransformRipemd160: Final = __Transform('ripemd160', 'http://www.w3.org/2001/04/xmlenc#ripemd160', 4)
-TransformRsaMd5: Final = __Transform('rsa-md5', 'http://www.w3.org/2001/04/xmldsig-more#rsa-md5', 8)
-TransformRsaOaep: Final = __Transform('rsa-oaep-mgf1p', 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', 16)
-TransformRsaPkcs1: Final = __Transform('rsa-1_5', 'http://www.w3.org/2001/04/xmlenc#rsa-1_5', 16)
-TransformRsaRipemd160: Final = __Transform('rsa-ripemd160', 'http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160', 8)
-TransformRsaSha1: Final = __Transform('rsa-sha1', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 8)
-TransformRsaSha224: Final = __Transform('rsa-sha224', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224', 8)
-TransformRsaSha256: Final = __Transform('rsa-sha256', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', 8)
-TransformRsaSha384: Final = __Transform('rsa-sha384', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384', 8)
-TransformRsaSha512: Final = __Transform('rsa-sha512', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512', 8)
-TransformSha1: Final = __Transform('sha1', 'http://www.w3.org/2000/09/xmldsig#sha1', 4)
-TransformSha224: Final = __Transform('sha224', 'http://www.w3.org/2001/04/xmldsig-more#sha224', 4)
-TransformSha256: Final = __Transform('sha256', 'http://www.w3.org/2001/04/xmlenc#sha256', 4)
-TransformSha384: Final = __Transform('sha384', 'http://www.w3.org/2001/04/xmldsig-more#sha384', 4)
-TransformSha512: Final = __Transform('sha512', 'http://www.w3.org/2001/04/xmlenc#sha512', 4)
-TransformUsageAny: Final = 65535
-TransformUsageC14NMethod: Final = 2
-TransformUsageDSigTransform: Final = 1
-TransformUsageDigestMethod: Final = 4
-TransformUsageEncryptionMethod: Final = 16
-TransformUsageSignatureMethod: Final = 8
-TransformUsageUnknown: Final = 0
-TransformVisa3DHack: Final = __Transform('Visa3DHackTransform', None, 1)
-TransformXPath: Final = __Transform('xpath', 'http://www.w3.org/TR/1999/REC-xpath-19991116', 1)
-TransformXPath2: Final = __Transform('xpath2', 'http://www.w3.org/2002/06/xmldsig-filter2', 1)
-TransformXPointer: Final = __Transform('xpointer', 'http://www.w3.org/2001/04/xmldsig-more/xptr', 1)
-TransformXslt: Final = __Transform('xslt', 'http://www.w3.org/TR/1999/REC-xslt-19991116', 1)
-TypeEncContent: Final = 'http://www.w3.org/2001/04/xmlenc#Content'
-TypeEncElement: Final = 'http://www.w3.org/2001/04/xmlenc#Element'
-XPath2Ns: Final = 'http://www.w3.org/2002/06/xmldsig-filter2'
-XPathNs: Final = 'http://www.w3.org/TR/1999/REC-xpath-19991116'
-XPointerNs: Final = 'http://www.w3.org/2001/04/xmldsig-more/xptr'
+class __TransformNoHref(NamedTuple): # __Transform type
+ href: None
+ name: str
+ usage: int
+
+DSigNs: Final[str]
+EncNs: Final[str]
+KeyDataAes: Final[__KeyData]
+KeyDataDes: Final[__KeyData]
+KeyDataDsa: Final[__KeyData]
+KeyDataEc: Final[__KeyData]
+KeyDataEcdsa: Final[__KeyData]
+KeyDataEncryptedKey: Final[__KeyData]
+KeyDataFormatBinary: Final[int]
+KeyDataFormatCertDer: Final[int]
+KeyDataFormatCertPem: Final[int]
+KeyDataFormatDer: Final[int]
+KeyDataFormatPem: Final[int]
+KeyDataFormatPkcs12: Final[int]
+KeyDataFormatPkcs8Der: Final[int]
+KeyDataFormatPkcs8Pem: Final[int]
+KeyDataFormatUnknown: Final[int]
+KeyDataHmac: Final[__KeyData]
+KeyDataName: Final[__KeyDataNoHref]
+KeyDataRawX509Cert: Final[__KeyData]
+KeyDataRetrievalMethod: Final[__KeyDataNoHref]
+KeyDataRsa: Final[__KeyData]
+KeyDataTypeAny: Final[int]
+KeyDataTypeNone: Final[int]
+KeyDataTypePermanent: Final[int]
+KeyDataTypePrivate: Final[int]
+KeyDataTypePublic: Final[int]
+KeyDataTypeSession: Final[int]
+KeyDataTypeSymmetric: Final[int]
+KeyDataTypeTrusted: Final[int]
+KeyDataTypeUnknown: Final[int]
+KeyDataValue: Final[__KeyDataNoHref]
+KeyDataX509: Final[__KeyData]
+NodeCanonicalizationMethod: Final[str]
+NodeCipherData: Final[str]
+NodeCipherReference: Final[str]
+NodeCipherValue: Final[str]
+NodeDataReference: Final[str]
+NodeDigestMethod: Final[str]
+NodeDigestValue: Final[str]
+NodeEncryptedData: Final[str]
+NodeEncryptedKey: Final[str]
+NodeEncryptionMethod: Final[str]
+NodeEncryptionProperties: Final[str]
+NodeEncryptionProperty: Final[str]
+NodeKeyInfo: Final[str]
+NodeKeyName: Final[str]
+NodeKeyReference: Final[str]
+NodeKeyValue: Final[str]
+NodeManifest: Final[str]
+NodeObject: Final[str]
+NodeReference: Final[str]
+NodeReferenceList: Final[str]
+NodeSignature: Final[str]
+NodeSignatureMethod: Final[str]
+NodeSignatureProperties: Final[str]
+NodeSignatureValue: Final[str]
+NodeSignedInfo: Final[str]
+NodeX509Data: Final[str]
+Ns: Final[str]
+NsExcC14N: Final[str]
+NsExcC14NWithComments: Final[str]
+TransformAes128Cbc: Final[__Transform]
+TransformAes128Gcm: Final[__Transform]
+TransformAes192Cbc: Final[__Transform]
+TransformAes192Gcm: Final[__Transform]
+TransformAes256Cbc: Final[__Transform]
+TransformAes256Gcm: Final[__Transform]
+TransformDes3Cbc: Final[__Transform]
+TransformDsaSha1: Final[__Transform]
+TransformEcdsaSha1: Final[__Transform]
+TransformEcdsaSha224: Final[__Transform]
+TransformEcdsaSha256: Final[__Transform]
+TransformEcdsaSha384: Final[__Transform]
+TransformEcdsaSha512: Final[__Transform]
+TransformEnveloped: Final[__Transform]
+TransformExclC14N: Final[__Transform]
+TransformExclC14NWithComments: Final[__Transform]
+TransformHmacMd5: Final[__Transform]
+TransformHmacRipemd160: Final[__Transform]
+TransformHmacSha1: Final[__Transform]
+TransformHmacSha224: Final[__Transform]
+TransformHmacSha256: Final[__Transform]
+TransformHmacSha384: Final[__Transform]
+TransformHmacSha512: Final[__Transform]
+TransformInclC14N: Final[__Transform]
+TransformInclC14N11: Final[__Transform]
+TransformInclC14N11WithComments: Final[__Transform]
+TransformInclC14NWithComments: Final[__Transform]
+TransformKWAes128: Final[__Transform]
+TransformKWAes192: Final[__Transform]
+TransformKWAes256: Final[__Transform]
+TransformKWDes3: Final[__Transform]
+TransformMd5: Final[__Transform]
+TransformRemoveXmlTagsC14N: Final[__TransformNoHref]
+TransformRipemd160: Final[__Transform]
+TransformRsaMd5: Final[__Transform]
+TransformRsaOaep: Final[__Transform]
+TransformRsaPkcs1: Final[__Transform]
+TransformRsaRipemd160: Final[__Transform]
+TransformRsaSha1: Final[__Transform]
+TransformRsaSha224: Final[__Transform]
+TransformRsaSha256: Final[__Transform]
+TransformRsaSha384: Final[__Transform]
+TransformRsaSha512: Final[__Transform]
+TransformSha1: Final[__Transform]
+TransformSha224: Final[__Transform]
+TransformSha256: Final[__Transform]
+TransformSha384: Final[__Transform]
+TransformSha512: Final[__Transform]
+TransformUsageAny: Final[int]
+TransformUsageC14NMethod: Final[int]
+TransformUsageDSigTransform: Final[int]
+TransformUsageDigestMethod: Final[int]
+TransformUsageEncryptionMethod: Final[int]
+TransformUsageSignatureMethod: Final[int]
+TransformUsageUnknown: Final[int]
+TransformVisa3DHack: Final[__TransformNoHref]
+TransformXPath: Final[__Transform]
+TransformXPath2: Final[__Transform]
+TransformXPointer: Final[__Transform]
+TransformXslt: Final[__Transform]
+TypeEncContent: Final[str]
+TypeEncElement: Final[str]
+XPath2Ns: Final[str]
+XPathNs: Final[str]
+XPointerNs: Final[str]
diff --git a/src/xmlsec/template.pyi b/src/xmlsec/template.pyi
index 162fe25d..d1755fa2 100644
--- a/src/xmlsec/template.pyi
+++ b/src/xmlsec/template.pyi
@@ -1,37 +1,38 @@
-from typing import Any, Optional, Sequence, Union
+from collections.abc import Sequence
+from typing import Any
from lxml.etree import _Element
from xmlsec.constants import __Transform as Transform
def add_encrypted_key(
- node: _Element, method: Transform, id: Optional[str] = None, type: Optional[str] = None, recipient: Optional[str] = None
+ node: _Element, method: Transform, id: str | None = ..., type: str | None = ..., recipient: str | None = ...
) -> _Element: ...
-def add_key_name(node: _Element, name: Optional[str] = ...) -> _Element: ...
+def add_key_name(node: _Element, name: str | None = ...) -> _Element: ...
def add_key_value(node: _Element) -> _Element: ...
def add_reference(
- node: _Element, digest_method: Transform, id: Optional[str] = ..., uri: Optional[str] = ..., type: Optional[str] = ...
+ node: _Element, digest_method: Transform, id: str | None = ..., uri: str | None = ..., type: str | None = ...
) -> _Element: ...
def add_transform(node: _Element, transform: Transform) -> Any: ...
def add_x509_data(node: _Element) -> _Element: ...
-def create(node: _Element, c14n_method: Transform, sign_method: Transform) -> _Element: ...
+def create(node: _Element, c14n_method: Transform, sign_method: Transform, id: str | None = ..., ns: str | None = ...) -> _Element: ...
def encrypted_data_create(
node: _Element,
method: Transform,
- id: Optional[str] = ...,
- type: Optional[str] = ...,
- mime_type: Optional[str] = ...,
- encoding: Optional[str] = ...,
- ns: Optional[str] = ...,
+ id: str | None = ...,
+ type: str | None = ...,
+ mime_type: str | None = ...,
+ encoding: str | None = ...,
+ ns: str | None = ...,
) -> _Element: ...
def encrypted_data_ensure_cipher_value(node: _Element) -> _Element: ...
-def encrypted_data_ensure_key_info(node: _Element, id: Optional[str] = ..., ns: Optional[str] = ...) -> _Element: ...
-def ensure_key_info(node: _Element, id: Optional[str] = ...) -> _Element: ...
-def transform_add_c14n_inclusive_namespaces(node: _Element, prefixes: Union[str, Sequence[str]]) -> None: ...
+def encrypted_data_ensure_key_info(node: _Element, id: str | None = ..., ns: str | None = ...) -> _Element: ...
+def ensure_key_info(node: _Element, id: str | None = ...) -> _Element: ...
+def transform_add_c14n_inclusive_namespaces(node: _Element, prefixes: str | Sequence[str]) -> None: ...
def x509_data_add_certificate(node: _Element) -> _Element: ...
def x509_data_add_crl(node: _Element) -> _Element: ...
def x509_data_add_issuer_serial(node: _Element) -> _Element: ...
def x509_data_add_ski(node: _Element) -> _Element: ...
def x509_data_add_subject_name(node: _Element) -> _Element: ...
-def x509_issuer_serial_add_issuer_name(node: _Element, name: Optional[str] = ...) -> _Element: ...
-def x509_issuer_serial_add_serial_number(node: _Element, serial: Optional[str] = ...) -> _Element: ...
+def x509_issuer_serial_add_issuer_name(node: _Element, name: str | None = ...) -> _Element: ...
+def x509_issuer_serial_add_serial_number(node: _Element, serial: str | None = ...) -> _Element: ...
diff --git a/src/xmlsec/tree.pyi b/src/xmlsec/tree.pyi
index 6447fd08..9f96e447 100644
--- a/src/xmlsec/tree.pyi
+++ b/src/xmlsec/tree.pyi
@@ -1,17 +1,18 @@
-from typing import Optional, overload, Sequence
+from collections.abc import Sequence
+from typing import overload
from lxml.etree import _Element
def add_ids(node: _Element, ids: Sequence[str]) -> None: ...
@overload
-def find_child(parent: _Element, name: str) -> Optional[_Element]: ...
+def find_child(parent: _Element, name: str) -> _Element | None: ...
@overload
-def find_child(parent: _Element, name: str, namespace: str = ...) -> Optional[_Element]: ...
+def find_child(parent: _Element, name: str, namespace: str = ...) -> _Element | None: ...
@overload
-def find_node(node: _Element, name: str) -> Optional[_Element]: ...
+def find_node(node: _Element, name: str) -> _Element | None: ...
@overload
-def find_node(node: _Element, name: str, namespace: str = ...) -> Optional[_Element]: ...
+def find_node(node: _Element, name: str, namespace: str = ...) -> _Element | None: ...
@overload
-def find_parent(node: _Element, name: str) -> Optional[_Element]: ...
+def find_parent(node: _Element, name: str) -> _Element | None: ...
@overload
-def find_parent(node: _Element, name: str, namespace: str = ...) -> Optional[_Element]: ...
+def find_parent(node: _Element, name: str, namespace: str = ...) -> _Element | None: ...
diff --git a/tests/base.py b/tests/base.py
index b05de1de..1d21c89b 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -1,14 +1,13 @@
import gc
import os
import sys
+import unittest
from lxml import etree
-import xmlsec
-
-import unittest
+import xmlsec
-etype = type(etree.Element("test"))
+etype = type(etree.Element('test'))
ns = {'dsig': xmlsec.constants.DSigNs, 'enc': xmlsec.constants.EncNs}
@@ -16,40 +15,26 @@
try:
import resource
- def get_memory_usage():
- return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
-except ImportError:
- resource = None
-
- def get_memory_usage():
- return 0
-
-
-def get_iterations():
- if sys.platform in ('win32',):
- return 0
-
- try:
- return int(os.getenv("PYXMLSEC_TEST_ITERATIONS", "10"))
- except ValueError:
- return 0
+ test_iterations = int(os.environ.get('PYXMLSEC_TEST_ITERATIONS', '10'))
+except (ImportError, ValueError):
+ test_iterations = 0
class TestMemoryLeaks(unittest.TestCase):
maxDiff = None
- iterations = get_iterations()
+ iterations = test_iterations
- data_dir = os.path.join(os.path.dirname(__file__), "data")
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
def setUp(self):
gc.disable()
- self.addTypeEqualityFunc(etype, "assertXmlEqual")
+ self.addTypeEqualityFunc(etype, 'assertXmlEqual')
xmlsec.enable_debug_trace(1)
def run(self, result=None):
# run first time
- super(TestMemoryLeaks, self).run(result=result)
+ super().run(result=result)
if self.iterations == 0:
return
@@ -57,8 +42,8 @@ def run(self, result=None):
o_count = gc.get_count()[0]
m_hits = 0
o_hits = 0
- for i in range(self.iterations):
- super(TestMemoryLeaks, self).run(result=result)
+ for _ in range(self.iterations):
+ super().run(result=result)
m_usage_n = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
if m_usage_n > m_usage:
m_usage = m_usage_n
@@ -73,63 +58,60 @@ def run(self, result=None):
if m_hits > int(self.iterations * 0.8):
result.buffer = False
try:
- raise AssertionError("memory leak detected")
+ raise AssertionError('memory leak detected')
except AssertionError:
result.addError(self, sys.exc_info())
if o_hits > int(self.iterations * 0.8):
result.buffer = False
try:
- raise AssertionError("unreferenced objects detected")
+ raise AssertionError('unreferenced objects detected')
except AssertionError:
result.addError(self, sys.exc_info())
def path(self, name):
- """returns full path for resource"""
+ """Return full path for resource."""
return os.path.join(self.data_dir, name)
def load(self, name):
- """loads resource by name"""
- with open(self.path(name), "rb") as stream:
+ """Load resource by name."""
+ with open(self.path(name), 'rb') as stream:
return stream.read()
def load_xml(self, name, xpath=None):
- """returns xml.etree"""
- root = etree.parse(self.path(name)).getroot()
- if xpath is None:
- return root
- return root.find(xpath)
+ """Return xml.etree."""
+ with open(self.path(name)) as f:
+ root = etree.parse(f).getroot()
+ if xpath is None:
+ return root
+ return root.find(xpath)
def dump(self, root):
print(etree.tostring(root))
def assertXmlEqual(self, first, second, msg=None):
- """Checks equality of etree.roots"""
+ """Check equality of etree.roots."""
msg = msg or ''
if first.tag != second.tag:
- self.fail('Tags do not match: %s and %s. %s' % (first.tag, second.tag, msg))
+ self.fail(f'Tags do not match: {first.tag} and {second.tag}. {msg}')
for name, value in first.attrib.items():
if second.attrib.get(name) != value:
- self.fail(
- 'Attributes do not match: %s=%r, %s=%r. %s' % (name, value, name, second.attrib.get(name), msg)
- )
- for name in second.attrib.keys():
+ self.fail(f'Attributes do not match: {name}={value!r}, {name}={second.attrib.get(name)!r}. {msg}')
+ for name in second.attrib:
if name not in first.attrib:
- self.fail('x2 has an attribute x1 is missing: %s. %s' % (name, msg))
- if not xml_text_compare(first.text, second.text):
- self.fail('text: %r != %r. %s' % (first.text, second.text, msg))
- if not xml_text_compare(first.tail, second.tail):
- self.fail('tail: %r != %r. %s' % (first.tail, second.tail, msg))
+ self.fail(f'x2 has an attribute x1 is missing: {name}. {msg}')
+ if not _xml_text_compare(first.text, second.text):
+ self.fail(f'text: {first.text!r} != {second.text!r}. {msg}')
+ if not _xml_text_compare(first.tail, second.tail):
+ self.fail(f'tail: {first.tail!r} != {second.tail!r}. {msg}')
cl1 = sorted(first.getchildren(), key=lambda x: x.tag)
cl2 = sorted(second.getchildren(), key=lambda x: x.tag)
if len(cl1) != len(cl2):
- self.fail('children length differs, %i != %i. %s' % (len(cl1), len(cl2), msg))
- i = 0
+ self.fail(f'children length differs, {len(cl1)} != {len(cl2)}. {msg}')
for c1, c2 in zip(cl1, cl2):
- i += 1
self.assertXmlEqual(c1, c2)
-def xml_text_compare(t1, t2):
+def _xml_text_compare(t1, t2):
if not t1 and not t2:
return True
if t1 == '*' or t2 == '*':
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..a65235d5
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,10 @@
+def pytest_collection_modifyitems(items):
+ """Put the module init test first.
+
+ This way, we implicitly check whether any subsequent test fails because of module reinitialization.
+ """
+
+ def module_init_tests_first(item):
+ return int('test_xmlsec.py::TestModule::test_reinitialize_module' not in item.nodeid)
+
+ items.sort(key=module_init_tests_first)
diff --git a/tests/data/deskey.bin b/tests/data/deskey.bin
index 019924a7..73245c3c 100644
--- a/tests/data/deskey.bin
+++ b/tests/data/deskey.bin
@@ -1 +1 @@
-012345670123456701234567
\ No newline at end of file
+012345670123456701234567
diff --git a/tests/data/doc.xml b/tests/data/doc.xml
index fd474859..39f8d761 100644
--- a/tests/data/doc.xml
+++ b/tests/data/doc.xml
@@ -4,4 +4,4 @@ XML Security Library example: Original XML doc file for enc example.
-->
Hello, World!
-
\ No newline at end of file
+
diff --git a/tests/data/enc-bad-in.xml b/tests/data/enc-bad-in.xml
deleted file mode 100644
index 460738fc..00000000
--- a/tests/data/enc-bad-in.xml
+++ /dev/null
@@ -1,208 +0,0 @@
-
-
-
-
-
-
-
-
- MyNextCar
- CreditApplication
- MYNEXTCAR
- VW
- 409D03
- MyNextCar
-
- 2018-11-20T09:37:45Z
- 7f0842cc-8d47-4955-be31-c61d07ee490b
-
- VW
-
-
-
-
-
-
-
- VCI_MNA_0000070250
-
-
- Car Chantilly
-
- 14839 Stonecroft Center Ct
- Chantilly
- VA
- US
- 20151
-
-
-
- MyNextCar
- MNA
-
- 7039562100
-
- CAR
-
- N
-
-
-
- 2017
- Q7
- CAR
- New
- 0
- Prestige
-
- 64300.0
- MSRP
-
-
- 64300.0
- Selling Price
-
-
-
-
- 113456789
- NationalId
-
-
- John
- Q
- Public
-
-
- 999 Washington Ave
- Apt #332
- Front Royal
- VA
- US
- 22630
- 01
- 10
- Own
-
-
- 21 E 9th Ave
- Boulder
- CO
- US
- 80301-7577
- 07
- 11
- Own
-
-
- 3032852402
- 3032852405
- 7203554444
- JohnQPublic@anydomain.org
-
-
- 1967-07-31
-
- 0
-
- UPS
-
- 1775 Wiehle Ave.
- Reston
- VA
- US
- 20190
-
- 9500.0
- Driver
- 01
- 05
- Current
-
-
- FedEx
- 4000.00
- Driver
- 04
- 09
- Previous
-
- 1252.52
-
- 1500.00
-
-
- 1
- Consents to Credit Check
-
-
-
-
- 123435325
- NationalId
-
-
- Lisa
- C
- Public
-
-
- 999 Lewis Street
- Front Royal
- VA
- US
- 22630
- 5
- 0
- Own
-
-
- 5401110000
- 5401110073
- public@test.com
-
-
- 1963-04-20
-
-
- Christendom College
-
- 999 Christendom Dr
- Front Royal
- VA
- US
- 22630
-
- 6200.00
- Professor
- 5
- 0
- Current
-
- 1252.52
-
- 1
- Consents to Credit Check
-
-
-
- R
- 0.00
- 66
- 5000.00
- INDIVCOAPP
- 2000.00
- MyNextCar
-
- 1978
- Bonneville
- Pontiac
- Coupe
-
-
-
-
-
-
-
-
diff --git a/tests/data/enc1-in.xml b/tests/data/enc1-in.xml
index fd474859..39f8d761 100644
--- a/tests/data/enc1-in.xml
+++ b/tests/data/enc1-in.xml
@@ -4,4 +4,4 @@ XML Security Library example: Original XML doc file for enc example.
-->
Hello, World!
-
\ No newline at end of file
+
diff --git a/tests/data/enc1-out.xml b/tests/data/enc1-out.xml
index 9499453b..ab0b1a6c 100644
--- a/tests/data/enc1-out.xml
+++ b/tests/data/enc1-out.xml
@@ -19,4 +19,4 @@ DY/U86tTpTn95NwHD10SLyrL6rpXdbEuoIQHhWLwV9uQxnJA/Pn1KZ+xXK/fePfP
2pb5Mxd0f+AW56Cs3MfQ9HJkUVeliSi1hVCNCVHTKeMyC2VL6lPhQ9+L01aSeTSY
-
\ No newline at end of file
+
diff --git a/tests/data/enc2-out.xml b/tests/data/enc2-out.xml
index 6556248e..4b3b5c34 100644
--- a/tests/data/enc2-out.xml
+++ b/tests/data/enc2-out.xml
@@ -19,4 +19,4 @@ CTBwsOXCAEJYXPkTrnB3qQ==
4m5BRKEswOe8JISY7NrPGLBYv7Ay5pBV+nG6it51gz0=
-
\ No newline at end of file
+
diff --git a/tests/data/rsacert.pem b/tests/data/rsacert.pem
index 02489a43..e8a68228 100644
--- a/tests/data/rsacert.pem
+++ b/tests/data/rsacert.pem
@@ -32,13 +32,13 @@ Certificate:
65:c3
Exponent: 65537 (0x10001)
X509v3 extensions:
- X509v3 Basic Constraints:
+ X509v3 Basic Constraints:
CA:FALSE
- Netscape Comment:
+ Netscape Comment:
OpenSSL Generated Certificate
- X509v3 Subject Key Identifier:
+ X509v3 Subject Key Identifier:
24:84:2C:F2:D4:59:20:62:8B:2E:5C:86:90:A3:AA:30:BA:27:1A:9C
- X509v3 Authority Key Identifier:
+ X509v3 Authority Key Identifier:
keyid:B4:B9:EF:9A:E6:97:0E:68:65:1E:98:CE:FA:55:0D:89:06:DB:4C:7C
DirName:/C=US/ST=California/L=Sunnyvale/O=XML Security Library (http://www.aleksey.com/xmlsec)/OU=Root Certificate/CN=Aleksey Sanin/emailAddress=xmlsec@aleksey.com
serial:00
diff --git a/tests/data/sign1-in.xml b/tests/data/sign1-in.xml
index ac71a949..0a0cd442 100644
--- a/tests/data/sign1-in.xml
+++ b/tests/data/sign1-in.xml
@@ -1,6 +1,6 @@
-
@@ -24,4 +24,3 @@ XML Security Library example: Simple signature template file for sign1 example.
-
diff --git a/tests/data/sign1-out.xml b/tests/data/sign1-out.xml
index 04d8fed0..f46ac1f4 100644
--- a/tests/data/sign1-out.xml
+++ b/tests/data/sign1-out.xml
@@ -1,6 +1,6 @@
-
diff --git a/tests/data/sign2-in.xml b/tests/data/sign2-in.xml
index 5d9fb352..2f2592f2 100644
--- a/tests/data/sign2-in.xml
+++ b/tests/data/sign2-in.xml
@@ -1,6 +1,6 @@
-
diff --git a/tests/data/sign2-out.xml b/tests/data/sign2-out.xml
index b37cad94..b5782d6c 100644
--- a/tests/data/sign2-out.xml
+++ b/tests/data/sign2-out.xml
@@ -1,6 +1,6 @@
-
diff --git a/tests/data/sign3-in.xml b/tests/data/sign3-in.xml
index f75da16a..96260b8f 100644
--- a/tests/data/sign3-in.xml
+++ b/tests/data/sign3-in.xml
@@ -1,6 +1,6 @@
-
diff --git a/tests/data/sign3-out.xml b/tests/data/sign3-out.xml
index 847e1af2..b7bf15c3 100644
--- a/tests/data/sign3-out.xml
+++ b/tests/data/sign3-out.xml
@@ -1,6 +1,6 @@
-
diff --git a/tests/data/sign4-in.xml b/tests/data/sign4-in.xml
index cc00479b..d49fc3ae 100644
--- a/tests/data/sign4-in.xml
+++ b/tests/data/sign4-in.xml
@@ -1,6 +1,6 @@
-
diff --git a/tests/data/sign4-out.xml b/tests/data/sign4-out.xml
index a6fecb44..bdd1014e 100644
--- a/tests/data/sign4-out.xml
+++ b/tests/data/sign4-out.xml
@@ -52,4 +52,4 @@ ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL
NJ2D
-
\ No newline at end of file
+
diff --git a/tests/data/sign5-out-xmlsec_1_2_36_to_37.xml b/tests/data/sign5-out-xmlsec_1_2_36_to_37.xml
new file mode 100644
index 00000000..f359b138
--- /dev/null
+++ b/tests/data/sign5-out-xmlsec_1_2_36_to_37.xml
@@ -0,0 +1,67 @@
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+
+
+
+HjY8ilZAIEM2tBbPn5mYO1ieIX4=
+
+
+SIaj/6KY3C1SmDXU2++Gm31U1xTadFp04WhBgfsJFbxrL+q7GKSKN9kfQ+UpN9+i
+D5fWmuavXEHe4Gw6RMaMEkq2URQo7F68+d5J/ajq8/l4n+xE6/reGScVwT6L4dEP
+XXVJcAi2ZnQ3O7GTNvNGCPibL9mUcyCWBFZ92Uemtc/vJFCQ7ZyKMdMfACgxOwyN
+T/9971oog241/2doudhonc0I/3mgPYWkZdX6yvr62mEjnG+oUZkhWYJ4ewZJ4hM4
+JjbFqZO+OEzDRSbw3DkmuBA/mtlx+3t13SESfEub5hqoMdVmtth/eTb64dsPdl9r
+3k1ACVX9f8aHfQQdJOmLFQ==
+
+
+
+
+
+
+Test Issuer
+1
+
+MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx
+EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE
+ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v
+eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl
+a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X
+DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw
+EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy
+eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt
+cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf
+BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt
+quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E
+mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg
+qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53
+7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w
+Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG
+A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp
+ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw
+MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx
+EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE
+ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v
+eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl
+a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA
+MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY
+1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn
+ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL
+NJ2D
+
+
+
+
+
diff --git a/tests/softhsm_setup.py b/tests/softhsm_setup.py
new file mode 100644
index 00000000..d55c16fd
--- /dev/null
+++ b/tests/softhsm_setup.py
@@ -0,0 +1,328 @@
+"""Testing the PKCS#11 shim layer.
+
+Heavily inspired by from https://github.com/IdentityPython/pyXMLSecurity by leifj
+under license "As is", see https://github.com/IdentityPython/pyXMLSecurity/blob/master/LICENSE.txt
+"""
+
+import logging
+import os
+import shutil
+import subprocess
+import tempfile
+import traceback
+import unittest
+
+DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
+
+
+def paths_for_component(component: str, default_paths):
+ env_path = os.environ.get(component)
+ return [env_path] if env_path else default_paths
+
+
+def find_alts(component_name, alts) -> str:
+ for a in alts:
+ if os.path.exists(a):
+ return a
+ raise unittest.SkipTest(f'Required component is missing: {component_name}')
+
+
+def run_cmd(args, softhsm_conf=None):
+ env = {}
+ if softhsm_conf is not None:
+ env['SOFTHSM_CONF'] = softhsm_conf
+ env['SOFTHSM2_CONF'] = softhsm_conf
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
+ out, err = proc.communicate()
+ if err is not None and len(err) > 0:
+ logging.error(err)
+ if out is not None and len(out) > 0:
+ logging.debug(out)
+ rv = proc.wait()
+ if rv:
+ with open(softhsm_conf) as f:
+ conf = f.read()
+ msg = '[cmd: {cmd}] [code: {code}] [stdout: {out}] [stderr: {err}] [config: {conf}]'
+ msg = msg.format(
+ cmd=' '.join(args),
+ code=rv,
+ out=out.strip(),
+ err=err.strip(),
+ conf=conf,
+ )
+ raise RuntimeError(msg)
+ return out, err
+
+
+component_default_paths = {
+ 'P11_MODULE': [
+ '/usr/lib/softhsm/libsofthsm2.so',
+ '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so',
+ '/usr/lib/softhsm/libsofthsm.so',
+ '/usr/lib64/softhsm/libsofthsm2.so',
+ ],
+ 'P11_ENGINE': [
+ '/usr/lib/ssl/engines/libpkcs11.so',
+ '/usr/lib/engines/engine_pkcs11.so',
+ '/usr/lib/x86_64-linux-gnu/engines-1.1/pkcs11.so',
+ '/usr/lib64/engines-1.1/pkcs11.so',
+ '/usr/lib64/engines-1.1/libpkcs11.so',
+ '/usr/lib64/engines-3/pkcs11.so',
+ '/usr/lib64/engines-3/libpkcs11.so',
+ '/usr/lib/x86_64-linux-gnu/engines-3/pkcs11.so',
+ '/usr/lib/x86_64-linux-gnu/engines-3/libpkcs11.so',
+ ],
+ 'PKCS11_TOOL': [
+ '/usr/bin/pkcs11-tool',
+ ],
+ 'SOFTHSM': [
+ '/usr/bin/softhsm2-util',
+ '/usr/bin/softhsm',
+ ],
+ 'OPENSSL': [
+ '/usr/bin/openssl',
+ ],
+}
+
+component_path = {
+ component_name: find_alts(component_name, paths_for_component(component_name, default_paths))
+ for component_name, default_paths in component_default_paths.items()
+}
+
+softhsm_version = 1
+if component_path['SOFTHSM'].endswith('softhsm2-util'):
+ softhsm_version = 2
+
+openssl_version = subprocess.check_output([component_path['OPENSSL'], 'version'])[8:11].decode()
+
+p11_test_files = []
+softhsm_conf = None
+softhsm_db = None
+
+
+def _temp_file() -> str:
+ f = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115
+ p11_test_files.append(f.name)
+ return f.name
+
+
+def _temp_dir() -> str:
+ d = tempfile.mkdtemp()
+ p11_test_files.append(d)
+ return d
+
+
+@unittest.skipIf(component_path['P11_MODULE'] is None, 'SoftHSM PKCS11 module not installed')
+def setup() -> None:
+ logging.debug('Creating test pkcs11 token using softhsm')
+ try:
+ global softhsm_conf
+ softhsm_conf = _temp_file()
+ logging.debug('Generating softhsm.conf')
+ with open(softhsm_conf, 'w') as f:
+ if softhsm_version == 2:
+ softhsm_db = _temp_dir()
+ f.write(
+ f"""
+# Generated by test
+directories.tokendir = {softhsm_db}
+objectstore.backend = file
+log.level = DEBUG
+"""
+ )
+ else:
+ softhsm_db = _temp_file()
+ f.write(
+ f"""
+# Generated by test
+0:{softhsm_db}
+"""
+ )
+
+ logging.debug('Initializing the token')
+ _, _ = run_cmd(
+ [
+ component_path['SOFTHSM'],
+ '--slot',
+ '0',
+ '--label',
+ 'test',
+ '--init-token',
+ '--pin',
+ 'secret1',
+ '--so-pin',
+ 'secret2',
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+
+ hash_priv_key = _temp_file()
+ logging.debug('Converting test private key to format for softhsm')
+ run_cmd(
+ [
+ component_path['OPENSSL'],
+ 'pkcs8',
+ '-topk8',
+ '-inform',
+ 'PEM',
+ '-outform',
+ 'PEM',
+ '-nocrypt',
+ '-in',
+ os.path.join(DATA_DIR, 'rsakey.pem'),
+ '-out',
+ hash_priv_key,
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+
+ logging.debug('Importing the test key to softhsm')
+ run_cmd(
+ [
+ component_path['SOFTHSM'],
+ '--import',
+ hash_priv_key,
+ '--token',
+ 'test',
+ '--id',
+ 'a1b2',
+ '--label',
+ 'test',
+ '--pin',
+ 'secret1',
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+ run_cmd(
+ [
+ component_path['PKCS11_TOOL'],
+ '--module',
+ component_path['P11_MODULE'],
+ '-l',
+ '--pin',
+ 'secret1',
+ '-O',
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+ signer_cert_pem = _temp_file()
+ openssl_conf = _temp_file()
+ logging.debug('Generating OpenSSL config for version %s', openssl_version)
+ with open(openssl_conf, 'w') as f:
+ f.write(
+ '\n'.join(
+ [
+ 'openssl_conf = openssl_def',
+ '[openssl_def]',
+ 'engines = engine_section',
+ '[engine_section]',
+ 'pkcs11 = pkcs11_section',
+ '[req]',
+ 'distinguished_name = req_distinguished_name',
+ '[req_distinguished_name]',
+ '[pkcs11_section]',
+ 'engine_id = pkcs11',
+ # dynamic_path,
+ 'MODULE_PATH = {}'.format(component_path['P11_MODULE']),
+ 'init = 0',
+ ]
+ )
+ )
+
+ with open(openssl_conf) as f:
+ logging.debug('-------- START DEBUG openssl_conf --------')
+ logging.debug(f.readlines())
+ logging.debug('-------- END DEBUG openssl_conf --------')
+ logging.debug('-------- START DEBUG paths --------')
+ logging.debug(run_cmd(['ls', '-ld', component_path['P11_ENGINE']]))
+ logging.debug(run_cmd(['ls', '-ld', component_path['P11_MODULE']]))
+ logging.debug('-------- END DEBUG paths --------')
+
+ signer_cert_der = _temp_file()
+
+ logging.debug('Generating self-signed certificate')
+ run_cmd(
+ [
+ component_path['OPENSSL'],
+ 'req',
+ '-new',
+ '-x509',
+ '-subj',
+ '/CN=Test Signer',
+ '-engine',
+ 'pkcs11',
+ '-config',
+ openssl_conf,
+ '-keyform',
+ 'engine',
+ '-key',
+ 'label_test',
+ '-passin',
+ 'pass:secret1',
+ '-out',
+ signer_cert_pem,
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+
+ run_cmd(
+ [
+ component_path['OPENSSL'],
+ 'x509',
+ '-inform',
+ 'PEM',
+ '-outform',
+ 'DER',
+ '-in',
+ signer_cert_pem,
+ '-out',
+ signer_cert_der,
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+
+ logging.debug('Importing certificate into token')
+
+ run_cmd(
+ [
+ component_path['PKCS11_TOOL'],
+ '--module',
+ component_path['P11_MODULE'],
+ '-l',
+ '--slot-index',
+ '0',
+ '--id',
+ 'a1b2',
+ '--label',
+ 'test',
+ '-y',
+ 'cert',
+ '-w',
+ signer_cert_der,
+ '--pin',
+ 'secret1',
+ ],
+ softhsm_conf=softhsm_conf,
+ )
+
+ # TODO: Should be teardowned in teardown
+ os.environ['SOFTHSM_CONF'] = softhsm_conf
+ os.environ['SOFTHSM2_CONF'] = softhsm_conf
+
+ except Exception as ex:
+ print('-' * 64)
+ traceback.print_exc()
+ print('-' * 64)
+ logging.exception('PKCS11 tests disabled: unable to initialize test token')
+ raise ex
+
+
+def teardown() -> None:
+ global p11_test_files
+ for o in p11_test_files:
+ if os.path.exists(o):
+ if os.path.isdir(o):
+ shutil.rmtree(o)
+ else:
+ os.unlink(o)
+ p11_test_files = []
diff --git a/tests/test_constants.py b/tests/test_constants.py
index 857a1cdd..2c39d5f6 100644
--- a/tests/test_constants.py
+++ b/tests/test_constants.py
@@ -1,7 +1,8 @@
"""Test constants from :mod:`xmlsec.constants` module."""
+import pytest
+
import xmlsec
-from hypothesis import given, strategies
def _constants(typename):
@@ -17,25 +18,25 @@ def _constants(typename):
)
-@given(transform=strategies.sampled_from(_constants('__Transform')))
+@pytest.mark.parametrize('transform', _constants('__Transform'), ids=repr)
def test_transform_str(transform):
"""Test string representation of ``xmlsec.constants.__Transform``."""
- assert str(transform) == '{}, {}'.format(transform.name, transform.href)
+ assert str(transform) == f'{transform.name}, {transform.href}'
-@given(transform=strategies.sampled_from(_constants('__Transform')))
+@pytest.mark.parametrize('transform', _constants('__Transform'), ids=repr)
def test_transform_repr(transform):
"""Test raw string representation of ``xmlsec.constants.__Transform``."""
- assert repr(transform) == '__Transform({!r}, {!r}, {})'.format(transform.name, transform.href, transform.usage)
+ assert repr(transform) == f'__Transform({transform.name!r}, {transform.href!r}, {transform.usage})'
-@given(keydata=strategies.sampled_from(_constants('__KeyData')))
+@pytest.mark.parametrize('keydata', _constants('__KeyData'), ids=repr)
def test_keydata_str(keydata):
"""Test string representation of ``xmlsec.constants.__KeyData``."""
- assert str(keydata) == '{}, {}'.format(keydata.name, keydata.href)
+ assert str(keydata) == f'{keydata.name}, {keydata.href}'
-@given(keydata=strategies.sampled_from(_constants('__KeyData')))
+@pytest.mark.parametrize('keydata', _constants('__KeyData'), ids=repr)
def test_keydata_repr(keydata):
"""Test raw string representation of ``xmlsec.constants.__KeyData``."""
- assert repr(keydata) == '__KeyData({!r}, {!r})'.format(keydata.name, keydata.href)
+ assert repr(keydata) == f'__KeyData({keydata.name!r}, {keydata.href!r})'
diff --git a/tests/test_doc_examples.py b/tests/test_doc_examples.py
index 2fc490f3..7aa8e517 100644
--- a/tests/test_doc_examples.py
+++ b/tests/test_doc_examples.py
@@ -3,24 +3,17 @@
import contextlib
import os
import runpy
-import sys
+from pathlib import Path
import pytest
-if sys.version_info >= (3, 4):
- from pathlib import Path
-else: # python2.7 compat
- from _pytest.pathlib import Path
-
-
examples_dir = Path(__file__, '../../doc/source/examples').resolve()
examples = sorted(examples_dir.glob('*.py'))
@contextlib.contextmanager
def cd(where_to):
- """
- Temporarily change the working directory.
+ """Temporarily change the working directory.
Restore the current working dir after exiting the context.
"""
@@ -34,8 +27,7 @@ def cd(where_to):
@pytest.mark.parametrize('example', examples, ids=lambda p: p.name)
def test_doc_example(example):
- """
- Verify example scripts included in the docs are up to date.
+ """Verify example scripts included in the docs are up to date.
Execute each script in :file:`docs/source/examples`,
not raising any errors is good enough.
diff --git a/tests/test_ds.py b/tests/test_ds.py
index 26d49075..dd0657d3 100644
--- a/tests/test_ds.py
+++ b/tests/test_ds.py
@@ -1,7 +1,7 @@
-from tests import base
+import unittest
import xmlsec
-
+from tests import base
consts = xmlsec.constants
@@ -11,36 +11,87 @@ def test_init(self):
ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
del ctx
- def test_key(self):
+ def test_init_no_keys_manager(self):
+ ctx = xmlsec.SignatureContext()
+ del ctx
+
+ def test_init_bad_args(self):
+ with self.assertRaisesRegex(TypeError, 'KeysManager required'):
+ xmlsec.SignatureContext(manager='foo')
+
+ def test_no_key(self):
ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
self.assertIsNone(ctx.key)
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+
+ def test_del_key(self):
+ ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ del ctx.key
+ self.assertIsNone(ctx.key)
+
+ def test_set_key(self):
+ ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
+ def test_set_key_bad_type(self):
+ ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
+ with self.assertRaisesRegex(TypeError, r'instance of \*xmlsec.Key\* expected.'):
+ ctx.key = ''
+
+ def test_set_invalid_key(self):
+ ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
+ with self.assertRaisesRegex(TypeError, 'empty key.'):
+ ctx.key = xmlsec.Key()
+
def test_register_id(self):
ctx = xmlsec.SignatureContext()
- root = self.load_xml("sign_template.xml")
- sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1, "Id")
- ctx.register_id(sign, "Id")
+ root = self.load_xml('sign_template.xml')
+ sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1, 'Id')
+ ctx.register_id(sign, 'Id')
+
+ def test_register_id_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ with self.assertRaises(TypeError):
+ ctx.register_id('')
+
+ def test_register_id_with_namespace_without_attribute(self):
+ ctx = xmlsec.SignatureContext()
+ root = self.load_xml('sign_template.xml')
+ sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1, 'Id')
+ with self.assertRaisesRegex(xmlsec.Error, 'missing attribute.'):
+ ctx.register_id(sign, 'Id', id_ns='foo')
+
+ def test_sign_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaises(TypeError):
+ ctx.sign('')
+
+ def test_sign_fail(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to sign'):
+ ctx.sign(self.load_xml('sign1-in.xml'))
def test_sign_case1(self):
"""Should sign a pre-constructed template file using a key from a PEM file."""
- root = self.load_xml("sign1-in.xml")
+ root = self.load_xml('sign1-in.xml')
sign = xmlsec.tree.find_node(root, consts.NodeSignature)
self.assertIsNotNone(sign)
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
ctx.sign(sign)
- self.assertEqual(self.load_xml("sign1-out.xml"), root)
+ self.assertEqual(self.load_xml('sign1-out.xml'), root)
def test_sign_case2(self):
"""Should sign a dynamicaly constructed template file using a key from a PEM file."""
- root = self.load_xml("sign2-in.xml")
+ root = self.load_xml('sign2-in.xml')
sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1)
self.assertIsNotNone(sign)
root.append(sign)
@@ -50,17 +101,17 @@ def test_sign_case2(self):
xmlsec.template.add_key_name(ki)
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
ctx.sign(sign)
- self.assertEqual(self.load_xml("sign2-out.xml"), root)
+ self.assertEqual(self.load_xml('sign2-out.xml'), root)
def test_sign_case3(self):
"""Should sign a file using a dynamicaly created template, key from PEM and an X509 cert."""
- root = self.load_xml("sign3-in.xml")
+ root = self.load_xml('sign3-in.xml')
sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1)
self.assertIsNotNone(sign)
root.append(sign)
@@ -70,24 +121,23 @@ def test_sign_case3(self):
xmlsec.template.add_x509_data(ki)
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.load_cert_from_file(self.path('rsacert.pem'), consts.KeyDataFormatPem)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
ctx.sign(sign)
- self.assertEqual(self.load_xml("sign3-out.xml"), root)
+ self.assertEqual(self.load_xml('sign3-out.xml'), root)
def test_sign_case4(self):
"""Should sign a file using a dynamically created template, key from PEM and an X509 cert with custom ns."""
-
- root = self.load_xml("sign4-in.xml")
- xmlsec.tree.add_ids(root, ["ID"])
+ root = self.load_xml('sign4-in.xml')
+ xmlsec.tree.add_ids(root, ['ID'])
elem_id = root.get('ID', None)
if elem_id:
elem_id = '#' + elem_id
- sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1, ns="ds")
+ sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1, ns='ds')
self.assertIsNotNone(sign)
root.append(sign)
ref = xmlsec.template.add_reference(sign, consts.TransformSha1, uri=elem_id)
@@ -97,18 +147,18 @@ def test_sign_case4(self):
xmlsec.template.add_x509_data(ki)
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.load_cert_from_file(self.path('rsacert.pem'), consts.KeyDataFormatPem)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
ctx.sign(sign)
- self.assertEqual(self.load_xml("sign4-out.xml"), root)
+ self.assertEqual(self.load_xml('sign4-out.xml'), root)
def test_sign_case5(self):
"""Should sign a file using a dynamicaly created template, key from PEM file and an X509 certificate."""
- root = self.load_xml("sign5-in.xml")
+ root = self.load_xml('sign5-in.xml')
sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1)
self.assertIsNotNone(sign)
root.append(sign)
@@ -125,24 +175,66 @@ def test_sign_case5(self):
xmlsec.template.x509_issuer_serial_add_serial_number(x509_issuer_serial, '1')
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.load_cert_from_file(self.path('rsacert.pem'), consts.KeyDataFormatPem)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
ctx.sign(sign)
- self.assertEqual(self.load_xml("sign5-out.xml"), root)
+ if (1, 2, 36) <= xmlsec.get_libxmlsec_version() <= (1, 2, 37):
+ expected_xml_file = 'sign5-out-xmlsec_1_2_36_to_37.xml'
+ else:
+ expected_xml_file = 'sign5-out.xml'
+ self.assertEqual(self.load_xml(expected_xml_file), root)
+
+ def test_sign_binary_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaises(TypeError):
+ ctx.sign_binary(bytes=1, transform='')
+
+ def test_sign_binary_no_key(self):
+ ctx = xmlsec.SignatureContext()
+ with self.assertRaisesRegex(xmlsec.Error, 'Sign key is not specified.'):
+ ctx.sign_binary(bytes=b'', transform=consts.TransformRsaSha1)
+
+ @unittest.skipIf(not hasattr(consts, 'TransformXslt'), reason='XSLT transformations not enabled')
+ def test_sign_binary_invalid_signature_method(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaisesRegex(xmlsec.Error, 'incompatible signature method'):
+ ctx.sign_binary(bytes=b'', transform=consts.TransformXslt)
def test_sign_binary(self):
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
+
+ sign = ctx.sign_binary(self.load('sign6-in.bin'), consts.TransformRsaSha1)
+ self.assertEqual(self.load('sign6-out.bin'), sign)
- sign = ctx.sign_binary(self.load("sign6-in.bin"), consts.TransformRsaSha1)
- self.assertEqual(self.load("sign6-out.bin"), sign)
+ def test_sign_binary_twice_not_possible(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ data = self.load('sign6-in.bin')
+ ctx.sign_binary(data, consts.TransformRsaSha1)
+ with self.assertRaisesRegex(xmlsec.Error, 'Signature context already used; it is designed for one use only.'):
+ ctx.sign_binary(data, consts.TransformRsaSha1)
+
+ def test_verify_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaises(TypeError):
+ ctx.verify('')
+
+ def test_verify_fail(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to verify'):
+ ctx.verify(self.load_xml('sign1-in.xml'))
def test_verify_case_1(self):
self.check_verify(1)
@@ -160,34 +252,86 @@ def test_verify_case_5(self):
self.check_verify(5)
def check_verify(self, i):
- root = self.load_xml("sign%d-out.xml" % i)
- xmlsec.tree.add_ids(root, ["ID"])
+ root = self.load_xml(f'sign{i}-out.xml')
+ xmlsec.tree.add_ids(root, ['ID'])
sign = xmlsec.tree.find_node(root, consts.NodeSignature)
self.assertIsNotNone(sign)
- self.assertEqual(consts.NodeSignature, sign.tag.partition("}")[2])
+ self.assertEqual(consts.NodeSignature, sign.tag.partition('}')[2])
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsapub.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsapub.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.name = 'rsapub.pem'
- self.assertEqual("rsapub.pem", ctx.key.name)
+ self.assertEqual('rsapub.pem', ctx.key.name)
ctx.verify(sign)
def test_validate_binary_sign(self):
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
- ctx.verify_binary(self.load("sign6-in.bin"), consts.TransformRsaSha1, self.load("sign6-out.bin"))
+ ctx.verify_binary(self.load('sign6-in.bin'), consts.TransformRsaSha1, self.load('sign6-out.bin'))
def test_validate_binary_sign_fail(self):
ctx = xmlsec.SignatureContext()
- ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(ctx.key)
ctx.key.name = 'rsakey.pem'
- self.assertEqual("rsakey.pem", ctx.key.name)
+ self.assertEqual('rsakey.pem', ctx.key.name)
with self.assertRaises(xmlsec.Error):
- ctx.verify_binary(self.load("sign6-in.bin"), consts.TransformRsaSha1, b"invalid")
+ ctx.verify_binary(self.load('sign6-in.bin'), consts.TransformRsaSha1, b'invalid')
+
+ def test_enable_reference_transform(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ ctx.enable_reference_transform(consts.TransformRsaSha1)
+
+ def test_enable_reference_transform_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaises(TypeError):
+ ctx.enable_reference_transform('')
+ with self.assertRaises(TypeError):
+ ctx.enable_reference_transform(0)
+ with self.assertRaises(TypeError):
+ ctx.enable_reference_transform(consts.KeyDataAes)
+
+ def test_enable_signature_transform(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ ctx.enable_signature_transform(consts.TransformRsaSha1)
+
+ def test_enable_signature_transform_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaises(TypeError):
+ ctx.enable_signature_transform('')
+ with self.assertRaises(TypeError):
+ ctx.enable_signature_transform(0)
+ with self.assertRaises(TypeError):
+ ctx.enable_signature_transform(consts.KeyDataAes)
+
+ def test_set_enabled_key_data(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ ctx.set_enabled_key_data([consts.KeyDataAes])
+
+ def test_set_enabled_key_data_empty(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ ctx.set_enabled_key_data([])
+
+ def test_set_enabled_key_data_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaises(TypeError):
+ ctx.set_enabled_key_data(0)
+
+ def test_set_enabled_key_data_bad_list(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ with self.assertRaisesRegex(TypeError, 'expected list of KeyData constants.'):
+ ctx.set_enabled_key_data('foo')
diff --git a/tests/test_enc.py b/tests/test_enc.py
index 2f8c9d74..41f78d74 100644
--- a/tests/test_enc.py
+++ b/tests/test_enc.py
@@ -1,7 +1,9 @@
-from tests import base
+import tempfile
-import xmlsec
+from lxml import etree
+import xmlsec
+from tests import base
consts = xmlsec.constants
@@ -11,26 +13,57 @@ def test_init(self):
ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
del ctx
- def test_key(self):
+ def test_init_no_keys_manager(self):
+ ctx = xmlsec.EncryptionContext()
+ del ctx
+
+ def test_init_bad_args(self):
+ with self.assertRaisesRegex(TypeError, 'KeysManager required'):
+ xmlsec.EncryptionContext(manager='foo')
+
+ def test_no_key(self):
ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
self.assertIsNone(ctx.key)
- ctx.key = xmlsec.Key.from_file(self.path("rsacert.pem"), format=consts.KeyDataFormatCertPem)
+
+ def test_get_key(self):
+ ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
+ self.assertIsNone(ctx.key)
+ ctx.key = xmlsec.Key.from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatCertPem)
self.assertIsNotNone(ctx.key)
+ def test_del_key(self):
+ ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
+ ctx.key = xmlsec.Key.from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatCertPem)
+ del ctx.key
+ self.assertIsNone(ctx.key)
+
+ def test_set_key(self):
+ ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
+ ctx.key = xmlsec.Key.from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatCertPem)
+ self.assertIsNotNone(ctx.key)
+
+ def test_set_key_bad_type(self):
+ ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
+ with self.assertRaisesRegex(TypeError, r'instance of \*xmlsec.Key\* expected.'):
+ ctx.key = ''
+
+ def test_set_invalid_key(self):
+ ctx = xmlsec.EncryptionContext(manager=xmlsec.KeysManager())
+ with self.assertRaisesRegex(TypeError, 'empty key.'):
+ ctx.key = xmlsec.Key()
+
def test_encrypt_xml(self):
root = self.load_xml('enc1-in.xml')
- enc_data = xmlsec.template.encrypted_data_create(
- root, consts.TransformAes128Cbc, type=consts.TypeEncElement, ns="xenc"
- )
+ enc_data = xmlsec.template.encrypted_data_create(root, consts.TransformAes128Cbc, type=consts.TypeEncElement, ns='xenc')
xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
- ki = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="dsig")
+ ki = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns='dsig')
ek = xmlsec.template.add_encrypted_key(ki, consts.TransformRsaOaep)
xmlsec.template.encrypted_data_ensure_cipher_value(ek)
data = root.find('./Data')
self.assertIsNotNone(data)
manager = xmlsec.KeysManager()
- manager.add_key(xmlsec.Key.from_file(self.path("rsacert.pem"), format=consts.KeyDataFormatCertPem))
+ manager.add_key(xmlsec.Key.from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatCertPem))
ctx = xmlsec.EncryptionContext(manager)
ctx.key = xmlsec.Key.generate(consts.KeyDataAes, 128, consts.KeyDataTypeSession)
@@ -40,47 +73,126 @@ def test_encrypt_xml(self):
enc_method = xmlsec.tree.find_child(enc_data, consts.NodeEncryptionMethod, consts.EncNs)
self.assertIsNotNone(enc_method)
- self.assertEqual("http://www.w3.org/2001/04/xmlenc#aes128-cbc", enc_method.get("Algorithm"))
+ self.assertEqual('http://www.w3.org/2001/04/xmlenc#aes128-cbc', enc_method.get('Algorithm'))
ki = xmlsec.tree.find_child(enc_data, consts.NodeKeyInfo, consts.DSigNs)
self.assertIsNotNone(ki)
enc_method2 = xmlsec.tree.find_node(ki, consts.NodeEncryptionMethod, consts.EncNs)
self.assertIsNotNone(enc_method2)
- self.assertEqual("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", enc_method2.get("Algorithm"))
+ self.assertEqual('http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', enc_method2.get('Algorithm'))
cipher_value = xmlsec.tree.find_node(ki, consts.NodeCipherValue, consts.EncNs)
self.assertIsNotNone(cipher_value)
+ def test_encrypt_xml_bad_args(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaises(TypeError):
+ ctx.encrypt_xml('', 0)
+
+ def test_encrypt_xml_bad_template(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaisesRegex(xmlsec.Error, 'unsupported `Type`, it should be `element` or `content`'):
+ ctx.encrypt_xml(etree.Element('root'), etree.Element('node'))
+
+ def test_encrypt_xml_bad_template_bad_type_attribute(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaisesRegex(xmlsec.Error, 'unsupported `Type`, it should be `element` or `content`'):
+ root = etree.Element('root')
+ root.attrib['Type'] = 'foo'
+ ctx.encrypt_xml(root, etree.Element('node'))
+
+ def test_encrypt_xml_fail(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to encrypt xml'):
+ root = etree.Element('root')
+ root.attrib['Type'] = consts.TypeEncElement
+ ctx.encrypt_xml(root, etree.Element('node'))
+
def test_encrypt_binary(self):
root = self.load_xml('enc2-in.xml')
enc_data = xmlsec.template.encrypted_data_create(
- root, consts.TransformAes128Cbc, type=consts.TypeEncContent, ns="xenc", mime_type="binary/octet-stream"
+ root, consts.TransformAes128Cbc, type=consts.TypeEncContent, ns='xenc', mime_type='binary/octet-stream'
)
xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
- ki = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="dsig")
+ ki = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns='dsig')
ek = xmlsec.template.add_encrypted_key(ki, consts.TransformRsaOaep)
xmlsec.template.encrypted_data_ensure_cipher_value(ek)
manager = xmlsec.KeysManager()
- manager.add_key(xmlsec.Key.from_file(self.path("rsacert.pem"), format=consts.KeyDataFormatCertPem))
+ manager.add_key(xmlsec.Key.from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatCertPem))
ctx = xmlsec.EncryptionContext(manager)
ctx.key = xmlsec.Key.generate(consts.KeyDataAes, 128, consts.KeyDataTypeSession)
encrypted = ctx.encrypt_binary(enc_data, b'test')
self.assertIsNotNone(encrypted)
- self.assertEqual("{%s}%s" % (consts.EncNs, consts.NodeEncryptedData), encrypted.tag)
+ self.assertEqual(f'{{{consts.EncNs}}}{consts.NodeEncryptedData}', encrypted.tag)
enc_method = xmlsec.tree.find_child(enc_data, consts.NodeEncryptionMethod, consts.EncNs)
self.assertIsNotNone(enc_method)
- self.assertEqual("http://www.w3.org/2001/04/xmlenc#aes128-cbc", enc_method.get("Algorithm"))
+ self.assertEqual('http://www.w3.org/2001/04/xmlenc#aes128-cbc', enc_method.get('Algorithm'))
ki = xmlsec.tree.find_child(enc_data, consts.NodeKeyInfo, consts.DSigNs)
self.assertIsNotNone(ki)
enc_method2 = xmlsec.tree.find_node(ki, consts.NodeEncryptionMethod, consts.EncNs)
self.assertIsNotNone(enc_method2)
- self.assertEqual("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", enc_method2.get("Algorithm"))
+ self.assertEqual('http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', enc_method2.get('Algorithm'))
cipher_value = xmlsec.tree.find_node(ki, consts.NodeCipherValue, consts.EncNs)
self.assertIsNotNone(cipher_value)
+ def test_encrypt_binary_bad_args(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaises(TypeError):
+ ctx.encrypt_binary('', 0)
+
+ def test_encrypt_binary_bad_template(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to encrypt binary'):
+ ctx.encrypt_binary(etree.Element('root'), b'data')
+
+ def test_encrypt_uri(self):
+ root = self.load_xml('enc2-in.xml')
+ enc_data = xmlsec.template.encrypted_data_create(
+ root, consts.TransformAes128Cbc, type=consts.TypeEncContent, ns='xenc', mime_type='binary/octet-stream'
+ )
+ xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
+ ki = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns='dsig')
+ ek = xmlsec.template.add_encrypted_key(ki, consts.TransformRsaOaep)
+ xmlsec.template.encrypted_data_ensure_cipher_value(ek)
+
+ manager = xmlsec.KeysManager()
+ manager.add_key(xmlsec.Key.from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatCertPem))
+
+ ctx = xmlsec.EncryptionContext(manager)
+ ctx.key = xmlsec.Key.generate(consts.KeyDataAes, 128, consts.KeyDataTypeSession)
+
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
+ tmpfile.write(b'test')
+
+ encrypted = ctx.encrypt_binary(enc_data, 'file://' + tmpfile.name)
+ self.assertIsNotNone(encrypted)
+ self.assertEqual(f'{{{consts.EncNs}}}{consts.NodeEncryptedData}', encrypted.tag)
+
+ enc_method = xmlsec.tree.find_child(enc_data, consts.NodeEncryptionMethod, consts.EncNs)
+ self.assertIsNotNone(enc_method)
+ self.assertEqual('http://www.w3.org/2001/04/xmlenc#aes128-cbc', enc_method.get('Algorithm'))
+
+ ki = xmlsec.tree.find_child(enc_data, consts.NodeKeyInfo, consts.DSigNs)
+ self.assertIsNotNone(ki)
+ enc_method2 = xmlsec.tree.find_node(ki, consts.NodeEncryptionMethod, consts.EncNs)
+ self.assertIsNotNone(enc_method2)
+ self.assertEqual('http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', enc_method2.get('Algorithm'))
+ cipher_value = xmlsec.tree.find_node(ki, consts.NodeCipherValue, consts.EncNs)
+ self.assertIsNotNone(cipher_value)
+
+ def test_encrypt_uri_bad_args(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaises(TypeError):
+ ctx.encrypt_uri('', 0)
+
+ def test_encrypt_uri_fail(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to encrypt URI'):
+ ctx.encrypt_uri(etree.Element('root'), '')
+
def test_decrypt1(self):
self.check_decrypt(1)
@@ -93,7 +205,7 @@ def test_decrypt_key(self):
self.assertIsNotNone(enc_key)
manager = xmlsec.KeysManager()
- manager.add_key(xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem))
+ manager.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
ctx = xmlsec.EncryptionContext(manager)
keydata = ctx.decrypt(enc_key)
ctx.reset()
@@ -103,37 +215,21 @@ def test_decrypt_key(self):
self.assertIsNotNone(enc_data)
decrypted = ctx.decrypt(enc_data)
self.assertIsNotNone(decrypted)
- self.assertEqual(self.load_xml("enc3-in.xml"), decrypted)
+ self.assertEqual(self.load_xml('enc3-in.xml'), decrypted)
def check_decrypt(self, i):
- root = self.load_xml('enc%d-out.xml' % i)
+ root = self.load_xml(f'enc{i}-out.xml')
enc_data = xmlsec.tree.find_child(root, consts.NodeEncryptedData, consts.EncNs)
self.assertIsNotNone(enc_data)
manager = xmlsec.KeysManager()
- manager.add_key(xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem))
+ manager.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
ctx = xmlsec.EncryptionContext(manager)
decrypted = ctx.decrypt(enc_data)
self.assertIsNotNone(decrypted)
- self.assertEqual(self.load_xml("enc%d-in.xml" % i), root)
-
-
- def check_no_segfault(self):
- namespaces = {
- 'soap': 'http://schemas.xmlsoap.org/soap/envelope/'
- }
+ self.assertEqual(self.load_xml(f'enc{i}-in.xml'), root)
- manager = xmlsec.KeysManager()
- key = xmlsec.Key.from_file(self.path("rsacert.pem"), format=consts.KeyDataFormatCertPem)
- manager.add_key(key)
- template = self.load_xml('enc-bad-in.xml')
- enc_data = xmlsec.template.encrypted_data_create(
- template, xmlsec.Transform.AES128, type=xmlsec.EncryptionType.CONTENT, ns='xenc')
- xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
- key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns='dsig')
- enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.Transform.RSA_PKCS1)
- xmlsec.template.encrypted_data_ensure_cipher_value(enc_key)
- data = template.find('soap:Body', namespaces=namespaces)
- enc_ctx = xmlsec.EncryptionContext(manager)
- enc_ctx.key = xmlsec.Key.generate(xmlsec.KeyData.AES, 192, xmlsec.KeyDataType.SESSION)
- self.assertRaises(Exception, enc_ctx.encrypt_xml(enc_data, data))
+ def test_decrypt_bad_args(self):
+ ctx = xmlsec.EncryptionContext()
+ with self.assertRaises(TypeError):
+ ctx.decrypt('')
diff --git a/tests/test_keys.py b/tests/test_keys.py
index add11e41..977ddf82 100644
--- a/tests/test_keys.py
+++ b/tests/test_keys.py
@@ -1,132 +1,218 @@
-from tests import base
-
import copy
+import tempfile
import xmlsec
-
+from tests import base
consts = xmlsec.constants
class TestKeys(base.TestMemoryLeaks):
def test_key_from_memory(self):
- key = xmlsec.Key.from_memory(self.load("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_memory(self.load('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
def test_key_from_memory_with_bad_args(self):
with self.assertRaises(TypeError):
- xmlsec.Key.from_memory(1, format="")
+ xmlsec.Key.from_memory(1, format='')
+
+ def test_key_from_memory_invalid_data(self):
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot load key.*'):
+ xmlsec.Key.from_memory(b'foo', format=consts.KeyDataFormatPem)
def test_key_from_file(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
def test_key_from_file_with_bad_args(self):
with self.assertRaises(TypeError):
- xmlsec.Key.from_file(1, format="")
+ xmlsec.Key.from_file(1, format='')
+
+ def test_key_from_invalid_file(self):
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot read key.*'), tempfile.NamedTemporaryFile() as tmpfile:
+ tmpfile.write(b'foo')
+ xmlsec.Key.from_file(tmpfile.name, format=consts.KeyDataFormatPem)
def test_key_from_fileobj(self):
- with open(self.path("rsakey.pem"), "rb") as fobj:
+ with open(self.path('rsakey.pem'), 'rb') as fobj:
key = xmlsec.Key.from_file(fobj, format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
+ def test_key_from_invalid_fileobj(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
+ tmpfile.write(b'foo')
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot read key.*'), open(tmpfile.name) as fp:
+ xmlsec.Key.from_file(fp, format=consts.KeyDataFormatPem)
+
def test_generate(self):
key = xmlsec.Key.generate(klass=consts.KeyDataAes, size=256, type=consts.KeyDataTypeSession)
self.assertIsNotNone(key)
def test_generate_with_bad_args(self):
with self.assertRaises(TypeError):
- xmlsec.Key.generate(klass="", size="", type="")
+ xmlsec.Key.generate(klass='', size='', type='')
+
+ def test_generate_invalid_size(self):
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot generate key.*'):
+ xmlsec.Key.generate(klass=consts.KeyDataAes, size=0, type=consts.KeyDataTypeSession)
def test_from_binary_file(self):
- key = xmlsec.Key.from_binary_file(klass=consts.KeyDataDes, filename=self.path("deskey.bin"))
+ key = xmlsec.Key.from_binary_file(klass=consts.KeyDataDes, filename=self.path('deskey.bin'))
self.assertIsNotNone(key)
def test_from_binary_file_with_bad_args(self):
with self.assertRaises(TypeError):
- xmlsec.Key.from_binary_file(klass="", filename=1)
+ xmlsec.Key.from_binary_file(klass='', filename=1)
+
+ def test_from_invalid_binary_file(self):
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot read key.*'), tempfile.NamedTemporaryFile() as tmpfile:
+ tmpfile.write(b'foo')
+ xmlsec.Key.from_binary_file(klass=consts.KeyDataDes, filename=tmpfile.name)
def test_from_binary_data(self):
- key = xmlsec.Key.from_binary_data(klass=consts.KeyDataDes, data=self.load("deskey.bin"))
+ key = xmlsec.Key.from_binary_data(klass=consts.KeyDataDes, data=self.load('deskey.bin'))
self.assertIsNotNone(key)
def test_from_binary_data_with_bad_args(self):
with self.assertRaises(TypeError):
- xmlsec.Key.from_binary_data(klass="", data=1)
+ xmlsec.Key.from_binary_data(klass='', data=1)
+
+ def test_from_invalid_binary_data(self):
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot read key.*'):
+ xmlsec.Key.from_binary_data(klass=consts.KeyDataDes, data=b'')
def test_load_cert_from_file(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
- key.load_cert_from_file(self.path("rsacert.pem"), format=consts.KeyDataFormatPem)
+ key.load_cert_from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatPem)
def test_load_cert_from_file_with_bad_args(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
with self.assertRaises(TypeError):
- key.load_cert_from_file(1, format="")
+ key.load_cert_from_file(1, format='')
+
+ def test_load_cert_from_invalid_file(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ self.assertIsNotNone(key)
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot load cert.*'), tempfile.NamedTemporaryFile() as tmpfile:
+ tmpfile.write(b'foo')
+ key.load_cert_from_file(tmpfile.name, format=consts.KeyDataFormatPem)
def test_load_cert_from_fileobj(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
- with open(self.path("rsacert.pem"), "rb") as fobj:
+ with open(self.path('rsacert.pem'), 'rb') as fobj:
key.load_cert_from_file(fobj, format=consts.KeyDataFormatPem)
+ def test_load_cert_from_fileobj_with_bad_args(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ self.assertIsNotNone(key)
+ with self.assertRaises(TypeError), open(self.path('rsacert.pem'), 'rb') as fobj:
+ key.load_cert_from_file(fobj, format='')
+
+ def test_load_cert_from_invalid_fileobj(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ self.assertIsNotNone(key)
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
+ tmpfile.write(b'foo')
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot load cert.*'), open(tmpfile.name) as fp:
+ key.load_cert_from_file(fp, format=consts.KeyDataFormatPem)
+
def test_load_cert_from_memory(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
- key.load_cert_from_memory(self.load("rsacert.pem"), format=consts.KeyDataFormatPem)
+ key.load_cert_from_memory(self.load('rsacert.pem'), format=consts.KeyDataFormatPem)
def test_load_cert_from_memory_with_bad_args(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
self.assertIsNotNone(key)
with self.assertRaises(TypeError):
- key.load_cert_from_memory(1, format="")
+ key.load_cert_from_memory(1, format='')
+
+ def test_load_cert_from_memory_invalid_data(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ self.assertIsNotNone(key)
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot load cert.*'):
+ key.load_cert_from_memory(b'', format=consts.KeyDataFormatPem)
+
+ def test_get_name(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ self.assertIsNone(key.name)
+
+ def test_get_name_invalid_key(self):
+ key = xmlsec.Key()
+ with self.assertRaisesRegex(ValueError, 'key is not ready'):
+ key.name # noqa: B018
- def test_name(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ def test_del_name(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ key.name = 'rsakey'
+ del key.name
self.assertIsNone(key.name)
- key.name = "rsakey"
- self.assertEqual("rsakey", key.name)
+
+ def test_set_name(self):
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ key.name = 'rsakey'
+ self.assertEqual('rsakey', key.name)
+
+ def test_set_name_invalid_key(self):
+ key = xmlsec.Key()
+ with self.assertRaisesRegex(ValueError, 'key is not ready'):
+ key.name = 'foo'
def test_copy(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
key2 = copy.copy(key)
del key
- key2.load_cert_from_file(self.path("rsacert.pem"), format=consts.KeyDataFormatPem)
+ key2.load_cert_from_file(self.path('rsacert.pem'), format=consts.KeyDataFormatPem)
class TestKeysManager(base.TestMemoryLeaks):
def test_add_key(self):
- key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem)
+ key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
mngr = xmlsec.KeysManager()
mngr.add_key(key)
def test_add_key_with_bad_args(self):
mngr = xmlsec.KeysManager()
with self.assertRaises(TypeError):
- mngr.add_key("")
+ mngr.add_key('')
def test_load_cert(self):
mngr = xmlsec.KeysManager()
- mngr.add_key(xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem))
- mngr.load_cert(self.path("rsacert.pem"), format=consts.KeyDataFormatPem, type=consts.KeyDataTypeTrusted)
+ mngr.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
+ mngr.load_cert(self.path('rsacert.pem'), format=consts.KeyDataFormatPem, type=consts.KeyDataTypeTrusted)
def test_load_cert_with_bad_args(self):
mngr = xmlsec.KeysManager()
- mngr.add_key(xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem))
+ mngr.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot load cert.*'), tempfile.NamedTemporaryFile() as tmpfile:
+ tmpfile.write(b'foo')
+ mngr.load_cert(tmpfile.name, format=consts.KeyDataFormatPem, type=consts.KeyDataTypeTrusted)
+
+ def test_load_invalid_cert(self):
+ mngr = xmlsec.KeysManager()
+ mngr.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
with self.assertRaises(TypeError):
- mngr.load_cert(1, format="", type="")
+ mngr.load_cert(1, format='', type='')
def test_load_cert_from_memory(self):
mngr = xmlsec.KeysManager()
- mngr.add_key(xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem))
- mngr.load_cert_from_memory(self.load("rsacert.pem"), format=consts.KeyDataFormatPem, type=consts.KeyDataTypeTrusted)
+ mngr.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
+ mngr.load_cert_from_memory(self.load('rsacert.pem'), format=consts.KeyDataFormatPem, type=consts.KeyDataTypeTrusted)
def test_load_cert_from_memory_with_bad_args(self):
mngr = xmlsec.KeysManager()
- mngr.add_key(xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem))
+ mngr.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
with self.assertRaises(TypeError):
- mngr.load_cert_from_memory(1, format="", type="")
+ mngr.load_cert_from_memory(1, format='', type='')
+
+ def test_load_cert_from_memory_invalid_data(self):
+ mngr = xmlsec.KeysManager()
+ mngr.add_key(xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem))
+ with self.assertRaisesRegex(xmlsec.Error, '.*cannot load cert.*'):
+ mngr.load_cert_from_memory(b'', format=consts.KeyDataFormatPem, type=consts.KeyDataTypeTrusted)
def test_load_invalid_key(self):
mngr = xmlsec.KeysManager()
diff --git a/tests/test_main.py b/tests/test_main.py
new file mode 100644
index 00000000..8f1501f2
--- /dev/null
+++ b/tests/test_main.py
@@ -0,0 +1,160 @@
+import sys
+from io import BytesIO
+from unittest import skipIf
+
+import xmlsec
+from tests import base
+from xmlsec import constants as consts
+
+
+class TestBase64LineSize(base.TestMemoryLeaks):
+ def tearDown(self):
+ xmlsec.base64_default_line_size(64)
+ super().tearDown()
+
+ def test_get_base64_default_line_size(self):
+ self.assertEqual(xmlsec.base64_default_line_size(), 64)
+
+ def test_set_base64_default_line_size_positional_arg(self):
+ xmlsec.base64_default_line_size(0)
+ self.assertEqual(xmlsec.base64_default_line_size(), 0)
+
+ def test_set_base64_default_line_size_keyword_arg(self):
+ xmlsec.base64_default_line_size(size=0)
+ self.assertEqual(xmlsec.base64_default_line_size(), 0)
+
+ def test_set_base64_default_line_size_with_bad_args(self):
+ size = xmlsec.base64_default_line_size()
+ for bad_size in (None, '', object()):
+ with self.assertRaises(TypeError):
+ xmlsec.base64_default_line_size(bad_size)
+ self.assertEqual(xmlsec.base64_default_line_size(), size)
+
+ def test_set_base64_default_line_size_rejects_negative_values(self):
+ size = xmlsec.base64_default_line_size()
+ with self.assertRaises(ValueError):
+ xmlsec.base64_default_line_size(-1)
+ self.assertEqual(xmlsec.base64_default_line_size(), size)
+
+
+class TestCallbacks(base.TestMemoryLeaks):
+ def setUp(self):
+ super().setUp()
+ xmlsec.cleanup_callbacks()
+
+ def _sign_doc(self):
+ root = self.load_xml('doc.xml')
+ sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
+ xmlsec.template.add_reference(sign, consts.TransformSha1, uri='cid:123456')
+
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem)
+ ctx.sign(sign)
+ return sign
+
+ def _expect_sign_failure(self):
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to sign'):
+ self._sign_doc()
+
+ def _mismatch_callbacks(self, match_cb=lambda filename: False):
+ return [
+ match_cb,
+ lambda filename: None,
+ lambda none, buf: 0,
+ lambda none: None,
+ ]
+
+ def _register_mismatch_callbacks(self, match_cb=lambda filename: False):
+ xmlsec.register_callbacks(*self._mismatch_callbacks(match_cb))
+
+ def _register_match_callbacks(self):
+ xmlsec.register_callbacks(
+ lambda filename: filename == b'cid:123456',
+ lambda filename: BytesIO(b''),
+ lambda bio, buf: bio.readinto(buf),
+ lambda bio: bio.close(),
+ )
+
+ def _find(self, elem, *tags):
+ try:
+ return elem.xpath(
+ './' + '/'.join(f'xmldsig:{tag}' for tag in tags),
+ namespaces={
+ 'xmldsig': 'http://www.w3.org/2000/09/xmldsig#',
+ },
+ )[0]
+ except IndexError as e:
+ raise KeyError(tags) from e
+
+ def _verify_external_data_signature(self):
+ signature = self._sign_doc()
+ digest = self._find(signature, 'SignedInfo', 'Reference', 'DigestValue').text
+ self.assertEqual(digest, 'VihZwVMGJ48NsNl7ertVHiURXk8=')
+
+ def test_sign_external_data_no_callbacks_fails(self):
+ self._expect_sign_failure()
+
+ def test_sign_external_data_default_callbacks_fails(self):
+ xmlsec.register_default_callbacks()
+ self._expect_sign_failure()
+
+ def test_sign_external_data_no_matching_callbacks_fails(self):
+ self._register_mismatch_callbacks()
+ self._expect_sign_failure()
+
+ def test_sign_data_from_callbacks(self):
+ self._register_match_callbacks()
+ self._verify_external_data_signature()
+
+ def test_sign_data_not_first_callback(self):
+ bad_match_calls = 0
+
+ def match_cb(filename):
+ nonlocal bad_match_calls
+ bad_match_calls += 1
+ return False
+
+ for _ in range(2):
+ self._register_mismatch_callbacks(match_cb)
+
+ self._register_match_callbacks()
+
+ for _ in range(2):
+ self._register_mismatch_callbacks()
+
+ self._verify_external_data_signature()
+ self.assertEqual(bad_match_calls, 0)
+
+ @skipIf(sys.platform == 'win32', 'unclear behaviour on windows')
+ def test_failed_sign_because_default_callbacks(self):
+ mismatch_calls = 0
+
+ def mismatch_cb(filename):
+ nonlocal mismatch_calls
+ mismatch_calls += 1
+ return False
+
+ # NB: These first two sets of callbacks should never get called,
+ # because the default callbacks always match beforehand:
+ self._register_match_callbacks()
+ self._register_mismatch_callbacks(mismatch_cb)
+ xmlsec.register_default_callbacks()
+ self._register_mismatch_callbacks(mismatch_cb)
+ self._register_mismatch_callbacks(mismatch_cb)
+ self._expect_sign_failure()
+ self.assertEqual(mismatch_calls, 2)
+
+ def test_register_non_callables(self):
+ for idx in range(4):
+ cbs = self._mismatch_callbacks()
+ cbs[idx] = None
+ self.assertRaises(TypeError, xmlsec.register_callbacks, *cbs)
+
+ def test_sign_external_data_fails_on_read_callback_wrong_returns(self):
+ xmlsec.register_callbacks(
+ lambda filename: filename == b'cid:123456',
+ lambda filename: BytesIO(b''),
+ lambda bio, buf: None,
+ lambda bio: bio.close(),
+ )
+ self._expect_sign_failure()
diff --git a/tests/test_pkcs11.py b/tests/test_pkcs11.py
new file mode 100644
index 00000000..cba1a3f0
--- /dev/null
+++ b/tests/test_pkcs11.py
@@ -0,0 +1,57 @@
+import xmlsec
+from tests import base
+from xmlsec import constants as consts
+
+KEY_URL = 'pkcs11;pkcs11:token=test;object=test;pin-value=secret1'
+
+
+def setUpModule():
+ from tests import softhsm_setup
+
+ softhsm_setup.setup()
+
+
+def tearDownModule():
+ from tests import softhsm_setup
+
+ softhsm_setup.teardown()
+
+
+class TestKeys(base.TestMemoryLeaks):
+ def test_del_key(self):
+ ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
+ ctx.key = xmlsec.Key.from_engine(KEY_URL)
+ del ctx.key
+ self.assertIsNone(ctx.key)
+
+ def test_set_key(self):
+ ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager())
+ ctx.key = xmlsec.Key.from_engine(KEY_URL)
+ self.assertIsNotNone(ctx.key)
+
+ def test_sign_bad_args(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_engine(KEY_URL)
+ with self.assertRaises(TypeError):
+ ctx.sign('')
+
+ def test_sign_fail(self):
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_engine(KEY_URL)
+ with self.assertRaisesRegex(xmlsec.Error, 'failed to sign'):
+ ctx.sign(self.load_xml('sign1-in.xml'))
+
+ def test_sign_case1(self):
+ """Should sign a pre-constructed template file using a key from a pkcs11 engine."""
+ root = self.load_xml('sign1-in.xml')
+ sign = xmlsec.tree.find_node(root, consts.NodeSignature)
+ self.assertIsNotNone(sign)
+
+ ctx = xmlsec.SignatureContext()
+ ctx.key = xmlsec.Key.from_engine(KEY_URL)
+ self.assertIsNotNone(ctx.key)
+ ctx.key.name = 'rsakey.pem'
+ self.assertEqual('rsakey.pem', ctx.key.name)
+
+ ctx.sign(sign)
+ self.assertEqual(self.load_xml('sign1-out.xml'), root)
diff --git a/tests/test_templates.py b/tests/test_templates.py
index dcd8d940..bbf7f42d 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -1,86 +1,116 @@
-from tests import base
+import unittest
-import xmlsec
+from lxml import etree
+import xmlsec
+from tests import base
consts = xmlsec.constants
class TestTemplates(base.TestMemoryLeaks):
def test_create(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(
- root,
- c14n_method=consts.TransformExclC14N,
- sign_method=consts.TransformRsaSha1,
- id="Id",
- ns="test"
+ root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1, id='Id', ns='test'
)
- self.assertEqual("Id", sign.get("Id"))
- self.assertEqual("test", sign.prefix)
+ self.assertEqual('Id', sign.get('Id'))
+ self.assertEqual('test', sign.prefix)
+
+ def test_create_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.create('', c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
def test_encrypt_data_create(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
enc = xmlsec.template.encrypted_data_create(
- root, method=consts.TransformDes3Cbc, id="Id", type="Type", mime_type="MimeType", encoding="Encoding",
- ns="test"
+ root, method=consts.TransformDes3Cbc, id='Id', type='Type', mime_type='MimeType', encoding='Encoding', ns='test'
)
- for a in ("Id", "Type", "MimeType", "Encoding"):
+ for a in ('Id', 'Type', 'MimeType', 'Encoding'):
self.assertEqual(a, enc.get(a))
- self.assertEqual("test", enc.prefix)
+ self.assertEqual('test', enc.prefix)
def test_ensure_key_info(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
- ki = xmlsec.template.ensure_key_info(sign, id="Id")
- self.assertEqual("Id", ki.get("Id"))
+ ki = xmlsec.template.ensure_key_info(sign, id='Id')
+ self.assertEqual('Id', ki.get('Id'))
+
+ def test_ensure_key_info_fail(self):
+ with self.assertRaisesRegex(xmlsec.Error, 'cannot ensure key info.'):
+ xmlsec.template.ensure_key_info(etree.fromstring(b''), id='Id')
+
+ def test_ensure_key_info_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.ensure_key_info('', id=0)
def test_add_encrypted_key(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
ki = xmlsec.template.ensure_key_info(sign)
ek = xmlsec.template.add_encrypted_key(ki, consts.TransformRsaOaep)
- self.assertEqual(
- ek,
- xmlsec.tree.find_node(self.load_xml("sign_template.xml"), consts.NodeEncryptedKey, consts.EncNs)
- )
- ek2 = xmlsec.template.add_encrypted_key(
- ki, consts.TransformRsaOaep, id="Id", type="Type", recipient="Recipient"
- )
- for a in ("Id", "Type", "Recipient"):
+ self.assertEqual(ek, xmlsec.tree.find_node(self.load_xml('sign_template.xml'), consts.NodeEncryptedKey, consts.EncNs))
+ ek2 = xmlsec.template.add_encrypted_key(ki, consts.TransformRsaOaep, id='Id', type='Type', recipient='Recipient')
+ for a in ('Id', 'Type', 'Recipient'):
self.assertEqual(a, ek2.get(a))
def test_add_key_name(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
ki = xmlsec.template.ensure_key_info(sign)
kn = xmlsec.template.add_key_name(ki)
- self.assertEqual(
- kn,
- xmlsec.tree.find_node(self.load_xml("sign_template.xml"), consts.NodeKeyName, consts.DSigNs)
- )
- kn2 = xmlsec.template.add_key_name(ki, name="name")
- self.assertEqual("name", kn2.text)
+ self.assertEqual(kn, xmlsec.tree.find_node(self.load_xml('sign_template.xml'), consts.NodeKeyName, consts.DSigNs))
+ kn2 = xmlsec.template.add_key_name(ki, name='name')
+ self.assertEqual('name', kn2.text)
+
+ def test_add_key_name_none(self):
+ root = self.load_xml('doc.xml')
+ sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
+ ki = xmlsec.template.ensure_key_info(sign)
+ kn2 = xmlsec.template.add_key_name(ki, name=None)
+ self.assertEqual(kn2.text, None)
+ print(etree.tostring(kn2))
+
+ def test_add_key_name_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_key_name('')
def test_add_reference(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
- ref = xmlsec.template.add_reference(sign, consts.TransformSha1, id="Id", uri="URI", type="Type")
- for a in ("Id", "URI", "Type"):
+ ref = xmlsec.template.add_reference(sign, consts.TransformSha1, id='Id', uri='URI', type='Type')
+ for a in ('Id', 'URI', 'Type'):
self.assertEqual(a, ref.get(a))
+ def test_add_reference_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_reference('', consts.TransformSha1)
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_reference(etree.Element('root'), '')
+
+ def test_add_reference_fail(self):
+ with self.assertRaisesRegex(xmlsec.Error, 'cannot add reference.'):
+ xmlsec.template.add_reference(etree.Element('root'), consts.TransformSha1)
+
+ def test_add_transform_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_transform('', consts.TransformSha1)
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_transform(etree.Element('root'), '')
+
def test_add_key_value(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
ki = xmlsec.template.ensure_key_info(sign)
kv = xmlsec.template.add_key_value(ki)
- self.assertEqual(
- kv,
- xmlsec.tree.find_node(self.load_xml("sign_template.xml"), consts.NodeKeyValue, consts.DSigNs)
- )
+ self.assertEqual(kv, xmlsec.tree.find_node(self.load_xml('sign_template.xml'), consts.NodeKeyValue, consts.DSigNs))
+
+ def test_add_key_value_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_key_value('')
def test_add_x509_data(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
ki = xmlsec.template.ensure_key_info(sign)
x509 = xmlsec.template.add_x509_data(ki)
@@ -91,52 +121,93 @@ def test_add_x509_data(self):
xmlsec.template.x509_data_add_subject_name(x509)
xmlsec.template.x509_issuer_serial_add_issuer_name(issuer)
xmlsec.template.x509_issuer_serial_add_serial_number(issuer)
- self.assertEqual(
- x509,
- xmlsec.tree.find_node(self.load_xml("sign_template.xml"), consts.NodeX509Data, consts.DSigNs)
- )
+ self.assertEqual(x509, xmlsec.tree.find_node(self.load_xml('sign_template.xml'), consts.NodeX509Data, consts.DSigNs))
+
+ def test_add_x509_data_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_x509_data('')
def test_x509_issuer_serial_add_issuer(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
ki = xmlsec.template.ensure_key_info(sign)
x509 = xmlsec.template.add_x509_data(ki)
issuer = xmlsec.template.x509_data_add_issuer_serial(x509)
- name = xmlsec.template.x509_issuer_serial_add_issuer_name(issuer, name="Name")
- serial = xmlsec.template.x509_issuer_serial_add_serial_number(issuer, serial="Serial")
- self.assertEqual("Name", name.text)
- self.assertEqual("Serial", serial.text)
+ name = xmlsec.template.x509_issuer_serial_add_issuer_name(issuer, name='Name')
+ serial = xmlsec.template.x509_issuer_serial_add_serial_number(issuer, serial='Serial')
+ self.assertEqual('Name', name.text)
+ self.assertEqual('Serial', serial.text)
+
+ def test_x509_issuer_serial_add_issuer_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_data_add_issuer_serial('')
+
+ def test_x509_issuer_serial_add_issuer_name_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_issuer_serial_add_issuer_name('')
+
+ def test_x509_issuer_serial_add_serial_number_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_issuer_serial_add_serial_number('')
+
+ def test_x509_data_add_subject_name_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_data_add_subject_name('')
+
+ def test_x509_data_add_ski_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_data_add_ski('')
+
+ def test_x509_data_add_certificate_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_data_add_certificate('')
+
+ def test_x509_data_add_crl_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.x509_data_add_crl('')
+
+ def test_add_encrypted_key_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.add_encrypted_key('', 0)
+
+ def test_encrypted_data_create_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.encrypted_data_create('', 0)
def test_encrypted_data_ensure_cipher_value(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
enc = xmlsec.template.encrypted_data_create(root, method=consts.TransformDes3Cbc)
cv = xmlsec.template.encrypted_data_ensure_cipher_value(enc)
- self.assertEqual(
- cv,
- xmlsec.tree.find_node(self.load_xml("sign_template.xml"), consts.NodeCipherValue, consts.EncNs)
- )
+ self.assertEqual(cv, xmlsec.tree.find_node(self.load_xml('sign_template.xml'), consts.NodeCipherValue, consts.EncNs))
+
+ def test_encrypted_data_ensure_cipher_value_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.encrypted_data_ensure_cipher_value('')
def test_encrypted_data_ensure_key_info(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
enc = xmlsec.template.encrypted_data_create(root, method=consts.TransformDes3Cbc)
ki = xmlsec.template.encrypted_data_ensure_key_info(enc)
- self.assertEqual(
- ki,
- xmlsec.tree.find_node(self.load_xml("enc_template.xml"), consts.NodeKeyInfo, consts.DSigNs)
- )
- ki2 = xmlsec.template.encrypted_data_ensure_key_info(enc, id="Id", ns="test")
- self.assertEqual("Id", ki2.get("Id"))
- self.assertEqual("test", ki2.prefix)
+ self.assertEqual(ki, xmlsec.tree.find_node(self.load_xml('enc_template.xml'), consts.NodeKeyInfo, consts.DSigNs))
+ ki2 = xmlsec.template.encrypted_data_ensure_key_info(enc, id='Id', ns='test')
+ self.assertEqual('Id', ki2.get('Id'))
+ self.assertEqual('test', ki2.prefix)
+
+ def test_encrypted_data_ensure_key_info_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.encrypted_data_ensure_key_info('')
+ @unittest.skipIf(not hasattr(consts, 'TransformXslt'), reason='XSLT transformations not enabled')
def test_transform_add_c14n_inclusive_namespaces(self):
- root = self.load_xml("doc.xml")
+ root = self.load_xml('doc.xml')
sign = xmlsec.template.create(root, c14n_method=consts.TransformExclC14N, sign_method=consts.TransformRsaSha1)
ref = xmlsec.template.add_reference(sign, consts.TransformSha1)
trans1 = xmlsec.template.add_transform(ref, consts.TransformEnveloped)
- xmlsec.template.transform_add_c14n_inclusive_namespaces(trans1, "default")
+ xmlsec.template.transform_add_c14n_inclusive_namespaces(trans1, 'default')
trans2 = xmlsec.template.add_transform(ref, consts.TransformXslt)
- xmlsec.template.transform_add_c14n_inclusive_namespaces(trans2, ["ns1", "ns2"])
- self.assertEqual(
- ref,
- xmlsec.tree.find_node(self.load_xml("sign_template.xml"), consts.NodeReference, consts.DSigNs)
- )
+ xmlsec.template.transform_add_c14n_inclusive_namespaces(trans2, ['ns1', 'ns2'])
+ self.assertEqual(ref, xmlsec.tree.find_node(self.load_xml('sign_template.xml'), consts.NodeReference, consts.DSigNs))
+
+ def test_transform_add_c14n_inclusive_namespaces_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.template.transform_add_c14n_inclusive_namespaces('', [])
diff --git a/tests/test_tree.py b/tests/test_tree.py
index f07c654c..5e80a60a 100644
--- a/tests/test_tree.py
+++ b/tests/test_tree.py
@@ -1,31 +1,45 @@
-from tests import base
-
import xmlsec
-
+from tests import base
consts = xmlsec.constants
class TestTree(base.TestMemoryLeaks):
def test_find_child(self):
- root = self.load_xml("sign_template.xml")
+ root = self.load_xml('sign_template.xml')
si = xmlsec.tree.find_child(root, consts.NodeSignedInfo, consts.DSigNs)
self.assertEqual(consts.NodeSignedInfo, si.tag.partition('}')[2])
self.assertIsNone(xmlsec.tree.find_child(root, consts.NodeReference))
self.assertIsNone(xmlsec.tree.find_child(root, consts.NodeSignedInfo, consts.EncNs))
+ def test_find_child_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.tree.find_child('', 0, True)
+
def test_find_parent(self):
- root = self.load_xml("sign_template.xml")
+ root = self.load_xml('sign_template.xml')
si = xmlsec.tree.find_child(root, consts.NodeSignedInfo, consts.DSigNs)
self.assertIs(root, xmlsec.tree.find_parent(si, consts.NodeSignature))
self.assertIsNone(xmlsec.tree.find_parent(root, consts.NodeSignedInfo))
+ def test_find_parent_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.tree.find_parent('', 0, True)
+
def test_find_node(self):
- root = self.load_xml("sign_template.xml")
+ root = self.load_xml('sign_template.xml')
ref = xmlsec.tree.find_node(root, consts.NodeReference)
self.assertEqual(consts.NodeReference, ref.tag.partition('}')[2])
self.assertIsNone(xmlsec.tree.find_node(root, consts.NodeReference, consts.EncNs))
+ def test_find_node_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.tree.find_node('', 0, True)
+
def test_add_ids(self):
- root = self.load_xml("sign_template.xml")
- xmlsec.tree.add_ids(root, ["id1", "id2", "id3"])
+ root = self.load_xml('sign_template.xml')
+ xmlsec.tree.add_ids(root, ['id1', 'id2', 'id3'])
+
+ def test_add_ids_bad_args(self):
+ with self.assertRaises(TypeError):
+ xmlsec.tree.add_ids('', [])
diff --git a/tests/test_type_stubs.py b/tests/test_type_stubs.py
index 3b1c3757..82f7df7f 100644
--- a/tests/test_type_stubs.py
+++ b/tests/test_type_stubs.py
@@ -1,7 +1,6 @@
"""Test type stubs for correctness where possible."""
import os
-import sys
import pytest
@@ -10,12 +9,6 @@
black = pytest.importorskip('black')
-if sys.version_info >= (3, 4):
- from pathlib import Path
-else:
- from _pytest.pathlib import Path
-
-
constants_stub_header = """
import sys
from typing import NamedTuple
@@ -25,24 +18,29 @@
else:
from typing_extensions import Final
-
class __KeyData(NamedTuple): # __KeyData type
href: str
name: str
+class __KeyDataNoHref(NamedTuple): # __KeyData type
+ href: None
+ name: str
class __Transform(NamedTuple): # __Transform type
href: str
name: str
usage: int
+class __TransformNoHref(NamedTuple): # __Transform type
+ href: None
+ name: str
+ usage: int
"""
def gen_constants_stub():
- """
- Generate contents of the file:`xmlsec/constants.pyi`.
+ """Generate contents of the file:`xmlsec/constants.pyi`.
Simply load all constants at runtime,
generate appropriate type hint for each constant type.
@@ -51,7 +49,10 @@ def gen_constants_stub():
def process_constant(name):
"""Generate line in stub file for constant name."""
obj = getattr(xmlsec.constants, name)
- return '{name}: Final = {obj!r}'.format(name=name, obj=obj)
+ type_name = type(obj).__name__
+ if type_name in ('__KeyData', '__Transform') and obj.href is None:
+ type_name += 'NoHref'
+ return f'{name}: Final[{type_name}]'
names = list(sorted(name for name in dir(xmlsec.constants) if not name.startswith('__')))
lines = [process_constant(name) for name in names]
@@ -59,13 +60,11 @@ def process_constant(name):
def test_xmlsec_constants_stub(request):
- """
- Generate the stub file for :mod:`xmlsec.constants` from existing code.
+ """Generate the stub file for :mod:`xmlsec.constants` from existing code.
Compare it against the existing stub :file:`xmlsec/constants.pyi`.
"""
- rootdir = Path(str(request.config.rootdir))
- stub = rootdir / 'src' / 'xmlsec' / 'constants.pyi'
- mode = black.FileMode(target_versions=[black.TargetVersion.PY38], line_length=130, is_pyi=True, string_normalization=False)
+ stub = request.config.rootpath / 'src' / 'xmlsec' / 'constants.pyi'
+ mode = black.FileMode(target_versions={black.TargetVersion.PY39}, line_length=130, is_pyi=True, string_normalization=False)
formatted = black.format_file_contents(gen_constants_stub(), fast=False, mode=mode)
assert formatted == stub.read_text()
diff --git a/tests/test_xmlsec.py b/tests/test_xmlsec.py
new file mode 100644
index 00000000..52dce2b3
--- /dev/null
+++ b/tests/test_xmlsec.py
@@ -0,0 +1,13 @@
+import xmlsec
+from tests import base
+
+
+class TestModule(base.TestMemoryLeaks):
+ def test_reinitialize_module(self):
+ """This test doesn't explicitly verify anything, but will be invoked first in the suite.
+
+ So if the subsequent tests don't fail, we know that the ``init()``/``shutdown()``
+ function pair doesn't break anything.
+ """
+ xmlsec.shutdown()
+ xmlsec.init()
diff --git a/typeshed/lxml/etree.pyi b/typeshed/lxml/etree.pyi
deleted file mode 100644
index 9420180f..00000000
--- a/typeshed/lxml/etree.pyi
+++ /dev/null
@@ -1,6 +0,0 @@
-from typing import Any
-
-def __getattr__(name: str) -> Any: ... # incomplete
-
-class _Element:
- def __getattr__(self, name: str) -> Any: ... # incomplete
diff --git a/xmlsec_extra.py b/xmlsec_extra.py
deleted file mode 100644
index 9bf6c86a..00000000
--- a/xmlsec_extra.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import os
-import sys
-
-try:
- from urlparse import urljoin
- from urllib import urlretrieve, urlcleanup
-except ImportError:
- from urllib.parse import urljoin
- from urllib.request import urlretrieve, urlcleanup
-
-
-# use pre-built libraries on Windows
-def get_prebuilt_libs(download_dir, static_include_dirs, static_library_dirs):
- assert sys.platform.startswith('win')
- libs = download_and_extract_windows_binaries(download_dir)
- for ln, path in libs.items():
- if ln == 'xmlsec1':
- i = os.path.join(path, 'include', 'xmlsec1')
- else:
- i = os.path.join(path, 'include')
-
- l = os.path.join(path, 'lib')
- assert os.path.exists(i), 'does not exist: %s' % i
- assert os.path.exists(l), 'does not exist: %s' % l
- static_include_dirs.append(i)
- static_library_dirs.append(l)
-
-
-def download_and_extract_windows_binaries(destdir):
- url = "https://github.com/bgaifullin/libxml2-win-binaries/releases/download/v2018.08/"
- if sys.version_info < (3, 5):
- if sys.maxsize > 2147483647:
- suffix = "vs2008.win64"
- else:
- suffix = "vs2008.win32"
- else:
- if sys.maxsize > 2147483647:
- suffix = "win64"
- else:
- suffix = "win32"
-
- libs = {
- 'libxml2': 'libxml2-2.9.4.{}.zip'.format(suffix),
- 'libxslt': 'libxslt-1.1.29.{}.zip'.format(suffix),
- 'zlib': 'zlib-1.2.8.{}.zip'.format(suffix),
- 'iconv': 'iconv-1.14.{}.zip'.format(suffix),
- 'openssl': 'openssl-1.0.1.{}.zip'.format(suffix),
- 'xmlsec': 'xmlsec-1.2.24.{}.zip'.format(suffix),
- }
-
- if not os.path.exists(destdir):
- os.makedirs(destdir)
-
- for ln, fn in libs.items():
- srcfile = urljoin(url, fn)
- destfile = os.path.join(destdir, fn)
- if os.path.exists(destfile + ".keep"):
- print('Using local copy of "{}"'.format(srcfile))
- else:
- print('Retrieving "%s" to "%s"' % (srcfile, destfile))
- urlcleanup() # work around FTP bug 27973 in Py2.7.12+
- urlretrieve(srcfile, destfile)
-
- libs[ln] = unpack_zipfile(destfile, destdir)
-
- return libs
-
-
-def find_top_dir_of_zipfile(zipfile):
- topdir = None
- files = [f.filename for f in zipfile.filelist]
- dirs = [d for d in files if d.endswith('/')]
- if dirs:
- dirs.sort(key=len)
- topdir = dirs[0]
- topdir = topdir[:topdir.index("/")+1]
- for path in files:
- if not path.startswith(topdir):
- topdir = None
- break
- assert topdir, (
- "cannot determine single top-level directory in zip file %s" %
- zipfile.filename)
- return topdir.rstrip('/')
-
-
-def unpack_zipfile(zipfn, destdir):
- assert zipfn.endswith('.zip')
- import zipfile
- print('Unpacking %s into %s' % (os.path.basename(zipfn), destdir))
- f = zipfile.ZipFile(zipfn)
- try:
- extracted_dir = os.path.join(destdir, find_top_dir_of_zipfile(f))
- f.extractall(path=destdir)
- finally:
- f.close()
- assert os.path.exists(extracted_dir), 'missing: %s' % extracted_dir
- return extracted_dir
diff --git a/xmlsec_setupinfo.py b/xmlsec_setupinfo.py
deleted file mode 100644
index bcfbd321..00000000
--- a/xmlsec_setupinfo.py
+++ /dev/null
@@ -1,258 +0,0 @@
-from __future__ import print_function
-
-import glob
-import os
-import pkg_resources
-import sys
-
-from distutils.errors import DistutilsOptionError
-
-
-WIN32 = sys.platform.lower().startswith('win')
-
-__MODULE_NAME = "xmlsec"
-__MODULE_VERSION = None
-__MODULE_DESCRIPTION = "Python bindings for the XML Security Library"
-__MODULE_REQUIREMENTS = None
-__XMLSEC_CONFIG = None
-
-
-def name():
- return __MODULE_NAME
-
-
-def version():
- global __MODULE_VERSION
- if __MODULE_VERSION is None:
- with open(os.path.join(get_base_dir(), 'version.txt')) as f:
- __MODULE_VERSION = f.read().strip()
- return __MODULE_VERSION
-
-
-def description():
- return __MODULE_DESCRIPTION
-
-
-def sources():
- return sorted(glob.glob(os.path.join(get_base_dir(), "src", "*.c")))
-
-
-def define_macros():
- macros = [
- ("MODULE_NAME", __MODULE_NAME),
- ("MODULE_VERSION", version()),
- ]
- if OPTION_ENABLE_DEBUG:
- macros.append(("PYXMLSEC_ENABLE_DEBUG", "1"))
-
- macros.extend(xmlsec_config()['define_macros'])
-
- return macros
-
-
-def cflags():
- options = []
- if WIN32:
- options.append("/Zi")
- else:
- options.append("-g")
- options.append("-std=c99")
- options.append("-fno-strict-aliasing")
- options.append("-Wno-error=declaration-after-statement")
- options.append("-Werror=implicit-function-declaration")
-
- if OPTION_ENABLE_DEBUG:
- options.append("-Wall")
- options.append("-O0")
- else:
- options.append("-Os")
-
- return options
-
-
-def include_dirs():
- import lxml
-
- dirs = xmlsec_config()['include_dirs']
- dirs.extend(lxml.get_include())
- return dirs
-
-
-def libraries():
- return xmlsec_config()['libraries']
-
-
-def library_dirs():
- return xmlsec_config()['library_dirs']
-
-
-def dev_status():
- _version = version()
- if 'a' in _version:
- return 'Development Status :: 3 - Alpha'
- elif 'b' in _version or 'c' in _version:
- return 'Development Status :: 4 - Beta'
- else:
- return 'Development Status :: 5 - Production/Stable'
-
-
-def requirements():
- global __MODULE_REQUIREMENTS
- if __MODULE_REQUIREMENTS is None:
- with open(os.path.join(get_base_dir(), "requirements.txt")) as f:
- __MODULE_REQUIREMENTS = [str(req) for req in pkg_resources.parse_requirements(f)]
- return __MODULE_REQUIREMENTS
-
-
-def xmlsec_config():
- global __XMLSEC_CONFIG
-
- if __XMLSEC_CONFIG is None:
- __XMLSEC_CONFIG = load_xmlsec1_config()
-
- return __XMLSEC_CONFIG
-
-
-def load_xmlsec1_config():
- config = None
-
- if WIN32:
- import xmlsec_extra
-
- config = {
- 'define_macros': [
- ('XMLSEC_CRYPTO', '\\"openssl\\"'),
- ('__XMLSEC_FUNCTION__', '__FUNCTION__'),
- ('XMLSEC_NO_GOST', '1'),
- ('XMLSEC_NO_XKMS', '1'),
- ('XMLSEC_NO_CRYPTO_DYNAMIC_LOADING', '1'),
- ('XMLSEC_CRYPTO_OPENSSL', '1'),
- ('UNICODE', '1'),
- ('_UNICODE', '1'),
- ('LIBXML_ICONV_ENABLED', 1),
- ('LIBXML_STATIC', '1'),
- ('LIBXSLT_STATIC', '1'),
- ('XMLSEC_STATIC', '1'),
- ('inline', '__inline'),
- ],
- 'libraries': [
- 'libxmlsec_a',
- 'libxmlsec-openssl_a',
- 'libeay32',
- 'iconv_a',
- 'libxslt_a',
- 'libexslt_a',
- 'libxml2_a',
- 'zlib',
- 'WS2_32',
- 'Advapi32',
- 'User32',
- 'Gdi32',
- 'Crypt32',
- ],
- 'include_dirs': [],
- 'library_dirs': [],
- }
-
- xmlsec_extra.get_prebuilt_libs(
- OPTION_DOWNLOAD_DIR, config['include_dirs'], config['library_dirs']
- )
- else:
- import pkgconfig
-
- try:
- config = pkgconfig.parse('xmlsec1')
- except EnvironmentError:
- pass
-
- if config is None or not config.get('libraries'):
- fatal_xmlsec1_error()
-
- # make sure that all options are list
- for x in ('libraries', 'include_dirs', 'library_dirs'):
- config[x] = list(config.get(x) or [])
-
- # fix macros, ensure that macros is list
- macros = list(config.get('define_macros', []))
- for i, v in enumerate(macros):
- if v[0] == 'XMLSEC_CRYPTO' and not (v[1].startswith('"') and v[1].endswith('"')):
- macros[i] = ('XMLSEC_CRYPTO', '"{0}"'.format(v[1]))
- break
- config['define_macros'] = macros
- return config
-
-
-def fatal_xmlsec1_error():
- print('*********************************************************************************')
- print('Could not find xmlsec1 config. Are libxmlsec1-dev and pkg-config installed?')
- if sys.platform in ('darwin',):
- print('Perhaps try: xcode-select --install')
- print('*********************************************************************************')
- sys.exit(1)
-
-
-def get_base_dir():
- return os.path.abspath(os.path.dirname(sys.argv[0]))
-
-
-if sys.version_info[0] >= 3:
- _system_encoding = sys.getdefaultencoding()
- if _system_encoding is None:
- _system_encoding = "iso-8859-1"
-
- def decode_input(data):
- if isinstance(data, str):
- return data
- return data.decode(_system_encoding)
-else:
- def decode_input(data):
- return data
-
-
-def env_var(n):
- value = os.getenv(n)
- if value:
- value = decode_input(value)
- if sys.platform == 'win32' and ';' in value:
- return value.split(';')
- else:
- return value.split()
- else:
- return []
-
-
-def env_var_name(n):
- return "PYXMLSEC_" + n.upper().replace('-', '_')
-
-
-# Option handling:
-
-def has_option(n):
- try:
- sys.argv.remove('--%s' % n)
- return True
- except ValueError:
- pass
- # allow passing all cmd line options also as environment variables
- env_val = os.getenv(env_var_name(n), 'false').lower()
- return env_val in ("true", "1")
-
-
-def option_value(n, default=None):
- for index, option in enumerate(sys.argv):
- if option == '--' + n:
- if index+1 >= len(sys.argv):
- raise DistutilsOptionError(
- 'The option %s requires a value' % option)
- value = sys.argv[index+1]
- sys.argv[index:index+2] = []
- return value
- if option.startswith('--' + n + '='):
- value = option[len(n)+3:]
- sys.argv[index:index+1] = []
- return value
- return os.getenv(env_var_name(n), default)
-
-
-OPTION_ENABLE_DEBUG = has_option('enable-debug')
-OPTION_DOWNLOAD_DIR = option_value('download-dir', 'build/extra')