Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pysaml2 for openSUSE:Factory 
checked in at 2025-02-28 17:39:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pysaml2 (Old)
 and      /work/SRC/openSUSE:Factory/.python-pysaml2.new.19136 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pysaml2"

Fri Feb 28 17:39:27 2025 rev:33 rq:1249137 version:7.5.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pysaml2/python-pysaml2.changes    
2024-10-29 14:37:27.979280983 +0100
+++ /work/SRC/openSUSE:Factory/.python-pysaml2.new.19136/python-pysaml2.changes 
2025-02-28 17:40:48.288297549 +0100
@@ -1,0 +2,11 @@
+Fri Feb 28 04:15:23 UTC 2025 - Nico Krapp <[email protected]>
+
+- Update to 7.5.2
+  * Include the XSD of the XML Encryption Syntax and Processing 
+    Version 1.1 to the schema validator
+- Update to 7.5.1
+  * deps: restrict pyOpenSSL up to v24.2.1 until it is replaced
+  * deps: update dependncies for the lockfile and examples
+- add use-cryptography.patch to fix tests
+
+-------------------------------------------------------------------

Old:
----
  v7.5.0.tar.gz

New:
----
  use-cryptography.patch
  v7.5.2.tar.gz

BETA DEBUG BEGIN:
  New:  * deps: update dependncies for the lockfile and examples
- add use-cryptography.patch to fix tests
BETA DEBUG END:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pysaml2.spec ++++++
--- /var/tmp/diff_new_pack.l326UJ/_old  2025-02-28 17:40:49.184335049 +0100
+++ /var/tmp/diff_new_pack.l326UJ/_new  2025-02-28 17:40:49.188335216 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pysaml2
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,20 +19,21 @@
 %global modname pysaml2
 %{?sle15_python_module_pythons}
 Name:           python-pysaml2
-Version:        7.5.0
+Version:        7.5.2
 Release:        0
 Summary:        Python implementation of SAML Version 2 to be used in a WSGI 
environment
 License:        Apache-2.0
 URL:            https://github.com/IdentityPython/pysaml2
 Source:         
https://github.com/IdentityPython/pysaml2/archive/v%{version}.tar.gz
+# PATCH-FIX-UPSTREAM use-cryptography.patch 
https://github.com/IdentityPython/pysaml2/issues/879
+Patch0:         use-cryptography.patch
 BuildRequires:  %{python_module Paste}
-BuildRequires:  %{python_module cryptography >= 3.1}
+BuildRequires:  %{python_module cryptography >= 40.0}
 BuildRequires:  %{python_module dbm}
 BuildRequires:  %{python_module defusedxml}
 BuildRequires:  %{python_module importlib-resources}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module poetry-core}
-BuildRequires:  %{python_module pyOpenSSL}
 BuildRequires:  %{python_module pymongo >= 3.5}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module python-dateutil}
@@ -40,7 +41,7 @@
 BuildRequires:  %{python_module requests >= 1.0.0}
 BuildRequires:  %{python_module responses}
 BuildRequires:  %{python_module setuptools}
-BuildRequires:  %{python_module xmlschema >= 1.2.1}
+BuildRequires:  %{python_module xmlschema >= 2}
 BuildRequires:  %{python_module zope.interface}
 BuildRequires:  fdupes
 # This is needed as xmlsec itself does not pull any backend by default
@@ -95,10 +96,11 @@
 sed -i 's:import mock:from unittest import mock:' tests/test_41_response.py
 sed -i 's:mock.mock:unittest.mock:' tests/test_52_default_sign_alg.py
 # Excluded tests for i586 gh#IdentityPython/pysaml2#682 and 
gh#IdentityPython/pysaml2#759
+# Exclude broken namespace test 
(https://github.com/IdentityPython/pysaml2/issues/921)
 %ifarch %{ix86}
-%pytest -k "not (test_assertion_consumer_service or test_swamid_sp or 
test_swamid_idp or test_other_response or test_mta or test_unknown_subject or 
test_filter_ava_registration_authority_1)" tests
+%pytest -k "not (test_namespace_processing or test_assertion_consumer_service 
or test_swamid_sp or test_swamid_idp or test_other_response or test_mta or 
test_unknown_subject or test_filter_ava_registration_authority_1)" tests
 %else
-%pytest tests
+%pytest -k "not test_namespace_processing" tests
 %endif
 
 %post
@@ -115,5 +117,5 @@
 %python_alternative %{_bindir}/mdexport
 %python_alternative %{_bindir}/merge_metadata
 %{python_sitelib}/saml2
-%{python_sitelib}/pysaml2-%{version}*-info
+%{python_sitelib}/pysaml2-%{version}.dist-info
 

++++++ use-cryptography.patch ++++++
>From 930a652a240c8cd1489429a7d70cf5fa7ef1606a Mon Sep 17 00:00:00 2001
From: Patrick Rauscher <[email protected]>
Date: Wed, 12 Feb 2025 23:29:34 +0100
Subject: [PATCH] replace pyopenssl with cryptography

---
 pyproject.toml      |   3 +-
 src/saml2/cert.py   | 178 ++++++++++++++++++++++++--------------------
 src/saml2/sigver.py |  12 +--
 3 files changed, 105 insertions(+), 88 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 985692043..8a7cd9185 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,12 +37,11 @@ parse_xsd2 = "saml2.tools.parse_xsd2:main"
 
 [tool.poetry.dependencies]
 python = "^3.9"
-cryptography = ">=3.1"
+cryptography = ">=40.0"
 defusedxml = "*"
 importlib-metadata = {version = ">=1.7.0", python = "<3.8"}
 importlib-resources = {python = "<3.9", version = "*"}
 paste = {optional = true, version = "*"}
-pyopenssl = "<24.3.0"
 python-dateutil = "*"
 pytz = "*"
 "repoze.who" = {optional = true, version = "*"}
diff --git a/src/saml2/cert.py b/src/saml2/cert.py
index c5f626601..1759b9b24 100644
--- a/src/saml2/cert.py
+++ b/src/saml2/cert.py
@@ -5,7 +5,11 @@
 from os import remove
 from os.path import join
 
-from OpenSSL import crypto
+from cryptography import x509
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.x509.oid import NameOID
 import dateutil.parser
 import pytz
 
@@ -36,7 +40,6 @@ def create_certificate(
         valid_to=315360000,
         sn=1,
         key_length=1024,
-        hash_alg="sha256",
         write_to_file=False,
         cert_dir="",
         cipher_passphrase=None,
@@ -87,8 +90,6 @@ def create_certificate(
                                   is 1.
         :param key_length:        Length of the key to be generated. Defaults
                                   to 1024.
-        :param hash_alg:          Hash algorithm to use for the key. Default
-                                  is sha256.
         :param write_to_file:     True if you want to write the certificate
                                   to a file. The method will then return
                                   a tuple with path to certificate file and
@@ -131,49 +132,68 @@ def create_certificate(
             k_f = join(cert_dir, key_file)
 
         # create a key pair
-        k = crypto.PKey()
-        k.generate_key(crypto.TYPE_RSA, key_length)
+        k = rsa.generate_private_key(
+            public_exponent=65537,
+            key_size=key_length,
+        )
 
         # create a self-signed cert
-        cert = crypto.X509()
+        builder = x509.CertificateBuilder()
 
         if request:
-            cert = crypto.X509Req()
+            builder = x509.CertificateSigningRequestBuilder()
 
         if len(cert_info["country_code"]) != 2:
             raise WrongInput("Country code must be two letters!")
-        cert.get_subject().C = cert_info["country_code"]
-        cert.get_subject().ST = cert_info["state"]
-        cert.get_subject().L = cert_info["city"]
-        cert.get_subject().O = cert_info["organization"]  # noqa: E741
-        cert.get_subject().OU = cert_info["organization_unit"]
-        cert.get_subject().CN = cn
+        subject_name = x509.Name([
+            x509.NameAttribute(NameOID.COUNTRY_NAME,
+                               cert_info["country_code"]),
+            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME,
+                               cert_info["state"]),
+            x509.NameAttribute(NameOID.LOCALITY_NAME,
+                               cert_info["city"]),
+            x509.NameAttribute(NameOID.ORGANIZATION_NAME,
+                               cert_info["organization"]),
+            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
+                               cert_info["organization_unit"]),
+            x509.NameAttribute(NameOID.COMMON_NAME, cn),
+        ])
+        builder = builder.subject_name(subject_name)
         if not request:
-            cert.set_serial_number(sn)
-            cert.gmtime_adj_notBefore(valid_from)  # Valid before present time
-            cert.gmtime_adj_notAfter(valid_to)  # 3 650 days
-            cert.set_issuer(cert.get_subject())
-        cert.set_pubkey(k)
-        cert.sign(k, hash_alg)
+            now = datetime.datetime.now(datetime.UTC)
+            builder = builder.serial_number(
+                sn,
+            ).not_valid_before(
+                now + datetime.timedelta(seconds=valid_from),
+            ).not_valid_after(
+                now + datetime.timedelta(seconds=valid_to),
+            ).issuer_name(
+                subject_name,
+            ).public_key(
+                k.public_key(),
+            )
+        cert = builder.sign(k, hashes.SHA256())
 
         try:
-            if request:
-                tmp_cert = 
crypto.dump_certificate_request(crypto.FILETYPE_PEM, cert)
-            else:
-                tmp_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
-            tmp_key = None
+            tmp_cert = cert.public_bytes(serialization.Encoding.PEM)
+            key_encryption = None
             if cipher_passphrase is not None:
                 passphrase = cipher_passphrase["passphrase"]
                 if isinstance(cipher_passphrase["passphrase"], str):
                     passphrase = passphrase.encode("utf-8")
-                tmp_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k, 
cipher_passphrase["cipher"], passphrase)
+                key_encryption = 
serialization.BestAvailableEncryption(passphrase)
             else:
-                tmp_key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
+                key_encryption = serialization.NoEncryption()
+            tmp_key = k.private_bytes(
+                encoding=serialization.Encoding.PEM,
+                format=serialization.PrivateFormat.TraditionalOpenSSL,
+                encryption_algorithm=key_encryption,
+            )
             if write_to_file:
-                with open(c_f, "w") as fc:
-                    fc.write(tmp_cert.decode("utf-8"))
-                with open(k_f, "w") as fk:
-                    fk.write(tmp_key.decode("utf-8"))
+                with open(c_f, "wb") as fc:
+                    fc.write(tmp_cert)
+                with open(k_f, "wb") as fk:
+                    fk.write(tmp_key)
                 return c_f, k_f
             return tmp_cert, tmp_key
         except Exception as ex:
@@ -198,7 +218,6 @@ def create_cert_signed_certificate(
         sign_cert_str,
         sign_key_str,
         request_cert_str,
-        hash_alg="sha256",
         valid_from=0,
         valid_to=315360000,
         sn=1,
@@ -222,8 +241,6 @@ def create_cert_signed_certificate(
                                   the requested certificate. If you only have
                                   a file use the method read_str_from_file
                                   to get a string representation.
-        :param hash_alg:          Hash algorithm to use for the key. Default
-                                  is sha256.
         :param valid_from:        When the certificate starts to be valid.
                                   Amount of seconds from when the
                                   certificate is generated.
@@ -237,27 +254,29 @@ def create_cert_signed_certificate(
         :return:                  String representation of the signed
                                   certificate.
         """
-        ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, sign_cert_str)
-        ca_key = None
-        if passphrase is not None:
-            ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, sign_key_str, 
passphrase)
-        else:
-            ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, sign_key_str)
-        req_cert = crypto.load_certificate_request(crypto.FILETYPE_PEM, 
request_cert_str)
-
-        cert = crypto.X509()
-        cert.set_subject(req_cert.get_subject())
-        cert.set_serial_number(sn)
-        cert.gmtime_adj_notBefore(valid_from)
-        cert.gmtime_adj_notAfter(valid_to)
-        cert.set_issuer(ca_cert.get_subject())
-        cert.set_pubkey(req_cert.get_pubkey())
-        cert.sign(ca_key, hash_alg)
-
-        cert_dump = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
-        if isinstance(cert_dump, str):
-            return cert_dump
-        return cert_dump.decode("utf-8")
+        if isinstance(sign_cert_str, str):
+            sign_cert_str = sign_cert_str.encode("utf-8")
+        ca_cert = x509.load_pem_x509_certificate(sign_cert_str)
+        ca_key = serialization.load_pem_private_key(
+            sign_key_str, password=passphrase)
+        req_cert = x509.load_pem_x509_csr(request_cert_str)
+
+        now = datetime.datetime.now(datetime.UTC)
+        cert = x509.CertificateBuilder().subject_name(
+            req_cert.subject,
+        ).serial_number(
+            sn,
+        ).not_valid_before(
+            now + datetime.timedelta(seconds=valid_from),
+        ).not_valid_after(
+            now + datetime.timedelta(seconds=valid_to),
+        ).issuer_name(
+            ca_cert.subject,
+        ).public_key(
+            req_cert.public_key(),
+        ).sign(ca_key, hashes.SHA256())
+
+        return cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
 
     def verify_chain(self, cert_chain_str_list, cert_str):
         """
@@ -276,13 +295,6 @@ def verify_chain(self, cert_chain_str_list, cert_str):
                 cert_str = tmp_cert_str
             return (True, "Signed certificate is valid and correctly signed by 
CA " "certificate.")
 
-    def certificate_not_valid_yet(self, cert):
-        starts_to_be_valid = dateutil.parser.parse(cert.get_notBefore())
-        now = pytz.UTC.localize(datetime.datetime.utcnow())
-        if starts_to_be_valid < now:
-            return False
-        return True
-
     def verify(self, signing_cert_str, cert_str):
         """
         Verifies if a certificate is valid and signed by a given certificate.
@@ -303,34 +315,34 @@ def verify(self, signing_cert_str, cert_str):
                                  Message = Why the validation failed.
         """
         try:
-            ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, 
signing_cert_str)
-            cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_str)
-
-            if self.certificate_not_valid_yet(ca_cert):
+            if isinstance(signing_cert_str, str):
+                signing_cert_str = signing_cert_str.encode("utf-8")
+            if isinstance(cert_str, str):
+                cert_str = cert_str.encode("utf-8")
+            ca_cert = x509.load_pem_x509_certificate(signing_cert_str)
+            cert = x509.load_pem_x509_certificate(cert_str)
+            now = datetime.datetime.now(datetime.UTC)
+
+            if ca_cert.not_valid_before_utc >= now:
                 return False, "CA certificate is not valid yet."
 
-            if ca_cert.has_expired() == 1:
+            if ca_cert.not_valid_after_utc < now:
                 return False, "CA certificate is expired."
 
-            if cert.has_expired() == 1:
+            if cert.not_valid_after_utc < now:
                 return False, "The signed certificate is expired."
 
-            if self.certificate_not_valid_yet(cert):
+            if cert.not_valid_before_utc >= now:
                 return False, "The signed certificate is not valid yet."
 
-            if ca_cert.get_subject().CN == cert.get_subject().CN:
+            if ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == \
+               cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME):
                 return False, ("CN may not be equal for CA certificate and the 
" "signed certificate.")
 
-            cert_algorithm = cert.get_signature_algorithm()
-            cert_algorithm = cert_algorithm.decode("ascii")
-            cert_str = cert_str.encode("ascii")
-
-            cert_crypto = 
saml2.cryptography.pki.load_pem_x509_certificate(cert_str)
-
             try:
-                crypto.verify(ca_cert, cert_crypto.signature, 
cert_crypto.tbs_certificate_bytes, cert_algorithm)
+                cert.verify_directly_issued_by(ca_cert)
                 return True, "Signed certificate is valid and correctly signed 
by CA certificate."
-            except crypto.Error as e:
+            except (ValueError, TypeError, InvalidSignature) as e:
                 return False, f"Certificate is incorrectly signed: {str(e)}"
         except Exception as e:
             return False, f"Certificate is not valid for an unknown reason. 
{str(e)}"
@@ -352,8 +364,14 @@ def read_cert_from_file(cert_file, cert_type="pem"):
         data = fp.read()
 
     try:
-        cert = saml2.cryptography.pki.load_x509_certificate(data, cert_type)
-        pem_data = saml2.cryptography.pki.get_public_bytes_from_cert(cert)
+        cert = None
+        if cert_type == "pem":
+            cert = x509.load_pem_x509_certificate(data)
+        elif cert_type == "der":
+            cert = x509.load_der_x509_certificate(data)
+        else:
+            raise ValueError(f"cert-type {cert_type} not supported")
+        pem_data = 
cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
     except Exception as e:
         raise CertificateError(e)
 
diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py
index f3af1ec99..98d11b1d1 100644
--- a/src/saml2/sigver.py
+++ b/src/saml2/sigver.py
@@ -28,7 +28,7 @@
 
 from urllib import parse
 
-from OpenSSL import crypto
+from cryptography import x509
 import pytz
 
 from saml2 import ExtensionElement
@@ -383,14 +383,14 @@ def active_cert(key):
     """
     try:
         cert_str = pem_format(key)
-        cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_str)
+        cert = x509.load_pem_x509_certificate(cert_str)
     except AttributeError:
         return False
 
-    now = pytz.UTC.localize(datetime.datetime.utcnow())
-    valid_from = dateutil.parser.parse(cert.get_notBefore())
-    valid_to = dateutil.parser.parse(cert.get_notAfter())
-    active = not cert.has_expired() and valid_from <= now < valid_to
+    now = datetime.datetime.now(datetime.UTC)
+    valid_from = cert.not_valid_before_utc
+    valid_to = cert.not_valid_after_utc
+    active = valid_from <= now < valid_to
     return active
 
 

++++++ v7.5.0.tar.gz -> v7.5.2.tar.gz ++++++
++++ 2671 lines of diff (skipped)

Reply via email to