diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index eaffc880..a9462d65 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 0.38.0
+current_version = 0.39.0
commit = True
tag = False
message = chore: Bump version from {current_version} to {new_version}
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index b72c97d3..0b87ff0d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -34,10 +34,10 @@ jobs:
steps:
- name: Check Out VCS Repository
- uses: actions/checkout@v4.2.0
+ uses: actions/checkout@v4.2.2
- name: Set Up Python ${{ matrix.python_version }}
- uses: actions/setup-python@v5.2.0
+ uses: actions/setup-python@v5.3.0
with:
python-version: "${{ matrix.python_version }}"
@@ -45,7 +45,7 @@ jobs:
run: make python-virtualenv PYTHON_VIRTUALENV_DIR="venv"
- name: Restoring/Saving Cache
- uses: actions/cache@v4.0.2
+ uses: actions/cache@v4.1.2
with:
path: "venv"
key: py-v1-deps-${{ runner.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }}
@@ -75,15 +75,15 @@ jobs:
steps:
- name: Check Out VCS Repository
- uses: actions/checkout@v4.2.0
+ uses: actions/checkout@v4.2.2
- name: Set Up Python ${{ matrix.python_version }}
- uses: actions/setup-python@v5.2.0
+ uses: actions/setup-python@v5.3.0
with:
python-version: "${{ matrix.python_version }}"
- name: Restoring/Saving Cache
- uses: actions/cache@v4.0.2
+ uses: actions/cache@v4.1.2
with:
path: "venv"
key: py-v1-deps-${{ runner.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }}
@@ -122,7 +122,7 @@ jobs:
make test-coverage-report
- name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v4.5.0
+ uses: codecov/codecov-action@v5.0.7
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./test-reports/coverage/
@@ -137,7 +137,7 @@ jobs:
- name: Store Artifacts
if: ${{ always() }}
- uses: actions/upload-artifact@v4.4.0
+ uses: actions/upload-artifact@v4.4.3
with:
name: test_reports_${{ matrix.python_version }}
path: test-reports/
diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml
index 801cda46..e453b58a 100644
--- a/.github/workflows/dependency-review.yaml
+++ b/.github/workflows/dependency-review.yaml
@@ -21,9 +21,9 @@ jobs:
steps:
- name: Check Out VCS Repository
- uses: actions/checkout@v4.2.0
+ uses: actions/checkout@v4.2.2
- name: Dependency Review
- uses: actions/dependency-review-action@v4.3.4
+ uses: actions/dependency-review-action@v4.5.0
with:
fail-on-severity: critical
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index c96e4ec4..8875905b 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -38,16 +38,16 @@ jobs:
steps:
- name: Check Out VCS Repository
- uses: actions/checkout@v4.2.0
+ uses: actions/checkout@v4.2.2
- name: Set Up Python
id: set_up_python
- uses: actions/setup-python@v5.2.0
+ uses: actions/setup-python@v5.3.0
with:
python-version: "3.10.9"
- name: Restoring/Saving Cache
- uses: actions/cache@v4.0.2
+ uses: actions/cache@v4.1.2
with:
path: "venv"
key: py-v1-deps-${{ runner.os }}-${{ steps.set_up_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }}
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 26fd20bd..2d122a52 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -35,11 +35,11 @@ jobs:
steps:
- name: Check Out VCS Repository
- uses: actions/checkout@v4.2.0
+ uses: actions/checkout@v4.2.2
- name: Set Up Python
id: set_up_python
- uses: actions/setup-python@v5.2.0
+ uses: actions/setup-python@v5.3.0
with:
python-version: "3.10.9"
@@ -47,7 +47,7 @@ jobs:
run: make python-virtualenv PYTHON_VIRTUALENV_DIR="venv"
- name: Restoring/Saving Cache
- uses: actions/cache@v4.0.2
+ uses: actions/cache@v4.1.2
with:
path: "venv"
key: py-v1-deps-${{ runner.os }}-${{ steps.set_up_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }}
@@ -68,7 +68,7 @@ jobs:
make dist
- name: Store Artifacts
- uses: actions/upload-artifact@v4.4.0
+ uses: actions/upload-artifact@v4.4.3
with:
name: release
path: ${{ env.ARTIFACTS_PATH }}/
diff --git a/HISTORY.md b/HISTORY.md
index 09c48a39..243d7015 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,17 @@
# History
+## 0.39.0 (2024-12-12)
+
+- (PR #729, 2024-10-30) extras: Fix serialization of `None` in Pydantic `Rut` type
+- (PR #730, 2024-11-19) chore: Bump the production-dependencies group with 6 updates
+- (PR #733, 2024-11-20) chore: Bump the development-dependencies group across 1 directory with 5 updates
+- (PR #734, 2024-12-05) chore: Bump the production-dependencies group with 2 updates
+- (PR #732, 2024-12-05) chore(deps): Bump cryptography from 43.0.1 to 43.0.3
+- (PR #739, 2024-12-09) chore(deps): Bump pydantic from 2.9.2 to 2.10.3
+- (PR #740, 2024-12-09) chore(deps): Bump django from 4.2.16 to 4.2.17
+- (PR #736, 2024-12-12) chore(deps): Bump signxml from 3.2.2 to 4.0.3
+- (PR #742, 2024-12-12) deps: Update cryptography to 44.0.0 and pyopenssl to 24.3.0
+
## 0.38.0 (2024-10-28)
- (PR #725, 2024-10-28) extras: Fix generation of JSON Schema for Pydantic `Rut` type
diff --git a/pyproject.toml b/pyproject.toml
index 66c5dad6..4512943c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@
[build-system]
requires = [
"setuptools==70.3.0",
- "wheel==0.44.0",
+ "wheel==0.45.0",
]
build-backend = "setuptools.build_meta"
@@ -26,7 +26,7 @@ dependencies = [
"pydantic>=2.3.0,!=1.7.*,!=1.8.*,!=1.9.*",
"pyOpenSSL>=22.0.0",
"pytz>=2019.3",
- "signxml>=3.1.0",
+ "signxml>=4.0.0",
]
requires-python = ">=3.8, <3.11"
authors = [
diff --git a/requirements-dev.in b/requirements-dev.in
index 1379f1cf..0d8e5475 100644
--- a/requirements-dev.in
+++ b/requirements-dev.in
@@ -5,17 +5,17 @@
-c requirements.txt
black==24.8.0
-build==1.2.2
+build==1.2.2.post1
bumpversion==0.5.3
coverage==7.6.1
flake8==7.1.1
isort==5.13.2
mypy==1.13.0
pip-tools==7.4.1
-tox==4.21.0
+tox==4.23.2
twine==5.1.1
types-jsonschema==4.23.0.20240813
-types-lxml==2024.9.16
+types-lxml==2024.11.8
types-pyOpenSSL==24.1.0.20240722
-types-pytz==2024.2.0.20240913
-wheel==0.44.0
+types-pytz==2024.2.0.20241003
+wheel==0.45.0
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 92ccab52..9e9b3405 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -12,7 +12,7 @@ black==24.8.0
# via -r requirements-dev.in
bleach==5.0.1
# via readme-renderer
-build==1.2.2
+build==1.2.2.post1
# via
# -r requirements-dev.in
# pip-tools
@@ -40,7 +40,7 @@ colorama==0.4.6
# via tox
coverage==7.6.1
# via -r requirements-dev.in
-cryptography==43.0.1
+cryptography==44.0.0
# via
# -c requirements.txt
# secretstorage
@@ -155,7 +155,7 @@ tomli==2.0.1
# pyproject-api
# pyproject-hooks
# tox
-tox==4.21.0
+tox==4.23.2
# via -r requirements-dev.in
twine==5.1.1
# via -r requirements-dev.in
@@ -167,11 +167,11 @@ types-html5lib==1.1.11.20240806
# via types-beautifulsoup4
types-jsonschema==4.23.0.20240813
# via -r requirements-dev.in
-types-lxml==2024.9.16
+types-lxml==2024.11.8
# via -r requirements-dev.in
types-pyopenssl==24.1.0.20240722
# via -r requirements-dev.in
-types-pytz==2024.2.0.20240913
+types-pytz==2024.2.0.20241003
# via -r requirements-dev.in
types-setuptools==69.5.0.20240415
# via types-cffi
@@ -191,7 +191,7 @@ virtualenv==20.26.6
# via tox
webencodings==0.5.1
# via bleach
-wheel==0.44.0
+wheel==0.45.0
# via
# -r requirements-dev.in
# pip-tools
diff --git a/requirements.in b/requirements.in
index 7518f49a..4a5edf65 100644
--- a/requirements.in
+++ b/requirements.in
@@ -6,7 +6,7 @@
# git+https://github.com/example/example.git@example-vcs-ref#egg=example-pkg[foo,bar]==1.42.3
backports-zoneinfo==0.2.1 ; python_version < "3.9" # Used by `djangorestframework`.
-cryptography==43.0.1
+cryptography==44.0.0
defusedxml==0.7.1
django-filter>=24.2
Django>=2.2.24
@@ -15,7 +15,7 @@ importlib-metadata==8.5.0
jsonschema==4.23.0
lxml==5.3.0
marshmallow==3.22.0
-pydantic==2.9.2
-pyOpenSSL==24.2.1
+pydantic==2.10.3
+pyOpenSSL==24.3.0
pytz==2024.2
-signxml==3.2.2
+signxml==4.0.3
diff --git a/requirements.txt b/requirements.txt
index 18815cbe..b2cb5d4c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,14 +21,14 @@ certifi==2024.7.4
# via signxml
cffi==1.16.0
# via cryptography
-cryptography==43.0.1
+cryptography==44.0.0
# via
# -r requirements.in
# pyopenssl
# signxml
defusedxml==0.7.1
# via -r requirements.in
-django==4.2.16
+django==4.2.17
# via
# -r requirements.in
# django-filter
@@ -59,14 +59,12 @@ pkgutil-resolve-name==1.3.10
# via jsonschema
pycparser==2.22
# via cffi
-pydantic==2.9.2
+pydantic==2.10.3
# via -r requirements.in
-pydantic-core==2.23.4
+pydantic-core==2.27.1
# via pydantic
-pyopenssl==24.2.1
- # via
- # -r requirements.in
- # signxml
+pyopenssl==24.3.0
+ # via -r requirements.in
pytz==2024.2
# via -r requirements.in
referencing==0.35.1
@@ -77,7 +75,7 @@ rpds-py==0.19.0
# via
# jsonschema
# referencing
-signxml==3.2.2
+signxml==4.0.3
# via -r requirements.in
sqlparse==0.5.0
# via django
diff --git a/src/cl_sii/__init__.py b/src/cl_sii/__init__.py
index 708d3cb0..ed0d0720 100644
--- a/src/cl_sii/__init__.py
+++ b/src/cl_sii/__init__.py
@@ -4,4 +4,4 @@
"""
-__version__ = '0.38.0'
+__version__ = '0.39.0'
diff --git a/src/cl_sii/extras/pydantic_types.py b/src/cl_sii/extras/pydantic_types.py
index b1dd73e7..86724434 100644
--- a/src/cl_sii/extras/pydantic_types.py
+++ b/src/cl_sii/extras/pydantic_types.py
@@ -117,7 +117,8 @@ def validate_from_str(value: str) -> cl_sii.rut.Rut:
]
),
serialization=pydantic_core.core_schema.plain_serializer_function_ser_schema(
- lambda instance: instance.canonical
+ lambda instance: instance.canonical,
+ when_used='unless-none',
),
)
diff --git a/src/cl_sii/libs/crypto_utils.py b/src/cl_sii/libs/crypto_utils.py
index 776cd8a4..cd20233d 100644
--- a/src/cl_sii/libs/crypto_utils.py
+++ b/src/cl_sii/libs/crypto_utils.py
@@ -157,8 +157,7 @@ def add_pem_cert_header_footer(pem_cert: bytes) -> bytes:
"""
pem_value_str = pem_cert.decode('ascii')
# note: it would be great if 'add_pem_header' did not forcefully convert bytes to str.
- mod_pem_value_str = signxml.util.add_pem_header(pem_value_str)
- mod_pem_value: bytes = mod_pem_value_str.encode('ascii')
+ mod_pem_value: bytes = signxml.util.add_pem_header(pem_value_str)
return mod_pem_value
diff --git a/src/cl_sii/libs/xml_utils.py b/src/cl_sii/libs/xml_utils.py
index da4be0fd..381ebc51 100644
--- a/src/cl_sii/libs/xml_utils.py
+++ b/src/cl_sii/libs/xml_utils.py
@@ -440,14 +440,8 @@ def verify_xml_signature(
)
if isinstance(trusted_x509_cert, crypto_utils._X509CertOpenSsl):
- trusted_x509_cert_open_ssl = trusted_x509_cert
- elif isinstance(trusted_x509_cert, crypto_utils.X509Cert):
- trusted_x509_cert_open_ssl = crypto_utils._X509CertOpenSsl.from_cryptography(
- trusted_x509_cert
- )
- elif trusted_x509_cert is None:
- trusted_x509_cert_open_ssl = None
- else:
+ trusted_x509_cert = trusted_x509_cert.to_cryptography()
+ elif not (isinstance(trusted_x509_cert, crypto_utils.X509Cert) or trusted_x509_cert is None):
# A 'crypto_utils._X509CertOpenSsl' is ok but we prefer 'crypto_utils.X509Cert'.
raise TypeError("'trusted_x509_cert' must be a 'crypto_utils.X509Cert' instance, or None.")
@@ -481,10 +475,10 @@ def verify_xml_signature(
# https://github.com/XML-Security/signxml/commit/ef15da8dbb904f1dedfdd210ae3e0df5da535612
result = xml_verifier.verify(
data=tmp_bytes,
- require_x509=True,
- x509_cert=trusted_x509_cert_open_ssl,
- ignore_ambiguous_key_info=True,
+ x509_cert=trusted_x509_cert,
expect_config=signxml.verifier.SignatureConfiguration(
+ require_x509=True,
+ ignore_ambiguous_key_info=True,
signature_methods=frozenset([signxml.algorithms.SignatureMethod.RSA_SHA1]),
digest_algorithms=frozenset([signxml.algorithms.DigestAlgorithm.SHA1]),
),
diff --git a/src/tests/test_data/sii-dte/DTE--76354771-K--33--170--cleaned-mod-replaced-cert.xml b/src/tests/test_data/sii-dte/DTE--76354771-K--33--170--cleaned-mod-replaced-cert.xml
index ded4f5c2..3abb9f87 100644
--- a/src/tests/test_data/sii-dte/DTE--76354771-K--33--170--cleaned-mod-replaced-cert.xml
+++ b/src/tests/test_data/sii-dte/DTE--76354771-K--33--170--cleaned-mod-replaced-cert.xml
@@ -70,9 +70,11 @@
-fsYP5p/lNfofAz8POShrJjqXdBTNNtvv4/TWCxbvwTIAXr7BLrlvX3C/Hpfo4viqaxSu1OGFgPnk
-ddDIFwj/ZsVdbdB+MhpKkyha83RxhJpYBVBY3c+y9J6oMfdIdMAYXhEkFw8w63KHyhdf2E9dnbKi
-wqSxDcYjTT6vXsLPrZk=
+wwOMQuFqa6c5gzYSJ5PWfo0OiAf+yNcJK6wx4xJ3VNehlAcMrUB2q+rK/DDhCvjxAoX4NxBACiFD
+MrTMIfvxrwXjLd1oX37lSFOtsWX6JxL0SV+tLF7qvWCu1Yzw8ypUf7GDkbymJkoTYDF9JFF8kYU4
+FdU2wttiwne9XH8QFHgXsocKP/aygwiOeGqiNX9o/O5XS2GWpt+KM20jrvtYn7UFMED/3aPacCb1
+GABizr8mlVEZggZgJunMDChpFQyEigSXMK5I737Ac8D2bw7WB47Wj1WBL3sCFRDlXUXtnMvChBVp
+0HRUXYuKHyfpCzqIBXygYrIZexxXgOSnKu/yGg==
@@ -87,50 +89,35 @@ Uavs/9J+gR9BBMs/eYE=
-MIIIDTCCBvWgAwIBAgIQXD9eCvh/44P1ET5RI1LuJjANBgkqhkiG9w0BAQsFADBU
-MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMSUw
-IwYDVQQDExxHb29nbGUgSW50ZXJuZXQgQXV0aG9yaXR5IEczMB4XDTE5MDMyNjEz
-NDA0MFoXDTE5MDYxODEzMjQwMFowZjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
-bGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAoMCkdvb2ds
-ZSBMTEMxFTATBgNVBAMMDCouZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49
-AwEHA0IABANpWSLXLbJm5eRzc1EJmvSIbz0nANT+b11r+XhSUCAbfQhS+4M/91YJ
-gVE6UtZJrLO7GGxvp1tV/DL857NaLEWjggWSMIIFjjATBgNVHSUEDDAKBggrBgEF
-BQcDATAOBgNVHQ8BAf8EBAMCB4AwggRXBgNVHREEggROMIIESoIMKi5nb29nbGUu
-Y29tgg0qLmFuZHJvaWQuY29tghYqLmFwcGVuZ2luZS5nb29nbGUuY29tghIqLmNs
-b3VkLmdvb2dsZS5jb22CGCouY3Jvd2Rzb3VyY2UuZ29vZ2xlLmNvbYIGKi5nLmNv
-gg4qLmdjcC5ndnQyLmNvbYIKKi5nZ3BodC5jboIWKi5nb29nbGUtYW5hbHl0aWNz
-LmNvbYILKi5nb29nbGUuY2GCCyouZ29vZ2xlLmNsgg4qLmdvb2dsZS5jby5pboIO
-Ki5nb29nbGUuY28uanCCDiouZ29vZ2xlLmNvLnVrgg8qLmdvb2dsZS5jb20uYXKC
-DyouZ29vZ2xlLmNvbS5hdYIPKi5nb29nbGUuY29tLmJygg8qLmdvb2dsZS5jb20u
-Y2+CDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUuY29tLnRygg8qLmdvb2dsZS5j
-b20udm6CCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5lc4ILKi5nb29nbGUuZnKCCyou
-Z29vZ2xlLmh1ggsqLmdvb2dsZS5pdIILKi5nb29nbGUubmyCCyouZ29vZ2xlLnBs
-ggsqLmdvb2dsZS5wdIISKi5nb29nbGVhZGFwaXMuY29tgg8qLmdvb2dsZWFwaXMu
-Y26CESouZ29vZ2xlY25hcHBzLmNughQqLmdvb2dsZWNvbW1lcmNlLmNvbYIRKi5n
-b29nbGV2aWRlby5jb22CDCouZ3N0YXRpYy5jboINKi5nc3RhdGljLmNvbYISKi5n
-c3RhdGljY25hcHBzLmNuggoqLmd2dDEuY29tggoqLmd2dDIuY29tghQqLm1ldHJp
-Yy5nc3RhdGljLmNvbYIMKi51cmNoaW4uY29tghAqLnVybC5nb29nbGUuY29tghYq
-LnlvdXR1YmUtbm9jb29raWUuY29tgg0qLnlvdXR1YmUuY29tghYqLnlvdXR1YmVl
-ZHVjYXRpb24uY29tghEqLnlvdXR1YmVraWRzLmNvbYIHKi55dC5iZYILKi55dGlt
-Zy5jb22CGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29tggthbmRyb2lkLmNvbYIb
-ZGV2ZWxvcGVyLmFuZHJvaWQuZ29vZ2xlLmNughxkZXZlbG9wZXJzLmFuZHJvaWQu
-Z29vZ2xlLmNuggRnLmNvgghnZ3BodC5jboIGZ29vLmdsghRnb29nbGUtYW5hbHl0
-aWNzLmNvbYIKZ29vZ2xlLmNvbYIPZ29vZ2xlY25hcHBzLmNughJnb29nbGVjb21t
-ZXJjZS5jb22CGHNvdXJjZS5hbmRyb2lkLmdvb2dsZS5jboIKdXJjaGluLmNvbYIK
-d3d3Lmdvby5nbIIIeW91dHUuYmWCC3lvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0
-aW9uLmNvbYIPeW91dHViZWtpZHMuY29tggV5dC5iZTBoBggrBgEFBQcBAQRcMFow
-LQYIKwYBBQUHMAKGIWh0dHA6Ly9wa2kuZ29vZy9nc3IyL0dUU0dJQUczLmNydDAp
-BggrBgEFBQcwAYYdaHR0cDovL29jc3AucGtpLmdvb2cvR1RTR0lBRzMwHQYDVR0O
-BBYEFM8C2hpNgJL/BEX/yzeB408dhba2MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgw
-FoAUd8K4UJpndnaxLcKG0IOgfqZ+ukswIQYDVR0gBBowGDAMBgorBgEEAdZ5AgUD
-MAgGBmeBDAECAjAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vY3JsLnBraS5nb29n
-L0dUU0dJQUczLmNybDANBgkqhkiG9w0BAQsFAAOCAQEAF9PM41ShwCbhtJG7tj2y
-ZvF2sHbQ5YuZrMfJc6eeCG+nCKm1U5iJzXnXctFGvfJnUCZpj9YrfwDswdEddWyZ
-IG6m6wONF3ZiQifQrcDi0oDA+0BwjEuzYGCGkbfE+Xxb30bVEyDRe51DpJf+cqsb
-+DW2pYdikbdrPem5/hwdNerc7nqrQOJ93sqwbVNGktuyJsTOGNKkSwSaejxdN7yl
-g5aa4CJsE94gy4+mCywWjnnsjcLGJM3RBUxDdAdTGMldU/r33HCUCXl33Qxc4nvP
-MlE9LyFOTIJoajWcpGOsbKWiL3Zr19DKNBSn4Xof0onbtCH7dbpyMwP8XcA2O1dA
-ow==
+MIIGVDCCBTygAwIBAgIKMUWmvgAAAAjUHTANBgkqhkiG9w0BAQUFADCB0jELMAkGA1UEBhMCQ0wx
+HTAbBgNVBAgTFFJlZ2lvbiBNZXRyb3BvbGl0YW5hMREwDwYDVQQHEwhTYW50aWFnbzEUMBIGA1UE
+ChMLRS1DRVJUQ0hJTEUxIDAeBgNVBAsTF0F1dG9yaWRhZCBDZXJ0aWZpY2Fkb3JhMTAwLgYDVQQD
+EydFLUNFUlRDSElMRSBDQSBGSVJNQSBFTEVDVFJPTklDQSBTSU1QTEUxJzAlBgkqhkiG9w0BCQEW
+GHNjbGllbnRlc0BlLWNlcnRjaGlsZS5jbDAeFw0xNzA5MDQyMTExMTJaFw0yMDA5MDMyMTExMTJa
+MIHXMQswCQYDVQQGEwJDTDEUMBIGA1UECBMLVkFMUEFSQUlTTyAxETAPBgNVBAcTCFF1aWxsb3Rh
+MS8wLQYDVQQKEyZTZXJ2aWNpb3MgQm9uaWxsYSB5IExvcGV6IHkgQ2lhLiBMdGRhLjEkMCIGA1UE
+CwwbSW5nZW5pZXLDrWEgeSBDb25zdHJ1Y2Npw7NuMSMwIQYDVQQDExpSYW1vbiBodW1iZXJ0byBM
+b3BleiAgSmFyYTEjMCEGCSqGSIb3DQEJARYUZW5hY29ubHRkYUBnbWFpbC5jb20wgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAKQeAbNDqfi9M2v86RUGAYgq1ZSDioFC6OLr0SwiOaYnLsSOl+Kx
+O394PVwSGa6rZk1ErIZonyi15fU/0nHZLi8iHLB49EB5G3tCwh0s8NfqR9ck0/3Z+TXhVUdiJyJC
+/z8x5I5lSUfzNEedJRidVvp6jVGr7P/SfoEfQQTLP3mBAgMBAAGjggKnMIICozA9BgkrBgEEAYI3
+FQcEMDAuBiYrBgEEAYI3FQiC3IMvhZOMZoXVnReC4twnge/sPGGBy54UhqiCWAIBZAIBBDAdBgNV
+HQ4EFgQU1dVHhF0UVe7RXIz4cjl3/Vew+qowCwYDVR0PBAQDAgTwMB8GA1UdIwQYMBaAFHjhPp/S
+ErN6PI3NMA5Ts0MpB7NVMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9jcmwuZS1jZXJ0Y2hpbGUu
+Y2wvZWNlcnRjaGlsZWNhRkVTLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0dHA6
+Ly9vY3NwLmVjZXJ0Y2hpbGUuY2wvb2NzcDAjBgNVHREEHDAaoBgGCCsGAQQBwQEBoAwWCjEzMTg1
+MDk1LTYwIwYDVR0SBBwwGqAYBggrBgEEAcEBAqAMFgo5NjkyODE4MC01MIIBTQYDVR0gBIIBRDCC
+AUAwggE8BggrBgEEAcNSBTCCAS4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cuZS1jZXJ0Y2hpbGUu
+Y2wvQ1BTLmh0bTCB/AYIKwYBBQUHAgIwge8egewAQwBlAHIAdABpAGYAaQBjAGEAZABvACAARgBp
+AHIAbQBhACAAUwBpAG0AcABsAGUALgAgAEgAYQAgAHMAaQBkAG8AIAB2AGEAbABpAGQAYQBkAG8A
+IABlAG4AIABmAG8AcgBtAGEAIABwAHIAZQBzAGUAbgBjAGkAYQBsACwAIABxAHUAZQBkAGEAbgBk
+AG8AIABoAGEAYgBpAGwAaQB0AGEAZABvACAAZQBsACAAQwBlAHIAdABpAGYAaQBjAGEAZABvACAA
+cABhAHIAYQAgAHUAcwBvACAAdAByAGkAYgB1AHQAYQByAGkAbzANBgkqhkiG9w0BAQUFAAOCAQEA
+mxtPpXWslwI0+uJbyuS9s/S3/Vs0imn758xMU8t4BHUd+OlMdNAMQI1G2+q/OugdLQ/a9Sg3clKD
+qXR4lHGl8d/Yq4yoJzDD3Ceez8qenY3JwGUhPzw9oDpg4mXWvxQDXSFeW/u/BgdadhfGnpwx61Un
++/fU24ZgU1dDJ4GKj5oIPHUIjmoSBhnstEhIr6GJWSTcDKTyzRdqBlaVhenH2Qs6Mw6FrOvRPuud
+B7lo1+OgxMb/Gjyu6XnEaPu7Vq4XlLYMoCD2xrV7WEADaDTm7KcNLczVAYqWSF1WUqYSxmPoQDFY
++kMTThJyCXBlE0NADInrkwWgLLygkKI7zXkwaw==
diff --git a/src/tests/test_extras_pydantic_types.py b/src/tests/test_extras_pydantic_types.py
index 3664e122..7c07da73 100644
--- a/src/tests/test_extras_pydantic_types.py
+++ b/src/tests/test_extras_pydantic_types.py
@@ -49,6 +49,19 @@ def test_serialize_to_python(self) -> None:
self.assertEqual(expected_serialized_value, actual_serialized_value)
+ def test_serialize_none_to_python(self) -> None:
+ # -----Arrange-----
+
+ instance = None
+
+ # -----Act-----
+
+ actual_serialized_value = self.pydantic_type_adapter.dump_python(instance)
+
+ # -----Assert-----
+
+ self.assertIsNone(actual_serialized_value)
+
def test_serialize_to_json(self) -> None:
# -----Arrange-----
@@ -67,6 +80,24 @@ def test_serialize_to_json(self) -> None:
self.assertEqual(expected_serialized_value, actual_serialized_value)
+ def test_serialize_none_to_json(self) -> None:
+ # -----Arrange-----
+
+ instance = None
+
+ expected_serialized_value = b'null'
+ self.assertEqual(
+ expected_serialized_value, json.dumps(json.loads(expected_serialized_value)).encode()
+ )
+
+ # -----Act-----
+
+ actual_serialized_value = self.pydantic_type_adapter.dump_json(instance)
+
+ # -----Assert-----
+
+ self.assertEqual(expected_serialized_value, actual_serialized_value)
+
def test_deserialize_from_instance(self) -> None:
# -----Arrange-----
diff --git a/src/tests/test_libs_xml_utils.py b/src/tests/test_libs_xml_utils.py
index e30c7f45..a503f2d5 100644
--- a/src/tests/test_libs_xml_utils.py
+++ b/src/tests/test_libs_xml_utils.py
@@ -1,9 +1,15 @@
+import datetime
import io
import unittest
+from typing import Any
+from unittest import mock
import lxml.etree
+import signxml
-from cl_sii.libs.crypto_utils import load_pem_x509_cert
+from cl_sii.base.constants import SII_OFFICIAL_TZ
+from cl_sii.libs.crypto_utils import _X509CertOpenSsl, load_pem_x509_cert
+from cl_sii.libs.tz_utils import convert_naive_dt_to_tz_aware
from cl_sii.libs.xml_utils import ( # noqa: F401
XmlElement,
XmlFeatureForbidden,
@@ -185,6 +191,28 @@ def test_ok_external_trusted_cert(self) -> None:
signature_xml_bytes = f.getvalue()
self.assertEqual(signature_xml_bytes, self.with_valid_signature_signature_xml)
+ def test_ok_external_trusted_open_ssl_cert_with_signature(self) -> None:
+ xml_doc = parse_untrusted_xml(self.with_valid_signature)
+ cert = load_pem_x509_cert(self.xml_doc_cert_pem_bytes)
+
+ open_ssl_cert = _X509CertOpenSsl.from_cryptography(cert)
+
+ signed_data, signed_xml, signature_xml = verify_xml_signature(
+ xml_doc, trusted_x509_cert=open_ssl_cert
+ )
+
+ self.assertEqual(signed_data, self.with_valid_signature_signed_data)
+
+ f = io.BytesIO()
+ write_xml_doc(signed_xml, f)
+ signed_xml_bytes = f.getvalue()
+ self.assertEqual(signed_xml_bytes, self.with_valid_signature_signed_xml)
+
+ f = io.BytesIO()
+ write_xml_doc(signature_xml, f)
+ signature_xml_bytes = f.getvalue()
+ self.assertEqual(signature_xml_bytes, self.with_valid_signature_signature_xml)
+
def test_ok_cert_in_signature(self) -> None:
# TODO: implement!
@@ -221,7 +249,7 @@ def test_fail_verify_with_other_cert(self) -> None:
verify_xml_signature(xml_doc, trusted_x509_cert=cert)
self.assertEqual(
cm.exception.args,
- ("Signature verification failed: wrong signature length",),
+ ("Signature verification failed: ",),
)
def test_bad_cert_included(self) -> None:
@@ -244,26 +272,58 @@ def test_bad_cert_included(self) -> None:
)
def test_fail_replaced_cert(self) -> None:
+ """
+ Tests that the signature verification fails
+ when the certificate is not the one that was used to sign the document.
+ """
xml_doc = parse_untrusted_xml(self.with_replaced_cert)
- cert = load_pem_x509_cert(self.any_x509_cert_pem_file)
+ cert = load_pem_x509_cert(self.xml_doc_cert_pem_bytes)
with self.assertRaises(XmlSignatureInvalid) as cm:
verify_xml_signature(xml_doc, trusted_x509_cert=cert)
self.assertEqual(
cm.exception.args,
- ("Signature verification failed: []",),
+ ("Signature verification failed: ",),
)
def test_fail_included_cert_not_from_a_known_ca(self) -> None:
xml_doc = parse_untrusted_xml(self.with_valid_signature)
+ xml_doc_signature_timestamp = convert_naive_dt_to_tz_aware(
+ dt=datetime.datetime.fromisoformat('2019-04-01T01:36:40'), # From XML doc’s
+ tz=SII_OFFICIAL_TZ,
+ )
+
+ def _get_cert_chain_verifier(
+ *args: Any, **kwargs: Any
+ ) -> signxml.util.X509CertChainVerifier:
+ # The default signature verification time is the current time (see
+ # https://cryptography.io/en/43.0.3/x509/verification/#cryptography.x509.verification.PolicyBuilder.time
+ # ), but that causes verification to fail with the message
+ # “validation failed: cert is not valid at validation time”.
+ # To avoid that, we set the verification time to the time of the signature.
+ return signxml.util.X509CertChainVerifier(
+ ca_pem_file=kwargs['ca_pem_file'], verification_time=xml_doc_signature_timestamp
+ )
# Without cert: fails because the issuer of the cert in the signature is not a known CA.
- with self.assertRaises(XmlSignatureInvalidCertificate) as cm:
+ with self.assertRaises(XmlSignatureInvalidCertificate) as cm, mock.patch.object(
+ signxml.verifier.XMLVerifier,
+ 'get_cert_chain_verifier',
+ side_effect=_get_cert_chain_verifier,
+ ) as mock_get_cert_chain_verifier:
verify_xml_signature(xml_doc, trusted_x509_cert=None)
self.assertEqual(
cm.exception.args,
- ('unable to get local issuer certificate',),
+ # According to some test cases from https://x509-limbo.com/, OpenSSL’s error message
+ # “unable to get local issuer certificate” seems to be equivalent to PyCA Cryptography’s
+ # error message below:
+ (
+ 'validation failed:'
+ ' candidates exhausted:'
+ ' all candidates exhausted with no interior errors',
+ ),
)
+ mock_get_cert_chain_verifier.assert_called_once_with(ca_pem_file=None)
def test_fail_signed_data_modified(self) -> None:
xml_doc = parse_untrusted_xml(self.with_signature_and_modified)