mirror of
https://github.com/certbot/certbot.git
synced 2026-06-05 06:42:10 -04:00
Merge branch 'master' into reenable-build-isolation
# Conflicts: # snap/snapcraft.yaml
This commit is contained in:
commit
ec2441f773
144 changed files with 1902 additions and 1536 deletions
|
|
@ -4,7 +4,7 @@ jobs:
|
|||
- name: IMAGE_NAME
|
||||
value: ubuntu-18.04
|
||||
- name: PYTHON_VERSION
|
||||
value: 3.8
|
||||
value: 3.9
|
||||
- group: certbot-common
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
@ -14,6 +14,9 @@ jobs:
|
|||
linux-py37:
|
||||
PYTHON_VERSION: 3.7
|
||||
TOXENV: py37
|
||||
linux-py38:
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: py38
|
||||
linux-py37-nopin:
|
||||
PYTHON_VERSION: 3.7
|
||||
TOXENV: py37
|
||||
|
|
@ -62,10 +65,20 @@ jobs:
|
|||
PYTHON_VERSION: 3.8
|
||||
TOXENV: integration
|
||||
ACME_SERVER: boulder-v2
|
||||
linux-boulder-v1-py39-integration:
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: integration
|
||||
ACME_SERVER: boulder-v1
|
||||
linux-boulder-v2-py39-integration:
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: integration
|
||||
ACME_SERVER: boulder-v2
|
||||
nginx-compat:
|
||||
TOXENV: nginx_compat
|
||||
le-auto-oraclelinux6:
|
||||
TOXENV: le_auto_oraclelinux6
|
||||
linux-integration-rfc2136:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: integration-dns-rfc2136
|
||||
docker-dev:
|
||||
TOXENV: docker_dev
|
||||
macos-farmtest-apache2:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
jobs:
|
||||
- job: test
|
||||
variables:
|
||||
PYTHON_VERSION: 3.8
|
||||
PYTHON_VERSION: 3.9
|
||||
strategy:
|
||||
matrix:
|
||||
macos-py27:
|
||||
IMAGE_NAME: macOS-10.15
|
||||
PYTHON_VERSION: 2.7
|
||||
TOXENV: py27
|
||||
macos-py38:
|
||||
macos-py39:
|
||||
IMAGE_NAME: macOS-10.15
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: py38
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: py39
|
||||
windows-py36:
|
||||
IMAGE_NAME: vs2017-win2016
|
||||
PYTHON_VERSION: 3.6
|
||||
|
|
@ -38,10 +38,10 @@ jobs:
|
|||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 3.6
|
||||
TOXENV: py36
|
||||
linux-py38-cover:
|
||||
linux-py39-cover:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: py38-cover
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: py39-cover
|
||||
linux-py37-lint:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 3.7
|
||||
|
|
@ -52,15 +52,15 @@ jobs:
|
|||
TOXENV: mypy
|
||||
linux-integration:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 2.7
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: integration
|
||||
ACME_SERVER: pebble
|
||||
apache-compat:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
TOXENV: apache_compat
|
||||
le-auto-centos6:
|
||||
le-modification:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
TOXENV: le_auto_centos6
|
||||
TOXENV: modification
|
||||
apacheconftest:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 2.7
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ Authors
|
|||
* [Luca Olivetti](https://github.com/olivluca)
|
||||
* [Luke Rogers](https://github.com/lukeroge)
|
||||
* [Maarten](https://github.com/mrtndwrd)
|
||||
* [Mads Jensen](https://github.com/atombrella)
|
||||
* [Maikel Martens](https://github.com/krukas)
|
||||
* [Malte Janduda](https://github.com/MalteJ)
|
||||
* [Mantas Mikulėnas](https://github.com/grawity)
|
||||
|
|
@ -213,6 +214,7 @@ Authors
|
|||
* [Richard Barnes](https://github.com/r-barnes)
|
||||
* [Richard Panek](https://github.com/kernelpanek)
|
||||
* [Robert Buchholz](https://github.com/rbu)
|
||||
* [Robert Dailey](https://github.com/pahrohfit)
|
||||
* [Robert Habermann](https://github.com/frennkie)
|
||||
* [Robert Xiao](https://github.com/nneonneo)
|
||||
* [Roland Shoemaker](https://github.com/rolandshoemaker)
|
||||
|
|
|
|||
|
|
@ -20,3 +20,10 @@ for mod in list(sys.modules):
|
|||
# preserved (acme.jose.* is josepy.*)
|
||||
if mod == 'josepy' or mod.startswith('josepy.'):
|
||||
sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod]
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
warnings.warn(
|
||||
"Python 2 support will be dropped in the next release of acme. "
|
||||
"Please upgrade your Python version.",
|
||||
PendingDeprecationWarning,
|
||||
) # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
@ -66,6 +66,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -108,11 +108,11 @@ class ConstantTest(unittest.TestCase):
|
|||
|
||||
def test_equality(self):
|
||||
const_a_prime = self.MockConstant('a')
|
||||
self.assertFalse(self.const_a == self.const_b)
|
||||
self.assertTrue(self.const_a == const_a_prime)
|
||||
self.assertNotEqual(self.const_a, self.const_b)
|
||||
self.assertEqual(self.const_a, const_a_prime)
|
||||
|
||||
self.assertTrue(self.const_a != self.const_b)
|
||||
self.assertFalse(self.const_a != const_a_prime)
|
||||
self.assertNotEqual(self.const_a, self.const_b)
|
||||
self.assertEqual(self.const_a, const_a_prime)
|
||||
|
||||
|
||||
class DirectoryTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -53,6 +53,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -1350,10 +1350,10 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
|
||||
# And the actual returned values
|
||||
self.assertEqual(len(vhs), 1)
|
||||
self.assertTrue(vhs[0].name == "certbot.demo")
|
||||
self.assertEqual(vhs[0].name, "certbot.demo")
|
||||
self.assertTrue(vhs[0].ssl)
|
||||
|
||||
self.assertFalse(vhs[0] == self.vh_truth[3])
|
||||
self.assertNotEqual(vhs[0], self.vh_truth[3])
|
||||
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl")
|
||||
def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl):
|
||||
|
|
@ -1464,10 +1464,10 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.config.parser.aug.match = mock_match
|
||||
vhs = self.config.get_virtual_hosts()
|
||||
self.assertEqual(len(vhs), 2)
|
||||
self.assertTrue(vhs[0] == self.vh_truth[1])
|
||||
self.assertEqual(vhs[0], self.vh_truth[1])
|
||||
# mock_vhost should have replaced the vh_truth[0], because its filepath
|
||||
# isn't a symlink
|
||||
self.assertTrue(vhs[1] == mock_vhost)
|
||||
self.assertEqual(vhs[1], mock_vhost)
|
||||
|
||||
|
||||
class AugeasVhostsTest(util.ApacheTest):
|
||||
|
|
|
|||
|
|
@ -412,9 +412,9 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
|
|||
ancestor=self.block,
|
||||
filepath="/path/to/whatever",
|
||||
metadata=self.metadata)
|
||||
self.assertFalse(self.block == ne_block)
|
||||
self.assertFalse(self.directive == ne_directive)
|
||||
self.assertFalse(self.comment == ne_comment)
|
||||
self.assertNotEqual(self.block, ne_block)
|
||||
self.assertNotEqual(self.directive, ne_directive)
|
||||
self.assertNotEqual(self.comment, ne_comment)
|
||||
|
||||
def test_parsed_paths(self):
|
||||
mock_p = mock.MagicMock(return_value=['/path/file.conf',
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ class VirtualHostTest(unittest.TestCase):
|
|||
"certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))")
|
||||
|
||||
def test_eq(self):
|
||||
self.assertTrue(self.vhost1b == self.vhost1)
|
||||
self.assertFalse(self.vhost1 == self.vhost2)
|
||||
self.assertEqual(self.vhost1b, self.vhost1)
|
||||
self.assertNotEqual(self.vhost1, self.vhost2)
|
||||
self.assertEqual(str(self.vhost1b), str(self.vhost1))
|
||||
self.assertFalse(self.vhost1b == 1234)
|
||||
self.assertNotEqual(self.vhost1b, 1234)
|
||||
|
||||
def test_ne(self):
|
||||
self.assertTrue(self.vhost1 != self.vhost2)
|
||||
self.assertFalse(self.vhost1 != self.vhost1b)
|
||||
self.assertNotEqual(self.vhost1, self.vhost2)
|
||||
self.assertEqual(self.vhost1, self.vhost1b)
|
||||
|
||||
def test_conflicts(self):
|
||||
from certbot_apache._internal.obj import Addr
|
||||
|
|
@ -128,13 +128,13 @@ class AddrTest(unittest.TestCase):
|
|||
self.assertTrue(self.addr1.conflicts(self.addr2))
|
||||
|
||||
def test_equal(self):
|
||||
self.assertTrue(self.addr1 == self.addr2)
|
||||
self.assertFalse(self.addr == self.addr1)
|
||||
self.assertFalse(self.addr == 123)
|
||||
self.assertEqual(self.addr1, self.addr2)
|
||||
self.assertNotEqual(self.addr, self.addr1)
|
||||
self.assertNotEqual(self.addr, 123)
|
||||
|
||||
def test_not_equal(self):
|
||||
self.assertFalse(self.addr1 != self.addr2)
|
||||
self.assertTrue(self.addr != self.addr1)
|
||||
self.assertEqual(self.addr1, self.addr2)
|
||||
self.assertNotEqual(self.addr, self.addr1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
32
certbot-auto
32
certbot-auto
|
|
@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
|
||||
LE_AUTO_VERSION="1.9.0"
|
||||
LE_AUTO_VERSION="1.10.1"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -799,11 +799,7 @@ BootstrapMageiaCommon() {
|
|||
# that function. If Bootstrap is set to a function that doesn't install any
|
||||
# packages BOOTSTRAP_VERSION is not set.
|
||||
if [ -f /etc/debian_version ]; then
|
||||
Bootstrap() {
|
||||
BootstrapMessage "Debian-based OSes"
|
||||
BootstrapDebCommon
|
||||
}
|
||||
BOOTSTRAP_VERSION="BootstrapDebCommon $BOOTSTRAP_DEB_COMMON_VERSION"
|
||||
DEPRECATED_OS=1
|
||||
elif [ -f /etc/mageia-release ]; then
|
||||
# Mageia has both /etc/mageia-release and /etc/redhat-release
|
||||
DEPRECATED_OS=1
|
||||
|
|
@ -1497,18 +1493,18 @@ letsencrypt==0.7.0 \
|
|||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
certbot==1.9.0 \
|
||||
--hash=sha256:d5a804d32e471050921f7b39ed9859e2e9de02824176ed78f57266222036b53a \
|
||||
--hash=sha256:2ff9bf7d9af381c7efee22dec2dd6938d9d8fddcc9e11682b86e734164a30b57
|
||||
acme==1.9.0 \
|
||||
--hash=sha256:d8061b396a22b21782c9b23ff9a945b23e50fca2573909a42f845e11d5658ac5 \
|
||||
--hash=sha256:38a1630c98e144136c62eec4d2c545a1bdb1a3cd4eca82214be6b83a1f5a161f
|
||||
certbot-apache==1.9.0 \
|
||||
--hash=sha256:09528a820d57e54984d490100644cd8a6603db97bf5776f86e95795ecfacf23d \
|
||||
--hash=sha256:f47fb3f4a9bd927f4812121a0beefe56b163475a28f4db34c64dc838688d9e9e
|
||||
certbot-nginx==1.9.0 \
|
||||
--hash=sha256:bb2e3f7fe17f071f350a3efa48571b8ef40a8e4b6db9c6da72539206a20b70be \
|
||||
--hash=sha256:ab26a4f49d53b0e8bf0f903e58e2a840cda233fe1cbbc54c36ff17f973e57d65
|
||||
certbot==1.10.1 \
|
||||
--hash=sha256:011ac980fa21b9f29e02c9b8d8b86e8a4bf4670b51b6ad91656e401e9d2d2231 \
|
||||
--hash=sha256:0d9ee3fc09e0d03b2d1b1f1c4916e61ecfc6904b4216ddef4e6a5ca1424d9cb7
|
||||
acme==1.10.1 \
|
||||
--hash=sha256:752d598e54e98ad1e874de53fd50c61044f1b566d6deb790db5676ce9c573546 \
|
||||
--hash=sha256:fcbb559aedc96b404edf593e78517dcd7291984d5a37036c3fc77f3c5c122fd8
|
||||
certbot-apache==1.10.1 \
|
||||
--hash=sha256:f077b4b7f166627ef5e0921fe7cde57700670fc86e9ad9dbdfaf2c573cc0f2fa \
|
||||
--hash=sha256:97ed637b4c7b03820db6c69aa90145dc989933351d46a3d62baf6b71674f0a10
|
||||
certbot-nginx==1.10.1 \
|
||||
--hash=sha256:7c36459021f8a1ec3b6c062e4c4fc866bfaa1dbf26ccd29e043dd6848003be08 \
|
||||
--hash=sha256:c0bbeccf85f46b728fd95e6bb8c2649d32d3383d7f47ea4b9c312d12bf04d2f0
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
options {
|
||||
directory "/var/cache/bind";
|
||||
|
||||
// Running inside Docker. Bind address on Docker host is 127.0.0.1.
|
||||
listen-on { any; };
|
||||
listen-on-v6 { any; };
|
||||
|
||||
// We are allowing BIND to service recursive queries, but only in an extremely limimited sense
|
||||
// where it is entirely disconnected from public DNS:
|
||||
// - Iterative queries are disabled. Only forwarding to a non-existent forwarder.
|
||||
// - The only recursive answers we can get (that will not be a SERVFAIL) will come from the
|
||||
// RPZ "mock-recursion" zone. Effectively this means we are mocking out the entirety of
|
||||
// public DNS.
|
||||
allow-recursion { any; }; // BIND will only answer using RPZ if recursion is enabled
|
||||
forwarders { 192.0.2.254; }; // Nobody is listening, this is TEST-NET-1
|
||||
forward only; // Do NOT perform iterative queries from the root zone
|
||||
dnssec-validation no; // Do not bother fetching the root DNSKEY set (performance)
|
||||
response-policy { // All recursive queries will be served from here.
|
||||
zone "mock-recursion"
|
||||
log yes;
|
||||
} recursive-only no // Allow RPZs to affect authoritative zones too.
|
||||
qname-wait-recurse no // No real recursion.
|
||||
nsip-wait-recurse no; // No real recursion.
|
||||
|
||||
allow-transfer { none; };
|
||||
allow-update { none; };
|
||||
};
|
||||
|
||||
key "default-key." {
|
||||
algorithm hmac-sha512;
|
||||
secret "91CgOwzihr0nAVEHKFXJPQCbuBBbBI19Ks5VAweUXgbF40NWTD83naeg3c5y2MPdEiFRXnRLJxL6M+AfHCGLNw==";
|
||||
};
|
||||
|
||||
zone "mock-recursion" {
|
||||
type primary;
|
||||
file "/var/lib/bind/rpz.mock-recursion";
|
||||
allow-query {
|
||||
none;
|
||||
};
|
||||
};
|
||||
|
||||
zone "example.com." {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.example.com";
|
||||
journal "/var/cache/bind/db.example.com.jnl";
|
||||
|
||||
update-policy {
|
||||
grant default-key zonesub TXT;
|
||||
};
|
||||
};
|
||||
|
||||
zone "sub.example.com." {
|
||||
type primary;
|
||||
file "/var/lib/bind/db.sub.example.com";
|
||||
journal "/var/cache/bind/db.sub.example.com.jnl";
|
||||
|
||||
update-policy {
|
||||
grant default-key zonesub TXT;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Target DNS server
|
||||
dns_rfc2136_server = {server_address}
|
||||
# Target DNS port
|
||||
dns_rfc2136_port = {server_port}
|
||||
# TSIG key name
|
||||
dns_rfc2136_name = default-key.
|
||||
# TSIG key secret
|
||||
dns_rfc2136_secret = 91CgOwzihr0nAVEHKFXJPQCbuBBbBI19Ks5VAweUXgbF40NWTD83naeg3c5y2MPdEiFRXnRLJxL6M+AfHCGLNw==
|
||||
# TSIG key algorithm
|
||||
dns_rfc2136_algorithm = HMAC-SHA512
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
$ORIGIN example.com.
|
||||
$TTL 3600
|
||||
example.com. IN SOA ns1.example.com. admin.example.com. ( 2020091025 7200 3600 1209600 3600 )
|
||||
|
||||
example.com. IN NS ns1
|
||||
example.com. IN NS ns2
|
||||
|
||||
ns1 IN A 192.0.2.2
|
||||
ns2 IN A 192.0.2.3
|
||||
|
||||
@ IN A 192.0.2.1
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
$ORIGIN sub.example.com.
|
||||
$TTL 3600
|
||||
sub.example.com. IN SOA ns1.example.com. admin.example.com. ( 2020091025 7200 3600 1209600 3600 )
|
||||
|
||||
sub.example.com. IN NS ns1
|
||||
sub.example.com. IN NS ns2
|
||||
|
||||
ns1 IN A 192.0.2.2
|
||||
ns2 IN A 192.0.2.3
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
$TTL 3600
|
||||
|
||||
@ SOA ns1.example.test. dummy.example.test. 1 12h 15m 3w 2h
|
||||
NS ns1.example.test.
|
||||
|
||||
_acme-challenge.aliased.example IN CNAME _acme-challenge.example.com.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
This directory contains your keys and certificates.
|
||||
|
||||
`privkey.pem` : the private key for your certificate.
|
||||
`fullchain.pem`: the certificate file used in most server software.
|
||||
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
|
||||
`cert.pem` : will break many server configurations, and should not be used
|
||||
without reading further documentation (see link below).
|
||||
|
||||
WARNING: DO NOT MOVE OR RENAME THESE FILES!
|
||||
Certbot expects these files to remain in this location in order
|
||||
to function properly!
|
||||
|
||||
We recommend not moving these files. For more information, see the Certbot
|
||||
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIIBvrEnbPRYu8wDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjEwNzQw
|
||||
WhcNMjUxMDEyMjEwNzQwWjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARjMhuW0ENPPC33PjB5XsYU
|
||||
CRw640kPQENIDatcTJaENZIZdqKd6rI6jc+lpbmXot7Zi52clJlSJS+V6oDAt2Lh
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUj7Kd3ENqxlPf8B2bIGhsjydX
|
||||
mPswHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCl
|
||||
k0JXsa8y7fg41WWMDhw60bPW77O0FtOmTcnhdI5daYNemQVk+Q5EMaBLQ/oGjgXd
|
||||
9QXFzXH1PL904YEnSLt+iTpXn++7rQSNzQsdYqw0neWk4f5pEBiN+WORpb6mwobV
|
||||
ifMtBOkNEHvrJ2Pkci9U1lLwtKD/DSew6QtJU5DSkmH1XdGuMJiubygEIvELtvgq
|
||||
cP9S368ZvPmPGmKaJQXBiuaR8MTjY/Bkr79aXQMjKbf+mpn7h0POCcePk1DY/rm6
|
||||
Da+X16lf0hHyQhSUa7Vgyim6rK1/hlw+Z00i+sQCKD9Ih7kXuuGqfSDC33cfO8Tj
|
||||
o/MXO8lcxkrem5zU5QWP
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIILlmGtZhUFEwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjA1MDM0
|
||||
WhcNMjUxMDEyMjA1MDM0WjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARHEzR8JPWrEmpmgM+F2bk5
|
||||
9mT0u6CjzmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU1CsVL+bPnzaxxQ5jUENmQJIO
|
||||
lKwwHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBn
|
||||
2D8loC7pfk28JYpFLr5lmFKJWWmtLGlpsWDj61fVjtTfGKLziJz+MM6il4Y3hIz5
|
||||
58qiFK0ue0M63dIBJ33N+XxSEXon4Q0gy/zRWfH9jtPJ3FwfjkU/RT9PAUClYi0G
|
||||
ptNWnTmgQkNzousbcAtRNXuuShH3856vhUnwkX+xM+cbIDi1JVmFjcGrEEQJ0rUF
|
||||
mv2ZTyfbWbUs3v4rReETi2NVzr1Ql6J+ByNcMvHODzFy3t0L6yelAw2ca1I+c9HU
|
||||
+Z0tnp/ykR7eXNuVLivok8UBf5OC413lh8ZO5g+Bgzh/LdtkUuavg1MYtEX0H6mX
|
||||
9U7y3nVI8WEbPGf+HDeu
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNgefv2dad4U1VYEi
|
||||
0WkdHuqywi5QXAe30OwNTTGjhbihRANCAARHEzR8JPWrEmpmgM+F2bk59mT0u6Cj
|
||||
zmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
This directory contains your keys and certificates.
|
||||
|
||||
`privkey.pem` : the private key for your certificate.
|
||||
`fullchain.pem`: the certificate file used in most server software.
|
||||
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
|
||||
`cert.pem` : will break many server configurations, and should not be used
|
||||
without reading further documentation (see link below).
|
||||
|
||||
WARNING: DO NOT MOVE OR RENAME THESE FILES!
|
||||
Certbot expects these files to remain in this location in order
|
||||
to function properly!
|
||||
|
||||
We recommend not moving these files. For more information, see the Certbot
|
||||
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/cert.pem
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/chain.pem
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/fullchain.pem
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/privkey.pem
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# renew_before_expiry = 30 days
|
||||
version = 1.10.0.dev0
|
||||
archive_dir = sample-config/archive/c.encryption-example.com
|
||||
cert = sample-config/live/c.encryption-example.com/cert.pem
|
||||
privkey = sample-config/live/c.encryption-example.com/privkey.pem
|
||||
chain = sample-config/live/c.encryption-example.com/chain.pem
|
||||
fullchain = sample-config/live/c.encryption-example.com/fullchain.pem
|
||||
|
||||
# Options used in the renewal process
|
||||
[renewalparams]
|
||||
authenticator = apache
|
||||
installer = apache
|
||||
account = 48d6b9e8d767eccf7e4d877d6ffa81e3
|
||||
key_type = ecdsa
|
||||
config_dir = sample-config-ec
|
||||
elliptic_curve = secp256r1
|
||||
manual_public_ip_logging_ok = True
|
||||
|
|
@ -2,6 +2,11 @@
|
|||
import io
|
||||
import os
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
try:
|
||||
import grp
|
||||
POSIX_MODE = True
|
||||
|
|
@ -16,6 +21,33 @@ SYSTEM_SID = 'S-1-5-18'
|
|||
ADMINS_SID = 'S-1-5-32-544'
|
||||
|
||||
|
||||
def assert_elliptic_key(key, curve):
|
||||
"""
|
||||
Asserts that the key at the given path is an EC key using the given curve.
|
||||
:param key: path to key
|
||||
:param curve: name of the expected elliptic curve
|
||||
"""
|
||||
with open(key, 'rb') as file:
|
||||
privkey1 = file.read()
|
||||
|
||||
key = load_pem_private_key(data=privkey1, password=None, backend=default_backend())
|
||||
|
||||
assert isinstance(key, EllipticCurvePrivateKey)
|
||||
assert isinstance(key.curve, curve)
|
||||
|
||||
|
||||
def assert_rsa_key(key):
|
||||
"""
|
||||
Asserts that the key at the given path is an RSA key.
|
||||
:param key: path to key
|
||||
"""
|
||||
with open(key, 'rb') as file:
|
||||
privkey1 = file.read()
|
||||
|
||||
key = load_pem_private_key(data=privkey1, password=None, backend=default_backend())
|
||||
assert isinstance(key, RSAPrivateKey)
|
||||
|
||||
|
||||
def assert_hook_execution(probe_path, probe_content):
|
||||
"""
|
||||
Assert that a certbot hook has been executed
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ import shutil
|
|||
import subprocess
|
||||
import time
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1, SECP384R1
|
||||
from cryptography.x509 import NameOID
|
||||
|
||||
import pytest
|
||||
|
||||
from certbot_integration_tests.certbot_tests import context as certbot_context
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_cert_count_for_lineage
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_elliptic_key
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_rsa_key
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_owner
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_permissions
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_equals_world_read_permissions
|
||||
|
|
@ -289,7 +292,7 @@ def test_renew_with_changed_private_key_complexity(context):
|
|||
assert_cert_count_for_lineage(context.config_dir, certname, 1)
|
||||
|
||||
context.certbot(['renew'])
|
||||
|
||||
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 2)
|
||||
key2 = join(context.config_dir, 'archive', certname, 'privkey2.pem')
|
||||
assert os.stat(key2).st_size > 3000
|
||||
|
|
@ -421,20 +424,93 @@ def test_reuse_key(context):
|
|||
assert len({cert1, cert2, cert3}) == 3
|
||||
|
||||
|
||||
def test_incorrect_key_type(context):
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
context.certbot(['--key-type="failwhale"'])
|
||||
|
||||
|
||||
def test_ecdsa(context):
|
||||
"""Test certificate issuance with ECDSA key."""
|
||||
"""Test issuance for ECDSA CSR based request (legacy supported mode)."""
|
||||
key_path = join(context.workspace, 'privkey-p384.pem')
|
||||
csr_path = join(context.workspace, 'csr-p384.der')
|
||||
cert_path = join(context.workspace, 'cert-p384.pem')
|
||||
chain_path = join(context.workspace, 'chain-p384.pem')
|
||||
|
||||
misc.generate_csr([context.get_domain('ecdsa')], key_path, csr_path, key_type=misc.ECDSA_KEY_TYPE)
|
||||
context.certbot(['auth', '--csr', csr_path, '--cert-path', cert_path, '--chain-path', chain_path])
|
||||
misc.generate_csr(
|
||||
[context.get_domain('ecdsa')],
|
||||
key_path, csr_path,
|
||||
key_type=misc.ECDSA_KEY_TYPE
|
||||
)
|
||||
context.certbot([
|
||||
'auth', '--csr', csr_path, '--cert-path', cert_path,
|
||||
'--chain-path', chain_path,
|
||||
])
|
||||
|
||||
certificate = misc.read_certificate(cert_path)
|
||||
assert 'ASN1 OID: secp384r1' in certificate
|
||||
|
||||
|
||||
def test_default_key_type(context):
|
||||
"""Test default key type is RSA"""
|
||||
certname = context.get_domain('renew')
|
||||
context.certbot([
|
||||
'certonly',
|
||||
'--cert-name', certname, '-d', certname
|
||||
])
|
||||
filename = join(context.config_dir, 'archive/{0}/privkey1.pem').format(certname)
|
||||
assert_rsa_key(filename)
|
||||
|
||||
|
||||
def test_default_curve_type(context):
|
||||
"""test that the curve used when not specifying any is secp256r1"""
|
||||
certname = context.get_domain('renew')
|
||||
context.certbot([
|
||||
'--key-type', 'ecdsa', '--cert-name', certname, '-d', certname
|
||||
])
|
||||
key1 = join(context.config_dir, 'archive/{0}/privkey1.pem'.format(certname))
|
||||
assert_elliptic_key(key1, SECP256R1)
|
||||
|
||||
|
||||
def test_renew_with_ec_keys(context):
|
||||
"""Test proper renew with updated private key complexity."""
|
||||
certname = context.get_domain('renew')
|
||||
context.certbot([
|
||||
'certonly',
|
||||
'--cert-name', certname,
|
||||
'--key-type', 'ecdsa', '--elliptic-curve', 'secp256r1',
|
||||
'--force-renewal', '-d', certname,
|
||||
])
|
||||
|
||||
key1 = join(context.config_dir, "archive", certname, 'privkey1.pem')
|
||||
assert 200 < os.stat(key1).st_size < 250 # ec keys of 256 bits are ~225 bytes
|
||||
assert_elliptic_key(key1, SECP256R1)
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 1)
|
||||
|
||||
context.certbot(['renew', '--elliptic-curve', 'secp384r1'])
|
||||
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 2)
|
||||
key2 = join(context.config_dir, 'archive', certname, 'privkey2.pem')
|
||||
assert_elliptic_key(key2, SECP384R1)
|
||||
assert 280 < os.stat(key2).st_size < 320 # ec keys of 384 bits are ~310 bytes
|
||||
|
||||
# We expect here that the command will fail because without --key-type specified,
|
||||
# Certbot must error out to prevent changing an existing certificate key type,
|
||||
# without explicit user consent (by specifying both --cert-name and --key-type).
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
context.certbot([
|
||||
'certonly',
|
||||
'--force-renewal',
|
||||
'-d', certname
|
||||
])
|
||||
|
||||
# We expect that the previous behavior of requiring both --cert-name and
|
||||
# --key-type to be set to not apply to the renew subcommand.
|
||||
context.certbot(['renew', '--force-renewal', '--key-type', 'rsa'])
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 3)
|
||||
key3 = join(context.config_dir, 'archive', certname, 'privkey3.pem')
|
||||
assert_rsa_key(key3)
|
||||
|
||||
|
||||
def test_ocsp_must_staple(context):
|
||||
"""Test that OCSP Must-Staple is correctly set in the generated certificate."""
|
||||
if context.acme_server == 'pebble':
|
||||
|
|
@ -658,4 +734,4 @@ def test_preferred_chain(context):
|
|||
|
||||
with open(conf_path, 'r') as f:
|
||||
assert 'preferred_chain = {}'.format(requested) in f.read(), \
|
||||
'Expected preferred_chain to be set in renewal config'
|
||||
'Expected preferred_chain to be set in renewal config'
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import subprocess
|
|||
import sys
|
||||
|
||||
from certbot_integration_tests.utils import acme_server as acme_lib
|
||||
from certbot_integration_tests.utils import dns_server as dns_lib
|
||||
from certbot_integration_tests.utils.dns_server import DNSServer
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
|
|
@ -23,6 +25,10 @@ def pytest_addoption(parser):
|
|||
choices=['boulder-v1', 'boulder-v2', 'pebble'],
|
||||
help='select the ACME server to use (boulder-v1, boulder-v2, '
|
||||
'pebble), defaulting to pebble')
|
||||
parser.addoption('--dns-server', default='challtestsrv',
|
||||
choices=['bind', 'challtestsrv'],
|
||||
help='select the DNS server to use (bind, challtestsrv), '
|
||||
'defaulting to challtestsrv')
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
|
@ -32,7 +38,7 @@ def pytest_configure(config):
|
|||
"""
|
||||
if not hasattr(config, 'slaveinput'): # If true, this is the primary node
|
||||
with _print_on_err():
|
||||
config.acme_xdist = _setup_primary_node(config)
|
||||
_setup_primary_node(config)
|
||||
|
||||
|
||||
def pytest_configure_node(node):
|
||||
|
|
@ -41,6 +47,7 @@ def pytest_configure_node(node):
|
|||
:param node: current worker node
|
||||
"""
|
||||
node.slaveinput['acme_xdist'] = node.config.acme_xdist
|
||||
node.slaveinput['dns_xdist'] = node.config.dns_xdist
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -61,12 +68,18 @@ def _print_on_err():
|
|||
def _setup_primary_node(config):
|
||||
"""
|
||||
Setup the environment for integration tests.
|
||||
Will:
|
||||
|
||||
This function will:
|
||||
- check runtime compatibility (Docker, docker-compose, Nginx)
|
||||
- create a temporary workspace and the persistent GIT repositories space
|
||||
- configure and start a DNS server using Docker, if configured
|
||||
- configure and start paralleled ACME CA servers using Docker
|
||||
- transfer ACME CA servers configurations to pytest nodes using env variables
|
||||
:param config: Configuration of the pytest primary node
|
||||
- transfer ACME CA and DNS servers configurations to pytest nodes using env variables
|
||||
|
||||
This function modifies `config` by injecting the ACME CA and DNS server configurations,
|
||||
in addition to cleanup functions for those servers.
|
||||
|
||||
:param config: Configuration of the pytest primary node. Is modified by this function.
|
||||
"""
|
||||
# Check for runtime compatibility: some tools are required to be available in PATH
|
||||
if 'boulder' in config.option.acme_server:
|
||||
|
|
@ -86,11 +99,26 @@ def _setup_primary_node(config):
|
|||
workers = ['primary'] if not config.option.numprocesses\
|
||||
else ['gw{0}'.format(i) for i in range(config.option.numprocesses)]
|
||||
|
||||
# If a non-default DNS server is configured, start it and feed it to the ACME server
|
||||
dns_server = None
|
||||
acme_dns_server = None
|
||||
if config.option.dns_server == 'bind':
|
||||
dns_server = dns_lib.DNSServer(workers)
|
||||
config.add_cleanup(dns_server.stop)
|
||||
print('DNS xdist config:\n{0}'.format(dns_server.dns_xdist))
|
||||
dns_server.start()
|
||||
acme_dns_server = '{}:{}'.format(
|
||||
dns_server.dns_xdist['address'],
|
||||
dns_server.dns_xdist['port']
|
||||
)
|
||||
|
||||
# By calling setup_acme_server we ensure that all necessary acme server instances will be
|
||||
# fully started. This runtime is reflected by the acme_xdist returned.
|
||||
acme_server = acme_lib.ACMEServer(config.option.acme_server, workers)
|
||||
acme_server = acme_lib.ACMEServer(config.option.acme_server, workers,
|
||||
dns_server=acme_dns_server)
|
||||
config.add_cleanup(acme_server.stop)
|
||||
print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist))
|
||||
acme_server.start()
|
||||
|
||||
return acme_server.acme_xdist
|
||||
config.acme_xdist = acme_server.acme_xdist
|
||||
config.dns_xdist = dns_server.dns_xdist if dns_server else None
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""General purpose nginx test configuration generator."""
|
||||
import getpass
|
||||
|
||||
|
|
@ -42,6 +43,8 @@ events {{
|
|||
worker_connections 1024;
|
||||
}}
|
||||
|
||||
# “This comment contains valid Unicode”.
|
||||
|
||||
http {{
|
||||
# Set an array of temp, cache and log file options that will otherwise default to
|
||||
# restricted locations accessible only to root.
|
||||
|
|
@ -51,61 +54,61 @@ http {{
|
|||
#scgi_temp_path {nginx_root}/scgi_temp;
|
||||
#uwsgi_temp_path {nginx_root}/uwsgi_temp;
|
||||
access_log {nginx_root}/error.log;
|
||||
|
||||
|
||||
# This should be turned off in a Virtualbox VM, as it can cause some
|
||||
# interesting issues with data corruption in delivered files.
|
||||
sendfile off;
|
||||
|
||||
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
|
||||
#include /etc/nginx/mime.types;
|
||||
index index.html index.htm index.php;
|
||||
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] $status '
|
||||
'"$request" $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
|
||||
default_type application/octet-stream;
|
||||
|
||||
|
||||
server {{
|
||||
# IPv4.
|
||||
listen {http_port} {default_server};
|
||||
# IPv6.
|
||||
listen [::]:{http_port} {default_server};
|
||||
server_name nginx.{wtf_prefix}.wtf nginx2.{wtf_prefix}.wtf;
|
||||
|
||||
|
||||
root {nginx_webroot};
|
||||
|
||||
|
||||
location / {{
|
||||
# First attempt to serve request as file, then as directory, then fall
|
||||
# back to index.html.
|
||||
try_files $uri $uri/ /index.html;
|
||||
}}
|
||||
}}
|
||||
|
||||
|
||||
server {{
|
||||
listen {http_port};
|
||||
listen [::]:{http_port};
|
||||
server_name nginx3.{wtf_prefix}.wtf;
|
||||
|
||||
|
||||
root {nginx_webroot};
|
||||
|
||||
|
||||
location /.well-known/ {{
|
||||
return 404;
|
||||
}}
|
||||
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}}
|
||||
|
||||
|
||||
server {{
|
||||
listen {other_port};
|
||||
listen [::]:{other_port};
|
||||
server_name nginx4.{wtf_prefix}.wtf nginx5.{wtf_prefix}.wtf;
|
||||
}}
|
||||
|
||||
|
||||
server {{
|
||||
listen {http_port};
|
||||
listen [::]:{http_port};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
from contextlib import contextmanager
|
||||
from pytest import skip
|
||||
from pkg_resources import resource_filename
|
||||
import tempfile
|
||||
|
||||
from certbot_integration_tests.certbot_tests import context as certbot_context
|
||||
from certbot_integration_tests.utils import certbot_call
|
||||
|
||||
|
||||
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
|
||||
"""Integration test context for certbot-dns-rfc2136"""
|
||||
def __init__(self, request):
|
||||
super(IntegrationTestsContext, self).__init__(request)
|
||||
|
||||
self.request = request
|
||||
|
||||
self._dns_xdist = None
|
||||
if hasattr(request.config, 'slaveinput'): # Worker node
|
||||
self._dns_xdist = request.config.slaveinput['dns_xdist']
|
||||
else: # Primary node
|
||||
self._dns_xdist = request.config.dns_xdist
|
||||
|
||||
def certbot_test_rfc2136(self, args):
|
||||
"""
|
||||
Main command to execute certbot using the RFC2136 DNS authenticator.
|
||||
:param list args: list of arguments to pass to Certbot
|
||||
"""
|
||||
command = ['--authenticator', 'dns-rfc2136', '--dns-rfc2136-propagation-seconds', '2']
|
||||
command.extend(args)
|
||||
return certbot_call.certbot_test(
|
||||
command, self.directory_url, self.http_01_port, self.tls_alpn_01_port,
|
||||
self.config_dir, self.workspace, force_renew=True)
|
||||
|
||||
@contextmanager
|
||||
def rfc2136_credentials(self, label='default'):
|
||||
# type: (str) -> str
|
||||
"""
|
||||
Produces the contents of a certbot-dns-rfc2136 credentials file.
|
||||
:param str label: which RFC2136 credential to use
|
||||
:yields: Path to credentials file
|
||||
:rtype: str
|
||||
"""
|
||||
src_file = resource_filename('certbot_integration_tests',
|
||||
'assets/bind-config/rfc2136-credentials-{}.ini.tpl'
|
||||
.format(label))
|
||||
contents = None
|
||||
|
||||
with open(src_file, 'r') as f:
|
||||
contents = f.read().format(
|
||||
server_address=self._dns_xdist['address'],
|
||||
server_port=self._dns_xdist['port']
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile('w+', prefix='rfc2136-creds-{}'.format(label),
|
||||
suffix='.ini', dir=self.workspace) as f:
|
||||
f.write(contents)
|
||||
f.flush()
|
||||
yield f.name
|
||||
|
||||
def skip_if_no_bind9_server(self):
|
||||
"""Skips the test if there was no RFC2136-capable DNS server configured
|
||||
in the test environment"""
|
||||
if not self._dns_xdist:
|
||||
skip('No RFC2136-capable DNS server is configured')
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
"""Module executing integration tests against Certbot with the RFC2136 DNS authenticator."""
|
||||
import pytest
|
||||
|
||||
from certbot_integration_tests.rfc2136_tests import context as rfc2136_context
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def context(request):
|
||||
# Fixture request is a built-in pytest fixture describing current test request.
|
||||
integration_test_context = rfc2136_context.IntegrationTestsContext(request)
|
||||
try:
|
||||
yield integration_test_context
|
||||
finally:
|
||||
integration_test_context.cleanup()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('domain', [('example.com'), ('sub.example.com')])
|
||||
def test_get_certificate(domain, context):
|
||||
context.skip_if_no_bind9_server()
|
||||
|
||||
with context.rfc2136_credentials() as creds:
|
||||
context.certbot_test_rfc2136([
|
||||
'certonly', '--dns-rfc2136-credentials', creds,
|
||||
'-d', domain, '-d', '*.{}'.format(domain)
|
||||
])
|
||||
|
|
@ -2,10 +2,12 @@
|
|||
"""Module to setup an ACME CA server environment able to run multiple tests in parallel"""
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
from os.path import join
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -32,13 +34,14 @@ class ACMEServer(object):
|
|||
ACMEServer is also a context manager, and so can be used to ensure ACME server is started/stopped
|
||||
upon context enter/exit.
|
||||
"""
|
||||
def __init__(self, acme_server, nodes, http_proxy=True, stdout=False):
|
||||
def __init__(self, acme_server, nodes, http_proxy=True, stdout=False, dns_server=None):
|
||||
"""
|
||||
Create an ACMEServer instance.
|
||||
:param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble)
|
||||
:param list nodes: list of node names that will be setup by pytest xdist
|
||||
:param bool http_proxy: if False do not start the HTTP proxy
|
||||
:param bool stdout: if True stream all subprocesses stdout to standard stdout
|
||||
:param str dns_server: if set, Pebble/Boulder will use it to resolve domains
|
||||
"""
|
||||
self._construct_acme_xdist(acme_server, nodes)
|
||||
|
||||
|
|
@ -47,6 +50,7 @@ class ACMEServer(object):
|
|||
self._workspace = tempfile.mkdtemp()
|
||||
self._processes = []
|
||||
self._stdout = sys.stdout if stdout else open(os.devnull, 'w')
|
||||
self._dns_server = dns_server
|
||||
|
||||
def start(self):
|
||||
"""Start the test stack"""
|
||||
|
|
@ -132,18 +136,23 @@ class ACMEServer(object):
|
|||
environ['PEBBLE_AUTHZREUSE'] = '100'
|
||||
environ['PEBBLE_ALTERNATE_ROOTS'] = str(PEBBLE_ALTERNATE_ROOTS)
|
||||
|
||||
if self._dns_server:
|
||||
dns_server = self._dns_server
|
||||
else:
|
||||
dns_server = '127.0.0.1:8053'
|
||||
self._launch_process(
|
||||
[challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT),
|
||||
'-defaultIPv6', '""', '-defaultIPv4', '127.0.0.1', '-http01', '""',
|
||||
'-tlsalpn01', '""', '-https01', '""'])
|
||||
|
||||
self._launch_process(
|
||||
[pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053', '-strict'],
|
||||
[pebble_path, '-config', pebble_config_path, '-dnsserver', dns_server, '-strict'],
|
||||
env=environ)
|
||||
|
||||
self._launch_process(
|
||||
[challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT), '-defaultIPv6', '""',
|
||||
'-defaultIPv4', '127.0.0.1', '-http01', '""', '-tlsalpn01', '""', '-https01', '""'])
|
||||
|
||||
# pebble_ocsp_server is imported here and not at the top of module in order to avoid a useless
|
||||
# ImportError, in the case where cryptography dependency is too old to support ocsp, but
|
||||
# Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is the typical
|
||||
# situation of integration-certbot-oldest tox testenv.
|
||||
# pebble_ocsp_server is imported here and not at the top of module in order to avoid a
|
||||
# useless ImportError, in the case where cryptography dependency is too old to support ocsp,
|
||||
# but Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is the
|
||||
# typical situation of integration-certbot-oldest tox testenv.
|
||||
from certbot_integration_tests.utils import pebble_ocsp_server
|
||||
self._launch_process([sys.executable, pebble_ocsp_server.__file__])
|
||||
|
||||
|
|
@ -167,6 +176,15 @@ class ACMEServer(object):
|
|||
os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'),
|
||||
join(instance_path, 'test/rate-limit-policies.yml'))
|
||||
|
||||
if self._dns_server:
|
||||
# Change Boulder config to use the provided DNS server
|
||||
for suffix in ["", "-remote-a", "-remote-b"]:
|
||||
with open(join(instance_path, 'test/config/va{}.json'.format(suffix)), 'r') as f:
|
||||
config = json.loads(f.read())
|
||||
config['va']['dnsResolvers'] = [self._dns_server]
|
||||
with open(join(instance_path, 'test/config/va{}.json'.format(suffix)), 'w') as f:
|
||||
f.write(json.dumps(config, indent=2, separators=(',', ': ')))
|
||||
|
||||
try:
|
||||
# Launch the Boulder server
|
||||
self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path)
|
||||
|
|
@ -175,10 +193,11 @@ class ACMEServer(object):
|
|||
print('=> Waiting for boulder instance to respond...')
|
||||
misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=300)
|
||||
|
||||
# Configure challtestsrv to answer any A record request with ip of the docker host.
|
||||
response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT),
|
||||
json={'ip': '10.77.77.1'})
|
||||
response.raise_for_status()
|
||||
if not self._dns_server:
|
||||
# Configure challtestsrv to answer any A record request with ip of the docker host.
|
||||
response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT),
|
||||
json={'ip': '10.77.77.1'})
|
||||
response.raise_for_status()
|
||||
except BaseException:
|
||||
# If we failed to set up boulder, print its logs.
|
||||
print('=> Boulder setup failed. Boulder logs are:')
|
||||
|
|
@ -208,14 +227,19 @@ class ACMEServer(object):
|
|||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
server_type = args[0] if args else 'pebble'
|
||||
possible_values = ('pebble', 'boulder-v1', 'boulder-v2')
|
||||
if server_type not in possible_values:
|
||||
raise ValueError('Invalid server value {0}, should be one of {1}'
|
||||
.format(server_type, possible_values))
|
||||
parser = argparse.ArgumentParser(
|
||||
description='CLI tool to start a local instance of Pebble or Boulder CA server.')
|
||||
parser.add_argument('--server-type', '-s',
|
||||
choices=['pebble', 'boulder-v1', 'boulder-v2'], default='pebble',
|
||||
help='type of CA server to start: can be Pebble or Boulder '
|
||||
'(in ACMEv1 or ACMEv2 mode), Pebble is used if not set.')
|
||||
parser.add_argument('--dns-server', '-d',
|
||||
help='specify the DNS server as `IP:PORT` to use by '
|
||||
'Pebble; if not specified, a local mock DNS server will be used to '
|
||||
'resolve domains to localhost.')
|
||||
args = parser.parse_args()
|
||||
|
||||
acme_server = ACMEServer(server_type, [], http_proxy=False, stdout=True)
|
||||
acme_server = ACMEServer(args.server_type, [], http_proxy=False, stdout=True, dns_server=args.dns_server)
|
||||
|
||||
try:
|
||||
with acme_server as acme_xdist:
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_por
|
|||
'--no-verify-ssl',
|
||||
'--http-01-port', str(http_01_port),
|
||||
'--https-port', str(tls_alpn_01_port),
|
||||
'--manual-public-ip-logging-ok',
|
||||
'--config-dir', config_dir,
|
||||
'--work-dir', os.path.join(workspace, 'work'),
|
||||
'--logs-dir', os.path.join(workspace, 'logs'),
|
||||
|
|
|
|||
144
certbot-ci/certbot_integration_tests/utils/dns_server.py
Normal file
144
certbot-ci/certbot_integration_tests/utils/dns_server.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#!/usr/bin/env python
|
||||
"""Module to setup an RFC2136-capable DNS server"""
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import os.path
|
||||
from pkg_resources import resource_filename
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
||||
BIND_DOCKER_IMAGE = 'internetsystemsconsortium/bind9:9.16'
|
||||
BIND_BIND_ADDRESS = ('127.0.0.1', 45953)
|
||||
|
||||
# A TCP DNS message which is a query for '. CH A' transaction ID 0xcb37. This is used
|
||||
# by _wait_until_ready to check that BIND is responding without depending on dnspython.
|
||||
BIND_TEST_QUERY = bytearray.fromhex('0011cb37000000010000000000000000010003')
|
||||
|
||||
|
||||
class DNSServer(object):
|
||||
"""
|
||||
DNSServer configures and handles the lifetime of an RFC2136-capable server.
|
||||
DNServer provides access to the dns_xdist parameter, listing the address and port
|
||||
to use for each pytest node.
|
||||
|
||||
At this time, DNSServer should only be used with a single node, but may be expanded in
|
||||
future to support parallelization (https://github.com/certbot/certbot/issues/8455).
|
||||
"""
|
||||
|
||||
def __init__(self, nodes, show_output=False):
|
||||
"""
|
||||
Create an DNSServer instance.
|
||||
:param list nodes: list of node names that will be setup by pytest xdist
|
||||
:param bool show_output: if True, print the output of the DNS server
|
||||
"""
|
||||
|
||||
self.bind_root = tempfile.mkdtemp()
|
||||
|
||||
self.process = None
|
||||
|
||||
self.dns_xdist = {
|
||||
'address': BIND_BIND_ADDRESS[0],
|
||||
'port': BIND_BIND_ADDRESS[1]
|
||||
}
|
||||
|
||||
# Unfortunately the BIND9 image forces everything to stderr with -g and we can't
|
||||
# modify the verbosity.
|
||||
self._output = sys.stderr if show_output else open(os.devnull, 'w')
|
||||
|
||||
def start(self):
|
||||
"""Start the DNS server"""
|
||||
try:
|
||||
self._configure_bind()
|
||||
self._start_bind()
|
||||
except:
|
||||
self.stop()
|
||||
raise
|
||||
|
||||
def stop(self):
|
||||
"""Stop the DNS server, and clean its resources"""
|
||||
if self.process:
|
||||
try:
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
except BaseException as e:
|
||||
print("BIND9 did not stop cleanly: {}".format(e), file=sys.stderr)
|
||||
|
||||
shutil.rmtree(self.bind_root, ignore_errors=True)
|
||||
|
||||
if self._output != sys.stderr:
|
||||
self._output.close()
|
||||
|
||||
def _configure_bind(self):
|
||||
"""Configure the BIND9 server based on the prebaked configuration"""
|
||||
bind_conf_src = resource_filename('certbot_integration_tests', 'assets/bind-config')
|
||||
for dir in ('conf', 'zones'):
|
||||
shutil.copytree(os.path.join(bind_conf_src, dir), os.path.join(self.bind_root, dir))
|
||||
|
||||
def _start_bind(self):
|
||||
"""Launch the BIND9 server as a Docker container"""
|
||||
addr_str = '{}:{}'.format(BIND_BIND_ADDRESS[0], BIND_BIND_ADDRESS[1])
|
||||
self.process = subprocess.Popen([
|
||||
'docker', 'run', '--rm',
|
||||
'-p', '{}:53/udp'.format(addr_str),
|
||||
'-p', '{}:53/tcp'.format(addr_str),
|
||||
'-v', '{}/conf:/etc/bind'.format(self.bind_root),
|
||||
'-v', '{}/zones:/var/lib/bind'.format(self.bind_root),
|
||||
BIND_DOCKER_IMAGE
|
||||
], stdout=self._output, stderr=self._output)
|
||||
|
||||
if self.process.poll():
|
||||
raise("BIND9 server stopped unexpectedly")
|
||||
|
||||
try:
|
||||
self._wait_until_ready()
|
||||
except:
|
||||
# The container might be running even if we think it isn't
|
||||
self.stop()
|
||||
raise
|
||||
|
||||
def _wait_until_ready(self, attempts=30):
|
||||
# type: (int) -> None
|
||||
"""
|
||||
Polls the DNS server over TCP until it gets a response, or until
|
||||
it runs out of attempts and raises a ValueError.
|
||||
The DNS response message must match the txn_id of the DNS query message,
|
||||
but otherwise the contents are ignored.
|
||||
:param int attempts: The number of attempts to make.
|
||||
"""
|
||||
for _ in range(attempts):
|
||||
if self.process.poll():
|
||||
raise ValueError('BIND9 server stopped unexpectedly')
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(5.0)
|
||||
try:
|
||||
sock.connect(BIND_BIND_ADDRESS)
|
||||
sock.sendall(BIND_TEST_QUERY)
|
||||
buf = sock.recv(1024)
|
||||
# We should receive a DNS message with the same tx_id
|
||||
if buf and len(buf) > 4 and buf[2:4] == BIND_TEST_QUERY[2:4]:
|
||||
return
|
||||
# If we got a response but it wasn't the one we wanted, wait a little
|
||||
time.sleep(1)
|
||||
except:
|
||||
# If there was a network error, wait a little
|
||||
time.sleep(1)
|
||||
pass
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
raise ValueError(
|
||||
'Gave up waiting for DNS server {} to respond'.format(BIND_BIND_ADDRESS))
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
return self.dns_xdist
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.stop()
|
||||
|
|
@ -280,7 +280,11 @@ def load_sample_data_path(workspace):
|
|||
|
||||
if os.name == 'nt':
|
||||
# Fix the symlinks on Windows if GIT is not configured to create them upon checkout
|
||||
for lineage in ['a.encryption-example.com', 'b.encryption-example.com']:
|
||||
for lineage in [
|
||||
'a.encryption-example.com',
|
||||
'b.encryption-example.com',
|
||||
'c.encryption-example.com',
|
||||
]:
|
||||
current_live = os.path.join(copied, 'live', lineage)
|
||||
for name in os.listdir(current_live):
|
||||
if name != 'README':
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ See https://docs.pytest.org/en/latest/reference.html#hook-reference
|
|||
from __future__ import print_function
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
|
@ -50,6 +50,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -64,6 +64,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -74,6 +74,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
@ -62,6 +62,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -85,9 +85,13 @@ class _GoogleClient(object):
|
|||
|
||||
scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite']
|
||||
if account_json is not None:
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_name(account_json, scopes)
|
||||
with open(account_json) as account:
|
||||
self.project_id = json.load(account)['project_id']
|
||||
try:
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_name(account_json, scopes)
|
||||
with open(account_json) as account:
|
||||
self.project_id = json.load(account)['project_id']
|
||||
except Exception as e:
|
||||
raise errors.PluginError(
|
||||
"Error parsing credentials file '{}': {}".format(account_json, e))
|
||||
else:
|
||||
credentials = None
|
||||
self.project_id = self.get_project_id()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -66,6 +66,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -107,6 +107,17 @@ class GoogleClientTest(unittest.TestCase):
|
|||
self.assertFalse(credential_mock.called)
|
||||
self.assertTrue(get_project_id_mock.called)
|
||||
|
||||
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
|
||||
def test_client_bad_credentials_file(self, credential_mock):
|
||||
credential_mock.side_effect = ValueError('Some exception buried in oauth2client')
|
||||
with self.assertRaises(errors.PluginError) as cm:
|
||||
self._setUp_client_with_mock([])
|
||||
self.assertEqual(
|
||||
str(cm.exception),
|
||||
"Error parsing credentials file '/not/a/real/path.json': "
|
||||
"Some exception buried in oauth2client"
|
||||
)
|
||||
|
||||
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
|
||||
@mock.patch('certbot_dns_google._internal.dns_google.open',
|
||||
mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
@ -62,6 +62,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
# type: ignore
|
||||
# pylint: disable=no-member
|
||||
# Many attributes of dnspython are now dynamically defined which causes both
|
||||
# mypy and pylint to error about accessing attributes they think do not exist.
|
||||
# This is the case even in up-to-date versions of mypy and pylint which as of
|
||||
# writing this are 0.790 and 2.6.0 respectively. This problem may be fixed in
|
||||
# dnspython 2.1.0. See https://github.com/rthalley/dnspython/issues/598. For
|
||||
# now, let's disable these checks. This is done at the very top of the file
|
||||
# like this because "type: ignore" must be the first line in the file to be
|
||||
# respected by mypy.
|
||||
"""DNS Authenticator using RFC 2136 Dynamic Updates."""
|
||||
import logging
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -63,6 +63,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
# _find_domain | pylint: disable=protected-access
|
||||
domain = self.rfc2136_client._find_domain('foo.bar.'+DOMAIN)
|
||||
|
||||
self.assertTrue(domain == DOMAIN)
|
||||
self.assertEqual(domain, DOMAIN)
|
||||
|
||||
def test_find_domain_wraps_errors(self):
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -58,6 +58,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
@ -62,6 +62,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""A class that performs HTTP-01 challenges for Nginx"""
|
||||
|
||||
import io
|
||||
import logging
|
||||
|
||||
from acme import challenges
|
||||
|
|
@ -102,7 +103,7 @@ class NginxHttp01(common.ChallengePerformer):
|
|||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
with open(self.challenge_conf, "w") as new_conf:
|
||||
with io.open(self.challenge_conf, "w", encoding="utf-8") as new_conf:
|
||||
nginxparser.dump(config, new_conf)
|
||||
|
||||
def _default_listen_addresses(self):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from pyparsing import stringEnd
|
|||
from pyparsing import White
|
||||
from pyparsing import ZeroOrMore
|
||||
import six
|
||||
from acme.magic_typing import IO, Any # pylint: disable=unused-import
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -130,26 +131,27 @@ def load(_file):
|
|||
|
||||
|
||||
def dumps(blocks):
|
||||
"""Dump to a string.
|
||||
# type: (UnspacedList) -> six.text_type
|
||||
"""Dump to a Unicode string.
|
||||
|
||||
:param UnspacedList block: The parsed tree
|
||||
:param int indentation: The number of spaces to indent
|
||||
:rtype: str
|
||||
:rtype: six.text_type
|
||||
|
||||
"""
|
||||
return str(RawNginxDumper(blocks.spaced))
|
||||
return six.text_type(RawNginxDumper(blocks.spaced))
|
||||
|
||||
|
||||
def dump(blocks, _file):
|
||||
# type: (UnspacedList, IO[Any]) -> None
|
||||
"""Dump to a file.
|
||||
|
||||
:param UnspacedList block: The parsed tree
|
||||
:param file _file: The file to dump to
|
||||
:param int indentation: The number of spaces to indent
|
||||
:rtype: NoneType
|
||||
:param IO[Any] _file: The file stream to dump to. It must be opened with
|
||||
Unicode encoding.
|
||||
:rtype: None
|
||||
|
||||
"""
|
||||
return _file.write(dumps(blocks))
|
||||
_file.write(dumps(blocks))
|
||||
|
||||
|
||||
spacey = lambda x: (isinstance(x, six.string_types) and x.isspace()) or x == ''
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ class NginxParser(object):
|
|||
continue
|
||||
out = nginxparser.dumps(tree)
|
||||
logger.debug('Writing nginx conf tree to %s:\n%s', filename, out)
|
||||
with open(filename, 'w') as _file:
|
||||
with io.open(filename, 'w', encoding='utf-8') as _file:
|
||||
_file.write(out)
|
||||
|
||||
except IOError:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from setuptools import __version__ as setuptools_version
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.10.0.dev0'
|
||||
version = '1.11.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
@ -49,6 +49,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
|
|
@ -842,7 +842,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
self.config.recovery_routine()
|
||||
self.config.revert_challenge_config()
|
||||
self.config.rollback_checkpoints()
|
||||
self.assertTrue(mock_parser_load.call_count == 3)
|
||||
self.assertEqual(mock_parser_load.call_count, 3)
|
||||
|
||||
def test_choose_vhosts_wildcard(self):
|
||||
# pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class AddrTest(unittest.TestCase):
|
|||
new_addr1 = Addr.fromstring("192.168.1.1 spdy")
|
||||
self.assertEqual(self.addr1, new_addr1)
|
||||
self.assertNotEqual(self.addr1, self.addr2)
|
||||
self.assertFalse(self.addr1 == 3333)
|
||||
self.assertNotEqual(self.addr1, 3333)
|
||||
|
||||
def test_equivalent_any_addresses(self):
|
||||
from certbot_nginx._internal.obj import Addr
|
||||
|
|
@ -168,7 +168,7 @@ class VirtualHostTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(vhost1b, self.vhost1)
|
||||
self.assertEqual(str(vhost1b), str(self.vhost1))
|
||||
self.assertFalse(vhost1b == 1234)
|
||||
self.assertNotEqual(vhost1b, 1234)
|
||||
|
||||
def test_str(self):
|
||||
stringified = '\n'.join(['file: filep', 'addrs: localhost',
|
||||
|
|
|
|||
|
|
@ -492,6 +492,14 @@ class NginxParserTest(util.NginxTest):
|
|||
self.assertEqual(['server'], parsed[0][2][0])
|
||||
self.assertEqual(['listen', '80'], parsed[0][2][1][3])
|
||||
|
||||
def test_valid_unicode_roundtrip(self):
|
||||
"""This tests the parser's ability to load and save a config containing Unicode"""
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
nparser._parse_files(
|
||||
nparser.abs_path('valid_unicode_comments.conf')
|
||||
) # pylint: disable=protected-access
|
||||
nparser.filedump(lazy=False)
|
||||
|
||||
def test_invalid_unicode_characters(self):
|
||||
with self.assertLogs() as log:
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
|
|
|
|||
|
|
@ -2,24 +2,57 @@
|
|||
|
||||
Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## 1.10.0 - master
|
||||
## 1.11.0 - master
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
|
||||
### Changed
|
||||
|
||||
* We deprecated support for Python 2 in Certbot and its ACME library.
|
||||
Support for Python 2 will be removed in the next planned release of Certbot.
|
||||
* certbot-auto was deprecated on all systems.
|
||||
|
||||
### Fixed
|
||||
|
||||
* The Certbot snap no longer loads packages installed via `pip install --user`. This
|
||||
was unintended and DNS plugins should be installed via `snap` instead.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 1.10.1 - 2020-12-03
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed a bug in `certbot.util.add_deprecated_argument` that caused the
|
||||
deprecated `--manual-public-ip-logging-ok` flag to crash Certbot in some
|
||||
scenarios.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 1.10.0 - 2020-12-01
|
||||
|
||||
### Added
|
||||
|
||||
* Added timeout to DNS query function calls for dns-rfc2136 plugin.
|
||||
* Confirmation when deleting certificates
|
||||
*
|
||||
* CLI flag `--key-type` has been added to specify 'rsa' or 'ecdsa' (default 'rsa').
|
||||
* CLI flag `--elliptic-curve` has been added which takes an NIST/SECG elliptic curve. Any of
|
||||
`secp256r1`, `secp384r1` and `secp521r1` are accepted values.
|
||||
* The command `certbot certficates` lists the which type of the private key that was used
|
||||
for the private key.
|
||||
* Support for Python 3.9 was added to Certbot and all of its components.
|
||||
|
||||
### Changed
|
||||
|
||||
* certbot-auto was deprecated on Debian based systems.
|
||||
* CLI flag `--manual-public-ip-logging-ok` is now a no-op, generates a
|
||||
deprecation warning, and will be removed in a future release.
|
||||
*
|
||||
|
||||
### Fixed
|
||||
|
||||
*
|
||||
* Fixed a Unicode-related crash in the nginx plugin when running under Python 2.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
|
|
@ -55,7 +88,7 @@ More details about these changes can be found on our GitHub repo.
|
|||
|
||||
### Added
|
||||
|
||||
* Added the ability to remove email and phone contact information from an account
|
||||
* Added the ability to remove email and phone contact information from an account
|
||||
using `update_account --register-unsafely-without-email`
|
||||
|
||||
### Changed
|
||||
|
|
@ -67,7 +100,7 @@ More details about these changes can be found on our GitHub repo.
|
|||
* The problem causing the Apache plugin in the Certbot snap on ARM systems to
|
||||
fail to load the Augeas library it depends on has been fixed.
|
||||
* The `acme` library can now tell the ACME server to clear contact information by passing an empty
|
||||
`tuple` to the `contact` field of a `Registration` message.
|
||||
`tuple` to the `contact` field of a `Registration` message.
|
||||
* Fixed the `*** stack smashing detected ***` error in the Certbot snap on some systems.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ Current Features
|
|||
* Can get domain-validated (DV) certificates.
|
||||
* Can revoke certificates.
|
||||
* Adjustable RSA key bit-length (2048 (default), 4096, ...).
|
||||
* Adjustable `EC <https://en.wikipedia.org/wiki/Elliptic-curve_cryptography>`_
|
||||
key (`secp256r1` (default), `secp384r1`, `secp521r1`).
|
||||
* Can optionally install a http -> https redirect, so your site effectively
|
||||
runs https only (Apache only)
|
||||
* Fully automated.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
"""Certbot client."""
|
||||
import warnings
|
||||
import sys
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '1.10.0.dev0'
|
||||
__version__ = '1.11.0.dev0'
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
warnings.warn(
|
||||
"Python 2 support will be dropped in the next release of Certbot. "
|
||||
"Please upgrade your Python version.",
|
||||
PendingDeprecationWarning,
|
||||
) # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ def rename_lineage(config):
|
|||
disp.notification("Successfully renamed {0} to {1}."
|
||||
.format(certname, new_certname), pause=False)
|
||||
|
||||
|
||||
def certificates(config):
|
||||
"""Display information about certs configured with Certbot
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ def certificates(config):
|
|||
# Describe all the certs
|
||||
_describe_certs(config, parsed_certs, parse_failures)
|
||||
|
||||
|
||||
def delete(config):
|
||||
"""Delete Certbot files associated with a certificate lineage."""
|
||||
certnames = get_certnames(config, "delete", allow_multiple=True)
|
||||
|
|
@ -123,11 +125,13 @@ def lineage_for_certname(cli_config, certname):
|
|||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
return None
|
||||
|
||||
|
||||
def domains_for_certname(config, certname):
|
||||
"""Find the domains in the cert with name certname."""
|
||||
lineage = lineage_for_certname(config, certname)
|
||||
return lineage.names() if lineage else None
|
||||
|
||||
|
||||
def find_duplicative_certs(config, domains):
|
||||
"""Find existing certs that match the given domain names.
|
||||
|
||||
|
|
@ -172,6 +176,7 @@ def find_duplicative_certs(config, domains):
|
|||
|
||||
return _search_lineages(config, update_certs_for_domain_matches, (None, None))
|
||||
|
||||
|
||||
def _archive_files(candidate_lineage, filetype):
|
||||
""" In order to match things like:
|
||||
/etc/letsencrypt/archive/example.com/chain1.pem.
|
||||
|
|
@ -193,6 +198,7 @@ def _archive_files(candidate_lineage, filetype):
|
|||
return pattern
|
||||
return None
|
||||
|
||||
|
||||
def _acceptable_matches():
|
||||
""" Generates the list that's passed to match_and_check_overlaps. Is its own function to
|
||||
make unit testing easier.
|
||||
|
|
@ -203,6 +209,7 @@ def _acceptable_matches():
|
|||
return [lambda x: x.fullchain_path, lambda x: x.cert_path,
|
||||
lambda x: _archive_files(x, "cert"), lambda x: _archive_files(x, "fullchain")]
|
||||
|
||||
|
||||
def cert_path_to_lineage(cli_config):
|
||||
""" If config.cert_path is defined, try to find an appropriate value for config.certname.
|
||||
|
||||
|
|
@ -219,6 +226,7 @@ def cert_path_to_lineage(cli_config):
|
|||
lambda x: cli_config.cert_path[0], lambda x: x.lineagename)
|
||||
return match[0]
|
||||
|
||||
|
||||
def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func):
|
||||
""" Searches through all lineages for a match, and checks for duplicates.
|
||||
If a duplicate is found, an error is raised, as performing operations on lineages
|
||||
|
|
@ -284,20 +292,23 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False):
|
|||
|
||||
valid_string = "{0} ({1})".format(cert.target_expiry, status)
|
||||
serial = format(crypto_util.get_serial_from_cert(cert.cert_path), 'x')
|
||||
certinfo.append(" Certificate Name: {0}\n"
|
||||
" Serial Number: {1}\n"
|
||||
" Domains: {2}\n"
|
||||
" Expiry Date: {3}\n"
|
||||
" Certificate Path: {4}\n"
|
||||
" Private Key Path: {5}".format(
|
||||
certinfo.append(" Certificate Name: {}\n"
|
||||
" Serial Number: {}\n"
|
||||
" Key Type: {}\n"
|
||||
" Domains: {}\n"
|
||||
" Expiry Date: {}\n"
|
||||
" Certificate Path: {}\n"
|
||||
" Private Key Path: {}".format(
|
||||
cert.lineagename,
|
||||
serial,
|
||||
cert.private_key_type,
|
||||
" ".join(cert.names()),
|
||||
valid_string,
|
||||
cert.fullchain,
|
||||
cert.privkey))
|
||||
return "".join(certinfo)
|
||||
|
||||
|
||||
def get_certnames(config, verb, allow_multiple=False, custom_prompt=None):
|
||||
"""Get certname from flag, interactively, or error out.
|
||||
"""
|
||||
|
|
@ -337,10 +348,12 @@ def get_certnames(config, verb, allow_multiple=False, custom_prompt=None):
|
|||
# Private Helpers
|
||||
###################
|
||||
|
||||
|
||||
def _report_lines(msgs):
|
||||
"""Format a results report for a category of single-line renewal outcomes"""
|
||||
return " " + "\n ".join(str(msg) for msg in msgs)
|
||||
|
||||
|
||||
def _report_human_readable(config, parsed_certs):
|
||||
"""Format a results report for a parsed cert"""
|
||||
certinfo = []
|
||||
|
|
@ -348,6 +361,7 @@ def _report_human_readable(config, parsed_certs):
|
|||
certinfo.append(human_readable_cert_info(config, cert))
|
||||
return "\n".join(certinfo)
|
||||
|
||||
|
||||
def _describe_certs(config, parsed_certs, parse_failures):
|
||||
"""Print information about the certs we know about"""
|
||||
out = [] # type: List[str]
|
||||
|
|
@ -369,6 +383,7 @@ def _describe_certs(config, parsed_certs, parse_failures):
|
|||
disp = zope.component.getUtility(interfaces.IDisplay)
|
||||
disp.notification("\n".join(out), pause=False, wrap=False)
|
||||
|
||||
|
||||
def _search_lineages(cli_config, func, initial_rv, *args):
|
||||
"""Iterate func over unbroken lineages, allowing custom return conditions.
|
||||
|
||||
|
|
|
|||
|
|
@ -313,6 +313,16 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
|
|||
helpful.add(
|
||||
"security", "--rsa-key-size", type=int, metavar="N",
|
||||
default=flag_default("rsa_key_size"), help=config_help("rsa_key_size"))
|
||||
helpful.add(
|
||||
"security", "--key-type", choices=['rsa', 'ecdsa'], type=str,
|
||||
default=flag_default("key_type"), help=config_help("key_type"))
|
||||
helpful.add(
|
||||
"security", "--elliptic-curve", type=str, choices=[
|
||||
'secp256r1',
|
||||
'secp384r1',
|
||||
'secp521r1',
|
||||
], metavar="N",
|
||||
default=flag_default("elliptic_curve"), help=config_help("elliptic_curve"))
|
||||
helpful.add(
|
||||
"security", "--must-staple", action="store_true",
|
||||
dest="must_staple", default=flag_default("must_staple"),
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
from __future__ import print_function
|
||||
import argparse
|
||||
import copy
|
||||
import functools
|
||||
import glob
|
||||
import sys
|
||||
|
||||
import configargparse
|
||||
import six
|
||||
import zope.component
|
||||
|
|
@ -230,6 +232,10 @@ class HelpfulArgumentParser(object):
|
|||
raise errors.Error(
|
||||
"Parameters --hsts and --auto-hsts cannot be used simultaneously.")
|
||||
|
||||
if isinstance(parsed_args.key_type, list) and len(parsed_args.key_type) > 1:
|
||||
raise errors.Error(
|
||||
"Only *one* --key-type type may be provided at this time.")
|
||||
|
||||
return parsed_args
|
||||
|
||||
def set_test_server(self, parsed_args):
|
||||
|
|
@ -352,6 +358,18 @@ class HelpfulArgumentParser(object):
|
|||
:param dict **kwargs: various argparse settings for this argument
|
||||
|
||||
"""
|
||||
action = kwargs.get("action")
|
||||
if action is util.DeprecatedArgumentAction:
|
||||
# If the argument is deprecated through
|
||||
# certbot.util.add_deprecated_argument, it is not shown in the help
|
||||
# output and any value given to the argument is thrown away during
|
||||
# argument parsing. Because of this, we handle this case early
|
||||
# skipping putting the argument in different help topics and
|
||||
# handling default detection since these actions aren't needed and
|
||||
# can cause bugs like
|
||||
# https://github.com/certbot/certbot/issues/8495.
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
return
|
||||
|
||||
if isinstance(topics, list):
|
||||
# if this flag can be listed in multiple sections, try to pick the one
|
||||
|
|
@ -406,8 +424,22 @@ class HelpfulArgumentParser(object):
|
|||
:param int nargs: Number of arguments the option takes.
|
||||
|
||||
"""
|
||||
util.add_deprecated_argument(
|
||||
self.parser.add_argument, argument_name, num_args)
|
||||
# certbot.util.add_deprecated_argument expects the normal add_argument
|
||||
# interface provided by argparse. This is what is given including when
|
||||
# certbot.util.add_deprecated_argument is used by plugins, however, in
|
||||
# that case the first argument to certbot.util.add_deprecated_argument
|
||||
# is certbot._internal.cli.HelpfulArgumentGroup.add_argument which
|
||||
# internally calls the add method of this class.
|
||||
#
|
||||
# The difference between the add method of this class and the standard
|
||||
# argparse add_argument method caused a bug in the past (see
|
||||
# https://github.com/certbot/certbot/issues/8495) so we use the same
|
||||
# code path here for consistency and to ensure it works. To do that, we
|
||||
# wrap the add method in a similar way to
|
||||
# HelpfulArgumentGroup.add_argument by providing a help topic (which in
|
||||
# this case is set to None).
|
||||
add_func = functools.partial(self.add, None)
|
||||
util.add_deprecated_argument(add_func, argument_name, num_args)
|
||||
|
||||
def add_group(self, topic, verbs=(), **kwargs):
|
||||
"""Create a new argument group.
|
||||
|
|
|
|||
|
|
@ -312,7 +312,6 @@ class Client(object):
|
|||
:rtype: tuple
|
||||
|
||||
"""
|
||||
|
||||
# We need to determine the key path, key PEM data, CSR path,
|
||||
# and CSR PEM data. For a dry run, the paths are None because
|
||||
# they aren't permanently saved to disk. For a lineage with
|
||||
|
|
@ -335,16 +334,41 @@ class Client(object):
|
|||
# The key is set to None here but will be created below.
|
||||
key = None
|
||||
|
||||
key_size = self.config.rsa_key_size
|
||||
elliptic_curve = None
|
||||
|
||||
# key-type defaults to a list, but we are only handling 1 currently
|
||||
if isinstance(self.config.key_type, list):
|
||||
self.config.key_type = self.config.key_type[0]
|
||||
if self.config.elliptic_curve and self.config.key_type == 'ecdsa':
|
||||
elliptic_curve = self.config.elliptic_curve
|
||||
self.config.auth_chain_path = "./chain-ecdsa.pem"
|
||||
self.config.auth_cert_path = "./cert-ecdsa.pem"
|
||||
self.config.key_path = "./key-ecdsa.pem"
|
||||
elif self.config.rsa_key_size and self.config.key_type.lower() == 'rsa':
|
||||
key_size = self.config.rsa_key_size
|
||||
|
||||
# Create CSR from names
|
||||
if self.config.dry_run:
|
||||
key = key or util.Key(file=None,
|
||||
pem=crypto_util.make_key(self.config.rsa_key_size))
|
||||
key = key or util.Key(
|
||||
file=None,
|
||||
pem=crypto_util.make_key(
|
||||
bits=key_size,
|
||||
elliptic_curve=elliptic_curve,
|
||||
key_type=self.config.key_type,
|
||||
|
||||
),
|
||||
)
|
||||
csr = util.CSR(file=None, form="pem",
|
||||
data=acme_crypto_util.make_csr(
|
||||
key.pem, domains, self.config.must_staple))
|
||||
else:
|
||||
key = key or crypto_util.init_save_key(self.config.rsa_key_size,
|
||||
self.config.key_dir)
|
||||
key = key or crypto_util.init_save_key(
|
||||
key_size=key_size,
|
||||
key_dir=self.config.key_dir,
|
||||
key_type=self.config.key_type,
|
||||
elliptic_curve=elliptic_curve,
|
||||
)
|
||||
csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)
|
||||
|
||||
orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ CLI_DEFAULTS = dict(
|
|||
https_port=443,
|
||||
break_my_certs=False,
|
||||
rsa_key_size=2048,
|
||||
elliptic_curve="secp256r1",
|
||||
key_type="rsa",
|
||||
must_staple=False,
|
||||
redirect=None,
|
||||
auto_hsts=False,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ from __future__ import print_function
|
|||
import functools
|
||||
import logging.handlers
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import configobj
|
||||
import josepy as jose
|
||||
import zope.component
|
||||
|
||||
from acme import errors as acme_errors
|
||||
from acme.magic_typing import Union, Iterable, Optional # pylint: disable=unused-import
|
||||
from acme.magic_typing import Union, Iterable, Optional, List, Tuple # pylint: disable=unused-import
|
||||
import certbot
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
|
|
@ -113,12 +114,24 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
|
|||
if lineage is not None:
|
||||
# Renewal, where we already know the specific lineage we're
|
||||
# interested in
|
||||
logger.info("Renewing an existing certificate")
|
||||
display_util.notify(
|
||||
"{action} for {domains}".format(
|
||||
action="Simulating renewal of an existing certificate"
|
||||
if config.dry_run else "Renewing an existing certificate",
|
||||
domains=display_util.summarize_domain_list(domains or lineage.names())
|
||||
)
|
||||
)
|
||||
renewal.renew_cert(config, domains, le_client, lineage)
|
||||
else:
|
||||
# TREAT AS NEW REQUEST
|
||||
assert domains is not None
|
||||
logger.info("Obtaining a new certificate")
|
||||
display_util.notify(
|
||||
"{action} for {domains}".format(
|
||||
action="Simulating a certificate request" if config.dry_run else
|
||||
"Requesting a certificate",
|
||||
domains=display_util.summarize_domain_list(domains)
|
||||
)
|
||||
)
|
||||
lineage = le_client.obtain_and_enroll_certificate(domains, certname)
|
||||
if lineage is False:
|
||||
raise errors.Error("Certificate could not be obtained")
|
||||
|
|
@ -130,7 +143,33 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
|
|||
return lineage
|
||||
|
||||
|
||||
def _handle_subset_cert_request(config, domains, cert):
|
||||
def _handle_unexpected_key_type_migration(config, cert):
|
||||
# type: (configuration.NamespaceConfig, storage.RenewableCert) -> None
|
||||
"""
|
||||
This function ensures that the user will not implicitly migrate an existing key
|
||||
from one type to another in the situation where a certificate for that lineage
|
||||
already exist and they have not provided explicitly --key-type and --cert-name.
|
||||
:param config: Current configuration provided by the client
|
||||
:param cert: Matching certificate that could be renewed
|
||||
"""
|
||||
if not cli.set_by_cli("key_type") or not cli.set_by_cli("certname"):
|
||||
|
||||
new_key_type = config.key_type.upper()
|
||||
cur_key_type = cert.private_key_type.upper()
|
||||
|
||||
if new_key_type != cur_key_type:
|
||||
msg = ('Are you trying to change the key type of the certificate named {0} '
|
||||
'from {1} to {2}? Please provide both --cert-name and --key-type on '
|
||||
'the command line confirm the change you are trying to make.')
|
||||
msg = msg.format(cert.lineagename, cur_key_type, new_key_type)
|
||||
raise errors.Error(msg)
|
||||
|
||||
|
||||
def _handle_subset_cert_request(config, # type: configuration.NamespaceConfig
|
||||
domains, # type: List[str]
|
||||
cert # type: storage.RenewableCert
|
||||
):
|
||||
# type: (...) -> Tuple[str, Optional[storage.RenewableCert]]
|
||||
"""Figure out what to do if a previous cert had a subset of the names now requested
|
||||
|
||||
:param config: Configuration object
|
||||
|
|
@ -147,6 +186,8 @@ def _handle_subset_cert_request(config, domains, cert):
|
|||
:rtype: `tuple` of `str`
|
||||
|
||||
"""
|
||||
_handle_unexpected_key_type_migration(config, cert)
|
||||
|
||||
existing = ", ".join(cert.names())
|
||||
question = (
|
||||
"You have an existing certificate that contains a portion of "
|
||||
|
|
@ -175,7 +216,10 @@ def _handle_subset_cert_request(config, domains, cert):
|
|||
raise errors.Error(USER_CANCELLED)
|
||||
|
||||
|
||||
def _handle_identical_cert_request(config, lineage):
|
||||
def _handle_identical_cert_request(config, # type: configuration.NamespaceConfig
|
||||
lineage, # type: storage.RenewableCert
|
||||
):
|
||||
# type: (...) -> Tuple[str, Optional[storage.RenewableCert]]
|
||||
"""Figure out what to do if a lineage has the same names as a previously obtained one
|
||||
|
||||
:param config: Configuration object
|
||||
|
|
@ -189,6 +233,8 @@ def _handle_identical_cert_request(config, lineage):
|
|||
:rtype: `tuple` of `str`
|
||||
|
||||
"""
|
||||
_handle_unexpected_key_type_migration(config, lineage)
|
||||
|
||||
if not lineage.ensure_deployed():
|
||||
return "reinstall", lineage
|
||||
if renewal.should_renew(config, lineage):
|
||||
|
|
@ -264,6 +310,7 @@ def _find_lineage_for_domains(config, domains):
|
|||
return _handle_subset_cert_request(config, domains, subset_names_cert)
|
||||
return None, None
|
||||
|
||||
|
||||
def _find_cert(config, domains, certname):
|
||||
"""Finds an existing certificate object given domains and/or a certificate name.
|
||||
|
||||
|
|
@ -287,7 +334,12 @@ def _find_cert(config, domains, certname):
|
|||
logger.info("Keeping the existing certificate")
|
||||
return (action != "reinstall"), lineage
|
||||
|
||||
def _find_lineage_for_domains_and_certname(config, domains, certname):
|
||||
|
||||
def _find_lineage_for_domains_and_certname(config, # type: configuration.NamespaceConfig
|
||||
domains, # type: List[str]
|
||||
certname # type: str
|
||||
):
|
||||
# type: (...) -> Tuple[str, Optional[storage.RenewableCert]]
|
||||
"""Find appropriate lineage based on given domains and/or certname.
|
||||
|
||||
:param config: Configuration object
|
||||
|
|
@ -314,8 +366,9 @@ def _find_lineage_for_domains_and_certname(config, domains, certname):
|
|||
if lineage:
|
||||
if domains:
|
||||
if set(cert_manager.domains_for_certname(config, certname)) != set(domains):
|
||||
_handle_unexpected_key_type_migration(config, lineage)
|
||||
_ask_user_to_confirm_new_names(config, domains, certname,
|
||||
lineage.names()) # raises if no
|
||||
lineage.names()) # raises if no
|
||||
return "renew", lineage
|
||||
# unnecessarily specified domains or no domains specified
|
||||
return _handle_identical_cert_request(config, lineage)
|
||||
|
|
@ -384,6 +437,7 @@ def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains):
|
|||
if not obj.yesno(msg, "Update cert", "Cancel", default=True):
|
||||
raise errors.ConfigurationError("Specified mismatched cert name and domains.")
|
||||
|
||||
|
||||
def _find_domains_or_certname(config, installer, question=None):
|
||||
"""Retrieve domains and certname from config or user input.
|
||||
|
||||
|
|
@ -1002,6 +1056,7 @@ def delete(config, unused_plugins):
|
|||
"""
|
||||
cert_manager.delete(config)
|
||||
|
||||
|
||||
def certificates(config, unused_plugins):
|
||||
"""Display information about certs configured with Certbot
|
||||
|
||||
|
|
@ -1017,6 +1072,7 @@ def certificates(config, unused_plugins):
|
|||
"""
|
||||
cert_manager.certificates(config)
|
||||
|
||||
|
||||
# TODO: coop with renewal config
|
||||
def revoke(config, unused_plugins):
|
||||
"""Revoke a previously obtained certificate.
|
||||
|
|
@ -1149,6 +1205,7 @@ def _csr_get_and_save_cert(config, le_client):
|
|||
os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path))
|
||||
return cert_path, fullchain_path
|
||||
|
||||
|
||||
def renew_cert(config, plugins, lineage):
|
||||
"""Renew & save an existing cert. Do not install it.
|
||||
|
||||
|
|
@ -1346,6 +1403,13 @@ def main(cli_args=None):
|
|||
if config.func != plugins_cmd: # pylint: disable=comparison-with-callable
|
||||
raise
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
warnings.warn(
|
||||
"Python 2 support will be dropped in the next release of Certbot. "
|
||||
"Please upgrade your Python version.",
|
||||
PendingDeprecationWarning,
|
||||
) # pragma: no cover
|
||||
|
||||
set_displayer(config)
|
||||
|
||||
# Reporter
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""Null plugin."""
|
||||
import logging
|
||||
|
||||
import zope.component
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import time
|
|||
import traceback
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
import OpenSSL
|
||||
import six
|
||||
|
|
@ -19,6 +19,7 @@ import zope.component
|
|||
from acme.magic_typing import List
|
||||
from acme.magic_typing import Optional # pylint: disable=unused-import
|
||||
from certbot import crypto_util
|
||||
from certbot.display import util as display_util
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
|
|
@ -40,7 +41,7 @@ logger = logging.getLogger(__name__)
|
|||
STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent",
|
||||
"server", "account", "authenticator", "installer",
|
||||
"renew_hook", "pre_hook", "post_hook", "http01_address",
|
||||
"preferred_chain"]
|
||||
"preferred_chain", "key_type", "elliptic_curve"]
|
||||
INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"]
|
||||
BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key",
|
||||
"autorenew"]
|
||||
|
|
@ -347,40 +348,42 @@ def report(msgs, category):
|
|||
|
||||
def _renew_describe_results(config, renew_successes, renew_failures,
|
||||
renew_skipped, parse_failures):
|
||||
# type: (interfaces.IConfig, List[str], List[str], List[str], List[str]) -> None
|
||||
"""
|
||||
Print a report to the terminal about the results of the renewal process.
|
||||
|
||||
out = [] # type: List[str]
|
||||
notify = out.append
|
||||
disp = zope.component.getUtility(interfaces.IDisplay)
|
||||
:param interfaces.IConfig config: Configuration
|
||||
:param list renew_successes: list of fullchain paths which were renewed
|
||||
:param list renew_failures: list of fullchain paths which failed to be renewed
|
||||
:param list renew_skipped: list of messages to print about skipped certificates
|
||||
:param list parse_failures: list of renewal parameter paths which had erorrs
|
||||
"""
|
||||
notify = display_util.notify
|
||||
notify_error = logger.error
|
||||
|
||||
def notify_error(err):
|
||||
"""Notify and log errors."""
|
||||
notify(str(err))
|
||||
logger.error(err)
|
||||
notify('\n{}'.format(display_util.SIDE_FRAME))
|
||||
|
||||
renewal_noun = "simulated renewal" if config.dry_run else "renewal"
|
||||
|
||||
if config.dry_run:
|
||||
notify("** DRY RUN: simulating 'certbot renew' close to cert expiry")
|
||||
notify("** (The test certificates below have not been saved.)")
|
||||
notify("")
|
||||
if renew_skipped:
|
||||
notify("The following certs are not due for renewal yet:")
|
||||
notify(report(renew_skipped, "skipped"))
|
||||
if not renew_successes and not renew_failures:
|
||||
notify("No renewals were attempted.")
|
||||
notify("No {renewal}s were attempted.".format(renewal=renewal_noun))
|
||||
if (config.pre_hook is not None or
|
||||
config.renew_hook is not None or config.post_hook is not None):
|
||||
notify("No hooks were run.")
|
||||
elif renew_successes and not renew_failures:
|
||||
notify("Congratulations, all renewals succeeded. The following certs "
|
||||
"have been renewed:")
|
||||
notify("Congratulations, all {renewal}s succeeded: ".format(renewal=renewal_noun))
|
||||
notify(report(renew_successes, "success"))
|
||||
elif renew_failures and not renew_successes:
|
||||
notify_error("All renewal attempts failed. The following certs could "
|
||||
"not be renewed:")
|
||||
notify_error("All %ss failed. The following certs could "
|
||||
"not be renewed:", renewal_noun)
|
||||
notify_error(report(renew_failures, "failure"))
|
||||
elif renew_failures and renew_successes:
|
||||
notify("The following certs were successfully renewed:")
|
||||
notify("The following {renewal}s succeeded:".format(renewal=renewal_noun))
|
||||
notify(report(renew_successes, "success") + "\n")
|
||||
notify_error("The following certs could not be renewed:")
|
||||
notify_error("The following %ss failed:", renewal_noun)
|
||||
notify_error(report(renew_failures, "failure"))
|
||||
|
||||
if parse_failures:
|
||||
|
|
@ -388,11 +391,7 @@ def _renew_describe_results(config, renew_successes, renew_failures,
|
|||
"were invalid: ")
|
||||
notify(report(parse_failures, "parsefail"))
|
||||
|
||||
if config.dry_run:
|
||||
notify("** DRY RUN: simulating 'certbot renew' close to cert expiry")
|
||||
notify("** (The test certificates above have not been saved.)")
|
||||
|
||||
disp.notification("\n".join(out), wrap=False)
|
||||
notify(display_util.SIDE_FRAME)
|
||||
|
||||
|
||||
def handle_renewal_request(config):
|
||||
|
|
@ -482,9 +481,10 @@ def handle_renewal_request(config):
|
|||
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
# obtain_cert (presumably) encountered an unanticipated problem.
|
||||
logger.warning("Attempting to renew cert (%s) from %s produced an "
|
||||
"unexpected error: %s. Skipping.", lineagename,
|
||||
renewal_file, e)
|
||||
logger.error(
|
||||
"Failed to renew cert %s with error: %s",
|
||||
lineagename, e
|
||||
)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
renew_failures.append(renewal_candidate.fullchain)
|
||||
|
||||
|
|
@ -506,6 +506,10 @@ def _update_renewal_params_from_key(key_path, config):
|
|||
with open(key_path, 'rb') as file_h:
|
||||
key = load_pem_private_key(file_h.read(), password=None, backend=default_backend())
|
||||
if isinstance(key, rsa.RSAPrivateKey):
|
||||
config.key_type = 'rsa'
|
||||
config.rsa_key_size = key.key_size
|
||||
elif isinstance(key, ec.EllipticCurvePrivateKey):
|
||||
config.key_type = 'ecdsa'
|
||||
config.elliptic_curve = key.curve.name
|
||||
else:
|
||||
raise errors.Error('Key at {0} is of an unsupported type: {1}.'.format(key_path, type(key)))
|
||||
|
|
|
|||
|
|
@ -49,22 +49,22 @@ def prepare_env(cli_args):
|
|||
os.environ['CERTBOT_AUGEAS_PATH'] = '{0}/usr/lib/{1}/libaugeas.so.0'.format(
|
||||
os.environ.get('SNAP'), _ARCH_TRIPLET_MAP[snap_arch])
|
||||
|
||||
session = Session()
|
||||
session.mount('http://snapd/', _SnapdAdapter())
|
||||
with Session() as session:
|
||||
session.mount('http://snapd/', _SnapdAdapter())
|
||||
|
||||
try:
|
||||
response = session.get('http://snapd/v2/connections?snap=certbot&interface=content')
|
||||
response.raise_for_status()
|
||||
except RequestException as e:
|
||||
if isinstance(e, HTTPError) and e.response.status_code == 404:
|
||||
LOGGER.error('An error occurred while fetching Certbot snap plugins: '
|
||||
'your version of snapd is outdated.')
|
||||
LOGGER.error('Please run "sudo snap install core; sudo snap refresh core" '
|
||||
'in your terminal and try again.')
|
||||
else:
|
||||
LOGGER.error('An error occurred while fetching Certbot snap plugins: '
|
||||
'make sure the snapd service is running.')
|
||||
raise e
|
||||
try:
|
||||
response = session.get('http://snapd/v2/connections?snap=certbot&interface=content')
|
||||
response.raise_for_status()
|
||||
except RequestException as e:
|
||||
if isinstance(e, HTTPError) and e.response.status_code == 404:
|
||||
LOGGER.error('An error occurred while fetching Certbot snap plugins: '
|
||||
'your version of snapd is outdated.')
|
||||
LOGGER.error('Please run "sudo snap install core; sudo snap refresh core" '
|
||||
'in your terminal and try again.')
|
||||
else:
|
||||
LOGGER.error('An error occurred while fetching Certbot snap plugins: '
|
||||
'make sure the snapd service is running.')
|
||||
raise e
|
||||
|
||||
data = response.json()
|
||||
connections = ['/snap/{0}/current/lib/python3.8/site-packages/'.format(item['slot']['snap'])
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import configobj
|
|||
import parsedatetime
|
||||
import pytz
|
||||
import six
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
import certbot
|
||||
from certbot import crypto_util
|
||||
|
|
@ -46,6 +49,7 @@ def renewal_conf_files(config):
|
|||
result.sort()
|
||||
return result
|
||||
|
||||
|
||||
def renewal_file_for_certname(config, certname):
|
||||
"""Return /path/to/certname.conf in the renewal conf directory"""
|
||||
path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname))
|
||||
|
|
@ -1055,6 +1059,23 @@ class RenewableCert(interfaces.RenewableCert):
|
|||
target, values)
|
||||
return cls(new_config.filename, cli_config)
|
||||
|
||||
@property
|
||||
def private_key_type(self):
|
||||
"""
|
||||
:returns: The type of algorithm for the private, RSA or ECDSA
|
||||
:rtype: str
|
||||
"""
|
||||
with open(self.configuration["privkey"], "rb") as priv_key_file:
|
||||
key = load_pem_private_key(
|
||||
data=priv_key_file.read(),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
if isinstance(key, RSAPrivateKey):
|
||||
return "RSA"
|
||||
else:
|
||||
return "ECDSA"
|
||||
|
||||
def save_successor(self, prior_version, new_cert,
|
||||
new_privkey, new_chain, cli_config):
|
||||
"""Save new cert and chain as a successor of a prior version.
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ import warnings
|
|||
import re
|
||||
# See https://github.com/pyca/cryptography/issues/4275
|
||||
from cryptography import x509 # type: ignore
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA, EllipticCurvePublicKey
|
||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat
|
||||
from OpenSSL import crypto
|
||||
from OpenSSL import SSL # type: ignore
|
||||
|
||||
import pyrfc3339
|
||||
import six
|
||||
import zope.component
|
||||
|
|
@ -34,7 +36,9 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
# High level functions
|
||||
def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
||||
def init_save_key(
|
||||
key_size, key_dir, key_type="rsa", elliptic_curve="secp256r1", keyname="key-certbot.pem"
|
||||
):
|
||||
"""Initializes and saves a privkey.
|
||||
|
||||
Inits key and saves it in PEM format on the filesystem.
|
||||
|
|
@ -42,8 +46,10 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
|||
.. note:: keyname is the attempted filename, it may be different if a file
|
||||
already exists at the path.
|
||||
|
||||
:param int key_size: RSA key size in bits
|
||||
:param int key_size: key size in bits if key size is rsa.
|
||||
:param str key_dir: Key save directory.
|
||||
:param str key_type: Key Type [rsa, ecdsa]
|
||||
:param str elliptic_curve: Name of the elliptic curve if key type is ecdsa.
|
||||
:param str keyname: Filename of key
|
||||
|
||||
:returns: Key
|
||||
|
|
@ -53,7 +59,9 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
|||
|
||||
"""
|
||||
try:
|
||||
key_pem = make_key(key_size)
|
||||
key_pem = make_key(
|
||||
bits=key_size, elliptic_curve=elliptic_curve or "secp256r1", key_type=key_type,
|
||||
)
|
||||
except ValueError as err:
|
||||
logger.error("", exc_info=True)
|
||||
raise err
|
||||
|
|
@ -65,7 +73,10 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
|||
os.path.join(key_dir, keyname), 0o600, "wb")
|
||||
with key_f:
|
||||
key_f.write(key_pem)
|
||||
logger.debug("Generating key (%d bits): %s", key_size, key_path)
|
||||
if key_type == 'rsa':
|
||||
logger.debug("Generating RSA key (%d bits): %s", key_size, key_path)
|
||||
else:
|
||||
logger.debug("Generating ECDSA key (%d bits): %s", key_size, key_path)
|
||||
|
||||
return util.Key(key_path, key_pem)
|
||||
|
||||
|
|
@ -174,18 +185,45 @@ def import_csr_file(csrfile, data):
|
|||
return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains
|
||||
|
||||
|
||||
def make_key(bits):
|
||||
"""Generate PEM encoded RSA key.
|
||||
def make_key(bits=1024, key_type="rsa", elliptic_curve=None):
|
||||
"""Generate PEM encoded RSA|EC key.
|
||||
|
||||
:param int bits: Number of bits, at least 1024.
|
||||
:param int bits: Number of bits if key_type=rsa. At least 1024 for RSA.
|
||||
|
||||
:returns: new RSA key in PEM form with specified number of bits
|
||||
:param str ec_curve: The elliptic curve to use.
|
||||
|
||||
:returns: new RSA or ECDSA key in PEM form with specified number of bits
|
||||
or of type ec_curve when key_type ecdsa is used.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
assert bits >= 1024 # XXX
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, bits)
|
||||
if key_type == 'rsa':
|
||||
if bits < 1024:
|
||||
raise errors.Error("Unsupported RSA key length: {}".format(bits))
|
||||
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, bits)
|
||||
elif key_type == 'ecdsa':
|
||||
try:
|
||||
name = elliptic_curve.upper()
|
||||
if name in ('SECP256R1', 'SECP384R1', 'SECP512R1'):
|
||||
_key = ec.generate_private_key(
|
||||
curve=getattr(ec, elliptic_curve.upper(), None)(),
|
||||
backend=default_backend()
|
||||
)
|
||||
else:
|
||||
raise errors.Error("Unsupported elliptic curve: {}".format(elliptic_curve))
|
||||
except TypeError:
|
||||
raise errors.Error("Unsupported elliptic curve: {}".format(elliptic_curve))
|
||||
except UnsupportedAlgorithm as e:
|
||||
raise six.raise_from(e, errors.Error(str(e)))
|
||||
_key_pem = _key.private_bytes(
|
||||
encoding=Encoding.PEM,
|
||||
format=PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=NoEncryption()
|
||||
)
|
||||
key = crypto.load_privatekey(crypto.FILETYPE_PEM, _key_pem)
|
||||
else:
|
||||
raise errors.Error("Invalid key_type specified: {}. Use [rsa|ecdsa]".format(key_type))
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
|
||||
|
||||
|
||||
|
|
@ -447,9 +485,8 @@ def _notAfterBefore(cert_path, method):
|
|||
|
||||
"""
|
||||
# pylint: disable=redefined-outer-name
|
||||
with open(cert_path) as f:
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
|
||||
f.read())
|
||||
with open(cert_path, "rb") as f: # type: IO[bytes]
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
|
||||
# pyopenssl always returns bytes
|
||||
timestamp = method(x509)
|
||||
reformatted_timestamp = [timestamp[0:4], b"-", timestamp[4:6], b"-",
|
||||
|
|
@ -520,6 +557,7 @@ def cert_and_chain_from_fullchain(fullchain_pem):
|
|||
# Since each normalized cert has a newline suffix, no extra newlines are required.
|
||||
return (certs_normalized[0], "".join(certs_normalized[1:]))
|
||||
|
||||
|
||||
def get_serial_from_cert(cert_path):
|
||||
"""Retrieve the serial number of a certificate from certificate path
|
||||
|
||||
|
|
@ -529,9 +567,8 @@ def get_serial_from_cert(cert_path):
|
|||
:rtype: int
|
||||
"""
|
||||
# pylint: disable=redefined-outer-name
|
||||
with open(cert_path) as f:
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
|
||||
f.read())
|
||||
with open(cert_path, "rb") as f: # type: IO[bytes]
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
|
||||
return x509.get_serial_number()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import textwrap
|
|||
import zope.interface
|
||||
import zope.component
|
||||
|
||||
from acme.magic_typing import List
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot._internal import constants
|
||||
|
|
@ -105,7 +106,7 @@ def notify(msg):
|
|||
|
||||
"""
|
||||
zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
msg, pause=False, decorate=False
|
||||
msg, pause=False, decorate=False, wrap=False
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -633,3 +634,28 @@ def _parens_around_char(label):
|
|||
|
||||
"""
|
||||
return "({first}){rest}".format(first=label[0], rest=label[1:])
|
||||
|
||||
|
||||
def summarize_domain_list(domains):
|
||||
# type: (List[str]) -> str
|
||||
"""Summarizes a list of domains in the format of:
|
||||
example.com.com and N more domains
|
||||
or if there is are only two domains:
|
||||
example.com and www.example.com
|
||||
or if there is only one domain:
|
||||
example.com
|
||||
|
||||
:param list domains: `str` list of domains
|
||||
:returns: the domain list summary
|
||||
:rtype: str
|
||||
"""
|
||||
if not domains:
|
||||
return ""
|
||||
|
||||
l = len(domains)
|
||||
if l == 1:
|
||||
return domains[0]
|
||||
elif l == 2:
|
||||
return " and ".join(domains)
|
||||
else:
|
||||
return "{0} and {1} more domains".format(domains[0], l-1)
|
||||
|
|
|
|||
|
|
@ -198,6 +198,13 @@ class IConfig(zope.interface.Interface):
|
|||
"register multiple emails, ex: u1@example.com,u2@example.com. "
|
||||
"(default: Ask).")
|
||||
rsa_key_size = zope.interface.Attribute("Size of the RSA key.")
|
||||
elliptic_curve = zope.interface.Attribute(
|
||||
"The SECG elliptic curve name to use. Please see RFC 8446 "
|
||||
"for supported values."
|
||||
)
|
||||
key_type = zope.interface.Attribute(
|
||||
"Type of generated private key"
|
||||
"(Only *ONE* per invocation can be provided at this time)")
|
||||
must_staple = zope.interface.Attribute(
|
||||
"Adds the OCSP Must Staple extension to the certificate. "
|
||||
"Autoconfigures OCSP Stapling for supported setups "
|
||||
|
|
@ -260,6 +267,7 @@ class IConfig(zope.interface.Interface):
|
|||
"offered chain will be used."
|
||||
)
|
||||
|
||||
|
||||
class IInstaller(IPlugin):
|
||||
"""Generic Certbot Installer Interface.
|
||||
|
||||
|
|
|
|||
8
certbot/certbot/tests/testdata/README
vendored
8
certbot/certbot/tests/testdata/README
vendored
|
|
@ -2,10 +2,16 @@ The following command has been used to generate test keys:
|
|||
|
||||
for x in 256 512 2048; do openssl genrsa -out rsa${k}_key.pem $k; done
|
||||
|
||||
For the elliptic curve private keys, this command was used:
|
||||
|
||||
for k in "prime256v1" "secp384r1" "secp521r1" do
|
||||
openssl genpkey -algorithm ${k} -out ec_${k}_key.pem
|
||||
done
|
||||
|
||||
and for the CSR PEM (Certificate Signing Request):
|
||||
|
||||
openssl req -new -out csr-Xsans_X.pem -key rsa512_key.pem [-config csr-Xsans_X.conf | -subj '/CN=example.com'] [-outform DER > csr_X.der]
|
||||
|
||||
and for the certificate:
|
||||
|
||||
openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der]
|
||||
openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der]
|
||||
|
|
|
|||
8
certbot/certbot/tests/testdata/ec_prime256v1_key.pem
vendored
Normal file
8
certbot/certbot/tests/testdata/ec_prime256v1_key.pem
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN EC PARAMETERS-----
|
||||
BggqhkjOPQMBBw==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIDqQPQl69kuh+DrecC8SFPt21f0F/HHDP3T4/Lf0zIVFoAoGCCqGSM49
|
||||
AwEHoUQDQgAEHou50Ee9u+8Vial6VbUHExlzsiCHtORlW0X0pKo5RspIKB0QyKwo
|
||||
dUXvBbv95I9yCO5+MlGkKjwLHtIEze0Hww==
|
||||
-----END EC PRIVATE KEY-----
|
||||
9
certbot/certbot/tests/testdata/ec_secp384r1_key.pem
vendored
Normal file
9
certbot/certbot/tests/testdata/ec_secp384r1_key.pem
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQAIg==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDAgvZGw5C7Mp26N0cXA+vIg5K/J5MJw+MVGYfGF4ZutuCLeYMrWT68R
|
||||
A0h6hJvDtMSgBwYFK4EEACKhZANiAAR1uQYZeU5Kml5o53Q8/PCdwUbqdgCSkV0C
|
||||
J5a6bhDRMp20fdp2T/mbkdxuVEl81lqfKPZhsd4CZsLaVIU3RUoGgIT1R3QKawpJ
|
||||
SuXq37yWFX2hqlgt+lsBufZ8RD5QnZc=
|
||||
-----END EC PRIVATE KEY-----
|
||||
10
certbot/certbot/tests/testdata/ec_secp521r1_key.pem
vendored
Normal file
10
certbot/certbot/tests/testdata/ec_secp521r1_key.pem
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQAIw==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIACWWVKm1qAIejZ6qmqk9D69wQW5FAe3Er0IxWAMkonTEhu8EH5Q2i
|
||||
2vT2bESm730zhGTe2Pn11b85H6UI9hxhCHygBwYFK4EEACOhgYkDgYYABAEQi1WF
|
||||
m3suHjPyWACyOJYGUn1Kx6rfBo0PjC7X2TU9jr8umLkIpaaF5UsBuMBmdz1IHL0U
|
||||
k0gQtoOQ0Qu8N74GuAGzGR0S3RYIv6gfYVz3dS1K4n4b307Lx62bnvtlNxcIvt3w
|
||||
hmS5OdvQ1Kdxh6oqbSVhhbQmJcgab78Txx3R2QeCxw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
18
certbot/certbot/tests/testdata/sample-archive-ec/cert1.pem
vendored
Normal file
18
certbot/certbot/tests/testdata/sample-archive-ec/cert1.pem
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIIBvrEnbPRYu8wDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjEwNzQw
|
||||
WhcNMjUxMDEyMjEwNzQwWjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARjMhuW0ENPPC33PjB5XsYU
|
||||
CRw640kPQENIDatcTJaENZIZdqKd6rI6jc+lpbmXot7Zi52clJlSJS+V6oDAt2Lh
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUj7Kd3ENqxlPf8B2bIGhsjydX
|
||||
mPswHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCl
|
||||
k0JXsa8y7fg41WWMDhw60bPW77O0FtOmTcnhdI5daYNemQVk+Q5EMaBLQ/oGjgXd
|
||||
9QXFzXH1PL904YEnSLt+iTpXn++7rQSNzQsdYqw0neWk4f5pEBiN+WORpb6mwobV
|
||||
ifMtBOkNEHvrJ2Pkci9U1lLwtKD/DSew6QtJU5DSkmH1XdGuMJiubygEIvELtvgq
|
||||
cP9S368ZvPmPGmKaJQXBiuaR8MTjY/Bkr79aXQMjKbf+mpn7h0POCcePk1DY/rm6
|
||||
Da+X16lf0hHyQhSUa7Vgyim6rK1/hlw+Z00i+sQCKD9Ih7kXuuGqfSDC33cfO8Tj
|
||||
o/MXO8lcxkrem5zU5QWP
|
||||
-----END CERTIFICATE-----
|
||||
20
certbot/certbot/tests/testdata/sample-archive-ec/chain1.pem
vendored
Normal file
20
certbot/certbot/tests/testdata/sample-archive-ec/chain1.pem
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
38
certbot/certbot/tests/testdata/sample-archive-ec/fullchain1.pem
vendored
Normal file
38
certbot/certbot/tests/testdata/sample-archive-ec/fullchain1.pem
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIILlmGtZhUFEwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjA1MDM0
|
||||
WhcNMjUxMDEyMjA1MDM0WjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARHEzR8JPWrEmpmgM+F2bk5
|
||||
9mT0u6CjzmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU1CsVL+bPnzaxxQ5jUENmQJIO
|
||||
lKwwHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBn
|
||||
2D8loC7pfk28JYpFLr5lmFKJWWmtLGlpsWDj61fVjtTfGKLziJz+MM6il4Y3hIz5
|
||||
58qiFK0ue0M63dIBJ33N+XxSEXon4Q0gy/zRWfH9jtPJ3FwfjkU/RT9PAUClYi0G
|
||||
ptNWnTmgQkNzousbcAtRNXuuShH3856vhUnwkX+xM+cbIDi1JVmFjcGrEEQJ0rUF
|
||||
mv2ZTyfbWbUs3v4rReETi2NVzr1Ql6J+ByNcMvHODzFy3t0L6yelAw2ca1I+c9HU
|
||||
+Z0tnp/ykR7eXNuVLivok8UBf5OC413lh8ZO5g+Bgzh/LdtkUuavg1MYtEX0H6mX
|
||||
9U7y3nVI8WEbPGf+HDeu
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
5
certbot/certbot/tests/testdata/sample-archive-ec/privkey1.pem
vendored
Normal file
5
certbot/certbot/tests/testdata/sample-archive-ec/privkey1.pem
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNgefv2dad4U1VYEi
|
||||
0WkdHuqywi5QXAe30OwNTTGjhbihRANCAARHEzR8JPWrEmpmgM+F2bk59mT0u6Cj
|
||||
zmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
-----END PRIVATE KEY-----
|
||||
79
certbot/certbot/tests/testdata/sample-renewal-ec.conf
vendored
Normal file
79
certbot/certbot/tests/testdata/sample-renewal-ec.conf
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# add some stuff here
|
||||
# assets/integration_tests
|
||||
|
||||
cert = MAGICDIR/live/sample-renewal-ec/cert.pem
|
||||
privkey = MAGICDIR/live/sample-renewal-ec/privkey.pem
|
||||
chain = MAGICDIR/live/sample-renewal-ec/chain.pem
|
||||
fullchain = MAGICDIR/live/sample-renewal-ec/fullchain.pem
|
||||
renew_before_expiry = 4 years
|
||||
|
||||
# Options and defaults used in the renewal process
|
||||
[renewalparams]
|
||||
no_self_upgrade = False
|
||||
apache_enmod = a2enmod
|
||||
no_verify_ssl = False
|
||||
ifaces = None
|
||||
apache_dismod = a2dismod
|
||||
register_unsafely_without_email = False
|
||||
apache_handle_modules = True
|
||||
uir = None
|
||||
installer = None
|
||||
nginx_ctl = nginx
|
||||
config_dir = MAGICDIR
|
||||
text_mode = False
|
||||
func = <function obtain_cert at 0x7f093a163c08>
|
||||
staging = True
|
||||
prepare = False
|
||||
work_dir = /var/lib/letsencrypt
|
||||
tos = False
|
||||
init = False
|
||||
http01_port = 80
|
||||
duplicate = False
|
||||
noninteractive_mode = True
|
||||
key_path = None
|
||||
nginx = False
|
||||
nginx_server_root = /etc/nginx
|
||||
fullchain_path = /home/ubuntu/letsencrypt/chain.pem
|
||||
email = None
|
||||
csr = None
|
||||
agree_dev_preview = None
|
||||
redirect = None
|
||||
verb = certonly
|
||||
verbose_count = -3
|
||||
config_file = None
|
||||
renew_by_default = False
|
||||
hsts = False
|
||||
apache_handle_sites = True
|
||||
authenticator = standalone
|
||||
domains = isnot.org,
|
||||
key_type = ecdsa
|
||||
elliptic_curve = secp256r1
|
||||
apache_challenge_location = /etc/apache2
|
||||
checkpoints = 1
|
||||
manual_test_mode = False
|
||||
apache = False
|
||||
cert_path = /home/ubuntu/letsencrypt/cert.pem
|
||||
webroot_path = None
|
||||
reinstall = False
|
||||
expand = False
|
||||
strict_permissions = False
|
||||
apache_server_root = /etc/apache2
|
||||
account = None
|
||||
dry_run = False
|
||||
manual_public_ip_logging_ok = False
|
||||
chain_path = /home/ubuntu/letsencrypt/chain.pem
|
||||
break_my_certs = False
|
||||
standalone = True
|
||||
manual = False
|
||||
server = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
webroot = False
|
||||
os_packages_only = False
|
||||
apache_init_script = None
|
||||
user_agent = None
|
||||
apache_le_vhost_ext = -le-ssl.conf
|
||||
debug = False
|
||||
logs_dir = /var/log/letsencrypt
|
||||
apache_vhost_root = /etc/apache2/sites-available
|
||||
configurator = None
|
||||
must_staple = True
|
||||
[[webroot_map]]
|
||||
|
|
@ -44,6 +44,7 @@ apache_handle_sites = True
|
|||
authenticator = standalone
|
||||
domains = isnot.org,
|
||||
rsa_key_size = 2048
|
||||
elliptic_curve = secp256r1
|
||||
apache_challenge_location = /etc/apache2
|
||||
checkpoints = 1
|
||||
manual_test_mode = False
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ def load_pyopenssl_private_key(*names):
|
|||
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
|
||||
|
||||
|
||||
def make_lineage(config_dir, testfile):
|
||||
def make_lineage(config_dir, testfile, ec=False):
|
||||
"""Creates a lineage defined by testfile.
|
||||
|
||||
This creates the archive, live, and renewal directories if
|
||||
|
|
@ -119,7 +119,7 @@ def make_lineage(config_dir, testfile):
|
|||
if not os.path.exists(directory):
|
||||
filesystem.makedirs(directory)
|
||||
|
||||
sample_archive = vector_path('sample-archive')
|
||||
sample_archive = vector_path('sample-archive{}'.format('-ec' if ec else ''))
|
||||
for kind in os.listdir(sample_archive):
|
||||
shutil.copyfile(os.path.join(sample_archive, kind),
|
||||
os.path.join(archive_dir, kind))
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ def safe_email(email):
|
|||
return False
|
||||
|
||||
|
||||
class _ShowWarning(argparse.Action):
|
||||
class DeprecatedArgumentAction(argparse.Action):
|
||||
"""Action to log a warning when an argument is used."""
|
||||
def __call__(self, unused1, unused2, unused3, option_string=None):
|
||||
logger.warning("Use of %s is deprecated.", option_string)
|
||||
|
|
@ -458,16 +458,16 @@ def add_deprecated_argument(add_argument, argument_name, nargs):
|
|||
:param nargs: Value for nargs when adding the argument to argparse.
|
||||
|
||||
"""
|
||||
if _ShowWarning not in configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE:
|
||||
if DeprecatedArgumentAction not in configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE:
|
||||
# In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was
|
||||
# changed from a set to a tuple.
|
||||
if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set):
|
||||
configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add(
|
||||
_ShowWarning)
|
||||
DeprecatedArgumentAction)
|
||||
else:
|
||||
configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE += (
|
||||
_ShowWarning,)
|
||||
add_argument(argument_name, action=_ShowWarning,
|
||||
DeprecatedArgumentAction,)
|
||||
add_argument(argument_name, action=DeprecatedArgumentAction,
|
||||
help=argparse.SUPPRESS, nargs=nargs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ its preferences in accordance with its own policy or its administrators'
|
|||
preferences, and use different cryptographic mechanisms or parameters,
|
||||
or a different priority order, than the defaults provided by Certbot.
|
||||
|
||||
If you don't use Certbot to configure your server directly, because the
|
||||
client doesn't integrate with your server software or because you chose
|
||||
If you don't use Certbot to configure your server directly, because the
|
||||
client doesn't integrate with your server software or because you chose
|
||||
not to use this integration, then the cryptographic defaults haven't been
|
||||
modified, and the cryptography chosen by the server will still be whatever
|
||||
the default for your software was. For example, if you obtain a
|
||||
|
|
@ -254,7 +254,7 @@ https://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-secur
|
|||
U.S. Government 18F
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The 18F site (https://18f.gsa.gov/) is using
|
||||
The 18F site (https://18f.gsa.gov/) is using
|
||||
|
||||
::
|
||||
|
||||
|
|
|
|||
|
|
@ -118,12 +118,12 @@ optional arguments:
|
|||
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/1.9.0 (certbot(-auto);
|
||||
OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY
|
||||
(SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).
|
||||
The flags encoded in the user agent are: --duplicate,
|
||||
--force-renew, --allow-subset-of-names, -n, and
|
||||
whether any hooks are set.
|
||||
"". (default: CertbotACMEClient/1.10.1
|
||||
(certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX
|
||||
Installer/YYY (SUBCOMMAND; flags: FLAGS)
|
||||
Py/major.minor.patchlevel). The flags encoded in the
|
||||
user agent are: --duplicate, --force-renew, --allow-
|
||||
subset-of-names, -n, and whether any hooks are set.
|
||||
--user-agent-comment USER_AGENT_COMMENT
|
||||
Add a comment to the default user agent string. May be
|
||||
used when repackaging Certbot or calling it from
|
||||
|
|
@ -188,6 +188,12 @@ security:
|
|||
Security parameters & server settings
|
||||
|
||||
--rsa-key-size N Size of the RSA key. (default: 2048)
|
||||
--key-type {rsa,ecdsa}
|
||||
Type of generated private key(Only *ONE* per
|
||||
invocation can be provided at this time) (default:
|
||||
rsa)
|
||||
--elliptic-curve N The SECG elliptic curve name to use. Please see RFC
|
||||
8446 for supported values. (default: secp256r1)
|
||||
--must-staple Adds the OCSP Must Staple extension to the
|
||||
certificate. Autoconfigures OCSP Stapling for
|
||||
supported setups (Apache version >= 2.3.3 ). (default:
|
||||
|
|
@ -688,8 +694,6 @@ manual:
|
|||
--manual-cleanup-hook MANUAL_CLEANUP_HOOK
|
||||
Path or command to execute for the cleanup script
|
||||
(default: None)
|
||||
--manual-public-ip-logging-ok
|
||||
Automatically allows public IP logging (default: Ask)
|
||||
|
||||
nginx:
|
||||
Nginx Web Server plugin
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ To do so you need:
|
|||
- Docker installed, and a user with access to the Docker client,
|
||||
- an available `local copy`_ of Certbot.
|
||||
|
||||
The virtual environment set up with `python tools/venv3.py` contains two commands
|
||||
The virtual environment set up with `python tools/venv3.py` contains two CLI tools
|
||||
that can be used once the virtual environment is activated:
|
||||
|
||||
.. code-block:: shell
|
||||
|
|
@ -180,6 +180,9 @@ that can be used once the virtual environment is activated:
|
|||
- Press CTRL+C to stop this instance.
|
||||
- This instance is configured to validate challenges against certbot executed locally.
|
||||
|
||||
.. note:: Some options are available to tweak the local ACME server. You can execute
|
||||
``run_acme_server --help`` to see the inline help of the ``run_acme_server`` tool.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
certbot_test [ARGS...]
|
||||
|
|
@ -431,16 +434,23 @@ Please:
|
|||
|
||||
4. Remember to use ``pylint``.
|
||||
|
||||
5. You may consider installing a plugin for `editorconfig`_ in
|
||||
your editor to prevent some linting warnings.
|
||||
|
||||
6. Please avoid `unittest.assertTrue` or `unittest.assertFalse` when
|
||||
possible, and use `assertEqual` or more specific assert. They give
|
||||
better messages when it's failing, and are generally more correct.
|
||||
|
||||
.. _Google Python Style Guide:
|
||||
https://google.github.io/styleguide/pyguide.html
|
||||
.. _Sphinx-style: https://www.sphinx-doc.org/
|
||||
.. _PEP 8 - Style Guide for Python Code:
|
||||
https://www.python.org/dev/peps/pep-0008
|
||||
.. _editorconfig: https://editorconfig.org/
|
||||
|
||||
Use ``certbot.compat.os`` instead of ``os``
|
||||
===========================================
|
||||
|
||||
|
||||
Python's standard library ``os`` module lacks full support for several Windows
|
||||
security features about file permissions (eg. DACLs). However several files
|
||||
handled by Certbot (eg. private keys) need strongly restricted access
|
||||
|
|
@ -506,11 +516,13 @@ Steps:
|
|||
4. Run ``tox --skip-missing-interpreters`` to run the entire test suite
|
||||
including coverage. The ``--skip-missing-interpreters`` argument ignores
|
||||
missing versions of Python needed for running the tests. Fix any errors.
|
||||
5. Submit the PR. Once your PR is open, please do not force push to the branch
|
||||
5. If any documentation should be added or updated as part of the changes you
|
||||
have made, please include the documentation changes in your PR.
|
||||
6. Submit the PR. Once your PR is open, please do not force push to the branch
|
||||
containing your pull request to squash or amend commits. We use `squash
|
||||
merges <https://github.com/blog/2141-squash-your-commits>`_ on PRs and
|
||||
rewriting commits makes changes harder to track between reviews.
|
||||
6. Did your tests pass on Azure Pipelines? If they didn't, fix any errors.
|
||||
7. Did your tests pass on Azure Pipelines? If they didn't, fix any errors.
|
||||
|
||||
.. _ask for help:
|
||||
|
||||
|
|
|
|||
|
|
@ -319,6 +319,7 @@ This returns information in the following format::
|
|||
Domains: example.com, www.example.com
|
||||
Expiry Date: 2017-02-19 19:53:00+00:00 (VALID: 30 days)
|
||||
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
|
||||
Key Type: RSA
|
||||
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
|
||||
|
||||
``Certificate Name`` shows the name of the certificate. Pass this name
|
||||
|
|
@ -346,7 +347,6 @@ control Certbot's behavior when re-creating
|
|||
a certificate with the same name as an existing certificate.
|
||||
If you don't specify a requested behavior, Certbot may ask you what you intended.
|
||||
|
||||
|
||||
``--force-renewal`` tells Certbot to request a new certificate
|
||||
with the same domains as an existing certificate. Each domain
|
||||
must be explicitly specified via ``-d``. If successful, this certificate
|
||||
|
|
@ -380,7 +380,6 @@ If you prefer, you can specify the domains individually like this:
|
|||
Consider using ``--cert-name`` instead of ``--expand``, as it gives more control
|
||||
over which certificate is modified and it lets you remove domains as well as adding them.
|
||||
|
||||
|
||||
``--allow-subset-of-names`` tells Certbot to continue with certificate generation if
|
||||
only some of the specified domain authorizations can be obtained. This may
|
||||
be useful if some domains specified in a certificate no longer point at this
|
||||
|
|
@ -411,6 +410,68 @@ replace that set entirely::
|
|||
certbot certonly --cert-name example.com -d example.org,www.example.org
|
||||
|
||||
|
||||
Using ECDSA keys
|
||||
----------------
|
||||
|
||||
As of version 1.10, Certbot supports two types of private key algorithms:
|
||||
``rsa`` and ``ecdsa``. The type of key used by Certbot can be controlled
|
||||
through the ``--key-type`` option. You can also use the ``--elliptic-curve``
|
||||
option to control the curve used in ECDSA certificates.
|
||||
|
||||
.. warning:: If you obtain certificates using ECDSA keys, you should be careful
|
||||
not to downgrade your Certbot installation since ECDSA keys are not
|
||||
supported by older versions of Certbot. Downgrades like this are possible if
|
||||
you switch from something like the snaps or certbot-auto to packages
|
||||
provided by your operating system which often lag behind.
|
||||
|
||||
Changing existing certificates from RSA to ECDSA
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Unless you are aware that you need to support very old HTTPS clients that are
|
||||
not supported by most sites, you can safely just transition your site to use
|
||||
ECDSA keys instead of RSA keys. To accomplish this if you have existing
|
||||
certificates managed by Certbot, you may freely change the certificate to a new
|
||||
private key.
|
||||
|
||||
If you want to use ECDSA keys for all certificates in the future, you can
|
||||
simply add the following line to Certbot's :ref:`configuration file <config-file>`
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
key-type = ecdsa
|
||||
|
||||
After this option is set, newly obtained certificates will use ECDSA keys. This
|
||||
includes certificates managed by Certbot that previously used RSA keys.
|
||||
|
||||
If you want to change a single certificate to use ECDSA keys, you'll need to
|
||||
issue a new Certbot command setting ``--key-type ecdsa`` on the command line
|
||||
like
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
certbot renew --key-type ecdsa --cert-name example.com --force-renewal
|
||||
|
||||
Obtaining ECDSA certificates in addition to RSA certificates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When Certbot configures the certificates it obtains with Apache or Nginx, all
|
||||
HTTPS clients that we try to support can use certificates with ECDSA keys. If,
|
||||
however, you are aware of having a specific need to support very old TLS
|
||||
clients, you may want to obtain both ECDSA and RSA certificates for the same
|
||||
domains. Certbot can only configure Apache or Nginx to use a single
|
||||
certificate, however, you could manually configure your software to use the
|
||||
different certificates depending on your needs.
|
||||
|
||||
When obtaining both ECDSA and RSA certificates for the same domains with
|
||||
Certbot, we recommend using the ``--cert-name`` option to give your
|
||||
certificates names so that you can easily identify them. For instance, you may
|
||||
want to append "ecdsa" to the name of your ECDSA certificate by using a command
|
||||
like
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
certbot certonly --key-type ecdsa --cert-name example.com-ecdsa
|
||||
|
||||
Revoking certificates
|
||||
---------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
# certificate on a system with several certificates should not be placed
|
||||
# here.
|
||||
|
||||
# Use ECC for the private key
|
||||
key-type = ecdsa
|
||||
elliptic-curve = secp384r1
|
||||
|
||||
# Use a 4096 bit RSA key instead of 2048
|
||||
rsa-key-size = 4096
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue