Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-acme for openSUSE:Factory 
checked in at 2025-04-22 17:28:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-acme (Old)
 and      /work/SRC/openSUSE:Factory/.python-acme.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-acme"

Tue Apr 22 17:28:56 2025 rev:73 rq:1271239 version:4.0.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes  2025-03-21 
20:23:38.205791507 +0100
+++ /work/SRC/openSUSE:Factory/.python-acme.new.30101/python-acme.changes       
2025-04-22 17:29:38.318497910 +0200
@@ -1,0 +2,22 @@
+Tue Apr 22 03:28:47 UTC 2025 - Steve Kowalik <steven.kowa...@suse.com>
+
+- Update to 4.0.0:
+  * Added
+    + The --preferred-profile and --required-profile flags allow requesting
+      a profile.
+  * Changed
+    + Certificates now renew with 1/3rd of lifetime left (or 1/2 of lifetime
+      left, if the lifetime is shorter than 10 days).
+    + removed acme.crypto_util._pyopenssl_cert_or_req_all_names
+    + removed acme.crypto_util._pyopenssl_cert_or_req_san
+    + removed acme.crypto_util.dump_pyopenssl_chain
+    + removed acme.crypto_util.gen_ss_cert
+    + removed certbot.crypto_util.dump_pyopenssl_chain
+    + removed certbot.crypto_util.pyopenssl_load_certificate
+  * Fixed
+    + Moved RewriteEngine on directive added during apache http01
+      authentication to the end of the virtual host, so that it overwrites
+      any RewriteEngine off directives that already exist and allows
+      redirection to the challenge URL.
+
+-------------------------------------------------------------------

Old:
----
  acme-3.3.0.tar.gz

New:
----
  acme-4.0.0.tar.gz

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

Other differences:
------------------
++++++ python-acme.spec ++++++
--- /var/tmp/diff_new_pack.3P6fKE/_old  2025-04-22 17:29:38.866520930 +0200
+++ /var/tmp/diff_new_pack.3P6fKE/_new  2025-04-22 17:29:38.866520930 +0200
@@ -19,14 +19,14 @@
 %{?sle15_python_module_pythons}
 %define libname acme
 Name:           python-%{libname}
-Version:        3.3.0
+Version:        4.0.0
 Release:        0
 Summary:        Python library for the ACME protocol
 License:        Apache-2.0
 URL:            https://github.com/certbot/certbot
 Source0:        
https://files.pythonhosted.org/packages/source/a/%{libname}/%{libname}-%{version}.tar.gz
 BuildRequires:  %{python_module cryptography >= 43.0.0}
-BuildRequires:  %{python_module josepy >= 1.13.0}
+BuildRequires:  %{python_module josepy >= 2.0.0}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pyOpenSSL >= 25.0.0}
 BuildRequires:  %{python_module pyRFC3339}
@@ -38,7 +38,7 @@
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-cryptography >= 43.0.0
-Requires:       python-josepy >= 1.13.0
+Requires:       python-josepy >= 2.0.0
 Requires:       python-pyOpenSSL >= 25.0.0
 Requires:       python-pyRFC3339
 Requires:       python-pytz >= 2019.3
@@ -61,7 +61,6 @@
 
 %install
 %pyproject_install
-# remove duplicates
 %python_expand %fdupes %{buildroot}%{$python_sitelib}/%{libname}
 
 %check
@@ -71,5 +70,5 @@
 %license LICENSE.txt
 %pycache_only %{python_sitelib}/%{libname}/__pycache__
 %{python_sitelib}/%{libname}
-%{python_sitelib}/%{libname}-%{version}*info
+%{python_sitelib}/%{libname}-%{version}.dist-info
 

++++++ acme-3.3.0.tar.gz -> acme-4.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/PKG-INFO new/acme-4.0.0/PKG-INFO
--- old/acme-3.3.0/PKG-INFO     2025-03-11 16:03:30.108355800 +0100
+++ new/acme-4.0.0/PKG-INFO     2025-04-08 00:03:34.974550000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
 Name: acme
-Version: 3.3.0
+Version: 4.0.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/certbot/certbot
 Author: Certbot Project
@@ -21,7 +21,7 @@
 Requires-Python: >=3.9
 License-File: LICENSE.txt
 Requires-Dist: cryptography>=43.0.0
-Requires-Dist: josepy<2,>=1.13.0
+Requires-Dist: josepy>=2.0.0
 Requires-Dist: PyOpenSSL>=25.0.0
 Requires-Dist: pyrfc3339
 Requires-Dist: pytz>=2019.3
@@ -38,6 +38,7 @@
 Dynamic: classifier
 Dynamic: home-page
 Dynamic: license
+Dynamic: license-file
 Dynamic: provides-extra
 Dynamic: requires-dist
 Dynamic: requires-python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/challenges_test.py 
new/acme-4.0.0/acme/_internal/tests/challenges_test.py
--- old/acme-3.3.0/acme/_internal/tests/challenges_test.py      2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/challenges_test.py      2025-04-08 
00:03:33.000000000 +0200
@@ -13,7 +13,7 @@
 from acme import errors
 from acme._internal.tests import test_util
 
-CERT = test_util.load_comparable_cert('cert.pem')
+CERT = test_util.load_cert('cert.pem')
 KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/crypto_util_test.py 
new/acme-4.0.0/acme/_internal/tests/crypto_util_test.py
--- old/acme-3.3.0/acme/_internal/tests/crypto_util_test.py     2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/crypto_util_test.py     2025-04-08 
00:03:33.000000000 +0200
@@ -11,8 +11,6 @@
 from unittest import mock
 import warnings
 
-import josepy as jose
-import OpenSSL
 import pytest
 from cryptography import x509
 from cryptography.hazmat.primitives import serialization
@@ -33,10 +31,10 @@
     """Tests for acme.crypto_util.SSLSocket/probe_sni."""
 
     def setUp(self):
-        self.cert = test_util.load_comparable_cert('rsa2048_cert.pem')
+        self.cert = test_util.load_cert('rsa2048_cert.pem')
         key = test_util.load_pyopenssl_private_key('rsa2048_key.pem')
         # pylint: disable=protected-access
-        certs = {b'foo': (key, self.cert.wrapped)}
+        certs = {b'foo': (key, self.cert)}
 
         from acme.crypto_util import SSLSocket
 
@@ -58,8 +56,7 @@
 
     def _probe(self, name):
         from acme.crypto_util import probe_sni
-        return jose.ComparableX509(probe_sni(
-            name, host='127.0.0.1', port=self.port))
+        return probe_sni(name, host='127.0.0.1', port=self.port)
 
     def _start_server(self):
         self.server_thread.start()
@@ -97,48 +94,29 @@
             _ = SSLSocket(None)
 
 
-class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
-    """Test for acme.crypto_util._pyopenssl_cert_or_req_all_names."""
+class MiscTests(unittest.TestCase):
 
-    @classmethod
-    def _call(cls, loader, name):
-        # pylint: disable=protected-access
-        from acme.crypto_util import _pyopenssl_cert_or_req_all_names
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='acme.crypto_util._pyopenssl_cert_or_req_all_names is 
deprecated *'
-            )
-            return _pyopenssl_cert_or_req_all_names(loader(name))
-
-    def _call_cert(self, name):
-        return self._call(test_util.load_cert, name)
+    def test_dump_cryptography_chain(self):
+        from acme.crypto_util import dump_cryptography_chain
 
-    def test_cert_one_san_no_common(self):
-        assert self._call_cert('cert-nocn.der') == \
-                         ['no-common-name.badssl.com']
+        cert1 = test_util.load_cert('rsa2048_cert.pem')
+        cert2 = test_util.load_cert('rsa4096_cert.pem')
 
-    def test_cert_no_sans_yes_common(self):
-        assert self._call_cert('cert.pem') == ['example.com']
+        chain = [cert1, cert2]
+        dumped = dump_cryptography_chain(chain)
 
-    def test_cert_two_sans_yes_common(self):
-        assert self._call_cert('cert-san.pem') == \
-                         ['example.com', 'www.example.com']
+        # default is PEM encoding Encoding.PEM
+        assert isinstance(dumped, bytes)
 
 
-class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
-    """Test for acme.crypto_util._pyopenssl_cert_or_req_san."""
+class CryptographyCertOrReqSANTest(unittest.TestCase):
+    """Test for acme.crypto_util._cryptography_cert_or_req_san."""
 
     @classmethod
     def _call(cls, loader, name):
         # pylint: disable=protected-access
-        from acme.crypto_util import _pyopenssl_cert_or_req_san
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='acme.crypto_util._pyopenssl_cert_or_req_san is 
deprecated *'
-            )
-            return _pyopenssl_cert_or_req_san(loader(name))
+        from acme.crypto_util import _cryptography_cert_or_req_san
+        return _cryptography_cert_or_req_san(loader(name))
 
     @classmethod
     def _get_idn_names(cls):
@@ -285,41 +263,6 @@
         self.assertIn(extension, cert.extensions)
 
 
-class GenSsCertTest(unittest.TestCase):
-    """Test for gen_ss_cert (generation of self-signed cert)."""
-
-
-    def setUp(self):
-        self.cert_count = 5
-        self.serial_num: List[int] = []
-        self.key = OpenSSL.crypto.PKey()
-        self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
-
-    def test_sn_collisions(self):
-        from acme.crypto_util import gen_ss_cert
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore", DeprecationWarning)
-            for _ in range(self.cert_count):
-                cert = gen_ss_cert(self.key, ['dummy'], force_san=True,
-                                ips=[ipaddress.ip_address("10.10.10.10")])
-                self.serial_num.append(cert.get_serial_number())
-            assert len(set(self.serial_num)) >= self.cert_count
-
-    def test_no_name(self):
-        from acme.crypto_util import gen_ss_cert
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore", DeprecationWarning)
-            with pytest.raises(AssertionError):
-                gen_ss_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")])
-                gen_ss_cert(self.key)
-
-    def test_no_ips(self):
-        from acme.crypto_util import gen_ss_cert
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore", DeprecationWarning)
-            gen_ss_cert(self.key, ['dummy'])
-
-
 class MakeCSRTest(unittest.TestCase):
     """Test for standalone functions."""
 
@@ -395,42 +338,5 @@
             make_csr(privkey_pem, ["a.example"])
 
 
-class DumpPyopensslChainTest(unittest.TestCase):
-    """Test for dump_pyopenssl_chain."""
-
-    @classmethod
-    def _call(cls, loaded):
-        # pylint: disable=protected-access
-        from acme.crypto_util import dump_pyopenssl_chain
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='acme.crypto_util.dump_pyopenssl_chain is *'
-            )
-            return dump_pyopenssl_chain(loaded)
-
-    def test_dump_pyopenssl_chain(self):
-        names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem']
-        loaded = [test_util.load_cert(name) for name in names]
-        length = sum(
-            len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, 
cert))
-            for cert in loaded)
-        assert len(self._call(loaded)) == length
-
-    def test_dump_pyopenssl_chain_wrapped(self):
-        names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem']
-        loaded = [test_util.load_cert(name) for name in names]
-        wrap_func = jose.ComparableX509
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='The next major version of josepy *'
-            )
-            wrapped = [wrap_func(cert) for cert in loaded]
-            dump_func = OpenSSL.crypto.dump_certificate
-            length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for 
cert in loaded)
-            assert len(self._call(wrapped)) == length
-
-
 if __name__ == '__main__':
     sys.exit(pytest.main(sys.argv[1:] + [__file__]))  # pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/jose_test.py 
new/acme-4.0.0/acme/_internal/tests/jose_test.py
--- old/acme-3.3.0/acme/_internal/tests/jose_test.py    2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/jose_test.py    2025-04-08 
00:03:33.000000000 +0200
@@ -1,7 +1,6 @@
 """Tests for acme.jose shim."""
 import importlib
 import sys
-import unittest
 
 import pytest
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/messages_test.py 
new/acme-4.0.0/acme/_internal/tests/messages_test.py
--- old/acme-3.3.0/acme/_internal/tests/messages_test.py        2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/messages_test.py        2025-04-08 
00:03:33.000000000 +0200
@@ -1,10 +1,8 @@
 """Tests for acme.messages."""
-import contextlib
 import sys
 from typing import Dict
 import unittest
 from unittest import mock
-import warnings
 
 import josepy as jose
 import pytest
@@ -12,8 +10,8 @@
 from acme import challenges
 from acme._internal.tests import test_util
 
-CERT = test_util.load_comparable_cert('cert.der')
-CSR = test_util.load_comparable_csr('csr.der')
+CERT = test_util.load_cert('cert.der')
+CSR = test_util.load_csr('csr.der')
 KEY = test_util.load_rsa_private_key('rsa512_key.pem')
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/standalone_test.py 
new/acme-4.0.0/acme/_internal/tests/standalone_test.py
--- old/acme-3.3.0/acme/_internal/tests/standalone_test.py      2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/standalone_test.py      2025-04-08 
00:03:33.000000000 +0200
@@ -144,8 +144,8 @@
     #    cert = crypto_util.probe_sni(
     #        b'localhost', host=host, port=port, timeout=1)
     #    # Expect normal cert when connecting without ALPN.
-    #    self.assertEqual(jose.ComparableX509(cert),
-    #                     jose.ComparableX509(self.certs[b'localhost'][1]))
+    #    self.assertEqual(cert,
+    #                     self.certs[b'localhost'][1])
 
     def test_challenge_certs(self):
         host, port = self.server.socket.getsockname()[:2]
@@ -153,7 +153,7 @@
             b'localhost', host=host, port=port, timeout=1,
             alpn_protocols=[b"acme-tls/1"])
         #  Expect challenge cert when connecting with ALPN.
-        assert cert.to_cryptography() == self.challenge_certs[b'localhost'][1]
+        assert cert == self.challenge_certs[b'localhost'][1]
 
     def test_bad_alpn(self):
         host, port = self.server.socket.getsockname()[:2]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/test_util.py 
new/acme-4.0.0/acme/_internal/tests/test_util.py
--- old/acme-3.3.0/acme/_internal/tests/test_util.py    2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/test_util.py    2025-04-08 
00:03:33.000000000 +0200
@@ -5,8 +5,9 @@
 """
 import importlib.resources
 import os
-import sys
+from typing import Callable
 
+from cryptography import x509
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization
 import josepy as jose
@@ -21,37 +22,36 @@
     return vector_ref.read_bytes()
 
 
-def _guess_loader(filename, loader_pem, loader_der):
+def _guess_loader(filename: str, loader_pem: Callable, loader_der: Callable) 
-> Callable:
     _, ext = os.path.splitext(filename)
-    if ext.lower() == '.pem':
+    if ext.lower() == ".pem":
         return loader_pem
-    elif ext.lower() == '.der':
+    elif ext.lower() == ".der":
         return loader_der
-    raise ValueError("Loader could not be recognized based on extension")  # 
pragma: no cover
+    else:  # pragma: no cover
+        raise ValueError("Loader could not be recognized based on extension")
 
 
-def load_cert(*names):
-    """Load certificate."""
-    loader = _guess_loader(
-        names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
-    return crypto.load_certificate(loader, load_vector(*names))
-
-
-def load_comparable_cert(*names):
-    """Load ComparableX509 cert."""
-    return jose.ComparableX509(load_cert(*names))
+def _guess_pyopenssl_loader(filename: str, loader_pem: int, loader_der: int) 
-> int:
+    _, ext = os.path.splitext(filename)
+    if ext.lower() == ".pem":
+        return loader_pem
+    else:  # pragma: no cover
+        raise ValueError("Loader could not be recognized based on extension")
 
 
-def load_csr(*names):
-    """Load certificate request."""
+def load_cert(*names: str) -> x509.Certificate:
+    """Load certificate."""
     loader = _guess_loader(
-        names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
-    return crypto.load_certificate_request(loader, load_vector(*names))
+        names[-1], x509.load_pem_x509_certificate, 
x509.load_der_x509_certificate
+    )
+    return loader(load_vector(*names))
 
 
-def load_comparable_csr(*names):
-    """Load ComparableX509 certificate request."""
-    return jose.ComparableX509(load_csr(*names))
+def load_csr(*names: str) -> x509.CertificateSigningRequest:
+    """Load certificate request."""
+    loader = _guess_loader(names[-1], x509.load_pem_x509_csr, 
x509.load_der_x509_csr)
+    return loader(load_vector(*names))
 
 
 def load_rsa_private_key(*names):
@@ -72,6 +72,6 @@
 
 def load_pyopenssl_private_key(*names):
     """Load pyOpenSSL private key."""
-    loader = _guess_loader(
+    loader = _guess_pyopenssl_loader(
         names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
     return crypto.load_privatekey(loader, load_vector(*names))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/_internal/tests/util_test.py 
new/acme-4.0.0/acme/_internal/tests/util_test.py
--- old/acme-3.3.0/acme/_internal/tests/util_test.py    2025-03-11 
16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/_internal/tests/util_test.py    2025-04-08 
00:03:33.000000000 +0200
@@ -1,6 +1,5 @@
 """Tests for acme.util."""
 import sys
-import unittest
 
 import pytest
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/challenges.py 
new/acme-4.0.0/acme/challenges.py
--- old/acme-3.3.0/acme/challenges.py   2025-03-11 16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/challenges.py   2025-04-08 00:03:33.000000000 +0200
@@ -419,7 +419,7 @@
         return hashlib.sha256(self.key_authorization.encode('utf-8')).digest()
 
     def gen_cert(self, domain: str, key: Optional[crypto.PKey] = None, bits: 
int = 2048
-                 ) -> Tuple[crypto.X509, crypto.PKey]:
+                 ) -> Tuple[x509.Certificate, crypto.PKey]:
         """Generate tls-alpn-01 certificate.
 
         :param str domain: Domain verified by the challenge.
@@ -428,7 +428,7 @@
             fresh key will be generated.
         :param int bits: Number of bits for newly generated key.
 
-        :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
+        :rtype: `tuple` of `x509.Certificate` and `OpenSSL.crypto.PKey`
 
         """
         if key is None:
@@ -450,10 +450,10 @@
             force_san=True,
             extensions=[acme_extension]
         )
-        return crypto.X509.from_cryptography(cert), key
+        return cert, key
 
     def probe_cert(self, domain: str, host: Optional[str] = None,
-                   port: Optional[int] = None) -> crypto.X509:
+                   port: Optional[int] = None) -> x509.Certificate:
         """Probe tls-alpn-01 challenge certificate.
 
         :param str domain: domain being validated, required.
@@ -470,20 +470,17 @@
         return crypto_util.probe_sni(host=host.encode(), port=port, 
name=domain.encode(),
                                      alpn_protocols=[self.ACME_TLS_1_PROTOCOL])
 
-    def verify_cert(self, domain: str, cert: Union[x509.Certificate, 
crypto.X509]) -> bool:
+    def verify_cert(self, domain: str, cert: x509.Certificate, ) -> bool:
         """Verify tls-alpn-01 challenge certificate.
 
         :param str domain: Domain name being validated.
         :param cert: Challenge certificate.
-        :type cert: `cryptography.x509.Certificate` or `OpenSSL.crypto.X509`
+        :type cert: `cryptography.x509.Certificate`
 
         :returns: Whether the certificate was successfully verified.
         :rtype: bool
 
         """
-        if not isinstance(cert, x509.Certificate):
-            cert = cert.to_cryptography()
-
         names = crypto_util.get_names_from_subject_and_extensions(
             cert.subject, cert.extensions
         )
@@ -506,7 +503,7 @@
 
     # pylint: disable=too-many-arguments
     def simple_verify(self, chall: 'TLSALPN01', domain: str, 
account_public_key: jose.JWK,
-                      cert: Optional[crypto.X509] = None, host: Optional[str] 
= None,
+                      cert: Optional[x509.Certificate] = None, host: 
Optional[str] = None,
                       port: Optional[int] = None) -> bool:
         """Simple verify.
 
@@ -516,7 +513,7 @@
         :param .challenges.TLSALPN01 chall: Corresponding challenge.
         :param str domain: Domain name being validated.
         :param JWK account_public_key:
-        :param OpenSSL.crypto.X509 cert: Optional certificate. If not
+        :param x509.Certificate cert: Optional certificate. If not
             provided (``None``) certificate will be retrieved using
             `probe_cert`.
         :param string host: IP address used to probe the certificate.
@@ -547,7 +544,8 @@
     response_cls = TLSALPN01Response
     typ = response_cls.typ
 
-    def validation(self, account_key: jose.JWK, **kwargs: Any) -> 
Tuple[crypto.X509, crypto.PKey]:
+    def validation(self, account_key: jose.JWK,
+                   **kwargs: Any) -> Tuple[x509.Certificate, crypto.PKey]:
         """Generate validation.
 
         :param JWK account_key:
@@ -556,7 +554,7 @@
             in certificate generation. If not provided (``None``), then
             fresh key will be generated.
 
-        :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
+        :rtype: `tuple` of `x509.Certificate` and `OpenSSL.crypto.PKey`
 
         """
         # TODO: Remove cast when response() is generic.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/client.py 
new/acme-4.0.0/acme/client.py
--- old/acme-3.3.0/acme/client.py       2025-03-11 16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/client.py       2025-04-08 00:03:33.000000000 +0200
@@ -14,12 +14,10 @@
 from typing import Set
 from typing import Tuple
 from typing import Union
-import warnings
 
 from cryptography import x509
 
 import josepy as jose
-import OpenSSL
 import requests
 from requests.adapters import HTTPAdapter
 from requests.utils import parse_header_links
@@ -227,12 +225,8 @@
         :returns: updated order
         :rtype: messages.OrderResource
         """
-        csr = OpenSSL.crypto.load_certificate_request(
-            OpenSSL.crypto.FILETYPE_PEM, orderr.csr_pem)
-        with warnings.catch_warnings():
-            warnings.filterwarnings('ignore',
-                message='The next major version of josepy will remove 
josepy.util.ComparableX509')
-            wrapped_csr = 
messages.CertificateRequest(csr=jose.ComparableX509(csr))
+        csr = x509.load_pem_x509_csr(orderr.csr_pem)
+        wrapped_csr = messages.CertificateRequest(csr=csr)
         res = self._post(orderr.body.finalize, wrapped_csr)
         orderr = orderr.update(body=messages.Order.from_json(res.json()))
         return orderr
@@ -285,11 +279,10 @@
         self.begin_finalization(orderr)
         return self.poll_finalization(orderr, deadline, 
fetch_alternative_chains)
 
-    def revoke(self, cert: jose.ComparableX509, rsn: int) -> None:
+    def revoke(self, cert: x509.Certificate, rsn: int) -> None:
         """Revoke certificate.
 
-        :param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in
-            `.ComparableX509`
+        :param x509.Certificate cert: `x509.Certificate`
 
         :param int rsn: Reason code for certificate revocation.
 
@@ -477,11 +470,10 @@
 
         return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
 
-    def _revoke(self, cert: jose.ComparableX509, rsn: int, url: str) -> None:
+    def _revoke(self, cert: x509.Certificate, rsn: int, url: str) -> None:
         """Revoke certificate.
 
-        :param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in
-            `.ComparableX509`
+        :param .x509.Certificate cert: `x509.Certificate`
 
         :param int rsn: Reason code for certificate revocation.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/crypto_util.py 
new/acme-4.0.0/acme/crypto_util.py
--- old/acme-3.3.0/acme/crypto_util.py  2025-03-11 16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/crypto_util.py  2025-04-08 00:03:33.000000000 +0200
@@ -1,16 +1,15 @@
 """Crypto utilities."""
-import binascii
 import contextlib
 import enum
 from datetime import datetime, timedelta, timezone
 import ipaddress
 import logging
-import os
 import socket
 import typing
 from typing import Any
 from typing import Callable
 from typing import List
+from typing import Literal
 from typing import Mapping
 from typing import Optional
 from typing import Sequence
@@ -21,23 +20,22 @@
 from cryptography import x509
 from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.asymmetric import dsa, rsa, ec, ed25519, 
ed448, types
-import josepy as jose
+from cryptography.hazmat.primitives.serialization import Encoding
 from OpenSSL import crypto
 from OpenSSL import SSL
 
 from acme import errors
-import warnings
 
 logger = logging.getLogger(__name__)
 
 # Default SSL method selected here is the most compatible, while secure
 # SSL method: TLSv1_METHOD is only compatible with
-# TLSv1_METHOD, while SSLv23_METHOD is compatible with all other
+# TLSv1_METHOD, while TLS_method is compatible with all other
 # methods, including TLSv2_METHOD (read more at
-# https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
+# https://docs.openssl.org/master/man3/SSL_CTX_new/#notes). _serve_sni
 # should be changed to use "set_options" to disable SSLv2 and SSLv3,
 # in case it's used for things other than probing/serving!
-_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD
+_DEFAULT_SSL_METHOD = SSL.TLS_METHOD
 
 
 class Format(enum.IntEnum):
@@ -49,13 +47,13 @@
     DER = crypto.FILETYPE_ASN1
     PEM = crypto.FILETYPE_PEM
 
-    def to_cryptography_encoding(self) -> serialization.Encoding:
+    def to_cryptography_encoding(self) -> Encoding:
         """Converts the Format to the corresponding cryptography `Encoding`.
         """
         if self == Format.DER:
-            return serialization.Encoding.DER
+            return Encoding.DER
         else:
-            return serialization.Encoding.PEM
+            return Encoding.PEM
 
 
 _KeyAndCert = Union[
@@ -74,6 +72,7 @@
             return self.certs.get(server_name, None)
         return None # pragma: no cover
 
+
 class SSLSocket:  # pylint: disable=too-few-public-methods
     """SSL wrapper for sockets.
 
@@ -133,9 +132,10 @@
             return
         key, cert = pair
         new_context = SSL.Context(self.method)
-        new_context.set_options(SSL.OP_NO_SSLv2)
-        new_context.set_options(SSL.OP_NO_SSLv3)
+        new_context.set_min_proto_version(SSL.TLS1_2_VERSION)
         new_context.use_privatekey(key)
+        if isinstance(cert, x509.Certificate):
+            cert = crypto.X509.from_cryptography(cert)
         new_context.use_certificate(cert)
         if self.alpn_selection is not None:
             new_context.set_alpn_select_callback(self.alpn_selection)
@@ -197,7 +197,7 @@
 
 def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300,  
# pylint: disable=too-many-arguments
               method: int = _DEFAULT_SSL_METHOD, source_address: Tuple[str, 
int] = ('', 0),
-              alpn_protocols: Optional[Sequence[bytes]] = None) -> crypto.X509:
+              alpn_protocols: Optional[Sequence[bytes]] = None) -> 
x509.Certificate:
     """Probe SNI server for SSL certificate.
 
     :param bytes name: Byte string to send as the server name in the
@@ -215,7 +215,7 @@
     :raises acme.errors.Error: In case of any problems.
 
     :returns: SSL certificate presented by the server.
-    :rtype: OpenSSL.crypto.X509
+    :rtype: cryptography.x509.Certificate
 
     """
     context = SSL.Context(method)
@@ -249,21 +249,9 @@
             raise errors.Error(error)
     cert = client_ssl.get_peer_certificate()
     assert cert # Appease mypy. We would have crashed out by now if there was 
no certificate.
-    return cert
+    return cert.to_cryptography()
 
 
-# Annoyingly, we can't directly use cryptography's equivalent Union[] type for
-# our type signatures since they're only public API in 40.0.x+, which is too 
new
-# for some Certbot # distribution channels. Once we bump our oldest 
cryptography
-# version past 40.0.x, usage of this type can be replaced with:
-# 
cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes
-CertificateIssuerPrivateKeyTypes = Union[
-    dsa.DSAPrivateKey,
-    rsa.RSAPrivateKey,
-    ec.EllipticCurvePrivateKey,
-    ed25519.Ed25519PrivateKey,
-    ed448.Ed448PrivateKey,
-]
 # Even *more* annoyingly, due to a mypy bug, we can't use Union[] types in
 # isinstance expressions without causing false mypy errors. So we have to
 # recreate the type collection as a tuple here. And no, typing.get_args doesn't
@@ -334,7 +322,7 @@
         )
 
     csr = builder.sign(private_key, hashes.SHA256())
-    return csr.public_bytes(serialization.Encoding.PEM)
+    return csr.public_bytes(Encoding.PEM)
 
 
 def get_names_from_subject_and_extensions(
@@ -371,32 +359,16 @@
         return [cns[0]] + [d for d in dns_names if d != cns[0]]
 
 
-def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req: Union[crypto.X509, 
crypto.X509Req]
-                                     ) -> List[str]:
-    """
-    Deprecated
-    .. deprecated: 3.2.1
-    """
-    warnings.warn(
-        "acme.crypto_util._pyopenssl_cert_or_req_all_names is deprecated and "
-        "will be removed in the next major release of Certbot.",
-        DeprecationWarning,
-        stacklevel=2
-    )
-    cert_or_req = loaded_cert_or_req.to_cryptography()
-    return get_names_from_subject_and_extensions(
-        cert_or_req.subject, cert_or_req.extensions
-    )
-
-
-def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, 
crypto.X509Req]) -> List[str]:
+def _cryptography_cert_or_req_san(
+    cert_or_req: Union[x509.Certificate, x509.CertificateSigningRequest],
+) -> List[str]:
     """Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
 
     .. note:: Although this is `acme` internal API, it is used by
         `letsencrypt`.
 
     :param cert_or_req: Certificate or CSR.
-    :type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
+    :type cert_or_req: `x509.Certificate` or `x509.CertificateSigningRequest`.
 
     :returns: A list of Subject Alternative Names that is DNS.
     :rtype: `list` of `str`
@@ -404,13 +376,8 @@
     Deprecated
     .. deprecated: 3.2.1
     """
-    warnings.warn(
-        "acme.crypto_util._pyopenssl_cert_or_req_san is deprecated and "
-        "will be removed in the next major release of Certbot.",
-        DeprecationWarning,
-        stacklevel=2
-    )
-    exts = cert_or_req.to_cryptography().extensions
+    # ???: is this translation needed?
+    exts = cert_or_req.extensions
     try:
         san_ext = exts.get_extension_for_class(x509.SubjectAlternativeName)
     except x509.ExtensionNotFound:
@@ -424,7 +391,7 @@
     return datetime.now(tz=timezone.utc)
 
 
-def make_self_signed_cert(private_key: CertificateIssuerPrivateKeyTypes,
+def make_self_signed_cert(private_key: types.CertificateIssuerPrivateKeyTypes,
                           domains: Optional[List[str]] = None,
                           not_before: Optional[datetime] = None,
                           validity: Optional[timedelta] = None, force_san: 
bool = True,
@@ -441,7 +408,8 @@
     :param validity: Duration for which the cert will be valid. Defaults to 1
     week
     :type validity: `datetime.timedelta`
-    :param buffer private_key_pem: One of `CertificateIssuerPrivateKeyTypes`
+    :param buffer private_key_pem: One of
+    
`cryptography.hazmat.primitives.asymmetric.types.CertificateIssuerPrivateKeyTypes`
     :param bool force_san:
     :param extensions: List of additional extensions to include in the cert.
     :type extensions: `list` of `x509.Extension[x509.ExtensionType]`
@@ -498,85 +466,13 @@
     return builder.sign(private_key, hashes.SHA256())
 
 
-def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
-                not_before: Optional[int] = None,
-                validity: int = (7 * 24 * 60 * 60), force_san: bool = True,
-                extensions: Optional[List[crypto.X509Extension]] = None,
-                ips: Optional[List[Union[ipaddress.IPv4Address, 
ipaddress.IPv6Address]]] = None
-                ) -> crypto.X509:
-    """Generate new self-signed certificate.
-
-    :type domains: `list` of `str`
-    :param OpenSSL.crypto.PKey key:
-    :param bool force_san:
-    :param extensions: List of additional extensions to include in the cert.
-    :type extensions: `list` of `OpenSSL.crypto.X509Extension`
-    :type ips: `list` of (`ipaddress.IPv4Address` or `ipaddress.IPv6Address`)
-
-    If more than one domain is provided, all of the domains are put into
-    ``subjectAltName`` X.509 extension and first domain is set as the
-    subject CN. If only one domain is provided no ``subjectAltName``
-    extension is used, unless `force_san` is ``True``.
-
-    .. deprecated: 2.10.0
-    """
-    warnings.warn(
-        "acme.crypto_util.gen_ss_cert is deprecated and will be removed in the 
"
-        "next major release of Certbot. Please use "
-        "acme.crypto_util.make_self_signed_cert instead.", DeprecationWarning,
-        stacklevel=2
-    )
-    assert domains or ips, "Must provide one or more hostnames or IPs for the 
cert."
-
-    cert = crypto.X509()
-    cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16))
-    cert.set_version(2)
-
-    if extensions is None:
-        extensions = []
-    if domains is None:
-        domains = []
-    if ips is None:
-        ips = []
-    extensions.append(
-        crypto.X509Extension(
-            b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
-    )
-
-    if len(domains) > 0:
-        cert.get_subject().CN = domains[0]
-    # TODO: what to put into cert.get_subject()?
-    cert.set_issuer(cert.get_subject())
-
-    sanlist = []
-    for address in domains:
-        sanlist.append('DNS:' + address)
-    for ip in ips:
-        sanlist.append('IP:' + ip.exploded)
-    san_string = ', '.join(sanlist).encode('ascii')
-    if force_san or len(domains) > 1 or len(ips) > 0:
-        extensions.append(crypto.X509Extension(
-            b"subjectAltName",
-            critical=False,
-            value=san_string
-        ))
-
-    cert.add_extensions(extensions)
-
-    cert.gmtime_adj_notBefore(0 if not_before is None else not_before)
-    cert.gmtime_adj_notAfter(validity)
-
-    cert.set_pubkey(key)
-    cert.sign(key, "sha256")
-    return cert
-
-
-def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], 
List[crypto.X509]],
-                         filetype: Union[Format, int] = Format.PEM) -> bytes:
+def dump_cryptography_chain(
+    chain: List[x509.Certificate],
+    encoding: Literal[Encoding.PEM, Encoding.DER] = Encoding.PEM,
+) -> bytes:
     """Dump certificate chain into a bundle.
 
-    :param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
-        :class:`josepy.util.ComparableX509`).
+    :param list chain: List of `cryptography.x509.Certificate`.
 
     :returns: certificate chain bundle
     :rtype: bytes
@@ -584,24 +480,12 @@
     Deprecated
     .. deprecated: 3.2.1
     """
-    warnings.warn(
-        "acme.crypto_util.dump_pyopenssl_chain is deprecated and "
-        "will be removed in the next major release of Certbot.",
-        DeprecationWarning,
-        stacklevel=2
-    )
     # XXX: returns empty string when no chain is available, which
     # shuts up RenewableCert, but might not be the best solution...
 
-    filetype = Format(filetype)
-    def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes:
-        if isinstance(cert, jose.ComparableX509):
-            if isinstance(cert.wrapped, crypto.X509Req):
-                raise errors.Error("Unexpected CSR provided.")  # pragma: no 
cover
-            cert = cert.wrapped
-
-        return 
cert.to_cryptography().public_bytes(filetype.to_cryptography_encoding())
+    def _dump_cert(cert: x509.Certificate) -> bytes:
+        return cert.public_bytes(encoding)
 
-    # assumes that OpenSSL.crypto.dump_certificate includes ending
+    # assumes that x509.Certificate.public_bytes includes ending
     # newline character
     return b"".join(_dump_cert(cert) for cert in chain)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme/messages.py 
new/acme-4.0.0/acme/messages.py
--- old/acme-3.3.0/acme/messages.py     2025-03-11 16:03:28.000000000 +0100
+++ new/acme-4.0.0/acme/messages.py     2025-04-08 00:03:33.000000000 +0200
@@ -12,7 +12,8 @@
 from typing import Tuple
 from typing import Type
 from typing import TypeVar
-import warnings
+
+from cryptography import x509
 
 import josepy as jose
 
@@ -580,45 +581,33 @@
 class CertificateRequest(jose.JSONObjectWithFields):
     """ACME newOrder request.
 
-    :ivar jose.ComparableX509 csr:
-        `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
+    :ivar x509.CertificateSigningRequest csr: `x509.CertificateSigningRequest`
 
     """
-    with warnings.catch_warnings():
-        warnings.filterwarnings('ignore',
-            message='The next major version of josepy will remove 
josepy.util.ComparableX509')
-        csr: jose.ComparableX509 = jose.field(
-            'csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
+    csr: x509.CertificateSigningRequest = jose.field(
+        'csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
 
 
 class CertificateResource(ResourceWithURI):
     """Certificate Resource.
 
-    :ivar josepy.util.ComparableX509 body:
-        `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
+    :ivar x509.Certificate body: `x509.Certificate`
     :ivar str cert_chain_uri: URI found in the 'up' ``Link`` header
     :ivar tuple authzrs: `tuple` of `AuthorizationResource`.
 
     """
-    with warnings.catch_warnings():
-        warnings.filterwarnings('ignore',
-            message='The next major version of josepy will remove 
josepy.util.ComparableX509')
-        cert_chain_uri: str = jose.field('cert_chain_uri')
+    cert_chain_uri: str = jose.field('cert_chain_uri')
     authzrs: Tuple[AuthorizationResource, ...] = jose.field('authzrs')
 
 
 class Revocation(jose.JSONObjectWithFields):
     """Revocation message.
 
-    :ivar jose.ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
-        `jose.ComparableX509`
+    :ivar x509.Certificate certificate: `x509.Certificate`
 
     """
-    with warnings.catch_warnings():
-        warnings.filterwarnings('ignore',
-            message='The next major version of josepy will remove 
josepy.util.ComparableX509')
-        certificate: jose.ComparableX509 = jose.field(
-            'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
+    certificate: x509.Certificate = jose.field(
+        'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
     reason: int = jose.field('reason')
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme.egg-info/PKG-INFO 
new/acme-4.0.0/acme.egg-info/PKG-INFO
--- old/acme-3.3.0/acme.egg-info/PKG-INFO       2025-03-11 16:03:30.000000000 
+0100
+++ new/acme-4.0.0/acme.egg-info/PKG-INFO       2025-04-08 00:03:34.000000000 
+0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
 Name: acme
-Version: 3.3.0
+Version: 4.0.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/certbot/certbot
 Author: Certbot Project
@@ -21,7 +21,7 @@
 Requires-Python: >=3.9
 License-File: LICENSE.txt
 Requires-Dist: cryptography>=43.0.0
-Requires-Dist: josepy<2,>=1.13.0
+Requires-Dist: josepy>=2.0.0
 Requires-Dist: PyOpenSSL>=25.0.0
 Requires-Dist: pyrfc3339
 Requires-Dist: pytz>=2019.3
@@ -38,6 +38,7 @@
 Dynamic: classifier
 Dynamic: home-page
 Dynamic: license
+Dynamic: license-file
 Dynamic: provides-extra
 Dynamic: requires-dist
 Dynamic: requires-python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/acme.egg-info/requires.txt 
new/acme-4.0.0/acme.egg-info/requires.txt
--- old/acme-3.3.0/acme.egg-info/requires.txt   2025-03-11 16:03:30.000000000 
+0100
+++ new/acme-4.0.0/acme.egg-info/requires.txt   2025-04-08 00:03:34.000000000 
+0200
@@ -1,5 +1,5 @@
 cryptography>=43.0.0
-josepy<2,>=1.13.0
+josepy>=2.0.0
 PyOpenSSL>=25.0.0
 pyrfc3339
 pytz>=2019.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/examples/http01_example.py 
new/acme-4.0.0/examples/http01_example.py
--- old/acme-3.3.0/examples/http01_example.py   2025-03-11 16:03:28.000000000 
+0100
+++ new/acme-4.0.0/examples/http01_example.py   2025-04-08 00:03:33.000000000 
+0200
@@ -200,11 +200,7 @@
 
     # Revoke certificate
 
-    fullchain_com = jose.ComparableX509(
-        OpenSSL.crypto.X509.from_cryptography(
-            x509.load_pem_x509_certificate(fullchain_pem)
-        )
-    )
+    fullchain_com = x509.load_pem_x509_certificate(fullchain_pem)
 
     try:
         client_acme.revoke(fullchain_com, 0)  # revocation reason = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-3.3.0/setup.py new/acme-4.0.0/setup.py
--- old/acme-3.3.0/setup.py     2025-03-11 16:03:29.000000000 +0100
+++ new/acme-4.0.0/setup.py     2025-04-08 00:03:33.000000000 +0200
@@ -1,15 +1,11 @@
-import sys
-
 from setuptools import find_packages
 from setuptools import setup
 
-version = '3.3.0'
+version = '4.0.0'
 
 install_requires = [
     'cryptography>=43.0.0',
-    # Josepy 2+ may introduce backward incompatible changes by droping usage of
-    # deprecated PyOpenSSL APIs.
-    'josepy>=1.13.0, <2',
+    'josepy>=2.0.0',
     # PyOpenSSL>=25.0.0 is just needed to satisfy mypy right now so this 
dependency can probably be
     # relaxed to >=24.0.0 if needed.
     'PyOpenSSL>=25.0.0',

Reply via email to