Script 'mail_helper' called by obssrc
Hello community,

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

Package is "python-certbot"

Tue Apr 22 17:28:57 2025 rev:56 rq:1271240 version:4.0.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-certbot/python-certbot.changes    
2025-03-21 20:23:39.033825955 +0100
+++ /work/SRC/openSUSE:Factory/.python-certbot.new.30101/python-certbot.changes 
2025-04-22 17:29:40.074571675 +0200
@@ -1,0 +2,22 @@
+Tue Apr 22 03:35:34 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:
----
  certbot-3.3.0.tar.gz

New:
----
  certbot-4.0.0.tar.gz

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

Other differences:
------------------
++++++ python-certbot.spec ++++++
--- /var/tmp/diff_new_pack.iPnNNN/_old  2025-04-22 17:29:40.702598055 +0200
+++ /var/tmp/diff_new_pack.iPnNNN/_new  2025-04-22 17:29:40.702598055 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-certbot
-Version:        3.3.0
+Version:        4.0.0
 Release:        0
 Summary:        ACME client
 License:        Apache-2.0
@@ -30,7 +30,7 @@
 BuildRequires:  %{python_module cryptography >= 43.0.0}
 BuildRequires:  %{python_module distro >= 1.0.1}
 BuildRequires:  %{python_module importlib-metadata if %python-base < 3.10}
-BuildRequires:  %{python_module josepy >= 1.13.0}
+BuildRequires:  %{python_module josepy >= 2.0.0}
 BuildRequires:  %{python_module parsedatetime >= 2.4}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pyRFC3339}
@@ -44,7 +44,7 @@
 Requires:       python-configobj >= 5.0.6
 Requires:       python-cryptography >= 43.0.0
 Requires:       python-distro >= 1.0.1
-Requires:       python-josepy >= 1.9.0
+Requires:       python-josepy >= 2.0.0
 Requires:       python-parsedatetime >= 2.4
 Requires:       python-pyRFC3339
 Requires:       python-pytz >= 2019.3
@@ -63,8 +63,7 @@
 to lower the barriers to entry for encrypting all HTTP traffic on the internet.
 
 %prep
-%setup -q -n certbot-%{version}
-%autopatch -p1
+%autosetup -p1 -n certbot-%{version}
 
 %build
 %pyproject_wheel
@@ -94,6 +93,6 @@
 %license LICENSE.txt
 %doc README.rst
 %{python_sitelib}/certbot
-%{python_sitelib}/certbot-%{version}*info
+%{python_sitelib}/certbot-%{version}.dist-info
 %python_alternative %{_bindir}/certbot
 

++++++ certbot-3.3.0.tar.gz -> certbot-4.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/CHANGELOG.md 
new/certbot-4.0.0/CHANGELOG.md
--- old/certbot-3.3.0/CHANGELOG.md      2025-03-11 16:03:28.000000000 +0100
+++ new/certbot-4.0.0/CHANGELOG.md      2025-04-08 00:03:33.000000000 +0200
@@ -2,6 +2,36 @@
 
 Certbot adheres to [Semantic Versioning](https://semver.org/).
 
+## 4.0.0 - 2025-04-07
+
+### Added
+
+* The --preferred-profile and --required-profile flags allow requesting a 
profile.
+  https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
+
+### Changed
+
+* Certificates now renew with 1/3rd of lifetime left (or 1/2 of lifetime left,
+  if the lifetime is shorter than 10 days). This is a change from a hardcoded
+  renewal at 30 days before expiration. The config field renew_before_expiry
+  still overrides this default.
+
+* 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.
+
+More details about these changes can be found on our GitHub repo.
+
 ## 3.3.0 - 2025-03-11
 
 ### Added
@@ -12,9 +42,9 @@
 
 * The --register-unsafely-without-email flag is no longer needed in 
non-interactive mode.
 * In interactive mode, pressing Enter at the email prompt will register 
without an email.
-* deprecated `acme.crypto_util.dump_pyopenssl_chain`
 * deprecated `acme.crypto_util._pyopenssl_cert_or_req_all_names`
 * deprecated `acme.crypto_util._pyopenssl_cert_or_req_san`
+* deprecated `acme.crypto_util.dump_pyopenssl_chain`
 * deprecated `certbot.crypto_util.dump_pyopenssl_chain`
 * deprecated `certbot.crypto_util.pyopenssl_load_certificate`
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/PKG-INFO new/certbot-4.0.0/PKG-INFO
--- old/certbot-3.3.0/PKG-INFO  2025-03-11 16:03:29.509018700 +0100
+++ new/certbot-4.0.0/PKG-INFO  2025-04-08 00:03:34.270558800 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
 Name: certbot
-Version: 3.3.0
+Version: 4.0.0
 Summary: ACME client
 Home-page: https://github.com/certbot/certbot
 Author: Certbot Project
@@ -27,13 +27,13 @@
 Classifier: Topic :: Utilities
 Requires-Python: >=3.9
 License-File: LICENSE.txt
-Requires-Dist: acme>=3.3.0
+Requires-Dist: acme>=4.0.0
 Requires-Dist: ConfigArgParse>=1.5.3
 Requires-Dist: configobj>=5.0.6
 Requires-Dist: cryptography>=43.0.0
 Requires-Dist: distro>=1.0.1
 Requires-Dist: importlib_metadata>=4.6; python_version < "3.10"
-Requires-Dist: josepy<2,>=1.13.0
+Requires-Dist: josepy>=2.0.0
 Requires-Dist: parsedatetime>=2.4
 Requires-Dist: pyrfc3339
 Requires-Dist: pytz>=2019.3
@@ -94,6 +94,7 @@
 Dynamic: description
 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/certbot-3.3.0/certbot/__init__.py 
new/certbot-4.0.0/certbot/__init__.py
--- old/certbot-3.3.0/certbot/__init__.py       2025-03-11 16:03:29.000000000 
+0100
+++ new/certbot-4.0.0/certbot/__init__.py       2025-04-08 00:03:33.000000000 
+0200
@@ -1,4 +1,4 @@
 """Certbot client."""
 
 # version number like 1.2.3a0, must have at least 2 parts, like 1.2
-__version__ = '3.3.0'
+__version__ = '4.0.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/cli/__init__.py 
new/certbot-4.0.0/certbot/_internal/cli/__init__.py
--- old/certbot-3.3.0/certbot/_internal/cli/__init__.py 2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/cli/__init__.py 2025-04-08 
00:03:33.000000000 +0200
@@ -360,6 +360,16 @@
              "user; only needed if your config is somewhere unsafe like /tmp/")
     helpful.add(
         [None, "certonly", "renew", "run"],
+        "--required-profile", dest="required_profile",
+        default=flag_default("required_profile"), 
help=config_help("required_profile")
+    )
+    helpful.add(
+        [None, "certonly", "renew", "run"],
+        "--preferred-profile", dest="preferred_profile",
+        default=flag_default("preferred_profile"), 
help=config_help("preferred_profile")
+    )
+    helpful.add(
+        [None, "certonly", "renew", "run"],
         "--preferred-chain", dest="preferred_chain",
         default=flag_default("preferred_chain"), 
help=config_help("preferred_chain")
     )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/client.py 
new/certbot-4.0.0/certbot/_internal/client.py
--- old/certbot-3.3.0/certbot/_internal/client.py       2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/client.py       2025-04-08 
00:03:33.000000000 +0200
@@ -48,8 +48,9 @@
 logger = logging.getLogger(__name__)
 
 
-def acme_from_config_key(config: configuration.NamespaceConfig, key: jose.JWK,
-                         regr: Optional[messages.RegistrationResource] = None
+def acme_from_config_key(config: configuration.NamespaceConfig,
+                         key: jose.JWK,
+                         regr: Optional[messages.RegistrationResource] = None,
                          ) -> acme_client.ClientV2:
     """Wrangle ACME client construction"""
     if key.typ == 'EC':
@@ -470,8 +471,17 @@
         """
         if not self.acme:
             raise errors.Error("ACME client is not set.")
+
+        profile = None
+        available_profiles = self.acme.directory.meta.profiles
+        preferred_profile = self.config.preferred_profile
+        if self.config.required_profile is not None:
+            profile = self.config.required_profile
+        elif (preferred_profile and available_profiles and
+              preferred_profile in available_profiles):
+            profile = preferred_profile
         try:
-            orderr = self.acme.new_order(csr_pem)
+            orderr = self.acme.new_order(csr_pem, profile=profile)
         except acme_errors.WildcardUnsupportedError:
             raise errors.Error("The currently selected ACME CA endpoint does"
                                " not support issuing wildcard certificates.")
@@ -484,7 +494,7 @@
             deactivated, failed = 
self.auth_handler.deactivate_valid_authorizations(orderr)
             if deactivated:
                 logger.debug("Recreating order after authz deactivations")
-                orderr = self.acme.new_order(csr_pem)
+                orderr = self.acme.new_order(csr_pem, profile=profile)
             if failed:
                 logger.warning("Certbot was unable to obtain fresh 
authorizations for every domain"
                                ". The dry run will continue, but results may 
not be accurate.")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/constants.py 
new/certbot-4.0.0/certbot/_internal/constants.py
--- old/certbot-3.3.0/certbot/_internal/constants.py    2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/constants.py    2025-04-08 
00:03:33.000000000 +0200
@@ -70,6 +70,8 @@
     uir=None, # listed as False in help output
     staple=None, # listed as False in help output
     strict_permissions=False,
+    required_profile=None,
+    preferred_profile=None,
     preferred_chain=None,
     pref_challs=[],
     validate_hooks=True,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/main.py 
new/certbot-4.0.0/certbot/_internal/main.py
--- old/certbot-3.3.0/certbot/_internal/main.py 2025-03-11 16:03:28.000000000 
+0100
+++ new/certbot-4.0.0/certbot/_internal/main.py 2025-04-08 00:03:33.000000000 
+0200
@@ -17,6 +17,7 @@
 from typing import Union
 
 import configobj
+from cryptography import x509
 import josepy as jose
 from josepy import b64
 
@@ -1364,10 +1365,10 @@
         acme = client.acme_from_config_key(config, acc.key, acc.regr)
 
     with open(config.cert_path, 'rb') as f:
-        cert = crypto_util.pyopenssl_load_certificate(f.read())[0]
+        cert = x509.load_pem_x509_certificate(f.read())
     logger.debug("Reason code for revocation: %s", config.reason)
     try:
-        acme.revoke(jose.ComparableX509(cert), config.reason)
+        acme.revoke(cert, config.reason)
         _delete_if_appropriate(config)
     except acme_errors.ClientError as e:
         return str(e)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/storage.py 
new/certbot-4.0.0/certbot/_internal/storage.py
--- old/certbot-3.3.0/certbot/_internal/storage.py      2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/storage.py      2025-04-08 
00:03:33.000000000 +0200
@@ -1001,17 +1001,32 @@
                 logger.debug("Should renew, certificate is revoked.")
                 return True
 
-            # Renews some period before expiry time
-            default_interval = 
constants.RENEWER_DEFAULTS["renew_before_expiry"]
-            interval = self.configuration.get("renew_before_expiry", 
default_interval)
-            expiry = crypto_util.notAfter(self.version(
-                "cert", self.latest_common_version()))
+            cert = self.version("cert", self.latest_common_version())
+            notBefore = crypto_util.notBefore(cert)
+            notAfter = crypto_util.notAfter(cert)
+            lifetime = notAfter - notBefore
+
+            config_interval = self.configuration.get("renew_before_expiry")
             now = datetime.datetime.now(pytz.UTC)
-            if expiry < add_time_interval(now, interval):
+            if config_interval is not None and notAfter < 
add_time_interval(now, config_interval):
                 logger.debug("Should renew, less than %s before certificate "
-                             "expiry %s.", interval,
-                             expiry.strftime("%Y-%m-%d %H:%M:%S %Z"))
+                             "expiry %s.", config_interval,
+                             notAfter.strftime("%Y-%m-%d %H:%M:%S %Z"))
+                return True
+
+            # No config for "renew_before_expiry", provide default behavior.
+            # For most certs, renew with 1/3 of certificate lifetime remaining.
+            # For short lived certificates, renew at 1/2 of certificate 
lifetime.
+            default_interval = lifetime / 3
+            if lifetime.total_seconds() < 10 * 86400:
+                default_interval = lifetime / 2
+            remaining_time = notAfter - now
+            if remaining_time < default_interval:
+                logger.debug("Should renew, less than %ss before certificate "
+                             "expiry %s.", default_interval,
+                             notAfter.strftime("%Y-%m-%d %H:%M:%S %Z"))
                 return True
+
         return False
 
     @classmethod
@@ -1091,8 +1106,7 @@
             logger.debug("Writing chain to %s.", target["chain"])
             f_b.write(chain)
         with open(target["fullchain"], "wb") as f_b:
-            # assumes that OpenSSL.crypto.dump_certificate includes
-            # ending newline character
+            # assumes the cert includes ending newline character
             logger.debug("Writing full chain to %s.", target["fullchain"])
             f_b.write(cert + chain)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/tests/client_test.py 
new/certbot-4.0.0/certbot/_internal/tests/client_test.py
--- old/certbot-3.3.0/certbot/_internal/tests/client_test.py    2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/tests/client_test.py    2025-04-08 
00:03:33.000000000 +0200
@@ -383,6 +383,92 @@
             self.eg_order.fullchain_pem)
 
     @mock.patch("certbot._internal.client.crypto_util")
+    def test_obtain_certificate_no_profile_preference(self, mock_crypto_util):
+        csr = util.CSR(form="pem", file=None, data=CSR_SAN)
+        mock_crypto_util.generate_csr.return_value = csr
+        mock_crypto_util.generate_key.return_value = mock.sentinel.key
+        
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
+
+        self.client.config.required_profile = None
+        self.client.config.preferred_profile = None
+
+        self._test_obtain_certificate_common(mock.sentinel.key, csr)
+        self.acme.new_order.assert_called_once_with(mock.ANY, profile=None)
+
+    @mock.patch("certbot._internal.client.crypto_util")
+    def test_obtain_certificate_required_profile(self, mock_crypto_util):
+        csr = util.CSR(form="pem", file=None, data=CSR_SAN)
+        mock_crypto_util.generate_csr.return_value = csr
+        mock_crypto_util.generate_key.return_value = mock.sentinel.key
+        
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
+
+        self.client.config.required_profile = "exampleProfile"
+        self.client.config.preferred_profile = None
+
+        self._test_obtain_certificate_common(mock.sentinel.key, csr)
+        self.acme.new_order.assert_called_once_with(mock.ANY, 
profile="exampleProfile")
+
+    @mock.patch("certbot._internal.client.crypto_util")
+    def test_obtain_certificate_preferred_profile_exists(self, 
mock_crypto_util):
+        csr = util.CSR(form="pem", file=None, data=CSR_SAN)
+        mock_crypto_util.generate_csr.return_value = csr
+        mock_crypto_util.generate_key.return_value = mock.sentinel.key
+        
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
+
+        self.client.config.required_profile = None
+        self.client.config.preferred_profile = "exampleProfile"
+
+        from acme.messages import Directory
+        self.acme.directory = Directory.from_json({
+            'meta': {
+                'profiles': {
+                    'exampleProfile': 'here is some descriptive text, very 
informative',
+                }
+            }
+        })
+
+        self._test_obtain_certificate_common(mock.sentinel.key, csr)
+        self.acme.new_order.assert_called_once_with(mock.ANY, 
profile="exampleProfile")
+
+    @mock.patch("certbot._internal.client.crypto_util")
+    def test_obtain_certificate_preferred_profile_does_not_exist(self, 
mock_crypto_util):
+        csr = util.CSR(form="pem", file=None, data=CSR_SAN)
+        mock_crypto_util.generate_csr.return_value = csr
+        mock_crypto_util.generate_key.return_value = mock.sentinel.key
+        
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
+
+        self.client.config.required_profile = None
+        self.client.config.preferred_profile = "thisProfileDoesNotExist"
+
+        from acme.messages import Directory
+        self.acme.directory = Directory.from_json({
+            'meta': {
+                'profiles': {
+                    'example': 'profiles!',
+                }
+            }
+        })
+
+        self._test_obtain_certificate_common(mock.sentinel.key, csr)
+        self.acme.new_order.assert_called_once_with(mock.ANY, profile=None)
+
+    @mock.patch("certbot._internal.client.crypto_util")
+    def test_obtain_certificate_preferred_profile_no_profiles_exist(self, 
mock_crypto_util):
+        csr = util.CSR(form="pem", file=None, data=CSR_SAN)
+        mock_crypto_util.generate_csr.return_value = csr
+        mock_crypto_util.generate_key.return_value = mock.sentinel.key
+        
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
+
+        self.client.config.required_profile = None
+        self.client.config.preferred_profile = "thisProfileDoesNotExist"
+
+        from acme.messages import Directory
+        self.acme.directory = Directory.from_json({})
+
+        self._test_obtain_certificate_common(mock.sentinel.key, csr)
+        self.acme.new_order.assert_called_once_with(mock.ANY, profile=None)
+
+    @mock.patch("certbot._internal.client.crypto_util")
     def test_obtain_certificate_partial_success(self, mock_crypto_util):
         csr = util.CSR(form="pem", file=mock.sentinel.csr_file, data=CSR_SAN)
         key = util.CSR(form="pem", file=mock.sentinel.key_file, data=CSR_SAN)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-3.3.0/certbot/_internal/tests/crypto_util_test.py 
new/certbot-4.0.0/certbot/_internal/tests/crypto_util_test.py
--- old/certbot-3.3.0/certbot/_internal/tests/crypto_util_test.py       
2025-03-11 16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/tests/crypto_util_test.py       
2025-04-08 00:03:33.000000000 +0200
@@ -1,19 +1,17 @@
 """Tests for certbot.crypto_util."""
-import binascii
 import logging
 import re
 import sys
 import unittest
 from unittest import mock
-import warnings
 
-import OpenSSL
 import pytest
 from cryptography import x509
-from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.primitives import serialization
 from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.serialization import Encoding
 
+from acme import crypto_util as acme_crypto_util
 from certbot import errors
 from certbot import util
 from certbot.compat import filesystem
@@ -143,7 +141,7 @@
         data = test_util.load_vector('csr_512.der')
         data_pem = test_util.load_vector('csr_512.pem')
 
-        assert (OpenSSL.crypto.FILETYPE_PEM,
+        assert (acme_crypto_util.Format.PEM,
              util.CSR(file=csrfile,
                       data=data_pem,
                       form="pem"),
@@ -154,7 +152,7 @@
         csrfile = test_util.vector_path('csr_512.pem')
         data = test_util.load_vector('csr_512.pem')
 
-        assert (OpenSSL.crypto.FILETYPE_PEM,
+        assert (acme_crypto_util.Format.PEM,
              util.CSR(file=csrfile,
                       data=data,
                       form="pem"),
@@ -396,39 +394,8 @@
             self._call(test_util.load_vector('csr-6sans_512.pem'))
 
     def test_der(self):
-        from OpenSSL.crypto import FILETYPE_ASN1
         assert ['Example.com'] == \
-            self._call(test_util.load_vector('csr_512.der'), typ=FILETYPE_ASN1)
-
-
-class CertLoaderTest(unittest.TestCase):
-    """Tests for certbot.crypto_util.pyopenssl_load_certificate"""
-
-    def test_load_valid_cert(self):
-        from certbot.crypto_util import pyopenssl_load_certificate
-
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='certbot.crypto_util.pyopenssl_load_certificate is *'
-            )
-            cert, file_type = pyopenssl_load_certificate(CERT)
-
-        assert file_type == OpenSSL.crypto.FILETYPE_PEM
-        assert binascii.unhexlify(
-            cert.digest("sha256").replace(b":", b"")
-        ) == x509.load_pem_x509_certificate(CERT).fingerprint(hashes.SHA256())
-
-    def test_load_invalid_cert(self):
-        from certbot.crypto_util import pyopenssl_load_certificate
-        bad_cert_data = CERT.replace(b"BEGIN CERTIFICATE", b"ASDFASDFASDF!!!")
-        with pytest.raises(errors.Error):
-            with warnings.catch_warnings():
-                warnings.filterwarnings(
-                    'ignore',
-                    message='certbot.crypto_util.pyopenssl_load_certificate is 
*'
-                )
-                pyopenssl_load_certificate(bad_cert_data)
+            self._call(test_util.load_vector('csr_512.der'), 
typ=acme_crypto_util.Format.DER)
 
 
 class NotBeforeTest(unittest.TestCase):
@@ -460,20 +427,19 @@
 class CertAndChainFromFullchainTest(unittest.TestCase):
     """Tests for certbot.crypto_util.cert_and_chain_from_fullchain"""
 
-    def _parse_and_reencode_pem(self, cert_pem):
-        from OpenSSL import crypto
-        return crypto.dump_certificate(crypto.FILETYPE_PEM,
-            crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)).decode()
+    def _parse_and_reencode_pem(self, cert_pem:str)->str:
+        cert = x509.load_pem_x509_certificate(cert_pem.encode())
+        return cert.public_bytes(Encoding.PEM).decode()
 
     def test_cert_and_chain_from_fullchain(self):
-        cert_pem = CERT.decode()
-        chain_pem = cert_pem + SS_CERT.decode()
-        fullchain_pem = cert_pem + chain_pem
-        spacey_fullchain_pem = cert_pem + u'\n' + chain_pem
-        crlf_fullchain_pem = fullchain_pem.replace(u'\n', u'\r\n')
+        cert_pem: str = CERT.decode()
+        chain_pem: str = cert_pem + SS_CERT.decode()
+        fullchain_pem: str = cert_pem + chain_pem
+        spacey_fullchain_pem: str = cert_pem + u'\n' + chain_pem
+        crlf_fullchain_pem: str = fullchain_pem.replace(u'\n', u'\r\n')
 
         # In the ACME v1 code path, the fullchain is constructed by loading 
cert+chain DERs
-        # and using OpenSSL to dump them, so here we confirm that OpenSSL is 
producing certs
+        # and using OpenSSL to dump them, so here we confirm that cryptography 
is producing certs
         # that will be parseable by cert_and_chain_from_fullchain.
         acmev1_fullchain_pem = self._parse_and_reencode_pem(cert_pem) + \
             self._parse_and_reencode_pem(cert_pem) + 
self._parse_and_reencode_pem(SS_CERT.decode())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/_internal/tests/main_test.py 
new/certbot-4.0.0/certbot/_internal/tests/main_test.py
--- old/certbot-3.3.0/certbot/_internal/tests/main_test.py      2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/tests/main_test.py      2025-04-08 
00:03:33.000000000 +0200
@@ -14,15 +14,14 @@
 from typing import List
 import unittest
 from unittest import mock
-import warnings
 
 import configobj
+from cryptography import x509
 import josepy as jose
 import pytest
 import pytz
 
 from acme.messages import Error as acme_error
-from certbot import crypto_util
 from certbot import errors
 from certbot import interfaces
 from certbot import util
@@ -219,6 +218,7 @@
         with pytest.raises(errors.NotSupportedError):
             main.run(self.config, plugins)
 
+
 class CertonlyTest(unittest.TestCase):
     """Tests for certbot._internal.main.certonly."""
 
@@ -436,12 +436,7 @@
         config = cli.prepare_and_parse_args(plugins, args)
 
         from certbot._internal.main import revoke
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='certbot.crypto_util.pyopenssl_load_certificate is *'
-            )
-            revoke(config, plugins)
+        revoke(config, plugins)
 
     @mock.patch('certbot._internal.main._delete_if_appropriate')
     @mock.patch('certbot._internal.main.client.acme_client')
@@ -1801,23 +1796,16 @@
             mock_delete_if_appropriate):
         mock_delete_if_appropriate.return_value = False
         server = 'foo.bar'
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='certbot.crypto_util.pyopenssl_load_certificate is *'
-            )
-            self._call_no_clientmock(['--cert-path', SS_CERT_PATH, 
'--key-path', RSA2048_KEY_PATH,
-                                     '--server', server, 'revoke'])
-            with open(RSA2048_KEY_PATH, 'rb') as f:
-                assert mock_acme_client.ClientV2.call_count == 1
-                assert mock_acme_client.ClientNetwork.call_args[0][0] == \
-                                 jose.JWK.load(f.read())
-            with open(SS_CERT_PATH, 'rb') as f:
-                cert = crypto_util.pyopenssl_load_certificate(f.read())[0]
-                mock_revoke = mock_acme_client.ClientV2().revoke
-                mock_revoke.assert_called_once_with(
-                        jose.ComparableX509(cert),
-                        mock.ANY)
+        self._call_no_clientmock(['--cert-path', SS_CERT_PATH, '--key-path', 
RSA2048_KEY_PATH,
+                                 '--server', server, 'revoke'])
+        with open(RSA2048_KEY_PATH, 'rb') as f:
+            assert mock_acme_client.ClientV2.call_count == 1
+            assert mock_acme_client.ClientNetwork.call_args[0][0] == \
+                             jose.JWK.load(f.read())
+        with open(SS_CERT_PATH, 'rb') as f:
+            cert = x509.load_pem_x509_certificate(f.read())
+            mock_revoke = mock_acme_client.ClientV2().revoke
+            mock_revoke.assert_called_once_with(cert, mock.ANY)
 
     def test_revoke_with_key_mismatch(self):
         server = 'foo.bar'
@@ -1831,18 +1819,11 @@
             mock_delete_if_appropriate):
         mock_delete_if_appropriate.return_value = False
         mock_determine_account.return_value = (mock.MagicMock(), None)
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                'ignore',
-                message='certbot.crypto_util.pyopenssl_load_certificate is *'
-            )
-            _, _, _, client = self._call(['--cert-path', CERT, 'revoke'])
-            with open(CERT) as f:
-                cert = crypto_util.pyopenssl_load_certificate(f.read())[0]
-                mock_revoke = client.acme_from_config_key().revoke
-                mock_revoke.assert_called_once_with(
-                        jose.ComparableX509(cert),
-                        mock.ANY)
+        _, _, _, client = self._call(['--cert-path', CERT, 'revoke'])
+        with open(CERT, 'rb') as f:
+            cert = x509.load_pem_x509_certificate(f.read())
+            mock_revoke = client.acme_from_config_key().revoke
+            mock_revoke.assert_called_once_with(cert, mock.ANY)
 
     @mock.patch('certbot._internal.log.post_arg_parse_setup')
     def test_register(self, _):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot-3.3.0/certbot/_internal/tests/storage_test.py 
new/certbot-4.0.0/certbot/_internal/tests/storage_test.py
--- old/certbot-3.3.0/certbot/_internal/tests/storage_test.py   2025-03-11 
16:03:28.000000000 +0100
+++ new/certbot-4.0.0/certbot/_internal/tests/storage_test.py   2025-04-08 
00:03:33.000000000 +0200
@@ -19,8 +19,33 @@
 from certbot.compat import os
 import certbot.tests.util as test_util
 
-CERT = test_util.load_cert('cert_512.pem')
-
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives import serialization, hashes
+from cryptography import x509
+from cryptography.x509 import Certificate
+
+import datetime
+from typing import Optional, Any
+
+def make_cert_with_lifetime(not_before: datetime.datetime, lifetime_days: int) 
-> bytes:
+    """Return PEM of a self-signed certificate with the given notBefore and 
lifetime."""
+    key = ec.generate_private_key(ec.SECP256R1())
+    not_after=not_before + datetime.timedelta(days=lifetime_days)
+    cert = x509.CertificateBuilder(
+        issuer_name=x509.Name([]),
+        subject_name=x509.Name([]),
+        public_key=key.public_key(),
+        serial_number=x509.random_serial_number(),
+        not_valid_before=not_before,
+        not_valid_after=not_after,
+    ).add_extension(
+        x509.SubjectAlternativeName([x509.DNSName("example.com")]),
+        critical=False,
+    ).sign(
+        private_key=key,
+        algorithm=hashes.SHA256(),
+    )
+    return cert.public_bytes(serialization.Encoding.PEM)
 
 def unlink_all(rc_object):
     """Unlink all four items associated with this RenewableCert."""
@@ -445,32 +470,45 @@
     @mock.patch("certbot._internal.storage.datetime")
     def test_time_interval_judgments(self, mock_datetime, mock_set_by_user):
         """Test should_autorenew() on the basis of expiry time windows."""
-        test_cert = test_util.load_vector("cert_512.pem")
+        # Note: this certificate happens to have a lifetime of 7 days,
+        # and the tests below that use a "None" interval (i.e. choose a
+        # default) rely on that fact.
+        #
+        # Not Before: Dec 11 22:34:45 2014 GMT
+        # Not After : Dec 18 22:34:45 2014 GMT
+        not_before = datetime.datetime(2014, 12, 11, 22, 34, 45)
+        short_cert = make_cert_with_lifetime(not_before, 7)
 
         self._write_out_ex_kinds()
 
         self.test_rc.update_all_links_to(12)
         with open(self.test_rc.cert, "wb") as f:
-            f.write(test_cert)
+            f.write(short_cert)
         self.test_rc.update_all_links_to(11)
         with open(self.test_rc.cert, "wb") as f:
-            f.write(test_cert)
+            f.write(short_cert)
 
         mock_datetime.timedelta = datetime.timedelta
         mock_set_by_user.return_value = False
         self.test_rc.configuration["renewalparams"] = {}
 
         for (current_time, interval, result) in [
-                # 2014-12-13 12:00:00+00:00 (about 5 days prior to expiry)
+                # 2014-12-13 12:00 (about 5 days prior to expiry)
                 # Times that should result in autorenewal/autodeployment
                 (1418472000, "2 months", True), (1418472000, "1 week", True),
-                # Times that should not
+                # With the "default" logic, this 7-day certificate should 
autorenew
+                # at 3.5 days prior to expiry. We haven't reached that yet,
+                # so don't renew.
+                (1418472000, None, False),
+                # 2014-12-16 03:20, a little less than 3.5 days to expiry.
+                (1418700000, None, True),
+                # Times that should not renew
                 (1418472000, "4 days", False), (1418472000, "2 days", False),
                 # 2009-05-01 12:00:00+00:00 (about 5 years prior to expiry)
                 # Times that should result in autorenewal/autodeployment
                 (1241179200, "7 years", True),
                 (1241179200, "11 years 2 months", True),
-                # Times that should not
+                # Times that should not renew
                 (1241179200, "8 hours", False), (1241179200, "2 days", False),
                 (1241179200, "40 days", False), (1241179200, "9 months", 
False),
                 # 2015-01-01 (after expiry has already happened, so all
@@ -480,6 +518,28 @@
                 (1420070400, "10 minutes", True),
                 (1420070400, "10 weeks", True), (1420070400, "10 months", 
True),
                 (1420070400, "10 years", True), (1420070400, "99 months", 
True),
+                (1420070400, None, True)
+        ]:
+            sometime = datetime.datetime.fromtimestamp(current_time, pytz.UTC)
+            mock_datetime.datetime.now.return_value = sometime
+            self.test_rc.configuration["renew_before_expiry"] = interval
+            assert self.test_rc.should_autorenew() == result
+
+        # Lifetime: 31 years
+        # Default renewal: about 10 years from expiry
+        # Not Before: May 29 07:42:01 2017 GMT
+        # Not After : Mar 30 07:42:01 2048 GMT
+        not_before=datetime.datetime(2017, 5, 29, 7, 42, 1)
+        long_cert = make_cert_with_lifetime(not_before, 31 * 365)
+        self.test_rc.update_all_links_to(12)
+        with open(self.test_rc.cert, "wb") as f:
+            f.write(long_cert)
+        self.test_rc.update_all_links_to(11)
+        with open(self.test_rc.cert, "wb") as f:
+            f.write(long_cert)
+        for (current_time, result) in [
+            (2114380800, False), # 2037-01-01
+            (2148000000, True), # 2038-01-25
         ]:
             sometime = datetime.datetime.fromtimestamp(current_time, pytz.UTC)
             mock_datetime.datetime.now.return_value = sometime
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/configuration.py 
new/certbot-4.0.0/certbot/configuration.py
--- old/certbot-3.3.0/certbot/configuration.py  2025-03-11 16:03:28.000000000 
+0100
+++ new/certbot-4.0.0/certbot/configuration.py  2025-04-08 00:03:33.000000000 
+0200
@@ -364,6 +364,28 @@
         return self.namespace.disable_renew_updates
 
     @property
+    def required_profile(self) -> Optional[str]:
+        """Request the given profile name from the ACME server.
+
+        If the ACME server returns an error, issuance (or renewal) will fail.
+        For long-term reliability, setting preferred_profile instead may be
+        preferable because it allows fallback to a default. Use this setting
+        when renewal failure is preferable to fallback.
+        """
+        return self.namespace.required_profile
+
+    @property
+    def preferred_profile(self) -> Optional[str]:
+        """Request the given profile name from the ACME server, or fallback to 
default.
+
+        If the given profile name exists in the ACME directory, use it to 
request a
+        a certificate. Otherwise, fall back to requesting a certificate 
without a profile
+        (which means the CA will use its default profile). This allows 
renewals to
+        succeed even if the CA deprecates and removes a given profile.
+        """
+        return self.namespace.preferred_profile
+
+    @property
     def preferred_chain(self) -> Optional[str]:
         """Set the preferred certificate chain.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/crypto_util.py 
new/certbot-4.0.0/certbot/crypto_util.py
--- old/certbot-3.3.0/certbot/crypto_util.py    2025-03-11 16:03:28.000000000 
+0100
+++ new/certbot-4.0.0/certbot/crypto_util.py    2025-04-08 00:03:33.000000000 
+0200
@@ -14,7 +14,6 @@
 from typing import Tuple
 from typing import TYPE_CHECKING
 from typing import Union
-import warnings
 
 from cryptography import x509
 from cryptography.exceptions import InvalidSignature
@@ -32,8 +31,6 @@
 from cryptography.hazmat.primitives.serialization import Encoding
 from cryptography.hazmat.primitives.serialization import NoEncryption
 from cryptography.hazmat.primitives.serialization import PrivateFormat
-import josepy
-from OpenSSL import crypto
 from OpenSSL import SSL
 
 from acme import crypto_util as acme_crypto_util
@@ -351,7 +348,7 @@
     :raises errors.Error: If they don't match.
     """
     try:
-        context = SSL.Context(SSL.SSLv23_METHOD)
+        context = SSL.Context(SSL.TLS_METHOD)
         context.use_certificate_file(cert_path)
         context.use_privatekey_file(key_path)
         context.check_privatekey()
@@ -391,32 +388,6 @@
         raise e
 
 
-def pyopenssl_load_certificate(data: bytes) -> Tuple[crypto.X509, int]:
-    """Load PEM/DER certificate.
-
-    :raises errors.Error:
-
-    Deprecated
-    .. deprecated: 3.2.1
-    """
-    warnings.warn(
-        "certbot.crypto_util.pyopenssl_load_certificate is deprecated and "
-        "will be removed in the next major release of Certbot.",
-        DeprecationWarning,
-        stacklevel=2
-    )
-
-    openssl_errors = []
-
-    for file_type in (crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1):
-        try:
-            return crypto.load_certificate(file_type, data), file_type
-        except crypto.Error as error:  # TODO: other errors?
-            openssl_errors.append(error)
-    raise errors.Error("Unable to load: {0}".format(",".join(
-        str(error) for error in openssl_errors)))
-
-
 def get_sans_from_cert(
     cert: bytes, typ: Union[acme_crypto_util.Format, int] = 
acme_crypto_util.Format.PEM
 ) -> List[str]:
@@ -491,29 +462,6 @@
     )
 
 
-def dump_pyopenssl_chain(
-    chain: Union[List[crypto.X509], List[josepy.ComparableX509]],
-    filetype: Union[acme_crypto_util.Format, int] = 
acme_crypto_util.Format.PEM,
-) -> bytes:
-    """Dump certificate chain into a bundle.
-
-    :param list chain: List of `crypto.X509` (or wrapped in
-        :class:`josepy.util.ComparableX509`).
-
-    Deprecated
-    .. deprecated: 3.2.1
-    """
-    warnings.warn(
-        "certbot.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...
-    return acme_crypto_util.dump_pyopenssl_chain(chain, filetype)
-
-
 def notBefore(cert_path: str) -> datetime.datetime:
     """When does the cert at cert_path start being valid?
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot/tests/util.py 
new/certbot-4.0.0/certbot/tests/util.py
--- old/certbot-3.3.0/certbot/tests/util.py     2025-03-11 16:03:28.000000000 
+0100
+++ new/certbot-4.0.0/certbot/tests/util.py     2025-04-08 00:03:33.000000000 
+0200
@@ -22,6 +22,7 @@
 import unittest
 from unittest import mock
 
+from cryptography import x509
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization
 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
@@ -98,7 +99,7 @@
         return data
 
 
-def _guess_loader(filename: str, loader_pem: int, loader_der: int) -> int:
+def _guess_loader(filename: str, loader_pem: Callable, loader_der: Callable) 
-> Callable:
     _, ext = os.path.splitext(filename)
     if ext.lower() == '.pem':
         return loader_pem
@@ -107,23 +108,12 @@
     raise ValueError("Loader could not be recognized based on extension")  # 
pragma: no cover
 
 
-def load_cert(*names: str) -> crypto.X509:
+def load_cert(*names: str) -> x509.Certificate:
     """Load certificate."""
     loader = _guess_loader(
-        names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
-    return crypto.load_certificate(loader, load_vector(*names))
-
-
-def load_csr(*names: str) -> crypto.X509Req:
-    """Load certificate request."""
-    loader = _guess_loader(
-        names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
-    return crypto.load_certificate_request(loader, load_vector(*names))
-
-
-def load_comparable_csr(*names: str) -> jose.ComparableX509:
-    """Load ComparableX509 certificate request."""
-    return jose.ComparableX509(load_csr(*names))
+        names[-1], x509.load_pem_x509_certificate, 
x509.load_der_x509_certificate
+    )
+    return loader(load_vector(*names))
 
 
 def load_jose_rsa_private_key_pem(*names: str) -> jose.ComparableRSAKey:
@@ -131,9 +121,19 @@
     return jose.ComparableRSAKey(load_rsa_private_key_pem(*names))
 
 
+def _guess_loader_pyopenssl(filename: str, loader_pem: int, loader_der: int) 
-> int:
+    # note: used by `load_rsa_private_key_pem`
+    _, ext = os.path.splitext(filename)
+    if ext.lower() == '.pem':
+        return loader_pem
+    elif ext.lower() == '.der':
+        return loader_der
+    raise ValueError("Loader could not be recognized based on extension")  # 
pragma: no cover
+
+
 def load_rsa_private_key_pem(*names: str) -> RSAPrivateKey:
     """Load RSA private key."""
-    loader = _guess_loader(names[-1], crypto.FILETYPE_PEM, 
crypto.FILETYPE_ASN1)
+    loader = _guess_loader_pyopenssl(names[-1], crypto.FILETYPE_PEM, 
crypto.FILETYPE_ASN1)
     loader_fn: Callable[..., Any]
     if loader == crypto.FILETYPE_PEM:
         loader_fn = serialization.load_pem_private_key
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/certbot.egg-info/PKG-INFO 
new/certbot-4.0.0/certbot.egg-info/PKG-INFO
--- old/certbot-3.3.0/certbot.egg-info/PKG-INFO 2025-03-11 16:03:29.000000000 
+0100
+++ new/certbot-4.0.0/certbot.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: certbot
-Version: 3.3.0
+Version: 4.0.0
 Summary: ACME client
 Home-page: https://github.com/certbot/certbot
 Author: Certbot Project
@@ -27,13 +27,13 @@
 Classifier: Topic :: Utilities
 Requires-Python: >=3.9
 License-File: LICENSE.txt
-Requires-Dist: acme>=3.3.0
+Requires-Dist: acme>=4.0.0
 Requires-Dist: ConfigArgParse>=1.5.3
 Requires-Dist: configobj>=5.0.6
 Requires-Dist: cryptography>=43.0.0
 Requires-Dist: distro>=1.0.1
 Requires-Dist: importlib_metadata>=4.6; python_version < "3.10"
-Requires-Dist: josepy<2,>=1.13.0
+Requires-Dist: josepy>=2.0.0
 Requires-Dist: parsedatetime>=2.4
 Requires-Dist: pyrfc3339
 Requires-Dist: pytz>=2019.3
@@ -94,6 +94,7 @@
 Dynamic: description
 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/certbot-3.3.0/certbot.egg-info/requires.txt 
new/certbot-4.0.0/certbot.egg-info/requires.txt
--- old/certbot-3.3.0/certbot.egg-info/requires.txt     2025-03-11 
16:03:29.000000000 +0100
+++ new/certbot-4.0.0/certbot.egg-info/requires.txt     2025-04-08 
00:03:34.000000000 +0200
@@ -1,9 +1,9 @@
-acme>=3.3.0
+acme>=4.0.0
 ConfigArgParse>=1.5.3
 configobj>=5.0.6
 cryptography>=43.0.0
 distro>=1.0.1
-josepy<2,>=1.13.0
+josepy>=2.0.0
 parsedatetime>=2.4
 pyrfc3339
 pytz>=2019.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/docs/cli-help.txt 
new/certbot-4.0.0/docs/cli-help.txt
--- old/certbot-3.3.0/docs/cli-help.txt 2025-03-11 16:03:28.000000000 +0100
+++ new/certbot-4.0.0/docs/cli-help.txt 2025-04-08 00:03:33.000000000 +0200
@@ -122,7 +122,7 @@
                         case, and to know when to deprecate support for past
                         Python versions and flags. If you wish to hide this
                         information from the Let's Encrypt server, set this to
-                        "". (default: CertbotACMEClient/3.2.0 (certbot;
+                        "". (default: CertbotACMEClient/3.3.0 (certbot;
                         OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY
                         (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).
                         The flags encoded in the user agent are: --duplicate,
@@ -398,14 +398,6 @@
 register:
   Options for account registration
 
-  --register-unsafely-without-email
-                        Specifying this flag enables registering an account
-                        with no email address. This is strongly discouraged,
-                        because you will be unable to receive notice about
-                        impending expiration or revocation of your
-                        certificates or problems with your Certbot
-                        installation that will lead to failure to renew.
-                        (default: False)
   -m EMAIL, --email EMAIL
                         Email used for registration and recovery contact. Use
                         comma to register multiple emails, ex:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/docs/using.rst 
new/certbot-4.0.0/docs/using.rst
--- old/certbot-3.3.0/docs/using.rst    2025-03-11 16:03:28.000000000 +0100
+++ new/certbot-4.0.0/docs/using.rst    2025-04-08 00:03:33.000000000 +0200
@@ -628,10 +628,6 @@
 Renewing certificates
 ---------------------
 
-.. note:: Let's Encrypt CA issues short-lived certificates (90
-   days). Make sure you renew the certificates at least once in 3
-   months.
-
 .. seealso:: Most Certbot installations come with automatic
    renewal out of the box. See `Automated Renewals`_ for more details.
 
@@ -639,14 +635,18 @@
    will not renew automatically, unless combined with authentication hook 
scripts.
    See `Renewal with the manual plugin <#manual-renewal>`_.
 
-As of version 0.10.0, Certbot supports a ``renew`` action to check
-all installed certificates for impending expiry and attempt to renew
-them. The simplest form is simply
+Certbot supports a ``renew`` action to check all installed certificates for
+impending expiry and attempt to renew them. The simplest form is simply
 
 ``certbot renew``
 
-This command attempts to renew any previously-obtained certificates that
-expire in less than 30 days. The same plugin and options that were used
+This command attempts to renew any previously-obtained certificates which are 
ready
+for renewal. As of Certbot 4.0.0, a certificate is considered ready for renewal
+when less than 1/3rd of its lifetime remains. For certificates with a lifetime
+of 10 days or less, that threshold is 1/2 of the lifetime. Prior to Certbot 
4.0.0
+the threshold was a fixed 30 days.
+
+The same plugin and options that were used
 at the time the certificate was originally issued will be used for the
 renewal attempt, unless you specify other plugins or options. Unlike 
``certonly``, ``renew`` acts on
 multiple certificates and always takes into account whether each one is near
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot-3.3.0/setup.py new/certbot-4.0.0/setup.py
--- old/certbot-3.3.0/setup.py  2025-03-11 16:03:28.000000000 +0100
+++ new/certbot-4.0.0/setup.py  2025-04-08 00:03:33.000000000 +0200
@@ -5,6 +5,7 @@
 from setuptools import find_packages
 from setuptools import setup
 
+
 def read_file(filename, encoding='utf8'):
     """Read unicode from given file."""
     with codecs.open(filename, encoding=encoding) as fd:
@@ -33,9 +34,7 @@
     'cryptography>=43.0.0',
     'distro>=1.0.1',
     'importlib_metadata>=4.6; python_version < "3.10"',
-    # Josepy 2+ may introduce backward incompatible changes by droping usage of
-    # deprecated PyOpenSSL APIs.
-    'josepy>=1.13.0, <2',
+    'josepy>=2.0.0',
     'parsedatetime>=2.4',
     'pyrfc3339',
     'pytz>=2019.3',

Reply via email to