Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kislyuk committed Jan 2, 2023
1 parent 18f5129 commit eb42584
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 50 deletions.
24 changes: 9 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@ on: [push, pull_request]

jobs:
unit_tests:
runs-on: ${{ matrix.os }}
runs-on: ${{matrix.os}}
strategy:
max-parallel: 8
matrix:
os: [ubuntu-18.04, ubuntu-22.04, macos-12]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install
run: |
python-version: ${{matrix.python-version}}
- run: |
[[ $(uname) == Linux ]] && sudo apt-get install --no-install-recommends python3-openssl python3-lxml
pip install coverage wheel
make install
- name: Test
run: |
make test
- name: Upload coverage data
run: |
bash <(curl -s https://codecov.io/bash)
- run: make install
- run: make lint
- run: make test
- uses: codecov/codecov-action@v3
black:
runs-on: ubuntu-22.04
steps:
Expand Down
28 changes: 12 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
test_deps:
pip install coverage flake8 wheel mypy types-certifi types-pyOpenSSL lxml-stubs
SHELL=/bin/bash

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

test: test_deps lint
coverage run --source=$$(python setup.py --name) ./test/test.py
test:
python ./test/test.py -v

init_docs:
cd docs; sphinx-quickstart

docs:
sphinx-build docs docs/html

install: clean
pip install wheel
python setup.py bdist_wheel
pip install --upgrade dist/*.whl
install:
-rm -rf dist
python -m pip install build
python -m build
python -m pip install --upgrade $$(echo dist/*.whl)[tests]

clean:
-rm -rf build dist
-rm -rf *.egg-info

.PHONY: lint test test_deps docs install clean
.PHONY: test lint release docs

include common.mk
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ Please report bugs, issues, feature requests, etc. on `GitHub <https://github.co

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

Expand Down
2 changes: 0 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
[bdist_wheel]
universal=1
[flake8]
max-line-length=120
extend-ignore=E203
Expand Down
11 changes: 11 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,18 @@
"cryptography >= 3.4.8", # Set to the version in Ubuntu 22.04 due to features we need from cryptography 3.1
"pyOpenSSL >= 17.5.0",
"certifi >= 2018.1.18",
"tsp-client >= 0.1.3",
],
extras_require={
"tests": [
"flake8",
"coverage",
"build",
"wheel",
"mypy",
"lxml-stubs",
]
},
packages=find_packages(exclude=["test"]),
platforms=["MacOS X", "Posix"],
package_data={"signxml": ["schemas/*.xsd", "py.typed"]},
Expand Down
21 changes: 10 additions & 11 deletions signxml/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
from dataclasses import dataclass
from typing import Any, List, Optional

import certifi
from cryptography.hazmat.primitives import hashes, hmac
from lxml.etree import QName
from OpenSSL.crypto import Error as OpenSSLCryptoError
from OpenSSL.crypto import X509Store, X509StoreContext, X509StoreContextError

from ..exceptions import InvalidCertificate, RedundantCert, SignXMLException

Expand Down Expand Up @@ -207,9 +210,6 @@ def p_sha1(client_b64_bytes, server_b64_bytes):


def _add_cert_to_store(store, cert):
from OpenSSL.crypto import Error as OpenSSLCryptoError
from OpenSSL.crypto import X509StoreContext, X509StoreContextError

try:
X509StoreContext(store, cert).verify_certificate()
except X509StoreContextError as e:
Expand All @@ -233,22 +233,21 @@ def verify_x509_cert_chain(cert_chain, ca_pem_file=None, ca_path=None):
No ordering is implied by the above constraints"
"""
# TODO: migrate to Cryptography (pending cert validation support) or https://github.com/wbond/certvalidator
from OpenSSL import SSL

context = SSL.Context(SSL.TLSv1_METHOD)
x509_store = X509Store()
if ca_pem_file is None and ca_path is None:
import certifi

ca_pem_file = certifi.where()
context.load_verify_locations(ensure_bytes(ca_pem_file, none_ok=True), capath=ca_path)
store = context.get_cert_store()
x509_store.load_locations(cafile=ca_pem_file, capath=ca_path)

# FIXME: use X509StoreContext(store=x509_store, certificate=cert, chain=cert_chain).get_verified_chain()
# This requires identifying the signing cert out-of-band

certs = list(reversed(cert_chain))
end_of_chain = None
last_error: Exception = SignXMLException("Invalid certificate chain")
while len(certs) > 0:
for cert in certs:
try:
end_of_chain = _add_cert_to_store(store, cert)
end_of_chain = _add_cert_to_store(x509_store, cert)
certs.remove(cert)
break
except RedundantCert:
Expand Down
3 changes: 3 additions & 0 deletions signxml/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def verify(
raise InvalidInput(msg)
else:
cert_chain = [load_certificate(FILETYPE_PEM, add_pem_header(cert)) for cert in certs]
# FIXME: switch to wbondcrypto cert chain verify
signing_cert = verify_x509_cert_chain(cert_chain, ca_pem_file=ca_pem_file, ca_path=ca_path)
elif isinstance(self.x509_cert, X509):
signing_cert = self.x509_cert
Expand All @@ -387,6 +388,8 @@ def verify(

try:
digest_alg_name = str(digest_algorithm_implementations[signature_alg].name)
# FIXME: confirm the specified signature algorithm matches the certificate's public key
# FIXME: switch to cryptography verify
openssl_verify(signing_cert, raw_signature, signed_info_c14n, digest_alg_name)
except OpenSSLCryptoError as e:
try:
Expand Down
34 changes: 29 additions & 5 deletions signxml/xades/xades.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from lxml.etree import SubElement, _Element
from OpenSSL.crypto import FILETYPE_ASN1, FILETYPE_PEM, X509, dump_certificate, load_certificate
from tsp_client import TSPVerifier

from .. import SignatureConfiguration, VerifyResult, XMLSignatureProcessor, XMLSigner, XMLVerifier
from ..algorithms import DigestAlgorithm
Expand Down Expand Up @@ -264,9 +265,31 @@ class XAdESVerifier(XAdESProcessor, XMLVerifier):
"""

# TODO: document/support SignatureTimeStamp / timestamp attestation
# TODO: allow setting required attributes, including timestamp
# SignatureTimeStamp is required by certain profiles but is an unsigned property
def _verify_signing_time(self, verify_result: VerifyResult):
pass

def _verify_signing_time(self, verify_result: VerifyResult, all_verify_results: List[VerifyResult]):
"""
The Implicit mechanism (see clause 5.1.4.4.1) shall be used for generating this qualifying property.
The input to the computation of the message imprint shall be the result of processing all the ds:Reference
elements within the ds:SignedInfo except the one referencing the SignedProperties element, in their order of
appearance, as follows:
1) process the retrieved ds:Reference element according to the reference-processing model of XMLDSIG [1]
clause 4.4.3.2;
2) if the result is a XML node set, canonicalize it as specified in clause 4.5; and
3) concatenate the resulting octets to those resulting from previously processed ds:Reference elements in
ds:SignedInfo.
"""
ts_path = "xades:SignedDataObjectProperties/xades:AllDataObjectsTimeStamp/xades:EncapsulatedTimeStamp"
if verify_result.signed_xml is None:
return
all_data_objs_ts = verify_result.signed_xml.find(ts_path, namespaces=namespaces)
if all_data_objs_ts is None:
return
print("Will verify", all_data_objs_ts.text)
ts = b64decode(all_data_objs_ts.text) # type: ignore
tsp_message = b"".join(r.signed_data for r in all_verify_results if r != verify_result)
TSPVerifier().verify(ts, message=tsp_message)

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

def _verify_signed_properties(self, verify_result):
self._verify_signing_time(verify_result)
def _verify_signed_properties(self, verify_result, *, all_verify_results):
self._verify_signing_time(verify_result, all_verify_results=all_verify_results)
self._verify_cert_digests(verify_result)
if self.expect_signature_policy:
self._verify_signature_policy(
Expand Down Expand Up @@ -364,7 +387,8 @@ def verify( # type: ignore
continue
if verify_result.signed_xml.tag == xades_tag("SignedProperties"):
verify_results[i] = XAdESVerifyResult( # type: ignore
*astuple(verify_result), signed_properties=self._verify_signed_properties(verify_result)
*astuple(verify_result),
signed_properties=self._verify_signed_properties(verify_result, all_verify_results=verify_results),
)
break
else:
Expand Down

0 comments on commit eb42584

Please sign in to comment.