From 953a0c5fb2ae781ee8bc383c7303d9ed9d3f303d Mon Sep 17 00:00:00 2001 From: Andrey Kislyuk Date: Sun, 11 Aug 2024 07:59:23 -0700 Subject: [PATCH] Restore compatibility with DSA and EC keys --- .github/workflows/ci.yml | 2 +- signxml/verifier.py | 30 ++++++++++++++++-------------- signxml/xades/xades.py | 4 +++- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d49f77..8e56766 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 8 matrix: - os: [ubuntu-20.04, ubuntu-22.04, macos-12] + os: [ubuntu-20.04, ubuntu-24.04, macos-12] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/signxml/verifier.py b/signxml/verifier.py index bb3027b..f17be69 100644 --- a/signxml/verifier.py +++ b/signxml/verifier.py @@ -226,6 +226,16 @@ def _apply_transforms(self, payload, *, transforms_node: etree._Element, signatu def get_cert_chain_verifier(self, ca_pem_file, ca_path): return X509CertChainVerifier(ca_pem_file=ca_pem_file, ca_path=ca_path) + def _verify_signature_with_public_key_impl(self, *, signature, data, public_key, signature_alg_impl): + verify_args = dict(signature=signature, data=data) + if isinstance(public_key, rsa.RSAPublicKey): + verify_args.update(padding=PKCS1v15(), algorithm=signature_alg_impl) + elif isinstance(public_key, dsa.DSAPublicKey): + verify_args.update(algorithm=signature_alg_impl) + elif isinstance(public_key, ec.EllipticCurvePublicKey): + verify_args.update(signature_algorithm=ec.ECDSA(signature_alg_impl)) + public_key.verify(**verify_args) + def verify( self, data, @@ -397,24 +407,16 @@ def verify( raise InvalidSignature("Certificate subject common name mismatch") if signature_alg.name.startswith("ECDSA"): - # FIXME: test coverage and replace public_key().bits() with public_key().public_bytes() - raw_signature = self._encode_dss_signature(raw_signature, signing_cert.public_key().bits()) + raw_signature = self._encode_dss_signature(raw_signature, signing_cert.public_key().key_size) cert_public_key = signing_cert.public_key() signature_alg_impl = digest_algorithm_implementations[signature_alg]() - # FIXME: this is for RSA only; use listing below to make compatible with other public key types - cert_public_key.verify( - signature=raw_signature, data=signed_info_c14n, padding=PKCS1v15(), algorithm=signature_alg_impl + self._verify_signature_with_public_key_impl( + signature=raw_signature, + data=signed_info_c14n, + public_key=cert_public_key, + signature_alg_impl=signature_alg_impl, ) - """ - RSAPublicKey.verify(signature, data, padding, algorithm) - DSAPublicKey.verify(signature, data, algorithm) - EllipticCurvePublicKey.verify(signature, data, signature_algorithm) - Ed25519PublicKey.verify(signature, data) - Ed448PublicKey.verify(signature, data) - X25519PublicKey - no verify - DO NOT SUPPORT - X448PublicKey - no verify - DO NOT SUPPORT - """ # If both X509Data and KeyValue are present, match one against the other and raise an error on mismatch if key_value is not None: diff --git a/signxml/xades/xades.py b/signxml/xades/xades.py index b169d2d..6e13911 100644 --- a/signxml/xades/xades.py +++ b/signxml/xades/xades.py @@ -285,7 +285,9 @@ def _verify_cert_digest(self, signing_cert_node, expect_cert): def _verify_cert_digests(self, verify_result: VerifyResult): x509_data = verify_result.signature_xml.find("ds:KeyInfo/ds:X509Data", namespaces=namespaces) - cert_from_key_info = load_pem_x509_certificate(add_pem_header(self._find(x509_data, "X509Certificate").text)) + cert_from_key_info = x509.load_pem_x509_certificate( + add_pem_header(self._find(x509_data, "X509Certificate").text) + ) signed_signature_props = self._find(verify_result.signed_xml, "xades:SignedSignatureProperties") signing_cert = self._find(signed_signature_props, "xades:SigningCertificate", require=False) signing_cert_v2 = self._find(signed_signature_props, "xades:SigningCertificateV2", require=False)