├── .vscode └── settings.json ├── tests ├── __init__.py ├── data │ ├── dsacred.p12 │ ├── keyStore.p12 │ ├── sign-hmac-in.xml │ ├── sign-ecdsa-in.xml │ ├── sign-doc.xml │ ├── sign2-in.xml │ ├── sign3-in.xml │ ├── sign5-in.xml │ ├── sign4-in.xml │ ├── rsapub.pem │ ├── dsakey.pem │ ├── sign-dsa-in.xml │ ├── sign6-in.xml │ ├── sign1-in.xml │ ├── sign-hmac-out.xml │ ├── dsacert.pem │ ├── sign6-out.xml │ ├── sign1-out.xml │ ├── sign-dsa-out.xml │ ├── sign2-out.xml │ ├── rsakey.pem │ ├── sign-res.xml │ ├── sign3-out.xml │ ├── sign-fail_reference.xml │ ├── sign-fail_signature.xml │ ├── sign4-out.xml │ ├── sign5-out.xml │ └── rsacert.pem ├── base.py ├── test_hmac.py └── test_rsa.py ├── src └── xmlsig │ ├── algorithms │ ├── __init__.py │ ├── hmac.py │ ├── base.py │ └── rsa.py │ ├── __init__.py │ ├── ns.py │ ├── utils.py │ ├── template.py │ ├── constants.py │ ├── data │ └── xmldsig-core-schema.xsd │ └── signature_context.py ├── .idea ├── misc.xml ├── modules.xml ├── python-xmldsig.iml └── workspace.xml ├── codecov.yml ├── MANIFEST.in ├── Makefile ├── .github └── workflows │ ├── lint.yml │ └── test.yml ├── setup.cfg ├── tox.ini ├── .old-travis.yml ├── appveyor.yml ├── .pylintrc-mandatory ├── README.rst ├── .gitignore ├── setup.py ├── .pylintrc ├── .pre-commit-config.yaml └── LICENSE /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "esbonio.sphinx.confDir": "" 3 | } 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_hmac 2 | from . import test_rsa 3 | -------------------------------------------------------------------------------- /src/xmlsig/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | from .hmac import HMACAlgorithm 2 | from .rsa import RSAAlgorithm 3 | -------------------------------------------------------------------------------- /tests/data/dsacred.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etobella/python-xmlsig/HEAD/tests/data/dsacred.p12 -------------------------------------------------------------------------------- /tests/data/keyStore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etobella/python-xmlsig/HEAD/tests/data/keyStore.p12 -------------------------------------------------------------------------------- /tests/data/sign-hmac-in.xml: -------------------------------------------------------------------------------- 1 | 2 | Hello, World! 3 | 4 | -------------------------------------------------------------------------------- /tests/data/sign-ecdsa-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, World! 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/xmlsig/__init__.py: -------------------------------------------------------------------------------- 1 | from . import algorithms 2 | from . import constants 3 | from . import ns 4 | from . import template 5 | from .signature_context import SignatureContext 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/data/sign-doc.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/data/sign2-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/data/sign3-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/data/sign5-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | HjY8ilZAIEM2tBbPn5mYO1ieIX4= 7 | 8 | -------------------------------------------------------------------------------- /tests/data/sign4-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...100" 5 | 6 | status: 7 | project: 8 | default: 9 | target: auto 10 | if_no_uploads: error 11 | 12 | patch: 13 | default: 14 | if_no_uploads: error 15 | 16 | changes: true 17 | 18 | comment: 19 | layout: "header, diff, tree" 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Exclude everything by default 2 | exclude * 3 | recursive-exclude * * 4 | 5 | include MANIFEST.in 6 | include CHANGES 7 | include CONTRIBUTORS.rst 8 | include LICENSE 9 | include README.rst 10 | 11 | include setup.cfg 12 | include setup.py 13 | 14 | graft src 15 | # graft tests 16 | 17 | global-exclude __pycache__ 18 | global-exclude *.py[co] 19 | global-exclude .DS_Store 20 | -------------------------------------------------------------------------------- /.idea/python-xmldsig.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/data/rsapub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl7j+tD+DNXgWiQTsK2GM 3 | v8RfAIFKRebZzeniPJc7Ra2q5o0Ld3EHAU98+X3iGardkVn08c89unhGlhGctltG 4 | OXNVI6r3ngBc5elJ7DucP4SZOpCt335khsYmcs4xCHl+ExW45b/WVgKNYCFMJxhk 5 | +/tVcPYzvS9VcNVefpmupOCqRUcTqDDVoIqdzCDs5I5RyVTFfz5mLXS/o3r48+yU 6 | Vzm0rAB1YmFUtNDgUob4XnfsUEOc0rqnjGJavLL+88xifiNga8dRSTd4fiUVMKv6 7 | tK4ljyL8o0h/8gqKbuD+jfAB7cYzzGuh/aaA7waMr/ZAOo5CFCBhEh/j/AWxBdVl 8 | wwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install clean test retest coverage docs 2 | 3 | install: 4 | pip install -e .[docs,test,async] 5 | pip install bumpversion twine wheel 6 | 7 | lint: 8 | flake8 src/ tests/ 9 | isort --recursive --check-only --diff src tests 10 | 11 | clean: 12 | find . -name '*.pyc' -delete 13 | find . -name '__pycache__' -delete 14 | 15 | test: 16 | py.test -vvv 17 | 18 | retest: 19 | py.test -vvv --lf 20 | 21 | coverage: 22 | py.test --cov=xmlsig --cov-report=term-missing --cov-report=html 23 | 24 | release: 25 | pip install twine wheel 26 | rm -rf dist/* 27 | python setup.py sdist bdist_wheel 28 | twine upload -s dist/* 29 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: pre-commit 5 | 6 | on: 7 | push: 8 | branches: ["master"] 9 | pull_request: 10 | 11 | jobs: 12 | pre-commit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: 3.9 19 | - uses: pre-commit/action@v3.0.0 20 | -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from lxml import etree 4 | 5 | BASE_DIR = path.dirname(__file__) 6 | 7 | 8 | def parse_xml(name): 9 | return etree.parse(path.join(BASE_DIR, name)).getroot() 10 | 11 | 12 | def compare(name, result): 13 | # Parse the expected file. 14 | xml = parse_xml(name) 15 | 16 | # Stringify the root, nodes of the two documents. 17 | expected_text = etree.tostring(xml, pretty_print=False) 18 | result_text = etree.tostring(result, pretty_print=False) 19 | # Compare the results. 20 | if expected_text != result_text: 21 | print(expected_text) 22 | print(result_text.decode()) 23 | assert expected_text == result_text 24 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.0.1 3 | commit = true 4 | tag = true 5 | tag_name = {new_version} 6 | 7 | [tool:pytest] 8 | minversion = 3.0 9 | strict = true 10 | testpaths = tests 11 | 12 | [wheel] 13 | universal = 1 14 | 15 | [flake8] 16 | max-line-length = 99 17 | 18 | [bumpversion:file:setup.py] 19 | 20 | [bumpversion:file:src/__init__.py] 21 | 22 | [coverage:run] 23 | branch = True 24 | source = 25 | xmlsig 26 | 27 | [coverage:paths] 28 | source = 29 | src/xmlsig 30 | .tox/*/lib/python*/site-packages/xmlsig 31 | .tox/pypy*/site-packages/xmlsig 32 | 33 | [coverage:report] 34 | show_missing = True 35 | 36 | [metadata] 37 | description-file = README.rst 38 | 39 | [bdist_wheel] 40 | universal=1 41 | -------------------------------------------------------------------------------- /tests/data/dsakey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIBvAIBAAKBgQDZLuvPnu3LN/ADSKJctOJwWJQ2qXCg6Pl04/7M9LqSN3g1VTBj 3 | C7w1yv8GjrlC1dmaMNX58QrbLhikZckTJS+JKmA6mysRYlopmd2nogilZvlSY7v/ 4 | 6L2u7ilrYrw6rh2ySSm5pjFO9PSlyQEYm5jZQ8uSTEFMdPBUbTKBov4LNwIVAM9U 5 | IkqHPoMXE3NEFT02kENP3MB7AoGACeur4tx/azJj6ly6MynjqKDgoL+NH+1UIOFp 6 | SzBgg3V72bzC9HG23A6xRE9lr4hFykLwiwQ0FuiJkBVpkRsdyC67J3MWlekAfDCq 7 | WGUO5DDBqQc5wbSRrS0SNG28GpwnCTkrmx57I0vbmL8Wg1r/T3yQtktbXJfvMcFM 8 | kMPvN/ICgYEAtUurWdx8w6NXfn+0/2m8ZwFkNY9fxVAZB2s1jqrPzfZxtxY+C3Hk 9 | APBWnfr1rrsrqZEbFT6Yx8CRMJuznZzyiJKoIM/ybT864DCEcsk1gLzKRRo8+UlU 10 | 04cCVk4KPYSwUZc/yg+SE3mLDzdHhoC5xfUXwVSMP141NPpn5x9e8NECFQCFX1mJ 11 | 5wUH/H5de25Uqci+H6prLg== 12 | -----END DSA PRIVATE KEY----- 13 | -------------------------------------------------------------------------------- /tests/data/sign-dsa-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, World! 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/xmlsig/algorithms/hmac.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | import base64 5 | 6 | from cryptography.hazmat import backends 7 | from cryptography.hazmat.primitives import hmac 8 | 9 | from .base import Algorithm 10 | 11 | 12 | class HMACAlgorithm(Algorithm): 13 | @staticmethod 14 | def sign(data, private_key, digest): 15 | h = hmac.HMAC(private_key, digest(), backend=backends.default_backend()) 16 | h.update(data) 17 | return h.finalize() 18 | 19 | @staticmethod 20 | def verify(signature_value, data, public_key, digest): 21 | h = hmac.HMAC(public_key, digest(), backend=backends.default_backend()) 22 | h.update(data) 23 | h.verify(base64.b64decode(signature_value)) 24 | -------------------------------------------------------------------------------- /src/xmlsig/ns.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | DSigNs = "http://www.w3.org/2000/09/xmldsig#" 5 | DSigNs11 = "http://www.w3.org/2009/xmldsig11#" 6 | DSignNsMore = "http://www.w3.org/2001/04/xmldsig-more#" 7 | EncNs = "http://www.w3.org/2001/04/xmlenc#" 8 | XPathNs = "http://www.w3.org/TR/1999/REC-xpath-19991116" 9 | XPath2Ns = "http://www.w3.org/2002/06/xmldsig-filter2" 10 | XPointerNs = "http://www.w3.org/2001/04/xmldsig-more/xptr" 11 | Soap11Ns = "http://schemas.xmlsoap.org/soap/envelope/" 12 | Soap12Ns = "http://www.w3.org/2002/06/soap-envelope" 13 | NsExcC14N = "http://www.w3.org/2001/10/xml-exc-c14n#" 14 | NsExcC14NWithComments = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments" 15 | 16 | NS_MAP = {"ds": DSigNs, "ds11": DSigNs11} 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py33,py34,py35,py36,py37,py38 3 | 4 | 5 | [gh-actions] 6 | python = 7 | 2.7: py27 8 | 3.5: py35 9 | 3.6: py36 10 | 3.7: py37 11 | 3.8: py38 12 | 3.9: py39 13 | 14 | 15 | [testenv] 16 | extras = 17 | test 18 | xmlsec 19 | py{35,36}: async 20 | deps = 21 | py{35,36}: aioresponses==0.1.3 22 | py{35,36}: pytest-asyncio==0.5.0 23 | commands = coverage run --parallel -m pytest {posargs} 24 | 25 | 26 | [testenv:pypy] 27 | extras = 28 | test 29 | xmlsec 30 | commands = python -m pytest {posargs} 31 | 32 | 33 | # Uses default basepython otherwise reporting doesn't work on Travis where 34 | # Python 3.5 is only available in 3.5 jobs. 35 | [testenv:coverage-report] 36 | deps = coverage 37 | skip_install = true 38 | commands = 39 | coverage combine 40 | coverage report 41 | -------------------------------------------------------------------------------- /tests/data/sign6-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/data/sign1-in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/data/sign-hmac-out.xml: -------------------------------------------------------------------------------- 1 | 2 | Hello, World! 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ar01tgKya9a8sT7Px/K+h7i4+o4= 13 | 14 | 15 | 16 | 17 | 18 | 19 | zlWCFIxvDBKCM1uH317Uvkt4E5k= 20 | 21 | 22 | qUCyD7HeCnVzLxcJdw/Mf5GBfzU= 23 | U29tZSBUZXh0 24 | -------------------------------------------------------------------------------- /tests/data/dsacert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC5zCCAqWgAwIBAgIJAMAw6/Had2bMMAsGCWCGSAFlAwQDAjAqMQswCQYDVQQG 3 | EwJFUzEOMAwGA1UECAwFU3BhaW4xCzAJBgNVBAoMAkNCMB4XDTE3MDkxODA5MDMz 4 | NFoXDTE3MTAxODA5MDMzNFowKjELMAkGA1UEBhMCRVMxDjAMBgNVBAgMBVNwYWlu 5 | MQswCQYDVQQKDAJDQjCCAbcwggErBgcqhkjOOAQBMIIBHgKBgQDZLuvPnu3LN/AD 6 | SKJctOJwWJQ2qXCg6Pl04/7M9LqSN3g1VTBjC7w1yv8GjrlC1dmaMNX58QrbLhik 7 | ZckTJS+JKmA6mysRYlopmd2nogilZvlSY7v/6L2u7ilrYrw6rh2ySSm5pjFO9PSl 8 | yQEYm5jZQ8uSTEFMdPBUbTKBov4LNwIVAM9UIkqHPoMXE3NEFT02kENP3MB7AoGA 9 | Ceur4tx/azJj6ly6MynjqKDgoL+NH+1UIOFpSzBgg3V72bzC9HG23A6xRE9lr4hF 10 | ykLwiwQ0FuiJkBVpkRsdyC67J3MWlekAfDCqWGUO5DDBqQc5wbSRrS0SNG28Gpwn 11 | CTkrmx57I0vbmL8Wg1r/T3yQtktbXJfvMcFMkMPvN/IDgYUAAoGBALVLq1ncfMOj 12 | V35/tP9pvGcBZDWPX8VQGQdrNY6qz832cbcWPgtx5ADwVp369a67K6mRGxU+mMfA 13 | kTCbs52c8oiSqCDP8m0/OuAwhHLJNYC8ykUaPPlJVNOHAlZOCj2EsFGXP8oPkhN5 14 | iw83R4aAucX1F8FUjD9eNTT6Z+cfXvDRo1MwUTAdBgNVHQ4EFgQUNhwga3al1usE 15 | FyX1Q90JBHq5dccwHwYDVR0jBBgwFoAUNhwga3al1usEFyX1Q90JBHq5dccwDwYD 16 | VR0TAQH/BAUwAwEB/zALBglghkgBZQMEAwIDLwAwLAIUJIrofOoypaN2opQYag9i 17 | pG1MxpICFGOYX09iGr+xTKsMVXObld82EsOR 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /.old-travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: python 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - libssl-dev 9 | - openssl 10 | - libxml2 11 | - pkg-config 12 | 13 | matrix: 14 | include: 15 | - python: 2.7 16 | dist: trusty 17 | env: TOXENV=py27 LXML_VERSION=3.5.0 18 | - python: 3.5 19 | dist: trusty 20 | env: TOXENV=py35 LXML_VERSION=3.5.0 21 | - python: 3.6 22 | dist: trusty 23 | env: TOXENV=py36 LXML_VERSION=3.5.0 24 | - python: 2.7 25 | dist: trusty 26 | env: TOXENV=py27 LXML_VERSION=4.3.0 27 | - python: 3.5 28 | dist: trusty 29 | env: TOXENV=py35 LXML_VERSION=4.3.0 30 | - python: 3.6 31 | dist: trusty 32 | env: TOXENV=py36 LXML_VERSION=4.3.0 33 | - python: 3.7 34 | dist: xenial 35 | env: TOXENV=py37 LXML_VERSION=4.3.0 36 | 37 | before_cache: 38 | - rm -rf $HOME/.cache/pip/log 39 | 40 | cache: 41 | directories: 42 | - $HOME/.cache/pip 43 | 44 | deps: 45 | - codecov 46 | 47 | install: 48 | - pip install tox codecov 49 | - pip install lxml==$LXML_VERSION 50 | 51 | script: 52 | - tox -e $TOXENV 53 | 54 | after_success: 55 | - tox -e coverage-report 56 | - codecov 57 | 58 | notifications: 59 | email: false 60 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 14 | 15 | 16 | 1505223531542 17 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/data/sign6-out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | K8M/lPbKnuMDsO0Uzuj75lQtzQI= 16 | 17 | 18 | ffxibbOIFFeJXQrgz3ydvPrKAfYWc+OpROK5z/ULtCrC2qZPoPvqb3ONzcNrIEmx 19 | +PzzPO3XsQJuV2Wpn5YoOqgIVUS0wp7TVRvZs0Kiz06Gu8apQj1s5NxSKfdC7eZz 20 | izv2Zd/PYF4eSkObWqZvDVscdcV+3q2tNQf6WEEgif0iTwgdIRafR+pvcW1alA2x 21 | HPphiJt/eLWvckRaQr3TcLr4hO8YrBCFXpJ7MAIW3yEgUwM9ItpYLHXSYFRYsWef 22 | cg0fGWx3Qu86dlmDZrN8myNnh1s+fb2goIzP+7pGQ/LDRTIBGYwLg22jq+xAoJn5 23 | akG3YbT6VTbo1woIAMRHvQ== 24 | 25 | rsakey.pem 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/data/sign1-out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 9H/rQr2Axe9hYTV2n/tCp+3UIQQ= 19 | 20 | 21 | Mx4psIy9/UY+u8QBJRDrwQWKRaCGz0WOVftyDzAe6WHAFSjMNr7qb2ojq9kdipT8 22 | Oub5q2OQ7mzdSLiiejkrO1VeqM/90yEIGI4En6KEB6ArEzw+iq4N1wm6EptcyxXx 23 | M9StAOOa9ilWYqR9Tfx3SW1urUIuKYgUitxsONiUHBVaW6HeX51bsXoTF++4ZI+D 24 | jiPBjN4HHmr0cbJ6BXk91S27ffZIfp1Qj5nL9onFLUGbR6EFgu2luiRzQbPuM2tP 25 | XxyI7GZ8AfHnRJK28ARvBC9oi+O1ej20S79CIV7gdBxbLbFprozBHAwOEC57YgJc 26 | x+YEjSjcO7SBIR1FiUA7pw== 27 | 28 | rsakey.pem 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | environment: 3 | matrix: 4 | - PYTHON: "C:\\Python27" 5 | LXML: "3.5.0" 6 | - PYTHON: "C:\\Python27" 7 | LXML: "4.3.0" 8 | - PYTHON: "C:\\Python33" 9 | LXML: "3.5.0" 10 | - PYTHON: "C:\\Python33" 11 | LXML: "4.3.0" 12 | - PYTHON: "C:\\Python34" 13 | LXML: "3.5.0" 14 | - PYTHON: "C:\\Python34" 15 | LXML: "4.3.0" 16 | - PYTHON: "C:\\Python35" 17 | LXML: "3.5.0" 18 | - PYTHON: "C:\\Python35" 19 | LXML: "4.3.0" 20 | - PYTHON: "C:\\Python36" 21 | LXML: "3.5.0" 22 | - PYTHON: "C:\\Python36" 23 | LXML: "4.3.0" 24 | - PYTHON: "C:\\Python37" 25 | LXML: "3.5.0" 26 | - PYTHON: "C:\\Python37" 27 | LXML: "4.3.0" 28 | 29 | init: 30 | - "%PYTHON%/python -V" 31 | - "%PYTHON%/python -c \"import struct;print( 8 * struct.calcsize(\'P\'))\"" 32 | 33 | install: 34 | - "%PYTHON%/Scripts/easy_install -U pip" 35 | - "%PYTHON%/Scripts/easy_install lxml==%LXML" 36 | - "%PYTHON%/Scripts/pip install wheel" 37 | - "%PYTHON%/Scripts/pip install -e .[test]" 38 | 39 | 40 | build: false # Not a C# project, build stuff at the test step instead. 41 | 42 | test_script: 43 | - "%PYTHON%/Scripts/py.test --cov=zeep --cov-report=term-missing" 44 | 45 | after_test: 46 | - "%PYTHON%/python setup.py bdist_wheel" 47 | - ps: "ls dist" 48 | 49 | artifacts: 50 | - path: dist\* 51 | -------------------------------------------------------------------------------- /.pylintrc-mandatory: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | score=n 3 | 4 | [MESSAGES CONTROL] 5 | disable=all 6 | 7 | enable=anomalous-backslash-in-string, 8 | api-one-deprecated, 9 | api-one-multi-together, 10 | assignment-from-none, 11 | attribute-deprecated, 12 | class-camelcase, 13 | dangerous-default-value, 14 | dangerous-view-replace-wo-priority, 15 | duplicate-id-csv, 16 | duplicate-key, 17 | duplicate-xml-fields, 18 | duplicate-xml-record-id, 19 | eval-referenced, 20 | eval-used, 21 | incoherent-interpreter-exec-perm, 22 | license-allowed, 23 | manifest-author-string, 24 | manifest-deprecated-key, 25 | manifest-required-key, 26 | manifest-version-format, 27 | method-compute, 28 | method-inverse, 29 | method-required-super, 30 | method-search, 31 | missing-import-error, 32 | missing-manifest-dependency, 33 | openerp-exception-warning, 34 | pointless-statement, 35 | pointless-string-statement, 36 | print-used, 37 | redundant-keyword-arg, 38 | redundant-modulename-xml, 39 | reimported, 40 | relative-import, 41 | return-in-init, 42 | rst-syntax-error, 43 | sql-injection, 44 | too-few-format-args, 45 | translation-field, 46 | translation-required, 47 | unreachable, 48 | use-vim-comment, 49 | wrong-tabs-instead-of-spaces, 50 | xml-syntax-error 51 | 52 | [REPORTS] 53 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} 54 | output-format=colorized 55 | reports=no 56 | -------------------------------------------------------------------------------- /tests/data/sign-dsa-out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, World! 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ar01tgKya9a8sT7Px/K+h7i4+o4= 14 | 15 | 16 | Tq29gw/jH0QCdbpFJmdXvJD6PqNXnc1OF072bLzQnSz7F4Slsqep0A== 17 | 18 | 19 | 20 |

21 | 2S7rz57tyzfwA0iiXLTicFiUNqlwoOj5dOP+zPS6kjd4NVUwYwu8Ncr/Bo65QtXZ 22 | mjDV+fEK2y4YpGXJEyUviSpgOpsrEWJaKZndp6IIpWb5UmO7/+i9ru4pa2K8Oq4d 23 | skkpuaYxTvT0pckBGJuY2UPLkkxBTHTwVG0ygaL+Czc= 24 |

25 | 26 | z1QiSoc+gxcTc0QVPTaQQ0/cwHs= 27 | 28 | 29 | Ceur4tx/azJj6ly6MynjqKDgoL+NH+1UIOFpSzBgg3V72bzC9HG23A6xRE9lr4hF 30 | ykLwiwQ0FuiJkBVpkRsdyC67J3MWlekAfDCqWGUO5DDBqQc5wbSRrS0SNG28Gpwn 31 | CTkrmx57I0vbmL8Wg1r/T3yQtktbXJfvMcFMkMPvN/I= 32 | 33 | 34 | tUurWdx8w6NXfn+0/2m8ZwFkNY9fxVAZB2s1jqrPzfZxtxY+C3HkAPBWnfr1rrsr 35 | qZEbFT6Yx8CRMJuznZzyiJKoIM/ybT864DCEcsk1gLzKRRo8+UlU04cCVk4KPYSw 36 | UZc/yg+SE3mLDzdHhoC5xfUXwVSMP141NPpn5x9e8NE= 37 | 38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /tests/data/sign2-out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, World! 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | HjY8ilZAIEM2tBbPn5mYO1ieIX4= 15 | 16 | 17 | SIaj/6KY3C1SmDXU2++Gm31U1xTadFp04WhBgfsJFbxrL+q7GKSKN9kfQ+UpN9+i 18 | D5fWmuavXEHe4Gw6RMaMEkq2URQo7F68+d5J/ajq8/l4n+xE6/reGScVwT6L4dEP 19 | XXVJcAi2ZnQ3O7GTNvNGCPibL9mUcyCWBFZ92Uemtc/vJFCQ7ZyKMdMfACgxOwyN 20 | T/9971oog241/2doudhonc0I/3mgPYWkZdX6yvr62mEjnG+oUZkhWYJ4ewZJ4hM4 21 | JjbFqZO+OEzDRSbw3DkmuBA/mtlx+3t13SESfEub5hqoMdVmtth/eTb64dsPdl9r 22 | 3k1ACVX9f8aHfQQdJOmLFQ== 23 | 24 | rsakey.pem 25 | 26 | 27 | l7j+tD+DNXgWiQTsK2GMv8RfAIFKRebZzeniPJc7Ra2q5o0Ld3EHAU98+X3iGard 28 | kVn08c89unhGlhGctltGOXNVI6r3ngBc5elJ7DucP4SZOpCt335khsYmcs4xCHl+ 29 | ExW45b/WVgKNYCFMJxhk+/tVcPYzvS9VcNVefpmupOCqRUcTqDDVoIqdzCDs5I5R 30 | yVTFfz5mLXS/o3r48+yUVzm0rAB1YmFUtNDgUob4XnfsUEOc0rqnjGJavLL+88xi 31 | fiNga8dRSTd4fiUVMKv6tK4ljyL8o0h/8gqKbuD+jfAB7cYzzGuh/aaA7waMr/ZA 32 | Oo5CFCBhEh/j/AWxBdVlww== 33 | AQAB 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/test_hmac.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import unittest 3 | 4 | from lxml import etree 5 | 6 | import xmlsig 7 | 8 | from .base import compare, parse_xml 9 | 10 | 11 | class TestSignature(unittest.TestCase): 12 | def test_hmac(self): 13 | template = parse_xml("data/sign-hmac-in.xml") 14 | 15 | # Create a signature template for RSA-SHA1 enveloped signature. 16 | sign = xmlsig.template.create( 17 | c14n_method=xmlsig.constants.TransformExclC14N, 18 | sign_method=xmlsig.constants.TransformHmacSha1, 19 | ns="ds", 20 | ) 21 | 22 | assert sign is not None 23 | 24 | # Add the node to the document. 25 | template.append(sign) 26 | 27 | # Add the node to the signature template. 28 | ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) 29 | # Add the enveloped transform descriptor. 30 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 31 | 32 | ref_obj = xmlsig.template.add_reference( 33 | sign, xmlsig.constants.TransformSha1, uri="#R1" 34 | ) 35 | xmlsig.template.add_transform(ref_obj, xmlsig.constants.TransformBase64) 36 | obj = etree.SubElement(sign, etree.QName(xmlsig.constants.DSigNs, "Object")) 37 | obj.set("Id", "R1") 38 | obj.text = base64.b64encode(b"Some Text") 39 | ctx = xmlsig.SignatureContext() 40 | ctx.private_key = b"secret" 41 | 42 | ctx.sign(sign) 43 | ctx.verify(sign) 44 | compare("data/sign-hmac-out.xml", template) 45 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | XmlSIG: Python native XML Signature 3 | =================================== 4 | 5 | A python native library that signs and verifies xml signatures 6 | 7 | Highlights: 8 | * Build on top of lxml and cryptography 9 | 10 | 11 | .. start-no-pypi 12 | 13 | Status 14 | ------ 15 | 16 | .. image:: https://travis-ci.org/etobella/python-xmlsig.svg?branch=master 17 | :target: https://travis-ci.org/etobella/python-xmlsig 18 | 19 | .. image:: http://codecov.io/github/etobella/python-xmlsig/coverage.svg?branch=master 20 | :target: http://codecov.io/github/etobella/python-xmlsig?branch=master 21 | 22 | .. image:: https://img.shields.io/pypi/v/xmlsig.svg 23 | :target: https://pypi.python.org/pypi/xmlsig/ 24 | 25 | .. end-no-pypi 26 | 27 | Installation 28 | ------------ 29 | 30 | .. code-block:: bash 31 | 32 | pip install xmlsig 33 | 34 | Usage 35 | ===== 36 | 37 | .. code:: 38 | 39 | import xmlsig 40 | 41 | sign = xmlsig.template.create(c14n_method=xmlsig.constants.TransformExclC14N, sign_method=xmlsig.constants.TransformRsaSha1) 42 | ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) 43 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 44 | 45 | ctx = xmlsig.SignatureContext() 46 | 47 | 48 | 49 | To have more examples, look at the source code of the testings 50 | 51 | Functionality 52 | ============= 53 | 54 | Signature is only valid using RSA and HMAC validation. 55 | ECDSA and DSA is still being implemented 56 | 57 | License 58 | ======= 59 | 60 | This library is published under the LGPL-3 license. 61 | 62 | Contributors 63 | ============ 64 | 65 | * Enric Tobella 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | install_requires = [ 4 | "lxml>=3.0.0", 5 | "cryptography", 6 | ] 7 | 8 | tests_require = [ 9 | "freezegun==0.3.8", 10 | "mock==2.0.0", 11 | "pretend==1.0.8", 12 | "pytest-cov==2.5.1", 13 | "pytest==3.1.3", 14 | "requests_mock>=0.7.0", 15 | "PyOpenSSL", 16 | "isort==4.2.5", 17 | "flake8==3.3.0", 18 | "flake8-blind-except==0.1.1", 19 | "flake8-debugger==1.4.0", 20 | "flake8-imports==0.1.1", 21 | ] 22 | 23 | 24 | setup( 25 | name="xmlsig", 26 | version="1.0.1", 27 | description="Python based XML signature", 28 | long_description="XML Signature created with cryptography and lxml", 29 | author="Enric Tobella Alomar", 30 | author_email="etobella@creublanca.es", 31 | url="http://github.com/etobella/python-xmlsig", 32 | install_requires=install_requires, 33 | tests_require=tests_require, 34 | extras_require={"test": tests_require}, 35 | entry_points={}, 36 | package_dir={"": "src"}, 37 | packages=find_packages("src"), 38 | include_package_data=True, 39 | license="LGPL-3", 40 | classifiers=[ 41 | "Development Status :: 4 - Beta", 42 | "License :: OSI Approved :: GNU Affero General Public License v3", 43 | "Programming Language :: Python :: 2", 44 | "Programming Language :: Python :: 2.7", 45 | "Programming Language :: Python :: 3", 46 | "Programming Language :: Python :: 3.4", 47 | "Programming Language :: Python :: 3.5", 48 | "Programming Language :: Python :: 3.6", 49 | "Programming Language :: Python :: 3.7", 50 | "Programming Language :: Python :: Implementation :: PyPy", 51 | ], 52 | zip_safe=False, 53 | ) 54 | -------------------------------------------------------------------------------- /src/xmlsig/algorithms/base.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | 5 | import base64 6 | 7 | from cryptography.hazmat.backends import default_backend 8 | from cryptography.x509 import load_der_x509_certificate 9 | 10 | from .. import ns 11 | 12 | 13 | class Algorithm(object): 14 | private_key_class = None 15 | public_key_class = None 16 | 17 | @staticmethod 18 | def sign(data, private_key, digest): 19 | raise Exception("Sign function must be redefined") 20 | 21 | @staticmethod 22 | def verify(signature_value, data, public_key, digest): 23 | raise Exception("Verify function must be redefined") 24 | 25 | @staticmethod 26 | def key_value(node, public_key): 27 | raise Exception("Key Value function must be redefined") 28 | 29 | @staticmethod 30 | def get_public_key(key_info, ctx): 31 | """ 32 | Get the public key if its defined in X509Certificate node. Otherwise, 33 | take self.public_key element 34 | :param sign: Signature node 35 | :type sign: lxml.etree.Element 36 | :return: Public key to use 37 | """ 38 | x509_certificate = key_info.find( 39 | "ds:KeyInfo/ds:X509Data/ds:X509Certificate", namespaces={"ds": ns.DSigNs} 40 | ) 41 | if x509_certificate is not None: 42 | return load_der_x509_certificate( 43 | base64.b64decode(x509_certificate.text), default_backend() 44 | ).public_key() 45 | if ctx.public_key is not None: 46 | return ctx.public_key 47 | if isinstance(ctx.private_key, (str, bytes)): 48 | return ctx.private_key 49 | return ctx.private_key.public_key() 50 | -------------------------------------------------------------------------------- /tests/data/rsakey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAl7j+tD+DNXgWiQTsK2GMv8RfAIFKRebZzeniPJc7Ra2q5o0L 3 | d3EHAU98+X3iGardkVn08c89unhGlhGctltGOXNVI6r3ngBc5elJ7DucP4SZOpCt 4 | 335khsYmcs4xCHl+ExW45b/WVgKNYCFMJxhk+/tVcPYzvS9VcNVefpmupOCqRUcT 5 | qDDVoIqdzCDs5I5RyVTFfz5mLXS/o3r48+yUVzm0rAB1YmFUtNDgUob4XnfsUEOc 6 | 0rqnjGJavLL+88xifiNga8dRSTd4fiUVMKv6tK4ljyL8o0h/8gqKbuD+jfAB7cYz 7 | zGuh/aaA7waMr/ZAOo5CFCBhEh/j/AWxBdVlwwIDAQABAoIBAQCAvt6DnZF9gdW9 8 | l4vAlBqXb88d4phgELCp5tmviLUnP2NSGEWuqR7Eoeru2z9NgIxblvYfazh6Ty22 9 | kmNk6rcAcTnB9oYAcVZjUj8EUuEXlTFhXPvuNpafNu3RZd59znqJP1mSu+LpQWku 10 | NZMlabHnkTLDlGf7FXtvL9/rlgV4qk3QcDVF793JFszWrtK3mnld3KHQ6cuo9iSm 11 | 0rQKtkDjeHsRell8qTQvfBsgG1q2bv8QWT45/eQrra9mMbGTr3DbnXvoeJmTj1VN 12 | XJV7tBNllxxPahlYMByJaf/Tuva5j6HWUEIfYky5ihr2z1P/fNQ2OSCM6SQHpkiG 13 | EXQDueXBAoGBAMfW7KcmToEQEcTiqfey6C1LOLoemcX0/ROUktPq/5JQJRRrT4t7 14 | XevLX0ed8sLyR5T29XQtdnuV0DJfvcJD+6ZwfOcQ+f6ZzCaNXJP97JtEt5kSWY01 15 | Ei+nphZ0RFvPb04V3qDU9dElU26GR36CRBYJyM2WQPx4v+/YyDSZH9kLAoGBAMJc 16 | ZBU8pRbIia/FFOHUlS3v5P18nVmXyOd0fvRq0ZelaQCebTZ4K9wjnCfw//yzkb2Z 17 | 0vZFNB+xVBKB0Pt6nVvnSNzxdQ8EAXVFwHtXa25FUyP2RERQgTvmajqmgWjZsDYp 18 | 6GHcK3ZhmdmscQHF/Q2Uo4scvBcheahm9IXiNskpAoGAXelEgTBhSAmTMCEMmti6 19 | fz6QQ/bJcNu2apMxhOE0hT+gjT34vaWV9481EWTKho5w0TJVGumaem1mz6VqeXaV 20 | Nhw6tiOmN91ysNNRpEJ6BGWAmjCjYNaF21s/k+HDlhmfRuTEIHSzqDuQP6pewrbY 21 | 5Dpo4SQxGfRsznvjacRj0Q0CgYBN247oBvQnDUxCkhNMZ8kersOvW5T4x9neBge5 22 | R3UQZ12Jtu0O7dK8C7PJODyDcTeHmTAuIQjBTVrdUw1xP+v7XcoNX9hBnJws6zUw 23 | 85MAiFrGxCcSqqEqaqHRPtQGOXXiLKV/ViA++tgTn4VhbXtyTkG5P1iFd45xjFSV 24 | sUm7CQKBgDn92tHxzePly1L1mK584TkVryx4cP9RFHpebnmNduGwwjnRuYipoj8y 25 | pPPAkVbbaA3f9OB2go48rN0Ft9nHdlqgh9BpIKCVtkIb1XN0K3Oa/8BW8W/GAiNG 26 | HJcsrOtIrGVRdlyJG6bDaN8T49DnhOcsqMbf+IkIvfh50VeE9L/e 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/xmlsig/algorithms/rsa.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | from base64 import b64decode, b64encode 5 | 6 | from cryptography.hazmat.backends import default_backend 7 | from cryptography.hazmat.primitives.asymmetric import padding, rsa 8 | 9 | from ..ns import NS_MAP, DSigNs 10 | from ..utils import b64_print, create_node, long_to_bytes, os2ip 11 | from .base import Algorithm 12 | 13 | 14 | class RSAAlgorithm(Algorithm): 15 | private_key_class = rsa.RSAPrivateKey 16 | public_key_class = rsa.RSAPublicKey 17 | 18 | @staticmethod 19 | def sign(data, private_key, digest): 20 | return private_key.sign(data, padding.PKCS1v15(), digest()) 21 | 22 | @staticmethod 23 | def verify(signature_value, data, public_key, digest): 24 | public_key.verify( 25 | b64decode(signature_value), data, padding.PKCS1v15(), digest() 26 | ) 27 | 28 | @staticmethod 29 | def key_value(node, public_key): 30 | result = create_node("RSAKeyValue", node, DSigNs, "\n", "\n") 31 | create_node( 32 | "Modulus", 33 | result, 34 | DSigNs, 35 | tail="\n", 36 | text=b64_print(b64encode(long_to_bytes(public_key.public_numbers().n))), 37 | ) 38 | create_node( 39 | "Exponent", 40 | result, 41 | DSigNs, 42 | tail="\n", 43 | text=b64encode(long_to_bytes(public_key.public_numbers().e)), 44 | ) 45 | return result 46 | 47 | @staticmethod 48 | def get_public_key(key_info, ctx): 49 | """ 50 | Get the public key if its defined in X509Certificate node. Otherwise, 51 | take self.public_key element 52 | :param sign: Signature node 53 | :type sign: lxml.etree.Element 54 | :return: Public key to use 55 | """ 56 | key = key_info.find("ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue", namespaces=NS_MAP) 57 | if key is not None: 58 | n = os2ip(b64decode(key.find("ds:Modulus", namespaces=NS_MAP).text)) 59 | e = os2ip(b64decode(key.find("ds:Exponent", namespaces=NS_MAP).text)) 60 | return rsa.RSAPublicNumbers(e, n).public_key(default_backend()) 61 | return super(RSAAlgorithm, RSAAlgorithm).get_public_key(key_info, ctx) 62 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | score=n 3 | 4 | [MESSAGES CONTROL] 5 | disable=all 6 | 7 | # This .pylintrc contains optional AND mandatory checks and is meant to be 8 | # loaded in an IDE to have it check everything, in the hope this will make 9 | # optional checks more visible to contributors who otherwise never look at a 10 | # green travis to see optional checks that failed. 11 | # .pylintrc-mandatory containing only mandatory checks is used the pre-commit 12 | # config as a blocking check. 13 | 14 | enable=anomalous-backslash-in-string, 15 | api-one-deprecated, 16 | api-one-multi-together, 17 | assignment-from-none, 18 | attribute-deprecated, 19 | class-camelcase, 20 | dangerous-default-value, 21 | dangerous-view-replace-wo-priority, 22 | duplicate-id-csv, 23 | duplicate-key, 24 | duplicate-xml-fields, 25 | duplicate-xml-record-id, 26 | eval-referenced, 27 | eval-used, 28 | incoherent-interpreter-exec-perm, 29 | license-allowed, 30 | manifest-author-string, 31 | manifest-deprecated-key, 32 | manifest-required-key, 33 | manifest-version-format, 34 | method-compute, 35 | method-inverse, 36 | method-required-super, 37 | method-search, 38 | missing-import-error, 39 | missing-manifest-dependency, 40 | openerp-exception-warning, 41 | pointless-statement, 42 | pointless-string-statement, 43 | print-used, 44 | redundant-keyword-arg, 45 | redundant-modulename-xml, 46 | reimported, 47 | relative-import, 48 | return-in-init, 49 | rst-syntax-error, 50 | sql-injection, 51 | too-few-format-args, 52 | translation-field, 53 | translation-required, 54 | unreachable, 55 | use-vim-comment, 56 | wrong-tabs-instead-of-spaces, 57 | xml-syntax-error, 58 | # messages that do not cause the lint step to fail 59 | consider-merging-classes-inherited, 60 | create-user-wo-reset-password, 61 | dangerous-filter-wo-user, 62 | deprecated-module, 63 | file-not-used, 64 | invalid-commit, 65 | missing-newline-extrafiles, 66 | missing-readme, 67 | no-utf8-coding-comment, 68 | odoo-addons-relative-import, 69 | old-api7-method-defined, 70 | redefined-builtin, 71 | too-complex, 72 | unnecessary-utf8-coding-comment 73 | 74 | [REPORTS] 75 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} 76 | output-format=colorized 77 | reports=no 78 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: test XMLSIG 5 | 6 | on: 7 | push: 8 | branches: ["master"] 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | include: 17 | - PYTHON_VERSION: "3.6" 18 | LXML_VERSION: "4.3.0" 19 | os: ubuntu-20.04 20 | - PYTHON_VERSION: "3.6" 21 | LXML_VERSION: "4.6.4" 22 | os: ubuntu-20.04 23 | - PYTHON_VERSION: "3.7" 24 | LXML_VERSION: "4.3.0" 25 | os: ubuntu-latest 26 | - PYTHON_VERSION: "3.7" 27 | LXML_VERSION: "4.6.4" 28 | os: ubuntu-latest 29 | - PYTHON_VERSION: "3.8" 30 | LXML_VERSION: "4.3.0" 31 | os: ubuntu-latest 32 | - PYTHON_VERSION: "3.8" 33 | LXML_VERSION: "4.6.4" 34 | os: ubuntu-latest 35 | - PYTHON_VERSION: "3.9" 36 | LXML_VERSION: "4.6.4" 37 | os: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v3 40 | - name: Set up Python 41 | uses: actions/setup-python@v4 42 | with: 43 | python-version: ${{matrix.PYTHON_VERSION}} 44 | - name: Configuration 45 | run: | 46 | sudo apt update 47 | sudo apt install libssl-dev openssl libxml2-dev pkg-config libxslt-dev 48 | - name: Configure TOX 49 | run: | 50 | pip install pip --upgrade 51 | pip install tox codecov tox-gh-actions wheel 52 | pip install lxml==${{matrix.LXML_VERSION}} 53 | - name: Generate Report 54 | run: | 55 | pip install coverage 56 | pip install .[test] 57 | coverage run -m unittest 58 | - name: Upload Coverage to Codecov 59 | uses: codecov/codecov-action@v3 60 | -------------------------------------------------------------------------------- /tests/data/sign-res.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | HjY8ilZAIEM2tBbPn5mYO1ieIX4= 19 | 20 | 21 | SIaj/6KY3C1SmDXU2++Gm31U1xTadFp04WhBgfsJFbxrL+q7GKSKN9kfQ+UpN9+i 22 | D5fWmuavXEHe4Gw6RMaMEkq2URQo7F68+d5J/ajq8/l4n+xE6/reGScVwT6L4dEP 23 | XXVJcAi2ZnQ3O7GTNvNGCPibL9mUcyCWBFZ92Uemtc/vJFCQ7ZyKMdMfACgxOwyN 24 | T/9971oog241/2doudhonc0I/3mgPYWkZdX6yvr62mEjnG+oUZkhWYJ4ewZJ4hM4 25 | JjbFqZO+OEzDRSbw3DkmuBA/mtlx+3t13SESfEub5hqoMdVmtth/eTb64dsPdl9r 26 | 3k1ACVX9f8aHfQQdJOmLFQ== 27 | 28 | 29 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 30 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 31 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 32 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 33 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 34 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 35 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 36 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 37 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 38 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 39 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 40 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 41 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 42 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 43 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 44 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 45 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 46 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 47 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 48 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 49 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 50 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 51 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 52 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 53 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 54 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 55 | NJ2D 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/data/sign3-out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | HjY8ilZAIEM2tBbPn5mYO1ieIX4= 19 | 20 | 21 | SIaj/6KY3C1SmDXU2++Gm31U1xTadFp04WhBgfsJFbxrL+q7GKSKN9kfQ+UpN9+i 22 | D5fWmuavXEHe4Gw6RMaMEkq2URQo7F68+d5J/ajq8/l4n+xE6/reGScVwT6L4dEP 23 | XXVJcAi2ZnQ3O7GTNvNGCPibL9mUcyCWBFZ92Uemtc/vJFCQ7ZyKMdMfACgxOwyN 24 | T/9971oog241/2doudhonc0I/3mgPYWkZdX6yvr62mEjnG+oUZkhWYJ4ewZJ4hM4 25 | JjbFqZO+OEzDRSbw3DkmuBA/mtlx+3t13SESfEub5hqoMdVmtth/eTb64dsPdl9r 26 | 3k1ACVX9f8aHfQQdJOmLFQ== 27 | 28 | 29 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 30 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 31 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 32 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 33 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 34 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 35 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 36 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 37 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 38 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 39 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 40 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 41 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 42 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 43 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 44 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 45 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 46 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 47 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 48 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 49 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 50 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 51 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 52 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 53 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 54 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 55 | NJ2D 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/data/sign-fail_reference.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | HjY8ilZAIEM2tBbPn5mYO2ieIX4= 19 | 20 | 21 | SIaj/6KY3C1SmDXU2++Gm31U1xTadFp04WhBgfsJFbxrL+q7GKSKN9kfQ+UpN9+i 22 | D5fWmuavXEHe4Gw6RMaMEkq2URQo7F68+d5J/ajq8/l4n+xE6/reGScVwT6L4dEP 23 | XXVJcAi2ZnQ3O7GTNvNGCPibL9mUcyCWBFZ92Uemtc/vJFCQ7ZyKMdMfACgxOwyN 24 | T/9971oog241/2doudhonc0I/3mgPYWkZdX6yvr62mEjnG+oUZkhWYJ4ewZJ4hM4 25 | JjbFqZO+OEzDRSbw3DkmuBA/mtlx+3t13SESfEub5hqoMdVmtth/eTb64dsPdl9r 26 | 3k1ACVX9f8aHfQQdJOmLFQ== 27 | 28 | 29 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 30 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 31 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 32 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 33 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 34 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 35 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 36 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 37 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 38 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 39 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 40 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 41 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 42 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 43 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 44 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 45 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 46 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 47 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 48 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 49 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 50 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 51 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 52 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 53 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 54 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 55 | NJ2D 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/data/sign-fail_signature.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Hello, World! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | HjY8ilZAIEM2tBbPn5mYO1ieIX4= 19 | 20 | 21 | SIaj/6KY3C1SmeXU2++Gm31U1xTadFp04WhBgfsJFbxrL+q7GKSKN9kfQ+UpN9+i 22 | D5fWmuavXEHe4Gw6RMaMEkq2URQo7F68+d5J/ajq8/l4n+xE6/reGScVwT6L4dEP 23 | XXVJcAi2ZnQ3O7GTNvNGCPibL9mUcyCWBFZ92Uemtc/vJFCQ7ZyKMdMfACgxOwyN 24 | T/9971oog241/2doudhonc0I/3mgPYWkZdX6yvr62mEjnG+oUZkhWYJ4ewZJ4hM4 25 | JjbFqZO+OEzDRSbw3DkmuBA/mtlx+3t13SESfEub5hqoMdVmtth/eTb64dsPdl9r 26 | 3k1ACVX9f8aHfQQdJOmLFQ== 27 | 28 | 29 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 30 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 31 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 32 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 33 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 34 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 35 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 36 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 37 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 38 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 39 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 40 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 41 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 42 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 43 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 44 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 45 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 46 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 47 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 48 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 49 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 50 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 51 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 52 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 53 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 54 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 55 | NJ2D 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/data/sign4-out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, World! 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ERS7F/ifxZoyTe0mhco+HeAEKGo= 16 | 17 | 18 | G+mZNFqh9w0wIkDSbuYwvVDu7CMP8PEsw7jfwiZBC8nyF3loAtYKKAkdi6Zy3dJs 19 | tU8qKfhzvabmCjdIrGkFTdtlCNCVKDMzwogFtxEX4Oh77X6jjx4b22XNJx4AbnUG 20 | JV/EcsD+po8s5qVEXw62lRRd8cMDafbzOA/rBH96CMNgZhzxyaF9VRLa/vbt1ht2 21 | hE1KkdZCB4Y0Lv3QyeDL2jax3NFks9FUv8IqoWYQSvywdMLY2ZMiQ9UpPeVfMizi 22 | trd5zDUSD/s3hyIEs4gD5NJF3HZPD/Fe2Zw1PBPj9eLADdEzcdueyCdPJ2YioFIi 23 | 1sMW/qPDhR/DoOJwGpUxwQ== 24 | 25 | 26 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 27 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 28 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 29 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 30 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 31 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 32 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 33 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 34 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 35 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 36 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 37 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 38 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 39 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 40 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 41 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 42 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 43 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 44 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 45 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 46 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 47 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 48 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 49 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 50 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 51 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 52 | NJ2D 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: | 2 | (?x) 3 | # Files and folders generated by bots, to avoid loops 4 | ^setup/|/static/description/index\.html$| 5 | # Maybe reactivate this when all README files include prettier ignore tags? 6 | ^README\.md$| 7 | # Library files can have extraneous formatting (even minimized) 8 | /static/(src/)?lib/| 9 | # Repos using Sphinx to generate docs don't need prettying 10 | ^docs/_templates/.*\.html$| 11 | # You don't usually want a bot to modify your legal texts 12 | (LICENSE.*|COPYING.*) 13 | default_language_version: 14 | python: python3 15 | repos: 16 | - repo: https://github.com/psf/black 17 | rev: 22.3.0 18 | hooks: 19 | - id: black 20 | - repo: https://github.com/pre-commit/mirrors-eslint 21 | rev: v8.14.0 22 | hooks: 23 | - id: eslint 24 | verbose: true 25 | args: 26 | - --color 27 | - --fix 28 | - repo: https://github.com/pre-commit/pre-commit-hooks 29 | rev: v4.2.0 30 | hooks: 31 | - id: trailing-whitespace 32 | # exclude autogenerated files 33 | exclude: /README\.rst$|\.pot?$ 34 | - id: end-of-file-fixer 35 | # exclude autogenerated files 36 | exclude: /README\.rst$|\.pot?$ 37 | - id: debug-statements 38 | - id: fix-encoding-pragma 39 | args: ["--remove"] 40 | - id: check-case-conflict 41 | - id: check-docstring-first 42 | - id: check-executables-have-shebangs 43 | - id: check-merge-conflict 44 | # exclude files where underlines are not distinguishable from merge conflicts 45 | exclude: /README\.rst$|^docs/.*\.rst$ 46 | - id: check-symlinks 47 | - id: check-xml 48 | - id: mixed-line-ending 49 | args: ["--fix=lf"] 50 | - repo: https://github.com/pycqa/flake8 51 | rev: 3.9.2 52 | hooks: 53 | - id: flake8 54 | name: flake8 except __init__.py 55 | exclude: /__init__\.py$ 56 | additional_dependencies: ["flake8-bugbear==19.8.0"] 57 | - id: flake8 58 | name: flake8 only __init__.py 59 | args: ["--extend-ignore=F401"] # ignore unused imports in __init__.py 60 | files: /__init__\.py$ 61 | additional_dependencies: ["flake8-bugbear==19.8.0"] 62 | - repo: https://github.com/pre-commit/mirrors-pylint 63 | rev: v3.0.0a4 64 | hooks: 65 | - id: pylint 66 | name: pylint with optional checks 67 | args: ["--rcfile=.pylintrc", "--exit-zero"] 68 | verbose: true 69 | - id: pylint 70 | name: pylint with mandatory checks 71 | args: ["--rcfile=.pylintrc-mandatory"] 72 | - repo: https://github.com/asottile/pyupgrade 73 | rev: v2.32.0 74 | hooks: 75 | - id: pyupgrade 76 | - repo: https://github.com/pre-commit/mirrors-isort 77 | rev: v5.10.1 78 | hooks: 79 | - id: isort 80 | name: isort except __init__.py 81 | exclude: /__init__\.py$ 82 | -------------------------------------------------------------------------------- /src/xmlsig/utils.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | import struct 5 | import sys 6 | 7 | from cryptography.x509 import oid 8 | from cryptography.x509.name import _NAMEOID_TO_NAME, _escape_dn_value 9 | from lxml import etree 10 | 11 | USING_PYTHON2 = True if sys.version_info < (3, 0) else False 12 | b64_intro = 64 13 | 14 | 15 | def b64_print(s): 16 | """ 17 | Prints a string with spaces at every b64_intro characters 18 | :param s: String to print 19 | :return: String 20 | """ 21 | if USING_PYTHON2: 22 | string = str(s) 23 | else: 24 | string = str(s, "utf8") 25 | return "\n".join( 26 | string[pos : pos + b64_intro] # noqa: E203 27 | for pos in range(0, len(string), b64_intro) 28 | ) 29 | 30 | 31 | def long_to_bytes(n, blocksize=0): 32 | """long_to_bytes(n:long, blocksize:int) : string 33 | Convert a long integer to a byte string. 34 | If optional blocksize is given and greater than zero, pad the front of the 35 | byte string with binary zeros so that the length is a multiple of 36 | blocksize. 37 | """ 38 | # after much testing, this algorithm was deemed to be the fastest 39 | s = b"" 40 | if USING_PYTHON2: 41 | n = long(n) # noqa 42 | pack = struct.pack 43 | while n > 0: 44 | s = pack(b">I", n & 0xFFFFFFFF) + s 45 | n = n >> 32 46 | # strip off leading zeros 47 | for i in range(len(s)): 48 | if s[i] != b"\000"[0]: 49 | break 50 | else: 51 | # only happens when n == 0 52 | s = b"\000" 53 | i = 0 54 | s = s[i:] 55 | # add back some pad bytes. this could be done more efficiently w.r.t. the 56 | # de-padding being done above, but sigh... 57 | if blocksize > 0 and len(s) % blocksize: 58 | s = (blocksize - len(s) % blocksize) * b"\000" + s 59 | return s 60 | 61 | 62 | def os2ip(arr): 63 | x_len = len(arr) 64 | x = 0 65 | for i in range(x_len): 66 | if USING_PYTHON2: 67 | val = struct.unpack("B", arr[i])[0] 68 | else: 69 | val = arr[i] 70 | x = x + (val * pow(256, x_len - i - 1)) 71 | return x 72 | 73 | 74 | def create_node(name, parent=None, ns="", tail=False, text=False): 75 | """ 76 | Creates a new node 77 | :param name: Node name 78 | :param parent: Node parent 79 | :param ns: Namespace to use 80 | :param tail: Tail to add 81 | :param text: Text of the node 82 | :return: New node 83 | """ 84 | node = etree.Element(etree.QName(ns, name)) 85 | if parent is not None: 86 | parent.append(node) 87 | if tail: 88 | node.tail = tail 89 | if text: 90 | node.text = text 91 | return node 92 | 93 | 94 | def get_rdns_name(rdns): 95 | """ 96 | Gets the rdns String name 97 | :param rdns: RDNS object 98 | :type rdns: cryptography.x509.RelativeDistinguishedName 99 | :return: RDNS name 100 | """ 101 | data = [] 102 | XMLSIG_NAMEOID_TO_NAME = _NAMEOID_TO_NAME.copy() 103 | XMLSIG_NAMEOID_TO_NAME[oid.NameOID.SERIAL_NUMBER] = "SERIALNUMBER" 104 | XMLSIG_NAMEOID_TO_NAME[oid.NameOID.EMAIL_ADDRESS] = "E" 105 | for dn in rdns: 106 | dn_data = [] 107 | for attribute in dn._attributes: 108 | key = XMLSIG_NAMEOID_TO_NAME.get( 109 | attribute.oid, "OID.%s" % attribute.oid.dotted_string 110 | ) 111 | dn_data.insert(0, "{}={}".format(key, _escape_dn_value(attribute.value))) 112 | data.insert(0, "+".join(dn_data)) 113 | return ", ".join(data) 114 | -------------------------------------------------------------------------------- /tests/data/sign5-out.xml: -------------------------------------------------------------------------------- 1 | 2 | HjY8ilZAIEM2tBbPn5mYO1ieIX4= 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Xip8HLjwAy+BG5HXnmv2E5ev9OA= 13 | 14 | 15 | L/uM6kbHlgRr09RRH452+TT/RO3EPtONXFf/gDXBAILnqzGiJ38yK3R1t7FUO0LP 16 | YQNBT0mgesBWajm7DTOWY5c3zyS8eOeCU7PclVdYpuxdbIHiUVkRveALUda2Ah+K 17 | nBInCKMsyJ5H0UXEc8NQVU4EtCX8UxC3I0uT3Fi+pMHt5N85YbM8mCZlg8eqFW7h 18 | Is/W/HJaqV36MUIJ/Dx0wlauwa9+ZTcxEqAAau54q5k4L8iWrzXrluM34esXuHfF 19 | FghH31ewOXorKLptq2mAKxnaGqZMzEROAS9LN7GpJw7CTphm7Yf8o0ZtGB3+nI6h 20 | 3hIs/y0QA1mYY2W5lD5IYQ== 21 | 22 | 23 | E=xmlsec@aleksey.com, CN=Aleksey Sanin, OU=Examples RSA Certificate, O=XML Security Library (http://www.aleksey.com/xmlsec), ST=California, C=US 24 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 25 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 26 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 27 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 28 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 29 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 30 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 31 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 32 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 33 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 34 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 35 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 36 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 37 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 38 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 39 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 40 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 41 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 42 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 43 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 44 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 45 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 46 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 47 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 48 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 49 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 50 | NJ2D 51 | JIQs8tRZIGKLLlyGkKOqMLonGpw= 52 | 53 | E=xmlsec@aleksey.com, CN=Aleksey Sanin, OU=Root Certificate, O=XML Security Library (http://www.aleksey.com/xmlsec), L=Sunnyvale, ST=California, C=US 54 | 5 55 | 56 | 57 | 58 | 59 | l7j+tD+DNXgWiQTsK2GMv8RfAIFKRebZzeniPJc7Ra2q5o0Ld3EHAU98+X3iGard 60 | kVn08c89unhGlhGctltGOXNVI6r3ngBc5elJ7DucP4SZOpCt335khsYmcs4xCHl+ 61 | ExW45b/WVgKNYCFMJxhk+/tVcPYzvS9VcNVefpmupOCqRUcTqDDVoIqdzCDs5I5R 62 | yVTFfz5mLXS/o3r48+yUVzm0rAB1YmFUtNDgUob4XnfsUEOc0rqnjGJavLL+88xi 63 | fiNga8dRSTd4fiUVMKv6tK4ljyL8o0h/8gqKbuD+jfAB7cYzzGuh/aaA7waMr/ZA 64 | Oo5CFCBhEh/j/AWxBdVlww== 65 | AQAB 66 | 67 | 68 | rsakey.pem 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/xmlsig/template.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | from lxml import etree 5 | 6 | from .constants import ID_ATTR, NS_MAP, DSigNs 7 | from .utils import create_node 8 | 9 | 10 | def add_key_name(node, name=False): 11 | node.text = "\n" 12 | key_name = create_node("KeyName", node, DSigNs, tail="\n") 13 | if name: 14 | key_name.text = name 15 | return key_name 16 | 17 | 18 | def add_key_value(node): 19 | return create_node("KeyValue", node, DSigNs, tail="\n") 20 | 21 | 22 | def add_reference(node, digest_method, name=False, uri=False, uri_type=False): 23 | reference = create_node( 24 | "Reference", 25 | node.find("{" + DSigNs + "}SignedInfo"), 26 | DSigNs, 27 | tail="\n", 28 | text="\n", 29 | ) 30 | if name: 31 | reference.set(ID_ATTR, name) 32 | if uri == "": 33 | reference.set("URI", "") 34 | if uri: 35 | reference.set("URI", uri) 36 | if uri_type: 37 | reference.set("Type", uri_type) 38 | digest_method_node = create_node("DigestMethod", reference, DSigNs, tail="\n") 39 | digest_method_node.set("Algorithm", digest_method) 40 | create_node("DigestValue", reference, DSigNs, tail="\n") 41 | return reference 42 | 43 | 44 | def add_transform(node, transform): 45 | transforms_node = node.find("ds:Transforms", namespaces=NS_MAP) 46 | if transforms_node is None: 47 | transforms_node = create_node("Transforms", ns=DSigNs, tail="\n", text="\n") 48 | node.insert(0, transforms_node) 49 | transform_node = create_node("Transform", transforms_node, DSigNs, tail="\n") 50 | transform_node.set("Algorithm", transform) 51 | return transform_node 52 | 53 | 54 | def add_x509_data(node): 55 | node.text = "\n" 56 | return create_node("X509Data", node, DSigNs, tail="\n") 57 | 58 | 59 | def create(c14n_method=False, sign_method=False, name=False, ns="ds"): 60 | node = etree.Element(etree.QName(DSigNs, "Signature"), nsmap={ns: DSigNs}) 61 | node.text = "\n" 62 | if name: 63 | node.set(ID_ATTR, name) 64 | signed_info = create_node("SignedInfo", node, DSigNs, tail="\n", text="\n") 65 | canonicalization = create_node( 66 | "CanonicalizationMethod", signed_info, DSigNs, tail="\n" 67 | ) 68 | canonicalization.set("Algorithm", c14n_method) 69 | signature_method = create_node("SignatureMethod", signed_info, DSigNs, tail="\n") 70 | signature_method.set("Algorithm", sign_method) 71 | create_node("SignatureValue", node, DSigNs, tail="\n") 72 | return node 73 | 74 | 75 | def ensure_key_info(node, name=False): 76 | key_info = node.find("{" + DSigNs + "}KeyInfo") 77 | if key_info is None: 78 | key_info = create_node("KeyInfo", ns=DSigNs, tail="\n") 79 | node.insert(2, key_info) 80 | if name: 81 | key_info.set(ID_ATTR, name) 82 | return key_info 83 | 84 | 85 | def x509_data_add_certificate(node): 86 | node.text = "\n" 87 | return create_node("X509Certificate", node, DSigNs, tail="\n") 88 | 89 | 90 | def x509_data_add_crl(node): 91 | node.text = "\n" 92 | return create_node("X509CRL", node, DSigNs, tail="\n") 93 | 94 | 95 | def x509_data_add_issuer_serial(node): 96 | node.text = "\n" 97 | return create_node("X509IssuerSerial", node, DSigNs, tail="\n") 98 | 99 | 100 | def x509_data_add_ski(node): 101 | node.text = "\n" 102 | return create_node("X509SKI", node, DSigNs, tail="\n") 103 | 104 | 105 | def x509_data_add_subject_name(node): 106 | node.text = "\n" 107 | return create_node("X509SubjectName", node, DSigNs, tail="\n") 108 | 109 | 110 | def x509_issuer_serial_add_issuer_name(node): 111 | node.text = "\n" 112 | return create_node("X509IssuerName", node, DSigNs, tail="\n") 113 | 114 | 115 | def x509_issuer_serial_add_serial_number(node): 116 | node.text = "\n" 117 | return create_node("X509SerialNumber", node, DSigNs, tail="\n") 118 | -------------------------------------------------------------------------------- /src/xmlsig/constants.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | from cryptography.hazmat.primitives import hashes 5 | 6 | from .algorithms import HMACAlgorithm, RSAAlgorithm 7 | from .ns import NS_MAP # noqa:F401 8 | from .ns import DSignNsMore, DSigNs, DSigNs11, EncNs 9 | 10 | ID_ATTR = "Id" 11 | 12 | TransformInclC14N = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" 13 | TransformInclC14NWithComments = TransformInclC14N + "#WithComments" 14 | TransformInclC14N11 = "" 15 | TransformInclC14N11WithComments = "" 16 | TransformExclC14N = "http://www.w3.org/2001/10/xml-exc-c14n#" 17 | TransformExclC14NWithComments = TransformExclC14N + "WithComments" 18 | TransformEnveloped = DSigNs + "enveloped-signature" 19 | TransformXPath = "http://www.w3.org/TR/1999/REC-xpath-19991116" 20 | TransformXPath2 = "" 21 | TransformXPointer = "" 22 | TransformXslt = "http://www.w3.org/TR/1999/REC-xslt-19991116" 23 | TransformRemoveXmlTagsC14N = "" 24 | TransformBase64 = DSigNs + "base64" 25 | TransformVisa3DHack = "" 26 | TransformAes128Cbc = "" 27 | TransformAes192Cbc = "" 28 | TransformAes256Cbc = "" 29 | TransformKWAes128 = "" 30 | TransformKWAes192 = "" 31 | TransformKWAes256 = "" 32 | TransformDes3Cbc = "" 33 | TransformKWDes3 = "" 34 | TransformDsaSha1 = DSigNs + "dsa-sha1" 35 | TransformDsaSha256 = DSigNs11 + "dsa-sha256" 36 | TransformEcdsaSha1 = DSignNsMore + "ecdsa-sha1" 37 | TransformEcdsaSha224 = DSignNsMore + "ecdsa-sha224" 38 | TransformEcdsaSha256 = DSignNsMore + "ecdsa-sha256" 39 | TransformEcdsaSha384 = DSignNsMore + "ecdsa-sha384" 40 | TransformEcdsaSha512 = DSignNsMore + "ecdsa-sha512" 41 | TransformHmacRipemd160 = DSignNsMore + "hmac-ripemd160" 42 | TransformHmacSha1 = DSigNs + "hmac-sha1" 43 | TransformHmacSha224 = DSignNsMore + "hmac-sha224" 44 | TransformHmacSha256 = DSignNsMore + "hmac-sha256" 45 | TransformHmacSha384 = DSignNsMore + "hmac-sha384" 46 | TransformHmacSha512 = DSignNsMore + "hmac-sha512" 47 | TransformRsaMd5 = DSignNsMore + "rsa-md5" 48 | TransformRsaRipemd160 = DSignNsMore + "rsa-ripemd160" 49 | TransformRsaSha1 = DSigNs + "rsa-sha1" 50 | TransformRsaSha224 = DSignNsMore + "rsa-sha224" 51 | TransformRsaSha256 = DSignNsMore + "rsa-sha256" 52 | TransformRsaSha384 = DSignNsMore + "rsa-sha384" 53 | TransformRsaSha512 = DSignNsMore + "rsa-sha512" 54 | TransformRsaPkcs1 = "" 55 | TransformRsaOaep = "" 56 | TransformMd5 = DSignNsMore + "md5" 57 | TransformRipemd160 = EncNs + "ripemd160" 58 | TransformSha1 = DSigNs + "sha1" 59 | TransformSha224 = DSignNsMore + "sha224" 60 | TransformSha256 = EncNs + "sha256" 61 | TransformSha384 = DSignNsMore + "sha384" 62 | TransformSha512 = EncNs + "sha512" 63 | 64 | TransformUsageUnknown = {} 65 | TransformUsageDSigTransform = [TransformEnveloped, TransformBase64] 66 | TransformUsageC14NMethod = { 67 | TransformInclC14N: {"method": "c14n", "exclusive": False, "comments": False}, 68 | TransformInclC14NWithComments: { 69 | "method": "c14n", 70 | "exclusive": False, 71 | "comments": True, 72 | }, 73 | TransformExclC14N: {"method": "c14n", "exclusive": True, "comments": False}, 74 | TransformExclC14NWithComments: { 75 | "method": "c14n", 76 | "exclusive": True, 77 | "comments": False, 78 | }, 79 | } 80 | 81 | TransformUsageDSigTransform.extend(TransformUsageC14NMethod.keys()) 82 | 83 | TransformUsageDigestMethod = { 84 | TransformMd5: "md5", 85 | TransformSha1: "sha1", 86 | TransformSha224: "sha224", 87 | TransformSha256: "sha256", 88 | TransformSha384: "sha384", 89 | TransformSha512: "sha512", 90 | TransformRipemd160: "ripemd160", 91 | } 92 | 93 | TransformUsageSignatureMethod = { 94 | TransformRsaMd5: {"digest": hashes.MD5, "method": RSAAlgorithm}, 95 | TransformRsaSha1: {"digest": hashes.SHA1, "method": RSAAlgorithm}, 96 | TransformRsaSha224: {"digest": hashes.SHA224, "method": RSAAlgorithm}, 97 | TransformRsaSha256: {"digest": hashes.SHA256, "method": RSAAlgorithm}, 98 | TransformRsaSha384: {"digest": hashes.SHA384, "method": RSAAlgorithm}, 99 | TransformRsaSha512: {"digest": hashes.SHA512, "method": RSAAlgorithm}, 100 | TransformHmacSha1: {"digest": hashes.SHA1, "method": HMACAlgorithm}, 101 | TransformHmacSha224: {"digest": hashes.SHA224, "method": HMACAlgorithm}, 102 | TransformHmacSha256: {"digest": hashes.SHA256, "method": HMACAlgorithm}, 103 | TransformHmacSha384: {"digest": hashes.SHA384, "method": HMACAlgorithm}, 104 | TransformHmacSha512: {"digest": hashes.SHA512, "method": HMACAlgorithm}, 105 | } 106 | 107 | TransformUsageEncryptionMethod = {} 108 | TransformUsageAny = {} 109 | -------------------------------------------------------------------------------- /tests/data/rsacert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 (0x5) 5 | Signature Algorithm: md5WithRSAEncryption 6 | Issuer: 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 7 | Validity 8 | Not Before: Mar 31 04:02:22 2003 GMT 9 | Not After : Mar 28 04:02:22 2013 GMT 10 | Subject: C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Examples RSA Certificate, CN=Aleksey Sanin/emailAddress=xmlsec@aleksey.com 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | RSA Public Key: (2048 bit) 14 | Modulus (2048 bit): 15 | 00:97:b8:fe:b4:3f:83:35:78:16:89:04:ec:2b:61: 16 | 8c:bf:c4:5f:00:81:4a:45:e6:d9:cd:e9:e2:3c:97: 17 | 3b:45:ad:aa:e6:8d:0b:77:71:07:01:4f:7c:f9:7d: 18 | e2:19:aa:dd:91:59:f4:f1:cf:3d:ba:78:46:96:11: 19 | 9c:b6:5b:46:39:73:55:23:aa:f7:9e:00:5c:e5:e9: 20 | 49:ec:3b:9c:3f:84:99:3a:90:ad:df:7e:64:86:c6: 21 | 26:72:ce:31:08:79:7e:13:15:b8:e5:bf:d6:56:02: 22 | 8d:60:21:4c:27:18:64:fb:fb:55:70:f6:33:bd:2f: 23 | 55:70:d5:5e:7e:99:ae:a4:e0:aa:45:47:13:a8:30: 24 | d5:a0:8a:9d:cc:20:ec:e4:8e:51:c9:54:c5:7f:3e: 25 | 66:2d:74:bf:a3:7a:f8:f3:ec:94:57:39:b4:ac:00: 26 | 75:62:61:54:b4:d0:e0:52:86:f8:5e:77:ec:50:43: 27 | 9c:d2:ba:a7:8c:62:5a:bc:b2:fe:f3:cc:62:7e:23: 28 | 60:6b:c7:51:49:37:78:7e:25:15:30:ab:fa:b4:ae: 29 | 25:8f:22:fc:a3:48:7f:f2:0a:8a:6e:e0:fe:8d:f0: 30 | 01:ed:c6:33:cc:6b:a1:fd:a6:80:ef:06:8c:af:f6: 31 | 40:3a:8e:42:14:20:61:12:1f:e3:fc:05:b1:05:d5: 32 | 65:c3 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Basic Constraints: 36 | CA:FALSE 37 | Netscape Comment: 38 | OpenSSL Generated Certificate 39 | X509v3 Subject Key Identifier: 40 | 24:84:2C:F2:D4:59:20:62:8B:2E:5C:86:90:A3:AA:30:BA:27:1A:9C 41 | X509v3 Authority Key Identifier: 42 | keyid:B4:B9:EF:9A:E6:97:0E:68:65:1E:98:CE:FA:55:0D:89:06:DB:4C:7C 43 | 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 44 | serial:00 45 | 46 | Signature Algorithm: md5WithRSAEncryption 47 | b5:3f:9b:32:31:4a:ff:2f:84:3b:a8:9b:11:5c:a6:5c:f0:76: 48 | 52:d9:6e:f4:90:ad:fa:0d:90:c1:98:d5:4a:12:dd:82:6b:37: 49 | e8:d9:2d:62:92:c9:61:37:98:86:8f:a4:49:6a:5e:25:d0:18: 50 | 69:30:0f:98:8f:43:58:89:31:b2:3b:05:e2:ef:c7:a6:71:5f: 51 | f7:fe:73:c5:a7:b2:cd:2e:73:53:71:7d:a8:4c:68:1a:32:1b: 52 | 5e:48:2f:8f:9b:7a:a3:b5:f3:67:e8:b1:a2:89:4e:b2:4d:1b: 53 | 79:9c:ff:f0:0d:19:4f:4e:b1:03:3d:99:f0:44:b7:8a:0b:34: 54 | 9d:83 55 | -----BEGIN CERTIFICATE----- 56 | MIIE3zCCBEigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCVVMx 57 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 58 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 59 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 60 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMB4X 61 | DTAzMDMzMTA0MDIyMloXDTEzMDMyODA0MDIyMlowgb8xCzAJBgNVBAYTAlVTMRMw 62 | EQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFy 63 | eSAoaHR0cDovL3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMSEwHwYDVQQLExhFeGFt 64 | cGxlcyBSU0EgQ2VydGlmaWNhdGUxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf 65 | BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCCASIwDQYJKoZIhvcNAQEB 66 | BQADggEPADCCAQoCggEBAJe4/rQ/gzV4FokE7CthjL/EXwCBSkXm2c3p4jyXO0Wt 67 | quaNC3dxBwFPfPl94hmq3ZFZ9PHPPbp4RpYRnLZbRjlzVSOq954AXOXpSew7nD+E 68 | mTqQrd9+ZIbGJnLOMQh5fhMVuOW/1lYCjWAhTCcYZPv7VXD2M70vVXDVXn6ZrqTg 69 | qkVHE6gw1aCKncwg7OSOUclUxX8+Zi10v6N6+PPslFc5tKwAdWJhVLTQ4FKG+F53 70 | 7FBDnNK6p4xiWryy/vPMYn4jYGvHUUk3eH4lFTCr+rSuJY8i/KNIf/IKim7g/o3w 71 | Ae3GM8xrof2mgO8GjK/2QDqOQhQgYRIf4/wFsQXVZcMCAwEAAaOCAVcwggFTMAkG 72 | A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp 73 | ZmljYXRlMB0GA1UdDgQWBBQkhCzy1FkgYosuXIaQo6owuicanDCB+AYDVR0jBIHw 74 | MIHtgBS0ue+a5pcOaGUemM76VQ2JBttMfKGB0aSBzjCByzELMAkGA1UEBhMCVVMx 75 | EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTE9MDsGA1UE 76 | ChM0WE1MIFNlY3VyaXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20v 77 | eG1sc2VjKTEZMBcGA1UECxMQUm9vdCBDZXJ0aWZpY2F0ZTEWMBQGA1UEAxMNQWxl 78 | a3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tggEA 79 | MA0GCSqGSIb3DQEBBAUAA4GBALU/mzIxSv8vhDuomxFcplzwdlLZbvSQrfoNkMGY 80 | 1UoS3YJrN+jZLWKSyWE3mIaPpElqXiXQGGkwD5iPQ1iJMbI7BeLvx6ZxX/f+c8Wn 81 | ss0uc1NxfahMaBoyG15IL4+beqO182fosaKJTrJNG3mc//ANGU9OsQM9mfBEt4oL 82 | NJ2D 83 | -----END CERTIFICATE----- 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /src/xmlsig/data/xmldsig-core-schema.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | ]> 11 | 12 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 222 | 223 | 224 | 225 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | -------------------------------------------------------------------------------- /tests/test_rsa.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from os import path 3 | 4 | from cryptography.hazmat.backends import default_backend 5 | from cryptography.hazmat.primitives import serialization 6 | from cryptography.hazmat.primitives.serialization import pkcs12 7 | from cryptography.x509 import load_pem_x509_certificate 8 | 9 | import xmlsig 10 | 11 | from .base import BASE_DIR, compare, parse_xml 12 | 13 | 14 | class TestSignature(unittest.TestCase): 15 | def test_sign_generated_template_pem_with_x509(self): 16 | """ 17 | Should sign a file using a dynamicaly created template, key from PEM 18 | file and an X509 certificate. 19 | """ 20 | 21 | # Load document file. 22 | template = parse_xml("data/sign-doc.xml") 23 | 24 | # Create a signature template for RSA-SHA1 enveloped signature. 25 | sign = xmlsig.template.create( 26 | c14n_method=xmlsig.constants.TransformExclC14N, 27 | sign_method=xmlsig.constants.TransformRsaSha1, 28 | ns=None, 29 | ) 30 | 31 | assert sign is not None 32 | 33 | # Add the node to the document. 34 | template.append(sign) 35 | 36 | # Add the node to the signature template. 37 | ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) 38 | 39 | # Add the enveloped transform descriptor. 40 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 41 | 42 | # Add the and nodes. 43 | key_info = xmlsig.template.ensure_key_info(sign) 44 | x509_data = xmlsig.template.add_x509_data(key_info) 45 | xmlsig.template.x509_data_add_certificate(x509_data) 46 | # Create a digital signature context (no key manager is needed). 47 | # Load private key (assuming that there is no password). 48 | # Set the key on the context. 49 | ctx = xmlsig.SignatureContext() 50 | 51 | with open(path.join(BASE_DIR, "data/keyStore.p12"), "rb") as key_file: 52 | ctx.load_pkcs12(pkcs12.load_key_and_certificates(key_file.read(), None)) 53 | # Sign the template. 54 | ctx.sign(sign) 55 | ctx.verify(sign) 56 | # Assert the contents of the XML document against the expected result. 57 | compare("data/sign-res.xml", template) 58 | 59 | def test_sign_generated_template_pem_with_x509_openssl(self): 60 | """ 61 | Should sign a file using a dynamicaly created template, key from PEM 62 | file and an X509 certificate. 63 | """ 64 | 65 | # Load document file. 66 | template = parse_xml("data/sign-doc.xml") 67 | 68 | # Create a signature template for RSA-SHA1 enveloped signature. 69 | sign = xmlsig.template.create( 70 | c14n_method=xmlsig.constants.TransformExclC14N, 71 | sign_method=xmlsig.constants.TransformRsaSha1, 72 | ns=None, 73 | ) 74 | 75 | assert sign is not None 76 | 77 | # Add the node to the document. 78 | template.append(sign) 79 | 80 | # Add the node to the signature template. 81 | ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) 82 | 83 | # Add the enveloped transform descriptor. 84 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 85 | 86 | # Add the and nodes. 87 | key_info = xmlsig.template.ensure_key_info(sign) 88 | x509_data = xmlsig.template.add_x509_data(key_info) 89 | xmlsig.template.x509_data_add_certificate(x509_data) 90 | # Create a digital signature context (no key manager is needed). 91 | # Load private key (assuming that there is no password). 92 | # Set the key on the context. 93 | ctx = xmlsig.SignatureContext() 94 | 95 | with open(path.join(BASE_DIR, "data/keyStore.p12"), "rb") as key_file: 96 | ctx.load_pkcs12(pkcs12.load_key_and_certificates(key_file.read(), None)) 97 | # Sign the template. 98 | ctx.sign(sign) 99 | ctx.verify(sign) 100 | # Assert the contents of the XML document against the expected result. 101 | compare("data/sign-res.xml", template) 102 | 103 | def test_sign_case1(self): 104 | """Should sign a pre-constructed template file using a key from a PEM file.""" 105 | root = parse_xml("data/sign1-in.xml") 106 | sign = root.xpath("//ds:Signature", namespaces={"ds": xmlsig.constants.DSigNs})[ 107 | 0 108 | ] 109 | self.assertIsNotNone(sign) 110 | 111 | ctx = xmlsig.SignatureContext() 112 | with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: 113 | ctx.private_key = serialization.load_pem_private_key( 114 | key_file.read(), password=None, backend=default_backend() 115 | ) 116 | ctx.key_name = "rsakey.pem" 117 | self.assertEqual("rsakey.pem", ctx.key_name) 118 | 119 | ctx.sign(sign) 120 | ctx.verify(sign) 121 | compare("data/sign1-out.xml", root) 122 | 123 | def test_sign_case2(self): 124 | """Should sign a dynamicaly constructed template file using 125 | a key from a PEM file.""" 126 | root = parse_xml("data/sign2-in.xml") 127 | sign = xmlsig.template.create( 128 | c14n_method=xmlsig.constants.TransformExclC14N, 129 | sign_method=xmlsig.constants.TransformRsaSha1, 130 | ns=None, 131 | ) 132 | self.assertIsNotNone(sign) 133 | root.append(sign) 134 | ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) 135 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 136 | ki = xmlsig.template.ensure_key_info(sign) 137 | xmlsig.template.add_key_name(ki) 138 | xmlsig.template.add_key_value(ki) 139 | 140 | ctx = xmlsig.SignatureContext() 141 | with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: 142 | ctx.private_key = serialization.load_pem_private_key( 143 | key_file.read(), password=None, backend=default_backend() 144 | ) 145 | ctx.key_name = "rsakey.pem" 146 | self.assertEqual("rsakey.pem", ctx.key_name) 147 | ctx.sign(sign) 148 | with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: 149 | x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) 150 | ctx.verify(sign) 151 | ctx.public_key = x509.public_key() 152 | ctx.verify(sign) 153 | compare("data/sign2-out.xml", root) 154 | 155 | def test_sign_case3(self): 156 | """Should sign a file using a dynamicaly created template, key from 157 | PEM and an X509 cert.""" 158 | root = parse_xml("data/sign3-in.xml") 159 | sign = xmlsig.template.create( 160 | xmlsig.constants.TransformExclC14N, 161 | xmlsig.constants.TransformRsaSha1, 162 | ns=None, 163 | ) 164 | self.assertIsNotNone(sign) 165 | root.append(sign) 166 | ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) 167 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 168 | ki = xmlsig.template.ensure_key_info(sign) 169 | xmlsig.template.x509_data_add_certificate(xmlsig.template.add_x509_data(ki)) 170 | 171 | ctx = xmlsig.SignatureContext() 172 | with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: 173 | ctx.private_key = serialization.load_pem_private_key( 174 | key_file.read(), password=None, backend=default_backend() 175 | ) 176 | ctx.key_name = "rsakey.pem" 177 | self.assertEqual("rsakey.pem", ctx.key_name) 178 | with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: 179 | ctx.x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) 180 | ctx.public_key = ctx.x509.public_key() 181 | ctx.sign(sign) 182 | ctx.verify(sign) 183 | compare("data/sign3-out.xml", root) 184 | 185 | def test_sign_case4(self): 186 | """Should sign a file using a dynamically created template, key from PEM 187 | and an X509 cert with custom ns.""" 188 | 189 | root = parse_xml("data/sign4-in.xml") 190 | elem_id = root.get("ID", None) 191 | if elem_id: 192 | elem_id = "#" + elem_id 193 | sign = xmlsig.template.create( 194 | c14n_method=xmlsig.constants.TransformExclC14N, 195 | sign_method=xmlsig.constants.TransformRsaSha1, 196 | ns="ds", 197 | ) 198 | self.assertIsNotNone(sign) 199 | root.append(sign) 200 | ref = xmlsig.template.add_reference( 201 | sign, xmlsig.constants.TransformSha1, uri=elem_id 202 | ) 203 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 204 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformExclC14N) 205 | ki = xmlsig.template.ensure_key_info(sign) 206 | xmlsig.template.x509_data_add_certificate(xmlsig.template.add_x509_data(ki)) 207 | ctx = xmlsig.SignatureContext() 208 | with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: 209 | ctx.private_key = serialization.load_pem_private_key( 210 | key_file.read(), password=None, backend=default_backend() 211 | ) 212 | ctx.key_name = "rsakey.pem" 213 | self.assertEqual("rsakey.pem", ctx.key_name) 214 | with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: 215 | ctx.x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) 216 | ctx.sign(sign) 217 | ctx.verify(sign) 218 | compare("data/sign4-out.xml", root) 219 | 220 | def test_sign_case5(self): 221 | """Should sign a file using a dynamicaly created template, key from 222 | PEM file and an X509 certificate.""" 223 | root = parse_xml("data/sign5-in.xml") 224 | sign = xmlsig.template.create( 225 | c14n_method=xmlsig.constants.TransformExclC14N, 226 | sign_method=xmlsig.constants.TransformRsaSha256, 227 | ns=None, 228 | name="S1", 229 | ) 230 | self.assertIsNotNone(sign) 231 | root.append(sign) 232 | ref = xmlsig.template.add_reference( 233 | sign, xmlsig.constants.TransformSha1, name="R1", uri_type="Type" 234 | ) 235 | xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) 236 | xmlsig.template.ensure_key_info(sign, name="KI1") 237 | ki = xmlsig.template.ensure_key_info(sign) 238 | x509 = xmlsig.template.add_x509_data(ki) 239 | xmlsig.template.x509_data_add_subject_name(x509) 240 | xmlsig.template.x509_data_add_certificate(x509) 241 | xmlsig.template.x509_data_add_ski(x509) 242 | x509_issuer_serial = xmlsig.template.x509_data_add_issuer_serial(x509) 243 | xmlsig.template.x509_issuer_serial_add_issuer_name(x509_issuer_serial) 244 | xmlsig.template.x509_issuer_serial_add_serial_number(x509_issuer_serial) 245 | xmlsig.template.add_key_value(ki) 246 | xmlsig.template.add_key_name(ki, "rsakey.pem") 247 | ctx = xmlsig.SignatureContext() 248 | with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: 249 | ctx.private_key = serialization.load_pem_private_key( 250 | key_file.read(), password=None, backend=default_backend() 251 | ) 252 | with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: 253 | ctx.x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) 254 | ctx.sign(sign) 255 | ctx.verify(sign) 256 | compare("data/sign5-out.xml", root) 257 | 258 | def test_sign_case6(self): 259 | """Should sign a pre-constructed template file using a key from a PEM file.""" 260 | root = parse_xml("data/sign6-in.xml") 261 | sign = root.xpath("//ds:Signature", namespaces={"ds": xmlsig.constants.DSigNs})[ 262 | 0 263 | ] 264 | self.assertIsNotNone(sign) 265 | 266 | ctx = xmlsig.SignatureContext() 267 | with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: 268 | ctx.private_key = serialization.load_pem_private_key( 269 | key_file.read(), password=None, backend=default_backend() 270 | ) 271 | ctx.key_name = "rsakey.pem" 272 | self.assertEqual("rsakey.pem", ctx.key_name) 273 | 274 | ctx.sign(sign) 275 | ctx.verify(sign) 276 | compare("data/sign6-out.xml", root) 277 | 278 | def test_fail_reference(self): 279 | """Should sign a dynamicaly constructed template file using a key from a PEM file.""" 280 | root = parse_xml("data/sign-fail_reference.xml") 281 | sign = root.xpath("//ds:Signature", namespaces={"ds": xmlsig.constants.DSigNs})[ 282 | 0 283 | ] 284 | 285 | ctx = xmlsig.SignatureContext() 286 | with self.assertRaises(Exception): 287 | ctx.verify(sign) 288 | 289 | def test_fail_signature(self): 290 | """Should sign a dynamicaly constructed template file using a key from a PEM file.""" 291 | root = parse_xml("data/sign-fail_signature.xml") 292 | sign = root.xpath("//ds:Signature", namespaces={"ds": xmlsig.constants.DSigNs})[ 293 | 0 294 | ] 295 | 296 | ctx = xmlsig.SignatureContext() 297 | with self.assertRaises(Exception): 298 | ctx.verify(sign) 299 | -------------------------------------------------------------------------------- /src/xmlsig/signature_context.py: -------------------------------------------------------------------------------- 1 | # © 2017 Creu Blanca 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). 3 | 4 | import base64 5 | import hashlib 6 | from os import path 7 | 8 | from cryptography.hazmat.primitives import serialization 9 | from cryptography.x509.oid import ExtensionOID 10 | from lxml import etree 11 | 12 | from . import constants 13 | from .utils import b64_print, create_node, get_rdns_name 14 | 15 | try: 16 | import OpenSSL 17 | except ImportError: 18 | OpenSSL = None 19 | 20 | 21 | class SignatureContext(object): 22 | """ 23 | Signature context is used to sign and verify Signature nodes with keys 24 | """ 25 | 26 | def __init__(self): 27 | self.x509 = None 28 | self.crl = None 29 | self.private_key = None 30 | self.public_key = None 31 | self.key_name = None 32 | self.ca_certificates = [] 33 | 34 | def sign(self, node): 35 | """ 36 | Signs a Signature node 37 | :param node: Signature node 38 | :type node: lxml.etree.Element 39 | :return: None 40 | """ 41 | signed_info = node.find("ds:SignedInfo", namespaces=constants.NS_MAP) 42 | signature_method = signed_info.find( 43 | "ds:SignatureMethod", namespaces=constants.NS_MAP 44 | ).get("Algorithm") 45 | key_info = node.find("ds:KeyInfo", namespaces=constants.NS_MAP) 46 | if key_info is not None: 47 | self.fill_key_info(key_info, signature_method) 48 | self.fill_signed_info(signed_info) 49 | self.calculate_signature(node) 50 | 51 | def fill_key_info(self, key_info, signature_method): 52 | """ 53 | Fills the KeyInfo node 54 | :param key_info: KeyInfo node 55 | :type key_info: lxml.etree.Element 56 | :param signature_method: Signature node to use 57 | :type signature_method: str 58 | :return: None 59 | """ 60 | x509_data = key_info.find("ds:X509Data", namespaces=constants.NS_MAP) 61 | if x509_data is not None: 62 | self.fill_x509_data(x509_data) 63 | key_name = key_info.find("ds:KeyName", namespaces=constants.NS_MAP) 64 | if key_name is not None and self.key_name is not None: 65 | key_name.text = self.key_name 66 | key_value = key_info.find("ds:KeyValue", namespaces=constants.NS_MAP) 67 | if key_value is not None: 68 | key_value.text = "\n" 69 | signature = constants.TransformUsageSignatureMethod[signature_method] 70 | key = self.public_key 71 | if self.public_key is None: 72 | key = self.private_key.public_key() 73 | if not isinstance(key, signature["method"].public_key_class): 74 | raise Exception("Key not compatible with signature method") 75 | signature["method"].key_value(key_value, key) 76 | 77 | def fill_x509_data(self, x509_data): 78 | """ 79 | Fills the X509Data Node 80 | :param x509_data: X509Data Node 81 | :type x509_data: lxml.etree.Element 82 | :return: None 83 | """ 84 | x509_issuer_serial = x509_data.find( 85 | "ds:X509IssuerSerial", namespaces=constants.NS_MAP 86 | ) 87 | if x509_issuer_serial is not None: 88 | self.fill_x509_issuer_name(x509_issuer_serial) 89 | 90 | x509_crl = x509_data.find("ds:X509CRL", namespaces=constants.NS_MAP) 91 | if x509_crl is not None and self.crl is not None: 92 | x509_data.text = base64.b64encode( 93 | self.crl.public_bytes(serialization.Encoding.DER) 94 | ) 95 | x509_subject = x509_data.find("ds:X509SubjectName", namespaces=constants.NS_MAP) 96 | if x509_subject is not None: 97 | x509_subject.text = get_rdns_name(self.x509.subject.rdns) 98 | x509_ski = x509_data.find("ds:X509SKI", namespaces=constants.NS_MAP) 99 | if x509_ski is not None: 100 | x509_ski.text = base64.b64encode( 101 | self.x509.extensions.get_extension_for_oid( 102 | ExtensionOID.SUBJECT_KEY_IDENTIFIER 103 | ).value.digest 104 | ) 105 | x509_certificate = x509_data.find( 106 | "ds:X509Certificate", namespaces=constants.NS_MAP 107 | ) 108 | if x509_certificate is not None: 109 | s = base64.b64encode( 110 | self.x509.public_bytes(encoding=serialization.Encoding.DER) 111 | ) 112 | x509_certificate.text = b64_print(s) 113 | for certificate in self.ca_certificates: 114 | certificate_node = create_node( 115 | "X509Certificate", None, constants.DSigNs, tail="\n" 116 | ) 117 | certificate_node.text = b64_print( 118 | base64.b64encode( 119 | certificate.public_bytes(encoding=serialization.Encoding.DER) 120 | ) 121 | ) 122 | x509_certificate.addnext(certificate_node) 123 | 124 | def fill_x509_issuer_name(self, x509_issuer_serial): 125 | """ 126 | Fills the X509IssuerSerial node 127 | :param x509_issuer_serial: X509IssuerSerial node 128 | :type x509_issuer_serial: lxml.etree.Element 129 | :return: None 130 | """ 131 | x509_issuer_name = x509_issuer_serial.find( 132 | "ds:X509IssuerName", namespaces=constants.NS_MAP 133 | ) 134 | if x509_issuer_name is not None: 135 | x509_issuer_name.text = get_rdns_name(self.x509.issuer.rdns) 136 | x509_issuer_number = x509_issuer_serial.find( 137 | "ds:X509SerialNumber", namespaces=constants.NS_MAP 138 | ) 139 | if x509_issuer_number is not None: 140 | x509_issuer_number.text = str(self.x509.serial_number) 141 | 142 | def fill_signed_info(self, signed_info): 143 | """ 144 | Fills the SignedInfo node 145 | :param signed_info: SignedInfo node 146 | :type signed_info: lxml.etree.Element 147 | :return: None 148 | """ 149 | for reference in signed_info.findall( 150 | "ds:Reference", namespaces=constants.NS_MAP 151 | ): 152 | self.calculate_reference(reference, True) 153 | 154 | def verify(self, node): 155 | """ 156 | Verifies a signature 157 | :param node: Signature node 158 | :type node: lxml.etree.Element 159 | :return: None 160 | """ 161 | # Added XSD Validation 162 | with open( 163 | path.join(path.dirname(__file__), "data/xmldsig-core-schema.xsd"), "rb" 164 | ) as file: 165 | schema = etree.XMLSchema(etree.fromstring(file.read())) 166 | schema.assertValid(node) 167 | # Validates reference value 168 | signed_info = node.find("ds:SignedInfo", namespaces=constants.NS_MAP) 169 | for reference in signed_info.findall( 170 | "ds:Reference", namespaces=constants.NS_MAP 171 | ): 172 | if not self.calculate_reference(reference, False): 173 | raise Exception( 174 | 'Reference with URI:"' + reference.get("URI", "") + '" failed' 175 | ) 176 | # Validates signature value 177 | self.calculate_signature(node, False) 178 | 179 | def transform(self, transform, node): 180 | """ 181 | Transforms a node following the transform especification 182 | :param transform: Transform node 183 | :type transform: lxml.etree.Element 184 | :param node: Element to transform 185 | :type node: str 186 | :return: Transformed node in a String 187 | """ 188 | method = transform.get("Algorithm") 189 | if method not in constants.TransformUsageDSigTransform: 190 | raise Exception("Method not allowed") 191 | # C14N methods are allowed 192 | if method in constants.TransformUsageC14NMethod: 193 | return self.canonicalization(method, etree.fromstring(node)) 194 | # Enveloped method removes the Signature Node from the element 195 | if method == constants.TransformEnveloped: 196 | tree = transform.getroottree() 197 | root = etree.fromstring(node) 198 | signature = root.find( 199 | tree.getelementpath( 200 | transform.getparent().getparent().getparent().getparent() 201 | ) 202 | ) 203 | previous = signature.getprevious() 204 | if previous is not None and signature.tail: 205 | previous.tail = "".join([previous.tail or "", signature.tail or ""]) 206 | elif signature.tail: 207 | signature.getparent().text = "".join( 208 | [signature.getparent().text or "", signature.tail or ""] 209 | ) 210 | # When removing the signature node, we need to keep the tail 211 | root.remove(signature) 212 | return self.canonicalization(constants.TransformInclC14N, root) 213 | if method == constants.TransformBase64: 214 | try: 215 | root = etree.fromstring(node) 216 | return base64.b64decode(root.text) 217 | except Exception: 218 | return base64.b64decode(node) 219 | 220 | raise Exception("Method not found") 221 | 222 | def canonicalization(self, method, node): 223 | """ 224 | Canonicalizes a node following the method 225 | :param method: Method identification 226 | :type method: str 227 | :param node: object to canonicalize 228 | :type node: str 229 | :return: Canonicalized node in a String 230 | """ 231 | if method not in constants.TransformUsageC14NMethod: 232 | raise Exception("Method not allowed: " + method) 233 | c14n_method = constants.TransformUsageC14NMethod[method] 234 | return etree.tostring( 235 | node, 236 | method=c14n_method["method"], 237 | with_comments=c14n_method["comments"], 238 | exclusive=c14n_method["exclusive"], 239 | ) 240 | 241 | def digest(self, method, node): 242 | """ 243 | Returns the digest of an object from a method name 244 | :param method: hash method 245 | :type method: str 246 | :param node: Object to hash 247 | :type node: str 248 | :return: hash result 249 | """ 250 | if method not in constants.TransformUsageDigestMethod: 251 | raise Exception("Method not allowed") 252 | lib = hashlib.new(constants.TransformUsageDigestMethod[method]) 253 | lib.update(node) 254 | return base64.b64encode(lib.digest()) 255 | 256 | def get_uri(self, uri, reference): 257 | """ 258 | It returns the node of the specified URI 259 | :param uri: uri of the 260 | :type uri: str 261 | :param reference: Reference node 262 | :type reference: etree.lxml.Element 263 | :return: Element of the URI in a String 264 | """ 265 | if uri == "": 266 | return self.canonicalization( 267 | constants.TransformInclC14N, reference.getroottree() 268 | ) 269 | if uri.startswith("#"): 270 | query = "//*[@*[local-name() = '{}' ]=$uri]" 271 | node = reference.getroottree() 272 | results = self.check_uri_attr(node, query, uri, constants.ID_ATTR) 273 | if len(results) == 0: 274 | results = self.check_uri_attr(node, query, uri, "ID") 275 | if len(results) == 0: 276 | results = self.check_uri_attr(node, query, uri, "Id") 277 | if len(results) == 0: 278 | results = self.check_uri_attr(node, query, uri, "id") 279 | if len(results) > 1: 280 | raise Exception( 281 | "Ambiguous reference URI {} resolved to {} nodes".format( 282 | uri, len(results) 283 | ) 284 | ) 285 | elif len(results) == 1: 286 | return self.canonicalization(constants.TransformInclC14N, results[0]) 287 | raise Exception('URI "' + uri + '" cannot be readed') 288 | 289 | def check_uri_attr(self, node, xpath_query, uri, attr): 290 | return node.xpath(xpath_query.format(attr), uri=uri.lstrip("#")) 291 | 292 | def calculate_reference(self, reference, sign=True): 293 | """ 294 | Calculates or verifies the digest of the reference 295 | :param reference: Reference node 296 | :type reference: lxml.etree.Element 297 | :param sign: It marks if we must sign or check a signature 298 | :type sign: bool 299 | :return: None 300 | """ 301 | node = self.get_uri(reference.get("URI", ""), reference) 302 | transforms = reference.find("ds:Transforms", namespaces=constants.NS_MAP) 303 | if transforms is not None: 304 | for transform in transforms.findall( 305 | "ds:Transform", namespaces=constants.NS_MAP 306 | ): 307 | node = self.transform(transform, node) 308 | digest_value = self.digest( 309 | reference.find("ds:DigestMethod", namespaces=constants.NS_MAP).get( 310 | "Algorithm" 311 | ), 312 | node, 313 | ) 314 | if not sign: 315 | return ( 316 | digest_value.decode() 317 | == reference.find("ds:DigestValue", namespaces=constants.NS_MAP).text 318 | ) 319 | 320 | reference.find( 321 | "ds:DigestValue", namespaces=constants.NS_MAP 322 | ).text = digest_value 323 | 324 | def calculate_signature(self, node, sign=True): 325 | """ 326 | Calculate or verifies the signature 327 | :param node: Signature node 328 | :type node: lxml.etree.Element 329 | :param sign: It checks if it must calculate or verify 330 | :type sign: bool 331 | :return: None 332 | """ 333 | signed_info_xml = etree.fromstring( 334 | etree.tostring(node.find("ds:SignedInfo", namespaces=constants.NS_MAP)) 335 | ) 336 | # We need to extract it again in order to avoid namespace override. 337 | canonicalization_method = signed_info_xml.find( 338 | "ds:CanonicalizationMethod", namespaces=constants.NS_MAP 339 | ).get("Algorithm") 340 | signature_method = signed_info_xml.find( 341 | "ds:SignatureMethod", namespaces=constants.NS_MAP 342 | ).get("Algorithm") 343 | if signature_method not in constants.TransformUsageSignatureMethod: 344 | raise Exception("Method " + signature_method + " not accepted") 345 | signature = constants.TransformUsageSignatureMethod[signature_method] 346 | signed_info = self.canonicalization(canonicalization_method, signed_info_xml) 347 | if not sign: 348 | signature_value = node.find( 349 | "ds:SignatureValue", namespaces=constants.NS_MAP 350 | ).text 351 | public_key = signature["method"].get_public_key(node, self) 352 | signature["method"].verify( 353 | signature_value, signed_info, public_key, signature["digest"] 354 | ) 355 | else: 356 | node.find( 357 | "ds:SignatureValue", namespaces=constants.NS_MAP 358 | ).text = b64_print( 359 | base64.b64encode( 360 | signature["method"].sign( 361 | signed_info, self.private_key, signature["digest"] 362 | ) 363 | ) 364 | ) 365 | 366 | def load_pkcs12(self, key): 367 | """ 368 | This function fills the context public_key, private_key and x509 from 369 | PKCS12 Object 370 | :param key: the PKCS12 Object 371 | :type key: Union[OpenSSL.crypto.PKCS12, tuple] 372 | :return: None 373 | """ 374 | if OpenSSL is not None and isinstance(key, OpenSSL.crypto.PKCS12): 375 | # This would happen if we are using pyOpenSSL 376 | self.x509 = key.get_certificate().to_cryptography() 377 | self.public_key = key.get_certificate().to_cryptography().public_key() 378 | self.private_key = key.get_privatekey().to_cryptography_key() 379 | elif isinstance(key, tuple): 380 | # This would happen if we are using cryptography 381 | self.x509 = key[1] 382 | self.public_key = key[1].public_key() 383 | self.private_key = key[0] 384 | else: 385 | raise NotImplementedError() 386 | --------------------------------------------------------------------------------