Skip to content

Commit eb42584

Browse files
committed
WIP
1 parent 18f5129 commit eb42584

File tree

8 files changed

+75
-50
lines changed

8 files changed

+75
-50
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,23 @@ on: [push, pull_request]
44

55
jobs:
66
unit_tests:
7-
runs-on: ${{ matrix.os }}
7+
runs-on: ${{matrix.os}}
88
strategy:
99
max-parallel: 8
1010
matrix:
1111
os: [ubuntu-18.04, ubuntu-22.04, macos-12]
1212
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
1313
steps:
14-
- uses: actions/checkout@v2
15-
- name: Set up Python ${{ matrix.python-version }}
16-
uses: actions/setup-python@v2
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-python@v4
1716
with:
18-
python-version: ${{ matrix.python-version }}
19-
- name: Install
20-
run: |
17+
python-version: ${{matrix.python-version}}
18+
- run: |
2119
[[ $(uname) == Linux ]] && sudo apt-get install --no-install-recommends python3-openssl python3-lxml
22-
pip install coverage wheel
23-
make install
24-
- name: Test
25-
run: |
26-
make test
27-
- name: Upload coverage data
28-
run: |
29-
bash <(curl -s https://codecov.io/bash)
20+
- run: make install
21+
- run: make lint
22+
- run: make test
23+
- uses: codecov/codecov-action@v3
3024
black:
3125
runs-on: ubuntu-22.04
3226
steps:

Makefile

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
1-
test_deps:
2-
pip install coverage flake8 wheel mypy types-certifi types-pyOpenSSL lxml-stubs
1+
SHELL=/bin/bash
32

4-
lint: test_deps
5-
flake8 $$(python setup.py --name) test
6-
mypy $$(python setup.py --name) --check-untyped-defs
3+
lint:
4+
flake8
5+
mypy --install-types --non-interactive --check-untyped-defs $$(dirname */__init__.py)
76

8-
test: test_deps lint
9-
coverage run --source=$$(python setup.py --name) ./test/test.py
7+
test:
8+
python ./test/test.py -v
109

1110
init_docs:
1211
cd docs; sphinx-quickstart
1312

1413
docs:
1514
sphinx-build docs docs/html
1615

17-
install: clean
18-
pip install wheel
19-
python setup.py bdist_wheel
20-
pip install --upgrade dist/*.whl
16+
install:
17+
-rm -rf dist
18+
python -m pip install build
19+
python -m build
20+
python -m pip install --upgrade $$(echo dist/*.whl)[tests]
2121

22-
clean:
23-
-rm -rf build dist
24-
-rm -rf *.egg-info
25-
26-
.PHONY: lint test test_deps docs install clean
22+
.PHONY: test lint release docs
2723

2824
include common.mk

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ Please report bugs, issues, feature requests, etc. on `GitHub <https://github.co
244244

245245
License
246246
-------
247-
Copyright 2014-2022, Andrey Kislyuk and SignXML contributors. Licensed under the terms of the
247+
Copyright 2014-2023, Andrey Kislyuk and SignXML contributors. Licensed under the terms of the
248248
`Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
249249
files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
250250

setup.cfg

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
[bdist_wheel]
2-
universal=1
31
[flake8]
42
max-line-length=120
53
extend-ignore=E203

setup.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@
1919
"cryptography >= 3.4.8", # Set to the version in Ubuntu 22.04 due to features we need from cryptography 3.1
2020
"pyOpenSSL >= 17.5.0",
2121
"certifi >= 2018.1.18",
22+
"tsp-client >= 0.1.3",
2223
],
24+
extras_require={
25+
"tests": [
26+
"flake8",
27+
"coverage",
28+
"build",
29+
"wheel",
30+
"mypy",
31+
"lxml-stubs",
32+
]
33+
},
2334
packages=find_packages(exclude=["test"]),
2435
platforms=["MacOS X", "Posix"],
2536
package_data={"signxml": ["schemas/*.xsd", "py.typed"]},

signxml/util/__init__.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
from dataclasses import dataclass
1313
from typing import Any, List, Optional
1414

15+
import certifi
1516
from cryptography.hazmat.primitives import hashes, hmac
1617
from lxml.etree import QName
18+
from OpenSSL.crypto import Error as OpenSSLCryptoError
19+
from OpenSSL.crypto import X509Store, X509StoreContext, X509StoreContextError
1720

1821
from ..exceptions import InvalidCertificate, RedundantCert, SignXMLException
1922

@@ -207,9 +210,6 @@ def p_sha1(client_b64_bytes, server_b64_bytes):
207210

208211

209212
def _add_cert_to_store(store, cert):
210-
from OpenSSL.crypto import Error as OpenSSLCryptoError
211-
from OpenSSL.crypto import X509StoreContext, X509StoreContextError
212-
213213
try:
214214
X509StoreContext(store, cert).verify_certificate()
215215
except X509StoreContextError as e:
@@ -233,22 +233,21 @@ def verify_x509_cert_chain(cert_chain, ca_pem_file=None, ca_path=None):
233233
No ordering is implied by the above constraints"
234234
"""
235235
# TODO: migrate to Cryptography (pending cert validation support) or https://github.com/wbond/certvalidator
236-
from OpenSSL import SSL
237-
238-
context = SSL.Context(SSL.TLSv1_METHOD)
236+
x509_store = X509Store()
239237
if ca_pem_file is None and ca_path is None:
240-
import certifi
241-
242238
ca_pem_file = certifi.where()
243-
context.load_verify_locations(ensure_bytes(ca_pem_file, none_ok=True), capath=ca_path)
244-
store = context.get_cert_store()
239+
x509_store.load_locations(cafile=ca_pem_file, capath=ca_path)
240+
241+
# FIXME: use X509StoreContext(store=x509_store, certificate=cert, chain=cert_chain).get_verified_chain()
242+
# This requires identifying the signing cert out-of-band
243+
245244
certs = list(reversed(cert_chain))
246245
end_of_chain = None
247246
last_error: Exception = SignXMLException("Invalid certificate chain")
248247
while len(certs) > 0:
249248
for cert in certs:
250249
try:
251-
end_of_chain = _add_cert_to_store(store, cert)
250+
end_of_chain = _add_cert_to_store(x509_store, cert)
252251
certs.remove(cert)
253252
break
254253
except RedundantCert:

signxml/verifier.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ def verify(
373373
raise InvalidInput(msg)
374374
else:
375375
cert_chain = [load_certificate(FILETYPE_PEM, add_pem_header(cert)) for cert in certs]
376+
# FIXME: switch to wbondcrypto cert chain verify
376377
signing_cert = verify_x509_cert_chain(cert_chain, ca_pem_file=ca_pem_file, ca_path=ca_path)
377378
elif isinstance(self.x509_cert, X509):
378379
signing_cert = self.x509_cert
@@ -387,6 +388,8 @@ def verify(
387388

388389
try:
389390
digest_alg_name = str(digest_algorithm_implementations[signature_alg].name)
391+
# FIXME: confirm the specified signature algorithm matches the certificate's public key
392+
# FIXME: switch to cryptography verify
390393
openssl_verify(signing_cert, raw_signature, signed_info_c14n, digest_alg_name)
391394
except OpenSSLCryptoError as e:
392395
try:

signxml/xades/xades.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from lxml.etree import SubElement, _Element
3131
from OpenSSL.crypto import FILETYPE_ASN1, FILETYPE_PEM, X509, dump_certificate, load_certificate
32+
from tsp_client import TSPVerifier
3233

3334
from .. import SignatureConfiguration, VerifyResult, XMLSignatureProcessor, XMLSigner, XMLVerifier
3435
from ..algorithms import DigestAlgorithm
@@ -264,9 +265,31 @@ class XAdESVerifier(XAdESProcessor, XMLVerifier):
264265
"""
265266

266267
# TODO: document/support SignatureTimeStamp / timestamp attestation
268+
# TODO: allow setting required attributes, including timestamp
267269
# SignatureTimeStamp is required by certain profiles but is an unsigned property
268-
def _verify_signing_time(self, verify_result: VerifyResult):
269-
pass
270+
271+
def _verify_signing_time(self, verify_result: VerifyResult, all_verify_results: List[VerifyResult]):
272+
"""
273+
The Implicit mechanism (see clause 5.1.4.4.1) shall be used for generating this qualifying property.
274+
The input to the computation of the message imprint shall be the result of processing all the ds:Reference
275+
elements within the ds:SignedInfo except the one referencing the SignedProperties element, in their order of
276+
appearance, as follows:
277+
1) process the retrieved ds:Reference element according to the reference-processing model of XMLDSIG [1]
278+
clause 4.4.3.2;
279+
2) if the result is a XML node set, canonicalize it as specified in clause 4.5; and
280+
3) concatenate the resulting octets to those resulting from previously processed ds:Reference elements in
281+
ds:SignedInfo.
282+
"""
283+
ts_path = "xades:SignedDataObjectProperties/xades:AllDataObjectsTimeStamp/xades:EncapsulatedTimeStamp"
284+
if verify_result.signed_xml is None:
285+
return
286+
all_data_objs_ts = verify_result.signed_xml.find(ts_path, namespaces=namespaces)
287+
if all_data_objs_ts is None:
288+
return
289+
print("Will verify", all_data_objs_ts.text)
290+
ts = b64decode(all_data_objs_ts.text) # type: ignore
291+
tsp_message = b"".join(r.signed_data for r in all_verify_results if r != verify_result)
292+
TSPVerifier().verify(ts, message=tsp_message)
270293

271294
def _verify_cert_digest(self, signing_cert_node, expect_cert):
272295
for cert in self._findall(signing_cert_node, "xades:Cert"):
@@ -320,8 +343,8 @@ def _verify_signature_policy(self, verify_result: VerifyResult, expect_signature
320343
if b64decode(digest_value.text) != b64decode(expect_signature_policy.DigestValue):
321344
raise InvalidInput("Digest mismatch for signature policy hash")
322345

323-
def _verify_signed_properties(self, verify_result):
324-
self._verify_signing_time(verify_result)
346+
def _verify_signed_properties(self, verify_result, *, all_verify_results):
347+
self._verify_signing_time(verify_result, all_verify_results=all_verify_results)
325348
self._verify_cert_digests(verify_result)
326349
if self.expect_signature_policy:
327350
self._verify_signature_policy(
@@ -364,7 +387,8 @@ def verify( # type: ignore
364387
continue
365388
if verify_result.signed_xml.tag == xades_tag("SignedProperties"):
366389
verify_results[i] = XAdESVerifyResult( # type: ignore
367-
*astuple(verify_result), signed_properties=self._verify_signed_properties(verify_result)
390+
*astuple(verify_result),
391+
signed_properties=self._verify_signed_properties(verify_result, all_verify_results=verify_results),
368392
)
369393
break
370394
else:

0 commit comments

Comments
 (0)