Hello community,
here is the log from the commit of package python-certbot for openSUSE:Factory checked in at 2019-10-02 14:56:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-certbot (Old)
and /work/SRC/openSUSE:Factory/.python-certbot.new.2352 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-certbot"
Wed Oct 2 14:56:05 2019 rev:18 rq:734565 version:0.39.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-certbot/python-certbot.changes 2019-09-13 14:59:03.553279119 +0200
+++ /work/SRC/openSUSE:Factory/.python-certbot.new.2352/python-certbot.changes 2019-10-02 14:56:06.151253827 +0200
@@ -1,0 +2,7 @@
+Wed Oct 2 10:02:37 UTC 2019 - Marketa Calabkova
+
+- update to version 0.39.0
+ * Support for Python 3.8 was added to Certbot and all of its components.
+ * Don't send OCSP requests for expired certificates
+
+-------------------------------------------------------------------
Old:
----
certbot-0.38.0.tar.gz
New:
----
certbot-0.39.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-certbot.spec ++++++
--- /var/tmp/diff_new_pack.3o0GUS/_old 2019-10-02 14:56:06.991251624 +0200
+++ /var/tmp/diff_new_pack.3o0GUS/_new 2019-10-02 14:56:06.995251613 +0200
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-certbot
-Version: 0.38.0
+Version: 0.39.0
Release: 0
Summary: ACME client
License: Apache-2.0
++++++ certbot-0.38.0.tar.gz -> certbot-0.39.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/CHANGELOG.md new/certbot-0.39.0/CHANGELOG.md
--- old/certbot-0.38.0/CHANGELOG.md 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/CHANGELOG.md 2019-10-01 21:48:40.000000000 +0200
@@ -2,11 +2,30 @@
Certbot adheres to [Semantic Versioning](https://semver.org/).
+## 0.39.0 - 2019-10-01
+
+### Added
+
+* Support for Python 3.8 was added to Certbot and all of its components.
+* Support for CentOS 8 was added to certbot-auto.
+
+### Changed
+
+* Don't send OCSP requests for expired certificates
+* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8
+* Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11.
+
+### Fixed
+
+* Fixed OS detection in the Apache plugin on RHEL 6.
+
+More details about these changes can be found on our GitHub repo.
+
## 0.38.0 - 2019-09-03
### Added
-*
+* Disable session tickets for Nginx users when appropriate.
### Changed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/PKG-INFO new/certbot-0.39.0/PKG-INFO
--- old/certbot-0.38.0/PKG-INFO 2019-09-03 21:42:38.000000000 +0200
+++ new/certbot-0.39.0/PKG-INFO 2019-10-01 21:48:41.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: certbot
-Version: 0.38.0
+Version: 0.39.0
Summary: ACME client
Home-page: https://github.com/letsencrypt/letsencrypt
Author: Certbot Project
@@ -153,6 +153,7 @@
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Security
Classifier: Topic :: System :: Installation/Setup
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/__init__.py new/certbot-0.39.0/certbot/__init__.py
--- old/certbot-0.38.0/certbot/__init__.py 2019-09-03 21:42:37.000000000 +0200
+++ new/certbot-0.39.0/certbot/__init__.py 2019-10-01 21:48:41.000000000 +0200
@@ -1,4 +1,4 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
-__version__ = '0.38.0'
+__version__ = '0.39.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/cert_manager.py new/certbot-0.39.0/certbot/cert_manager.py
--- old/certbot-0.38.0/certbot/cert_manager.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/cert_manager.py 2019-10-01 21:48:40.000000000 +0200
@@ -262,7 +262,7 @@
reasons.append('TEST_CERT')
if cert.target_expiry <= now:
reasons.append('EXPIRED')
- if checker.ocsp_revoked(cert.cert, cert.chain):
+ elif checker.ocsp_revoked(cert):
reasons.append('REVOKED')
if reasons:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/compat/filesystem.py new/certbot-0.39.0/certbot/compat/filesystem.py
--- old/certbot-0.38.0/certbot/compat/filesystem.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/compat/filesystem.py 2019-10-01 21:48:40.000000000 +0200
@@ -20,7 +20,7 @@
else:
POSIX_MODE = False
-from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
+from acme.magic_typing import List, Union, Tuple # pylint: disable=unused-import, no-name-in-module
def chmod(file_path, mode):
@@ -264,6 +264,7 @@
def realpath(file_path):
+ # type: (str) -> str
"""
Find the real path for the given path. This method resolves symlinks, including
recursive symlinks, and is protected against symlinks that creates an infinite loop.
@@ -300,10 +301,11 @@
# requires to be run under a privileged shell, so the user will always benefit
# from the highest (privileged one) set of permissions on a given file.
def is_executable(path):
+ # type: (str) -> bool
"""
Is path an executable file?
:param str path: path to test
- :returns: True if path is an executable file
+ :return: True if path is an executable file
:rtype: bool
"""
if POSIX_MODE:
@@ -312,6 +314,118 @@
return _win_is_executable(path)
+def has_world_permissions(path):
+ # type: (str) -> bool
+ """
+ Check if everybody/world has any right (read/write/execute) on a file given its path
+ :param str path: path to test
+ :return: True if everybody/world has any right to the file
+ :rtype: bool
+ """
+ if POSIX_MODE:
+ return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO)
+
+ security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION)
+ dacl = security.GetSecurityDescriptorDacl()
+
+ return bool(dacl.GetEffectiveRightsFromAcl({
+ 'TrusteeForm': win32security.TRUSTEE_IS_SID,
+ 'TrusteeType': win32security.TRUSTEE_IS_USER,
+ 'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'),
+ }))
+
+
+def compute_private_key_mode(old_key, base_mode):
+ # type: (str, int) -> int
+ """
+ Calculate the POSIX mode to apply to a private key given the previous private key
+ :param str old_key: path to the previous private key
+ :param int base_mode: the minimum modes to apply to a private key
+ :return: the POSIX mode to apply
+ :rtype: int
+ """
+ if POSIX_MODE:
+ # On Linux, we keep read/write/execute permissions
+ # for group and read permissions for everybody.
+ old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) &
+ (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH))
+ return base_mode | old_mode
+
+ # On Windows, the mode returned by os.stat is not reliable,
+ # so we do not keep any permission from the previous private key.
+ return base_mode
+
+
+def has_same_ownership(path1, path2):
+ # type: (str, str) -> bool
+ """
+ Return True if the ownership of two files given their respective path is the same.
+ On Windows, ownership is checked against owner only, since files do not have a group owner.
+ :param str path1: path to the first file
+ :param str path2: path to the second file
+ :return: True if both files have the same ownership, False otherwise
+ :rtype: bool
+
+ """
+ if POSIX_MODE:
+ stats1 = os.stat(path1)
+ stats2 = os.stat(path2)
+ return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid)
+
+ security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION)
+ user1 = security1.GetSecurityDescriptorOwner()
+
+ security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION)
+ user2 = security2.GetSecurityDescriptorOwner()
+
+ return user1 == user2
+
+
+def has_min_permissions(path, min_mode):
+ # type: (str, int) -> bool
+ """
+ Check if a file given its path has at least the permissions defined by the given minimal mode.
+ On Windows, group permissions are ignored since files do not have a group owner.
+ :param str path: path to the file to check
+ :param int min_mode: the minimal permissions expected
+ :return: True if the file matches the minimal permissions expectations, False otherwise
+ :rtype: bool
+ """
+ if POSIX_MODE:
+ st_mode = os.stat(path).st_mode
+ return st_mode == st_mode | min_mode
+
+ # Resolve symlinks, to get a consistent result with os.stat on Linux,
+ # that follows symlinks by default.
+ path = realpath(path)
+
+ # Get owner sid of the file
+ security = win32security.GetFileSecurity(
+ path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION)
+ user = security.GetSecurityDescriptorOwner()
+ dacl = security.GetSecurityDescriptorDacl()
+ min_dacl = _generate_dacl(user, min_mode)
+
+ for index in range(min_dacl.GetAceCount()):
+ min_ace = min_dacl.GetAce(index)
+
+ # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID.
+ # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html
+ mask = min_ace[1]
+ user = min_ace[2]
+
+ effective_mask = dacl.GetEffectiveRightsFromAcl({
+ 'TrusteeForm': win32security.TRUSTEE_IS_SID,
+ 'TrusteeType': win32security.TRUSTEE_IS_USER,
+ 'Identifier': user,
+ })
+
+ if effective_mask != effective_mask | mask:
+ return False
+
+ return True
+
+
def _win_is_executable(path):
if not os.path.isfile(path):
return False
@@ -472,8 +586,8 @@
This method compare the two given DACLs to check if they are identical.
Identical means here that they contains the same set of ACEs in the same order.
"""
- return ([dacl1.GetAce(index) for index in range(0, dacl1.GetAceCount())] ==
- [dacl2.GetAce(index) for index in range(0, dacl2.GetAceCount())])
+ return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] ==
+ [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())])
def _get_current_user():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/compat/misc.py new/certbot-0.39.0/certbot/compat/misc.py
--- old/certbot-0.38.0/certbot/compat/misc.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/compat/misc.py 2019-10-01 21:48:40.000000000 +0200
@@ -5,7 +5,6 @@
from __future__ import absolute_import
import select
-import stat
import sys
try:
@@ -18,18 +17,6 @@
from certbot.compat import os
-# MASK_FOR_PRIVATE_KEY_PERMISSIONS defines what are the permissions flags to keep
-# when transferring the permissions from an old private key to a new one.
-if POSIX_MODE:
- # On Linux, we keep read/write/execute permissions
- # for group and read permissions for everybody.
- MASK_FOR_PRIVATE_KEY_PERMISSIONS = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH
-else:
- # On Windows, the mode returned by os.stat is not reliable,
- # so we do not keep any permission from the previous private key.
- MASK_FOR_PRIVATE_KEY_PERMISSIONS = 0
-
-
# For Linux: define OS specific standard binary directories
STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/compat/os.py new/certbot-0.39.0/certbot/compat/os.py
--- old/certbot-0.38.0/certbot/compat/os.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/compat/os.py 2019-10-01 21:48:40.000000000 +0200
@@ -116,3 +116,21 @@
raise RuntimeError('Usage of os.access() is forbidden. '
'Use certbot.compat.filesystem.check_mode() or '
'certbot.compat.filesystem.is_executable() instead.')
+
+
+# On Windows os.stat call result is inconsistent, with a lot of flags that are not set or
+# meaningless. We need to use specialized functions from the certbot.compat.filesystem module.
+def stat(*unused_args, **unused_kwargs):
+ """Method os.stat() is forbidden"""
+ raise RuntimeError('Usage of os.stat() is forbidden. '
+ 'Use certbot.compat.filesystem functions instead '
+ '(eg. has_min_permissions, has_same_ownership).')
+
+
+# Method os.fstat has the same problem than os.stat, since it is the same function,
+# but accepting a file descriptor instead of a path.
+def fstat(*unused_args, **unused_kwargs):
+ """Method os.stat() is forbidden"""
+ raise RuntimeError('Usage of os.fstat() is forbidden. '
+ 'Use certbot.compat.filesystem functions instead '
+ '(eg. has_min_permissions, has_same_ownership).')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/lock.py new/certbot-0.39.0/certbot/lock.py
--- old/certbot-0.38.0/certbot/lock.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/lock.py 2019-10-01 21:48:40.000000000 +0200
@@ -167,14 +167,18 @@
:returns: True if the lock was successfully acquired
:rtype: bool
"""
+ # Normally os module should not be imported in certbot codebase except in certbot.compat
+ # for the sake of compatibility over Windows and Linux.
+ # We make an exception here, since _lock_success is private and called only on Linux.
+ from os import stat, fstat # pylint: disable=os-module-forbidden
try:
- stat1 = os.stat(self._path)
+ stat1 = stat(self._path)
except OSError as err:
if err.errno == errno.ENOENT:
return False
raise
- stat2 = os.fstat(fd)
+ stat2 = fstat(fd)
# If our locked file descriptor and the file on disk refer to
# the same device and inode, they're the same file.
return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/ocsp.py new/certbot-0.39.0/certbot/ocsp.py
--- old/certbot-0.38.0/certbot/ocsp.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/ocsp.py 2019-10-01 21:48:40.000000000 +0200
@@ -16,11 +16,13 @@
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes # type: ignore
from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature
+import pytz
import requests
from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module
from certbot import crypto_util
from certbot import errors
+from certbot.storage import RenewableCert # pylint: disable=unused-import
from certbot import util
logger = logging.getLogger(__name__)
@@ -48,21 +50,29 @@
else:
self.host_args = lambda host: ["Host", host]
- def ocsp_revoked(self, cert_path, chain_path):
- # type: (str, str) -> bool
+ def ocsp_revoked(self, cert):
+ # type: (RenewableCert) -> bool
"""Get revoked status for a particular cert version.
.. todo:: Make this a non-blocking call
- :param str cert_path: Path to certificate
- :param str chain_path: Path to intermediate cert
- :returns: True if revoked; False if valid or the check failed
+ :param `.storage.RenewableCert` cert: Certificate object
+ :returns: True if revoked; False if valid or the check failed or cert is expired.
:rtype: bool
"""
+ cert_path, chain_path = cert.cert, cert.chain
+
if self.broken:
return False
+ # Let's Encrypt doesn't update OCSP for expired certificates,
+ # so don't check OCSP if the cert is expired.
+ # https://github.com/certbot/certbot/issues/7152
+ now = pytz.UTC.fromutc(datetime.utcnow())
+ if cert.target_expiry <= now:
+ return False
+
url, host = _determine_ocsp_server(cert_path)
if not host or not url:
return False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/plugins/dns_common.py new/certbot-0.39.0/certbot/plugins/dns_common.py
--- old/certbot-0.38.0/certbot/plugins/dns_common.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/plugins/dns_common.py 2019-10-01 21:48:40.000000000 +0200
@@ -2,7 +2,6 @@
import abc
import logging
-import stat
from time import sleep
import configobj
@@ -12,6 +11,7 @@
from certbot import errors
from certbot import interfaces
+from certbot.compat import filesystem
from certbot.compat import os
from certbot.display import ops
from certbot.display import util as display_util
@@ -312,8 +312,7 @@
validate_file(filename)
- permissions = stat.S_IMODE(os.stat(filename).st_mode)
- if permissions & stat.S_IRWXO:
+ if filesystem.has_world_permissions(filename):
logger.warning('Unsafe permissions on credentials configuration file: %s', filename)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/plugins/dns_common_test.py new/certbot-0.39.0/certbot/plugins/dns_common_test.py
--- old/certbot-0.38.0/certbot/plugins/dns_common_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/plugins/dns_common_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -7,14 +7,15 @@
import mock
from certbot import errors
+from certbot import util
from certbot.compat import os
from certbot.display import util as display_util
from certbot.plugins import dns_common
from certbot.plugins import dns_test_common
-from certbot.tests import util
+from certbot.tests import util as test_util
-class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest):
+class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest):
# pylint: disable=protected-access
class _FakeDNSAuthenticator(dns_common.DNSAuthenticator):
@@ -50,7 +51,7 @@
self.auth._cleanup.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY)
- @util.patch_get_utility()
+ @test_util.patch_get_utility()
def test_prompt(self, mock_get_utility):
mock_display = mock_get_utility()
mock_display.input.side_effect = ((display_util.OK, "",),
@@ -59,14 +60,14 @@
self.auth._configure("other_key", "")
self.assertEqual(self.auth.config.fake_other_key, "value")
- @util.patch_get_utility()
+ @test_util.patch_get_utility()
def test_prompt_canceled(self, mock_get_utility):
mock_display = mock_get_utility()
mock_display.input.side_effect = ((display_util.CANCEL, "c",),)
self.assertRaises(errors.PluginError, self.auth._configure, "other_key", "")
- @util.patch_get_utility()
+ @test_util.patch_get_utility()
def test_prompt_file(self, mock_get_utility):
path = os.path.join(self.tempdir, 'file.ini')
open(path, "wb").close()
@@ -80,7 +81,7 @@
self.auth._configure_file("file_path", "")
self.assertEqual(self.auth.config.fake_file_path, path)
- @util.patch_get_utility()
+ @test_util.patch_get_utility()
def test_prompt_file_canceled(self, mock_get_utility):
mock_display = mock_get_utility()
mock_display.directory_select.side_effect = ((display_util.CANCEL, "c",),)
@@ -96,7 +97,7 @@
self.assertEqual(credentials.conf("test"), "value")
- @util.patch_get_utility()
+ @test_util.patch_get_utility()
def test_prompt_credentials(self, mock_get_utility):
bad_path = os.path.join(self.tempdir, 'bad-file.ini')
dns_test_common.write({"fake_other": "other_value"}, bad_path)
@@ -116,7 +117,7 @@
self.assertEqual(credentials.conf("test"), "value")
-class CredentialsConfigurationTest(util.TempDirTestCase):
+class CredentialsConfigurationTest(test_util.TempDirTestCase):
class _MockLoggingHandler(logging.Handler):
messages = None
@@ -150,14 +151,14 @@
dns_common.logger.addHandler(log)
path = os.path.join(self.tempdir, 'too-permissive-file.ini')
- open(path, "wb").close()
+ util.safe_open(path, "wb", 0o744).close()
dns_common.CredentialsConfiguration(path)
self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")]))
-class CredentialsConfigurationRequireTest(util.TempDirTestCase):
+class CredentialsConfigurationRequireTest(test_util.TempDirTestCase):
def setUp(self):
super(CredentialsConfigurationRequireTest, self).setUp()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/plugins/webroot_test.py new/certbot-0.39.0/certbot/plugins/webroot_test.py
--- old/certbot-0.38.0/certbot/plugins/webroot_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/plugins/webroot_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -34,7 +34,13 @@
def setUp(self):
from certbot.plugins.webroot import Authenticator
- self.path = tempfile.mkdtemp()
+ # On Linux directories created by tempfile.mkdtemp inherit their permissions from their
+ # parent directory. So the actual permissions are inconsistent over various tests env.
+ # To circumvent this, a dedicated sub-workspace is created under the workspace, using
+ # filesystem.mkdir to get consistent permissions.
+ self.workspace = tempfile.mkdtemp()
+ self.path = os.path.join(self.workspace, 'webroot')
+ filesystem.mkdir(self.path)
self.partial_root_challenge_path = os.path.join(
self.path, ".well-known")
self.root_challenge_path = os.path.join(
@@ -170,17 +176,12 @@
self.assertTrue(filesystem.check_mode(self.validation_path, 0o644))
# Check permissions of the directories
-
for dirpath, dirnames, _ in os.walk(self.path):
for directory in dirnames:
full_path = os.path.join(dirpath, directory)
self.assertTrue(filesystem.check_mode(full_path, 0o755))
- parent_gid = os.stat(self.path).st_gid
- parent_uid = os.stat(self.path).st_uid
-
- self.assertEqual(os.stat(self.validation_path).st_gid, parent_gid)
- self.assertEqual(os.stat(self.validation_path).st_uid, parent_uid)
+ self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path))
def test_perform_cleanup(self):
self.auth.prepare()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/storage.py new/certbot-0.39.0/certbot/storage.py
--- old/certbot-0.38.0/certbot/storage.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/storage.py 2019-10-01 21:48:40.000000000 +0200
@@ -18,7 +18,6 @@
from certbot import error_handler
from certbot import errors
from certbot import util
-from certbot.compat import misc
from certbot.compat import os
from certbot.compat import filesystem
from certbot.plugins import common as plugins_common
@@ -1107,9 +1106,7 @@
f.write(new_privkey)
# Preserve gid and (mode & MASK_FOR_PRIVATE_KEY_PERMISSIONS)
# from previous privkey in this lineage.
- old_mode = (stat.S_IMODE(os.stat(old_privkey).st_mode) &
- misc.MASK_FOR_PRIVATE_KEY_PERMISSIONS)
- mode = BASE_PRIVKEY_MODE | old_mode
+ mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE)
filesystem.copy_ownership_and_apply_mode(
old_privkey, target["privkey"], mode, copy_user=False, copy_group=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/tests/compat/filesystem_test.py new/certbot-0.39.0/certbot/tests/compat/filesystem_test.py
--- old/certbot-0.38.0/certbot/tests/compat/filesystem_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/tests/compat/filesystem_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -150,6 +150,24 @@
self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2)
+class ComputePrivateKeyModeTest(TempDirTestCase):
+ def setUp(self):
+ super(ComputePrivateKeyModeTest, self).setUp()
+ self.probe_path = _create_probe(self.tempdir)
+
+ def test_compute_private_key_mode(self):
+ filesystem.chmod(self.probe_path, 0o777)
+ new_mode = filesystem.compute_private_key_mode(self.probe_path, 0o600)
+
+ if POSIX_MODE:
+ # On Linux RWX permissions for group and R permission for world
+ # are persisted from the existing moe
+ self.assertEqual(new_mode, 0o674)
+ else:
+ # On Windows no permission is persisted
+ self.assertEqual(new_mode, 0o600)
+
+
@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security')
class WindowsOpenTest(TempDirTestCase):
def test_new_file_correct_permissions(self):
@@ -262,14 +280,14 @@
self.assertEqual(original_mkdir, std_os.mkdir)
-class CopyOwnershipTest(test_util.TempDirTestCase):
- """Tests about replacement of chown: copy_ownership_and_apply_mode"""
+class OwnershipTest(test_util.TempDirTestCase):
+ """Tests about copy_ownership_and_apply_mode and has_same_ownership"""
def setUp(self):
- super(CopyOwnershipTest, self).setUp()
+ super(OwnershipTest, self).setUp()
self.probe_path = _create_probe(self.tempdir)
@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security')
- def test_windows(self):
+ def test_copy_ownership_windows(self):
system = win32security.ConvertStringSidToSid(SYSTEM_SID)
security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR
security.SetSecurityDescriptorOwner(system, False)
@@ -295,7 +313,7 @@
if dacl.GetAce(index)[2] == everybody])
@unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security')
- def test_linux(self):
+ def test_copy_ownership_linux(self):
with mock.patch('os.chown') as mock_chown:
with mock.patch('os.chmod') as mock_chmod:
with mock.patch('os.stat') as mock_stat:
@@ -307,8 +325,18 @@
mock_chown.assert_called_once_with(self.probe_path, 50, 51)
mock_chmod.assert_called_once_with(self.probe_path, 0o700)
+ def test_has_same_ownership(self):
+ path1 = os.path.join(self.tempdir, 'test1')
+ path2 = os.path.join(self.tempdir, 'test2')
+
+ util.safe_open(path1, 'w').close()
+ util.safe_open(path2, 'w').close()
+
+ self.assertTrue(filesystem.has_same_ownership(path1, path2))
+
class CheckPermissionsTest(test_util.TempDirTestCase):
+ """Tests relative to functions that check modes."""
def setUp(self):
super(CheckPermissionsTest, self).setUp()
self.probe_path = _create_probe(self.tempdir)
@@ -353,6 +381,23 @@
mock_owner.return_value = False
self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744))
+ def test_check_min_permissions(self):
+ filesystem.chmod(self.probe_path, 0o744)
+ self.assertTrue(filesystem.has_min_permissions(self.probe_path, 0o744))
+
+ filesystem.chmod(self.probe_path, 0o700)
+ self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744))
+
+ filesystem.chmod(self.probe_path, 0o741)
+ self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744))
+
+ def test_is_world_reachable(self):
+ filesystem.chmod(self.probe_path, 0o744)
+ self.assertTrue(filesystem.has_world_permissions(self.probe_path))
+
+ filesystem.chmod(self.probe_path, 0o700)
+ self.assertFalse(filesystem.has_world_permissions(self.probe_path))
+
class OsReplaceTest(test_util.TempDirTestCase):
"""Test to ensure consistent behavior of rename method"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/tests/compat/os_test.py new/certbot-0.39.0/certbot/tests/compat/os_test.py
--- old/certbot-0.38.0/certbot/tests/compat/os_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/tests/compat/os_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -8,8 +8,8 @@
"""Unit tests for os module."""
def test_forbidden_methods(self):
# Checks for os module
- for method in ['chmod', 'chown', 'open', 'mkdir',
- 'makedirs', 'rename', 'replace', 'access']:
+ for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename',
+ 'replace', 'access', 'stat', 'fstat']:
self.assertRaises(RuntimeError, getattr(os, method))
# Checks for os.path module
for method in ['realpath']:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/tests/lock_test.py new/certbot-0.39.0/certbot/tests/lock_test.py
--- old/certbot-0.38.0/certbot/tests/lock_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/tests/lock_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -82,7 +82,10 @@
'Race conditions on lock are specific to the non-blocking file access approach on Linux.')
def test_race(self):
should_delete = [True, False]
- stat = os.stat
+ # Normally os module should not be imported in certbot codebase except in certbot.compat
+ # for the sake of compatibility over Windows and Linux.
+ # We make an exception here, since test_race is a test function called only on Linux.
+ from os import stat # pylint: disable=os-module-forbidden
def delete_and_stat(path):
"""Wrap os.stat and maybe delete the file first."""
@@ -90,7 +93,7 @@
os.remove(path)
return stat(path)
- with mock.patch('certbot.lock.os.stat') as mock_stat:
+ with mock.patch('certbot.lock.filesystem.os.stat') as mock_stat:
mock_stat.side_effect = delete_and_stat
self._call(self.lock_path)
self.assertFalse(should_delete)
@@ -117,7 +120,7 @@
def test_unexpected_os_err(self):
if POSIX_MODE:
- mock_function = 'certbot.lock.os.stat'
+ mock_function = 'certbot.lock.filesystem.os.stat'
else:
mock_function = 'certbot.lock.msvcrt.locking'
# The only expected errno are ENOENT and EACCES in lock module.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/tests/ocsp_test.py new/certbot-0.39.0/certbot/tests/ocsp_test.py
--- old/certbot-0.38.0/certbot/tests/ocsp_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/tests/ocsp_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -16,6 +16,7 @@
except (ImportError, AttributeError): # pragma: no cover
ocsp_lib = None # type: ignore
import mock
+import pytz
from certbot import errors
from certbot.tests import util as test_util
@@ -72,21 +73,34 @@
@mock.patch('certbot.ocsp._determine_ocsp_server')
@mock.patch('certbot.util.run_script')
def test_ocsp_revoked(self, mock_run, mock_determine):
+ now = pytz.UTC.fromutc(datetime.utcnow())
+ cert_obj = mock.MagicMock()
+ cert_obj.cert = "x"
+ cert_obj.chain = "y"
+ cert_obj.target_expiry = now + timedelta(hours=2)
+
self.checker.broken = True
mock_determine.return_value = ("", "")
- self.assertEqual(self.checker.ocsp_revoked("x", "y"), False)
+ self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)
self.checker.broken = False
mock_run.return_value = tuple(openssl_happy[1:])
- self.assertEqual(self.checker.ocsp_revoked("x", "y"), False)
+ self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)
self.assertEqual(mock_run.call_count, 0)
mock_determine.return_value = ("http://x.co", "x.co")
- self.assertEqual(self.checker.ocsp_revoked("blah.pem", "chain.pem"), False)
+ self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)
mock_run.side_effect = errors.SubprocessError("Unable to load certificate launcher")
- self.assertEqual(self.checker.ocsp_revoked("x", "y"), False)
+ self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)
self.assertEqual(mock_run.call_count, 2)
+ # cert expired
+ cert_obj.target_expiry = now
+ mock_determine.return_value = ("", "")
+ count_before = mock_determine.call_count
+ self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)
+ self.assertEqual(mock_determine.call_count, count_before)
+
def test_determine_ocsp_server(self):
cert_path = test_util.vector_path('ocsp_certificate.pem')
@@ -131,18 +145,23 @@
self.checker = ocsp.RevocationChecker()
self.cert_path = test_util.vector_path('ocsp_certificate.pem')
self.chain_path = test_util.vector_path('ocsp_issuer_certificate.pem')
+ self.cert_obj = mock.MagicMock()
+ self.cert_obj.cert = self.cert_path
+ self.cert_obj.chain = self.chain_path
+ now = pytz.UTC.fromutc(datetime.utcnow())
+ self.cert_obj.target_expiry = now + timedelta(hours=2)
@mock.patch('certbot.ocsp._determine_ocsp_server')
@mock.patch('certbot.ocsp._check_ocsp_cryptography')
def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine):
mock_determine.return_value = ('http://example.com', 'example.com')
- self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ self.checker.ocsp_revoked(self.cert_obj)
mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com')
def test_revoke(self):
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertTrue(revoked)
def test_responder_is_issuer(self):
@@ -152,7 +171,7 @@
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,
ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:
mocks['mock_response'].return_value.responder_name = issuer.subject
- self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ self.checker.ocsp_revoked(self.cert_obj)
# Here responder and issuer are the same. So only the signature of the OCSP
# response is checked (using the issuer/responder public key).
self.assertEqual(mocks['mock_check'].call_count, 1)
@@ -167,7 +186,7 @@
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,
ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:
- self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ self.checker.ocsp_revoked(self.cert_obj)
# Here responder and issuer are not the same. Two signatures will be checked then,
# first to verify the responder cert (using the issuer public key), second to
# to verify the OCSP response itself (using the responder public key).
@@ -181,17 +200,17 @@
# Server return an invalid HTTP response
with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,
http_status_code=400):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# OCSP response in invalid
with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# OCSP response is valid, but certificate status is unknown
with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# The OCSP response says that the certificate is revoked, but certificate
@@ -200,32 +219,32 @@
with mock.patch('cryptography.x509.Extensions.get_extension_for_class',
side_effect=x509.ExtensionNotFound(
'Not found', x509.AuthorityInformationAccessOID.OCSP)):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# OCSP response uses an unsupported signature.
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,
check_signature_side_effect=UnsupportedAlgorithm('foo')):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# OSCP signature response is invalid.
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,
check_signature_side_effect=InvalidSignature('foo')):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# Assertion error on OCSP response validity
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,
check_signature_side_effect=AssertionError('foo')):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# No responder cert in OCSP response
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,
ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:
mocks['mock_response'].return_value.certificates = []
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
# Responder cert is not signed by certificate issuer
@@ -234,7 +253,7 @@
cert = mocks['mock_response'].return_value.certificates[0]
mocks['mock_response'].return_value.certificates[0] = mock.Mock(
issuer='fake', subject=cert.subject)
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):
@@ -245,7 +264,7 @@
with mock.patch('cryptography.x509.Extensions.get_extension_for_class',
side_effect=x509.ExtensionNotFound(
'Not found', x509.AuthorityInformationAccessOID.OCSP)):
- revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
+ revoked = self.checker.ocsp_revoked(self.cert_obj)
self.assertFalse(revoked)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/tests/util_test.py new/certbot-0.39.0/certbot/tests/util_test.py
--- old/certbot-0.38.0/certbot/tests/util_test.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/tests/util_test.py 2019-10-01 21:48:40.000000000 +0200
@@ -520,13 +520,16 @@
with mock.patch('platform.system_alias',
return_value=('linux', '', '')):
- with mock.patch('distro.linux_distribution',
- return_value=('', '', '')):
- self.assertEqual(get_python_os_info(), ("linux", ""))
+ with mock.patch('platform.linux_distribution',
+ side_effect=AttributeError,
+ create=True):
+ with mock.patch('distro.linux_distribution',
+ return_value=('', '', '')):
+ self.assertEqual(get_python_os_info(), ("linux", ""))
- with mock.patch('distro.linux_distribution',
- return_value=('testdist', '42', '')):
- self.assertEqual(get_python_os_info(), ("testdist", "42"))
+ with mock.patch('distro.linux_distribution',
+ return_value=('testdist', '42', '')):
+ self.assertEqual(get_python_os_info(), ("testdist", "42"))
with mock.patch('platform.system_alias',
return_value=('freebsd', '9.3-RC3-p1', '')):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot/util.py new/certbot-0.39.0/certbot/util.py
--- old/certbot-0.38.0/certbot/util.py 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/certbot/util.py 2019-10-01 21:48:40.000000000 +0200
@@ -392,7 +392,7 @@
os_type, os_ver, _ = info
os_type = os_type.lower()
if os_type.startswith('linux'):
- info = distro.linux_distribution()
+ info = _get_linux_distribution()
# On arch, distro.linux_distribution() is reportedly ('','',''),
# so handle it defensively
if info[0]:
@@ -424,6 +424,14 @@
os_ver = ''
return os_type, os_ver
+def _get_linux_distribution():
+ """Gets the linux distribution name from the underlying OS"""
+
+ try:
+ return platform.linux_distribution()
+ except AttributeError:
+ return distro.linux_distribution()
+
# Just make sure we don't get pwned... Make sure that it also doesn't
# start with a period or have two consecutive periods <- this needs to
# be done in addition to the regex
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/certbot.egg-info/PKG-INFO new/certbot-0.39.0/certbot.egg-info/PKG-INFO
--- old/certbot-0.38.0/certbot.egg-info/PKG-INFO 2019-09-03 21:42:37.000000000 +0200
+++ new/certbot-0.39.0/certbot.egg-info/PKG-INFO 2019-10-01 21:48:41.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: certbot
-Version: 0.38.0
+Version: 0.39.0
Summary: ACME client
Home-page: https://github.com/letsencrypt/letsencrypt
Author: Certbot Project
@@ -153,6 +153,7 @@
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Security
Classifier: Topic :: System :: Installation/Setup
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/docs/cli-help.txt new/certbot-0.39.0/docs/cli-help.txt
--- old/certbot-0.38.0/docs/cli-help.txt 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/docs/cli-help.txt 2019-10-01 21:48:40.000000000 +0200
@@ -113,7 +113,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/0.37.2
+ "". (default: CertbotACMEClient/0.38.0
(certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX
Installer/YYY (SUBCOMMAND; flags: FLAGS)
Py/major.minor.patchlevel). The flags encoded in the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/docs/using.rst new/certbot-0.39.0/docs/using.rst
--- old/certbot-0.38.0/docs/using.rst 2019-09-03 21:42:35.000000000 +0200
+++ new/certbot-0.39.0/docs/using.rst 2019-10-01 21:48:40.000000000 +0200
@@ -276,10 +276,8 @@
gandi_ Y N Obtain certificates via the Gandi LiveDNS API
varnish_ Y N Obtain certificates via a Varnish server
external_ Y N A plugin for convenient scripting (See also ticket 2782_)
-icecast_ N Y Deploy certificates to Icecast 2 streaming media servers
pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers
proxmox_ N Y Install certificates in Proxmox Virtualization servers
-heroku_ Y Y Integration with Heroku SSL
dns-standalone_ Y N Obtain certificates via an integrated DNS server
dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server
================== ==== ==== ===============================================================
@@ -287,13 +285,11 @@
.. _haproxy: https://github.com/greenhost/certbot-haproxy
.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front
.. _gandi: https://github.com/obynio/certbot-plugin-gandi
-.. _icecast: https://github.com/e00E/lets-encrypt-icecast
.. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin
.. _2782: https://github.com/certbot/certbot/issues/2782
.. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl
.. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox
.. _external: https://github.com/marcan/letsencrypt-external
-.. _heroku: https://github.com/gboudreau/certbot-heroku
.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone
.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/certbot-0.38.0/setup.py new/certbot-0.39.0/setup.py
--- old/certbot-0.38.0/setup.py 2019-09-03 21:42:36.000000000 +0200
+++ new/certbot-0.39.0/setup.py 2019-10-01 21:48:41.000000000 +0200
@@ -59,11 +59,17 @@
# However environment markers are supported only with setuptools >= 36.2.
# So this dependency is not added for old Linux distributions with old setuptools,
# in order to allow these systems to build certbot from sources.
+pywin32_req = 'pywin32>=224'
if StrictVersion(setuptools_version) >= StrictVersion('36.2'):
- install_requires.append("pywin32>=224 ; sys_platform == 'win32'")
+ install_requires.append(pywin32_req + " ; sys_platform == 'win32'")
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
+elif os.name == 'nt':
+ # This branch exists to improve this package's behavior on Windows. Without
+ # it, if the sdist is installed on Windows with an old version of
+ # setuptools, pywin32 will not be specified as a dependency.
+ install_requires.append(pywin32_req)
dev_extras = [
'astroid==1.6.5',
@@ -132,6 +138,7 @@
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',